]> git.ipfire.org Git - thirdparty/squid.git/blame - src/external_acl.cc
Revert r14303: Migrate StoreEntry to using MEMPROXY_CLASS
[thirdparty/squid.git] / src / external_acl.cc
CommitLineData
d9572179 1/*
ef57eb7b 2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
d9572179 3 *
bbc27441
AJ
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.
d9572179 7 */
8
bbc27441
AJ
9/* DEBUG: section 82 External ACL */
10
582c2af2 11#include "squid.h"
c0941a6a 12#include "acl/Acl.h"
582c2af2 13#include "acl/FilledChecklist.h"
8a01b99e 14#include "cache_cf.h"
a46d2c0e 15#include "client_side.h"
4e56d7f6 16#include "client_side_request.h"
5c336a3b 17#include "comm/Connection.h"
2eceb328 18#include "ConfigParser.h"
582c2af2 19#include "ExternalACL.h"
f9b6ff6e 20#include "ExternalACLEntry.h"
582c2af2 21#include "fde.h"
4e56d7f6 22#include "format/Token.h"
aa839030 23#include "helper.h"
24438ec5 24#include "helper/Reply.h"
d3dddfb5 25#include "http/Stream.h"
a5bac1d2 26#include "HttpHeaderTools.h"
582c2af2
FC
27#include "HttpReply.h"
28#include "HttpRequest.h"
29#include "ip/tools.h"
0eb49b6d 30#include "MemBuf.h"
582c2af2 31#include "mgr/Registration.h"
1fa9b1a7 32#include "rfc1738.h"
4d5904f7 33#include "SquidConfig.h"
f9b6ff6e 34#include "SquidString.h"
582c2af2
FC
35#include "SquidTime.h"
36#include "Store.h"
4e540555 37#include "tools.h"
b1bd952a 38#include "URL.h"
d295d770 39#include "wordlist.h"
cb4f4424 40#if USE_OPENSSL
cedca6e7 41#include "ssl/ServerBump.h"
4db984be
CT
42#include "ssl/support.h"
43#endif
582c2af2
FC
44#if USE_AUTH
45#include "auth/Acl.h"
46#include "auth/Gadgets.h"
47#include "auth/UserRequest.h"
48#endif
49#if USE_IDENT
50#include "ident/AclIdent.h"
51#endif
d9572179 52
53#ifndef DEFAULT_EXTERNAL_ACL_TTL
54#define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
55#endif
d4f2f353 56#ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
57#define DEFAULT_EXTERNAL_ACL_CHILDREN 5
d9572179 58#endif
59
c0941a6a 60static char *makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data);
abdd93d0
AJ
61static void external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry);
62static int external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry);
63static int external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry);
64static void external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry);
65static ExternalACLEntryPointer external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
d9572179 66
67/******************************************************************
68 * external_acl directive
69 */
62e76326 70
1e5562e3 71class external_acl
62e76326 72{
f205a92e
SM
73 /* FIXME: These are not really cbdata, but it is an easy way
74 * to get them pooled, refcounted, accounted and freed properly...
75 */
f963b531 76 CBDATA_CLASS(external_acl);
1e5562e3 77
78public:
f963b531
AJ
79 external_acl();
80 ~external_acl();
81
d9572179 82 external_acl *next;
1e5562e3 83
abdd93d0 84 void add(const ExternalACLEntryPointer &);
1e5562e3 85
86 void trimCache();
87
d9572179 88 int ttl;
1e5562e3 89
d9572179 90 int negative_ttl;
1e5562e3 91
47b0c1fa 92 int grace;
93
d9572179 94 char *name;
1e5562e3 95
4e56d7f6 96 Format::Format format;
1e5562e3 97
d9572179 98 wordlist *cmdline;
1e5562e3 99
76d9b994 100 Helper::ChildConfig children;
07eca7e0 101
e6ccf245 102 helper *theHelper;
1e5562e3 103
d9572179 104 hash_table *cache;
1e5562e3 105
d9572179 106 dlink_list lru_list;
1e5562e3 107
d9572179 108 int cache_size;
1e5562e3 109
d9572179 110 int cache_entries;
1e5562e3 111
d9572179 112 dlink_list queue;
1e5562e3 113
2f1431ea 114#if USE_AUTH
3265364b
AJ
115 /**
116 * Configuration flag. May only be altered by the configuration parser.
117 *
118 * Indicates that all uses of this external_acl_type helper require authentication
119 * details to be processed. If none are available its a fail match.
120 */
e870b1f8 121 bool require_auth;
2f1431ea 122#endif
dc1af3cf 123
95d78f10 124 Format::Quoting quote; // default quoting to use, set by protocol= parameter
cc192b50 125
b7ac5457 126 Ip::Address local_addr;
d9572179 127};
128
f963b531
AJ
129CBDATA_CLASS_INIT(external_acl);
130
131external_acl::external_acl() :
cc8c4af2 132 next(NULL),
f963b531
AJ
133 ttl(DEFAULT_EXTERNAL_ACL_TTL),
134 negative_ttl(-1),
135 grace(1),
136 name(NULL),
4e56d7f6 137 format("external_acl_type"),
f963b531
AJ
138 cmdline(NULL),
139 children(DEFAULT_EXTERNAL_ACL_CHILDREN),
140 theHelper(NULL),
141 cache(NULL),
142 cache_size(256*1024),
143 cache_entries(0),
144#if USE_AUTH
145 require_auth(0),
146#endif
95d78f10 147 quote(Format::LOG_QUOTE_URL)
d9572179 148{
f963b531
AJ
149 local_addr.setLocalhost();
150}
62e76326 151
f963b531
AJ
152external_acl::~external_acl()
153{
154 xfree(name);
f963b531
AJ
155 wordlistDestroy(&cmdline);
156
157 if (theHelper) {
158 helperShutdown(theHelper);
159 delete theHelper;
160 theHelper = NULL;
d9572179 161 }
62e76326 162
f963b531
AJ
163 while (lru_list.tail) {
164 ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
165 external_acl_cache_delete(this, e);
166 }
167 if (cache)
168 hashFreeMemory(cache);
169
170 while (next) {
171 external_acl *node = next;
172 next = node->next;
173 node->next = NULL; // prevent recursion
174 delete node;
abdd93d0 175 }
d9572179 176}
177
178void
179parse_externalAclHelper(external_acl ** list)
180{
f963b531
AJ
181 external_acl *a = new external_acl;
182 char *token = ConfigParser::NextToken();
62e76326 183
d9572179 184 if (!token)
62e76326 185 self_destruct();
186
d9572179 187 a->name = xstrdup(token);
188
2eceb328
CT
189 // Allow supported %macros inside quoted tokens
190 ConfigParser::EnableMacros();
191 token = ConfigParser::NextToken();
62e76326 192
d9572179 193 /* Parse options */
194 while (token) {
62e76326 195 if (strncmp(token, "ttl=", 4) == 0) {
196 a->ttl = atoi(token + 4);
197 } else if (strncmp(token, "negative_ttl=", 13) == 0) {
198 a->negative_ttl = atoi(token + 13);
07eca7e0 199 } else if (strncmp(token, "children=", 9) == 0) {
48d54e4d 200 a->children.n_max = atoi(token + 9);
fa84c01d 201 debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
48d54e4d
AJ
202 } else if (strncmp(token, "children-max=", 13) == 0) {
203 a->children.n_max = atoi(token + 13);
204 } else if (strncmp(token, "children-startup=", 17) == 0) {
205 a->children.n_startup = atoi(token + 17);
206 } else if (strncmp(token, "children-idle=", 14) == 0) {
207 a->children.n_idle = atoi(token + 14);
07eca7e0 208 } else if (strncmp(token, "concurrency=", 12) == 0) {
48d54e4d 209 a->children.concurrency = atoi(token + 12);
6825b101
CT
210 } else if (strncmp(token, "queue-size=", 11) == 0) {
211 a->children.queue_size = atoi(token + 11);
212 a->children.defaultQueueSize = false;
62e76326 213 } else if (strncmp(token, "cache=", 6) == 0) {
214 a->cache_size = atoi(token + 6);
47b0c1fa 215 } else if (strncmp(token, "grace=", 6) == 0) {
216 a->grace = atoi(token + 6);
dc1af3cf 217 } else if (strcmp(token, "protocol=2.5") == 0) {
95d78f10 218 a->quote = Format::LOG_QUOTE_SHELL;
dc1af3cf 219 } else if (strcmp(token, "protocol=3.0") == 0) {
05e52854 220 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option protocol=3.0 is deprecated. Remove this from your config.");
95d78f10 221 a->quote = Format::LOG_QUOTE_URL;
dc1af3cf 222 } else if (strcmp(token, "quote=url") == 0) {
05e52854 223 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
95d78f10 224 a->quote = Format::LOG_QUOTE_URL;
dc1af3cf 225 } else if (strcmp(token, "quote=shell") == 0) {
05e52854 226 debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
95d78f10 227 a->quote = Format::LOG_QUOTE_SHELL;
cc192b50 228
26ac0430
AJ
229 /* INET6: allow admin to configure some helpers explicitly to
230 bind to IPv4/v6 localhost port. */
cc192b50 231 } else if (strcmp(token, "ipv4") == 0) {
4dd643d5 232 if ( !a->local_addr.setIPv4() ) {
fa84c01d 233 debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
cc192b50 234 }
235 } else if (strcmp(token, "ipv6") == 0) {
055421ee 236 if (!Ip::EnableIpv6)
fa84c01d 237 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
055421ee 238 // else nothing to do.
62e76326 239 } else {
240 break;
241 }
242
2eceb328 243 token = ConfigParser::NextToken();
d9572179 244 }
2eceb328 245 ConfigParser::DisableMacros();
62e76326 246
6f78f0ed 247 /* check that child startup value is sane. */
a3cecd6c
AJ
248 if (a->children.n_startup > a->children.n_max)
249 a->children.n_startup = a->children.n_max;
48d54e4d 250
6f78f0ed 251 /* check that child idle value is sane. */
a3cecd6c
AJ
252 if (a->children.n_idle > a->children.n_max)
253 a->children.n_idle = a->children.n_max;
254 if (a->children.n_idle < 1)
255 a->children.n_idle = 1;
48d54e4d 256
d9572179 257 if (a->negative_ttl == -1)
62e76326 258 a->negative_ttl = a->ttl;
d9572179 259
e44a3ec4 260 if (a->children.defaultQueueSize)
6825b101
CT
261 a->children.queue_size = 2 * a->children.n_max;
262
4e56d7f6
AJ
263 /* Legacy external_acl_type format parser.
264 * Handles a series of %... tokens where any non-% means
265 * the start of another parameter field (ie the path to binary).
266 */
267 enum Format::Quoting quote = Format::LOG_QUOTE_NONE;
268 Format::Token **fmt = &a->format.format;
712fa21e 269 bool data_used = false;
d9572179 270 while (token) {
4e56d7f6 271 /* stop on first non-% token found */
62e76326 272 if (*token != '%')
273 break;
274
4e56d7f6 275 *fmt = new Format::Token;
09e34608
AJ
276 // these tokens are whitespace delimited
277 (*fmt)->space = true;
4e56d7f6 278
3a80bdd7 279 // set the default encoding to match the protocol= config
95d78f10
AJ
280 // this will be overridden by explicit %macro attributes
281 (*fmt)->quote = a->quote;
282
4e56d7f6
AJ
283 // compatibility for old tokens incompatible with Format::Token syntax
284#if USE_OPENSSL // dont bother if we dont have to.
285 if (strncmp(token, "%USER_CERT_", 11) == 0) {
286 (*fmt)->type = Format::LFT_EXT_ACL_USER_CERT;
287 (*fmt)->data.string = xstrdup(token + 11);
288 (*fmt)->data.header.header = (*fmt)->data.string;
f06585e0 289 } else if (strncmp(token, "%USER_CA_CERT_", 14) == 0) {
4e56d7f6
AJ
290 (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
291 (*fmt)->data.string = xstrdup(token + 14);
292 (*fmt)->data.header.header = (*fmt)->data.string;
f06585e0 293 } else if (strncmp(token, "%CA_CERT_", 9) == 0) {
3650c90e 294 debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead");
4e56d7f6
AJ
295 (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
296 (*fmt)->data.string = xstrdup(token + 9);
297 (*fmt)->data.header.header = (*fmt)->data.string;
298 } else
a7ad6e4e 299#endif
4e56d7f6
AJ
300 {
301 // we can use the Format::Token::parse() method since it
302 // only pulls off one token. Since we already checked
303 // for '%' prefix above this is guaranteed to be a token.
304 const size_t len = (*fmt)->parse(token, &quote);
305 assert(len == strlen(token));
306 }
307
308 // process special token-specific actions (only if necessary)
2f1431ea 309#if USE_AUTH
4e56d7f6
AJ
310 if ((*fmt)->type == Format::LFT_USER_LOGIN)
311 a->require_auth = true;
2f1431ea 312#endif
62e76326 313
95d78f10
AJ
314 if ((*fmt)->type == Format::LFT_EXT_ACL_DATA)
315 data_used = true;
712fa21e 316
4e56d7f6 317 fmt = &((*fmt)->next);
2eceb328 318 token = ConfigParser::NextToken();
d9572179 319 }
320
321 /* There must be at least one format token */
4e56d7f6 322 if (!a->format.format)
62e76326 323 self_destruct();
d9572179 324
712fa21e
AJ
325 // format has implicit %DATA on the end if not used explicitly
326 if (!data_used) {
327 *fmt = new Format::Token;
328 (*fmt)->type = Format::LFT_EXT_ACL_DATA;
57c2fb88 329 (*fmt)->quote = Format::LOG_QUOTE_URL;
712fa21e
AJ
330 }
331
d9572179 332 /* helper */
333 if (!token)
62e76326 334 self_destruct();
335
d9572179 336 wordlistAdd(&a->cmdline, token);
337
338 /* arguments */
a336d130 339 parse_wordlist(&a->cmdline);
d9572179 340
341 while (*list)
62e76326 342 list = &(*list)->next;
343
d9572179 344 *list = a;
345}
346
347void
348dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
349{
350 const external_acl *node;
d9572179 351 const wordlist *word;
62e76326 352
d9572179 353 for (node = list; node; node = node->next) {
62e76326 354 storeAppendPrintf(sentry, "%s %s", name, node->name);
355
4dd643d5 356 if (!node->local_addr.isIPv6())
cc192b50 357 storeAppendPrintf(sentry, " ipv4");
358 else
359 storeAppendPrintf(sentry, " ipv6");
360
62e76326 361 if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL)
362 storeAppendPrintf(sentry, " ttl=%d", node->ttl);
363
364 if (node->negative_ttl != node->ttl)
365 storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
366
47b0c1fa 367 if (node->grace)
368 storeAppendPrintf(sentry, " grace=%d", node->grace);
369
48d54e4d
AJ
370 if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
371 storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
62e76326 372
48d54e4d
AJ
373 if (node->children.n_startup != 1)
374 storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
62e76326 375
48d54e4d
AJ
376 if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
377 storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
378
379 if (node->children.concurrency)
380 storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
07eca7e0 381
47b0c1fa 382 if (node->cache)
383 storeAppendPrintf(sentry, " cache=%d", node->cache_size);
384
95d78f10 385 if (node->quote == Format::LOG_QUOTE_SHELL)
05e52854
AJ
386 storeAppendPrintf(sentry, " protocol=2.5");
387
4e56d7f6 388 node->format.dump(sentry, NULL, false);
62e76326 389
390 for (word = node->cmdline; word; word = word->next)
391 storeAppendPrintf(sentry, " %s", word->key);
392
393 storeAppendPrintf(sentry, "\n");
d9572179 394 }
395}
396
397void
398free_externalAclHelper(external_acl ** list)
399{
f963b531
AJ
400 delete *list;
401 *list = NULL;
d9572179 402}
403
404static external_acl *
405find_externalAclHelper(const char *name)
406{
407 external_acl *node;
408
409 for (node = Config.externalAclHelperList; node; node = node->next) {
62e76326 410 if (strcmp(node->name, name) == 0)
411 return node;
d9572179 412 }
62e76326 413
d9572179 414 return NULL;
415}
416
1e5562e3 417void
abdd93d0 418external_acl::add(const ExternalACLEntryPointer &anEntry)
1e5562e3 419{
420 trimCache();
abdd93d0 421 assert(anEntry != NULL);
1e5562e3 422 assert (anEntry->def == NULL);
423 anEntry->def = this;
abdd93d0
AJ
424 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(anEntry.getRaw()); // XXX: make hash a std::map of Pointer.
425 hash_join(cache, e);
426 dlinkAdd(e, &e->lru, &lru_list);
427 e->lock(); //cbdataReference(e); // lock it on behalf of the hash
95dc7ff4 428 ++cache_entries;
1e5562e3 429}
430
431void
432external_acl::trimCache()
433{
abdd93d0
AJ
434 if (cache_size && cache_entries >= cache_size) {
435 ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
436 external_acl_cache_delete(this, e);
437 }
1e5562e3 438}
439
d9572179 440/******************************************************************
441 * external acl type
442 */
443
f963b531
AJ
444class external_acl_data
445{
446 CBDATA_CLASS(external_acl_data);
447
448public:
449 explicit external_acl_data(external_acl *aDef) : def(cbdataReference(aDef)), name(NULL), arguments(NULL) {}
450 ~external_acl_data();
451
d9572179 452 external_acl *def;
ec2d5242 453 const char *name;
d9572179 454 wordlist *arguments;
455};
456
f963b531
AJ
457CBDATA_CLASS_INIT(external_acl_data);
458
459external_acl_data::~external_acl_data()
d9572179 460{
f963b531
AJ
461 xfree(name);
462 wordlistDestroy(&arguments);
463 cbdataReferenceDone(def);
d9572179 464}
465
466void
b0dd28ba 467ACLExternal::parse()
d9572179 468{
b0dd28ba 469 if (data)
62e76326 470 self_destruct();
471
16c5ad96 472 char *token = ConfigParser::strtokFile();
62e76326 473
d9572179 474 if (!token)
62e76326 475 self_destruct();
476
f963b531 477 data = new external_acl_data(find_externalAclHelper(token));
62e76326 478
d9572179 479 if (!data->def)
62e76326 480 self_destruct();
481
ec2d5242
HN
482 // def->name is the name of the external_acl_type.
483 // this is the name of the 'acl' directive being tested
484 data->name = xstrdup(AclMatchedName);
485
16c5ad96 486 while ((token = ConfigParser::strtokFile())) {
62e76326 487 wordlistAdd(&data->arguments, token);
d9572179 488 }
d9572179 489}
490
4b0f5de8 491bool
492ACLExternal::valid () const
493{
2f1431ea 494#if USE_AUTH
4b0f5de8 495 if (data->def->require_auth) {
496 if (authenticateSchemeCount() == 0) {
fa84c01d 497 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes were compiled.");
4b0f5de8 498 return false;
499 }
500
501 if (authenticateActiveSchemeCount() == 0) {
fa84c01d 502 debugs(28, DBG_CRITICAL, "Can't use proxy auth because no authentication schemes are fully configured.");
4b0f5de8 503 return false;
504 }
505 }
2f1431ea 506#endif
4b0f5de8 507
508 return true;
509}
510
511bool
512ACLExternal::empty () const
513{
514 return false;
515}
516
b0dd28ba 517ACLExternal::~ACLExternal()
d9572179 518{
f963b531
AJ
519 delete data;
520 xfree(class_);
d9572179 521}
522
1abe0161 523static void
abdd93d0 524copyResultsFromEntry(HttpRequest *req, const ExternalACLEntryPointer &entry)
1abe0161
AJ
525{
526 if (req) {
527#if USE_AUTH
528 if (entry->user.size())
529 req->extacl_user = entry->user;
530
531 if (entry->password.size())
532 req->extacl_passwd = entry->password;
533#endif
534 if (!req->tag.size())
535 req->tag = entry->tag;
536
537 if (entry->log.size())
538 req->extacl_log = entry->log;
539
540 if (entry->message.size())
541 req->extacl_message = entry->message;
a1df1417
NH
542
543 // attach the helper kv-pair to the transaction
544 UpdateRequestNotes(req->clientConnectionManager.get(), *req, entry->notes);
1abe0161
AJ
545 }
546}
547
e5f825ea 548static allow_t
c0941a6a 549aclMatchExternal(external_acl_data *acl, ACLFilledChecklist *ch)
d9572179 550{
c69ce640 551 debugs(82, 9, HERE << "acl=\"" << acl->def->name << "\"");
abdd93d0 552 ExternalACLEntryPointer entry = ch->extacl_entry;
62e76326 553
6f58d7d7
AR
554 external_acl_message = "MISSING REQUIRED INFORMATION";
555
abdd93d0
AJ
556 if (entry != NULL) {
557 if (entry->def == acl->def) {
ad06254e 558 /* Ours, use it.. if the key matches */
6f58d7d7
AR
559 const char *key = makeExternalAclKey(ch, acl);
560 if (!key)
561 return ACCESS_DUNNO; // insufficent data to continue
ad06254e 562 if (strcmp(key, (char*)entry->key) != 0) {
abdd93d0 563 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "' dont match. Discarded.");
ad06254e 564 // too bad. need a new lookup.
abdd93d0 565 entry = ch->extacl_entry = NULL;
ad06254e 566 }
62e76326 567 } else {
abdd93d0
AJ
568 /* Not ours.. get rid of it */
569 debugs(82, 9, "entry " << entry << " not valid or not ours. Discarded.");
570 if (entry != NULL) {
571 debugs(82, 9, "entry def=" << entry->def << ", our def=" << acl->def);
6f58d7d7 572 const char *key = makeExternalAclKey(ch, acl); // may be nil
abdd93d0 573 debugs(82, 9, "entry key='" << (char *)entry->key << "', our key='" << key << "'");
c69ce640 574 }
abdd93d0 575 entry = ch->extacl_entry = NULL;
62e76326 576 }
d9572179 577 }
62e76326 578
d9572179 579 if (!entry) {
c69ce640 580 debugs(82, 9, HERE << "No helper entry available");
2f1431ea 581#if USE_AUTH
62e76326 582 if (acl->def->require_auth) {
62e76326 583 /* Make sure the user is authenticated */
e5f825ea 584 debugs(82, 3, HERE << acl->def->name << " check user authenticated.");
e0f7153c
AR
585 const allow_t ti = AuthenticateAcl(ch);
586 if (ti != ACCESS_ALLOWED) {
e5f825ea 587 debugs(82, 2, HERE << acl->def->name << " user not authenticated (" << ti << ")");
e0f7153c 588 return ti;
62e76326 589 }
e5f825ea 590 debugs(82, 3, HERE << acl->def->name << " user is authenticated.");
62e76326 591 }
2f1431ea 592#endif
6f58d7d7 593 const char *key = makeExternalAclKey(ch, acl);
41218131 594
595 if (!key) {
596 /* Not sufficient data to process */
e5f825ea 597 return ACCESS_DUNNO;
41218131 598 }
599
abdd93d0 600 entry = static_cast<ExternalACLEntry *>(hash_lookup(acl->def->cache, key));
62e76326 601
abdd93d0
AJ
602 const ExternalACLEntryPointer staleEntry = entry;
603 if (entry != NULL && external_acl_entry_expired(acl->def, entry))
e0f7153c
AR
604 entry = NULL;
605
abdd93d0 606 if (entry != NULL && external_acl_grace_expired(acl->def, entry)) {
e0f7153c
AR
607 // refresh in the background
608 ExternalACLLookup::Start(ch, acl, true);
609 debugs(82, 4, HERE << "no need to wait for the refresh of '" <<
610 key << "' in '" << acl->def->name << "' (ch=" << ch << ").");
611 }
612
613 if (!entry) {
e5f825ea 614 debugs(82, 2, HERE << acl->def->name << "(\"" << key << "\") = lookup needed");
95a83c22 615
6825b101 616 if (!acl->def->theHelper->queueFull()) {
e5f825ea 617 debugs(82, 2, HERE << "\"" << key << "\": queueing a call.");
6f58d7d7
AR
618 if (!ch->goAsync(ExternalACLLookup::Instance()))
619 debugs(82, 2, "\"" << key << "\": no async support!");
e5f825ea 620 debugs(82, 2, HERE << "\"" << key << "\": return -1.");
e0f7153c 621 return ACCESS_DUNNO; // expired cached or simply absent entry
47b0c1fa 622 } else {
e0f7153c 623 if (!staleEntry) {
e5f825ea 624 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 625 "' queue overload. Request rejected '" << key << "'.");
4a972fa2 626 external_acl_message = "SYSTEM TOO BUSY, TRY AGAIN LATER";
e5f825ea 627 return ACCESS_DUNNO;
47b0c1fa 628 } else {
e5f825ea 629 debugs(82, DBG_IMPORTANT, "WARNING: external ACL '" << acl->def->name <<
bf8fe701 630 "' queue overload. Using stale result. '" << key << "'.");
e0f7153c 631 entry = staleEntry;
47b0c1fa 632 /* Fall thru to processing below */
633 }
634 }
635 }
d9572179 636 }
62e76326 637
e0f7153c
AR
638 debugs(82, 4, HERE << "entry = { date=" <<
639 (long unsigned int) entry->date <<
640 ", result=" << entry->result <<
641 " tag=" << entry->tag <<
642 " log=" << entry->log << " }");
643#if USE_AUTH
644 debugs(82, 4, HERE << "entry user=" << entry->user);
645#endif
646
d9572179 647 external_acl_cache_touch(acl->def, entry);
a7a42b14 648 external_acl_message = entry->message.termedBuf();
4a972fa2 649
e5f825ea 650 debugs(82, 2, HERE << acl->def->name << " = " << entry->result);
1abe0161 651 copyResultsFromEntry(ch->request, entry);
e5f825ea 652 return entry->result;
d9572179 653}
654
b0dd28ba 655int
656ACLExternal::match(ACLChecklist *checklist)
657{
e5f825ea 658 allow_t answer = aclMatchExternal(data, Filled(checklist));
e5f825ea
AJ
659
660 // convert to tri-state ACL match 1,0,-1
9b831d57 661 switch (answer) {
e5f825ea 662 case ACCESS_ALLOWED:
e5f825ea
AJ
663 return 1; // match
664
665 case ACCESS_DENIED:
e5f825ea
AJ
666 return 0; // non-match
667
668 case ACCESS_DUNNO:
669 case ACCESS_AUTH_REQUIRED:
670 default:
e0f7153c 671 // If the answer is not allowed or denied (matches/not matches) and
6f58d7d7
AR
672 // async authentication is not in progress, then we are done.
673 if (checklist->keepMatching())
e0f7153c 674 checklist->markFinished(answer, "aclMatchExternal exception");
e5f825ea
AJ
675 return -1; // other
676 }
b0dd28ba 677}
678
dfad5100 679SBufList
b0dd28ba 680ACLExternal::dump() const
d9572179 681{
b0dd28ba 682 external_acl_data const *acl = data;
dfad5100
FC
683 SBufList rv;
684 rv.push_back(SBuf(acl->def->name));
62e76326 685
dfad5100
FC
686 for (wordlist *arg = acl->arguments; arg; arg = arg->next) {
687 SBuf s;
688 s.Printf(" %s", arg->key);
689 rv.push_back(s);
d9572179 690 }
62e76326 691
dfad5100 692 return rv;
d9572179 693}
694
695/******************************************************************
696 * external_acl cache
697 */
698
d9572179 699static void
abdd93d0 700external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry)
d9572179 701{
c69ce640
AJ
702 // this must not be done when nothing is being cached.
703 if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
704 return;
705
d9572179 706 dlinkDelete(&entry->lru, &def->lru_list);
abdd93d0
AJ
707 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
708 dlinkAdd(e, &entry->lru, &def->lru_list);
d9572179 709}
710
711static char *
c0941a6a 712makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
d9572179 713{
032785bf 714 static MemBuf mb;
09e34608 715 mb.reset();
62e76326 716
4e56d7f6
AJ
717 // check for special case tokens in the format
718 for (Format::Token *t = acl_data->def->format.format; t ; t = t->next) {
62e76326 719
4e56d7f6
AJ
720 if (t->type == Format::LFT_EXT_ACL_NAME) {
721 // setup for %ACL
4ff6370b
AJ
722 safe_free(ch->al->lastAclName);
723 ch->al->lastAclName = xstrdup(acl_data->name);
789dda8d
CT
724 }
725
778647be 726 if (t->type == Format::LFT_EXT_ACL_DATA) {
4e56d7f6
AJ
727 // setup string for %DATA
728 SBuf sb;
4e56d7f6 729 for (auto arg = acl_data->arguments; arg; arg = arg->next) {
778647be 730 if (sb.length())
ec2d5242
HN
731 sb.append(" ", 1);
732
95d78f10 733 if (acl_data->def->quote == Format::LOG_QUOTE_URL) {
ec2d5242
HN
734 const char *quoted = rfc1738_escape(arg->key);
735 sb.append(quoted, strlen(quoted));
736 } else {
737 static MemBuf mb2;
738 mb2.init();
739 strwordquote(&mb2, arg->key);
740 sb.append(mb2.buf, mb2.size);
741 mb2.clean();
742 }
ec2d5242 743 }
778647be 744
4ff6370b 745 ch->al->lastAclData = xstrdup(sb.c_str());
dc1af3cf 746 }
62e76326 747
33c2eb4d 748#if USE_IDENT
4e56d7f6 749 if (t->type == Format::LFT_USER_IDENT) {
a51ed963 750 if (!*ch->rfc931) {
4e56d7f6
AJ
751 // if we fail to go async, we still return NULL and the caller
752 // will detect the failure in ACLExternal::match().
753 (void)ch->goAsync(IdentLookup::Instance());
754 return NULL;
ec2d5242 755 }
ec2d5242 756 }
33c2eb4d 757#endif
d9572179 758 }
62e76326 759
712fa21e 760 // assemble the full helper lookup string
4e56d7f6
AJ
761 acl_data->def->format.assemble(mb, ch->al, 0);
762
d9572179 763 return mb.buf;
d9572179 764}
765
766static int
abdd93d0 767external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry)
d9572179 768{
c69ce640
AJ
769 if (def->cache_size <= 0)
770 return 1;
771
d9572179 772 if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
62e76326 773 return 1;
d9572179 774 else
62e76326 775 return 0;
d9572179 776}
62e76326 777
47b0c1fa 778static int
abdd93d0 779external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry)
47b0c1fa 780{
c69ce640
AJ
781 if (def->cache_size <= 0)
782 return 1;
783
47b0c1fa 784 int ttl;
785 ttl = entry->result == 1 ? def->ttl : def->negative_ttl;
786 ttl = (ttl * (100 - def->grace)) / 100;
787
98b70eff 788 if (entry->date + ttl <= squid_curtime)
47b0c1fa 789 return 1;
790 else
791 return 0;
792}
793
abdd93d0 794static ExternalACLEntryPointer
1e5562e3 795external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const & data)
d9572179 796{
abdd93d0 797 ExternalACLEntryPointer entry;
c69ce640
AJ
798
799 // do not bother caching this result if TTL is going to expire it immediately
800 if (def->cache_size <= 0 || (def->ttl <= 0 && data.result == 1) || (def->negative_ttl <= 0 && data.result != 1)) {
801 debugs(82,6, HERE);
802 entry = new ExternalACLEntry;
803 entry->key = xstrdup(key);
804 entry->update(data);
805 entry->def = def;
806 return entry;
807 }
808
809 entry = static_cast<ExternalACLEntry *>(hash_lookup(def->cache, key));
bf8fe701 810 debugs(82, 2, "external_acl_cache_add: Adding '" << key << "' = " << data.result);
62e76326 811
abdd93d0
AJ
812 if (entry != NULL) {
813 debugs(82, 3, "updating existing entry");
c69ce640 814 entry->update(data);
62e76326 815 external_acl_cache_touch(def, entry);
62e76326 816 return entry;
d9572179 817 }
62e76326 818
1e5562e3 819 entry = new ExternalACLEntry;
4a8b20e8 820 entry->key = xstrdup(key);
c69ce640 821 entry->update(data);
1e5562e3 822
6ca34f6f 823 def->add(entry);
1e5562e3 824
d9572179 825 return entry;
826}
827
828static void
abdd93d0 829external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry)
d9572179 830{
abdd93d0 831 assert(entry != NULL);
c69ce640 832 assert(def->cache_size > 0 && entry->def == def);
abdd93d0
AJ
833 ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
834 hash_remove_link(def->cache, e);
835 dlinkDelete(&e->lru, &def->lru_list);
836 e->unlock(); // unlock on behalf of the hash
d9572179 837 def->cache_entries -= 1;
d9572179 838}
839
840/******************************************************************
841 * external_acl helpers
842 */
843
f963b531
AJ
844class externalAclState
845{
846 CBDATA_CLASS(externalAclState);
847
848public:
849 externalAclState(external_acl* aDef, const char *aKey) :
850 callback(NULL),
851 callback_data(NULL),
852 key(xstrdup(aKey)),
853 def(cbdataReference(aDef)),
854 queue(NULL)
855 {}
856 ~externalAclState();
62e76326 857
d9572179 858 EAH *callback;
859 void *callback_data;
860 char *key;
861 external_acl *def;
862 dlink_node list;
863 externalAclState *queue;
864};
865
f963b531
AJ
866CBDATA_CLASS_INIT(externalAclState);
867
868externalAclState::~externalAclState()
d9572179 869{
f963b531
AJ
870 xfree(key);
871 cbdataReferenceDone(callback_data);
872 cbdataReferenceDone(def);
d9572179 873}
874
d9572179 875/*
876 * The helper program receives queries on stdin, one
07eca7e0 877 * per line, and must return the result on on stdout
d9572179 878 *
879 * General result syntax:
880 *
881 * OK/ERR keyword=value ...
882 *
883 * Keywords:
884 *
4a972fa2 885 * user= The users name (login)
886 * message= Message describing the reason
f53969cc
SM
887 * tag= A string tag to be applied to the request that triggered the acl match.
888 * applies to both OK and ERR responses.
889 * Won't override existing request tags.
890 * log= A string to be used in access logging
d9572179 891 *
892 * Other keywords may be added to the protocol later
893 *
05e52854
AJ
894 * value needs to be URL-encoded or enclosed in double quotes (")
895 * with \-escaping on any whitespace, quotes, or slashes (\).
d9572179 896 */
d9572179 897static void
24438ec5 898externalAclHandleReply(void *data, const Helper::Reply &reply)
d9572179 899{
e6ccf245 900 externalAclState *state = static_cast<externalAclState *>(data);
d9572179 901 externalAclState *next;
1e5562e3 902 ExternalACLEntryData entryData;
e5f825ea 903 entryData.result = ACCESS_DENIED;
d9572179 904
0272dd08 905 debugs(82, 2, HERE << "reply=" << reply);
d9572179 906
2428ce02 907 if (reply.result == Helper::Okay)
0272dd08
AJ
908 entryData.result = ACCESS_ALLOWED;
909 // XXX: handle other non-DENIED results better
62e76326 910
24438ec5 911 // XXX: make entryData store a proper Helper::Reply object instead of copying.
ab332e27 912
a0592634
AJ
913 entryData.notes.append(&reply.notes);
914
cf9f0261
CT
915 const char *label = reply.notes.findFirst("tag");
916 if (label != NULL && *label != '\0')
917 entryData.tag = label;
62e76326 918
cf9f0261
CT
919 label = reply.notes.findFirst("message");
920 if (label != NULL && *label != '\0')
921 entryData.message = label;
62e76326 922
cf9f0261
CT
923 label = reply.notes.findFirst("log");
924 if (label != NULL && *label != '\0')
925 entryData.log = label;
62e76326 926
2f1431ea 927#if USE_AUTH
cf9f0261
CT
928 label = reply.notes.findFirst("user");
929 if (label != NULL && *label != '\0')
930 entryData.user = label;
7bbefa01 931
cf9f0261
CT
932 label = reply.notes.findFirst("password");
933 if (label != NULL && *label != '\0')
934 entryData.password = label;
2f1431ea 935#endif
62e76326 936
d9572179 937 dlinkDelete(&state->list, &state->def->queue);
62e76326 938
abdd93d0 939 ExternalACLEntryPointer entry;
4960475f 940 if (cbdataReferenceValid(state->def)) {
0272dd08 941 // only cache OK and ERR results.
2428ce02 942 if (reply.result == Helper::Okay || reply.result == Helper::Error)
1e5562e3 943 entry = external_acl_cache_add(state->def, state->key, entryData);
62e76326 944 else {
abdd93d0 945 const ExternalACLEntryPointer oldentry = static_cast<ExternalACLEntry *>(hash_lookup(state->def->cache, state->key));
62e76326 946
abdd93d0 947 if (oldentry != NULL)
2ae2408b 948 external_acl_cache_delete(state->def, oldentry);
62e76326 949 }
466090ac 950 }
d9572179 951
952 do {
62e76326 953 void *cbdata;
47b0c1fa 954 if (state->callback && cbdataReferenceValidDone(state->callback_data, &cbdata))
62e76326 955 state->callback(cbdata, entry);
956
957 next = state->queue;
f963b531 958 state->queue = NULL;
d9572179 959
f963b531 960 delete state;
d9572179 961
62e76326 962 state = next;
d9572179 963 } while (state);
964}
965
966void
e0f7153c 967ACLExternal::ExternalAclLookup(ACLChecklist *checklist, ACLExternal * me)
d9572179 968{
e0f7153c
AR
969 ExternalACLLookup::Start(checklist, me->data, false);
970}
c8e7608c 971
e0f7153c
AR
972void
973ExternalACLLookup::Start(ACLChecklist *checklist, external_acl_data *acl, bool inBackground)
974{
975 external_acl *def = acl->def;
c0f81932 976
c0941a6a 977 ACLFilledChecklist *ch = Filled(checklist);
c8e7608c 978 const char *key = makeExternalAclKey(ch, acl);
6f58d7d7 979 assert(key); // XXX: will fail if EXT_ACL_IDENT case needs an async lookup
62e76326 980
e0f7153c
AR
981 debugs(82, 2, HERE << (inBackground ? "bg" : "fg") << " lookup in '" <<
982 def->name << "' for '" << key << "'");
62e76326 983
47b0c1fa 984 /* Check for a pending lookup to hook into */
c69ce640 985 // only possible if we are caching results.
e0f7153c 986 externalAclState *oldstate = NULL;
c69ce640 987 if (def->cache_size > 0) {
e0f7153c 988 for (dlink_node *node = def->queue.head; node; node = node->next) {
c69ce640 989 externalAclState *oldstatetmp = static_cast<externalAclState *>(node->data);
62e76326 990
c69ce640
AJ
991 if (strcmp(key, oldstatetmp->key) == 0) {
992 oldstate = oldstatetmp;
993 break;
994 }
47b0c1fa 995 }
996 }
62e76326 997
e0f7153c
AR
998 // A background refresh has no need to piggiback on a pending request:
999 // When the pending request completes, the cache will be refreshed anyway.
1000 if (oldstate && inBackground) {
1001 debugs(82, 7, HERE << "'" << def->name << "' queue is already being refreshed (ch=" << ch << ")");
47b0c1fa 1002 return;
1003 }
62e76326 1004
f963b531 1005 externalAclState *state = new externalAclState(def, key);
62e76326 1006
e0f7153c
AR
1007 if (!inBackground) {
1008 state->callback = &ExternalACLLookup::LookupDone;
1009 state->callback_data = cbdataReference(checklist);
d9572179 1010 }
62e76326 1011
47b0c1fa 1012 if (oldstate) {
1013 /* Hook into pending lookup */
1014 state->queue = oldstate->queue;
1015 oldstate->queue = state;
1016 } else {
e0f7153c
AR
1017 /* No pending lookup found. Sumbit to helper */
1018
e0f7153c 1019 MemBuf buf;
2fe7eff9 1020 buf.init();
4391cd15 1021 buf.appendf("%s\n", key);
bf8fe701 1022 debugs(82, 4, "externalAclLookup: looking up for '" << key << "' in '" << def->name << "'.");
ab321f4b 1023
6825b101
CT
1024 if (!def->theHelper->trySubmit(buf.buf, externalAclHandleReply, state)) {
1025 debugs(82, 7, HERE << "'" << def->name << "' submit to helper failed");
1026 assert(inBackground); // or the caller should have checked
f963b531 1027 delete state;
6825b101
CT
1028 return;
1029 }
62e76326 1030
47b0c1fa 1031 dlinkAdd(state, &state->list, &def->queue);
62e76326 1032
2fe7eff9 1033 buf.clean();
47b0c1fa 1034 }
62e76326 1035
bf8fe701 1036 debugs(82, 4, "externalAclLookup: will wait for the result of '" << key <<
1037 "' in '" << def->name << "' (ch=" << ch << ").");
d9572179 1038}
1039
1040static void
1041externalAclStats(StoreEntry * sentry)
1042{
f963b531 1043 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1044 storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name);
1045 storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count);
bf3e8d5a
AJ
1046 assert(p->theHelper);
1047 p->theHelper->packStatsInto(sentry);
62e76326 1048 storeAppendPrintf(sentry, "\n");
d9572179 1049 }
1050}
1051
6b7d87bb
FC
1052static void
1053externalAclRegisterWithCacheManager(void)
1054{
8822ebee 1055 Mgr::RegisterAction("external_acl",
d9fc6862
A
1056 "External ACL stats",
1057 externalAclStats, 0, 1);
6b7d87bb
FC
1058}
1059
d9572179 1060void
1061externalAclInit(void)
1062{
f963b531 1063 for (external_acl *p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1064 if (!p->cache)
30abd221 1065 p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4);
62e76326 1066
1067 if (!p->theHelper)
48d54e4d 1068 p->theHelper = new helper(p->name);
62e76326 1069
1070 p->theHelper->cmdline = p->cmdline;
1071
1af735c7 1072 p->theHelper->childs.updateLimits(p->children);
07eca7e0 1073
62e76326 1074 p->theHelper->ipc_type = IPC_TCP_SOCKET;
1075
cc192b50 1076 p->theHelper->addr = p->local_addr;
1077
62e76326 1078 helperOpenServers(p->theHelper);
d9572179 1079 }
62e76326 1080
ea391f18 1081 externalAclRegisterWithCacheManager();
d9572179 1082}
1083
1084void
1085externalAclShutdown(void)
1086{
1087 external_acl *p;
62e76326 1088
d9572179 1089 for (p = Config.externalAclHelperList; p; p = p->next) {
62e76326 1090 helperShutdown(p->theHelper);
d9572179 1091 }
1092}
225b7b10 1093
1094ExternalACLLookup ExternalACLLookup::instance_;
1095ExternalACLLookup *
1096ExternalACLLookup::Instance()
1097{
1098 return &instance_;
1099}
1100
1101void
1102ExternalACLLookup::checkForAsync(ACLChecklist *checklist)const
1103{
b0dd28ba 1104 /* TODO: optimise this - we probably have a pointer to this
1105 * around somewhere */
97427e90 1106 ACL *acl = ACL::FindByName(AclMatchedName);
ab321f4b 1107 assert(acl);
b0dd28ba 1108 ACLExternal *me = dynamic_cast<ACLExternal *> (acl);
1109 assert (me);
e0f7153c 1110 ACLExternal::ExternalAclLookup(checklist, me);
225b7b10 1111}
1112
e0f7153c 1113/// Called when an async lookup returns
225b7b10 1114void
abdd93d0 1115ExternalACLLookup::LookupDone(void *data, const ExternalACLEntryPointer &result)
225b7b10 1116{
c0941a6a 1117 ACLFilledChecklist *checklist = Filled(static_cast<ACLChecklist*>(data));
abdd93d0 1118 checklist->extacl_entry = result;
6f58d7d7 1119 checklist->resumeNonBlockingCheck(ExternalACLLookup::Instance());
225b7b10 1120}
b0dd28ba 1121
1122/* This registers "external" in the registry. To do dynamic definitions
1123 * of external ACL's, rather than a static prototype, have a Prototype instance
1124 * prototype in the class that defines each external acl 'class'.
1125 * Then, then the external acl instance is created, it self registers under
1126 * it's name.
1127 * Be sure that clone is fully functional for that acl class though!
1128 */
1129ACL::Prototype ACLExternal::RegistryProtoype(&ACLExternal::RegistryEntry_, "external");
1130
1131ACLExternal ACLExternal::RegistryEntry_("external");
1132
1133ACL *
1134ACLExternal::clone() const
1135{
1136 return new ACLExternal(*this);
1137}
1138
86c63190 1139ACLExternal::ACLExternal(char const *theClass) : data(NULL), class_(xstrdup(theClass))
b0dd28ba 1140{}
1141
86c63190 1142ACLExternal::ACLExternal(ACLExternal const & old) : data(NULL), class_(old.class_ ? xstrdup(old.class_) : NULL)
b0dd28ba 1143{
1144 /* we don't have copy constructors for the data yet */
86c63190 1145 assert(!old.data);
b0dd28ba 1146}
1147
b0dd28ba 1148char const *
1149ACLExternal::typeString() const
1150{
1151 return class_;
1152}
1153
e870b1f8 1154bool
1155ACLExternal::isProxyAuth() const
1156{
2f1431ea 1157#if USE_AUTH
e870b1f8 1158 return data->def->require_auth;
2f1431ea
AJ
1159#else
1160 return false;
1161#endif
e870b1f8 1162}
f53969cc 1163