2 * Copyright (C) 1996-2021 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 */
12 #include "acl/Checklist.h"
15 #include "ConfigParser.h"
22 ACLIP::operator new (size_t)
24 fatal ("ACLIP::operator new: unused");
29 ACLIP::operator delete (void *)
31 fatal ("ACLIP::operator delete: unused");
35 * print/format an acl_ip_data structure for debugging output.
37 \param buf string buffer to write to
38 \param len size of the buffer available
41 acl_ip_data::toStr(char *buf
, int len
) const
48 addr1
.toStr(b1
, len
- rlen
);
52 if (!addr2
.isAnyAddr()) {
55 addr2
.toStr(&(b2
[1]), len
- rlen
);
62 if (!mask
.isNoAddr()) {
65 int cidr
= mask
.cidr() - (addr1
.isIPv4()?96:0);
66 snprintf(&(b3
[1]), (len
-rlen
), "%u", (unsigned int)(cidr
<0?0:cidr
) );
72 acl_ip_data::toSBuf() const
74 const int bufsz
= MAX_IPSTRLEN
*2+6;
75 static char tmpbuf
[ bufsz
];
81 * aclIpAddrNetworkCompare - The guts of the comparison for IP ACLs
82 * matching checks. The first argument (p) is a "host" address,
83 * i.e. the IP address of a cache client. The second argument (q)
84 * is an entry in some address-based access control element. This
85 * function is called via ACLIP::match() and the splay library.
88 aclIpAddrNetworkCompare(acl_ip_data
* const &p
, acl_ip_data
* const &q
)
90 Ip::Address A
= p
->addr1
;
95 debugs(28,9, "aclIpAddrNetworkCompare: compare: " << p
->addr1
<< "/" << q
->mask
<< " (" << A
<< ") vs " <<
96 q
->addr1
<< "-" << q
->addr2
<< "/" << q
->mask
);
98 if (q
->addr2
.isAnyAddr()) { /* single address check */
100 return A
.matchIPAddr( q
->addr1
);
102 } else { /* range address check */
104 if ( (A
>= q
->addr1
) && (A
<= q
->addr2
) )
105 return 0; /* valid. inside range. */
107 return A
.matchIPAddr( q
->addr1
); /* outside of range, 'less than' */
112 * acl_ip_data::NetworkCompare - Compare two acl_ip_data entries. Strictly
113 * used by the splay insertion routine. It emits a warning if it
114 * detects a "collision" or overlap that would confuse the splay
115 * sorting algorithm. Much like aclDomainCompare.
116 * The first argument (p) is a "host" address, i.e. the IP address of a cache client.
117 * The second argument (b) is a "network" address that might have a subnet and/or range.
118 * We mask the host address bits with the network subnet mask.
121 acl_ip_data::NetworkCompare(acl_ip_data
* const & a
, acl_ip_data
* const &b
)
125 ret
= aclIpAddrNetworkCompare(b
, a
);
129 ret
= aclIpAddrNetworkCompare(a
, b
);
133 char buf_n1
[3*(MAX_IPSTRLEN
+1)];
134 char buf_n2
[3*(MAX_IPSTRLEN
+1)];
136 b
->toStr(buf_n1
, 3*(MAX_IPSTRLEN
+1));
137 a
->toStr(buf_n2
, 3*(MAX_IPSTRLEN
+1));
139 a
->toStr(buf_n1
, 3*(MAX_IPSTRLEN
+1));
140 b
->toStr(buf_n2
, 3*(MAX_IPSTRLEN
+1));
142 debugs(28, DBG_CRITICAL
, "WARNING: (" << (bina
?'B':'A') << ") '" << buf_n1
<< "' is a subnetwork of (" << (bina
?'A':'B') << ") '" << buf_n2
<< "'");
143 debugs(28, DBG_CRITICAL
, "WARNING: because of this '" << (bina
?buf_n2
:buf_n1
) << "' is ignored to keep splay tree searching predictable");
144 debugs(28, DBG_CRITICAL
, "WARNING: You should probably remove '" << buf_n1
<< "' from the ACL named '" << AclMatchedName
<< "'");
151 * Decode an ascii representation (asc) of a IP netmask address or CIDR,
152 * and place resulting information in mask.
153 * This function should NOT be called if 'asc' is a hostname!
156 acl_ip_data::DecodeMask(const char *asc
, Ip::Address
&mask
, int ctype
)
161 /* default is a mask that doesn't change any IP */
168 /* An int mask 128, 32 */
169 if ((sscanf(asc
, "%d%c", &a1
, &junk
)==1) &&
170 (a1
<= 128) && (a1
>= 0)
172 return mask
.applyMask(a1
, ctype
);
175 /* dotted notation */
176 /* assignment returns true if asc contained an IP address as text */
178 /* HACK: IPv4 netmasks don't cleanly map to IPv6 masks. */
179 debugs(28, DBG_CRITICAL
, "WARNING: Netmasks are deprecated. Please use CIDR masks instead.");
181 /* locate what CIDR mask was _probably_ meant to be in its native protocol format. */
182 /* this will completely crap out with a security fail-open if the admin is playing mask tricks */
183 /* however, that's their fault, and we do warn. see bug 2601 for the effects if we don't do this. */
184 unsigned int m
= mask
.cidr();
185 debugs(28, DBG_CRITICAL
, "WARNING: IPv4 netmasks are particularly nasty when used to compare IPv6 to IPv4 ranges.");
186 debugs(28, DBG_CRITICAL
, "WARNING: For now we will assume you meant to write /" << m
);
187 /* reset the mask completely, and crop to the CIDR boundary back properly. */
189 return mask
.applyMask(m
,AF_INET
);
197 /* Handle either type of address, IPv6 will be discarded with a warning if disabled */
198 #define SCAN_ACL1_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]/%[0123456789]"
199 #define SCAN_ACL2_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]%c"
200 #define SCAN_ACL3_6 "%[0123456789ABCDEFabcdef:]/%[0123456789]"
201 #define SCAN_ACL4_6 "%[0123456789ABCDEFabcdef:]/%c"
202 /* We DO need to know which is which though, for proper CIDR masking. */
203 #define SCAN_ACL1_4 "%[0123456789.]-%[0123456789.]/%[0123456789.]"
204 #define SCAN_ACL2_4 "%[0123456789.]-%[0123456789.]%c"
205 #define SCAN_ACL3_4 "%[0123456789.]/%[0123456789.]"
206 #define SCAN_ACL4_4 "%[0123456789.]/%c"
209 acl_ip_data::FactoryParse(const char *t
)
211 LOCAL_ARRAY(char, addr1
, 256);
212 LOCAL_ARRAY(char, addr2
, 256);
213 LOCAL_ARRAY(char, mask
, 256);
214 acl_ip_data
*r
= NULL
;
215 acl_ip_data
**Q
= NULL
;
218 unsigned int changed
;
219 acl_ip_data
*q
= new acl_ip_data
;
220 int iptype
= AF_UNSPEC
;
222 debugs(28, 5, "aclIpParseIpData: " << t
);
224 /* Special ACL RHS "all" matches entire Internet */
225 if (strcmp(t
, "all") == 0) {
226 debugs(28, 9, "aclIpParseIpData: magic 'all' found.");
227 q
->addr1
.setAnyAddr();
229 q
->mask
.setAnyAddr();
233 /* Detect some old broken strings equivalent to 'all'.
234 * treat them nicely. But be loud until its fixed. */
235 if (strcmp(t
, "0/0") == 0 || strcmp(t
, "0.0.0.0/0") == 0 || strcmp(t
, "0.0.0.0/0.0.0.0") == 0 ||
236 strcmp(t
, "0.0.0.0-255.255.255.255") == 0 || strcmp(t
, "0.0.0.0-0.0.0.0/0") == 0) {
238 debugs(28,DBG_CRITICAL
, "ERROR: '" << t
<< "' needs to be replaced by the term 'all'.");
239 debugs(28,DBG_CRITICAL
, "SECURITY NOTICE: Overriding config setting. Using 'all' instead.");
240 q
->addr1
.setAnyAddr();
242 q
->mask
.setAnyAddr();
246 /* Special ACL RHS "ipv4" matches IPv4 Internet
247 * A nod to IANA; we include the entire class space in case
248 * they manage to find a way to recover and use it */
249 if (strcmp(t
, "ipv4") == 0) {
251 q
->mask
.applyMask(0, AF_INET
);
255 /* Special ACL RHS "ipv6" matches IPv6-Unicast Internet */
256 if (strcmp(t
, "ipv6") == 0) {
257 debugs(28, 9, "aclIpParseIpData: magic 'ipv6' found.");
258 r
= q
; // save head of the list for result.
260 /* 0000::/4 is a mix of localhost and obsolete IPv4-mapping space. Not valid outside this host. */
262 /* Future global unicast space: 1000::/4 */
265 q
->mask
.applyMask(4, AF_INET6
);
267 /* Current global unicast space: 2000::/4 = (2000::/4 - 3000::/4) */
268 q
->next
= new acl_ip_data
;
272 q
->mask
.applyMask(3, AF_INET6
);
274 /* Future global unicast space: 4000::/2 = (4000::/4 - 7000::/4) */
275 q
->next
= new acl_ip_data
;
279 q
->mask
.applyMask(2, AF_INET6
);
281 /* Future global unicast space: 8000::/2 = (8000::/4 - B000::/4) */
282 q
->next
= new acl_ip_data
;
286 q
->mask
.applyMask(2, AF_INET6
);
288 /* Future global unicast space: C000::/3 = (C000::/4 - D000::/4) */
289 q
->next
= new acl_ip_data
;
293 q
->mask
.applyMask(3, AF_INET6
);
295 /* Future global unicast space: E000::/4 */
296 q
->next
= new acl_ip_data
;
300 q
->mask
.applyMask(4, AF_INET6
);
302 /* F000::/4 is mostly reserved non-unicast. With some exceptions ... */
304 /* RFC 4193 Unique-Local unicast space: FC00::/7 */
305 q
->next
= new acl_ip_data
;
309 q
->mask
.applyMask(7, AF_INET6
);
311 /* Link-Local unicast space: FE80::/10 */
312 q
->next
= new acl_ip_data
;
316 q
->mask
.applyMask(10, AF_INET6
);
322 if (sscanf(t
, SCAN_ACL1_4
, addr1
, addr2
, mask
) == 3) {
323 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN1-v4: " << SCAN_ACL1_4
);
325 } else if (sscanf(t
, SCAN_ACL2_4
, addr1
, addr2
, &c
) >= 2) {
326 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN2-v4: " << SCAN_ACL2_4
);
329 } else if (sscanf(t
, SCAN_ACL3_4
, addr1
, mask
) == 2) {
330 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN3-v4: " << SCAN_ACL3_4
);
333 } else if (sscanf(t
, SCAN_ACL4_4
, addr1
,&c
) == 2) {
334 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN4-v4: " << SCAN_ACL4_4
);
340 } else if (sscanf(t
, SCAN_ACL1_6
, addr1
, addr2
, mask
) == 3) {
341 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN1-v6: " << SCAN_ACL1_6
);
343 } else if (sscanf(t
, SCAN_ACL2_6
, addr1
, addr2
, &c
) >= 2) {
344 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN2-v6: " << SCAN_ACL2_6
);
347 } else if (sscanf(t
, SCAN_ACL3_6
, addr1
, mask
) == 2) {
348 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN3-v6: " << SCAN_ACL3_6
);
351 } else if (sscanf(t
, SCAN_ACL4_6
, addr1
, mask
) == 2) {
352 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN4-v6: " << SCAN_ACL4_6
);
357 } else if (sscanf(t
, "%[^/]/%s", addr1
, mask
) == 2) {
358 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: non-IP pattern: %[^/]/%s");
360 } else if (sscanf(t
, "%s", addr1
) == 1) {
362 * Note, must use plain getaddrinfo() here because at startup
363 * ipcache hasn't been initialized
364 * TODO: offload this to one of the Ip::Address lookups.
367 debugs(28, 5, "aclIpParseIpData: Lookup Host/IP " << addr1
);
368 struct addrinfo
*hp
= NULL
, *x
= NULL
;
369 struct addrinfo hints
;
370 Ip::Address
*prev_addr
= NULL
;
372 memset(&hints
, 0, sizeof(struct addrinfo
));
374 int errcode
= getaddrinfo(addr1
,NULL
,&hints
,&hp
);
377 if (strcmp(addr1
, "::1") == 0) {
378 debugs(28, DBG_IMPORTANT
, "aclIpParseIpData: IPv6 has not been enabled in host DNS resolver.");
380 debugs(28, DBG_CRITICAL
, "aclIpParseIpData: Bad host/IP: '" << addr1
<<
381 "' in '" << t
<< "', flags=" << hints
.ai_flags
<<
382 " : (" << errcode
<< ") " << gai_strerror(errcode
) );
390 for (x
= hp
; x
!= NULL
;) {
391 if ((r
= *Q
) == NULL
)
392 r
= *Q
= new acl_ip_data
;
394 /* getaddrinfo given a host has a nasty tendency to return duplicate addr's */
395 /* BUT sorted fortunately, so we can drop most of them easily */
398 if ( prev_addr
&& r
->addr1
== *prev_addr
) {
399 debugs(28, 3, "aclIpParseIpData: Duplicate host/IP: '" << r
->addr1
<< "' dropped.");
404 prev_addr
= &r
->addr1
;
406 debugs(28, 3, "aclIpParseIpData: Located host/IP: '" << r
->addr1
<< "'");
408 r
->addr2
.setAnyAddr();
413 debugs(28, 3, "" << addr1
<< " --> " << r
->addr1
);
419 debugs(28, DBG_CRITICAL
, "aclIpParseIpData: Bad host/IP: '" << t
<< "'");
427 /* ignore IPv6 addresses when built with IPv4-only */
428 if ( iptype
== AF_INET6
&& !Ip::EnableIpv6
) {
429 debugs(28, DBG_IMPORTANT
, "aclIpParseIpData: IPv6 has not been enabled.");
435 if (!*addr1
|| !(q
->addr1
= addr1
)) {
436 debugs(28, DBG_CRITICAL
, "aclIpParseIpData: unknown first address in '" << t
<< "'");
444 q
->addr2
.setAnyAddr();
445 else if (!(q
->addr2
=addr2
) ) {
446 debugs(28, DBG_CRITICAL
, "aclIpParseIpData: unknown second address in '" << t
<< "'");
452 /* Decode mask (NULL or empty means a exact host mask) */
453 if (!DecodeMask(mask
, q
->mask
, iptype
)) {
454 debugs(28, DBG_CRITICAL
, "aclParseIpData: unknown netmask '" << mask
<< "' in '" << t
<< "'");
461 changed
+= q
->addr1
.applyMask(q
->mask
);
462 changed
+= q
->addr2
.applyMask(q
->mask
);
465 debugs(28, DBG_CRITICAL
, "aclIpParseIpData: WARNING: Netmask masks away part of the specified IP in '" << t
<< "'");
467 debugs(28,9, HERE
<< "Parsed: " << q
->addr1
<< "-" << q
->addr2
<< "/" << q
->mask
<< "(/" << q
->mask
.cidr() <<")");
469 /* 1.2.3.4/255.255.255.0 --> 1.2.3.0 */
470 /* Same as IPv6 (not so trivial to depict) */
478 data
= new IPSplay();
480 while (char *t
= ConfigParser::strtokFile()) {
481 acl_ip_data
*q
= acl_ip_data::FactoryParse(t
);
484 /* pop each result off the list and add it to the data tree individually */
485 acl_ip_data
*next_node
= q
->next
;
487 if (!data
->find(q
,acl_ip_data::NetworkCompare
))
488 data
->insert(q
, acl_ip_data::NetworkCompare
);
502 struct IpAclDumpVisitor
{
504 void operator() (acl_ip_data
* const & ip
) {
505 contents
.push_back(ip
->toSBuf());
512 IpAclDumpVisitor visitor
;
513 data
->visit(visitor
);
514 return visitor
.contents
;
520 return data
->empty();
524 ACLIP::match(const Ip::Address
&clientip
)
526 static acl_ip_data ClientAddress
;
528 * aclIpAddrNetworkCompare() takes two acl_ip_data pointers as
529 * arguments, so we must create a fake one for the client's IP
530 * address. Since we are scanning for a single IP mask and addr2
531 * MUST be set to empty.
533 ClientAddress
.addr1
= clientip
;
534 ClientAddress
.addr2
.setEmpty();
535 ClientAddress
.mask
.setEmpty();
537 const acl_ip_data
* const * result
= data
->find(&ClientAddress
, aclIpAddrNetworkCompare
);
538 debugs(28, 3, "aclIpMatchIp: '" << clientip
<< "' " << (result
? "found" : "NOT found"));
539 return (result
!= NULL
);
542 acl_ip_data::acl_ip_data() :addr1(), addr2(), mask(), next (NULL
) {}
544 acl_ip_data::acl_ip_data(Ip::Address
const &anAddress1
, Ip::Address
const &anAddress2
, Ip::Address
const &aMask
, acl_ip_data
*aNext
) : addr1(anAddress1
), addr2(anAddress2
), mask(aMask
), next(aNext
) {}