]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Asn.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / acl / Asn.cc
1 /*
2 * Copyright (C) 1996-2016 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 53 AS Number handling */
10
11 #include "squid.h"
12 #include "acl/Acl.h"
13 #include "acl/Asn.h"
14 #include "acl/Checklist.h"
15 #include "acl/DestinationAsn.h"
16 #include "acl/DestinationIp.h"
17 #include "acl/SourceAsn.h"
18 #include "FwdState.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
21 #include "ipcache.h"
22 #include "mgr/Registration.h"
23 #include "radix.h"
24 #include "RequestFlags.h"
25 #include "SquidConfig.h"
26 #include "Store.h"
27 #include "StoreClient.h"
28
29 #define WHOIS_PORT 43
30 #define AS_REQBUF_SZ 4096
31
32 /* BEGIN of definitions for radix tree entries */
33
34 /* 32/128 bits address in memory with length */
35 class m_ADDR
36 {
37 public:
38 uint8_t len;
39 Ip::Address addr;
40
41 m_ADDR() : len(sizeof(Ip::Address)) {};
42 };
43
44 /* END of definitions for radix tree entries */
45
46 /* Head for ip to asn radix tree */
47
48 struct squid_radix_node_head *AS_tree_head;
49
50 /* explicit instantiation required for some systems */
51
52 /// \cond AUTODOCS_IGNORE
53 template cbdata_type CbDataList<int>::CBDATA_CbDataList;
54 /// \endcond
55
56 /**
57 * Structure for as number information. it could be simply
58 * a list but it's coded as a structure for future
59 * enhancements (e.g. expires)
60 */
61 struct as_info {
62 CbDataList<int> *as_number;
63 time_t expires; /* NOTUSED */
64 };
65
66 class ASState
67 {
68 CBDATA_CLASS(ASState);
69
70 public:
71 ASState();
72 ~ASState();
73
74 StoreEntry *entry;
75 store_client *sc;
76 HttpRequest::Pointer request;
77 int as_number;
78 int64_t offset;
79 int reqofs;
80 char reqbuf[AS_REQBUF_SZ];
81 bool dataRead;
82 };
83
84 CBDATA_CLASS_INIT(ASState);
85
86 ASState::ASState() :
87 entry(NULL),
88 sc(NULL),
89 request(NULL),
90 as_number(0),
91 offset(0),
92 reqofs(0),
93 dataRead(false)
94 {
95 memset(reqbuf, 0, AS_REQBUF_SZ);
96 }
97
98 ASState::~ASState()
99 {
100 debugs(53, 3, entry->url());
101 storeUnregister(sc, entry, this);
102 entry->unlock("~ASState");
103 }
104
105 /** entry into the radix tree */
106 struct rtentry_t {
107 struct squid_radix_node e_nodes[2];
108 as_info *e_info;
109 m_ADDR e_addr;
110 m_ADDR e_mask;
111 };
112
113 static int asnAddNet(char *, int);
114
115 static void asnCacheStart(int as);
116
117 static STCB asHandleReply;
118
119 #if defined(__cplusplus)
120 extern "C" {
121 #endif
122
123 static int destroyRadixNode(struct squid_radix_node *rn, void *w);
124 static int printRadixNode(struct squid_radix_node *rn, void *sentry);
125
126 #if defined(__cplusplus)
127 }
128 #endif
129
130 void asnAclInitialize(ACL * acls);
131
132 static void destroyRadixNodeInfo(as_info *);
133
134 static OBJH asnStats;
135
136 /* PUBLIC */
137
138 int
139 asnMatchIp(CbDataList<int> *data, Ip::Address &addr)
140 {
141 struct squid_radix_node *rn;
142 as_info *e;
143 m_ADDR m_addr;
144 CbDataList<int> *a = NULL;
145 CbDataList<int> *b = NULL;
146
147 debugs(53, 3, "asnMatchIp: Called for " << addr );
148
149 if (AS_tree_head == NULL)
150 return 0;
151
152 if (addr.isNoAddr())
153 return 0;
154
155 if (addr.isAnyAddr())
156 return 0;
157
158 m_addr.addr = addr;
159
160 rn = squid_rn_match(&m_addr, AS_tree_head);
161
162 if (rn == NULL) {
163 debugs(53, 3, "asnMatchIp: Address not in as db.");
164 return 0;
165 }
166
167 debugs(53, 3, "asnMatchIp: Found in db!");
168 e = ((rtentry_t *) rn)->e_info;
169 assert(e);
170
171 for (a = data; a; a = a->next)
172 for (b = e->as_number; b; b = b->next)
173 if (a->element == b->element) {
174 debugs(53, 5, "asnMatchIp: Found a match!");
175 return 1;
176 }
177
178 debugs(53, 5, "asnMatchIp: AS not in as db.");
179 return 0;
180 }
181
182 void
183 ACLASN::prepareForUse()
184 {
185 for (CbDataList<int> *i = data; i; i = i->
186 next)
187 asnCacheStart(i->element);
188 }
189
190 static void
191 asnRegisterWithCacheManager(void)
192 {
193 Mgr::RegisterAction("asndb", "AS Number Database", asnStats, 0, 1);
194 }
195
196 /* initialize the radix tree structure */
197
198 SQUIDCEXTERN int squid_max_keylen; /* yuck.. this is in lib/radix.c */
199
200 void
201 asnInit(void)
202 {
203 static bool inited = false;
204 squid_max_keylen = 40;
205
206 if (!inited) {
207 inited = true;
208 squid_rn_init();
209 }
210
211 squid_rn_inithead(&AS_tree_head, 8);
212
213 asnRegisterWithCacheManager();
214 }
215
216 void
217 asnFreeMemory(void)
218 {
219 squid_rn_walktree(AS_tree_head, destroyRadixNode, AS_tree_head);
220
221 destroyRadixNode((struct squid_radix_node *) 0, (void *) AS_tree_head);
222 }
223
224 static void
225 asnStats(StoreEntry * sentry)
226 {
227 storeAppendPrintf(sentry, "Address \tAS Numbers\n");
228 squid_rn_walktree(AS_tree_head, printRadixNode, sentry);
229 }
230
231 /* PRIVATE */
232
233 static void
234 asnCacheStart(int as)
235 {
236 LOCAL_ARRAY(char, asres, 4096);
237 StoreEntry *e;
238 ASState *asState = new ASState;
239 debugs(53, 3, "AS " << as);
240 snprintf(asres, 4096, "whois://%s/!gAS%d", Config.as_whois_server, as);
241 asState->as_number = as;
242 asState->request = HttpRequest::CreateFromUrl(asres);
243 assert(asState->request != NULL);
244
245 if ((e = storeGetPublic(asres, Http::METHOD_GET)) == NULL) {
246 e = storeCreateEntry(asres, asres, RequestFlags(), Http::METHOD_GET);
247 asState->sc = storeClientListAdd(e, asState);
248 FwdState::fwdStart(Comm::ConnectionPointer(), e, asState->request.getRaw());
249 } else {
250 e->lock("Asn");
251 asState->sc = storeClientListAdd(e, asState);
252 }
253
254 asState->entry = e;
255 StoreIOBuffer readBuffer (AS_REQBUF_SZ, asState->offset, asState->reqbuf);
256 storeClientCopy(asState->sc, e, readBuffer, asHandleReply, asState);
257 }
258
259 static void
260 asHandleReply(void *data, StoreIOBuffer result)
261 {
262 ASState *asState = (ASState *)data;
263 StoreEntry *e = asState->entry;
264 char *s;
265 char *t;
266 char *buf = asState->reqbuf;
267 int leftoversz = -1;
268
269 debugs(53, 3, "asHandleReply: Called with size=" << (unsigned int)result.length);
270 debugs(53, 3, "asHandleReply: buffer='" << buf << "'");
271
272 /* First figure out whether we should abort the request */
273
274 if (EBIT_TEST(e->flags, ENTRY_ABORTED)) {
275 delete asState;
276 return;
277 }
278
279 if (result.length == 0 && asState->dataRead) {
280 debugs(53, 3, "asHandleReply: Done: " << e->url());
281 delete asState;
282 return;
283 } else if (result.flags.error) {
284 debugs(53, DBG_IMPORTANT, "asHandleReply: Called with Error set and size=" << (unsigned int) result.length);
285 delete asState;
286 return;
287 } else if (e->getReply()->sline.status() != Http::scOkay) {
288 debugs(53, DBG_IMPORTANT, "WARNING: AS " << asState->as_number << " whois request failed");
289 delete asState;
290 return;
291 }
292
293 /*
294 * Next, attempt to parse our request
295 * Remembering that the actual buffer size is retsize + reqofs!
296 */
297 s = buf;
298
299 while ((size_t)(s - buf) < result.length + asState->reqofs && *s != '\0') {
300 while (*s && xisspace(*s))
301 ++s;
302
303 for (t = s; *t; ++t) {
304 if (xisspace(*t))
305 break;
306 }
307
308 if (*t == '\0') {
309 /* oof, word should continue on next block */
310 break;
311 }
312
313 *t = '\0';
314 debugs(53, 3, "asHandleReply: AS# " << s << " (" << asState->as_number << ")");
315 asnAddNet(s, asState->as_number);
316 s = t + 1;
317 asState->dataRead = true;
318 }
319
320 /*
321 * Next, grab the end of the 'valid data' in the buffer, and figure
322 * out how much data is left in our buffer, which we need to keep
323 * around for the next request
324 */
325 leftoversz = (asState->reqofs + result.length) - (s - buf);
326
327 assert(leftoversz >= 0);
328
329 /*
330 * Next, copy the left over data, from s to s + leftoversz to the
331 * beginning of the buffer
332 */
333 memmove(buf, s, leftoversz);
334
335 /*
336 * Next, update our offset and reqofs, and kick off a copy if required
337 */
338 asState->offset += result.length;
339
340 asState->reqofs = leftoversz;
341
342 debugs(53, 3, "asState->offset = " << asState->offset);
343
344 if (e->store_status == STORE_PENDING) {
345 debugs(53, 3, "asHandleReply: store_status == STORE_PENDING: " << e->url() );
346 StoreIOBuffer tempBuffer (AS_REQBUF_SZ - asState->reqofs,
347 asState->offset,
348 asState->reqbuf + asState->reqofs);
349 storeClientCopy(asState->sc,
350 e,
351 tempBuffer,
352 asHandleReply,
353 asState);
354 } else {
355 StoreIOBuffer tempBuffer;
356 debugs(53, 3, "asHandleReply: store complete, but data received " << e->url() );
357 tempBuffer.offset = asState->offset;
358 tempBuffer.length = AS_REQBUF_SZ - asState->reqofs;
359 tempBuffer.data = asState->reqbuf + asState->reqofs;
360 storeClientCopy(asState->sc,
361 e,
362 tempBuffer,
363 asHandleReply,
364 asState);
365 }
366 }
367
368 /**
369 * add a network (addr, mask) to the radix tree, with matching AS number
370 */
371 static int
372 asnAddNet(char *as_string, int as_number)
373 {
374 struct squid_radix_node *rn;
375 CbDataList<int> **Tail = NULL;
376 CbDataList<int> *q = NULL;
377 as_info *asinfo = NULL;
378
379 Ip::Address mask;
380 Ip::Address addr;
381 char *t;
382 int bitl;
383
384 t = strchr(as_string, '/');
385
386 if (t == NULL) {
387 debugs(53, 3, "asnAddNet: failed, invalid response from whois server.");
388 return 0;
389 }
390
391 *t = '\0';
392 addr = as_string;
393 bitl = atoi(t + 1);
394
395 if (bitl < 0)
396 bitl = 0;
397
398 // INET6 TODO : find a better way of identifying the base IPA family for mask than this.
399 t = strchr(as_string, '.');
400
401 // generate Netbits Format Mask
402 mask.setNoAddr();
403 mask.applyMask(bitl, (t!=NULL?AF_INET:AF_INET6) );
404
405 debugs(53, 3, "asnAddNet: called for " << addr << "/" << mask );
406
407 rtentry_t *e = (rtentry_t *)xcalloc(1, sizeof(rtentry_t));
408
409 e->e_addr.addr = addr;
410
411 e->e_mask.addr = mask;
412
413 rn = squid_rn_lookup(&e->e_addr, &e->e_mask, AS_tree_head);
414
415 if (rn != NULL) {
416 asinfo = ((rtentry_t *) rn)->e_info;
417
418 if (asinfo->as_number->find(as_number)) {
419 debugs(53, 3, "asnAddNet: Ignoring repeated network '" << addr << "/" << bitl << "' for AS " << as_number);
420 } else {
421 debugs(53, 3, "asnAddNet: Warning: Found a network with multiple AS numbers!");
422
423 for (Tail = &asinfo->as_number; *Tail; Tail = &(*Tail)->next);
424 q = new CbDataList<int> (as_number);
425
426 *(Tail) = q;
427
428 e->e_info = asinfo;
429 }
430 } else {
431 q = new CbDataList<int> (as_number);
432 asinfo = (as_info *)xmalloc(sizeof(as_info));
433 asinfo->as_number = q;
434 squid_rn_addroute(&e->e_addr, &e->e_mask, AS_tree_head, e->e_nodes);
435 rn = squid_rn_match(&e->e_addr, AS_tree_head);
436 assert(rn != NULL);
437 e->e_info = asinfo;
438 }
439
440 if (rn == 0) { /* assert might expand to nothing */
441 xfree(asinfo);
442 delete q;
443 xfree(e);
444 debugs(53, 3, "asnAddNet: Could not add entry.");
445 return 0;
446 }
447
448 e->e_info = asinfo;
449 return 1;
450 }
451
452 static int
453 destroyRadixNode(struct squid_radix_node *rn, void *w)
454 {
455
456 struct squid_radix_node_head *rnh = (struct squid_radix_node_head *) w;
457
458 if (rn && !(rn->rn_flags & RNF_ROOT)) {
459 rtentry_t *e = (rtentry_t *) rn;
460 rn = squid_rn_delete(rn->rn_key, rn->rn_mask, rnh);
461
462 if (rn == 0)
463 debugs(53, 3, "destroyRadixNode: internal screwup");
464
465 destroyRadixNodeInfo(e->e_info);
466
467 xfree(rn);
468 }
469
470 return 1;
471 }
472
473 static void
474 destroyRadixNodeInfo(as_info * e_info)
475 {
476 CbDataList<int> *prev = NULL;
477 CbDataList<int> *data = e_info->as_number;
478
479 while (data) {
480 prev = data;
481 data = data->next;
482 delete prev;
483 }
484 }
485
486 static int
487 printRadixNode(struct squid_radix_node *rn, void *_sentry)
488 {
489 StoreEntry *sentry = (StoreEntry *)_sentry;
490 rtentry_t *e = (rtentry_t *) rn;
491 CbDataList<int> *q;
492 as_info *asinfo;
493 char buf[MAX_IPSTRLEN];
494 Ip::Address addr;
495 Ip::Address mask;
496
497 assert(e);
498 assert(e->e_info);
499 addr = e->e_addr.addr;
500 mask = e->e_mask.addr;
501 storeAppendPrintf(sentry, "%s/%d\t",
502 addr.toStr(buf, MAX_IPSTRLEN),
503 mask.cidr() );
504 asinfo = e->e_info;
505 assert(asinfo->as_number);
506
507 for (q = asinfo->as_number; q; q = q->next)
508 storeAppendPrintf(sentry, " %d", q->element);
509
510 storeAppendPrintf(sentry, "\n");
511
512 return 0;
513 }
514
515 ACLASN::~ACLASN()
516 {
517 if (data)
518 delete data;
519 }
520
521 bool
522
523 ACLASN::match(Ip::Address toMatch)
524 {
525 return asnMatchIp(data, toMatch);
526 }
527
528 SBufList
529 ACLASN::dump() const
530 {
531 SBufList sl;
532
533 CbDataList<int> *ldata = data;
534
535 while (ldata != NULL) {
536 SBuf s;
537 s.Printf("%d", ldata->element);
538 sl.push_back(s);
539 ldata = ldata->next;
540 }
541
542 return sl;
543 }
544
545 bool
546 ACLASN::empty () const
547 {
548 return data == NULL;
549 }
550
551 void
552 ACLASN::parse()
553 {
554 CbDataList<int> **curlist = &data;
555 CbDataList<int> **Tail;
556 CbDataList<int> *q = NULL;
557 char *t = NULL;
558
559 for (Tail = curlist; *Tail; Tail = &((*Tail)->next));
560 while ((t = ConfigParser::strtokFile())) {
561 q = new CbDataList<int> (atoi(t));
562 *(Tail) = q;
563 Tail = &q->next;
564 }
565 }
566
567 ACLData<Ip::Address> *
568 ACLASN::clone() const
569 {
570 if (data)
571 fatal ("cloning of ACLASN not implemented");
572
573 return new ACLASN(*this);
574 }
575
576 /* explicit template instantiation required for some systems */
577
578 template class ACLStrategised<Ip::Address>;
579
580 ACL::Prototype ACLASN::SourceRegistryProtoype(&ACLASN::SourceRegistryEntry_, "src_as");
581
582 ACLStrategised<Ip::Address> ACLASN::SourceRegistryEntry_(new ACLASN, ACLSourceASNStrategy::Instance(), "src_as");
583
584 ACL::Prototype ACLASN::DestinationRegistryProtoype(&ACLASN::DestinationRegistryEntry_, "dst_as");
585
586 ACLStrategised<Ip::Address> ACLASN::DestinationRegistryEntry_(new ACLASN, ACLDestinationASNStrategy::Instance(), "dst_as");
587
588 int
589 ACLSourceASNStrategy::match (ACLData<Ip::Address> * &data, ACLFilledChecklist *checklist, ACLFlags &)
590 {
591 return data->match(checklist->src_addr);
592 }
593
594 ACLSourceASNStrategy *
595 ACLSourceASNStrategy::Instance()
596 {
597 return &Instance_;
598 }
599
600 ACLSourceASNStrategy ACLSourceASNStrategy::Instance_;
601
602 int
603 ACLDestinationASNStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *checklist, ACLFlags &)
604 {
605 const ipcache_addrs *ia = ipcache_gethostbyname(checklist->request->url.host(), IP_LOOKUP_IF_MISS);
606
607 if (ia) {
608 for (int k = 0; k < (int) ia->count; ++k) {
609 if (data->match(ia->in_addrs[k]))
610 return 1;
611 }
612
613 return 0;
614
615 } else if (!checklist->request->flags.destinationIpLookedUp) {
616 /* No entry in cache, lookup not attempted */
617 debugs(28, 3, "can't yet compare '" << AclMatchedName << "' ACL for " << checklist->request->url.host());
618 if (checklist->goAsync(DestinationIPLookup::Instance()))
619 return -1;
620 // else fall through to noaddr match, hiding the lookup failure (XXX)
621 }
622 Ip::Address noaddr;
623 noaddr.setNoAddr();
624 return data->match(noaddr);
625 }
626
627 ACLDestinationASNStrategy *
628 ACLDestinationASNStrategy::Instance()
629 {
630 return &Instance_;
631 }
632
633 ACLDestinationASNStrategy ACLDestinationASNStrategy::Instance_;
634