2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 28 Access Control */
13 #include "acl/Checklist.h"
14 #include "acl/Gadgets.h"
15 #include "anyp/PortCfg.h"
17 #include "ConfigParser.h"
22 #include "profiler/Profiler.h"
23 #include "SquidConfig.h"
27 #define abortFlags(CONTENT) \
29 debugs(28, 0, CONTENT); \
33 const ACLFlag
ACLFlags::NoFlags
[1] = {ACL_F_END
};
35 const char *AclMatchedName
= NULL
;
37 ACLFlags::FlagsTokenizer::FlagsTokenizer(): tokPos(NULL
) { }
40 ACLFlags::FlagsTokenizer::nextFlag()
42 if (needNextToken()) {
51 ACLFlags::FlagsTokenizer::hasParameter() const
53 return tokPos
&& tokPos
[0] && tokPos
[1] == '=' && tokPos
[2];
57 ACLFlags::FlagsTokenizer::getParameter() const
59 return hasParameter() ? SBuf(&tokPos
[2]) : SBuf();
63 ACLFlags::FlagsTokenizer::needNextToken() const
65 return !tokPos
|| !tokPos
[0] || !tokPos
[1] || tokPos
[1] == '=';
69 ACLFlags::FlagsTokenizer::nextToken()
71 char *t
= ConfigParser::PeekAtToken();
72 if (t
== NULL
|| t
[0] != '-' || !t
[1])
74 (void)ConfigParser::NextQuotedToken();
75 if (strcmp(t
, "--") == 0)
87 ACLFlags::flagStatus(const ACLFlag f
) const
89 if (f
== ACL_F_REGEX_CASE
)
91 if (f
== ACL_F_SUBSTRING
)
92 return parameterOptional
;
93 if (supported_
.find(f
) != std::string::npos
)
99 ACLFlags::parameterSupported(const ACLFlag f
, const SBuf
&val
) const
101 if (f
== ACL_F_SUBSTRING
)
102 return val
.findFirstOf(CharacterSet::ALPHA
+ CharacterSet::DIGIT
) == SBuf::npos
;
107 ACLFlags::makeSet(const ACLFlag f
, const SBuf
¶m
)
109 flags_
|= flagToInt(f
);
110 if (!param
.isEmpty())
111 flagParameters_
[f
].append(param
);
115 ACLFlags::makeUnSet(const ACLFlag f
)
117 flags_
&= ~flagToInt(f
);
118 flagParameters_
[f
].clear();
122 ACLFlags::parseFlags()
124 FlagsTokenizer tokenizer
;
126 while ((flag
= tokenizer
.nextFlag())) {
127 switch (flagStatus(flag
))
130 abortFlags("Flag '" << flag
<< "' not supported");
135 case parameterRequired
:
136 if (!tokenizer
.hasParameter()) {
137 abortFlags("Flag '" << flag
<< "' must have a parameter");
140 case parameterOptional
:
142 if (tokenizer
.hasParameter()) {
143 param
= tokenizer
.getParameter();
144 if (!parameterSupported(flag
, param
))
145 abortFlags("Parameter '" << param
<< "' for flag '" << flag
<< "' not supported");
147 makeSet(flag
, param
);
152 /*Regex code needs to parse -i file*/
153 if ( isSet(ACL_F_REGEX_CASE
)) {
154 ConfigParser::TokenPutBack("-i");
160 ACLFlags::parameter(const ACLFlag f
) const
162 assert(static_cast<uint32_t>(f
- 'A') < FlagIndexMax
);
163 auto p
= flagParameters_
.find(f
);
164 return p
== flagParameters_
.end() ? SBuf() : p
->second
;
168 ACLFlags::delimiters()
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());
181 ACLFlags::flagsStr() const
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
)
199 ACL::operator new (size_t)
201 fatal ("unusable ACL::new");
206 ACL::operator delete (void *)
208 fatal ("unusable ACL::delete");
212 ACL::FindByName(const char *name
)
215 debugs(28, 9, "ACL::FindByName '" << name
<< "'");
217 for (a
= Config
.aclList
; a
; a
= a
->next
)
218 if (!strcasecmp(a
->name
, name
))
221 debugs(28, 9, "ACL::FindByName found no match");
227 ACL::Factory (char const *type
)
229 ACL
*result
= Prototype::Factory (type
);
232 fatal ("Unknown acl type in ACL::Factory");
245 ACL::ACL(const ACLFlag flgs
[]) :
254 bool ACL::valid () const
260 ACL::matches(ACLChecklist
*checklist
) const
262 PROF_start(ACL_matches
);
263 debugs(28, 5, "checking " << name
);
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
;
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.");
281 // make sure the ALE has as much data as possible
283 checklist
->syncAle();
285 // have to cast because old match() API is missing const
286 result
= const_cast<ACL
*>(this)->match(checklist
);
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
296 ACL::context(const char *aName
, const char *aCfgLine
)
300 xstrncpy(name
, aName
, ACL_NAME_SZ
-1);
303 cfgline
= xstrdup(aCfgLine
);
307 ACL::ParseAclLine(ConfigParser
&parser
, ACL
** head
)
309 /* we're already using strtok() to grok the line */
312 LOCAL_ARRAY(char, aclname
, ACL_NAME_SZ
);
315 /* snarf the ACL name */
317 if ((t
= ConfigParser::NextToken()) == NULL
) {
318 debugs(28, DBG_CRITICAL
, "aclParseAclLine: missing ACL name.");
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");
330 xstrncpy(aclname
, t
, ACL_NAME_SZ
);
331 /* snarf the ACL type */
334 if ((theType
= ConfigParser::NextToken()) == NULL
) {
335 debugs(28, DBG_CRITICAL
, "aclParseAclLine: missing ACL type.");
340 // Is this ACL going to work?
341 if (strcmp(theType
, "myip") == 0) {
342 AnyP::PortCfgPointer p
= HttpPortList
;
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.");
349 debugs(28, DBG_IMPORTANT
, "UPGRADE: ACL 'myip' type is has been renamed to 'localip' and matches the IP the client connected to.");
351 } else if (strcmp(theType
, "myport") == 0) {
352 AnyP::PortCfgPointer p
= HttpPortList
;
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.");
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
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.
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
);
381 if (strcmp (A
->typeString(),theType
) ) {
382 debugs(28, DBG_CRITICAL
, "aclParseAclLine: ACL '" << A
->name
<< "' already exists with different type.");
387 debugs(28, 3, "aclParseAclLine: Appending to '" << aclname
<< "'");
392 * Here we set AclMatchedName in case we need to use it in a
393 * warning message in aclDomainCompare().
395 AclMatchedName
= A
->name
; /* ugly */
397 A
->flags
.parseFlags();
399 /*split the function here */
403 * Clear AclMatchedName from our temporary hack
405 AclMatchedName
= NULL
; /* ugly */
411 debugs(28, DBG_CRITICAL
, "Warning: empty ACL: " << A
->cfgline
);
415 fatalf("ERROR: Invalid ACL: %s\n",
419 // add to the global list for searching explicit ACLs by name
420 assert(head
&& *head
== Config
.aclList
);
424 // register for centralized cleanup
429 ACL::isProxyAuth() const
434 /* ACL result caching routines */
437 ACL::matchForCache(ACLChecklist
*)
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 */
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)
452 * TODO: does a dlink_list perform well enough? Kinkie
455 ACL::cacheMatchAcl(dlink_list
* cache
, ACLChecklist
*checklist
)
457 acl_proxy_auth_match_cache
*auth_match
;
462 auth_match
= (acl_proxy_auth_match_cache
*)link
->data
;
464 if (auth_match
->acl_data
== this) {
465 debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name
<< "' (" << this << ")");
466 return auth_match
->matchrv
;
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
;
479 aclCacheMatchFlush(dlink_list
* cache
)
481 acl_proxy_auth_match_cache
*auth_match
;
482 dlink_node
*link
, *tmplink
;
485 debugs(28, 8, "aclCacheMatchFlush called for cache " << cache
);
488 auth_match
= (acl_proxy_auth_match_cache
*)link
->data
;
491 dlinkDelete(tmplink
, cache
);
497 ACL::requiresAle() const
503 ACL::requiresReply() const
509 ACL::requiresRequest() const
514 /*********************/
515 /* Destroy functions */
516 /*********************/
520 debugs(28, 3, "freeing ACL " << name
);
522 AclMatchedName
= NULL
; // in case it was pointing to our name
525 ACL::Prototype::Prototype() : prototype (NULL
), typeString (NULL
) {}
527 ACL::Prototype::Prototype (ACL
const *aPrototype
, char const *aType
) : prototype (aPrototype
), typeString (aType
)
532 std::vector
<ACL::Prototype
const *> * ACL::Prototype::Registry
;
533 void *ACL::Prototype::Initialized
;
536 ACL::Prototype::Registered(char const *aType
)
538 debugs(28, 7, "ACL::Prototype::Registered: invoked for type " << aType
);
540 for (iterator i
= Registry
->begin(); i
!= Registry
->end(); ++i
)
541 if (!strcmp (aType
, (*i
)->typeString
)) {
542 debugs(28, 7, "ACL::Prototype::Registered: yes");
546 debugs(28, 7, "ACL::Prototype::Registered: no");
551 ACL::Prototype::registerMe ()
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;
560 if (Registered (typeString
))
561 fatalf ("Attempt to register %s twice", typeString
);
563 Registry
->push_back (this);
566 ACL::Prototype::~Prototype()
568 // TODO: unregister me
572 ACL::Prototype::Factory (char const *typeToClone
)
574 debugs(28, 4, "ACL::Prototype::Factory: cloning an object for type '" << typeToClone
<< "'");
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
;
583 debugs(28, 4, "ACL::Prototype::Factory: cloning failed, no type '" << typeToClone
<< "' available");
591 ACL
*a
= Config
.aclList
;
592 debugs(53, 3, "ACL::Initialize");