OSDN Git Service

fixing resolver part 3: fix completely bogus locking
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 14 Nov 2008 00:37:48 +0000 (00:37 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 14 Nov 2008 00:37:48 +0000 (00:37 -0000)
 in __dns_lookup.

libc/inet/resolv.c

index b7ab276..d388db1 100644 (file)
@@ -222,16 +222,16 @@ int __libc_getdomainname(char *name, size_t len);
 libc_hidden_proto(__libc_getdomainname)
 
 
-#define MAX_RECURSE 5
+#define MAX_RECURSE    5
 #define REPLY_TIMEOUT 10
-#define MAX_RETRIES 3
-#define MAX_SERVERS 3
-#define MAX_SEARCH 4
+#define MAX_RETRIES    3
+#define MAX_SERVERS    3
+#define MAX_SEARCH     4
 
-#define MAX_ALIASES    5
+#define MAX_ALIASES    5
 
 /* 1:ip + 1:full + MAX_ALIASES:aliases + 1:NULL */
-#define ALIAS_DIM              (2 + MAX_ALIASES + 1)
+#define ALIAS_DIM      (2 + MAX_ALIASES + 1)
 
 #undef DEBUG
 /* #define DEBUG */
@@ -251,14 +251,6 @@ libc_hidden_proto(__libc_getdomainname)
 #define ALIGN_BUFFER_OFFSET(buf) ((ALIGN_ATTR - ((size_t)buf % ALIGN_ATTR)) % ALIGN_ATTR)
 
 
-/* Global stuff (stuff needing to be locked to be thread safe)... */
-extern int __nameservers attribute_hidden;
-extern char * __nameserver[MAX_SERVERS] attribute_hidden;
-extern int __searchdomains attribute_hidden;
-extern char * __searchdomain[MAX_SEARCH] attribute_hidden;
-
-
-
 /* Structs */
 struct resolv_header {
        int id;
@@ -294,6 +286,26 @@ enum etc_hosts_action {
        GET_HOSTS_BYADDR,
 };
 
+typedef union sockaddr46_t {
+       struct sockaddr sa;
+#ifdef __UCLIBC_HAS_IPV4__
+       struct sockaddr_in sa4;
+#endif
+#ifdef __UCLIBC_HAS_IPV6__
+       struct sockaddr_in6 sa6;
+#endif
+} sockaddr46_t;
+
+
+/* Protected by __resolv_lock */
+extern int __nameservers attribute_hidden;
+extern int __searchdomains attribute_hidden;
+extern sockaddr46_t *__nameserver attribute_hidden;
+extern char **__searchdomain attribute_hidden;
+/* Arbitrary */
+#define MAXLEN_searchdomain 128
+
+
 /* function prototypes */
 extern int __get_hosts_byname_r(const char * name, int type,
                              struct hostent * result_buf,
@@ -312,8 +324,8 @@ extern int __read_etc_hosts_r(FILE *fp, const char * name, int type,
                            char * buf, size_t buflen,
                            struct hostent ** result,
                            int * h_errnop) attribute_hidden;
-extern int __dns_lookup(const char * name, int type, int nscount,
-       char ** nsip, unsigned char ** outpacket, struct resolv_answer * a) attribute_hidden;
+extern int __dns_lookup(const char * name, int type,
+       unsigned char ** outpacket, struct resolv_answer * a) attribute_hidden;
 
 extern int __encode_dotted(const char * dotted, unsigned char * dest, int maxlen) attribute_hidden;
 extern int __decode_dotted(const unsigned char * const message, int offset,
@@ -731,16 +743,10 @@ int __form_query(int id, const char *name, int type, unsigned char *packet,
 #endif
 
 #ifdef L_dnslookup
-__UCLIBC_MUTEX_STATIC(mylock, PTHREAD_MUTEX_INITIALIZER);
 
-/* Just for the record, having to lock __dns_lookup() just for these two globals
- * is pretty lame.  I think these two variables can probably be de-global-ized,
- * which should eliminate the need for doing locking here...  Needs a closer
- * look anyways. */
-static int static_ns = 0;
-/* uint16: minimizing rw data size, even if code grows a tiny bit.
- * rw data costs more. */
-static uint16_t static_id = 1;
+/* Protected by __resolv_lock */
+static int last_ns_num = 0;
+static uint16_t last_id = 1;
 
 /* On entry:
  *  a.buf(len) = auxiliary buffer for IP addresses after first one
@@ -759,15 +765,11 @@ static uint16_t static_id = 1;
  *      This is a malloced string. May be NULL because strdup failed.
  */
 int attribute_hidden __dns_lookup(const char *name, int type,
-                       int nscount, char **nsip,
                        unsigned char **outpacket,
                        struct resolv_answer *a)
-//FIXME: nscount/nsip are *always* taken from __nameservers and __nameserver[]
-//globals. Locking is busted: __nameserver[] can be freed under us.
-//Fix it by eliminating these params and accessing __nameserver(s)
-//only under lock inside this routine.
 {
        int i, j, len, fd, pos, rc;
+       int name_len;
 #ifdef USE_SELECT
        struct timeval tv;
        fd_set fds;
@@ -780,119 +782,127 @@ int attribute_hidden __dns_lookup(const char *name, int type,
        bool first_answer = 1;
        unsigned retries = 0;
        unsigned char *packet = malloc(PACKETSZ);
-       char *dns, *lookup = malloc(MAXDNAME);
-       int variant = -1;  /* search domain to append, -1 - none */
-       int local_ns = -1, local_id = -1;
+       char *lookup;
+       int variant = -1;  /* search domain to append, -1: none */
+       int local_ns_num = -1; /* Nth server to use */
+       int local_id = local_id; /* for compiler */
+       int sdomains;
        bool ends_with_dot;
-       union {
-               struct sockaddr sa;
-#ifdef __UCLIBC_HAS_IPV4__
-               struct sockaddr_in sa4;
-#endif
-#ifdef __UCLIBC_HAS_IPV6__
-               struct sockaddr_in6 sa6;
-#endif
-       } sa;
+       sockaddr46_t sa;
 
        fd = -1;
-
-       if (!packet || !lookup || !nscount || !name[0])
+       lookup = NULL;
+       name_len = strlen(name);
+       if ((unsigned)name_len >= MAXDNAME - MAXLEN_searchdomain - 2)
+               goto fail; /* paranoia */
+       lookup = malloc(name_len + 1/*for '.'*/ + MAXLEN_searchdomain + 1);
+       if (!packet || !lookup || !name[0])
                goto fail;
-
+       ends_with_dot = (name[name_len - 1] == '.');
        DPRINTF("Looking up type %d answer for '%s'\n", type, name);
 
-       ends_with_dot = (name[strlen(name) - 1] == '.');
-
-       /* Mess with globals while under lock */
-       __UCLIBC_MUTEX_LOCK(mylock);
-       local_ns = static_ns % nscount;
-       local_id = static_id;
-       __UCLIBC_MUTEX_UNLOCK(mylock);
-
        while (retries < MAX_RETRIES) {
-               if (fd != -1)
+               if (fd != -1) {
                        close(fd);
+                       fd = -1;
+               }
 
-               memset(packet, 0, PACKETSZ);
+               /* no strcpy! paranoia, user might change name[] under us */
+               memcpy(lookup, name, name_len);
+               lookup[name_len] = '\0';
 
+               /* Mess with globals while under lock */
+               /* NB: even data *pointed to* by globals may vanish
+                * outside the locks. We should assume any and all
+                * globals can completely change between locked
+                * code regions. OTOH, this is rare, so we don't need
+                * to handle it "nicely" (do not skip servers,
+                * search domains, etc), we only need to ensure
+                * we do not SEGV, use freed+overwritten data
+                * or do other Really Bad Things. */
+               __UCLIBC_MUTEX_LOCK(__resolv_lock);
+               __open_nameservers();
+               sdomains = __searchdomains;
+               if ((unsigned)variant < sdomains) {
+                       /* lookup is name_len + 1 + MAXLEN_searchdomain + 1 long */
+                       /* __searchdomain[] is not bigger than MAXLEN_searchdomain */
+                       lookup[name_len] = '.';
+                       strcpy(&lookup[name_len + 1], __searchdomain[variant]);
+               }
+               if (local_ns_num < 0) { /* first time */
+                       local_id = last_id;
+//TODO: implement /etc/resolv.conf's "options rotate"
+// (a.k.a. RES_ROTATE bit in _res.options)
+//                     local_ns_num = 0;
+//                     if (_res.options & RES_ROTATE)
+                               local_ns_num = last_ns_num;
+                       if (local_ns_num >= __nameservers)
+                               local_ns_num = 0;
+               }
+               /* __nameservers == 0 case: act as if we
+                * have one DNS server configured - on 127.0.0.1 */
+               {
+                       int my_nameservers = __nameservers;
+                       if (my_nameservers == 0)
+                               my_nameservers++;
+                       if (local_ns_num >= my_nameservers) {
+                               local_ns_num = 0;
+                               retries++;
+                               /* break if retries >= MAX_RETRIES - *after unlock*! */
+                       }
+               }
+               local_id++;
+               local_id &= 0xffff;
+               /* write new values back while still under lock */
+               last_id = local_id;
+               last_ns_num = local_ns_num;
+               if (__nameservers != 0) {
+                       /* struct copy */
+                       /* can't just take a pointer, __nameserver[]
+                        * is not safe to use outside of locks */
+                       sa = __nameserver[local_ns_num];
+               } else {
+                       memset(&sa, 0, sizeof(sa));
+#ifdef __UCLIBC_HAS_IPV4__
+                       sa.sa4.sin_family = AF_INET;
+                       /*sa.sa4.sin_addr = INADDR_ANY; - done by memset */
+                       sa.sa4.sin_port = htons(NAMESERVER_PORT);
+#else
+                       sa.sa6.sin_family = AF_INET6;
+                       sa.sa6.sin6_port = htons(NAMESERVER_PORT);
+#endif
+               }
+               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
+               if (retries >= MAX_RETRIES)
+                       break;
+
+               memset(packet, 0, PACKETSZ);
                memset(&h, 0, sizeof(h));
 
-               ++local_id;
-               local_id &= 0xffff;
+               /* encode header */
                h.id = local_id;
                h.qdcount = 1;
                h.rd = 1;
-
                DPRINTF("encoding header\n", h.rd);
-
                i = __encode_header(&h, packet, PACKETSZ);
                if (i < 0)
                        goto fail;
 
-               strncpy(lookup, name, MAXDNAME);
-               __UCLIBC_MUTEX_LOCK(__resolv_lock);
-               /* nsip is really __nameserver[] which is a global that
-                  needs to hold __resolv_lock before access!! */
-               dns = nsip[local_ns];
-/* TODO: all future accesses to 'dns' were guarded by __resolv_lock too.
- * Why? We already fetched nsip[local_ns] here,
- * future changes to nsip[] by other threads cannot affect us.
- * We can use 'dns' without locking. If I'm wrong,
- * please explain in comments why locking is needed.
- * One thing that worries me is - what if __close_nameservers() free()s
- * dns under us? __resolv_lock'ing around accesses to dns won't help either,
- * as free() might occur between accesses!
- */
-               if (variant >= 0) {
-                       if (variant < __searchdomains) {
-                               strncat(lookup, ".", MAXDNAME);
-                               strncat(lookup, __searchdomain[variant], MAXDNAME);
-                       }
-               }
-               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-
+               /* encode question */
                DPRINTF("lookup name: %s\n", lookup);
                q.dotted = lookup;
                q.qtype = type;
                q.qclass = C_IN; /* CLASS_IN */
-
                j = __encode_question(&q, packet+i, PACKETSZ-i);
                if (j < 0)
                        goto fail;
-
                len = i + j;
 
-               DPRINTF("On try %d, sending query to port %d of machine %s\n",
-                               retries+1, NAMESERVER_PORT, dns);
-
-               sa.sa.sa_family = AF_INET;
-#ifdef __UCLIBC_HAS_IPV6__
-               //__UCLIBC_MUTEX_LOCK(__resolv_lock);
-               ///* 'dns' is really __nameserver[] which is a global that
-               //   needs to hold __resolv_lock before access!! */
-               if (inet_pton(AF_INET6, dns, &sa.sa6.sin6_addr) > 0)
-                       sa.sa.sa_family = AF_INET6;
-               //__UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-#endif
-               /* Connect to the UDP socket so that asyncronous errors are returned */
-#ifdef __UCLIBC_HAS_IPV6__
-               if (sa.sa.sa_family == AF_INET6) {
-                       sa.sa6.sin6_port = htons(NAMESERVER_PORT);
-                       /* sa6.sin6_addr is already here */
-               } else
-#endif
-#ifdef __UCLIBC_HAS_IPV4__
-               {
-                       sa.sa4.sin_port = htons(NAMESERVER_PORT);
-                       //__UCLIBC_MUTEX_LOCK(__resolv_lock);
-                       ///* 'dns' is really __nameserver[] which is a global that
-                       //   needs to hold __resolv_lock before access!! */
-                       sa.sa4.sin_addr.s_addr = inet_addr(dns);
-                       //__UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-               }
-#endif
+               /* send packet */
+               DPRINTF("On try %d, sending query to port %d\n",
+                               retries+1, NAMESERVER_PORT);
                fd = socket(sa.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
-               if (fd < 0) {
+               if (fd < 0) { /* paranoia */
                        retries++;
                        continue;
                }
@@ -900,16 +910,13 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                if (rc < 0) {
                        if (errno == ENETUNREACH) {
                                /* routing error, presume not transient */
-                               goto tryall;
+                               goto try_next_server;
                        }
                        /* retry */
                        retries++;
                        continue;
                }
-
-               DPRINTF("Transmitting packet of length %d, id=%d, qr=%d\n",
-                               len, h.id, h.qr);
-
+               DPRINTF("Xmit packet len:%d id:%d qr:%d\n", len, h.id, h.qr);
                send(fd, packet, len, 0);
 
 #ifdef USE_SELECT
@@ -919,34 +926,35 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                tv.tv_usec = 0;
                if (select(fd + 1, &fds, NULL, NULL, &tv) <= 0) {
                        DPRINTF("Timeout\n");
-                       /* timed out, so retry send and receive,
-                        * to next nameserver on queue */
-                       goto tryall;
+                       /* timed out, so retry send and receive
+                        * to next nameserver */
+                       goto try_next_server;
                }
 #else
                fds.fd = fd;
                fds.events = POLLIN;
                if (poll(&fds, 1, REPLY_TIMEOUT * 1000) <= 0) {
                        DPRINTF("Timeout\n");
-                       /* timed out, so retry send and receive,
-                        * to next nameserver on queue */
-                       goto tryall;
+                       /* timed out, so retry send and receive
+                        * to next nameserver */
+                       goto try_next_server;
                }
 #endif
-
+//TODO: MSG_DONTWAIT?
                len = recv(fd, packet, PACKETSZ, 0);
                if (len < HFIXEDSZ) {
                        /* too short! */
-                       goto again;
+//TODO: why next sdomain? it's just a bogus packet from somewhere,
+// we can as well wait more...
+                       goto try_next_sdomain;
                }
 
                __decode_header(packet, &h);
-
                DPRINTF("id = %d, qr = %d\n", h.id, h.qr);
-
                if ((h.id != local_id) || (!h.qr)) {
                        /* unsolicited */
-                       goto again;
+//TODO: why next sdomain?...
+                       goto try_next_sdomain;
                }
 
                DPRINTF("Got response (i think)!\n");
@@ -960,12 +968,9 @@ int attribute_hidden __dns_lookup(const char *name, int type,
 // which is, eh, an error. :) We were incurring long delays because of this.
                        /* if possible, try next search domain */
                        if (!ends_with_dot) {
-                               int sdomains;
-                               __UCLIBC_MUTEX_LOCK(__resolv_lock);
-                               sdomains = __searchdomains;
-                               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
+                               DPRINTF("variant:%d sdomains:%d\n", variant, sdomains);
                                if (variant < sdomains - 1) {
-                                       /* next search */
+                                       /* next search domain */
                                        variant++;
                                        continue;
                                }
@@ -975,21 +980,20 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                        goto fail1;
                }
                if (h.rcode != 0) /* error */
-                       goto again;
+                       goto try_next_sdomain;
                /* code below won't work correctly with h.ancount == 0, so... */
-               if (h.ancount < 1) {
+               if (h.ancount <= 0) {
                        h_errno = NO_DATA; /* is this correct code? */
                        goto fail1;
                }
 
                pos = HFIXEDSZ;
-
                for (j = 0; j < h.qdcount; j++) {
                        DPRINTF("Skipping question %d at %d\n", j, pos);
                        /* returns -1 only if packet == NULL (can't happen) */
                        i = __length_question(packet, pos);
                        //if (i < 0)
-                       //      goto again;
+                       //      goto try_next_sdomain;
                        DPRINTF("Length of question %d is %d\n", j, i);
                        pos += i;
                }
@@ -1001,10 +1005,10 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                        if (i < 0) {
                                DPRINTF("failed decode %d\n", i);
                                /* if the message was truncated and we have
-                                  decoded some answers, pretend it's OK */
+                                * decoded some answers, pretend it's OK */
                                if (j && h.tc)
                                        break;
-                               goto again;
+                               goto try_next_sdomain;
                        }
                        pos += i;
 
@@ -1032,7 +1036,7 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                                        free(a->dotted);
                                        DPRINTF("Answer address len(%u) differs from original(%u)\n",
                                                        ma.rdlength, a->rdlength);
-                                       goto again;
+                                       goto try_next_sdomain;
                                }
                                memcpy(a->buf + (a->add_count * ma.rdlength), ma.rdata, ma.rdlength);
                                ++a->add_count;
@@ -1041,52 +1045,29 @@ int attribute_hidden __dns_lookup(const char *name, int type,
 
                DPRINTF("Answer name = |%s|\n", a->dotted);
                DPRINTF("Answer type = |%d|\n", a->atype);
-
-               close(fd);
-
+               if (fd != -1)
+                       close(fd);
                if (outpacket)
                        *outpacket = packet;
                else
                        free(packet);
                free(lookup);
+               return len; /* success! */
 
-               /* Mess with globals while under lock */
-               __UCLIBC_MUTEX_LOCK(mylock);
-               static_ns = local_ns;
-               static_id = local_id;
-               __UCLIBC_MUTEX_UNLOCK(mylock);
-
-               return len;                             /* success! */
-
- tryall:
-               /* if there are other nameservers, give them a go,
-                  otherwise return with error */
-               variant = -1;
-               local_ns = (local_ns + 1) % nscount;
-               if (local_ns == 0)
-                       retries++;
-
-               continue;
-
- again:
-               /* if there are searchdomains, try them or fallback as passed */
+ try_next_sdomain:
+               /* if there are searchdomains, try them */
                if (!ends_with_dot) {
-                       int sdomains;
-                       __UCLIBC_MUTEX_LOCK(__resolv_lock);
-                       sdomains = __searchdomains;
-                       __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
                        if (variant < sdomains - 1) {
                                /* next search */
                                variant++;
                                continue;
                        }
                }
-               /* next server, first search */
-               local_ns = (local_ns + 1) % nscount;
-               if (local_ns == 0)
-                       retries++;
+ try_next_server:
+               /* if there are other nameservers, try them */
+               local_ns_num++;
                variant = -1;
-       }
+       } /* while (retries < MAX_RETRIES) */
 
  fail:
        h_errno = NETDB_INTERNAL;
@@ -1095,116 +1076,161 @@ int attribute_hidden __dns_lookup(const char *name, int type,
                close(fd);
        free(lookup);
        free(packet);
-       /* Mess with globals while under lock */
-       if (local_ns != -1) {
-               __UCLIBC_MUTEX_LOCK(mylock);
-               static_ns = local_ns;
-               static_id = local_id;
-               __UCLIBC_MUTEX_UNLOCK(mylock);
-       }
        return -1;
 }
 #endif
 
 #ifdef L_opennameservers
 
-/* We use __resolv_lock to guard access to the
- * '__nameservers' and __searchdomains globals */
+//TODO: need not be recursive
+__UCLIBC_MUTEX_INIT(__resolv_lock, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
+
+/* Protected by __resolv_lock */
 int __nameservers;
-char * __nameserver[MAX_SERVERS];
 int __searchdomains;
-char * __searchdomain[MAX_SEARCH];
+sockaddr46_t *__nameserver;
+char **__searchdomain;
 
-__UCLIBC_MUTEX_INIT(__resolv_lock, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
-
-/*
- *     we currently read formats not quite the same as that on normal
- *     unix systems, we can have a list of nameservers after the keyword.
- */
+/* Helpers. Both stop on EOL, if it's '\n', it is converted to NUL first */
+static char *skip_nospace(char *p)
+{
+       while (*p != '\0' && !isspace(*p)) {
+               if (*p == '\n') {
+                       *p = '\0';
+                       break;
+               }
+               p++;
+       }
+       return p;
+}
+static char *skip_and_NUL_space(char *p)
+{
+       /* NB: '\n' is not isspace! */
+       while (1) {
+               char c = *p;
+               if (c == '\0' || !isspace(c))
+                       break;
+               *p = '\0';
+               if (c == '\n' || c == '#')
+                       break;
+               p++;
+       }
+       return p;
+}
 
+/* Must be called under __resolv_lock. */
 void attribute_hidden __open_nameservers(void)
 {
+       char szBuffer[MAXLEN_searchdomain];
        FILE *fp;
        int i;
-#define RESOLV_ARGS 5
-       char szBuffer[128], *p, *argv[RESOLV_ARGS];
-       int argc;
+       sockaddr46_t sa;
 
-       __UCLIBC_MUTEX_LOCK(__resolv_lock);
        if (__nameservers > 0)
-               goto DONE;
+               return;
 
-       if ((fp = fopen("/etc/resolv.conf", "r")) ||
-               (fp = fopen("/etc/config/resolv.conf", "r")))
-       {
-               while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) {
-                       for (p = szBuffer; *p && isspace(*p); p++)
-                               /* skip white space */;
-                       if (*p == '\0' || *p == '\n' || *p == '#') /* skip comments etc */
-                               continue;
-                       argc = 0;
-                       while (*p && argc < RESOLV_ARGS) {
-                               argv[argc++] = p;
-                               while (*p && !isspace(*p) && *p != '\n')
-                                       p++;
-                               while (*p && (isspace(*p) || *p == '\n')) /* remove spaces */
-                                       *p++ = '\0';
-                       }
+       fp = fopen("/etc/resolv.conf", "r");
+       if (!fp) {
+               fp = fopen("/etc/config/resolv.conf", "r");
+               if (!fp) {
+                       DPRINTF("failed to open %s\n", "resolv.conf");
+                       h_errno = NO_RECOVERY;
+                       return;
+               }
+       }
 
-                       if (strcmp(argv[0], "nameserver") == 0) {
-                               for (i = 1; i < argc && __nameservers < MAX_SERVERS; i++) {
-// TODO: what if strdup fails?
-                                       __nameserver[__nameservers++] = strdup(argv[i]);
-                                       DPRINTF("adding nameserver %s\n", argv[i]);
-                               }
+       while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL) {
+               void *ptr;
+               char *keyword, *p;
+
+               keyword = p = skip_and_NUL_space(szBuffer);
+               /* skip keyword */
+               p = skip_nospace(p);
+               /* find next word */
+               p = skip_and_NUL_space(p);
+
+               if (strcmp(keyword, "nameserver") == 0) {
+                       /* terminate IP addr */
+                       *skip_nospace(p) = '\0';
+                       memset(&sa, 0, sizeof(sa));
+                       if (0) /* nothing */;
+#ifdef __UCLIBC_HAS_IPV6__
+                       else if (inet_pton(AF_INET6, p, &sa.sa6.sin6_addr) > 0) {
+                               sa.sa6.sin6_family = AF_INET6;
+                               sa.sa6.sin6_port = htons(NAMESERVER_PORT);
                        }
-
-                       /* domain and search are mutually exclusive, the last one wins */
-                       if (strcmp(argv[0],"domain") == 0 || strcmp(argv[0],"search") == 0) {
-                               while (__searchdomains > 0) {
-                                       free(__searchdomain[--__searchdomains]);
-                                       __searchdomain[__searchdomains] = NULL;
-                               }
-                               for (i = 1; i < argc && __searchdomains < MAX_SEARCH; i++) {
-// TODO: what if strdup fails?
-                                       __searchdomain[__searchdomains++] = strdup(argv[i]);
-                                       DPRINTF("adding search %s\n", argv[i]);
-                               }
+#endif
+#ifdef __UCLIBC_HAS_IPV4__
+                       else if (inet_pton(AF_INET, p, &sa.sa4.sin_addr) > 0) {
+                               sa.sa4.sin_family = AF_INET;
+                               sa.sa4.sin_port = htons(NAMESERVER_PORT);
                        }
+#endif
+                       else
+                               continue; /* garbage on this line */
+                       ptr = realloc(__nameserver, (__nameservers + 1) * sizeof(__nameserver[0]));
+                       if (!ptr)
+                               continue;
+                       __nameserver = ptr;
+                       __nameserver[__nameservers++] = sa; /* struct copy */
+                       continue;
                }
-               fclose(fp);
-               DPRINTF("nameservers = %d\n", __nameservers);
-               goto DONE;
+               if (strcmp(keyword, "domain") == 0 || strcmp(keyword, "search") == 0) {
+                       char *p1;
+ next_word:
+                       /* terminate current word */
+                       p1 = skip_nospace(p);
+                       /* find next word (maybe) */
+                       p1 = skip_and_NUL_space(p1);
+                       /* paranoia - done by having szBuffer[MAXLEN_searchdomain] */
+                       /*if (strlen(p) > MAXLEN_searchdomain)*/
+                       /*      goto skip;*/
+                       /* do we have this domain already? */
+                       for (i = 0; i < __searchdomains; i++)
+                               if (strcmp(p, __searchdomain[i]) == 0)
+                                       goto skip;
+                       /* add it */
+                       ptr = realloc(__searchdomain, (__searchdomains + 1) * sizeof(__searchdomain[0]));
+                       if (!ptr)
+                               continue;
+                       __searchdomain = ptr;
+                       ptr = strdup(p);
+                       if (!ptr)
+                               continue;
+                       DPRINTF("adding search %s\n", (char*)ptr);
+                       __searchdomain[__searchdomains++] = (char*)ptr;
+ skip:
+                       p = p1;
+                       if (*p)
+                               goto next_word;
+                       continue;
+               }
+               /* if (strcmp(keyword, "sortlist") == 0)... */
+               /* if (strcmp(keyword, "options") == 0)... */
        }
-       DPRINTF("failed to open %s\n", "resolv.conf");
-       h_errno = NO_RECOVERY;
-
-       /* rv = -1; */
-
- DONE:
-       __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-       /* return rv; */
+       fclose(fp);
+       DPRINTF("nameservers = %d\n", __nameservers);
 }
 #endif
 
 
 #ifdef L_closenameservers
 
+/* Must be called under __resolv_lock. */
 void attribute_hidden __close_nameservers(void)
 {
-       __UCLIBC_MUTEX_LOCK(__resolv_lock);
-       while (__nameservers > 0) {
-               free(__nameserver[--__nameservers]);
-               __nameserver[__nameservers] = NULL;
-       }
-       while (__searchdomains > 0) {
+       free(__nameserver);
+       __nameserver = NULL;
+       __nameservers = 0;
+       while (__searchdomains)
                free(__searchdomain[--__searchdomains]);
-               __searchdomain[__searchdomains] = NULL;
-       }
-       __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
+       free(__searchdomain);
+       __searchdomain = NULL;
+       /*__searchdomains = 0; - already is */
 }
 #endif
 
+
 #ifdef L_gethostbyname
 
 struct hostent *gethostbyname(const char *name)
@@ -1222,6 +1248,7 @@ struct hostent *gethostbyname(const char *name)
 libc_hidden_def(gethostbyname)
 #endif
 
+
 #ifdef L_gethostbyname2
 
 struct hostent *gethostbyname2(const char *name, int family)
@@ -1240,21 +1267,24 @@ struct hostent *gethostbyname2(const char *name, int family)
        return hp;
 #endif /* __UCLIBC_HAS_IPV6__ */
 }
-#endif
 
+#endif
 
 
 #ifdef L_res_init
+
 /* We use __resolv_lock to guard access to global '_res' */
 struct __res_state _res;
 
 int res_init(void)
 {
+       int i, n;
        struct __res_state *rp = &(_res);
 
-       __UCLIBC_MUTEX_LOCK(__resolv_lock);     /* must be a recursive lock! */
+       __UCLIBC_MUTEX_LOCK(__resolv_lock);
        __close_nameservers();
        __open_nameservers();
+
        memset(rp, 0, sizeof(*rp));
        rp->options = RES_INIT;
 #ifdef __UCLIBC_HAS_COMPAT_RES_STATE__
@@ -1276,26 +1306,28 @@ int res_init(void)
        rp->_vcsock = -1;
 #endif
 
-       if (__searchdomains) {
-               int i;
-               for (i = 0; i < __searchdomains; i++)
-                       rp->dnsrch[i] = __searchdomain[i];
-       }
-
-       if (__nameservers) {
-               int i;
-               struct in_addr a;
-               for (i = 0; i < __nameservers; i++) {
-                       if (inet_aton(__nameserver[i], &a)) {
-                               rp->nsaddr_list[i].sin_addr = a;
-                               rp->nsaddr_list[i].sin_family = AF_INET;
-                               rp->nsaddr_list[i].sin_port = htons(NAMESERVER_PORT);
-                       }
-               }
-               rp->nscount = __nameservers;
+#undef ARRAY_SIZE
+#define ARRAY_SIZE(v) (sizeof(v) / sizeof((v)[0]))
+       n = __searchdomains;
+       if (n > ARRAY_SIZE(rp->dnsrch))
+               n = ARRAY_SIZE(rp->dnsrch);
+       for (i = 0; i < n; i++)
+               rp->dnsrch[i] = __searchdomain[i];
+       i = 0;
+       n = 0;
+       while (n < ARRAY_SIZE(rp->nsaddr_list) && i < __nameservers) {
+               if (__nameserver[i].sa.sa_family != AF_INET)
+                       goto next_i;
+               rp->nsaddr_list[n] = __nameserver[i].sa4; /* struct copy */
+               n++;
+ next_i:
+               i++;
        }
+       if (n)
+               rp->nscount = n;
+       /* else stays 1 */
+#undef ARRAY_SIZE
        __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-
        return 0;
 }
 libc_hidden_def(res_init)
@@ -1303,8 +1335,10 @@ libc_hidden_def(res_init)
 #ifdef __UCLIBC_HAS_BSD_RES_CLOSE__
 void res_close(void)
 {
+       __UCLIBC_MUTEX_LOCK(__resolv_lock);
        __close_nameservers();
        memset(&_res, 0, sizeof(_res));
+       __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
 }
 #endif
 
@@ -1323,8 +1357,6 @@ int res_query(const char *dname, int class, int type,
        int i;
        unsigned char * packet = NULL;
        struct resolv_answer a;
-       int __nameserversXX;
-       char ** __nameserverXX;
 
        if (!dname || class != 1 /* CLASS_IN */) {
                h_errno = NO_RECOVERY;
@@ -1332,13 +1364,7 @@ int res_query(const char *dname, int class, int type,
        }
 
        memset(&a, '\0', sizeof(a));
-       __open_nameservers();
-
-       __UCLIBC_MUTEX_LOCK(__resolv_lock);
-       __nameserversXX = __nameservers;
-       __nameserverXX = __nameserver;
-       __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-       i = __dns_lookup(dname, type, __nameserversXX, __nameserverXX, &packet, &a);
+       i = __dns_lookup(dname, type, &packet, &a);
 
        if (i < 0) {
                h_errno = TRY_AGAIN;
@@ -1381,6 +1407,8 @@ int res_search(const char *name, int class, int type, u_char *answer,
        __UCLIBC_MUTEX_LOCK(__resolv_lock);
        _res_options = _res.options;
        __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
+//FIXME: locking is bogus
+//FIXME: our res_init never fails
        if ((!name || !answer) || ((_res_options & RES_INIT) == 0 && res_init() == -1)) {
                h_errno = NETDB_INTERNAL;
                return -1;
@@ -1528,6 +1556,8 @@ int res_querydomain(const char *name, const char *domain, int class, int type,
        __UCLIBC_MUTEX_LOCK(__resolv_lock);
        _res_options = _res.options;
        __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
+//FIXME: locking is bogus
+//FIXME: our res_init never fails
        if ((!name || !answer) || ((_res_options & RES_INIT) == 0 && res_init() == -1)) {
                h_errno = NETDB_INTERNAL;
                return -1;
@@ -2214,20 +2244,13 @@ int gethostbyname_r(const char * name,
        }
 
        /* talk to DNS servers */
-       __open_nameservers();
        {
-               int __nameserversXX;
-               char ** __nameserverXX;
-               __UCLIBC_MUTEX_LOCK(__resolv_lock);
-               __nameserversXX = __nameservers;
-               __nameserverXX = __nameserver;
-               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
                a.buf = buf;
                /* take into account that at least one address will be there,
                 * we'll need space of one in_addr + two addr_list[] elems */
                a.buflen = buflen - ((sizeof(addr_list[0]) * 2 + sizeof(struct in_addr)));
                a.add_count = 0;
-               i = __dns_lookup(name, T_A, __nameserversXX, __nameserverXX, &packet, &a);
+               i = __dns_lookup(name, T_A, &packet, &a);
                if (i < 0) {
                        *h_errnop = HOST_NOT_FOUND;
                        DPRINTF("__dns_lookup returned < 0\n");
@@ -2319,8 +2342,6 @@ int gethostbyname2_r(const char *name, int family,
        struct resolv_answer a;
        int i;
        int nest = 0;
-       int __nameserversXX;
-       char ** __nameserverXX;
        int wrong_af = 0;
 
        if (family == AF_INET)
@@ -2329,7 +2350,6 @@ int gethostbyname2_r(const char *name, int family,
        if (family != AF_INET6)
                return EINVAL;
 
-       __open_nameservers();
        *result = NULL;
        if (!name)
                return EINVAL;
@@ -2405,12 +2425,7 @@ int gethostbyname2_r(const char *name, int family,
        memset(&a, '\0', sizeof(a));
 
        for (;;) {
-               __UCLIBC_MUTEX_LOCK(__resolv_lock);
-               __nameserversXX = __nameservers;
-               __nameserverXX = __nameserver;
-               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-
-               i = __dns_lookup(buf, T_AAAA, __nameserversXX, __nameserverXX, &packet, &a);
+               i = __dns_lookup(buf, T_AAAA, &packet, &a);
 
                if (i < 0) {
                        *h_errnop = HOST_NOT_FOUND;
@@ -2478,8 +2493,6 @@ int gethostbyaddr_r(const void *addr, socklen_t len, int type,
        struct resolv_answer a;
        int i;
        int nest = 0;
-       int __nameserversXX;
-       char ** __nameserverXX;
 
        *result = NULL;
        if (!addr)
@@ -2514,8 +2527,6 @@ int gethostbyaddr_r(const void *addr, socklen_t len, int type,
                        return i;
        }
 
-       __open_nameservers();
-
 #ifdef __UCLIBC_HAS_IPV6__
        qp = buf;
        plen = buflen;
@@ -2592,11 +2603,7 @@ int gethostbyaddr_r(const void *addr, socklen_t len, int type,
        alias[1] = 0;
 
        for (;;) {
-               __UCLIBC_MUTEX_LOCK(__resolv_lock);
-               __nameserversXX = __nameservers;
-               __nameserverXX = __nameserver;
-               __UCLIBC_MUTEX_UNLOCK(__resolv_lock);
-               i = __dns_lookup(buf, T_PTR, __nameserversXX, __nameserverXX, &packet, &a);
+               i = __dns_lookup(buf, T_PTR, &packet, &a);
 
                if (i < 0) {
                        *h_errnop = HOST_NOT_FOUND;