/* minires.c.  Stub synchronous resolver for Cygwin.

   Copyright 2006, 2008, 2009 Red Hat, Inc.

   Written by Pierre A. Humblet <Pierre.Humblet@ieee.org>

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "minires.h"

/***********************************************************************

Utilities

***********************************************************************/
/***********************************************************************

dprintf
***********************************************************************/
void minires_dprintf(const char * format, ...)
{
  va_list args;

  va_start(args, format);
  fprintf(stderr, "Minires: ");
  vfprintf(stderr, format, args);
  va_end(args);
}

/***********************************************************************

scanline
Put pointers in list[] to the beginning of each space or comma delimited
word in "in", and put the lengths in sizes[] (counting the final 0).
Return the number of words found
***********************************************************************/
static int scanline(char * in, char **list, int * sizes, int maxnum)
{
  int i;
  char * startp;
  for (i = 0; i < maxnum; i++) {
    while((*in) && (isspace((unsigned char)*in) || *in == ',')) in++;
    if (*in == 0)
      break;
    startp = in++;
    while((*in) && !isspace((unsigned char)*in) && *in != ',') in++;
    list[i] = startp;
    sizes[i] = in - startp + 1;
    if (*in)
      *in++ = 0;
  }
  return i;
}

/***********************************************************************

Read the search string.

***********************************************************************/
void minires_get_search(char * string, res_state statp)
{
  char * words[MAXDNSRCH+1], * ptr;
  int sizes[MAXDNSRCH+1];
  int i, j, debug = statp->options & RES_DEBUG;

  i = scanline(string, words, sizes, MAXDNSRCH+1);
  ptr = statp->defdname;
  for (j = 0; j < i; j++) {
    if (j < MAXDNSRCH
	&& ptr + sizes[j] < &statp->defdname[DIM(statp->defdname)]) {
      statp->dnsrch[j] = strcpy(ptr, words[j]);
      statp->dnsrch[j+1] = NULL;
      ptr += sizes[j];
      DPRINTF(debug, "search \"%s\"\n", words[j]);
    }
    else
      DPRINTF(debug, "no space for \"%s\"\n", words[j]);
  }
}

/***********************************************************************

Read options


***********************************************************************/
static void get_options(res_state statp, int i, char **words)
{
  char *ptr;
  int value;

  while (i-- > 0) {
    if (!strcasecmp("debug", words[i])) {
      statp->options |= RES_DEBUG;
      DPRINTF(statp->options & RES_DEBUG, "%s: 1\n", words[i]);
      continue;
    }
    if (!strcasecmp("inet6", words[i])) {
      statp->options |= RES_USE_INET6;
      DPRINTF(statp->options & RES_DEBUG, "%s: 1\n", words[i]);
      continue;
    }
    if (!strcasecmp("osquery", words[i])) {
      statp->use_os = 1;
      DPRINTF(statp->options & RES_DEBUG, "%s: 1\n", words[i]);
      continue;
    }

    if ((ptr = strchr(words[i], ':'))) {
      *ptr++ = 0;
      value = atoi(ptr);
      /* Not supported
	 if (!strcasecmp("ndots", words[i])) {
	 statp->ndots = value;
	 continue;
	 }
      */
      if (!strcasecmp("retry", words[i])
	  || !strcasecmp("attempts", words[i])) {
	if (value < 1)
	  value = 1;
	else if (value > RES_MAXRETRY)
	  value = RES_MAXRETRY;
	statp->retry = value;
	DPRINTF(statp->options & RES_DEBUG, "%s: %d\n", words[i], value);
	continue;
      }
      if (!strcasecmp("retrans", words[i])
	  || !strcasecmp("timeout", words[i])) {
	if (value < 1)
	  value = 1;
	else if (value > RES_MAXRETRANS)
	  value = RES_MAXRETRANS;
	statp->retrans = value;
	DPRINTF(statp->options & RES_DEBUG, "%s: %d\n", words[i], value);
	continue;
      }
    }
    DPRINTF(statp->options & RES_DEBUG, "unknown option: \"%s\"\n", words[i]);
  }
}

/***********************************************************************

Read the resolv.conf file.
We only look for nameserver, domain, search and options

***********************************************************************/
#if MAXNS > MAXDNSRCH + 1
#define MAXSIZE MAXNS
#else
#define MAXSIZE MAXDNSRCH + 1 /* Make unused one visible */
#endif
static void get_resolv(res_state statp)
{
  FILE * fd;
  char *words[MAXSIZE + 1], line[4096], *ptr;
  int sizes[DIM(words)];
  int i, j, ns = 0, have_search, have_address, debug = statp->options & RES_DEBUG;

  fd = fopen(_PATH_RESCONF, "r");
  DPRINTF(debug, _PATH_RESCONF ": %s\n", fd?"OK":strerror(errno));
  if (fd == NULL)
    return;

  statp->use_os = 0; /* Do not use os_query, except if allowed by "options" */
  have_search = (statp->dnsrch[0] != NULL);
  have_address = (statp->nscount != 0);

  while ( fgets(line, sizeof(line), fd) != 0) {
    DPRINTF(debug, "resolv.conf %s", line);
    if ((i = scanline(line, words, sizes, DIM(words))) > 0) {
      if (!have_address
	  && !strncasecmp("nameserver", words[0], sizes[0])) {
	for ( j = 1; j < i ; j++) {
	  unsigned int address;
	  address = cygwin_inet_addr(words[j]);
	  if (address == -1) {
	    DPRINTF(debug, "invalid server \"%s\"\n", words[j]);
	  }
	  else if (ns >= MAXNS) {
	    DPRINTF(debug, "no space for server \"%s\"\n", words[j]);
	  }
	  else {
	    statp->nsaddr_list[ns++].sin_addr.s_addr = address;
	    statp->nscount++;
	    DPRINTF(debug, "server \"%s\"\n", words[j]);
	  }
	}
      }
      else if (!have_search
	       && (!strncasecmp("search", words[0], sizes[0])
		   || !strncasecmp("domain", words[0], sizes[0]))) {
	ptr = statp->defdname;
	for (j = 0; j + 1 < i; j++) {
	  if (j < MAXDNSRCH
	      && ptr + sizes[j + 1] < &statp->defdname[DIM(statp->defdname)]) {
	    statp->dnsrch[j] = strcpy(ptr, words[j+1]);
	    statp->dnsrch[j+1] = 0;
	    ptr += sizes[j+1];
	    DPRINTF(debug, "domain|search \"%s\"\n", statp->dnsrch[j]);
	  }
	  else {
	    DPRINTF(debug, "no space for \"%s\"\n", words[j+1]);
	  }
	}
      }
      /* Options line */
      else if (!strncasecmp("options", words[0], sizes[0]))
	get_options(statp, i - 1, &words[1]);
    }
  }
  fclose(fd);
  return;
}

/****************************************************************************/
/*
   open_sock()
   Create a datagram socket and call bind.

****************************************************************************/

static int open_sock(struct sockaddr_in *CliAddr, int debug)
{
  int fd;

  DPRINTF(debug, "opening UDP socket\n");

  /* Create a datagram socket */
  if ((fd = cygwin_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
    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);
  bzero(CliAddr->sin_zero, sizeof(CliAddr->sin_zero));
  /* Get a port */
  if (cygwin_bind(fd, (struct sockaddr *) CliAddr, sizeof(*CliAddr)) < 0) {
    DPRINTF(debug, "bind: %s\n", strerror(errno));
    return -1;
  }
  return fd;
}

/*****************************************************************
 *
 __res_state()
 Undocumented but public. Accessed through _res

 *****************************************************************/
static struct __res_state res;
struct __res_state *__res_state(void)
{
  return & res;
}

/*****************************************************************
 *
 res_init()

 *****************************************************************/
int res_ninit(res_state statp)
{
  int i;

  statp->res_h_errno = NETDB_SUCCESS;
   /* Only debug may be set before calling init */
  statp->options &= RES_DEBUG;
  statp->options |= RES_INIT | RES_DEFAULT;
  statp->nscount = 0;
  statp->os_query = NULL;
  statp->retrans = RES_TIMEOUT; /* timeout in seconds */
  statp->retry = RES_MAXRETRY;  /* max number of retries */
  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)*/
  get_resolv(statp);
  /* Get dns servers and search list from an os-specific routine, set os_query */
  get_dns_info(statp);

  if (statp->nscount == 0 && !statp->os_query) {
    errno = ENONET;
    statp->res_h_errno = NETDB_INTERNAL;
    DPRINTF(statp->options & RES_DEBUG, "no dns server found\n");
    return -1;
  }
  for (i = 0; i < statp->nscount; i++) {
    statp->nsaddr_list[i].sin_family = AF_INET;
    statp->nsaddr_list[i].sin_port = htons(NAMESERVER_PORT);
    bzero(statp->nsaddr_list[i].sin_zero, sizeof(statp->nsaddr_list[i].sin_zero));
  }
  return 0;
}

int res_init()
{
  int r = res_ninit(& res);
  h_errno = res.res_h_errno;
  return r;
}

/*****************************************************************
 *
 res_close()

 *****************************************************************/
void res_nclose(res_state statp)
{
  int res;
  if (statp->sockfd != -1) {
    res = close(statp->sockfd);
    DPRINTF(statp->options & RES_DEBUG, "close sockfd %d: %s\n",
	    statp->sockfd, (res == 0)?"OK":strerror(errno));
    statp->sockfd = -1;
  }
}

void res_close()
{
  res_nclose(& res);
}

/*****************************************************************
 *
 get_tcp_buf()

 *****************************************************************/
static int get_tcp_buf(int fd, unsigned char *buf, int size, int debug)
{
  int res;
  while (size > 0) {
    if ((res = read(fd, buf, size)) < 0) {
      DPRINTF(debug, "read: %s\n", strerror(errno));
      return -1;
    }
    DPRINTF(debug, "read %d out of %d\n", res, size);
    size -= res;
    buf += res;
  }
  return 0;
}

/*****************************************************************
 *
 get_tcp()

 *****************************************************************/
static int get_tcp(struct sockaddr_in *CliAddr,
		   const unsigned char * MsgPtr, int MsgLength,
		   unsigned char * AnsPtr, int AnsLength, int debug)
{
  int fd, res = -1;
  unsigned short ans_length;
  union {short len; u_char buf[sizeof(short)];} len_buf;

  DPRINTF(debug, "retrying with TCP\n");

  /* Create a tcp socket */
  if ((fd = cygwin_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    DPRINTF(debug, "socket(TCP): %s\n", strerror(errno));
    return -1;
  }

  if (cygwin_connect(fd, (struct sockaddr *)  CliAddr, sizeof(* CliAddr)) < 0) {
    DPRINTF(debug, "connect: %s\n", strerror(errno));
    goto done;
  }

  /* Send the length then the message */
  len_buf.len = htons(MsgLength);
  if (write(fd, len_buf.buf, sizeof(len_buf)) != sizeof(len_buf)
      || write(fd, MsgPtr, MsgLength) != MsgLength) {
    DPRINTF(debug, "write: %s\n", strerror(errno));
    goto done;
  }

  /* Read the answer length */
  if (get_tcp_buf(fd, len_buf.buf, sizeof(len_buf), debug))
    goto done;
  ans_length = ntohs(len_buf.len);

  /* Read the answer */
  if (get_tcp_buf(fd, AnsPtr, MIN(ans_length, AnsLength), debug))
    goto done;
  res = ans_length;

 done:
  close (fd);
  return res;
}

/*****************************************************************
 **
 res_send
 Assumes that the message is a query starting with a short id.
 Handles retransmissions until that id is received.

*****************************************************************/
int res_nsend( res_state statp, const unsigned char * MsgPtr,
	       int MsgLength, unsigned char * AnsPtr, int AnsLength)
{
  /* Current server, shared by all tasks */
  volatile static unsigned int SServ = 0XFFFFFFFF;
  int tcp;
  const int debug = statp->options & RES_DEBUG;

  fd_set fdset_read;
  int rslt, addrLen, transNum, wServ;
  struct sockaddr_in mySockAddr, dnsSockAddr;
  struct timeval timeOut;

  statp->res_h_errno = NETDB_SUCCESS;
  if (((statp->options & RES_INIT) == 0) && (res_ninit(statp) != 0))
    return -1;

  /* If a hook exists to a native implementation, use it */
  if (statp->os_query) {
    int len;
    short int Class, Type;
    char DomName[MAXDNAME];
    unsigned char * ptr = (unsigned char *) MsgPtr + HFIXEDSZ;
    len = dn_expand(MsgPtr, MsgPtr + MsgLength, ptr, DomName, sizeof(DomName));
    if (len > 0) {
      ptr += len;
      GETSHORT(Type, ptr);
      GETSHORT(Class, ptr);
      return ((os_query_t *) statp->os_query)(statp, DomName, Class, Type, AnsPtr, AnsLength);
    }
    else {
      /* dn_expand sets errno */ 
      statp->res_h_errno = NETDB_INTERNAL;
      return -1;
    }
  }	  

  /* Close the socket if it had been opened before a fork.
     Reuse of pid's cannot hurt */
  if ((statp->sockfd != -1) && (statp->mypid != getpid())) {
    res_nclose(statp);
  }

  /* Open a socket for this process */
  if (statp->sockfd == -1) {
    /* 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;
    }
    statp->mypid = getpid();
    if (SServ == 0XFFFFFFFF) /* Pseudo random */
      SServ =  statp->id % statp->nscount;
  }

  transNum = 0;
  while ( transNum++ < statp->retry) {
    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],
			 sizeof(struct sockaddr_in));
    DPRINTF(debug, "sendto: server %08x sockfd %d %s\n",
	    statp->nsaddr_list[wServ].sin_addr.s_addr,
	    statp->sockfd, (rslt == MsgLength)?"OK":strerror(errno));
    if (rslt != MsgLength) {
      statp->res_h_errno = NETDB_INTERNAL;
      return -1;
    };
    /*
      Wait for a reply with select()
    */
    FD_ZERO(&fdset_read);
    FD_SET (statp->sockfd, &fdset_read );
    timeOut.tv_sec = statp->retrans;
    timeOut.tv_usec = 0;
    rslt = cygwin_select(statp->sockfd + 1, &fdset_read, NULL, NULL, &timeOut);
    if ( rslt == 0 ) { /* Timeout */
      DPRINTF(statp->options & RES_DEBUG, "timeout for server %08x\n",
	      statp->nsaddr_list[wServ].sin_addr.s_addr);
      continue;
    }
    else if ((rslt != 1) || (FD_ISSET(statp->sockfd, &fdset_read) == 0)) {
      DPRINTF(debug, "select: %s\n", strerror(errno));
      statp->res_h_errno = NETDB_INTERNAL;
      return -1;
    }

    addrLen = sizeof(dnsSockAddr);
    rslt = cygwin_recvfrom(statp->sockfd, AnsPtr, AnsLength, 0,
			   (struct sockaddr *) & dnsSockAddr, & addrLen);
    if (rslt <= 0) {
      DPRINTF(debug, "recvfrom: %s\n", strerror(errno));
      statp->res_h_errno = NETDB_INTERNAL;
      return -1;
    }
    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 expected message from the expected server */
      if ((memcmp(& dnsSockAddr, & statp->nsaddr_list[wServ],
		  (char *) & dnsSockAddr.sin_zero[0] - (char *) & dnsSockAddr) == 0)
	  && (rslt >= HFIXEDSZ)
	  && (*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) && (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 /* 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 */
	  case NXDOMAIN:
	  case FORMERR:
	    statp->res_h_errno = HOST_NOT_FOUND;
	    break;
	  case SERVFAIL: 
	    statp->res_h_errno = TRY_AGAIN;
	    break;
	  default:
	    statp->res_h_errno = NO_RECOVERY;
	  }
	}
	return -1;
      }
      else {
	DPRINTF(debug, "unexpected answer\n");
	break;
      }
    } /* TCP */
  }
  DPRINTF(debug, "too many retries\n");
  statp->res_h_errno = TRY_AGAIN;
  return -1;
}

int res_send( const unsigned char * MsgPtr, int MsgLength,
	      unsigned char * AnsPtr, int AnsLength)
{
  int r = res_nsend(& res, MsgPtr, MsgLength, AnsPtr, AnsLength);
  h_errno = res.res_h_errno;
  return r;
}

/*****************************************************************
 *
 res_mkquery

 Return: packet size
	-1 name format is incorrect
*****************************************************************/
int res_nmkquery (res_state statp,
		  int op, const char * dnameptr, int qclass, int qtype,
		  const unsigned char * dataptr, int datalen,
		  const unsigned char * newrr, unsigned char * buf, int buflen)
{
  int i, len;
  const char * ptr;
  unsigned int id4;

  if (op == QUERY) {
    /* Write the name and verify buffer length */
    len = dn_comp(dnameptr, buf + HFIXEDSZ, buflen - HFIXEDSZ - QFIXEDSZ, NULL, NULL);
    if (len < 0) {
      DPRINTF(statp->options & RES_DEBUG,
	      "\"%s\" invalid or buffer too short\n", dnameptr);
      statp->res_h_errno = NETDB_INTERNAL;
      return -1;
    }

    /* Fill the header */
    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 */

    /* Write qtype and qclass */
    buf += len;
    PUTSHORT(qtype, buf);
    PUTSHORT(qclass, buf);

    /* 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;
    statp->res_h_errno = NETDB_INTERNAL;
    return -1;
  }
}

int res_mkquery (int op, const char * dnameptr, int qclass, int qtype,
		 const unsigned char * dataptr, int datalen,
		 const unsigned char * newrr, unsigned char * buf, int buflen)
{
  int r = res_nmkquery (& res, op, dnameptr, qclass, qtype,
			dataptr, datalen, newrr, buf, buflen);
  h_errno = res.res_h_errno;
  return r;

}

/*****************************************************************
 * res_query()
 *****************************************************************/

int res_nquery( res_state statp, const char * DomName, int Class, int Type,
		unsigned char * AnsPtr, int AnsLength)
{
  u_char packet[PACKETSZ];
  int len;

  DPRINTF(statp->options & RES_DEBUG, "query \"%s\" type %d\n", DomName, Type);
  statp->res_h_errno = NETDB_SUCCESS;

  /* If a hook exists to a native implementation, use it */
  if (statp->os_query)
    return ((os_query_t *) statp->os_query)(statp, DomName, Class, Type, AnsPtr, AnsLength);

  if ((len = res_nmkquery (statp, QUERY, DomName, Class, Type,
			   0, 0, 0, packet, PACKETSZ)) < 0)
    return -1;
  return res_nsend( statp, packet, len, AnsPtr, AnsLength);
}

int res_query( const char * DomName, int Class, int Type, unsigned char * AnsPtr, int AnsLength)
{
  int r = res_nquery(& res, DomName, Class, Type, AnsPtr, AnsLength);
  h_errno = res.res_h_errno;
  return r;
}

/*****************************************************************
 * res_querydomain()
 *****************************************************************/
int res_nquerydomain( res_state statp, const char * Name, const char * DomName,
		      int Class, int Type, unsigned char * AnsPtr, int AnsLength)
{
  char fqdn[MAXDNAME], *ptr;
  int nlen;

  if (!DomName)
    ptr = (char *) Name;
  else if ((nlen = strlen(Name)) >= sizeof(fqdn) - 1)
    goto error;
  else {
    strcpy(fqdn, Name);
    ptr = &fqdn[nlen];
    if (nlen && *(ptr - 1) != '.')
      *(ptr++ - 1) = '.';
    fqdn[sizeof(fqdn) - 1] = 0;
    strncpy(ptr, DomName, sizeof(fqdn) - (ptr - fqdn));
    if (fqdn[sizeof(fqdn) - 1])
      goto error;
    ptr = fqdn;
  }
  return res_nquery(statp, ptr, Class, Type, AnsPtr, AnsLength);

 error:
  DPRINTF(statp->options & RES_DEBUG, "querydomain: name too long\n");
  errno = EINVAL;
  statp->res_h_errno = NETDB_INTERNAL;;
  return -1;
}

int res_querydomain( const char * Name, const char * DomName, int Class,
		     int Type, unsigned char * AnsPtr, int AnsLength)
{
  int r = res_nquerydomain(& res, Name, DomName, Class, Type, AnsPtr,
			   AnsLength);
  h_errno = res.res_h_errno;
  return r;
}

/*****************************************************************
 *
 res_search()

 *****************************************************************/

int res_nsearch( res_state statp, const char * DomName, int Class, int Type,
		 unsigned char * AnsPtr, int AnsLength)
{
  int len, stat, i;
  char fullDomName[MAXDNAME], *ptr, *sptr;

  DPRINTF(statp->options & RES_DEBUG, "search \"%s\" type %d\n", DomName, Type);

  if (((statp->options & RES_INIT) == 0) && (res_ninit(statp) != 0))
    return -1;

  stat = res_nquery( statp, DomName, Class, Type, AnsPtr, AnsLength);

  /* Check if will skip search */
  if (statp->res_h_errno != HOST_NOT_FOUND               /* Success or hard failure */
      || ((ptr = strrchr(DomName, '.')) && (!*(ptr+1)))  /* Final dot */
      || (((statp->options & RES_DNSRCH) == 0)           /* Or no search */
	  && ((ptr != NULL)                              /*  And some dot */
	      || ((statp->options & RES_DEFNAMES) == 0)))/*    or no def domain */
      || (!(sptr = statp->dnsrch[0])))
    return stat;

  len = strlen(DomName);
  if (len >= MAXDNAME - 1) /* Space for next dot */
    goto error;
  strcpy(fullDomName, DomName);
  fullDomName[len++] = '.';
  fullDomName[MAXDNAME - 1] = 0; /* Overflow indicator */
  i = 0;
  do {
    strncpy(fullDomName + len, sptr, MAXDNAME - len);
    if (fullDomName[MAXDNAME - 1])
      goto error;
    stat = res_nquery(statp, fullDomName, Class, Type, AnsPtr, AnsLength);
  } while ((sptr = statp->dnsrch[++i]) != NULL
	   && statp->res_h_errno == HOST_NOT_FOUND
	   && (statp->options & RES_DNSRCH) != 0);

  /* Return last stat */
  return stat;

 error:
  DPRINTF(statp->options & RES_DEBUG, "name too long during search\n");
  errno = EINVAL;
  statp->res_h_errno = NETDB_INTERNAL;
  return -1;
}

int res_search( const char * DomName, int Class, int Type,
		unsigned char * AnsPtr, int AnsLength)
{
  int r = res_nsearch(& res, DomName, Class, Type, AnsPtr, AnsLength);
  h_errno = res.res_h_errno;
  return r;
}

/*****************************************************************
 * dn_expand
 *****************************************************************/

int dn_expand(const unsigned char *msg, const unsigned char *eomorig,
	      const unsigned char *comp_dn, char *exp_dn, int length)
{
  unsigned int len, complen = 0;
  const unsigned char *comp_dn_orig = comp_dn;

  if (comp_dn >= eomorig)
    goto expand_fail;
  if ((len = *comp_dn++) == 0)       /* Weird case */
    exp_dn++;
  else do {
    if (len <= MAXLABEL) {
      if ((length -= (len + 1)) >= 0 /* Need space for final . */
	  && comp_dn + len <= eomorig) {
	do { *exp_dn++ = *comp_dn++; } while (--len != 0);
	*exp_dn++ = '.';
      }
      else
	goto expand_fail;
    }
    else if (len >= (128+64)) {
      if (!complen)   /* Still in the original field? */
	complen = (comp_dn - comp_dn_orig) + 1;
      comp_dn = msg + (((len & ~(128+64)) << 8) + *comp_dn);
      if (comp_dn >= eomorig)
	goto expand_fail;
    }
    else
      goto expand_fail;
  } while ((len = *comp_dn++) != 0);
  /* Replace last . with a 0 */
  *(--exp_dn) = 0;
  if (!complen)
    complen = comp_dn - comp_dn_orig;
/*  fprintf(stderr, "dn_expand %s\n", exp_start); */
  return complen;

expand_fail:
  errno = EINVAL;
  return -1;
}

/*****************************************************************
 *
 dn_comp

 Return -1 in case of overflow, but still fill buffer correctly.
 We do not check the alphabet of the host names
 nor the length of the compressed name and we
 preserve the letter cases.

 *****************************************************************/
int dn_comp(const char * exp_dn, u_char * comp_dn, int length,
	    u_char ** dnptrs, u_char ** lastdnptr)
{
  u_char *cptr = comp_dn, *dptr, *lptr, *rptr;
  unsigned int i, len;
  u_char * const eptr = comp_dn + length - 1; /* Last valid */

  errno = EINVAL;

  if (*exp_dn == '.' && !*(exp_dn + 1))
    exp_dn++;
  while (1) {
    if (*exp_dn == '.' || cptr > eptr)
      return -1;
    if (*exp_dn == 0) {
      *cptr++ = 0;
      break;
    }
    /* Try to compress */
    if (dnptrs) {
      for (i = 1; dnptrs[i]; i++) {
	dptr = dnptrs[i];
	if (dptr >= comp_dn) /* Handle name.name */
	  continue;
	rptr = (u_char *) exp_dn;
	len = *dptr++;
	while (1) {
	  do {
	    if (*dptr++ != *rptr++)
	      goto next_dn;
	  } while (--len);
	  len = *dptr++;
	  if (len == 0) { /* last label */
	    if (!*rptr || (*rptr == '.' && !*(rptr + 1))) { /* Full match */
	      len = (dnptrs[i] - dnptrs[0]) | 0xC000;
	      /* Write pointer */
	      *cptr++ = len >> 8;
	      if (cptr > eptr)
		return -1;
	      *cptr++ = len;
	      goto done;
	    }
	    goto next_dn;
	  }
	  if (*rptr++ != '.')
	    goto next_dn;
	  if (len >= 128 + 64) {
	    dptr = dnptrs[0] + ((len - 128 - 64) << 8) + *dptr;
	    len = *dptr++;
	  }
	}
      next_dn: ;
      }
      /* Record label if asked and if space is available and if not too far off */
      if (lastdnptr && (lastdnptr != &dnptrs[i]) && (cptr - dnptrs[0]) < 0xC000) {
	dnptrs[i] = cptr;
	dnptrs[i+1] = NULL;
      }
    }
    /* Write label */
    lptr = cptr++; /* Length byte */
    rptr = (u_char *) exp_dn;
    do {
      if (cptr <= eptr)
	*cptr++ = *rptr;
    } while ((*++rptr != '.') && (*rptr != 0));
    len = rptr - (u_char *) exp_dn;
    if (len > MAXLABEL)
      return -1;
    *lptr = len;
    exp_dn = (char *) rptr;
    if (*exp_dn != 0)
      exp_dn++; /* Skip over . */
  }
 done:
  return cptr - comp_dn;
}

/*****************************************************************
 * dn_skipname

 Measures the compressed domain name length and returns it.
 *****************************************************************/
int dn_skipname(const unsigned char *comp_dn, const unsigned char *eom)
{
  int len;
  const unsigned char *comp_dn_orig = comp_dn;

  do {
    len = *comp_dn++;
    if (len >= (128 + 64)) {
      comp_dn++;
      break;
    }
    if (len > MAXLABEL ||
	(comp_dn += len) > eom)
      return -1;
  } while (len != 0);

  return comp_dn - comp_dn_orig;
}

/*****************************************************************
 * dn_length1    For internal use

 Return length of uncompressed name incl final 0.
 *****************************************************************/

int dn_length1(const unsigned char *msg, const unsigned char *eomorig,
	       const unsigned char *comp_dn)
{
  unsigned int len, length = 0;

  errno = EINVAL;
  if (comp_dn >= eomorig)
    goto expand_fail;
  else while ((len = *comp_dn++) != 0) {
    if (len <= MAXLABEL) {
      if ((comp_dn += len) <= eomorig)
	length += len + 1;
      else
	goto expand_fail;
    }
    else if (len >= (128+64)) {
      comp_dn = msg + (((len & ~(128+64)) << 8) + *comp_dn);
      if (comp_dn >= eomorig)
	goto expand_fail;
    }
    else
      goto expand_fail;
  }
  return length;

expand_fail:
  return -1;
}
