ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/hopm/trunk/src/firedns.c
Revision: 5081
Committed: Mon Dec 22 19:16:10 2014 UTC (9 years, 3 months ago) by michael
Content type: text/x-csrc
File size: 19016 byte(s)
Log Message:
- Removed rcs tags

File Contents

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