ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/hopm/trunk/src/firedns.c
Revision: 5380
Committed: Sat Jan 17 19:10:47 2015 UTC (9 years, 3 months ago) by michael
Content type: text/x-csrc
File size: 17465 byte(s)
Log Message:
- Use 'static' and 'extern' keywords where appropriate 

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 <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 "memory.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
52 /* Variables local to this file */
53 static unsigned int fdns_fdinuse;
54
55 /* up to FDNS_MAX nameservers; populated by firedns_init() */
56 static struct in_addr servers4[FDNS_MAX];
57 static struct in6_addr servers6[FDNS_MAX];
58 /* actual count of nameservers; set by firedns_init() */
59 static int i4;
60 static int i6;
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 char *p = NULL;
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 while (fgets(buf, sizeof(buf), f))
188 {
189 if ((p = strchr(buf, '\n')))
190 *p = '\0';
191
192 if (strncmp(buf, "nameserver", 10) == 0)
193 {
194 i = 10;
195
196 while (buf[i] == ' ' || buf[i] == '\t')
197 ++i;
198
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 while (fgets(buf, sizeof(buf), f))
219 {
220 if ((p = strchr(buf, '\n')))
221 *p = '\0';
222
223 if (i6 < FDNS_MAX)
224 {
225 if (inet_pton(AF_INET6, buf, &addr6) > 0)
226 {
227 memcpy(&servers6[i6++], &addr6, sizeof(struct in6_addr));
228 continue;
229 }
230 }
231
232 if (i4 < FDNS_MAX)
233 {
234 if (inet_pton(AF_INET, buf, &addr4) > 0)
235 memcpy(&servers4[i4++], &addr4, sizeof(struct in_addr));
236 }
237 }
238 }
239
240 fclose(f);
241 }
242
243 /* immediate A query */
244 struct in_addr *
245 firedns_resolveip4(const char *const name)
246 {
247 static struct in_addr addr;
248
249 if (inet_pton(AF_INET, name, &addr) > 0)
250 return &addr;
251
252 return (struct in_addr *)firedns_resolveip(FDNS_QRY_A, name);
253 }
254
255 /* immediate AAAA query */
256 struct in6_addr *
257 firedns_resolveip6(const char * const name)
258 {
259 static struct in6_addr addr;
260
261 if (inet_pton(AF_INET6, name, &addr) > 0)
262 return &addr;
263
264 return (struct in6_addr *)firedns_resolveip(FDNS_QRY_AAAA, name);
265 }
266
267 /* resolve a query of a given type */
268 char *
269 firedns_resolveip(int type, const char *const name)
270 {
271 struct firedns_result *result;
272 struct timeval tv;
273 fd_set s;
274
275 for (int t = 0; t < FIREDNS_TRIES; ++t)
276 {
277 int fd = firedns_getip(type, name, NULL);
278
279 if (fd == -1)
280 return NULL;
281
282 tv.tv_sec = 5;
283 tv.tv_usec = 0;
284 FD_ZERO(&s);
285 FD_SET(fd, &s);
286 select(fd + 1, &s, NULL, NULL, &tv);
287
288 result = firedns_getresult(fd);
289
290 if (fdns_errno == FDNS_ERR_NONE)
291 /*
292 * Return is from static memory in getresult, so there is no need to
293 * copy it until the next call to firedns.
294 */
295 return result->text;
296 else if (fdns_errno == FDNS_ERR_NXDOMAIN)
297 return NULL;
298 }
299
300 if (fdns_errno == FDNS_ERR_NONE)
301 fdns_errno = FDNS_ERR_TIMEOUT;
302
303 return NULL;
304 }
305
306 /*
307 * build, add and send specified query; retrieve result with
308 * firedns_getresult()
309 */
310 int
311 firedns_getip(int type, const char * const name, void *info)
312 {
313 struct s_connection *s;
314 node_t *node;
315 int fd;
316
317 s = firedns_add_query();
318 s->class = 1;
319 s->type = type;
320 s->info = info;
321 strlcpy(s->lookup, name, sizeof(s->lookup));
322
323 if (fdns_fdinuse >= OptionsItem->dns_fdlimit)
324 {
325 fdns_errno = FDNS_ERR_FDLIMIT;
326
327 /* Don't add to queue if there is no info */
328 if (info == NULL)
329 MyFree(s);
330 else
331 {
332 node = node_create(s);
333 list_add(CONNECTIONS, node);
334 }
335
336 return -1;
337 }
338
339 fd = firedns_doquery(s);
340
341 if (fd == -1)
342 {
343 MyFree(s);
344 return -1;
345 }
346
347 node = node_create(s);
348 list_add(CONNECTIONS, node);
349
350 return fd;
351 }
352
353 /* build DNS query, add to list */
354 static struct s_connection *
355 firedns_add_query(void)
356 {
357 struct s_connection *s;
358
359 /* create new connection object */
360 s = xcalloc(sizeof *s);
361
362 /* verified by firedns_getresult() */
363 s->id[0] = rand() % 255;
364 s->id[1] = rand() % 255;
365 s->fd = -1;
366
367 return s;
368 }
369
370 static int
371 firedns_doquery(struct s_connection *s)
372 {
373 int len;
374 struct s_header h;
375
376 len = firedns_build_query_payload(s->lookup, s->type, 1, (unsigned char *)&h.payload);
377
378 if (len == -1)
379 {
380 fdns_errno = FDNS_ERR_FORMAT;
381 return -1;
382 }
383
384 return firedns_send_requests(&h, s, len);
385 }
386
387 /*
388 * populate payload with query: name= question, rr= record type
389 */
390 static int
391 firedns_build_query_payload(const char *const name, uint16_t rr, uint16_t class,
392 unsigned char *payload)
393 {
394 int16_t payloadpos = 0;
395 const char *tempchr, *tempchr2;
396 uint16_t l;
397
398 tempchr2 = name;
399
400 /* split name up into labels, create query */
401 while ((tempchr = strchr(tempchr2, '.')))
402 {
403 l = tempchr - tempchr2;
404
405 if (payloadpos + l + 1 > 507)
406 return -1;
407
408 payload[payloadpos++] = l;
409 memcpy(&payload[payloadpos], tempchr2, l);
410 payloadpos += l;
411 tempchr2 = &tempchr[1];
412 }
413
414 l = strlen(tempchr2);
415
416 if (l)
417 {
418 if (payloadpos + l + 2 > 507)
419 return -1;
420
421 payload[payloadpos++] = l;
422 memcpy(&payload[payloadpos], tempchr2, l);
423 payloadpos += l;
424 payload[payloadpos++] = '\0';
425 }
426
427 if (payloadpos > 508)
428 return -1;
429
430 l = htons(rr);
431 memcpy(&payload[payloadpos], &l, 2);
432
433 l = htons(class);
434 memcpy(&payload[payloadpos + 2], &l, 2);
435
436 return payloadpos + 4;
437 }
438
439 /* send DNS query */
440 static int
441 firedns_send_requests(struct s_header *h, struct s_connection *s, int l)
442 {
443 int i, sent_ok = 0;
444 struct sockaddr_in addr4;
445 struct sockaddr_in6 addr6;
446
447 /* set header flags */
448 h->flags1 = 0 | FLAGS1_MASK_RD;
449 h->flags2 = 0;
450 h->qdcount = htons(1);
451 h->ancount = htons(0);
452 h->nscount = htons(0);
453 h->arcount = htons(0);
454 memcpy(h->id, s->id, 2);
455
456 /* try to create ipv6 or ipv4 socket */
457 s->v6 = 0;
458
459 if (i6 > 0)
460 {
461 s->fd = socket(PF_INET6, SOCK_DGRAM, 0);
462
463 if (s->fd != -1)
464 {
465 if (fcntl(s->fd, F_SETFL, O_NONBLOCK))
466 {
467 close(s->fd);
468 s->fd = -1;
469 }
470 }
471
472 if (s->fd != -1)
473 {
474 struct sockaddr_in6 addr6;
475
476 memset(&addr6, 0, sizeof(addr6));
477 addr6.sin6_family = AF_INET6;
478
479 if (bind(s->fd, (struct sockaddr *)&addr6, sizeof(addr6)) == 0)
480 s->v6 = 1;
481 else
482 close(s->fd);
483 }
484 }
485
486 if (s->v6 == 0)
487 {
488 s->fd = socket(PF_INET, SOCK_DGRAM, 0);
489
490 if (s->fd != -1)
491 {
492 if (fcntl(s->fd, F_SETFL, O_NONBLOCK))
493 {
494 close(s->fd);
495 s->fd = -1;
496 }
497 }
498
499 if (s->fd != -1)
500 {
501 struct sockaddr_in addr;
502
503 memset(&addr, 0, sizeof(addr));
504 addr.sin_family = AF_INET;
505 addr.sin_port = 0;
506 addr.sin_addr.s_addr = INADDR_ANY;
507
508 if (bind(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
509 {
510 close(s->fd);
511 s->fd = -1;
512 }
513 }
514
515 if (s->fd == -1)
516 {
517 fdns_errno = FDNS_ERR_NETWORK;
518 return -1;
519 }
520 }
521
522 /* if we've got ipv6 support, an ip v6 socket, and ipv6 servers, send to them */
523 if (i6 > 0 && s->v6 == 1)
524 {
525 for (i = 0; i < i6; i++)
526 {
527 memset(&addr6, 0, sizeof(addr6));
528 memcpy(&addr6.sin6_addr, &servers6[i], sizeof(addr6.sin6_addr));
529
530 addr6.sin6_family = AF_INET6;
531 addr6.sin6_port = htons(FDNS_PORT);
532
533 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0)
534 sent_ok = 1;
535 }
536 }
537
538 for (i = 0; i < i4; i++)
539 {
540 /* send via ipv4-over-ipv6 if we've got an ipv6 socket */
541 if (s->v6 == 1)
542 {
543 memset(&addr6, 0, sizeof(addr6));
544 memcpy(addr6.sin6_addr.s6_addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
545 memcpy(&addr6.sin6_addr.s6_addr[12], &servers4[i].s_addr, 4);
546 addr6.sin6_family = AF_INET6;
547 addr6.sin6_port = htons(FDNS_PORT);
548
549 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr6, sizeof(addr6)) > 0)
550 sent_ok = 1;
551
552 continue;
553 }
554
555 /* otherwise send via standard ipv4 boringness */
556 memset(&addr4, 0, sizeof(addr4));
557 memcpy(&addr4.sin_addr, &servers4[i], sizeof(addr4.sin_addr));
558 addr4.sin_family = AF_INET;
559 addr4.sin_port = htons(FDNS_PORT);
560
561 if (sendto(s->fd, h, l + 12, 0, (struct sockaddr *)&addr4, sizeof(addr4)) > 0)
562 sent_ok = 1;
563 }
564
565 if (!sent_ok)
566 {
567 close(s->fd);
568 s->fd = -1;
569 fdns_errno = FDNS_ERR_NETWORK;
570 return -1;
571 }
572
573 time(&s->start);
574 fdns_fdinuse++;
575 fdns_errno = FDNS_ERR_NONE;
576
577 return s->fd;
578 }
579
580 /* retrieve result of DNS query */
581 struct firedns_result *
582 firedns_getresult(const int fd)
583 {
584 static struct firedns_result result;
585 struct s_header h;
586 struct s_connection *c;
587 node_t *node;
588 int l, i, q, curanswer;
589 struct s_rr_middle *rr, rrbacking;
590 char *src, *dst;
591 int bytes;
592
593 fdns_errno = FDNS_ERR_OTHER;
594 result.info = NULL;
595
596 memset(result.text, 0, sizeof(result.text));
597
598 /* Find query in list of dns lookups */
599 LIST_FOREACH(node, CONNECTIONS->head)
600 {
601 c = node->data;
602
603 if (c->fd == fd)
604 break;
605 else
606 c = NULL;
607 }
608
609 /* query not found */
610 if (c == NULL)
611 return &result;
612
613 /* query found -- we remove in cleanup */
614 l = recv(c->fd, &h,sizeof(struct s_header), 0);
615 result.info = c->info;
616 strlcpy(result.lookup, c->lookup, sizeof(result.lookup));
617
618 if (l == -1)
619 {
620 fdns_errno = FDNS_ERR_NETWORK;
621 goto cleanup;
622 }
623
624 if (l < 12)
625 goto cleanup;
626
627 if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
628 /*
629 * ID mismatch: we keep the connection, as this could be an answer to
630 * a previous lookup..
631 */
632 return NULL;
633
634 if ((h.flags1 & FLAGS1_MASK_QR) == 0)
635 goto cleanup;
636
637 if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
638 goto cleanup;
639
640 if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
641 {
642 fdns_errno = (h.flags2 & FLAGS2_MASK_RCODE);
643 goto cleanup;
644 }
645
646 h.ancount = ntohs(h.ancount);
647
648 if (h.ancount < 1)
649 {
650 fdns_errno = FDNS_ERR_NXDOMAIN;
651 /* no sense going on if we don't have any answers */
652 goto cleanup;
653 }
654
655 /* skip queries */
656 i = 0;
657 q = 0;
658 l -= 12;
659 h.qdcount = ntohs(h.qdcount);
660
661 while (q < h.qdcount && i < l)
662 {
663 if (h.payload[i] > 63)
664 {
665 /* pointer */
666 i += 6; /* skip pointer, class and type */
667 q++;
668 }
669 else
670 {
671 /* label */
672 if (h.payload[i] == 0)
673 {
674 q++;
675 i += 5; /* skip nil, class and type */
676 }
677 else
678 i += h.payload[i] + 1; /* skip length and label */
679 }
680 }
681
682 /* &h.payload[i] should now be the start of the first response */
683 curanswer = 0;
684
685 while (curanswer < h.ancount)
686 {
687 q = 0;
688
689 while (q == 0 && i < l)
690 {
691 if (h.payload[i] > 63)
692 {
693 /* pointer */
694 i += 2; /* skip pointer */
695 q = 1;
696 }
697 else
698 {
699 /* label */
700 if (h.payload[i] == 0)
701 {
702 i++;
703 q = 1;
704 }
705 else
706 i += h.payload[i] + 1; /* skip length and label */
707 }
708 }
709
710 if (l - i < 10)
711 goto cleanup;
712
713 rr = (struct s_rr_middle *)&h.payload[i];
714 src = (char *)rr;
715 dst = (char *)&rrbacking;
716
717 for (bytes = sizeof(rrbacking); bytes; bytes--)
718 *dst++ = *src++;
719
720 rr = &rrbacking;
721 i += 10;
722 rr->rdlength = ntohs(rr->rdlength);
723
724 if (ntohs(rr->type) != c->type)
725 {
726 curanswer++;
727 i += rr->rdlength;
728 continue;
729 }
730
731 if (ntohs(rr->class) != c->class)
732 {
733 curanswer++;
734 i += rr->rdlength;
735 continue;
736 }
737
738 break;
739 }
740
741 if (curanswer == h.ancount)
742 goto cleanup;
743 if (i + rr->rdlength > l)
744 goto cleanup;
745 if (rr->rdlength > 1023)
746 goto cleanup;
747
748 fdns_errno = FDNS_ERR_NONE;
749 memcpy(result.text, &h.payload[i], rr->rdlength);
750 result.text[rr->rdlength] = '\0';
751
752 /* Clean-up */
753 cleanup:
754 list_remove(CONNECTIONS, node);
755 node_free(node);
756 close(c->fd);
757 fdns_fdinuse--;
758 MyFree(c);
759
760 return &result;
761 }
762
763 void
764 firedns_cycle(void)
765 {
766 node_t *node, *next;
767 struct s_connection *p;
768 struct firedns_result *res, new_result;
769 static struct pollfd *ufds = NULL;
770 int fd;
771 unsigned int size, i;
772 time_t timenow;
773
774 if (LIST_SIZE(CONNECTIONS) == 0)
775 return;
776
777 if (ufds == NULL)
778 ufds = xcalloc((sizeof *ufds) * OptionsItem->dns_fdlimit);
779
780 time(&timenow);
781 size = 0;
782
783 LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
784 {
785 if (size >= OptionsItem->dns_fdlimit)
786 break;
787
788 p = node->data;
789
790 if (p->fd < 0)
791 continue;
792
793 if (p->fd > 0 && (p->start + FDNS_TIMEOUT) < timenow)
794 {
795 /* Timed out - remove from list */
796 list_remove(CONNECTIONS, node);
797 node_free(node);
798
799 memset(new_result.text, 0, sizeof(new_result.text));
800 new_result.info = p->info;
801 strlcpy(new_result.lookup, p->lookup, sizeof(new_result.lookup));
802
803 close(p->fd);
804 fdns_fdinuse--;
805 MyFree(p);
806
807 fdns_errno = FDNS_ERR_TIMEOUT;
808
809 if (new_result.info)
810 dnsbl_result(&new_result);
811
812 continue;
813 }
814
815 ufds[size].events = 0;
816 ufds[size].revents = 0;
817 ufds[size].fd = p->fd;
818 ufds[size].events = POLLIN;
819
820 size++;
821 }
822
823 switch (poll(ufds, size, 0))
824 {
825 case -1:
826 case 0:
827 return;
828 }
829
830 LIST_FOREACH_SAFE(node, next, CONNECTIONS->head)
831 {
832 p = node->data;
833
834 if (p->fd > 0)
835 {
836 for (i = 0; i < size; i++)
837 {
838 if ((ufds[i].revents & POLLIN) && ufds[i].fd == p->fd)
839 {
840 fd = p->fd;
841 res = firedns_getresult(fd);
842
843 if (res && res->info)
844 dnsbl_result(res);
845
846 break;
847 }
848 }
849 }
850 else if (fdns_fdinuse < OptionsItem->dns_fdlimit)
851 firedns_doquery(p);
852 }
853 }
854
855 const char *
856 firedns_strerror(int error)
857 {
858 if (error == FDNS_ERR_NETWORK)
859 return strerror(errno);
860
861 return errors[error];
862 }

Properties

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