]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Ip.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Ip.cc
1 /*
2 * $Id$
3 *
4 * DEBUG: section 28 Access Control
5 * AUTHOR: Duane Wessels
6 *
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
9 *
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.
18 *
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.
23 *
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.
28 *
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.
32 *
33 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
34 */
35
36 #include "squid.h"
37 #include "acl/Ip.h"
38 #include "acl/Checklist.h"
39 #include "Debug.h"
40 #include "ip/tools.h"
41 #include "MemBuf.h"
42 #include "protos.h"
43 #include "wordlist.h"
44
45 void *
46 ACLIP::operator new (size_t byteCount)
47 {
48 fatal ("ACLIP::operator new: unused");
49 return (void *)1;
50 }
51
52 void
53 ACLIP::operator delete (void *address)
54 {
55 fatal ("ACLIP::operator delete: unused");
56 }
57
58 /**
59 * Writes an IP ACL data into a buffer, then copies the buffer into the wordlist given
60 *
61 \param ip ACL data structure to display
62 \param state wordlist structure which is being generated
63 */
64 void
65 ACLIP::DumpIpListWalkee(acl_ip_data * const & ip, void *state)
66 {
67 char tmpbuf[ ((MAX_IPSTRLEN*2)+6) ]; // space for 2 IPs and a CIDR mask(3) and seperators(3).
68 MemBuf mb;
69 wordlist **W = static_cast<wordlist **>(state);
70 tmpbuf[0] = '\0';
71
72 mb.init();
73 assert(mb.max_capacity > 0 && 1==1 );
74
75 ip->toStr(tmpbuf, sizeof(tmpbuf) );
76 assert(mb.max_capacity > 0 && 2==2 );
77 mb.append(tmpbuf, strlen(tmpbuf) );
78 assert(mb.max_capacity > 0 && 3==3);
79 wordlistAdd(W, mb.buf);
80 mb.clean();
81 }
82
83 /**
84 * print/format an acl_ip_data structure for debugging output.
85 *
86 \param buf string buffer to write to
87 \param len size of the buffer available
88 */
89 void
90 acl_ip_data::toStr(char *buf, int len) const
91 {
92 char *b1 = buf;
93 char *b2 = NULL;
94 char *b3 = NULL;
95 int rlen = 0;
96
97 addr1.NtoA(b1, len - rlen );
98 rlen = strlen(buf);
99 b2 = buf + rlen;
100
101 if (!addr2.IsAnyAddr()) {
102 b2[0] = '-';
103 ++rlen;
104 addr2.NtoA(&(b2[1]), len - rlen );
105 rlen = strlen(buf);
106 } else
107 b2[0] = '\0';
108
109 b3 = buf + rlen;
110
111 if (!mask.IsNoAddr()) {
112 b3[0] = '/';
113 ++rlen;
114 int cidr = mask.GetCIDR() - (addr1.IsIPv4()?96:0);
115 snprintf(&(b3[1]), (len-rlen), "%u", (unsigned int)(cidr<0?0:cidr) );
116 } else
117 b3[0] = '\0';
118 }
119
120 /*
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.
126 */
127 int
128 aclIpAddrNetworkCompare(acl_ip_data * const &p, acl_ip_data * const &q)
129 {
130 Ip::Address A = p->addr1;
131
132 /* apply netmask */
133 A.ApplyMask(q->mask);
134
135 debugs(28,9, "aclIpAddrNetworkCompare: compare: " << p->addr1 << "/" << q->mask << " (" << A << ") vs " <<
136 q->addr1 << "-" << q->addr2 << "/" << q->mask);
137
138 if (q->addr2.IsAnyAddr()) { /* single address check */
139
140 return A.matchIPAddr( q->addr1 );
141
142 } else { /* range address check */
143
144 if ( (A >= q->addr1) && (A <= q->addr2) )
145 return 0; /* valid. inside range. */
146 else
147 return A.matchIPAddr( q->addr1 ); /* outside of range, 'less than' */
148 }
149 }
150
151 /*
152 * acl_ip_data::NetworkCompare - Compare two acl_ip_data entries. Strictly
153 * used by the splay insertion routine. It emits a warning if it
154 * detects a "collision" or overlap that would confuse the splay
155 * sorting algorithm. Much like aclDomainCompare.
156 * The first argument (p) is a "host" address, i.e. the IP address of a cache client.
157 * The second argument (b) is a "network" address that might have a subnet and/or range.
158 * We mask the host address bits with the network subnet mask.
159 */
160 int
161 acl_ip_data::NetworkCompare(acl_ip_data * const & a, acl_ip_data * const &b)
162 {
163 int ret;
164 bool bina = true;
165 ret = aclIpAddrNetworkCompare(b, a);
166
167 if (ret != 0) {
168 bina = false;
169 ret = aclIpAddrNetworkCompare(a, b);
170 }
171
172 if (ret == 0) {
173 char buf_n1[3*(MAX_IPSTRLEN+1)];
174 char buf_n2[3*(MAX_IPSTRLEN+1)];
175 if (bina) {
176 b->toStr(buf_n1, 3*(MAX_IPSTRLEN+1));
177 a->toStr(buf_n2, 3*(MAX_IPSTRLEN+1));
178 } else {
179 a->toStr(buf_n1, 3*(MAX_IPSTRLEN+1));
180 b->toStr(buf_n2, 3*(MAX_IPSTRLEN+1));
181 }
182 debugs(28, DBG_CRITICAL, "WARNING: (" << (bina?'B':'A') << ") '" << buf_n1 << "' is a subnetwork of (" << (bina?'A':'B') << ") '" << buf_n2 << "'");
183 debugs(28, DBG_CRITICAL, "WARNING: because of this '" << (bina?buf_n2:buf_n1) << "' is ignored to keep splay tree searching predictable");
184 debugs(28, DBG_CRITICAL, "WARNING: You should probably remove '" << buf_n1 << "' from the ACL named '" << AclMatchedName << "'");
185 }
186
187 return ret;
188 }
189
190 /**
191 * Decode an ascii representation (asc) of a IP netmask address or CIDR,
192 * and place resulting information in mask.
193 * This function should NOT be called if 'asc' is a hostname!
194 */
195 bool
196 acl_ip_data::DecodeMask(const char *asc, Ip::Address &mask, int ctype)
197 {
198 char junk;
199 int a1 = 0;
200
201 /* default is a mask that doesn't change any IP */
202 mask.SetNoAddr();
203
204 if (!asc || !*asc) {
205 return true;
206 }
207
208 /* An int mask 128, 32 */
209 if ((sscanf(asc, "%d%c", &a1, &junk)==1) &&
210 (a1 <= 128) && (a1 >= 0)
211 ) {
212 return mask.ApplyMask(a1, ctype);
213 }
214
215 /* dotted notation */
216 /* assignment returns true if asc contained an IP address as text */
217 if ((mask = asc)) {
218 /* HACK: IPv4 netmasks don't cleanly map to IPv6 masks. */
219 debugs(28, DBG_CRITICAL, "WARNING: Netmasks are deprecated. Please use CIDR masks instead.");
220 if (mask.IsIPv4()) {
221 /* locate what CIDR mask was _probably_ meant to be in its native protocol format. */
222 /* this will completely crap out with a security fail-open if the admin is playing mask tricks */
223 /* however, thats their fault, and we do warn. see bug 2601 for the effects if we don't do this. */
224 unsigned int m = mask.GetCIDR();
225 debugs(28, DBG_CRITICAL, "WARNING: IPv4 netmasks are particularly nasty when used to compare IPv6 to IPv4 ranges.");
226 debugs(28, DBG_CRITICAL, "WARNING: For now we will assume you meant to write /" << m);
227 /* reset the mask completely, and crop to the CIDR boundary back properly. */
228 mask.SetNoAddr();
229 return mask.ApplyMask(m,AF_INET);
230 }
231 return true;
232 }
233
234 return false;
235 }
236
237 /* Handle either type of address, IPv6 will be discarded with a warning if disabled */
238 #define SCAN_ACL1_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]/%[0123456789]"
239 #define SCAN_ACL2_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]%c"
240 #define SCAN_ACL3_6 "%[0123456789ABCDEFabcdef:]/%[0123456789]"
241 #define SCAN_ACL4_6 "%[0123456789ABCDEFabcdef:]/%c"
242 /* We DO need to know which is which though, for proper CIDR masking. */
243 #define SCAN_ACL1_4 "%[0123456789.]-%[0123456789.]/%[0123456789.]"
244 #define SCAN_ACL2_4 "%[0123456789.]-%[0123456789.]%c"
245 #define SCAN_ACL3_4 "%[0123456789.]/%[0123456789.]"
246 #define SCAN_ACL4_4 "%[0123456789.]/%c"
247
248 acl_ip_data *
249 acl_ip_data::FactoryParse(const char *t)
250 {
251 LOCAL_ARRAY(char, addr1, 256);
252 LOCAL_ARRAY(char, addr2, 256);
253 LOCAL_ARRAY(char, mask, 256);
254 acl_ip_data *r = NULL;
255 acl_ip_data **Q = NULL;
256 Ip::Address temp;
257 char c;
258 unsigned int changed;
259 acl_ip_data *q = new acl_ip_data;
260 int iptype = AF_UNSPEC;
261
262 debugs(28, 5, "aclIpParseIpData: " << t);
263
264 /* Special ACL RHS "all" matches entire Internet */
265 if (strcasecmp(t, "all") == 0) {
266 debugs(28, 9, "aclIpParseIpData: magic 'all' found.");
267 q->addr1.SetAnyAddr();
268 q->addr2.SetEmpty();
269 q->mask.SetAnyAddr();
270 return q;
271 }
272
273 /* Detect some old broken strings equivalent to 'all'.
274 * treat them nicely. But be loud until its fixed. */
275 if (strcasecmp(t, "0/0") == 0 || strcasecmp(t, "0.0.0.0/0") == 0 || strcasecmp(t, "0.0.0.0/0.0.0.0") == 0 ||
276 strcasecmp(t, "0.0.0.0-255.255.255.255") == 0 || strcasecmp(t, "0.0.0.0-0.0.0.0/0") == 0) {
277
278 debugs(28,DBG_CRITICAL, "ERROR: '" << t << "' needs to be replaced by the term 'all'.");
279 debugs(28,DBG_CRITICAL, "SECURITY NOTICE: Overriding config setting. Using 'all' instead.");
280 q->addr1.SetAnyAddr();
281 q->addr2.SetEmpty();
282 q->mask.SetAnyAddr();
283 return q;
284 }
285
286 /* Special ACL RHS "ipv4" matches IPv4 Internet
287 * A nod to IANA; we include the entire class space in case
288 * they manage to find a way to recover and use it */
289 if (strcasecmp(t, "ipv4") == 0) {
290 q->mask.SetNoAddr();
291 q->mask.ApplyMask(0, AF_INET);
292 return q;
293 }
294
295 /* Special ACL RHS "ipv6" matches IPv6-Unicast Internet */
296 if (strcasecmp(t, "ipv6") == 0) {
297 debugs(28, 9, "aclIpParseIpData: magic 'ipv6' found.");
298 r = q; // save head of the list for result.
299
300 /* 0000::/4 is a mix of localhost and obsolete IPv4-mapping space. Not valid outside this host. */
301
302 /* Future global unicast space: 1000::/4 */
303 q->addr1 = "1000::";
304 q->mask.SetNoAddr();
305 q->mask.ApplyMask(4, AF_INET6);
306
307 /* Current global unicast space: 2000::/4 = (2000::/4 - 3000::/4) */
308 q->next = new acl_ip_data;
309 q = q->next;
310 q->addr1 = "2000::";
311 q->mask.SetNoAddr();
312 q->mask.ApplyMask(3, AF_INET6);
313
314 /* Future global unicast space: 4000::/2 = (4000::/4 - 7000::/4) */
315 q->next = new acl_ip_data;
316 q = q->next;
317 q->addr1 = "4000::";
318 q->mask.SetNoAddr();
319 q->mask.ApplyMask(2, AF_INET6);
320
321 /* Future global unicast space: 8000::/2 = (8000::/4 - B000::/4) */
322 q->next = new acl_ip_data;
323 q = q->next;
324 q->addr1 = "8000::";
325 q->mask.SetNoAddr();
326 q->mask.ApplyMask(2, AF_INET6);
327
328 /* Future global unicast space: C000::/3 = (C000::/4 - D000::/4) */
329 q->next = new acl_ip_data;
330 q = q->next;
331 q->addr1 = "C000::";
332 q->mask.SetNoAddr();
333 q->mask.ApplyMask(3, AF_INET6);
334
335 /* Future global unicast space: E000::/4 */
336 q->next = new acl_ip_data;
337 q = q->next;
338 q->addr1 = "E000::";
339 q->mask.SetNoAddr();
340 q->mask.ApplyMask(4, AF_INET6);
341
342 /* F000::/4 is mostly reserved non-unicast. With some exceptions ... */
343
344 /* RFC 4193 Unique-Local unicast space: FC00::/7 */
345 q->next = new acl_ip_data;
346 q = q->next;
347 q->addr1 = "FC00::";
348 q->mask.SetNoAddr();
349 q->mask.ApplyMask(7, AF_INET6);
350
351 /* Link-Local unicast space: FE80::/10 */
352 q->next = new acl_ip_data;
353 q = q->next;
354 q->addr1 = "FE80::";
355 q->mask.SetNoAddr();
356 q->mask.ApplyMask(10, AF_INET6);
357
358 return r;
359 }
360
361 // IPv4
362 if (sscanf(t, SCAN_ACL1_4, addr1, addr2, mask) == 3) {
363 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN1-v4: " << SCAN_ACL1_4);
364 iptype=AF_INET;
365 } else if (sscanf(t, SCAN_ACL2_4, addr1, addr2, &c) >= 2) {
366 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN2-v4: " << SCAN_ACL2_4);
367 mask[0] = '\0';
368 iptype=AF_INET;
369 } else if (sscanf(t, SCAN_ACL3_4, addr1, mask) == 2) {
370 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN3-v4: " << SCAN_ACL3_4);
371 addr2[0] = '\0';
372 iptype=AF_INET;
373 } else if (sscanf(t, SCAN_ACL4_4, addr1,&c) == 2) {
374 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN4-v4: " << SCAN_ACL4_4);
375 addr2[0] = '\0';
376 mask[0] = '\0';
377 iptype=AF_INET;
378
379 // IPv6
380 } else if (sscanf(t, SCAN_ACL1_6, addr1, addr2, mask) == 3) {
381 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN1-v6: " << SCAN_ACL1_6);
382 iptype=AF_INET6;
383 } else if (sscanf(t, SCAN_ACL2_6, addr1, addr2, &c) >= 2) {
384 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN2-v6: " << SCAN_ACL2_6);
385 mask[0] = '\0';
386 iptype=AF_INET6;
387 } else if (sscanf(t, SCAN_ACL3_6, addr1, mask) == 2) {
388 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN3-v6: " << SCAN_ACL3_6);
389 addr2[0] = '\0';
390 iptype=AF_INET6;
391 } else if (sscanf(t, SCAN_ACL4_6, addr1, mask) == 2) {
392 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN4-v6: " << SCAN_ACL4_6);
393 addr2[0] = '\0';
394 iptype=AF_INET6;
395
396 // Neither
397 } else if (sscanf(t, "%[^/]/%s", addr1, mask) == 2) {
398 debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: non-IP pattern: %[^/]/%s");
399 addr2[0] = '\0';
400 } else if (sscanf(t, "%s", addr1) == 1) {
401 /*
402 * Note, must use plain getaddrinfo() here because at startup
403 * ipcache hasn't been initialized
404 * TODO: offload this to one of the Ip::Address lookups.
405 */
406
407 debugs(28, 5, "aclIpParseIpData: Lookup Host/IP " << addr1);
408 struct addrinfo *hp = NULL, *x = NULL;
409 struct addrinfo hints;
410 Ip::Address *prev_addr = NULL;
411
412 memset(&hints, 0, sizeof(struct addrinfo));
413
414 if ( iptype != AF_UNSPEC ) {
415 hints.ai_flags |= AI_NUMERICHOST;
416 }
417
418 #if 0
419 if (Ip::EnableIpv6&IPV6_SPECIAL_V4MAPPING)
420 hints.ai_flags |= AI_V4MAPPED | AI_ALL;
421 #endif
422
423 int errcode = getaddrinfo(addr1,NULL,&hints,&hp);
424 if (hp == NULL) {
425 debugs(28, DBG_CRITICAL, "aclIpParseIpData: Bad host/IP: '" << addr1 <<
426 "' in '" << t << "', flags=" << hints.ai_flags <<
427 " : (" << errcode << ") " << gai_strerror(errcode) );
428 self_destruct();
429 return NULL;
430 }
431
432 Q = &q;
433
434 for (x = hp; x != NULL;) {
435 if ((r = *Q) == NULL)
436 r = *Q = new acl_ip_data;
437
438 /* getaddrinfo given a host has a nasty tendency to return duplicate addr's */
439 /* BUT sorted fortunately, so we can drop most of them easily */
440 r->addr1 = *x;
441 x = x->ai_next;
442 if ( prev_addr && r->addr1 == *prev_addr) {
443 debugs(28, 3, "aclIpParseIpData: Duplicate host/IP: '" << r->addr1 << "' dropped.");
444 delete r;
445 *Q = NULL;
446 continue;
447 } else
448 prev_addr = &r->addr1;
449
450 debugs(28, 3, "aclIpParseIpData: Located host/IP: '" << r->addr1 << "'");
451
452 r->addr2.SetAnyAddr();
453 r->mask.SetNoAddr();
454
455 Q = &r->next;
456
457 debugs(28, 3, "" << addr1 << " --> " << r->addr1 );
458 }
459
460 if (*Q != NULL) {
461 debugs(28, DBG_CRITICAL, "aclIpParseIpData: Bad host/IP: '" << t << "'");
462 self_destruct();
463 return NULL;
464 }
465
466 freeaddrinfo(hp);
467
468 return q;
469 }
470
471 /* ignore IPv6 addresses when built with IPv4-only */
472 if ( iptype == AF_INET6 && !Ip::EnableIpv6) {
473 debugs(28, DBG_IMPORTANT, "aclIpParseIpData: IPv6 has not been enabled.");
474 return NULL;
475 }
476
477 /* Decode addr1 */
478 if (!*addr1 || !(q->addr1 = addr1)) {
479 debugs(28, DBG_CRITICAL, "aclIpParseIpData: unknown first address in '" << t << "'");
480 delete q;
481 self_destruct();
482 return NULL;
483 }
484
485 /* Decode addr2 */
486 if (!*addr2)
487 q->addr2.SetAnyAddr();
488 else if (!(q->addr2=addr2) ) {
489 debugs(28, DBG_CRITICAL, "aclIpParseIpData: unknown second address in '" << t << "'");
490 delete q;
491 self_destruct();
492 return NULL;
493 }
494
495 /* Decode mask (NULL or empty means a exact host mask) */
496 if (!DecodeMask(mask, q->mask, iptype)) {
497 debugs(28, DBG_CRITICAL, "aclParseIpData: unknown netmask '" << mask << "' in '" << t << "'");
498 delete q;
499 self_destruct();
500 return NULL;
501 }
502
503 changed = 0;
504 changed += q->addr1.ApplyMask(q->mask);
505 changed += q->addr2.ApplyMask(q->mask);
506
507 if (changed)
508 debugs(28, DBG_CRITICAL, "aclIpParseIpData: WARNING: Netmask masks away part of the specified IP in '" << t << "'");
509
510 debugs(28,9, HERE << "Parsed: " << q->addr1 << "-" << q->addr2 << "/" << q->mask << "(/" << q->mask.GetCIDR() <<")");
511
512 /* 1.2.3.4/255.255.255.0 --> 1.2.3.0 */
513 /* Same as IPv6 (not so trivial to depict) */
514 return q;
515 }
516
517 void
518 ACLIP::parse()
519 {
520 char *t = NULL;
521
522 while ((t = strtokFile())) {
523 acl_ip_data *q = acl_ip_data::FactoryParse(t);
524
525 while (q != NULL) {
526 /* pop each result off the list and add it to the data tree individually */
527 acl_ip_data *next_node = q->next;
528 q->next = NULL;
529 data = data->insert(q, acl_ip_data::NetworkCompare);
530 q = next_node;
531 }
532 }
533 }
534
535 ACLIP::~ACLIP()
536 {
537 if (data)
538 data->destroy(IPSplay::DefaultFree);
539 }
540
541 wordlist *
542 ACLIP::dump() const
543 {
544 wordlist *w = NULL;
545 data->walk (DumpIpListWalkee, &w);
546 return w;
547 }
548
549 bool
550 ACLIP::empty () const
551 {
552 return data->empty();
553 }
554
555 int
556 ACLIP::match(Ip::Address &clientip)
557 {
558 static acl_ip_data ClientAddress;
559 /*
560 * aclIpAddrNetworkCompare() takes two acl_ip_data pointers as
561 * arguments, so we must create a fake one for the client's IP
562 * address. Since we are scanning for a single IP mask and addr2
563 * MUST be set to empty.
564 */
565 ClientAddress.addr1 = clientip;
566 ClientAddress.addr2.SetEmpty();
567 ClientAddress.mask.SetEmpty();
568
569 data = data->splay(&ClientAddress, aclIpAddrNetworkCompare);
570 debugs(28, 3, "aclIpMatchIp: '" << clientip << "' " << (splayLastResult ? "NOT found" : "found"));
571 return !splayLastResult;
572 }
573
574 acl_ip_data::acl_ip_data () :addr1(), addr2(), mask(), next (NULL) {}
575
576 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) {}