4 * DEBUG: section 28 Access Control
5 * AUTHOR: Duane Wessels
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
38 #include "acl/Checklist.h"
43 ACLIP::operator new (size_t byteCount
)
45 fatal ("ACLIP::operator new: unused");
50 ACLIP::operator delete (void *address
)
52 fatal ("ACLIP::operator delete: unused");
56 * Writes an IP ACL data into a buffer, then copies the buffer into the wordlist given
58 \param ip ACL data structure to display
59 \param state wordlist structure which is being generated
62 ACLIP::DumpIpListWalkee(acl_ip_data
* const & ip
, void *state
)
64 char tmpbuf
[ ((MAX_IPSTRLEN
*2)+6) ]; // space for 2 IPs and a CIDR mask(3) and seperators(3).
66 wordlist
**W
= static_cast<wordlist
**>(state
);
70 assert(mb
.max_capacity
> 0 && 1==1 );
72 ip
->toStr(tmpbuf
, sizeof(tmpbuf
) );
73 assert(mb
.max_capacity
> 0 && 2==2 );
74 mb
.append(tmpbuf
, strlen(tmpbuf
) );
75 assert(mb
.max_capacity
> 0 && 3==3);
76 wordlistAdd(W
, mb
.buf
);
81 * print/format an acl_ip_data structure for debugging output.
83 \param buf string buffer to write to
84 \param len size of the buffer available
87 acl_ip_data::toStr(char *buf
, int len
) const
94 addr1
.NtoA(b1
, len
- rlen
);
98 if (!addr2
.IsAnyAddr()) {
101 addr2
.NtoA(&(b2
[1]), len
- rlen
);
108 if (!mask
.IsNoAddr()) {
112 snprintf(&(b3
[1]), (len
-rlen
), "%u", mask
.GetCIDR() - (addr1
.IsIPv4()?96:0) );
114 snprintf(&(b3
[1]), (len
-rlen
), "%u", mask
.GetCIDR() );
121 * aclIpAddrNetworkCompare - The guts of the comparison for IP ACLs
122 * matching checks. The first argument (p) is a "host" address,
123 * i.e. the IP address of a cache client. The second argument (q)
124 * is an entry in some address-based access control element. This
125 * function is called via ACLIP::match() and the splay library.
128 aclIpAddrNetworkCompare(acl_ip_data
* const &p
, acl_ip_data
* const &q
)
130 IpAddress A
= p
->addr1
;
133 A
.ApplyMask(q
->mask
);
135 debugs(28,9, "aclIpAddrNetworkCompare: compare: " << p
->addr1
<< "/" << q
->mask
<< " (" << A
<< ") vs " <<
136 q
->addr1
<< "-" << q
->addr2
<< "/" << q
->mask
);
138 if (q
->addr2
.IsAnyAddr()) { /* single address check */
140 return A
.matchIPAddr( q
->addr1
);
142 } else { /* range address check */
144 if ( (A
>= q
->addr1
) && (A
<= q
->addr2
) )
145 return 0; /* valid. inside range. */
147 return A
.matchIPAddr( q
->addr1
); /* outside of range, 'less than' */
153 * acl_ip_data::NetworkCompare - Compare two acl_ip_data entries. Strictly
154 * used by the splay insertion routine. It emits a warning if it
155 * detects a "collision" or overlap that would confuse the splay
156 * sorting algorithm. Much like aclDomainCompare.
157 * The first argument (p) is a "host" address, i.e. the IP address of a cache client.
158 * The second argument (b) is a "network" address that might have a subnet and/or range.
159 * We mask the host address bits with the network subnet mask.
162 acl_ip_data::NetworkCompare(acl_ip_data
* const & a
, acl_ip_data
* const &b
)
166 ret
= aclIpAddrNetworkCompare(b
, a
);
170 ret
= aclIpAddrNetworkCompare(a
, b
);
174 char buf_n1
[3*(MAX_IPSTRLEN
+1)];
175 char buf_n2
[3*(MAX_IPSTRLEN
+1)];
177 b
->toStr(buf_n1
, 3*(MAX_IPSTRLEN
+1));
178 a
->toStr(buf_n2
, 3*(MAX_IPSTRLEN
+1));
180 a
->toStr(buf_n1
, 3*(MAX_IPSTRLEN
+1));
181 b
->toStr(buf_n2
, 3*(MAX_IPSTRLEN
+1));
183 debugs(28, 0, "WARNING: (" << (bina
?'B':'A') << ") '" << buf_n1
<< "' is a subnetwork of (" << (bina
?'A':'B') << ") '" << buf_n2
<< "'");
184 debugs(28, 0, "WARNING: because of this '" << (bina
?buf_n2
:buf_n1
) << "' is ignored to keep splay tree searching predictable");
185 debugs(28, 0, "WARNING: You should probably remove '" << buf_n1
<< "' from the ACL named '" << AclMatchedName
<< "'");
192 * Decode an ascii representation (asc) of a IP netmask address or CIDR,
193 * and place resulting information in mask.
194 * This function should NOT be called if 'asc' is a hostname!
197 acl_ip_data::DecodeMask(const char *asc
, IpAddress
&mask
, int ctype
)
202 /* default is a mask that doesn't change any IP */
209 /* An int mask 128, 32 */
210 if ((sscanf(asc
, "%d%c", &a1
, &junk
)==1) &&
211 (a1
<= 128) && (a1
>= 0)
213 return mask
.ApplyMask(a1
, ctype
);
216 /* dotted notation */
217 /* assignment returns true if asc contained an IP address as text */
219 /* HACK: IPv4 netmasks don't cleanly map to IPv6 masks. */
220 debugs(28, DBG_CRITICAL
, "WARNING: Netmasks are deprecated. Please use CIDR masks instead.");
222 /* locate what CIDR mask was _probably_ meant to be in its native protocol format. */
223 /* this will completely crap out with a security fail-open if the admin is playing mask tricks */
224 /* however, thats their fault, and we do warn. see bug 2601 for the effects if we don't do this. */
225 unsigned int m
= mask
.GetCIDR();
227 debugs(28, DBG_CRITICAL
, "WARNING: IPv4 netmasks are particularly nasty when used to compare IPv6 to IPv4 ranges.");
229 debugs(28, DBG_CRITICAL
, "WARNING: For now we will assume you meant to write /" << m
);
230 /* reset the mask completely, and crop to the CIDR boundary back properly. */
232 return mask
.ApplyMask(m
,AF_INET
);
240 /* Handle either type of address, IPv6 will be discarded with a warning if disabled */
241 #define SCAN_ACL1_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]/%[0123456789]"
242 #define SCAN_ACL2_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]%c"
243 #define SCAN_ACL3_6 "%[0123456789ABCDEFabcdef:]/%[0123456789]"
244 #define SCAN_ACL4_6 "%[0123456789ABCDEFabcdef:]/%c"
245 /* We DO need to know which is which though, for proper CIDR masking. */
246 #define SCAN_ACL1_4 "%[0123456789.]-%[0123456789.]/%[0123456789.]"
247 #define SCAN_ACL2_4 "%[0123456789.]-%[0123456789.]%c"
248 #define SCAN_ACL3_4 "%[0123456789.]/%[0123456789.]"
249 #define SCAN_ACL4_4 "%[0123456789.]/%c"
252 acl_ip_data::FactoryParse(const char *t
)
254 LOCAL_ARRAY(char, addr1
, 256);
255 LOCAL_ARRAY(char, addr2
, 256);
256 LOCAL_ARRAY(char, mask
, 256);
257 acl_ip_data
*r
= NULL
;
258 acl_ip_data
**Q
= NULL
;
261 unsigned int changed
;
262 acl_ip_data
*q
= new acl_ip_data
;
263 int iptype
= AF_UNSPEC
;
265 debugs(28, 5, "aclIpParseIpData: " << t
);
267 /* Special ACL RHS "all" matches entire Internet */
268 if (strcasecmp(t
, "all") == 0) {
269 debugs(28, 9, "aclIpParseIpData: magic 'all' found.");
270 q
->addr1
.SetAnyAddr();
272 q
->mask
.SetAnyAddr();
277 /* Special ACL RHS "ipv6" matches IPv6-Unicast Internet */
278 if (strcasecmp(t
, "ipv6") == 0) {
279 debugs(28, 9, "aclIpParseIpData: magic 'ipv6' found.");
281 /* AYJ: due to the nature os IPv6 this will not always work,
282 * we may need to turn recursive to catch all the valid v6 sub-nets. */
287 if (sscanf(t
, SCAN_ACL1_4
, addr1
, addr2
, mask
) == 3) {
288 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN1-v4: " << SCAN_ACL1_4
);
290 } else if (sscanf(t
, SCAN_ACL2_4
, addr1
, addr2
, &c
) >= 2) {
291 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN2-v4: " << SCAN_ACL2_4
);
294 } else if (sscanf(t
, SCAN_ACL3_4
, addr1
, mask
) == 2) {
295 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN3-v4: " << SCAN_ACL3_4
);
298 } else if (sscanf(t
, SCAN_ACL4_4
, addr1
,&c
) == 2) {
299 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN4-v4: " << SCAN_ACL4_4
);
305 } else if (sscanf(t
, SCAN_ACL1_6
, addr1
, addr2
, mask
) == 3) {
306 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN1-v6: " << SCAN_ACL1_6
);
308 } else if (sscanf(t
, SCAN_ACL2_6
, addr1
, addr2
, &c
) >= 2) {
309 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN2-v6: " << SCAN_ACL2_6
);
312 } else if (sscanf(t
, SCAN_ACL3_6
, addr1
, mask
) == 2) {
313 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN3-v6: " << SCAN_ACL3_6
);
316 } else if (sscanf(t
, SCAN_ACL4_6
, addr1
, mask
) == 2) {
317 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: SCAN4-v6: " << SCAN_ACL4_6
);
322 } else if (sscanf(t
, "%[^/]/%s", addr1
, mask
) == 2) {
323 debugs(28, 9, "aclIpParseIpData: '" << t
<< "' matched: non-IP pattern: %[^/]/%s");
325 } else if (sscanf(t
, "%s", addr1
) == 1) {
327 * Note, must use plain xgetaddrinfo() here because at startup
328 * ipcache hasn't been initialized
329 * TODO: offload this to one of the IpAddress lookups.
332 debugs(28, 5, "aclIpParseIpData: Lookup Host/IP " << addr1
);
333 struct addrinfo
*hp
= NULL
, *x
= NULL
;
334 struct addrinfo hints
;
335 IpAddress
*prev_addr
= NULL
;
337 memset(&hints
, 0, sizeof(struct addrinfo
));
339 if ( iptype
!= AF_UNSPEC
) {
340 hints
.ai_flags
|= AI_NUMERICHOST
;
343 #if 0 && USE_IPV6 && !IPV6_SPECIAL_SPLITSTACK
344 hints
.ai_flags
|= AI_V4MAPPED
| AI_ALL
;
347 int errcode
= xgetaddrinfo(addr1
,NULL
,&hints
,&hp
);
349 debugs(28, 0, "aclIpParseIpData: Bad host/IP: '" << addr1
<<
350 "' in '" << t
<< "', flags=" << hints
.ai_flags
<<
351 " : (" << errcode
<< ") " << xgai_strerror(errcode
) );
358 for (x
= hp
; x
!= NULL
;) {
359 if ((r
= *Q
) == NULL
)
360 r
= *Q
= new acl_ip_data
;
362 /* getaddrinfo given a host has a nasty tendency to return duplicate addr's */
363 /* BUT sorted fortunately, so we can drop most of them easily */
366 if ( prev_addr
&& r
->addr1
== *prev_addr
) {
367 debugs(28, 3, "aclIpParseIpData: Duplicate host/IP: '" << r
->addr1
<< "' dropped.");
372 prev_addr
= &r
->addr1
;
374 debugs(28, 3, "aclIpParseIpData: Located host/IP: '" << r
->addr1
<< "'");
376 r
->addr2
.SetAnyAddr();
381 debugs(28, 3, "" << addr1
<< " --> " << r
->addr1
);
385 debugs(28, 0, "aclIpParseIpData: Bad host/IP: '" << t
<< "'");
396 /* ignore IPv6 addresses when built with IPv4-only */
397 if ( iptype
== AF_INET6
) {
398 debugs(28, 0, "aclIpParseIpData: IPv6 has not been enabled. build with '--enable-ipv6'");
404 if (!*addr1
|| !(q
->addr1
= addr1
)) {
405 debugs(28, 0, "aclIpParseIpData: unknown first address in '" << t
<< "'");
413 q
->addr2
.SetAnyAddr();
414 else if (!(q
->addr2
=addr2
) ) {
415 debugs(28, 0, "aclIpParseIpData: unknown second address in '" << t
<< "'");
421 /* Decode mask (NULL or empty means a exact host mask) */
422 if (!DecodeMask(mask
, q
->mask
, iptype
)) {
423 debugs(28, 0, "aclParseIpData: unknown netmask '" << mask
<< "' in '" << t
<< "'");
430 changed
+= q
->addr1
.ApplyMask(q
->mask
);
431 changed
+= q
->addr2
.ApplyMask(q
->mask
);
434 debugs(28, 0, "aclIpParseIpData: WARNING: Netmask masks away part of the specified IP in '" << t
<< "'");
436 debugs(28,9, HERE
<< "Parsed: " << q
->addr1
<< "-" << q
->addr2
<< "/" << q
->mask
<< "(/" << q
->mask
.GetCIDR() <<")");
438 /* 1.2.3.4/255.255.255.0 --> 1.2.3.0 */
439 /* Same as IPv6 (not so trivial to depict) */
448 while ((t
= strtokFile())) {
449 acl_ip_data
*q
= acl_ip_data::FactoryParse(t
);
452 data
= data
->insert(q
, acl_ip_data::NetworkCompare
);
461 data
->destroy(IPSplay::DefaultFree
);
468 data
->walk (DumpIpListWalkee
, &w
);
473 ACLIP::empty () const
475 return data
->empty();
479 ACLIP::match(IpAddress
&clientip
)
481 static acl_ip_data ClientAddress
;
483 * aclIpAddrNetworkCompare() takes two acl_ip_data pointers as
484 * arguments, so we must create a fake one for the client's IP
485 * address. Since we are scanning for a single IP mask and addr2
486 * MUST be set to empty.
488 ClientAddress
.addr1
= clientip
;
489 ClientAddress
.addr2
.SetEmpty();
490 ClientAddress
.mask
.SetEmpty();
492 data
= data
->splay(&ClientAddress
, aclIpAddrNetworkCompare
);
493 debugs(28, 3, "aclIpMatchIp: '" << clientip
<< "' " << (splayLastResult
? "NOT found" : "found"));
494 return !splayLastResult
;
497 acl_ip_data::acl_ip_data () :addr1(), addr2(), mask(), next (NULL
) {}
499 acl_ip_data::acl_ip_data (IpAddress
const &anAddress1
, IpAddress
const &anAddress2
, IpAddress
const &aMask
, acl_ip_data
*aNext
) : addr1(anAddress1
), addr2(anAddress2
), mask(aMask
), next(aNext
) {}