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