]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Acl.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Acl.cc
1 /*
2 * Copyright (C) 1996-2016 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 28 Access Control */
10
11 #include "squid.h"
12 #include "acl/Acl.h"
13 #include "acl/Checklist.h"
14 #include "acl/Gadgets.h"
15 #include "anyp/PortCfg.h"
16 #include "cache_cf.h"
17 #include "ConfigParser.h"
18 #include "Debug.h"
19 #include "dlink.h"
20 #include "fatal.h"
21 #include "globals.h"
22 #include "profiler/Profiler.h"
23 #include "SquidConfig.h"
24
25 #include <vector>
26
27 #define abortFlags(CONTENT) \
28 do { \
29 debugs(28, 0, CONTENT); \
30 self_destruct(); \
31 } while (0)
32
33 const ACLFlag ACLFlags::NoFlags[1] = {ACL_F_END};
34
35 const char *AclMatchedName = NULL;
36
37 ACLFlags::FlagsTokenizer::FlagsTokenizer(): tokPos(NULL) { }
38
39 ACLFlag
40 ACLFlags::FlagsTokenizer::nextFlag()
41 {
42 if (needNextToken()) {
43 if (!nextToken())
44 return 0;
45 } else
46 ++tokPos;
47 return *tokPos;
48 }
49
50 bool
51 ACLFlags::FlagsTokenizer::hasParameter() const
52 {
53 return tokPos && tokPos[0] && tokPos[1] == '=' && tokPos[2];
54 }
55
56 SBuf
57 ACLFlags::FlagsTokenizer::getParameter() const
58 {
59 return hasParameter() ? SBuf(&tokPos[2]) : SBuf();
60 }
61
62 bool
63 ACLFlags::FlagsTokenizer::needNextToken() const
64 {
65 return !tokPos || !tokPos[0] || !tokPos[1] || tokPos[1] == '=';
66 }
67
68 bool
69 ACLFlags::FlagsTokenizer::nextToken()
70 {
71 char *t = ConfigParser::PeekAtToken();
72 if (t == NULL || t[0] != '-' || !t[1])
73 return false;
74 (void)ConfigParser::NextQuotedToken();
75 if (strcmp(t, "--") == 0)
76 return false;
77 tokPos = t + 1;
78 return true;
79 }
80
81 ACLFlags::~ACLFlags()
82 {
83 delete delimiters_;
84 }
85
86 ACLFlags::Status
87 ACLFlags::flagStatus(const ACLFlag f) const
88 {
89 if (f == ACL_F_REGEX_CASE)
90 return noParameter;
91 if (f == ACL_F_SUBSTRING)
92 return parameterOptional;
93 if (supported_.find(f) != std::string::npos)
94 return noParameter;
95 return notSupported;
96 }
97
98 bool
99 ACLFlags::parameterSupported(const ACLFlag f, const SBuf &val) const
100 {
101 if (f == ACL_F_SUBSTRING)
102 return val.findFirstOf(CharacterSet::ALPHA + CharacterSet::DIGIT) == SBuf::npos;
103 return true;
104 }
105
106 void
107 ACLFlags::makeSet(const ACLFlag f, const SBuf &param)
108 {
109 flags_ |= flagToInt(f);
110 if (!param.isEmpty())
111 flagParameters_[f].append(param);
112 }
113
114 void
115 ACLFlags::makeUnSet(const ACLFlag f)
116 {
117 flags_ &= ~flagToInt(f);
118 flagParameters_[f].clear();
119 }
120
121 void
122 ACLFlags::parseFlags()
123 {
124 FlagsTokenizer tokenizer;
125 ACLFlag flag('\0');
126 while ((flag = tokenizer.nextFlag())) {
127 switch (flagStatus(flag))
128 {
129 case notSupported:
130 abortFlags("Flag '" << flag << "' not supported");
131 break;
132 case noParameter:
133 makeSet(flag);
134 break;
135 case parameterRequired:
136 if (!tokenizer.hasParameter()) {
137 abortFlags("Flag '" << flag << "' must have a parameter");
138 break;
139 }
140 case parameterOptional:
141 SBuf param;
142 if (tokenizer.hasParameter()) {
143 param = tokenizer.getParameter();
144 if (!parameterSupported(flag, param))
145 abortFlags("Parameter '" << param << "' for flag '" << flag << "' not supported");
146 }
147 makeSet(flag, param);
148 break;
149 }
150 }
151
152 /*Regex code needs to parse -i file*/
153 if ( isSet(ACL_F_REGEX_CASE)) {
154 ConfigParser::TokenPutBack("-i");
155 makeUnSet('i');
156 }
157 }
158
159 SBuf
160 ACLFlags::parameter(const ACLFlag f) const
161 {
162 assert(static_cast<uint32_t>(f - 'A') < FlagIndexMax);
163 auto p = flagParameters_.find(f);
164 return p == flagParameters_.end() ? SBuf() : p->second;
165 }
166
167 const CharacterSet *
168 ACLFlags::delimiters()
169 {
170 if (isSet(ACL_F_SUBSTRING) && !delimiters_) {
171 static const SBuf defaultParameter(",");
172 SBuf rawParameter = parameter(ACL_F_SUBSTRING);
173 if (rawParameter.isEmpty())
174 rawParameter = defaultParameter;
175 delimiters_ = new CharacterSet("ACLFlags::delimiters", rawParameter.c_str());
176 }
177 return delimiters_;
178 }
179
180 const char *
181 ACLFlags::flagsStr() const
182 {
183 static char buf[64];
184 if (flags_ == 0)
185 return "";
186
187 char *s = buf;
188 *s++ = '-';
189 for (ACLFlag f = 'A'; f <= 'z'; f++) {
190 // ACL_F_REGEX_CASE (-i) flag handled by ACLRegexData class, ignore
191 if (isSet(f) && f != ACL_F_REGEX_CASE)
192 *s++ = f;
193 }
194 *s = '\0';
195 return buf;
196 }
197
198 void *
199 ACL::operator new (size_t)
200 {
201 fatal ("unusable ACL::new");
202 return (void *)1;
203 }
204
205 void
206 ACL::operator delete (void *)
207 {
208 fatal ("unusable ACL::delete");
209 }
210
211 ACL *
212 ACL::FindByName(const char *name)
213 {
214 ACL *a;
215 debugs(28, 9, "ACL::FindByName '" << name << "'");
216
217 for (a = Config.aclList; a; a = a->next)
218 if (!strcasecmp(a->name, name))
219 return a;
220
221 debugs(28, 9, "ACL::FindByName found no match");
222
223 return NULL;
224 }
225
226 ACL *
227 ACL::Factory (char const *type)
228 {
229 ACL *result = Prototype::Factory (type);
230
231 if (!result)
232 fatal ("Unknown acl type in ACL::Factory");
233
234 return result;
235 }
236
237 ACL::ACL() :
238 cfgline(nullptr),
239 next(nullptr),
240 registered(false)
241 {
242 *name = 0;
243 }
244
245 ACL::ACL(const ACLFlag flgs[]) :
246 cfgline(NULL),
247 next(NULL),
248 flags(flgs),
249 registered(false)
250 {
251 *name = 0;
252 }
253
254 bool ACL::valid () const
255 {
256 return true;
257 }
258
259 bool
260 ACL::matches(ACLChecklist *checklist) const
261 {
262 PROF_start(ACL_matches);
263 debugs(28, 5, "checking " << name);
264
265 // XXX: AclMatchedName does not contain a matched ACL name when the acl
266 // does not match. It contains the last (usually leaf) ACL name checked
267 // (or is NULL if no ACLs were checked).
268 AclMatchedName = name;
269
270 int result = 0;
271 if (!checklist->hasAle() && requiresAle()) {
272 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
273 "context without an ALE state. Assuming mismatch.");
274 } else if (!checklist->hasRequest() && requiresRequest()) {
275 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
276 "context without an HTTP request. Assuming mismatch.");
277 } else if (!checklist->hasReply() && requiresReply()) {
278 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
279 "context without an HTTP response. Assuming mismatch.");
280 } else {
281 // make sure the ALE has as much data as possible
282 if (requiresAle())
283 checklist->syncAle();
284
285 // have to cast because old match() API is missing const
286 result = const_cast<ACL*>(this)->match(checklist);
287 }
288
289 const char *extra = checklist->asyncInProgress() ? " async" : "";
290 debugs(28, 3, "checked: " << name << " = " << result << extra);
291 PROF_stop(ACL_matches);
292 return result == 1; // true for match; false for everything else
293 }
294
295 void
296 ACL::context(const char *aName, const char *aCfgLine)
297 {
298 name[0] = '\0';
299 if (aName)
300 xstrncpy(name, aName, ACL_NAME_SZ-1);
301 safe_free(cfgline);
302 if (aCfgLine)
303 cfgline = xstrdup(aCfgLine);
304 }
305
306 void
307 ACL::ParseAclLine(ConfigParser &parser, ACL ** head)
308 {
309 /* we're already using strtok() to grok the line */
310 char *t = NULL;
311 ACL *A = NULL;
312 LOCAL_ARRAY(char, aclname, ACL_NAME_SZ);
313 int new_acl = 0;
314
315 /* snarf the ACL name */
316
317 if ((t = ConfigParser::NextToken()) == NULL) {
318 debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL name.");
319 parser.destruct();
320 return;
321 }
322
323 if (strlen(t) >= ACL_NAME_SZ) {
324 debugs(28, DBG_CRITICAL, "aclParseAclLine: aclParseAclLine: ACL name '" << t <<
325 "' too long, max " << ACL_NAME_SZ - 1 << " characters supported");
326 parser.destruct();
327 return;
328 }
329
330 xstrncpy(aclname, t, ACL_NAME_SZ);
331 /* snarf the ACL type */
332 const char *theType;
333
334 if ((theType = ConfigParser::NextToken()) == NULL) {
335 debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL type.");
336 parser.destruct();
337 return;
338 }
339
340 // Is this ACL going to work?
341 if (strcmp(theType, "myip") == 0) {
342 AnyP::PortCfgPointer p = HttpPortList;
343 while (p != NULL) {
344 // Bug 3239: not reliable when there is interception traffic coming
345 if (p->flags.natIntercept)
346 debugs(28, DBG_CRITICAL, "WARNING: 'myip' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
347 p = p->next;
348 }
349 debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myip' type is has been renamed to 'localip' and matches the IP the client connected to.");
350 theType = "localip";
351 } else if (strcmp(theType, "myport") == 0) {
352 AnyP::PortCfgPointer p = HttpPortList;
353 while (p != NULL) {
354 // Bug 3239: not reliable when there is interception traffic coming
355 // Bug 3239: myport - not reliable (yet) when there is interception traffic coming
356 if (p->flags.natIntercept)
357 debugs(28, DBG_CRITICAL, "WARNING: 'myport' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
358 p = p->next;
359 }
360 theType = "localport";
361 debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myport' type is has been renamed to 'localport' and matches the port the client connected to.");
362 } else if (strcmp(theType, "proto") == 0 && strcmp(aclname, "manager") == 0) {
363 // ACL manager is now a built-in and has a different type.
364 debugs(28, DBG_PARSE_NOTE(DBG_IMPORTANT), "UPGRADE: ACL 'manager' is now a built-in ACL. Remove it from your config file.");
365 return; // ignore the line
366 }
367
368 if (!Prototype::Registered(theType)) {
369 debugs(28, DBG_CRITICAL, "FATAL: Invalid ACL type '" << theType << "'");
370 // XXX: make this an ERROR and skip the ACL creation. We *may* die later when its use is attempted. Or may not.
371 parser.destruct();
372 return;
373 }
374
375 if ((A = FindByName(aclname)) == NULL) {
376 debugs(28, 3, "aclParseAclLine: Creating ACL '" << aclname << "'");
377 A = ACL::Factory(theType);
378 A->context(aclname, config_input_line);
379 new_acl = 1;
380 } else {
381 if (strcmp (A->typeString(),theType) ) {
382 debugs(28, DBG_CRITICAL, "aclParseAclLine: ACL '" << A->name << "' already exists with different type.");
383 parser.destruct();
384 return;
385 }
386
387 debugs(28, 3, "aclParseAclLine: Appending to '" << aclname << "'");
388 new_acl = 0;
389 }
390
391 /*
392 * Here we set AclMatchedName in case we need to use it in a
393 * warning message in aclDomainCompare().
394 */
395 AclMatchedName = A->name; /* ugly */
396
397 A->flags.parseFlags();
398
399 /*split the function here */
400 A->parse();
401
402 /*
403 * Clear AclMatchedName from our temporary hack
404 */
405 AclMatchedName = NULL; /* ugly */
406
407 if (!new_acl)
408 return;
409
410 if (A->empty()) {
411 debugs(28, DBG_CRITICAL, "Warning: empty ACL: " << A->cfgline);
412 }
413
414 if (!A->valid()) {
415 fatalf("ERROR: Invalid ACL: %s\n",
416 A->cfgline);
417 }
418
419 // add to the global list for searching explicit ACLs by name
420 assert(head && *head == Config.aclList);
421 A->next = *head;
422 *head = A;
423
424 // register for centralized cleanup
425 aclRegister(A);
426 }
427
428 bool
429 ACL::isProxyAuth() const
430 {
431 return false;
432 }
433
434 /* ACL result caching routines */
435
436 int
437 ACL::matchForCache(ACLChecklist *)
438 {
439 /* This is a fatal to ensure that cacheMatchAcl calls are _only_
440 * made for supported acl types */
441 fatal("aclCacheMatchAcl: unknown or unexpected ACL type");
442 return 0; /* NOTREACHED */
443 }
444
445 /*
446 * we lookup an acl's cached results, and if we cannot find the acl being
447 * checked we check it and cache the result. This function is a template
448 * method to support caching of multiple acl types.
449 * Note that caching of time based acl's is not
450 * wise in long lived caches (i.e. the auth_user proxy match cache)
451 * RBC
452 * TODO: does a dlink_list perform well enough? Kinkie
453 */
454 int
455 ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist)
456 {
457 acl_proxy_auth_match_cache *auth_match;
458 dlink_node *link;
459 link = cache->head;
460
461 while (link) {
462 auth_match = (acl_proxy_auth_match_cache *)link->data;
463
464 if (auth_match->acl_data == this) {
465 debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name << "' (" << this << ")");
466 return auth_match->matchrv;
467 }
468
469 link = link->next;
470 }
471
472 auth_match = new acl_proxy_auth_match_cache(matchForCache(checklist), this);
473 dlinkAddTail(auth_match, &auth_match->link, cache);
474 debugs(28, 4, "ACL::cacheMatchAcl: miss for '" << name << "'. Adding result " << auth_match->matchrv);
475 return auth_match->matchrv;
476 }
477
478 void
479 aclCacheMatchFlush(dlink_list * cache)
480 {
481 acl_proxy_auth_match_cache *auth_match;
482 dlink_node *link, *tmplink;
483 link = cache->head;
484
485 debugs(28, 8, "aclCacheMatchFlush called for cache " << cache);
486
487 while (link) {
488 auth_match = (acl_proxy_auth_match_cache *)link->data;
489 tmplink = link;
490 link = link->next;
491 dlinkDelete(tmplink, cache);
492 delete auth_match;
493 }
494 }
495
496 bool
497 ACL::requiresAle() const
498 {
499 return false;
500 }
501
502 bool
503 ACL::requiresReply() const
504 {
505 return false;
506 }
507
508 bool
509 ACL::requiresRequest() const
510 {
511 return false;
512 }
513
514 /*********************/
515 /* Destroy functions */
516 /*********************/
517
518 ACL::~ACL()
519 {
520 debugs(28, 3, "freeing ACL " << name);
521 safe_free(cfgline);
522 AclMatchedName = NULL; // in case it was pointing to our name
523 }
524
525 ACL::Prototype::Prototype() : prototype (NULL), typeString (NULL) {}
526
527 ACL::Prototype::Prototype (ACL const *aPrototype, char const *aType) : prototype (aPrototype), typeString (aType)
528 {
529 registerMe ();
530 }
531
532 std::vector<ACL::Prototype const *> * ACL::Prototype::Registry;
533 void *ACL::Prototype::Initialized;
534
535 bool
536 ACL::Prototype::Registered(char const *aType)
537 {
538 debugs(28, 7, "ACL::Prototype::Registered: invoked for type " << aType);
539
540 for (iterator i = Registry->begin(); i != Registry->end(); ++i)
541 if (!strcmp (aType, (*i)->typeString)) {
542 debugs(28, 7, "ACL::Prototype::Registered: yes");
543 return true;
544 }
545
546 debugs(28, 7, "ACL::Prototype::Registered: no");
547 return false;
548 }
549
550 void
551 ACL::Prototype::registerMe ()
552 {
553 if (!Registry || (Initialized != ((char *)Registry - 5)) ) {
554 /* TODO: extract this */
555 /* Not initialised */
556 Registry = new std::vector<ACL::Prototype const *>;
557 Initialized = (char *)Registry - 5;
558 }
559
560 if (Registered (typeString))
561 fatalf ("Attempt to register %s twice", typeString);
562
563 Registry->push_back (this);
564 }
565
566 ACL::Prototype::~Prototype()
567 {
568 // TODO: unregister me
569 }
570
571 ACL *
572 ACL::Prototype::Factory (char const *typeToClone)
573 {
574 debugs(28, 4, "ACL::Prototype::Factory: cloning an object for type '" << typeToClone << "'");
575
576 for (iterator i = Registry->begin(); i != Registry->end(); ++i)
577 if (!strcmp (typeToClone, (*i)->typeString)) {
578 ACL *A = (*i)->prototype->clone();
579 A->flags = (*i)->prototype->flags;
580 return A;
581 }
582
583 debugs(28, 4, "ACL::Prototype::Factory: cloning failed, no type '" << typeToClone << "' available");
584
585 return NULL;
586 }
587
588 void
589 ACL::Initialize()
590 {
591 ACL *a = Config.aclList;
592 debugs(53, 3, "ACL::Initialize");
593
594 while (a) {
595 a->prepareForUse();
596 a = a->next;
597 }
598 }
599