/*
 * Copyright (c) 1983, 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:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 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
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Copyright (c) 1996,1999 by Internet Software Consortium.
 *
 * 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */



#include <w32api/windows.h>
#include <w32api/iphlpapi.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <stdlib.h>
#include <sys/unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "win32_resolv.h"


// misc definitions
#define BUF_SIZE 8096
#define DNS_PORT 53
#define UDP_PROTO 17
#define TCP_PROTO 6

// internal error codes
#define RESP_TRYTCP -1
#define RESP_TRYNEXTSERVER -2
#define RESP_UNKNOWNHOST -3


// Possible opcodes for query
#define OPCODE_QUERY 0
#define OPCODE_IQUERY 1
#define OPCODE_STATUS 2



/**
 * Perform a DNS lookup using UDP
 *
 * @param dnsServer DNS Server to look up
 * @param sendPktBuf Buffer containing the packet to send
 * @param sendPktSize Size of the packet to send
 * @param recvPktBuf Buffer to put received packet in
 * @param recvPktSize Size of the recvPktBuf buffer
 * @param expectedId Id counter to identify response packets
 * @return Received buffer size, or <0 on failure
 */
int dnsUdp(struct in_addr dnsServer, 
           char* sendPktBuf, int sendPktSize,
           char* recvPktBuf, int recvPktSize,
           int* expectedId);

/**
 * Perform a DNS lookup using TCP
 *
 * @param dnsServer DNS Server to look up
 * @param sendPktBuf Buffer containing the packet to send
 * @param sendPktSize Size of the packet to send
 * @param recvPktBuf Buffer to put received packet in
 * @param recvPktSize Size of the recvPktBuf buffer
 * @param expectedId Id counter to identify response packets
 * @return Received buffer size, or <0 on failure
 */
int dnsTcp(struct in_addr dnsServer, 
           char* sendPktBuf, int sendPktSize,
           char* recvPktBuf, int recvPktSize,
           int* expectedId);


/**
 * Format a DNS query packet. Id will be set to 0
 *
 * @param dstBuf Where to put the packet
 * @param bufSize Size of the above
 * @param name Name of service to look up
 * @param domain Domain To look it up in
 * @param qClass Class of query
 * @param qType Type of query
 * @return Packet size on success, <0 on failure
 */
int formatPacket(char* dstBuf, int bufSize,
                 const char* name, const char* domain,
                 u_int16_t qClass, u_int16_t qType);


/**
 * Retrieve list of DNS servers known to the machine
 *
 * @param list Updated to point to a buffer of ULONGs. This will be malloced, 
 * so it is up to you to free it when finished. They are already in network 
 * order. Will be NULL terminated.
 * @return 0 on success, nonzero error code on failure
 */
int getDnsServers(struct in_addr** list);

/*
 * ns_name_unpack(msg, eom, src, dst, dstsiz)
 *      Unpack a domain name from a message, source may be compressed.
 * return:
 *      -1 if it fails, or consumed octets if it succeeds.
 */
int ns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
                   u_char *dst, size_t dstsiz);

/*
 * ns_name_uncompress(msg, eom, src, dst, dstsiz)
 *      Expand compressed domain name to presentation format.
 * return:
 *      Number of bytes read out of `src', or -1 (with errno set).
 * note:
 *      Root domain returns as "." not "".
 */
int ns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
                       char *dst, size_t dstsiz);

/*
 * ns_name_ntop(src, dst, dstsiz)
 *      Convert an encoded domain name to printable ascii as per RFC1035.
 * return:
 *      Number of bytes written to buffer, or -1 (with errno set)
 * notes:
 *      The root is returned as "."
 *      All other domains are returned in non absolute form
 */
int ns_name_ntop(const u_char *src, char *dst, size_t dstsiz);


/*
 * Expand compressed domain name 'comp_dn' to full domain name.
 * 'msg' is a pointer to the begining of the message,
 * 'eomorig' points to the first location after the message,
 * 'exp_dn' is a pointer to a buffer of size 'length' for the result.
 * Return size of compressed name or -1 if there was an error.
 */
int dn_expand(const u_char *msg, const u_char *eom, const u_char *src,
              char *dst, int dstsiz);

/*
 * special(ch)
 *      Thinking in noninternationalized USASCII (per the DNS spec),
 *      is this characted special ("in need of quoting") ?
 * return:
 *      boolean.
 */
static int special(int ch);

/*
 * printable(ch)
 *      Thinking in noninternationalized USASCII (per the DNS spec),
 *      is this character visible and not a space when printed ?
 * return:
 *      boolean.
 */
static int printable(int ch);


// list of digits
static const char       digits[] = "0123456789";






/**
 * Retrieve list of DNS servers known to the machine
 *
 * @param list Updated to point to a buffer of ULONGs. This will be malloced, 
 * so it is up to you to free it when finished. They are already in network 
 * order. Will be NULL terminated.
 * @return 0 on success, nonzero error code on failure
 */
int getDnsServers(struct in_addr** list) {
  int err;
  int count;
  ULONG fixedInfoSize;
  PFIXED_INFO fixedInfo;
  PIP_ADDR_STRING addrStr;
  
  // get size of FIXED_INFO structure
  if (!(err = GetNetworkParams(NULL, &fixedInfoSize))) {
    if (err != ERROR_BUFFER_OVERFLOW) {
      return err;
    }
  }
  
  // alloc memory
  if (!(fixedInfo = malloc(fixedInfoSize))) {
    return -ENOMEM;
  }
  
  // Get the network params
  if (!(err = GetNetworkParams(fixedInfo, &fixedInfoSize))) {
    // First of all, count the number of servers
    count = 1;
    addrStr = fixedInfo->DnsServerList.Next;
    while(addrStr) {
      count++;
      addrStr = addrStr->Next;
    }

    // I know this cannot happen, but just in case someone changes the above
    if (count == 0) {
      *list = NULL;
      return 0;
    }

    // Allocate memory to store 'em in
    if (!(*list = (struct in_addr*) malloc(sizeof(struct in_addr) * count+1))) {
      return -ENOMEM;
    }
    
    // Now, copy 'em into the list
    count = 0;
    (*list)[count++].s_addr = 
      inet_addr(fixedInfo->DnsServerList.IpAddress.String);
    addrStr = fixedInfo->DnsServerList.Next;
    while(addrStr) {
      (*list)[count++].s_addr = inet_addr(addrStr->IpAddress.String);
      addrStr = addrStr->Next;
    }
    (*list)[count].s_addr = 0;
  } else {
    free(fixedInfo);
    return err;
  }
  
  // OK!
  free(fixedInfo);
  return 0;
}

/**
 * Format a DNS query packet. Id will be set to 0
 *
 * @param dstBuf Where to put the packet
 * @param bufSize Size of the above
 * @param name Name of service to look up
 * @param domain Domain To look it up in
 * @param qClass Class of query
 * @param qType Type of query
 * @return Packet size on success, <0 on failure
 */
int formatPacket(char* dstBuf, int bufSize,
                 const char* name, const char* domain,
                 u_int16_t qClass, u_int16_t qType) {
  int offset;
  int lastCountOffset;
  HEADER* headerSection;
  char tmpName[NS_MAXDNAME+10];



  // slap the two names together correctly
  if ((name == NULL) && (domain == NULL)) {
    return -1;
  }
  if ((name != NULL) && (domain == NULL)) {
    return -1;
  }
  if (name != NULL) {
    strcpy(tmpName, name);
  }
  if (tmpName[strlen(tmpName)-1] != '.') {
    strcat(tmpName, ".");
  }
  strcat(tmpName, domain);

  // check if buffer is big enough
  if ((sizeof(HEADER) + strlen(tmpName) + 4) > bufSize) {
    return -ENOMEM;
  }

  // zero the buffer
  memset(dstBuf, 0, bufSize);

  // make up DNS header section
  headerSection = (HEADER*) dstBuf;
  headerSection->id = htons(0);
  headerSection->rd = 1;
  headerSection->qdcount = htons(1); // One single query present

  // Fill out the question section
  offset = sizeof(HEADER);

  // We're now doing the name to look up, separated by NULLs
  lastCountOffset = offset;
  offset++; // Keep one byte free for count of first component of name
  strcpy(dstBuf + offset, tmpName);

  // Now, loop through the string we've just copied converting '.' to NULL
  while(dstBuf[offset]) {
    // If we've hit a '.', update the PREVIOUS 
    // counter to the size of the bit BEFORE the '.'
    if (dstBuf[offset] == '.') {
      dstBuf[lastCountOffset] = offset - lastCountOffset -1;
      lastCountOffset = offset;
      dstBuf[offset] = 0;
    }
    
    // next char
    offset++;
  }

  // Finally, need to update the count for the bit betweent the last 
  // dot and the end of string. Note: If the last character is a '.', it will 
  // already have been turned into a NULL
  if (dstBuf[offset - 1] != 0) { 
    // if the last character was NOT '.', normal update is OK
    dstBuf[lastCountOffset] = offset - lastCountOffset -1;
    // And woo! We've already GOT a terminating NULL character from the
    // string copy
    offset++;
  } else {
    // last character was a '.'. Therefore, we should use THAT as the
    // terminating null, and not the extra one which is now present at the 
    // end of the string. Therefore, just don't bother incrementing the count!
  }

  // Add in the type and class
  *((unsigned short*) (dstBuf + offset)) = htons(qType);
  offset+=2;
  *((unsigned short*) (dstBuf + offset)) = htons(qClass);
  offset+=2;

  // Finally, return the length
  return offset;
}



/**
 * Perform a DNS lookup
 *
 * @param name Name of service to look up
 * @param domain Domain To look it up in
 * @param qClass Class of query
 * @param qType Type of query
 * @param dnsServers DNS servers to try
 * @param dstBuffer Where to put received DNS packet
 * @param dstBufferSize Size of dstBuffer
 * @return Size of received packet, or <0 on failure
 */
int dnsLookup(const char* name, const char* domain, 
              u_int16_t qClass, u_int16_t qType,
              struct in_addr* dnsServers, 
              char* dstBuffer, int dstBufferSize) {
  char tmpBuf[BUF_SIZE];
  int pktSize;
  int dnsServerCount;
  int retries;
  int count;
  int expectedId;


  // start off with expectedId 0
  expectedId = 0;
  
  // OK, format the packet & check
  if ((pktSize = formatPacket(tmpBuf, BUF_SIZE,
                              name, domain,
                              qClass, qType)) < 0) {
    return -1;
  }
  
  // retry the lookup
  for(retries=0; retries < 5; retries++) {

    // Now, try each DNS server
    dnsServerCount = 0;
    while(dnsServers[dnsServerCount].s_addr) {
      // try UDP DNS if the packet ain't too big
      if (pktSize <= NS_PACKETSZ) {
        count = dnsUdp(dnsServers[dnsServerCount], 
                       tmpBuf, pktSize,
                       dstBuffer, dstBufferSize,
                       &expectedId);
        switch(count) {
        case RESP_TRYTCP:
        case RESP_TRYNEXTSERVER:
          break;

        case RESP_UNKNOWNHOST:
          return -1;

        default:
          return count;
        }
      } else {
        count = RESP_TRYTCP;
      }

      // try TCP connection?
      if (count == RESP_TRYTCP) {
        count = dnsTcp(dnsServers[dnsServerCount], 
                       tmpBuf, pktSize,
                       dstBuffer, dstBufferSize,
                       &expectedId);
        switch(count) {
        case RESP_TRYTCP:
        case RESP_TRYNEXTSERVER:
          break;
          
        case RESP_UNKNOWNHOST:
          return -1;
          
        default:
          return count;
        }
      }

      // Move on to next server
      dnsServerCount++;
    }
  }

  // if we get here, we have not found a valid address
  return -1;
}


/**
 * Perform a DNS lookup using UDP
 *
 * @param dnsServer DNS Server to look up
 * @param sendPktBuf Buffer containing the packet to send
 * @param sendPktSize Size of the packet to send
 * @param recvPktBuf Buffer to put received packet in
 * @param recvPktSize Size of the recvPktBuf buffer
 * @param expectedId Id counter to identify response packets
 * @return Received buffer size, or <0 on failure
 */
int dnsUdp(struct in_addr dnsServer, 
           char* sendPktBuf, int sendPktSize,
           char* recvPktBuf, int recvPktSize,
           int* expectedId) {
  int sockFd;
  struct sockaddr_in server;
  struct sockaddr_in local;
  struct sockaddr_in from;
  int count;
  int tmp;
  struct pollfd sockPollFd;
  HEADER* header;


  // setup sockaddr for local machine
  memset(&local, 0, sizeof(struct sockaddr_in));
  local.sin_family = AF_INET;
  local.sin_port = 0;
  local.sin_addr.s_addr = INADDR_ANY;

  // setup sockaddr for remote server
  memset(&server, 0, sizeof(struct sockaddr_in));
  server.sin_family = AF_INET;
  server.sin_port = htons(DNS_PORT);
  server.sin_addr.s_addr = dnsServer.s_addr;

  // Create UDP socket 
  if ((sockFd = socket(AF_INET, SOCK_DGRAM, UDP_PROTO)) < 0) {
    return RESP_TRYNEXTSERVER;
  }

  // bind input socket to local machine
  if (bind(sockFd, 
           (struct sockaddr*) &local, sizeof(struct sockaddr_in)) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  }

  // set the expectedId in the packet
  ((HEADER*) sendPktBuf)->id = htons(*expectedId);
  (*expectedId)++;

  // send the packet to the DNS server
  if (sendto(sockFd, 
             sendPktBuf, sendPktSize,
             0,
             (struct sockaddr*) &server,
             sizeof(struct sockaddr_in)) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  }

  // setup sockaddr for received packet
  memset(&from, 0, sizeof(from));
  from.sin_family = AF_INET;
  tmp = sizeof(from);
  memset(recvPktBuf, 0, recvPktSize);


  // This is where we listen for connections
  while(1) {

    // Wait for a response
    sockPollFd.fd = sockFd;
    sockPollFd.events = POLLIN | POLLERR;
    sockPollFd.revents = 0;
    if (poll(&sockPollFd, 1, 7000) <= 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // OK, check we have got some data
    if (sockPollFd.revents != POLLIN) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }    
    
    // read it
    if ((count = recvfrom(sockFd,
                          recvPktBuf,
                          recvPktSize,
                          0,
                          (struct sockaddr*) &from,
                          &tmp)) < 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // check packet is big enough
    header = (HEADER*) recvPktBuf;
    if (count < sizeof(HEADER)) {
      // packet too small. try next server
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // incorrect ID => old response. start listening again
    if (ntohs(header->id) != ((*expectedId) - 1)) {
      continue;
    }

    // packet truncated. try tcp
    if (header->tc) {
      close(sockFd);
      return RESP_TRYTCP;
    }

    // not a response. try next server
    if (!header->qr) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }

    // unknown host. exit lookup
    if (header->rcode == RCODE_NAMEERROR) {
      close(sockFd);
      return RESP_UNKNOWNHOST;
    }
  
    // error. try next server
    if (header->rcode != RCODE_OK) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
  
    // There were no actual records! try next server
    if (header->ancount == 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }   

    // If we get here, we are OK! exit the loop
    break;
  }

  // OK! return the count
  close(sockFd);
  return count;
}



/**
 * Perform a DNS lookup using TCP
 *
 * @param dnsServer DNS Server to look up
 * @param sendPktBuf Buffer containing the packet to send
 * @param sendPktSize Size of the packet to send
 * @param recvPktBuf Buffer to put received packet in
 * @param recvPktSize Size of the recvPktBuf buffer
 * @param expectedId Id counter to identify response packets
 * @return Received buffer size, or <0 on failure
 */
int dnsTcp(struct in_addr dnsServer, 
           char* sendPktBuf, int sendPktSize,
           char* recvPktBuf, int recvPktSize,
           int* expectedId) {
  int sockFd;
  struct sockaddr_in server;
  struct sockaddr_in local;
  int count;
  struct pollfd sockPollFd;
  char tmpBuf[10];
  HEADER* header;



  // setup sockaddr for local machine
  memset(&local, 0, sizeof(struct sockaddr_in));
  local.sin_family = AF_INET;
  local.sin_port = 0;
  local.sin_addr.s_addr = INADDR_ANY;

  // setup sockaddr for remote server
  memset(&server, 0, sizeof(struct sockaddr_in));
  server.sin_family = AF_INET;
  server.sin_port = htons(DNS_PORT);
  server.sin_addr.s_addr = dnsServer.s_addr;
  
  // Create TCP socket 
  if ((sockFd = socket(AF_INET, SOCK_STREAM, TCP_PROTO)) < 0) {
    return RESP_TRYNEXTSERVER;
  }

  // bind input socket to local machine
  if (bind(sockFd, 
           (struct sockaddr*) &local, sizeof(struct sockaddr_in)) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  }

  // connect to the remote server
  if (connect(sockFd,
              (struct sockaddr*) &server, sizeof(struct sockaddr_in)) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  }
  
  // set the expectedId in the packet
  ((HEADER*) sendPktBuf)->id = htons(*expectedId);
  (*expectedId)++;

  // send the size to the DNS server
  *((unsigned short*) tmpBuf) = htons(sendPktSize);
  if (send(sockFd, 
           tmpBuf, 2,
           0) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  } 

  // send the packet to the DNS server
  if (send(sockFd, 
           sendPktBuf, sendPktSize,
           0) < 0) {
    close(sockFd);
    return RESP_TRYNEXTSERVER;
  }

  // This is where we listen for connections
  while(1) {

    // Wait for a response
    sockPollFd.fd = sockFd;
    sockPollFd.events = POLLIN | POLLERR;
    sockPollFd.revents = 0;
    if (poll(&sockPollFd, 1, 7000) <= 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // OK, check we have got some data
    if (sockPollFd.revents != POLLIN) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }    
    
    // read it
    if ((count = recv(sockFd,
                      recvPktBuf,
                      recvPktSize,
                      0)) < 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // check packet is big enough
    header = (HEADER*) (recvPktBuf + 2);
    if (count < sizeof(HEADER)) {
      // packet too small. try next server
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // incorrect ID => old response. start listening again
    if (ntohs(header->id) != ((*expectedId) - 1)) {
      continue;
    }
    
    // packet truncated. try next server
    if (header->tc) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // not a response. try next server
    if (!header->qr) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }

    // unknown host. exit lookup
    if (header->rcode == RCODE_NAMEERROR) {
      close(sockFd);
      return RESP_UNKNOWNHOST;
    }
    
    // error. try next server
    if (header->rcode != RCODE_OK) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }
    
    // There were no actual records! try next server
    if (header->ancount == 0) {
      close(sockFd);
      return RESP_TRYNEXTSERVER;
    }   
    
    // If we get here, we are OK. Terminate the loop
    break;
  }

  // need to copy the packet BACK two bytes, 
  // since it has the size of it prepended
  memcpy(recvPktBuf, recvPktBuf+2, count-2);

  // OK! return the count
  close(sockFd);
  return count - 2;
}




/**
 * Query domain
 *
 * @param name Name of service to query for
 * @param domain Name of domain service is in
 * @param class Class of query
 * @param type Type of record to retrieve
 * @param dstBuffer Space to put received packet
 * @param dstLength Size of received packet buffer
 * @return Size of packet received, or < 0 on failure
 */
int res_querydomain(const char *name,
                    const char *domain,
                    int class, int type,
                    u_char *dstBuffer,
                    int dstLength) {
  struct in_addr* dnsServers;
  int pktSize;

  // find the list of DNS servers on this machine
  if (getDnsServers(&dnsServers)) {
    return -1;
  }
  
  // do the lookup
  pktSize = 
    dnsLookup(name, domain, class, type, 
              dnsServers, dstBuffer, dstLength);
  free(dnsServers);
  
  // Finally return the value
  return(pktSize);
}




// -----------------------------------------------------------
// Everything below this line is taken from bind v8.2.3
// See copyright notices above

/*
 * Expand compressed domain name 'comp_dn' to full domain name.
 * 'msg' is a pointer to the begining of the message,
 * 'eomorig' points to the first location after the message,
 * 'exp_dn' is a pointer to a buffer of size 'length' for the result.
 * Return size of compressed name or -1 if there was an error.
 */
int
dn_expand(const u_char *msg, const u_char *eom, const u_char *src,
          char *dst, int dstsiz)
{
        int n = ns_name_uncompress(msg, eom, src, dst, (size_t)dstsiz);

        if (n > 0 && dst[0] == '.')
                dst[0] = '\0';
        return (n);
}


/*
 * ns_name_ntop(src, dst, dstsiz)
 *      Convert an encoded domain name to printable ascii as per RFC1035.
 * return:
 *      Number of bytes written to buffer, or -1 (with errno set)
 * notes:
 *      The root is returned as "."
 *      All other domains are returned in non absolute form
 */
int
ns_name_ntop(const u_char *src, char *dst, size_t dstsiz) {
        const u_char *cp;
        char *dn, *eom;
        u_char c;
        u_int n;

        cp = src;
        dn = dst;
        eom = dst + dstsiz;

        while ((n = *cp++) != 0) {
                if ((n & NS_CMPRSFLGS) != 0) {
                        /* Some kind of compression pointer. */
                        errno = EMSGSIZE;
                        return (-1);
                }
                if (dn != dst) {
                        if (dn >= eom) {
                                errno = EMSGSIZE;
                                return (-1);
                        }
                        *dn++ = '.';
                }
                if (dn + n >= eom) {
                        errno = EMSGSIZE;
                        return (-1);
                }
                for ((void)NULL; n > 0; n--) {
                        c = *cp++;
                        if (special(c)) {
                                if (dn + 1 >= eom) {
                                        errno = EMSGSIZE;
                                        return (-1);
                                }
                                *dn++ = '\\';
                                *dn++ = (char)c;
                        } else if (!printable(c)) {
                                if (dn + 3 >= eom) {
                                        errno = EMSGSIZE;
                                        return (-1);
                                }
                                *dn++ = '\\';
                                *dn++ = digits[c / 100];
                                *dn++ = digits[(c % 100) / 10];
                                *dn++ = digits[c % 10];
                        } else {
                                if (dn >= eom) {
                                        errno = EMSGSIZE;
                                        return (-1);
                                }
                                *dn++ = (char)c;
                        }
                }
        }
        if (dn == dst) {
                if (dn >= eom) {
                        errno = EMSGSIZE;
                        return (-1);
                }
                *dn++ = '.';
        }
        if (dn >= eom) {
                errno = EMSGSIZE;
                return (-1);
        }
        *dn++ = '\0';
        return (dn - dst);
}

/*
 * ns_name_uncompress(msg, eom, src, dst, dstsiz)
 *      Expand compressed domain name to presentation format.
 * return:
 *      Number of bytes read out of `src', or -1 (with errno set).
 * note:
 *      Root domain returns as "." not "".
 */
int
ns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
                   char *dst, size_t dstsiz)
{
        u_char tmp[NS_MAXCDNAME];
        int n;
        
        if ((n = ns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1)
                return (-1);
        if (ns_name_ntop(tmp, dst, dstsiz) == -1)
                return (-1);
        return (n);
}


/*
 * ns_name_unpack(msg, eom, src, dst, dstsiz)
 *      Unpack a domain name from a message, source may be compressed.
 * return:
 *      -1 if it fails, or consumed octets if it succeeds.
 */
int
ns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
               u_char *dst, size_t dstsiz)
{
        const u_char *srcp, *dstlim;
        u_char *dstp;
        int n, len, checked;

        len = -1;
        checked = 0;
        dstp = dst;
        srcp = src;
        dstlim = dst + dstsiz;
        if (srcp < msg || srcp >= eom) {
                errno = EMSGSIZE;
                return (-1);
        }
        /* Fetch next label in domain name. */
        while ((n = *srcp++) != 0) {
                /* Check for indirection. */
                switch (n & NS_CMPRSFLGS) {
                case 0:
                        /* Limit checks. */
                        if (dstp + n + 1 >= dstlim || srcp + n >= eom) {
                                errno = EMSGSIZE;
                                return (-1);
                        }
                        checked += n + 1;
                        *dstp++ = n;
                        memcpy(dstp, srcp, n);
                        dstp += n;
                        srcp += n;
                        break;

                case NS_CMPRSFLGS:
                        if (srcp >= eom) {
                                errno = EMSGSIZE;
                                return (-1);
                        }
                        if (len < 0)
                                len = srcp - src + 1;
                        srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
                        if (srcp < msg || srcp >= eom) {  /* Out of range. */
                                errno = EMSGSIZE;
                                return (-1);
                        }
                        checked += 2;
                        /*
                         * Check for loops in the compressed name;
                         * if we've looked at the whole message,
                         * there must be a loop.
                         */
                        if (checked >= eom - msg) {
                                errno = EMSGSIZE;
                                return (-1);
                        }
                        break;

                default:
                        errno = EMSGSIZE;
                        return (-1);                    /* flag error */
                }
        }
        *dstp = '\0';
        if (len < 0)
                len = srcp - src;
        return (len);
}

/*
 * special(ch)
 *      Thinking in noninternationalized USASCII (per the DNS spec),
 *      is this characted special ("in need of quoting") ?
 * return:
 *      boolean.
 */
static int special(int ch) {
        switch (ch) {
        case 0x22: /* '"' */
        case 0x2E: /* '.' */
        case 0x3B: /* ';' */
        case 0x5C: /* '\\' */
        /* Special modifiers in zone files. */
        case 0x40: /* '@' */
        case 0x24: /* '$' */
                return (1);
        default:
                return (0);
        }
}

/*
 * printable(ch)
 *      Thinking in noninternationalized USASCII (per the DNS spec),
 *      is this character visible and not a space when printed ?
 * return:
 *      boolean.
 */
static int printable(int ch) {
        return (ch > 0x20 && ch < 0x7f);
}
