2 * Copyright (C) 1996-2023 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 "acl/Options.h"
16 #include "anyp/PortCfg.h"
17 #include "base/IoManip.h"
19 #include "ConfigParser.h"
20 #include "debug/Stream.h"
23 #include "sbuf/List.h"
24 #include "sbuf/Stream.h"
25 #include "SquidConfig.h"
30 const char *AclMatchedName
= nullptr;
34 /// ACL type name comparison functor
37 bool operator()(TypeName a
, TypeName b
) const { return strcmp(a
, b
) < 0; }
40 /// ACL makers indexed by ACL type name
41 typedef std::map
<TypeName
, Maker
, TypeNameCmp
> Makers
;
43 /// registered ACL Makers
47 static Makers Registry
;
51 /// creates an ACL object of the named (and already registered) ACL child type
54 Make(TypeName typeName
)
56 const auto pos
= TheMakers().find(typeName
);
57 if (pos
== TheMakers().end()) {
58 debugs(28, DBG_CRITICAL
, "FATAL: Invalid ACL type '" << typeName
<< "'");
60 assert(false); // not reached
63 ACL
*result
= (pos
->second
)(pos
->first
);
64 debugs(28, 4, typeName
<< '=' << result
);
72 Acl::RegisterMaker(TypeName typeName
, Maker maker
)
76 TheMakers().emplace(typeName
, maker
);
80 Acl::SetKey(SBuf
&keyStorage
, const char *keyParameterName
, const char *newKey
)
83 throw TextException(ToSBuf("An acl declaration is missing a ", keyParameterName
,
84 Debug::Extra
, "ACL name: ", AclMatchedName
),
88 if (keyStorage
.isEmpty()) {
93 if (keyStorage
.caseCmp(newKey
) == 0)
96 throw TextException(ToSBuf("Attempt to change the value of the ", keyParameterName
, " argument in a subsequent acl declaration:",
97 Debug::Extra
, "previously seen value: ", keyStorage
,
98 Debug::Extra
, "new/conflicting value: ", newKey
,
99 Debug::Extra
, "ACL name: ", AclMatchedName
,
100 Debug::Extra
, "advice: Use a dedicated ACL name for each distinct ", keyParameterName
,
101 " (and group those ACLs together using an 'any-of' ACL)."),
106 ACL::operator new (size_t)
108 fatal ("unusable ACL::new");
113 ACL::operator delete (void *)
115 fatal ("unusable ACL::delete");
119 ACL::FindByName(const char *name
)
122 debugs(28, 9, "ACL::FindByName '" << name
<< "'");
124 for (a
= Config
.aclList
; a
; a
= a
->next
)
125 if (!strcasecmp(a
->name
, name
))
128 debugs(28, 9, "ACL::FindByName found no match");
141 bool ACL::valid () const
147 ACL::matches(ACLChecklist
*checklist
) const
149 debugs(28, 5, "checking " << name
);
151 // XXX: AclMatchedName does not contain a matched ACL name when the acl
152 // does not match. It contains the last (usually leaf) ACL name checked
153 // (or is NULL if no ACLs were checked).
154 AclMatchedName
= name
;
157 if (!checklist
->hasAle() && requiresAle()) {
158 debugs(28, DBG_IMPORTANT
, "WARNING: " << name
<< " ACL is used in " <<
159 "context without an ALE state. Assuming mismatch.");
160 } else if (!checklist
->hasRequest() && requiresRequest()) {
161 debugs(28, DBG_IMPORTANT
, "WARNING: " << name
<< " ACL is used in " <<
162 "context without an HTTP request. Assuming mismatch.");
163 } else if (!checklist
->hasReply() && requiresReply()) {
164 debugs(28, DBG_IMPORTANT
, "WARNING: " << name
<< " ACL is used in " <<
165 "context without an HTTP response. Assuming mismatch.");
167 // make sure the ALE has as much data as possible
169 checklist
->verifyAle();
171 // have to cast because old match() API is missing const
172 result
= const_cast<ACL
*>(this)->match(checklist
);
175 const char *extra
= checklist
->asyncInProgress() ? " async" : "";
176 debugs(28, 3, "checked: " << name
<< " = " << result
<< extra
);
177 return result
== 1; // true for match; false for everything else
181 ACL::context(const char *aName
, const char *aCfgLine
)
185 xstrncpy(name
, aName
, ACL_NAME_SZ
-1);
188 cfgline
= xstrdup(aCfgLine
);
192 ACL::ParseAclLine(ConfigParser
&parser
, ACL
** head
)
194 /* we're already using strtok() to grok the line */
197 LOCAL_ARRAY(char, aclname
, ACL_NAME_SZ
);
200 /* snarf the ACL name */
202 if ((t
= ConfigParser::NextToken()) == nullptr) {
203 debugs(28, DBG_CRITICAL
, "ERROR: aclParseAclLine: missing ACL name.");
208 if (strlen(t
) >= ACL_NAME_SZ
) {
209 debugs(28, DBG_CRITICAL
, "aclParseAclLine: aclParseAclLine: ACL name '" << t
<<
210 "' too long, max " << ACL_NAME_SZ
- 1 << " characters supported");
215 xstrncpy(aclname
, t
, ACL_NAME_SZ
);
216 /* snarf the ACL type */
219 if ((theType
= ConfigParser::NextToken()) == nullptr) {
220 debugs(28, DBG_CRITICAL
, "ERROR: aclParseAclLine: missing ACL type.");
225 // Is this ACL going to work?
226 if (strcmp(theType
, "myip") == 0) {
227 AnyP::PortCfgPointer p
= HttpPortList
;
228 while (p
!= nullptr) {
229 // Bug 3239: not reliable when there is interception traffic coming
230 if (p
->flags
.natIntercept
)
231 debugs(28, DBG_CRITICAL
, "WARNING: 'myip' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
234 debugs(28, DBG_IMPORTANT
, "WARNING: UPGRADE: ACL 'myip' type has been renamed to 'localip' and matches the IP the client connected to.");
236 } else if (strcmp(theType
, "myport") == 0) {
237 AnyP::PortCfgPointer p
= HttpPortList
;
238 while (p
!= nullptr) {
239 // Bug 3239: not reliable when there is interception traffic coming
240 // Bug 3239: myport - not reliable (yet) when there is interception traffic coming
241 if (p
->flags
.natIntercept
)
242 debugs(28, DBG_CRITICAL
, "WARNING: 'myport' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
245 theType
= "localport";
246 debugs(28, DBG_IMPORTANT
, "WARNING: UPGRADE: ACL 'myport' type has been renamed to 'localport' and matches the port the client connected to.");
247 } else if (strcmp(theType
, "proto") == 0 && strcmp(aclname
, "manager") == 0) {
248 // ACL manager is now a built-in and has a different type.
249 debugs(28, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: UPGRADE: ACL 'manager' is now a built-in ACL. Remove it from your config file.");
250 return; // ignore the line
251 } else if (strcmp(theType
, "clientside_mark") == 0) {
252 debugs(28, DBG_IMPORTANT
, "WARNING: UPGRADE: ACL 'clientside_mark' type has been renamed to 'client_connection_mark'.");
253 theType
= "client_connection_mark";
256 if ((A
= FindByName(aclname
)) == nullptr) {
257 debugs(28, 3, "aclParseAclLine: Creating ACL '" << aclname
<< "'");
258 A
= Acl::Make(theType
);
259 A
->context(aclname
, config_input_line
);
262 if (strcmp (A
->typeString(),theType
) ) {
263 debugs(28, DBG_CRITICAL
, "aclParseAclLine: ACL '" << A
->name
<< "' already exists with different type.");
268 debugs(28, 3, "aclParseAclLine: Appending to '" << aclname
<< "'");
273 * Here we set AclMatchedName in case we need to use it in a
274 * warning message in aclDomainCompare().
276 AclMatchedName
= A
->name
; /* ugly */
280 /*split the function here */
284 * Clear AclMatchedName from our temporary hack
286 AclMatchedName
= nullptr; /* ugly */
292 debugs(28, DBG_CRITICAL
, "WARNING: empty ACL: " << A
->cfgline
);
296 fatalf("ERROR: Invalid ACL: %s\n",
300 // add to the global list for searching explicit ACLs by name
301 assert(head
&& *head
== Config
.aclList
);
305 // register for centralized cleanup
310 ACL::isProxyAuth() const
318 Acl::Options allOptions
= options();
319 for (const auto lineOption
: lineOptions()) {
320 lineOption
->unconfigure(); // forget any previous "acl ..." line effects
321 allOptions
.push_back(lineOption
);
323 Acl::ParseFlags(allOptions
);
327 ACL::dumpWhole(const char * const directiveName
, std::ostream
&os
)
329 // XXX: No lineOptions() call here because we do not remember ACL "line"
330 // boundaries and associated "line" options; we cannot report them.
331 os
<< directiveName
<< ' ' << name
<< ' ' << typeString() << options() <<
332 asList(dump()).prefixedBy(" ").delimitedBy(" ") <<
336 /* ACL result caching routines */
339 ACL::matchForCache(ACLChecklist
*)
341 /* This is a fatal to ensure that cacheMatchAcl calls are _only_
342 * made for supported acl types */
343 fatal("aclCacheMatchAcl: unknown or unexpected ACL type");
344 return 0; /* NOTREACHED */
348 * we lookup an acl's cached results, and if we cannot find the acl being
349 * checked we check it and cache the result. This function is a template
350 * method to support caching of multiple acl types.
351 * Note that caching of time based acl's is not
352 * wise in long lived caches (i.e. the auth_user proxy match cache)
354 * TODO: does a dlink_list perform well enough? Kinkie
357 ACL::cacheMatchAcl(dlink_list
* cache
, ACLChecklist
*checklist
)
359 acl_proxy_auth_match_cache
*auth_match
;
364 auth_match
= (acl_proxy_auth_match_cache
*)link
->data
;
366 if (auth_match
->acl_data
== this) {
367 debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name
<< "' (" << this << ")");
368 return auth_match
->matchrv
;
374 auth_match
= new acl_proxy_auth_match_cache(matchForCache(checklist
), this);
375 dlinkAddTail(auth_match
, &auth_match
->link
, cache
);
376 debugs(28, 4, "ACL::cacheMatchAcl: miss for '" << name
<< "'. Adding result " << auth_match
->matchrv
);
377 return auth_match
->matchrv
;
381 aclCacheMatchFlush(dlink_list
* cache
)
383 acl_proxy_auth_match_cache
*auth_match
;
384 dlink_node
*link
, *tmplink
;
387 debugs(28, 8, "aclCacheMatchFlush called for cache " << cache
);
390 auth_match
= (acl_proxy_auth_match_cache
*)link
->data
;
393 dlinkDelete(tmplink
, cache
);
399 ACL::requiresAle() const
405 ACL::requiresReply() const
411 ACL::requiresRequest() const
416 /*********************/
417 /* Destroy functions */
418 /*********************/
422 debugs(28, 3, "freeing ACL " << name
);
424 AclMatchedName
= nullptr; // in case it was pointing to our name
430 ACL
*a
= Config
.aclList
;
431 debugs(53, 3, "ACL::Initialize");