]> 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-2019 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 "cache_cf.h"
18 #include "ConfigParser.h"
19 #include "Debug.h"
20 #include "fatal.h"
21 #include "globals.h"
22 #include "profiler/Profiler.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 = NULL;
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::operator new (size_t)
81 {
82 fatal ("unusable ACL::new");
83 return (void *)1;
84 }
85
86 void
87 ACL::operator delete (void *)
88 {
89 fatal ("unusable ACL::delete");
90 }
91
92 ACL *
93 ACL::FindByName(const char *name)
94 {
95 ACL *a;
96 debugs(28, 9, "ACL::FindByName '" << name << "'");
97
98 for (a = Config.aclList; a; a = a->next)
99 if (!strcasecmp(a->name, name))
100 return a;
101
102 debugs(28, 9, "ACL::FindByName found no match");
103
104 return NULL;
105 }
106
107 ACL::ACL() :
108 cfgline(nullptr),
109 next(nullptr),
110 registered(false)
111 {
112 *name = 0;
113 }
114
115 bool ACL::valid () const
116 {
117 return true;
118 }
119
120 bool
121 ACL::matches(ACLChecklist *checklist) const
122 {
123 PROF_start(ACL_matches);
124 debugs(28, 5, "checking " << name);
125
126 // XXX: AclMatchedName does not contain a matched ACL name when the acl
127 // does not match. It contains the last (usually leaf) ACL name checked
128 // (or is NULL if no ACLs were checked).
129 AclMatchedName = name;
130
131 int result = 0;
132 if (!checklist->hasAle() && requiresAle()) {
133 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
134 "context without an ALE state. Assuming mismatch.");
135 } else if (!checklist->hasRequest() && requiresRequest()) {
136 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
137 "context without an HTTP request. Assuming mismatch.");
138 } else if (!checklist->hasReply() && requiresReply()) {
139 debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
140 "context without an HTTP response. Assuming mismatch.");
141 } else {
142 // make sure the ALE has as much data as possible
143 if (requiresAle())
144 checklist->verifyAle();
145
146 // have to cast because old match() API is missing const
147 result = const_cast<ACL*>(this)->match(checklist);
148 }
149
150 const char *extra = checklist->asyncInProgress() ? " async" : "";
151 debugs(28, 3, "checked: " << name << " = " << result << extra);
152 PROF_stop(ACL_matches);
153 return result == 1; // true for match; false for everything else
154 }
155
156 void
157 ACL::context(const char *aName, const char *aCfgLine)
158 {
159 name[0] = '\0';
160 if (aName)
161 xstrncpy(name, aName, ACL_NAME_SZ-1);
162 safe_free(cfgline);
163 if (aCfgLine)
164 cfgline = xstrdup(aCfgLine);
165 }
166
167 void
168 ACL::ParseAclLine(ConfigParser &parser, ACL ** head)
169 {
170 /* we're already using strtok() to grok the line */
171 char *t = NULL;
172 ACL *A = NULL;
173 LOCAL_ARRAY(char, aclname, ACL_NAME_SZ);
174 int new_acl = 0;
175
176 /* snarf the ACL name */
177
178 if ((t = ConfigParser::NextToken()) == NULL) {
179 debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL name.");
180 parser.destruct();
181 return;
182 }
183
184 if (strlen(t) >= ACL_NAME_SZ) {
185 debugs(28, DBG_CRITICAL, "aclParseAclLine: aclParseAclLine: ACL name '" << t <<
186 "' too long, max " << ACL_NAME_SZ - 1 << " characters supported");
187 parser.destruct();
188 return;
189 }
190
191 xstrncpy(aclname, t, ACL_NAME_SZ);
192 /* snarf the ACL type */
193 const char *theType;
194
195 if ((theType = ConfigParser::NextToken()) == NULL) {
196 debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL type.");
197 parser.destruct();
198 return;
199 }
200
201 // Is this ACL going to work?
202 if (strcmp(theType, "myip") == 0) {
203 AnyP::PortCfgPointer p = HttpPortList;
204 while (p != NULL) {
205 // Bug 3239: not reliable when there is interception traffic coming
206 if (p->flags.natIntercept)
207 debugs(28, DBG_CRITICAL, "WARNING: 'myip' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
208 p = p->next;
209 }
210 debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myip' type is has been renamed to 'localip' and matches the IP the client connected to.");
211 theType = "localip";
212 } else if (strcmp(theType, "myport") == 0) {
213 AnyP::PortCfgPointer p = HttpPortList;
214 while (p != NULL) {
215 // Bug 3239: not reliable when there is interception traffic coming
216 // Bug 3239: myport - not reliable (yet) when there is interception traffic coming
217 if (p->flags.natIntercept)
218 debugs(28, DBG_CRITICAL, "WARNING: 'myport' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
219 p = p->next;
220 }
221 theType = "localport";
222 debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myport' type is has been renamed to 'localport' and matches the port the client connected to.");
223 } else if (strcmp(theType, "proto") == 0 && strcmp(aclname, "manager") == 0) {
224 // ACL manager is now a built-in and has a different type.
225 debugs(28, DBG_PARSE_NOTE(DBG_IMPORTANT), "UPGRADE: ACL 'manager' is now a built-in ACL. Remove it from your config file.");
226 return; // ignore the line
227 } else if (strcmp(theType, "clientside_mark") == 0) {
228 debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'clientside_mark' type has been renamed to 'client_connection_mark'.");
229 theType = "client_connection_mark";
230 }
231
232 if ((A = FindByName(aclname)) == NULL) {
233 debugs(28, 3, "aclParseAclLine: Creating ACL '" << aclname << "'");
234 A = Acl::Make(theType);
235 A->context(aclname, config_input_line);
236 new_acl = 1;
237 } else {
238 if (strcmp (A->typeString(),theType) ) {
239 debugs(28, DBG_CRITICAL, "aclParseAclLine: ACL '" << A->name << "' already exists with different type.");
240 parser.destruct();
241 return;
242 }
243
244 debugs(28, 3, "aclParseAclLine: Appending to '" << aclname << "'");
245 new_acl = 0;
246 }
247
248 /*
249 * Here we set AclMatchedName in case we need to use it in a
250 * warning message in aclDomainCompare().
251 */
252 AclMatchedName = A->name; /* ugly */
253
254 A->parseFlags();
255
256 /*split the function here */
257 A->parse();
258
259 /*
260 * Clear AclMatchedName from our temporary hack
261 */
262 AclMatchedName = NULL; /* ugly */
263
264 if (!new_acl)
265 return;
266
267 if (A->empty()) {
268 debugs(28, DBG_CRITICAL, "Warning: empty ACL: " << A->cfgline);
269 }
270
271 if (!A->valid()) {
272 fatalf("ERROR: Invalid ACL: %s\n",
273 A->cfgline);
274 }
275
276 // add to the global list for searching explicit ACLs by name
277 assert(head && *head == Config.aclList);
278 A->next = *head;
279 *head = A;
280
281 // register for centralized cleanup
282 aclRegister(A);
283 }
284
285 bool
286 ACL::isProxyAuth() const
287 {
288 return false;
289 }
290
291 void
292 ACL::parseFlags()
293 {
294 // ACL kids that carry ACLData which supports parameter flags override this
295 Acl::ParseFlags(options(), Acl::NoFlags());
296 }
297
298 SBufList
299 ACL::dumpOptions()
300 {
301 SBufList result;
302 const auto &myOptions = options();
303 // optimization: most ACLs do not have myOptions
304 // this check also works around dump_SBufList() adding ' ' after empty items
305 if (!myOptions.empty()) {
306 SBufStream stream;
307 stream << myOptions;
308 const SBuf optionsImage = stream.buf();
309 if (!optionsImage.isEmpty())
310 result.push_back(optionsImage);
311 }
312 return result;
313 }
314
315 /* ACL result caching routines */
316
317 int
318 ACL::matchForCache(ACLChecklist *)
319 {
320 /* This is a fatal to ensure that cacheMatchAcl calls are _only_
321 * made for supported acl types */
322 fatal("aclCacheMatchAcl: unknown or unexpected ACL type");
323 return 0; /* NOTREACHED */
324 }
325
326 /*
327 * we lookup an acl's cached results, and if we cannot find the acl being
328 * checked we check it and cache the result. This function is a template
329 * method to support caching of multiple acl types.
330 * Note that caching of time based acl's is not
331 * wise in long lived caches (i.e. the auth_user proxy match cache)
332 * RBC
333 * TODO: does a dlink_list perform well enough? Kinkie
334 */
335 int
336 ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist)
337 {
338 acl_proxy_auth_match_cache *auth_match;
339 dlink_node *link;
340 link = cache->head;
341
342 while (link) {
343 auth_match = (acl_proxy_auth_match_cache *)link->data;
344
345 if (auth_match->acl_data == this) {
346 debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name << "' (" << this << ")");
347 return auth_match->matchrv;
348 }
349
350 link = link->next;
351 }
352
353 auth_match = new acl_proxy_auth_match_cache(matchForCache(checklist), this);
354 dlinkAddTail(auth_match, &auth_match->link, cache);
355 debugs(28, 4, "ACL::cacheMatchAcl: miss for '" << name << "'. Adding result " << auth_match->matchrv);
356 return auth_match->matchrv;
357 }
358
359 void
360 aclCacheMatchFlush(dlink_list * cache)
361 {
362 acl_proxy_auth_match_cache *auth_match;
363 dlink_node *link, *tmplink;
364 link = cache->head;
365
366 debugs(28, 8, "aclCacheMatchFlush called for cache " << cache);
367
368 while (link) {
369 auth_match = (acl_proxy_auth_match_cache *)link->data;
370 tmplink = link;
371 link = link->next;
372 dlinkDelete(tmplink, cache);
373 delete auth_match;
374 }
375 }
376
377 bool
378 ACL::requiresAle() const
379 {
380 return false;
381 }
382
383 bool
384 ACL::requiresReply() const
385 {
386 return false;
387 }
388
389 bool
390 ACL::requiresRequest() const
391 {
392 return false;
393 }
394
395 /*********************/
396 /* Destroy functions */
397 /*********************/
398
399 ACL::~ACL()
400 {
401 debugs(28, 3, "freeing ACL " << name);
402 safe_free(cfgline);
403 AclMatchedName = NULL; // in case it was pointing to our name
404 }
405
406 void
407 ACL::Initialize()
408 {
409 ACL *a = Config.aclList;
410 debugs(53, 3, "ACL::Initialize");
411
412 while (a) {
413 a->prepareForUse();
414 a = a->next;
415 }
416 }
417