Index: minires.c
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/libc/minires.c,v
retrieving revision 1.4
diff -u -p -r1.4 minires.c
--- minires.c	1 Apr 2008 10:22:33 -0000	1.4
+++ minires.c	3 Dec 2008 02:57:26 -0000
@@ -1,6 +1,6 @@
 /* minires.c.  Stub synchronous resolver for Cygwin.
 
-   Copyright 2006 Red Hat, Inc.
+   Copyright 2008 Red Hat, Inc.
 
    Written by Pierre A. Humblet <Pierre.Humblet@ieee.org>
 
@@ -225,6 +225,17 @@ static int open_sock(struct sockaddr_in 
     DPRINTF(debug, "socket(UDP): %s\n", strerror(errno));
     return -1;
   }
+  /* Set non-blocking */
+  if (fcntl64(fd, F_SETFL, O_NONBLOCK) < 0)  {
+    DPRINTF(debug, "fcntl: %s\n", strerror(errno));
+    return -1;
+  }
+  /* Set close on exec flag */
+  if (fcntl64(fd, F_SETFD, 1) == -1) {
+    DPRINTF(debug, "fcntl: %s\n", strerror(errno));
+    return -1;
+  }
+
   CliAddr->sin_family = AF_INET;
   CliAddr->sin_addr.s_addr = htonl(INADDR_ANY);
   CliAddr->sin_port = htons(0);
@@ -266,7 +277,10 @@ int res_ninit(res_state statp)
   statp->use_os = 1;            /* use os_query if available and allowed by get_resolv */
   statp->mypid = -1;
   statp->sockfd = -1;
-
+  /* Use the pid and the ppid for random seed, from the point of view of an outsider.
+     Mix the upper and lower bits as they are not used equally */
+  i = getpid();
+  statp->id = (ushort) (getppid() ^ (i << 8) ^ (i >> 8));
   for (i = 0; i < DIM(statp->dnsrch); i++)  statp->dnsrch[i] = 0;
 
   /* resolv.conf (dns servers & search list)*/
@@ -420,22 +434,15 @@ int res_nsend( res_state statp, const un
 
   /* Open a socket for this process */
   if (statp->sockfd == -1) {
-    /* Create a socket and bind it (to any port) */
+    /* Create a non-blocking, close on exec socket and bind it (to any port) */
     statp->sockfd = open_sock(& mySockAddr, debug);
     if (statp->sockfd  < 0 ) {
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
-    /* Set close on exec flag */
-    if (fcntl64(statp->sockfd, F_SETFD, 1) == -1) {
-      DPRINTF(debug, "fcntl: %s\n",
-	      strerror(errno));
-      statp->res_h_errno = NETDB_INTERNAL;
-      return -1;
-    }
     statp->mypid = getpid();
     if (SServ == 0XFFFFFFFF) /* Pseudo random */
-      SServ =  statp->mypid % statp->nscount;
+      SServ =  statp->id % statp->nscount;
   }
 
   transNum = 0;
@@ -443,6 +450,26 @@ int res_nsend( res_state statp, const un
     if ((wServ = SServ + 1) >= statp->nscount)
       wServ = 0;
     SServ = wServ;
+
+    /* There exists attacks on DNS where many wrong answers with guessed id's and
+       spoofed source address and port are generated at about the time when the
+       program is tricked into resolving a name.
+       This routine runs through the retry loop for each incorrect answer.
+       It is thus extremely likely that such attacks will cause a TRY_AGAIN return,
+       probably causing the calling program to retry after a delay.
+       
+       Note that valid late or duplicate answers to a previous questions also cause
+       a retry, although this is minimized by flushing the socket before sending the
+       new question.
+    */
+
+    /* Flush duplicate or late answers */
+    while ((rslt = cygwin_recvfrom( statp->sockfd, AnsPtr, AnsLength, 0, NULL, NULL)) >= 0) {
+      DPRINTF(debug, "Flushed %d bytes\n", rslt);
+    }
+    DPRINTF(debug && (errno != EWOULDBLOCK), 
+	    "Unexpected errno for flushing recvfrom: %s", strerror(errno)); 
+
     /* Send the message */
     rslt = cygwin_sendto(statp->sockfd, MsgPtr, MsgLength, 0,
 			 (struct sockaddr *) &statp->nsaddr_list[wServ],
@@ -481,59 +508,66 @@ int res_nsend( res_state statp, const un
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
-    /*
-       Prepare to retry with tcp
+    DPRINTF(debug, "recvfrom: %d bytes from %08x\n", rslt, dnsSockAddr.sin_addr.s_addr);
+    /* 
+       Prepare to retry with tcp 
     */
     for (tcp = 0; tcp < 2; tcp++) {
-      /* Check if this is the message we expected */
-      if ((*MsgPtr == *AnsPtr)     /* Ids match */
-	  && (*(MsgPtr + 1) == *(AnsPtr + 1))
-/* We have stopped checking this because the question may not be present on error,
-   in particular when the name in the question is not a valid name.
-   Simply check that the header is present. */
+      /* Check if this is the expected message from the expected server */
+      if ((memcmp(& dnsSockAddr, & statp->nsaddr_list[wServ],
+		  (char *) & dnsSockAddr.sin_zero[0] - (char *) & dnsSockAddr) == 0)
 	  && (rslt >= HFIXEDSZ)
-/*        && (rslt >= MsgLength )
-	  && (memcmp(MsgPtr + HFIXEDSZ, AnsPtr + HFIXEDSZ, MsgLength - HFIXEDSZ) == 0) */
-	  && ((AnsPtr[2] & QR) != 0)) {
-
-	DPRINTF(debug, "answer %u from %08x. Error %d. Count %d.\n",
-		rslt, dnsSockAddr.sin_addr.s_addr,
-		AnsPtr[3] & ERR_MASK, AnsPtr[6]*256 + AnsPtr[7]);
-#if 0
- NETDB_INTERNAL -1 /* see errno */
- NETDB_SUCCESS   0 /* no problem */
- HOST_NOT_FOUND  1 /* Authoritative Answer Host not found */
- TRY_AGAIN       2 /* Non-Authoritive Host not found, or SERVERFAIL */
-			 Also seen returned by some servers when the name is too long
- NO_RECOVERY     3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
- NO_DATA         4 /* Valid name, no data record of requested type */
-#endif
+	  && (*MsgPtr == *AnsPtr)     /* Ids match */
+	  && (*(MsgPtr + 1) == *(AnsPtr + 1))
+	  && ((AnsPtr[2] & QR) != 0)
+	  && (AnsPtr[4] == 0)
+	  /* We check the question if present.
+	     Some servers don't return it on error, in particular
+	     when the name in the question is not valid. */
+	  && (((AnsPtr[5] == 0)
+	       && ((AnsPtr[3] & ERR_MASK) != NOERROR))
+	      || ((AnsPtr[5] == 1)
+		  && (rslt >= MsgLength)
+		  && (memcmp(MsgPtr + HFIXEDSZ, AnsPtr + HFIXEDSZ, MsgLength - HFIXEDSZ) == 0)))) {
 	if ((AnsPtr[3] & ERR_MASK) == NOERROR) {
-	  if ((AnsPtr[2] & TC) && !(statp->options & RES_IGNTC)) { /* Truncated. Try TCP */
+	  if ((AnsPtr[2] & TC) && (tcp == 0) && !(statp->options & RES_IGNTC)) { 
+	    /* Truncated. Try TCP */
 	    rslt = get_tcp(&statp->nsaddr_list[wServ], MsgPtr, MsgLength,
 			   AnsPtr, AnsLength, statp->options & RES_DEBUG);
-	    continue;
+	    continue /* Tcp loop */; 
 	  }
 	  else if ((AnsPtr[6] | AnsPtr[7])!= 0)
 	    return rslt;
 	  else
 	    statp->res_h_errno = NO_DATA;
 	}
+#if 0
+ NETDB_INTERNAL -1 /* see errno */
+ NETDB_SUCCESS   0 /* no problem */
+ HOST_NOT_FOUND  1 /* Authoritative Answer Host not found */
+ TRY_AGAIN       2 /* Non-Authoritive Host not found, or SERVERFAIL */
+			 Also seen returned by some servers when the name is too long
+ NO_RECOVERY     3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
+ NO_DATA         4 /* Valid name, no data record of requested type */
+#endif
 	else {
+	  switch (AnsPtr[3] & ERR_MASK) {
 	  /* return HOST_NOT_FOUND even for non-authoritative answers */
-	  if ((AnsPtr[3] & ERR_MASK) == NXDOMAIN)
+	  case NXDOMAIN:
+	  case FORMERR:
 	    statp->res_h_errno = HOST_NOT_FOUND;
-	  else if ((AnsPtr[3] & ERR_MASK) == SERVFAIL)
+	    break;
+	  case SERVFAIL: 
 	    statp->res_h_errno = TRY_AGAIN;
-	  else
+	    break;
+	  default:
 	    statp->res_h_errno = NO_RECOVERY;
+	  }
 	}
 	return -1;
       }
       else {
-	DPRINTF(debug, "unexpected answer %u from %x to query to %x\n",
-		rslt, dnsSockAddr.sin_addr.s_addr,
-		statp->nsaddr_list[wServ].sin_addr.s_addr);
+	DPRINTF(debug, "unexpected answer\n");
 	break;
       }
     } /* TCP */
@@ -564,7 +598,8 @@ int res_nmkquery (res_state statp,
 		  const unsigned char * newrr, unsigned char * buf, int buflen)
 {
   int i, len;
-  short id;
+  const char * ptr;
+  unsigned int id4;
 
   if (op == QUERY) {
     /* Write the name and verify buffer length */
@@ -575,10 +610,10 @@ int res_nmkquery (res_state statp,
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
+
     /* Fill the header */
-    id = statp->id;
-    PUTSHORT(id, buf);
-    PUTSHORT(RD, buf);
+    PUTSHORT(statp->id, buf);
+    PUTSHORT(RD, buf); 
     PUTSHORT(1, buf); /* Number of questions */
     for (i = 0; i < 3; i++)
       PUTSHORT(0, buf); /* Number of answers */
@@ -587,7 +622,19 @@ int res_nmkquery (res_state statp,
     buf += len;
     PUTSHORT(qtype, buf);
     PUTSHORT(qclass, buf);
-    return len + 16; /* packet size */
+
+    /* Update id. The current query adds entropy to the next query id */
+    for (id4 = qtype, i = 0, ptr = dnameptr; *ptr; ptr++, i += 3) 
+      id4 ^= *ptr << (i & 0xF);
+    i = 1 + statp->id % 15; /* Between 1 and 16 */
+    /* id dependent rotation, also brings MSW to LSW */
+    id4 = (id4 << i) ^ (id4 >> (16 - i)) ^ (id4 >> (32 - i));
+    if ((short) id4)
+      statp->id ^= (short) id4;
+    else 
+      statp->id++; /* Force change */
+
+    return len + (HFIXEDSZ + QFIXEDSZ); /* packet size */
   }
   else { /* Not implemented */
     errno = ENOSYS;
