1999-03-07 Mark Kettenis <kettenis@gnu.org>
[kopensolaris-gnu/glibc.git] / resolv / res_send.c
index 65763fd..608659b 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * ++Copyright++ 1985, 1989
+ * ++Copyright++ 1985, 1989, 1993
  * -
- * Copyright (c) 1985, 1989 Regents of the University of California.
- * All rights reserved.
- * 
+ * Copyright (c) 1985, 1989, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -19,7 +19,7 @@
  * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  * SUCH DAMAGE.
  * -
  * Portions Copyright (c) 1993 by Digital Equipment Corporation.
- * 
+ *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies, and that
  * the name of Digital Equipment Corporation not be used in advertising or
  * publicity pertaining to distribution of the document or software without
  * specific, written prior permission.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
  * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)res_send.c 6.27 (Berkeley) 2/24/91";
+static char sccsid[] = "@(#)res_send.c 8.1 (Berkeley) 6/4/93";
 static char rcsid[] = "$Id$";
 #endif /* LIBC_SCCS and not lint */
 
+       /* change this to "0"
+        * if you talk to a lot
+        * of multi-homed SunOS
+        * ("broken") name servers.
+        */
+#define        CHECK_SRVR_ADDR 1       /* XXX - should be in options.h */
+
 /*
  * Send query to name server and wait for reply.
  */
 
+#include <sys/types.h>
 #include <sys/param.h>
+#include <sys/poll.h>
 #include <sys/time.h>
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <netinet/in.h>
 #include <arpa/nameser.h>
 #include <arpa/inet.h>
+
 #include <stdio.h>
+#include <netdb.h>
 #include <errno.h>
 #include <resolv.h>
-#include "../conf/portability.h"
+#if defined(BSD) && (BSD >= 199306)
+# include <stdlib.h>
+# include <string.h>
+# include <unistd.h>
+#else
+# include "../conf/portability.h"
+#endif
+
+#if defined(USE_OPTIONS_H)
+# include <../conf/options.h>
+#endif
 
 static int s = -1;     /* socket used for communications */
-static struct sockaddr no_addr;
-
-#ifndef FD_SET
-#define        NFDBITS         32
-#define        FD_SETSIZE      32
-#define        FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
-#define        FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
-#define        FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
-#define FD_ZERO(p)     bzero((char *)(p), sizeof(*(p)))
+static int connected = 0;      /* is the socket connected */
+static int vc = 0;     /* is the socket a virtual circuit? */
+
+/* XXX - this should be done in portability.h */
+#if (defined(BSD) && (BSD >= 199103)) || defined(linux)
+# define CAN_RECONNECT 1
+#else
+# define CAN_RECONNECT 0
+#endif
+
+#ifndef DEBUG
+#   define Dprint(cond, args) /*empty*/
+#   define DprintQ(cond, args, query, size) /*empty*/
+#   define Aerror(file, string, error, address) /*empty*/
+#   define Perror(file, string, error) /*empty*/
+#else
+#   define Dprint(cond, args) if (cond) {fprintf args;} else {}
+#   define DprintQ(cond, args, query, size) if (cond) {\
+                       fprintf args;\
+                       __fp_nquery(query, size, stdout);\
+               } else {}
+    static void
+    Aerror(file, string, error, address)
+       FILE *file;
+       char *string;
+       int error;
+       struct sockaddr_in address;
+    {
+       int save = errno;
+
+       if (_res.options & RES_DEBUG) {
+               fprintf(file, "res_send: %s ([%s].%u): %s\n",
+                       string,
+                       inet_ntoa(address.sin_addr),
+                       ntohs(address.sin_port),
+                       strerror(error));
+       }
+       __set_errno (save);
+    }
+    static void
+    Perror(file, string, error)
+       FILE *file;
+       char *string;
+       int error;
+    {
+       int save = errno;
+
+       if (_res.options & RES_DEBUG) {
+               fprintf(file, "res_send: %s: %s\n",
+                       string, strerror(error));
+       }
+       __set_errno (save);
+    }
 #endif
 
-res_send(buf, buflen, answer, anslen)
-       const char *buf;
+static res_send_qhook Qhook = NULL;
+static res_send_rhook Rhook = NULL;
+
+void
+res_send_setqhook(hook)
+       res_send_qhook hook;
+{
+
+       Qhook = hook;
+}
+
+void
+res_send_setrhook(hook)
+       res_send_rhook hook;
+{
+
+       Rhook = hook;
+}
+
+/* int
+ * res_isourserver(ina)
+ *     looks up "ina" in _res.ns_addr_list[]
+ * returns:
+ *     0  : not found
+ *     >0 : found
+ * author:
+ *     paul vixie, 29may94
+ */
+int
+res_isourserver(inp)
+       const struct sockaddr_in *inp;
+{
+       struct sockaddr_in ina;
+       register int ns, ret;
+
+       ina = *inp;
+       ret = 0;
+       for (ns = 0;  ns < _res.nscount;  ns++) {
+               register const struct sockaddr_in *srv = &_res.nsaddr_list[ns];
+
+               if (srv->sin_family == ina.sin_family &&
+                   srv->sin_port == ina.sin_port &&
+                   (srv->sin_addr.s_addr == INADDR_ANY ||
+                    srv->sin_addr.s_addr == ina.sin_addr.s_addr)) {
+                       ret++;
+                       break;
+               }
+       }
+       return (ret);
+}
+
+/* int
+ * res_nameinquery(name, type, class, buf, eom)
+ *     look for (name,type,class) in the query section of packet (buf,eom)
+ * requires:
+ *     buf + HFIXESDZ <= eom
+ * returns:
+ *     -1 : format error
+ *     0  : not found
+ *     >0 : found
+ * author:
+ *     paul vixie, 29may94
+ */
+int
+res_nameinquery(name, type, class, buf, eom)
+       const char *name;
+       register int type, class;
+       const u_char *buf, *eom;
+{
+       register const u_char *cp = buf + HFIXEDSZ;
+       int qdcount = ntohs(((HEADER*)buf)->qdcount);
+
+       while (qdcount-- > 0) {
+               char tname[MAXDNAME+1];
+               register int n, ttype, tclass;
+
+               n = dn_expand(buf, eom, cp, tname, sizeof tname);
+               if (n < 0)
+                       return (-1);
+               cp += n;
+               if (cp + 2 * INT16SZ > eom)
+                       return (-1);
+               ttype = _getshort(cp); cp += INT16SZ;
+               tclass = _getshort(cp); cp += INT16SZ;
+               if (ttype == type &&
+                   tclass == class &&
+                   strcasecmp(tname, name) == 0)
+                       return (1);
+       }
+       return (0);
+}
+
+/* int
+ * res_queriesmatch(buf1, eom1, buf2, eom2)
+ *     is there a 1:1 mapping of (name,type,class)
+ *     in (buf1,eom1) and (buf2,eom2)?
+ * returns:
+ *     -1 : format error
+ *     0  : not a 1:1 mapping
+ *     >0 : is a 1:1 mapping
+ * author:
+ *     paul vixie, 29may94
+ */
+int
+res_queriesmatch(buf1, eom1, buf2, eom2)
+       const u_char *buf1, *eom1;
+       const u_char *buf2, *eom2;
+{
+       register const u_char *cp = buf1 + HFIXEDSZ;
+       int qdcount = ntohs(((HEADER*)buf1)->qdcount);
+
+       if (buf1 + HFIXEDSZ > eom1 || buf2 + HFIXEDSZ > eom2)
+               return (-1);
+
+       if (qdcount != ntohs(((HEADER*)buf2)->qdcount))
+               return (0);
+       while (qdcount-- > 0) {
+               char tname[MAXDNAME+1];
+               register int n, ttype, tclass;
+
+               n = dn_expand(buf1, eom1, cp, tname, sizeof tname);
+               if (n < 0)
+                       return (-1);
+               cp += n;
+               if (cp + 2 * INT16SZ > eom1)
+                       return (-1);
+               ttype = _getshort(cp);  cp += INT16SZ;
+               tclass = _getshort(cp); cp += INT16SZ;
+               if (!res_nameinquery(tname, ttype, tclass, buf2, eom2))
+                       return (0);
+       }
+       return (1);
+}
+
+int
+res_send(buf, buflen, ans, anssiz)
+       const u_char *buf;
        int buflen;
-       char *answer;
-       int anslen;
+       u_char *ans;
+       int anssiz;
 {
-       register int n;
-       int try, v_circuit, resplen, ns;
-       int gotsomewhere = 0, connected = 0;
-       int connreset = 0;
-       u_short id, len;
-       char *cp;
-       fd_set dsmask;
-       struct timeval timeout;
        HEADER *hp = (HEADER *) buf;
-       HEADER *anhp = (HEADER *) answer;
-       u_int badns;            /* XXX NSMAX can't exceed #/bits per this */
-       struct iovec iov[2];
-       int terrno = ETIMEDOUT;
-       char junk[512];
-
-#ifdef DEBUG
-       if ((_res.options & RES_DEBUG) || (_res.pfcode & RES_PRF_QUERY)) {
-               printf(";; res_send()\n");
-               __p_query(buf);
+       HEADER *anhp = (HEADER *) ans;
+       int gotsomewhere, connreset, terrno, try, v_circuit, resplen, ns;
+       register int n;
+       u_int badns;    /* XXX NSMAX can't exceed #/bits in this var */
+
+       if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
+               /* errno should have been set by res_init() in this case. */
+               return (-1);
        }
-#endif
-       if (!(_res.options & RES_INIT))
-               if (res_init() == -1) {
-                       return(-1);
-               }
+       if (anssiz < HFIXEDSZ) {
+               __set_errno (EINVAL);
+               return (-1);
+       }
+       DprintQ((_res.options & RES_DEBUG) || (_res.pfcode & RES_PRF_QUERY),
+               (stdout, ";; res_send()\n"), buf, buflen);
        v_circuit = (_res.options & RES_USEVC) || buflen > PACKETSZ;
-       id = hp->id;
+       gotsomewhere = 0;
+       connreset = 0;
+       terrno = ETIMEDOUT;
        badns = 0;
+
        /*
         * Send request, RETRY times, or until successful
         */
        for (try = 0; try < _res.retry; try++) {
            for (ns = 0; ns < _res.nscount; ns++) {
-               if (badns & (1<<ns))
-                       continue;
-#ifdef DEBUG
-               if (_res.options & RES_DEBUG)
-                       printf(";; Querying server (# %d) address = %s\n",
-                              ns+1,
-                              inet_ntoa(_res.nsaddr_list[ns].sin_addr));
-#endif
-       usevc:
+               struct sockaddr_in *nsap = &_res.nsaddr_list[ns];
+    same_ns:
+               if (badns & (1 << ns)) {
+                       res_close();
+                       goto next_ns;
+               }
+
+               if (Qhook) {
+                       int done = 0, loops = 0;
+
+                       do {
+                               res_sendhookact act;
+
+                               act = (*Qhook)(&nsap, &buf, &buflen,
+                                              ans, anssiz, &resplen);
+                               switch (act) {
+                               case res_goahead:
+                                       done = 1;
+                                       break;
+                               case res_nextns:
+                                       res_close();
+                                       goto next_ns;
+                               case res_done:
+                                       return (resplen);
+                               case res_modified:
+                                       /* give the hook another try */
+                                       if (++loops < 42) /*doug adams*/
+                                               break;
+                                       /*FALLTHROUGH*/
+                               case res_error:
+                                       /*FALLTHROUGH*/
+                               default:
+                                       return (-1);
+                               }
+                       } while (!done);
+               }
+
+               Dprint(_res.options & RES_DEBUG,
+                      (stdout, ";; Querying server (# %d) address = %s\n",
+                       ns + 1, inet_ntoa(nsap->sin_addr)));
+
                if (v_circuit) {
-                       int truncated = 0;
+                       int truncated;
+                       struct iovec iov[2];
+                       u_short len;
+                       u_char *cp;
 
                        /*
                         * Use virtual circuit;
                         * at most one attempt per server.
                         */
                        try = _res.retry;
-                       if (s < 0) {
-                               s = socket(AF_INET, SOCK_STREAM, 0);
+                       truncated = 0;
+                       if ((s < 0) || (!vc)) {
+                               if (s >= 0)
+                                       res_close();
+
+                               s = socket(PF_INET, SOCK_STREAM, 0);
                                if (s < 0) {
                                        terrno = errno;
-#ifdef DEBUG
-                                       if (_res.options & RES_DEBUG)
-                                           perror("socket (vc) failed");
-#endif
-                                       continue;
+                                       Perror(stderr, "socket(vc)", errno);
+                                       return (-1);
                                }
-                               if (connect(s,
-                                   (struct sockaddr *)&(_res.nsaddr_list[ns]),
-                                   sizeof(struct sockaddr)) < 0) {
+                               __set_errno (0);
+                               if (connect(s, (struct sockaddr *)nsap,
+                                           sizeof(struct sockaddr)) < 0) {
                                        terrno = errno;
-#ifdef DEBUG
-                                       if (_res.options & RES_DEBUG)
-                                           perror("connect failed");
-#endif
-                                       (void) close(s);
-                                       s = -1;
-                                       continue;
+                                       Aerror(stderr, "connect/vc",
+                                              errno, *nsap);
+                                       badns |= (1 << ns);
+                                       res_close();
+                                       goto next_ns;
                                }
+                               vc = 1;
                        }
                        /*
                         * Send length & message
                         */
-                       len = htons((u_short)buflen);
+                       putshort((u_short)buflen, (u_char*)&len);
                        iov[0].iov_base = (caddr_t)&len;
-                       iov[0].iov_len = sizeof(len);
-                       iov[1].iov_base = (char *)buf;
+                       iov[0].iov_len = INT16SZ;
+                       iov[1].iov_base = (caddr_t)buf;
                        iov[1].iov_len = buflen;
-                       if (writev(s, iov, 2) != sizeof(len) + buflen) {
+                       if (writev(s, iov, 2) != (INT16SZ + buflen)) {
                                terrno = errno;
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       perror("write failed");
-#endif
-                               (void) close(s);
-                               s = -1;
-                               continue;
+                               Perror(stderr, "write failed", errno);
+                               badns |= (1 << ns);
+                               res_close();
+                               goto next_ns;
                        }
                        /*
                         * Receive length & response
                         */
-                       cp = answer;
-                       len = sizeof(short);
-                       while (len != 0 &&
-                           (n = read(s, (char *)cp, (int)len)) > 0) {
+read_len:
+                       cp = ans;
+                       len = INT16SZ;
+                       while ((n = read(s, (char *)cp, (int)len)) > 0) {
                                cp += n;
-                               len -= n;
+                               if ((len -= n) <= 0)
+                                       break;
                        }
                        if (n <= 0) {
                                terrno = errno;
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       perror("read failed");
-#endif
-                               (void) close(s);
-                               s = -1;
+                               Perror(stderr, "read failed", errno);
+                               res_close();
                                /*
                                 * A long running process might get its TCP
                                 * connection reset if the remote server was
@@ -212,35 +435,43 @@ res_send(buf, buflen, answer, anslen)
                                 */
                                if (terrno == ECONNRESET && !connreset) {
                                        connreset = 1;
-                                       ns--;
+                                       res_close();
+                                       goto same_ns;
                                }
-                               continue;
+                               res_close();
+                               goto next_ns;
                        }
-                       cp = answer;
-                       if ((resplen = ntohs(*(u_short *)cp)) > anslen) {
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       fprintf(stderr,
-                                               ";; response truncated\n");
-#endif
-                               len = anslen;
+                       resplen = _getshort(ans);
+                       if (resplen > anssiz) {
+                               Dprint(_res.options & RES_DEBUG,
+                                      (stdout, ";; response truncated\n")
+                                      );
                                truncated = 1;
+                               len = anssiz;
                        } else
                                len = resplen;
+                       if (len < HFIXEDSZ) {
+                               /*
+                                * Undersized message.
+                                */
+                               Dprint(_res.options & RES_DEBUG,
+                                      (stdout, ";; undersized: %d\n", len));
+                               terrno = EMSGSIZE;
+                               badns |= (1 << ns);
+                               res_close();
+                               goto next_ns;
+                       }
+                       cp = ans;
                        while (len != 0 &&
-                          (n = read(s, (char *)cp, (int)len)) > 0) {
+                              (n = read(s, (char *)cp, (int)len)) > 0) {
                                cp += n;
                                len -= n;
                        }
                        if (n <= 0) {
                                terrno = errno;
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       perror("read failed");
-#endif
-                               (void) close(s);
-                               s = -1;
-                               continue;
+                               Perror(stderr, "read(vc)", errno);
+                               res_close();
+                               goto next_ns;
                        }
                        if (truncated) {
                                /*
@@ -248,34 +479,57 @@ res_send(buf, buflen, answer, anslen)
                                 * so connection stays in synch.
                                 */
                                anhp->tc = 1;
-                               len = resplen - anslen;
+                               len = resplen - anssiz;
                                while (len != 0) {
-                                       n = (len > sizeof(junk) ?
-                                           sizeof(junk) : len);
+                                       char junk[PACKETSZ];
+
+                                       n = ((size_t) len > sizeof(junk)
+                                            ? sizeof(junk)
+                                            : len);
                                        if ((n = read(s, junk, n)) > 0)
                                                len -= n;
                                        else
                                                break;
                                }
                        }
+                       /*
+                        * The calling applicating has bailed out of
+                        * a previous call and failed to arrange to have
+                        * the circuit closed or the server has got
+                        * itself confused. Anyway drop the packet and
+                        * wait for the correct one.
+                        */
+                       if (hp->id != anhp->id) {
+                               DprintQ((_res.options & RES_DEBUG) ||
+                                       (_res.pfcode & RES_PRF_REPLY),
+                                       (stdout, ";; old answer (unexpected):\n"),
+                                       ans, (resplen>anssiz)?anssiz:resplen);
+                               goto read_len;
+                       }
                } else {
                        /*
                         * Use datagrams.
                         */
-                       if (s < 0) {
-                               s = socket(AF_INET, SOCK_DGRAM, 0);
+                       int timeout;
+                       struct pollfd pfd[1];
+                       struct sockaddr_in from;
+                       socklen_t fromlen;
+
+                       if ((s < 0) || vc) {
+                               if (vc)
+                                       res_close();
+                               s = socket(PF_INET, SOCK_DGRAM, 0);
                                if (s < 0) {
-                                       terrno = errno;
-#ifdef DEBUG
-                                       if (_res.options & RES_DEBUG)
-                                           perror("socket (dg) failed");
+#if !CAN_RECONNECT
+ bad_dg_sock:
 #endif
-                                       continue;
+                                       terrno = errno;
+                                       Perror(stderr, "socket(dg)", errno);
+                                       return (-1);
                                }
+                               connected = 0;
                        }
-#if    BSD >= 43
                        /*
-                        * I'm tired of answering this question, so:
                         * On a 4.3BSD+ machine (client and server,
                         * actually), sending to a nameserver datagram
                         * port with no nameserver will cause an
@@ -292,30 +546,27 @@ res_send(buf, buflen, answer, anslen)
                         */
                        if (_res.nscount == 1 || (try == 0 && ns == 0)) {
                                /*
-                                * Don't use connect if we might
-                                * still receive a response
-                                * from another server.
+                                * Connect only if we are sure we won't
+                                * receive a response from another server.
                                 */
-                               if (connected == 0) {
-                                       if (connect(s,
-                                                   (struct sockaddr *)
-                                                        &_res.nsaddr_list[ns],
+                               if (!connected) {
+                                       if (connect(s, (struct sockaddr *)nsap,
                                                    sizeof(struct sockaddr)
                                                    ) < 0) {
-#ifdef DEBUG
-                                               if (_res.options & RES_DEBUG)
-                                                       perror("connect");
-#endif
-                                               continue;
+                                               Aerror(stderr,
+                                                      "connect(dg)",
+                                                      errno, *nsap);
+                                               badns |= (1 << ns);
+                                               res_close();
+                                               goto next_ns;
                                        }
                                        connected = 1;
                                }
-                               if (send(s, buf, buflen, 0) != buflen) {
-#ifdef DEBUG
-                                       if (_res.options & RES_DEBUG)
-                                               perror("send");
-#endif
-                                       continue;
+                               if (send(s, (char*)buf, buflen, 0) != buflen) {
+                                       Perror(stderr, "send", errno);
+                                       badns |= (1 << ns);
+                                       res_close();
+                                       goto next_ns;
                                }
                        } else {
                                /*
@@ -323,142 +574,221 @@ res_send(buf, buflen, answer, anslen)
                                 * for responses from more than one server.
                                 */
                                if (connected) {
-                                       (void) connect(s, &no_addr,
-                                           sizeof(no_addr));
+#if CAN_RECONNECT
+                                       struct sockaddr_in no_addr;
+
+                                       no_addr.sin_family = AF_INET;
+                                       no_addr.sin_addr.s_addr = INADDR_ANY;
+                                       no_addr.sin_port = 0;
+                                       (void) connect(s,
+                                                      (struct sockaddr *)
+                                                       &no_addr,
+                                                      sizeof(no_addr));
+#else
+                                       int s1 = socket(PF_INET, SOCK_DGRAM,0);
+                                       if (s1 < 0)
+                                               goto bad_dg_sock;
+                                       (void) dup2(s1, s);
+                                       (void) close(s1);
+                                       Dprint(_res.options & RES_DEBUG,
+                                              (stdout, ";; new DG socket\n"))
+#endif
                                        connected = 0;
+                                       __set_errno (0);
                                }
-#endif /* BSD */
-                               if (sendto(s, buf, buflen, 0,
-                                   (struct sockaddr *)&_res.nsaddr_list[ns],
-                                   sizeof(struct sockaddr)) != buflen) {
-#ifdef DEBUG
-                                       if (_res.options & RES_DEBUG)
-                                               perror("sendto");
-#endif
-                                       continue;
+                               if (sendto(s, (char*)buf, buflen, 0,
+                                          (struct sockaddr *)nsap,
+                                          sizeof(struct sockaddr))
+                                   != buflen) {
+                                       Aerror(stderr, "sendto", errno, *nsap);
+                                       badns |= (1 << ns);
+                                       res_close();
+                                       goto next_ns;
                                }
-#if    BSD >= 43
                        }
-#endif
 
                        /*
                         * Wait for reply
                         */
-                       timeout.tv_sec = (_res.retrans << try);
+                       timeout = (_res.retrans << try) * 1000;
                        if (try > 0)
-                               timeout.tv_sec /= _res.nscount;
-                       if ((long) timeout.tv_sec <= 0)
-                               timeout.tv_sec = 1;
-                       timeout.tv_usec = 0;
-wait:
-                       FD_ZERO(&dsmask);
-                       FD_SET(s, &dsmask);
-                       n = select(s+1, &dsmask, (fd_set *)NULL,
-                               (fd_set *)NULL, &timeout);
+                               timeout /= _res.nscount;
+                       if (timeout <= 0)
+                               timeout = 1000;
+    wait:
+                       if (s < 0 || s >= FD_SETSIZE) {
+                               Perror(stderr, "s out-of-bounds", EMFILE);
+                               res_close();
+                               goto next_ns;
+                       }
+                       pfd[0].fd = s;
+                       pfd[0].events = POLLIN;
+                       n = __poll(pfd, 1, timeout);
                        if (n < 0) {
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       perror("select");
-#endif
-                               continue;
+                               if (errno == EINTR)
+                                       goto wait;
+                               Perror(stderr, "poll", errno);
+                               res_close();
+                               goto next_ns;
                        }
                        if (n == 0) {
                                /*
                                 * timeout
                                 */
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       printf(";; timeout\n");
-#endif
-#if BSD >= 43
+                               Dprint(_res.options & RES_DEBUG,
+                                      (stdout, ";; timeout\n"));
                                gotsomewhere = 1;
-#endif
-                               continue;
+                               res_close();
+                               goto next_ns;
                        }
-                       if ((resplen = recv(s, answer, anslen, 0)) <= 0) {
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       perror("recvfrom");
-#endif
-                               continue;
+                       __set_errno (0);
+                       fromlen = sizeof(struct sockaddr_in);
+                       resplen = recvfrom(s, (char*)ans, anssiz, 0,
+                                          (struct sockaddr *)&from, &fromlen);
+                       if (resplen <= 0) {
+                               Perror(stderr, "recvfrom", errno);
+                               res_close();
+                               goto next_ns;
                        }
                        gotsomewhere = 1;
-                       if (id != anhp->id) {
+                       if (resplen < HFIXEDSZ) {
                                /*
-                                * response from old query, ignore it
+                                * Undersized message.
                                 */
-#ifdef DEBUG
-                               if ((_res.options & RES_DEBUG) ||
-                                   (_res.pfcode & RES_PRF_REPLY)) {
-                                       printf(";; old answer:\n");
-                                       __p_query(answer);
-                               }
+                               Dprint(_res.options & RES_DEBUG,
+                                      (stdout, ";; undersized: %d\n",
+                                       resplen));
+                               terrno = EMSGSIZE;
+                               badns |= (1 << ns);
+                               res_close();
+                               goto next_ns;
+                       }
+                       if (hp->id != anhp->id) {
+                               /*
+                                * response from old query, ignore it.
+                                * XXX - potential security hazard could
+                                *       be detected here.
+                                */
+                               DprintQ((_res.options & RES_DEBUG) ||
+                                       (_res.pfcode & RES_PRF_REPLY),
+                                       (stdout, ";; old answer:\n"),
+                                       ans, (resplen>anssiz)?anssiz:resplen);
+                               goto wait;
+                       }
+#if CHECK_SRVR_ADDR
+                       if (!(_res.options & RES_INSECURE1) &&
+                           !res_isourserver(&from)) {
+                               /*
+                                * response from wrong server? ignore it.
+                                * XXX - potential security hazard could
+                                *       be detected here.
+                                */
+                               DprintQ((_res.options & RES_DEBUG) ||
+                                       (_res.pfcode & RES_PRF_REPLY),
+                                       (stdout, ";; not our server:\n"),
+                                       ans, (resplen>anssiz)?anssiz:resplen);
+                               goto wait;
+                       }
 #endif
+                       if (!(_res.options & RES_INSECURE2) &&
+                           !res_queriesmatch(buf, buf + buflen,
+                                             ans, ans + anssiz)) {
+                               /*
+                                * response contains wrong query? ignore it.
+                                * XXX - potential security hazard could
+                                *       be detected here.
+                                */
+                               DprintQ((_res.options & RES_DEBUG) ||
+                                       (_res.pfcode & RES_PRF_REPLY),
+                                       (stdout, ";; wrong query name:\n"),
+                                       ans, (resplen>anssiz)?anssiz:resplen);
                                goto wait;
                        }
-                       if (anhp->rcode == SERVFAIL
-                           || anhp->rcode == NOTIMP
-                           || anhp->rcode == REFUSED) {
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG) {
-                                       printf("server rejected query:\n");
-                                       __p_query(answer);
-                               }
-#endif DEBUG
-                               badns |= (1<<ns);
-                               continue;
+                       if (anhp->rcode == SERVFAIL ||
+                           anhp->rcode == NOTIMP ||
+                           anhp->rcode == REFUSED) {
+                               DprintQ(_res.options & RES_DEBUG,
+                                       (stdout, "server rejected query:\n"),
+                                       ans, (resplen>anssiz)?anssiz:resplen);
+                               badns |= (1 << ns);
+                               res_close();
+                               /* don't retry if called from dig */
+                               if (!_res.pfcode)
+                                       goto next_ns;
                        }
                        if (!(_res.options & RES_IGNTC) && anhp->tc) {
                                /*
                                 * get rest of answer;
                                 * use TCP with same server.
                                 */
-#ifdef DEBUG
-                               if (_res.options & RES_DEBUG)
-                                       printf(";; truncated answer\n");
-#endif
-                               (void) close(s);
-                               s = -1;
+                               Dprint(_res.options & RES_DEBUG,
+                                      (stdout, ";; truncated answer\n"));
                                v_circuit = 1;
-                               goto usevc;
+                               res_close();
+                               goto same_ns;
                        }
-               }
-#ifdef DEBUG
-               if (_res.options & RES_DEBUG)
-                       printf(";; got answer:\n");
-               if ((_res.options & RES_DEBUG)
-                   || (_res.pfcode & RES_PRF_REPLY)) {
-                       __p_query(answer);
-               }
-#endif
+               } /*if vc/dg*/
+               Dprint((_res.options & RES_DEBUG) ||
+                      ((_res.pfcode & RES_PRF_REPLY) &&
+                       (_res.pfcode & RES_PRF_HEAD1)),
+                      (stdout, ";; got answer:\n"));
+               DprintQ((_res.options & RES_DEBUG) ||
+                       (_res.pfcode & RES_PRF_REPLY),
+                       (stdout, ""),
+                       ans, (resplen>anssiz)?anssiz:resplen);
                /*
                 * If using virtual circuits, we assume that the first server
-                * is preferred over the rest (i.e. it is on the local
+                * is preferred over the rest (i.e. it is on the local
                 * machine) and only keep that one open.
                 * If we have temporarily opened a virtual circuit,
                 * or if we haven't been asked to keep a socket open,
                 * close the socket.
                 */
-               if ((v_circuit &&
-                   ((_res.options & RES_USEVC) == 0 || ns != 0)) ||
-                   (_res.options & RES_STAYOPEN) == 0) {
-                       (void) close(s);
-                       s = -1;
+               if ((v_circuit && (!(_res.options & RES_USEVC) || ns != 0)) ||
+                   !(_res.options & RES_STAYOPEN)) {
+                       res_close();
+               }
+               if (Rhook) {
+                       int done = 0, loops = 0;
+
+                       do {
+                               res_sendhookact act;
+
+                               act = (*Rhook)(nsap, buf, buflen,
+                                              ans, anssiz, &resplen);
+                               switch (act) {
+                               case res_goahead:
+                               case res_done:
+                                       done = 1;
+                                       break;
+                               case res_nextns:
+                                       res_close();
+                                       goto next_ns;
+                               case res_modified:
+                                       /* give the hook another try */
+                                       if (++loops < 42) /*doug adams*/
+                                               break;
+                                       /*FALLTHROUGH*/
+                               case res_error:
+                                       /*FALLTHROUGH*/
+                               default:
+                                       return (-1);
+                               }
+                       } while (!done);
+
                }
                return (resplen);
-          }
-       }
-       if (s >= 0) {
-               (void) close(s);
-               s = -1;
-       }
-       if (v_circuit == 0)
-               if (gotsomewhere == 0)
-                       errno = ECONNREFUSED;   /* no nameservers found */
+    next_ns: ;
+          } /*foreach ns*/
+       } /*foreach retry*/
+       res_close();
+       if (!v_circuit) {
+               if (!gotsomewhere)
+                       __set_errno (ECONNREFUSED); /* no nameservers found */
                else
-                       errno = ETIMEDOUT;      /* no answer obtained */
-       else
-               errno = terrno;
+                       __set_errno (ETIMEDOUT);    /* no answer obtained */
+       else
+               __set_errno (terrno);
        return (-1);
 }
 
@@ -469,10 +799,36 @@ wait:
  *
  * This routine is not expected to be user visible.
  */
-_res_close()
+void
+res_close()
 {
-       if (s != -1) {
+       if (s >= 0) {
                (void) close(s);
                s = -1;
+               connected = 0;
+               vc = 0;
        }
 }
+
+#ifdef ultrix
+/* ultrix 4.0 had some icky packaging in its libc.a.  alias for it here.
+ * there is more gunk of this kind over in res_debug.c.
+ */
+
+void
+_res_close()
+{
+       res_close();
+}
+
+#undef res_send
+int
+res_send(buf, buflen, ans, anssiz)
+       const u_char *buf;
+       int buflen;
+       u_char *ans;
+       int anssiz;
+{
+       return (__res_send(buf, buflen, ans, anssiz));
+}
+#endif /* Ultrix 4.0 hackery */