]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Acl.cc
Use PackableStream for dump_acl (#1573)
[thirdparty/squid.git] / src / acl / Acl.cc
1 /*
2 * Copyright (C) 1996-2023 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 "acl/Options.h"
16 #include "anyp/PortCfg.h"
17 #include "base/IoManip.h"
18 #include "cache_cf.h"
19 #include "ConfigParser.h"
20 #include "debug/Stream.h"
21 #include "fatal.h"
22 #include "globals.h"
23 #include "sbuf/List.h"
24 #include "sbuf/Stream.h"
25 #include "SquidConfig.h"
26
27 #include <algorithm>
28 #include <map>
29
30 const char *AclMatchedName = nullptr;
31
32 namespace Acl {
33
34 /// ACL type name comparison functor
35 class TypeNameCmp {
36 public:
37 bool operator()(TypeName a, TypeName b) const { return strcmp(a, b) < 0; }
38 };
39
40 /// ACL makers indexed by ACL type name
41 typedef std::map<TypeName, Maker, TypeNameCmp> Makers;
42
43 /// registered ACL Makers
44 static Makers &
45 TheMakers()
46 {
47 static Makers Registry;
48 return Registry;
49 }
50
51 /// creates an ACL object of the named (and already registered) ACL child type
52 static
53 ACL *
54 Make(TypeName typeName)
55 {
56 const auto pos = TheMakers().find(typeName);
57 if (pos == TheMakers().end()) {
58 debugs(28, DBG_CRITICAL, "FATAL: Invalid ACL type '" << typeName << "'");
59 self_destruct();
60 assert(false); // not reached
61 }
62
63 ACL *result = (pos->second)(pos->first);
64 debugs(28, 4, typeName << '=' << result);
65 assert(result);
66 return result;
67 }
68
69 } // namespace Acl
70
71 void
72 Acl::RegisterMaker(TypeName typeName, Maker maker)
73 {
74 assert(typeName);
75 assert(*typeName);
76 TheMakers().emplace(typeName, maker);
77 }
78
79 void
80 Acl::SetKey(SBuf &keyStorage, const char *keyParameterName, const char *newKey)
81 {
82 if (!newKey) {
83 throw TextException(ToSBuf("An acl declaration is missing a ", keyParameterName,
84 Debug::Extra, "ACL name: ", AclMatchedName),
85 Here());
86 }
87
88 if (keyStorage.isEmpty()) {
89 keyStorage = newKey;
90 return;
91 }
92
93 if (keyStorage.caseCmp(newKey) == 0)
94 return; // no change
95
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)."),
102 Here());
103 }
104
105 void *
106 ACL::operator new (size_t)
107 {
108 fatal ("unusable ACL::new");
109 return (void *)1;
110 }
111
112 void
113 ACL::operator delete (void *)
114 {
115 fatal ("unusable ACL::delete");
116 }
117
118 ACL *
119 ACL::FindByName(const char *name)
120 {
121 ACL *a;
122 debugs(28, 9, "ACL::FindByName '" << name << "'");
123
124 for (a = Config.aclList; a; a = a->next)
125 if (!strcasecmp(a->name, name))
126 return a;
127
128 debugs(28, 9, "ACL::FindByName found no match");
129
130 return nullptr;
131 }
132
133 ACL::ACL() :
134 cfgline(nullptr),
135 next(nullptr),
136 registered(false)
137 {
138 *name = 0;
139 }
140
141 bool ACL::valid () const
142 {
143 return true;
144 }
145
146 bool
147 ACL::matches(ACLChecklist *checklist) const
148 {
149 debugs(28, 5, "checking " << name);
150
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;
155
156 int result = 0;
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.");
166 } else {
167 // make sure the ALE has as much data as possible
168 if (requiresAle())
169 checklist->verifyAle();
170
171 // have to cast because old match() API is missing const
172 result = const_cast<ACL*>(this)->match(checklist);
173 }
174
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
178 }
179
180 void
181 ACL::context(const char *aName, const char *aCfgLine)
182 {
183 name[0] = '\0';
184 if (aName)
185 xstrncpy(name, aName, ACL_NAME_SZ-1);
186 safe_free(cfgline);
187 if (aCfgLine)
188 cfgline = xstrdup(aCfgLine);
189 }
190
191 void
192 ACL::ParseAclLine(ConfigParser &parser, ACL ** head)
193 {
194 /* we're already using strtok() to grok the line */
195 char *t = nullptr;
196 ACL *A = nullptr;
197 LOCAL_ARRAY(char, aclname, ACL_NAME_SZ);
198 int new_acl = 0;
199
200 /* snarf the ACL name */
201
202 if ((t = ConfigParser::NextToken()) == nullptr) {
203 debugs(28, DBG_CRITICAL, "ERROR: aclParseAclLine: missing ACL name.");
204 parser.destruct();
205 return;
206 }
207
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");
211 parser.destruct();
212 return;
213 }
214
215 xstrncpy(aclname, t, ACL_NAME_SZ);
216 /* snarf the ACL type */
217 const char *theType;
218
219 if ((theType = ConfigParser::NextToken()) == nullptr) {
220 debugs(28, DBG_CRITICAL, "ERROR: aclParseAclLine: missing ACL type.");
221 parser.destruct();
222 return;
223 }
224
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.");
232 p = p->next;
233 }
234 debugs(28, DBG_IMPORTANT, "WARNING: UPGRADE: ACL 'myip' type has been renamed to 'localip' and matches the IP the client connected to.");
235 theType = "localip";
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.");
243 p = p->next;
244 }
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";
254 }
255
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);
260 new_acl = 1;
261 } else {
262 if (strcmp (A->typeString(),theType) ) {
263 debugs(28, DBG_CRITICAL, "aclParseAclLine: ACL '" << A->name << "' already exists with different type.");
264 parser.destruct();
265 return;
266 }
267
268 debugs(28, 3, "aclParseAclLine: Appending to '" << aclname << "'");
269 new_acl = 0;
270 }
271
272 /*
273 * Here we set AclMatchedName in case we need to use it in a
274 * warning message in aclDomainCompare().
275 */
276 AclMatchedName = A->name; /* ugly */
277
278 A->parseFlags();
279
280 /*split the function here */
281 A->parse();
282
283 /*
284 * Clear AclMatchedName from our temporary hack
285 */
286 AclMatchedName = nullptr; /* ugly */
287
288 if (!new_acl)
289 return;
290
291 if (A->empty()) {
292 debugs(28, DBG_CRITICAL, "WARNING: empty ACL: " << A->cfgline);
293 }
294
295 if (!A->valid()) {
296 fatalf("ERROR: Invalid ACL: %s\n",
297 A->cfgline);
298 }
299
300 // add to the global list for searching explicit ACLs by name
301 assert(head && *head == Config.aclList);
302 A->next = *head;
303 *head = A;
304
305 // register for centralized cleanup
306 aclRegister(A);
307 }
308
309 bool
310 ACL::isProxyAuth() const
311 {
312 return false;
313 }
314
315 void
316 ACL::parseFlags()
317 {
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);
322 }
323 Acl::ParseFlags(allOptions);
324 }
325
326 void
327 ACL::dumpWhole(const char * const directiveName, std::ostream &os)
328 {
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(" ") <<
333 '\n';
334 }
335
336 /* ACL result caching routines */
337
338 int
339 ACL::matchForCache(ACLChecklist *)
340 {
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 */
345 }
346
347 /*
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)
353 * RBC
354 * TODO: does a dlink_list perform well enough? Kinkie
355 */
356 int
357 ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist)
358 {
359 acl_proxy_auth_match_cache *auth_match;
360 dlink_node *link;
361 link = cache->head;
362
363 while (link) {
364 auth_match = (acl_proxy_auth_match_cache *)link->data;
365
366 if (auth_match->acl_data == this) {
367 debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name << "' (" << this << ")");
368 return auth_match->matchrv;
369 }
370
371 link = link->next;
372 }
373
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;
378 }
379
380 void
381 aclCacheMatchFlush(dlink_list * cache)
382 {
383 acl_proxy_auth_match_cache *auth_match;
384 dlink_node *link, *tmplink;
385 link = cache->head;
386
387 debugs(28, 8, "aclCacheMatchFlush called for cache " << cache);
388
389 while (link) {
390 auth_match = (acl_proxy_auth_match_cache *)link->data;
391 tmplink = link;
392 link = link->next;
393 dlinkDelete(tmplink, cache);
394 delete auth_match;
395 }
396 }
397
398 bool
399 ACL::requiresAle() const
400 {
401 return false;
402 }
403
404 bool
405 ACL::requiresReply() const
406 {
407 return false;
408 }
409
410 bool
411 ACL::requiresRequest() const
412 {
413 return false;
414 }
415
416 /*********************/
417 /* Destroy functions */
418 /*********************/
419
420 ACL::~ACL()
421 {
422 debugs(28, 3, "freeing ACL " << name);
423 safe_free(cfgline);
424 AclMatchedName = nullptr; // in case it was pointing to our name
425 }
426
427 void
428 ACL::Initialize()
429 {
430 ACL *a = Config.aclList;
431 debugs(53, 3, "ACL::Initialize");
432
433 while (a) {
434 a->prepareForUse();
435 a = a->next;
436 }
437 }
438