ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/hopm/trunk/src/firedns.c
Revision: 5181
Committed: Sun Dec 28 23:34:01 2014 UTC (10 years, 8 months ago) by michael
Content type: text/x-csrc
File size: 17558 byte(s)
Log Message:
- firedns.c: style corrections

File Contents

# Content
1 /*
2 firedns.c - firedns library
3 Copyright (C) 2002 Ian Gulliver
4
5 This file has been gutted and mucked with for use in BOPM - see the
6 real library at http://ares.penguinhosting.net/~ian/ before you judge
7 firedns based on this..
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of version 2 of the GNU General Public License as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
22
23 #include "setup.h"
24
25 #include <stdlib.h>
26 #include <time.h>
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/poll.h>
30 #include <sys/time.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <fcntl.h>
38
39 #include "compat.h"
40 #include "malloc.h"
41 #include "firedns.h"
42 #include "config.h"
43 #include "list.h"
44 #include "log.h"
45 #include "dnsbl.h"
46
47 #define FIREDNS_TRIES 3
48 #define min(a,b) (a < b ? a : b)
49
50 int fdns_errno = FDNS_ERR_NONE;
51 unsigned int fdns_fdinuse = 0;
52
53 /* Variables local to this file */
54
55 /* up to FDNS_MAX nameservers; populated by firedns_init() */
56 static struct in_addr servers4[FDNS_MAX];
57 /* actual count of nameservers; set by firedns_init() */
58 static int i4;
59 static int i6;
60 static struct in6_addr servers6[FDNS_MAX];
61
62 /*
63 * Linked list of open DNS queries; populated by firedns_add_query(),
64 * decimated by firedns_getresult()
65 */
66 static list_t *CONNECTIONS;
67
68 /*
69 * List of errors, in order of values used in FDNS_ERR_*, returned by
70 * firedns_strerror
71 */
72 static const char *errors[] =
73 {
74 "Success",
75 "Format error",
76 "Server failure",
77 "Name error",
78 "Not implemented",
79 "Refused",
80 "Timeout",
81 "Network error",
82 "FD Limit reached",
83 "Unknown error"
84 };
85
86 /* Structures */
87
88 /* open DNS query */
89 struct s_connection
90 {
91 /*
92 * unique ID (random number), matches header ID; both set by
93 * firedns_add_query()
94 */
95 unsigned char id[2];
96 uint16_t class;
97 uint16_t type;
98
99 /* file descriptor returned from sockets */
100 int fd;
101 void *info;
102 time_t start;
103 char lookup[256];
104 int v6;
105 };
106
107 struct s_rr_middle
108 {
109 uint16_t type;
110 uint16_t class;
111
112 /* XXX - firedns depends on this being 4 bytes */
113 uint32_t ttl;
114 uint16_t rdlength;
115 };
116
117 /* DNS query header */
118 struct s_header
119 {
120 unsigned char id[2];
121 unsigned char flags1;
122 #define FLAGS1_MASK_QR 0x80
123 /* bitshift right 3 */
124 #define FLAGS1_MASK_OPCODE 0x78
125 #define FLAGS1_MASK_AA 0x04
126 #define FLAGS1_MASK_TC 0x02
127 #define FLAGS1_MASK_RD 0x01
128
129 unsigned char flags2;
130 #define FLAGS2_MASK_RA 0x80
131 #define FLAGS2_MASK_Z 0x70
132 #define FLAGS2_MASK_RCODE 0x0f
133
134 uint16_t qdcount;
135 uint16_t ancount;
136 uint16_t nscount;
137 uint16_t arcount;
138
139 /* DNS question, populated by firedns_build_query_payload() */
140 unsigned char payload[512];
141 };
142
143 /* Function prototypes */
144 static struct s_connection *firedns_add_query(void);
145 static int firedns_doquery(struct s_connection *);
146 static int firedns_build_query_payload(const char *const, uint16_t, uint16_t, unsigned char *);
147 static int firedns_send_requests(struct s_header *, struct s_connection *, int);
148
149
150 void
151 firedns_init(void)
152 {
153 /*
154 * populates servers4 (or -6) struct with up to FDNS_MAX nameserver IP
155 * addresses from /etc/firedns.conf (or /etc/resolv.conf)
156 */
157 FILE *f;
158 int i;
159 struct in_addr addr4;
160 struct in6_addr addr6;
161 char buf[1024];
162 const char *file;
163
164 i6 = 0;
165 i4 = 0;
166
167 /* Initialize connections list */
168 CONNECTIONS = list_create();
169
170 srand((unsigned int)time(NULL));
171 memset(servers4, 0, sizeof(struct in_addr) * FDNS_MAX);
172 memset(servers6, 0, sizeof(struct in6_addr) * FDNS_MAX);
173
174 /* read etc/firedns.conf if we've got it, otherwise parse /etc/resolv.conf */
175 f = fopen(FDNS_CONFIG_PREF, "r");
176
177 if (f == NULL)
178 {
179 f = fopen(FDNS_CONFIG_FBCK, "r");
180
181 if (f == NULL)
182 {
183 log_printf("Unable to open %s", FDNS_CONFIG_FBCK);
184 return;
185 }
186
187 file = FDNS_CONFIG_FBCK;
188
189 while (fgets(buf, 1024, f))
190 {
191 if (strncmp(buf, "nameserver", 10) == 0)
192 {
193 i = 10;
194
195 while (buf[i] == ' ' || buf[i] == '\t')
196 ++i;
197
198 /* glibc /etc/resolv.conf seems to allow ipv6 server names */
199 if (i6 < FDNS_MAX)
200 {
201 if (inet_pton(AF_INET6, &buf[i], &addr6) > 0)
202 {
203 memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr));
204 continue;
205 }
206 }
207
208 if (i4 < FDNS_MAX)
209 {
210 if (inet_pton(AF_INET, &buf[i], &addr4) > 0)
211 memcpy(&servers4[i4++], &addr4, sizeof(struct in_addr));
212 }
213 }
214 }
215 }
216 else
217 {
218 file = FDNS_CONFIG_PREF;
219
220 while (fgets(buf, 1024, f))
221 {
222 buf[strspn(buf, "0123456789.")] = '\0';
223
224 if (i6 < FDNS_MAX)
225 {
226 if (inet_pton(AF_INET6, buf, &addr6) > 0)
227 {
228 memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr));
229 continue;
230 }
231 }
232
233 if (i4 < FDNS_MAX)
234 {
235 if (inet_pton(AF_INET, buf, &addr4) > 0)
236 memcpy(&servers4[i4++], &addr4, sizeof(struct in_addr));
237 }
238 }
239 }
240
241 fclose(f);
242
243 if (i4 == 0 /* (yuck) */ && i6)
244 {
245 log_printf("FIREDNS -> No nameservers found in %s", file);
246 exit(EXIT_FAILURE);
247 }
248 }
249
250 /* immediate A query */
251 struct in_addr *
252 firedns_resolveip4(const char *const name)
253 {
254 static struct in_addr addr;
255
256 if (inet_pton(AF_INET, name, &addr) > 0)
257 return &addr;
258
259 return (struct in_addr *)firedns_resolveip(FDNS_QRY_A, name);
260 }
261
262 /* immediate AAAA query */
263 struct in6_addr *
264 firedns_resolveip6(const char * const name)
265 {
266 return (struct in6_addr *)firedns_resolveip(FDNS_QRY_AAAA, name);
267 }
268
269 /* resolve a query of a given type */
270 char *
271 firedns_resolveip(int type, const char *const name)
272 {
273 struct firedns_result *result;
274 struct timeval tv;
275 fd_set s;
276
277 for (int t = 0; t < FIREDNS_TRIES; ++t)
278 {
279 int fd = firedns_getip(type, name, NULL);
280
281 if (fd == -1)
282 return NULL;
283
284 tv.tv_sec = 5;
285 tv.tv_usec = 0;
286 FD_ZERO(&s);
287 FD_SET(fd, &s);
288 select(fd + 1, &s, NULL, NULL, &tv);
289
290 result = firedns_getresult(fd);
291
292 if (fdns_errno == FDNS_ERR_NONE)
293 /*
294 * Return is from static memory in getresult, so there is no need to
295 * copy it until the next call to firedns.
296 */
297 return result->text;
298 else if (fdns_errno == FDNS_ERR_NXDOMAIN)
299 return NULL;
300 }
301
302 if (fdns_errno == FDNS_ERR_NONE)
303 fdns_errno = FDNS_ERR_TIMEOUT;
304
305 return NULL;
306 }
307
308 /*
309 * build, add and send specified query; retrieve result with
310 * firedns_getresult()
311 */
312 int
313 firedns_getip(int type, const char * const name, void *info)
314 {
315 struct s_connection *s;
316 node_t *node;
317 int fd;
318
319 s = firedns_add_query();
320 s->class = 1;
321 s->type = type;
322 s->info = info;
323 strlcpy(s->lookup, name, sizeof(s->lookup));
324
325 if (fdns_fdinuse >= OptionsItem->dns_fdlimit)
326 {
327 fdns_errno = FDNS_ERR_FDLIMIT;
328
329 /* Don't add to queue if there is no info */
330 if (info == NULL)
331 MyFree(s);
332 else
333 {
334 node = node_create(s);
335 list_add(CONNECTIONS, node);
336 }
337
338 return -1;
339 }
340
341 fd = firedns_doquery(s);
342
343 if (fd == -1)
344 {
345 MyFree(s);
346 return -1;
347 }
348
349 node = node_create(s);
350 list_add(CONNECTIONS, node);
351
352 return fd;
353 }
354
355 /* build DNS query, add to list */
356 static struct s_connection *
357 firedns_add_query(void)
358 {
359 struct s_connection *s;
360
361 /* create new connection object */
362 s = MyMalloc(sizeof *s);
363
364 /* verified by firedns_getresult() */
365 s->id[0] = rand() % 255;
366 s->id[1] = rand() % 255;
367 s->fd = -1;
368
369 return s;
370 }
371
372 static int
373 firedns_doquery(struct s_connection *s)
374 {
375 int len;
376 struct s_header h;
377
378 len = firedns_build_query_payload(s->lookup, s->type, 1, (unsigned char *)&h.payload);
379
380 if (len == -1)
381 {
382 fdns_errno = FDNS_ERR_FORMAT;
383 return -1;
384 }
385
386 return firedns_send_requests(&h, s, len);
387 }
388
389 /*
390 * populate payload with query: name= question, rr= record type
391 */
392 static int
393 firedns_build_query_payload(const char *const name, uint16_t rr, uint16_t class,
394 unsigned char *payload)
395 {
396 int16_t payloadpos = 0;
397 const char *tempchr, *tempchr2;
398 uint16_t l;
399
400 tempchr2 = name;
401
402 /* split name up into labels, create query */
403 while ((tempchr = strchr(tempchr2, '.')))
404 {
405 l = tempchr - tempchr2;
406
407 if (payloadpos + l + 1 > 507)
408 return -1;
409
410 payload[payloadpos++] = l;
411 memcpy(&payload[payloadpos], tempchr2, l);
412 payloadpos += l;
413 tempchr2 = &tempchr[1];
414 }
415
416 l = strlen(tempchr2);
417
418 if (l)
419 {
420 if (payloadpos + l + 2 > 507)
421 return -1;
422
423 payload[payloadpos++] = l;
424 memcpy(&payload[payloadpos], tempchr2, l);
425 payloadpos += l;
426 payload[payloadpos++] = '\0';
427 }
428
429 if (payloadpos > 508)
430 return -1;
431
432 l = htons(rr);
433 memcpy(&payload[payloadpos], &l, 2);
434
435 l = htons(class);
436 memcpy(&payload[payloadpos + 2], &l, 2);
437
438 return payloadpos + 4;
439 }
440
441 /* send DNS query */
442 static int
443 firedns_send_requests(struct s_header *h, struct s_connection *s, int l)
444 {
445 int i, sent_ok = 0;
446 struct sockaddr_in addr4;
447 struct sockaddr_in6 addr6;
448
449 /* set header flags */
450 h->flags1 = 0 | FLAGS1_MASK_RD;
451 h->flags2 = 0;
452 h->qdcount = htons(1);
453 h->ancount = htons(0);
454 h->nscount = htons(0);
455 h->arcount = htons(0);
456 memcpy(h->id, s->id, 2);
457
458 /* try to create ipv6 or ipv4 socket */
459 s->v6 = 0;
460
461 if (i6 > 0)
462 {
463 s->fd = socket(PF_INET6, SOCK_DGRAM, 0);
464
465 if (s->fd != -1)
466 {
467 if (fcntl(s->fd, F_SETFL, O_NONBLOCK))
468 {
469 close(s->fd);
470 s->fd = -1;
471 }
472 }
473
474 if (s->fd != -1)
475 {
476 struct sockaddr_in6 addr6;
477
478 memset(&addr6, 0, sizeof(addr6));
479 addr6.sin6_family = AF_INET6;
480
481 if (bind(s->fd, (struct sockaddr *)&addr6, sizeof(addr6)) == 0)
482 s->v6 = 1;
483 else
484 close(s->fd);
485 }
486 }
487
488 if (s->v6 == 0)
489 {
490 s->fd = socket(PF_INET, SOCK_DGRAM, 0);
491
492 if (s->fd != -1)
493 {
494 if (fcntl(s->fd, F_SETFL, O_NONBLOCK))
495 {
496 close(s->fd);
497 s->fd = -1;
498 }
499 }
500
501 if (s->fd != -1)
502 {
503 struct sockaddr_in addr;
504
505 memset(&addr, 0, sizeof(addr));
506 addr.sin_family = AF_INET;
507 addr.sin_port = 0;
508 addr.sin_addr.s_addr = INADDR_ANY;
509
510 if (bind(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
511 {
512 close(s->fd);
513 s->fd = -1;
514 }
515 }
516
517 if (s->fd == -1)
518 {
519 fdns_errno = FDNS_ERR_NETWORK;
520 return -1;
521 }
522 }
523
524 /* if we've got ipv6 support, an ip v6 socket, and ipv6 servers, send to them */
525 if (i6 > 0 && s->v6 == 1)
526 {
527 for (i = 0; i < i6; i++)
528 {
529 memset(&addr6, 0, sizeof(addr6));
530 memcpy(&addr6.sin6_addr, &servers6[i], sizeof(addr6.sin6_addr));
531
532 addr6.sin6_family = AF_INET6;
533 addr6.sin6_port = htons(FDNS_PORT);
534
535 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0)
536 sent_ok = 1;
537 }
538 }
539
540 for (i = 0; i < i4; i++)
541 {
542 /* send via ipv4-over-ipv6 if we've got an ipv6 socket */
543 if (s->v6 == 1)
544 {
545 memset(&addr6, 0, sizeof(addr6));
546 memcpy(addr6.sin6_addr.s6_addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
547 memcpy(&addr6.sin6_addr.s6_addr[12], &servers4[i].s_addr, 4);
548 addr6.sin6_family = AF_INET6;
549 addr6.sin6_port = htons(FDNS_PORT);
550
551 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0)
552 sent_ok = 1;
553
554 continue;
555 }
556
557 /* otherwise send via standard ipv4 boringness */
558 memset(&addr4, 0, sizeof(addr4));
559 memcpy(&addr4.sin_addr, &servers4[i], sizeof(addr4.sin_addr));
560 addr4.sin_family = AF_INET;
561 addr4.sin_port = htons(FDNS_PORT);
562
563 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr4, sizeof(addr4)) > 0)
564 sent_ok = 1;
565 }
566
567 if (!sent_ok)
568 {
569 close(s->fd);
570 s->fd = -1;
571 fdns_errno = FDNS_ERR_NETWORK;
572 return -1;
573 }
574
575 time(&s->start);
576 fdns_fdinuse++;
577 fdns_errno = FDNS_ERR_NONE;
578
579 return s->fd;
580 }
581
582 /* retrieve result of DNS query */
583 struct firedns_result *
584 firedns_getresult(const int fd)
585 {
586 static struct firedns_result result;
587 struct s_header h;
588 struct s_connection *c;
589 node_t *node;
590 int l, i, q, curanswer;
591 struct s_rr_middle *rr, rrbacking;
592 char *src, *dst;
593 int bytes;
594
595 fdns_errno = FDNS_ERR_OTHER;
596 result.info = NULL;
597
598 memset(result.text, 0, sizeof(result.text));
599
600 /* Find query in list of dns lookups */
601 LIST_FOREACH(node, CONNECTIONS->head)
602 {
603 c = node->data;
604
605 if (c->fd == fd)
606 break;
607 else
608 c = NULL;
609 }
610
611 /* query not found */
612 if (c == NULL)
613 return &result;
614
615 /* query found -- we remove in cleanup */
616 l = recv(c->fd, &h,sizeof(struct s_header), 0);
617 result.info = c->info;
618 strlcpy(result.lookup, c->lookup, sizeof(result.lookup));
619
620 if (l == -1)
621 {
622 fdns_errno = FDNS_ERR_NETWORK;
623 goto cleanup;
624 }
625
626 if (l < 12)
627 goto cleanup;
628
629 if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
630 /*
631 * ID mismatch: we keep the connection, as this could be an answer to
632 * a previous lookup..
633 */
634 return NULL;
635
636 if ((h.flags1 & FLAGS1_MASK_QR) == 0)
637 goto cleanup;
638
639 if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
640 goto cleanup;
641
642 if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
643 {
644 fdns_errno = (h.flags2 & FLAGS2_MASK_RCODE);
645 goto cleanup;
646 }
647
648 h.ancount = ntohs(h.ancount);
649
650 if (h.ancount < 1)
651 {
652 fdns_errno = FDNS_ERR_NXDOMAIN;
653 /* no sense going on if we don't have any answers */
654 goto cleanup;
655 }
656
657 /* skip queries */
658 i = 0;
659 q = 0;
660 l -= 12;
661 h.qdcount = ntohs(h.qdcount);
662
663 while (q < h.qdcount && i < l)
664 {
665 if (h.payload[i] > 63)
666 {
667 /* pointer */
668 i += 6; /* skip pointer, class and type */
669 q++;
670 }
671 else
672 {
673 /* label */
674 if (h.payload[i] == 0)
675 {
676 q++;
677 i += 5; /* skip nil, class and type */
678 }
679 else
680 i += h.payload[i] + 1; /* skip length and label */
681 }
682 }
683
684 /* &h.payload[i] should now be the start of the first response */
685 curanswer = 0;
686
687 while (curanswer < h.ancount)
688 {
689 q = 0;
690
691 while (q == 0 && i < l)
692 {
693 if (h.payload[i] > 63)
694 {
695 /* pointer */
696 i += 2; /* skip pointer */
697 q = 1;
698 }
699 else
700 {
701 /* label */
702 if (h.payload[i] == 0)
703 {
704 i++;
705 q = 1;
706 }
707 else
708 i += h.payload[i] + 1; /* skip length and label */
709 }
710 }
711
712 if (l - i < 10)
713 goto cleanup;
714
715 rr = (struct s_rr_middle *)&h.payload[i];
716 src = (char *)rr;
717 dst = (char *)&rrbacking;
718
719 for (bytes = sizeof(rrbacking); bytes; bytes--)
720 *dst++ = *src++;
721
722 rr = &rrbacking;
723 i += 10;
724 rr->rdlength = ntohs(rr->rdlength);
725
726 if (ntohs(rr->type) != c->type)
727 {
728 curanswer++;
729 i += rr->rdlength;
730 continue;
731 }
732
733 if (ntohs(rr->class) != c->class)
734 {
735 curanswer++;
736 i += rr->rdlength;
737 continue;
738 }
739
740 break;
741 }
742
743 if (curanswer == h.ancount)
744 goto cleanup;
745 if (i + rr->rdlength > l)
746 goto cleanup;
747 if (rr->rdlength > 1023)
748 goto cleanup;
749
750 fdns_errno = FDNS_ERR_NONE;
751 memcpy(result.text, &h.payload[i], rr->rdlength);
752 result.text[rr->rdlength] = '\0';
753
754 /* Clean-up */
755 cleanup:
756 list_remove(CONNECTIONS, node);
757 node_free(node);
758 close(c->fd);
759 fdns_fdinuse--;
760 MyFree(c);
761
762 return &result;
763 }
764
765 void
766 firedns_cycle(void)
767 {
768 node_t *node, *next;
769 struct s_connection *p;
770 struct firedns_result *res, new_result;
771 static struct pollfd *ufds = NULL;
772 int fd;
773 unsigned int size, i;
774 time_t timenow;
775
776 if (LIST_SIZE(CONNECTIONS) == 0)
777 return;
778
779 if (ufds == NULL)
780 ufds = MyMalloc((sizeof *ufds) * OptionsItem->dns_fdlimit);
781
782 time(&timenow);
783 size = 0;
784
785 LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
786 {
787 if (size >= OptionsItem->dns_fdlimit)
788 break;
789
790 p = node->data;
791
792 if (p->fd < 0)
793 continue;
794
795 if (p->fd > 0 && (p->start + FDNS_TIMEOUT) < timenow)
796 {
797 /* Timed out - remove from list */
798 list_remove(CONNECTIONS, node);
799 node_free(node);
800
801 memset(new_result.text, 0, sizeof(new_result.text));
802 new_result.info = p->info;
803 strlcpy(new_result.lookup, p->lookup, sizeof(new_result.lookup));
804
805 close(p->fd);
806 fdns_fdinuse--;
807 MyFree(p);
808
809 fdns_errno = FDNS_ERR_TIMEOUT;
810
811 if (new_result.info)
812 dnsbl_result(&new_result);
813
814 continue;
815 }
816
817 ufds[size].events = 0;
818 ufds[size].revents = 0;
819 ufds[size].fd = p->fd;
820 ufds[size].events = POLLIN;
821
822 size++;
823 }
824
825 switch (poll(ufds, size, 0))
826 {
827 case -1:
828 case 0:
829 return;
830 }
831
832 LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
833 {
834 p = node->data;
835
836 if (p->fd > 0)
837 {
838 for (i = 0; i < size; i++)
839 {
840 if ((ufds[i].revents & POLLIN) && ufds[i].fd == p->fd)
841 {
842 fd = p->fd;
843 res = firedns_getresult(fd);
844
845 if (res && res->info)
846 dnsbl_result(res);
847
848 break;
849 }
850 }
851 }
852 else if (fdns_fdinuse < OptionsItem->dns_fdlimit)
853 firedns_doquery(p);
854 }
855 }
856
857 const char *
858 firedns_strerror(int error)
859 {
860 if (error == FDNS_ERR_NETWORK)
861 return strerror(errno);
862
863 return errors[error];
864 }

Properties

Name Value
svn:eol-style native
svn:keywords Id