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