/**************************************************************************** 
** File: dns.c
**
** Author: Mike Borella
**
** Comments: Dump DNS header information
**
** $Log: dns.c,v $
** Revision 1.2  1998/06/12 21:00:59  mborella
** Added log tag
**
*****************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include "config.h"
#include "dns.h"

extern u_char *packet_end;

/*----------------------------------------------------------------------------
**
** dump_dns()
**
** Parse DNS packet and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_dns(u_char *bp, int length)
{
  u_char *ep = bp + length;
  u_char *p;
  DNSHdr dns_fixed;
  int i, t;
  u_int16_t qt, qc;
  char *dns_query_type(u_int16_t);
  u_char *dump_rr(u_char *, u_char *, u_char *);
  u_char *parse_labels(u_char *, u_char *, u_char *);
#ifdef DEBUG
  void dump_ascii(u_char *, u_char *);
#endif

  /*
   * Make sure we don't run off the end of the packet
   */

  if (ep > packet_end) 
    ep = packet_end;

  
  /*
   * Overlay the first 12 bytes, which are fixed
   */

  memcpy((void *) &dns_fixed, bp, sizeof(dns_fixed));


  /*
   * Print it
   */

  printf("-----------------------------------------------------------------\n");
  printf("                        DNS Packet\n");
  printf("-----------------------------------------------------------------\n");
  printf("Identification:         %d\n", ntohs(dns_fixed.dns_id));

  printf("Flags: query/response:  %d ", dns_fixed.dns_fl_qr);
  if (dns_fixed.dns_fl_qr == 0)
    printf("(query)\n");
  else
    printf("(response)\n");
  printf("       opcode           %d ", dns_fixed.dns_fl_opcode);
  switch(dns_fixed.dns_fl_opcode)
    {
    case 0: printf("(standard)\n");
      break;
    case 1: printf("(inverse)\n");
      break;
    case 2: printf("(server status)\n");
      break;
    }
  printf("       auth answer      %d\n", dns_fixed.dns_fl_aa);
  printf("       truncated        %d\n", dns_fixed.dns_fl_tc);
  printf("       recursion req    %d\n", dns_fixed.dns_fl_rd);
  printf("       recursion avail  %d\n", dns_fixed.dns_fl_ra);
  printf("       zero             %d\n", dns_fixed.dns_fl_zero);
  printf("       return code      %d ", dns_fixed.dns_fl_rcode);
  switch(dns_fixed.dns_fl_rcode)
    {
    case 0: printf("(no error)\n");
      break;
    case 1: printf("(format error)\n");
      break;
    case 2: printf("(server error)\n");
      break;
    case 3: printf("(name error)\n");
      break;
    case 4: printf("(not implemented)\n");
      break;
    case 5: printf("(service refused)\n");
      break;
    }

  printf("# of questions          %d\n", ntohs(dns_fixed.dns_num_q));
  printf("# of answer RRs         %d\n", ntohs(dns_fixed.dns_num_ans));
  printf("# of authorization RRs  %d\n", ntohs(dns_fixed.dns_num_auth));
  printf("# of additional RRs     %d\n", ntohs(dns_fixed.dns_num_add));

  /*
   * Do the question part of the packet. 
   */

  p = bp + sizeof(DNSHdr);
  i = ntohs(dns_fixed.dns_num_q);

  while (i > 0)
    {
      printf("Question: query name    ");
      p = parse_labels(p, bp, ep);
      
      memcpy((void *) &qt, p, sizeof(qt)); 
      p = p + sizeof(qt);
      memcpy((void *) &qc, p, sizeof(qc));
      p = p + sizeof(qc);
      printf("          query type    %d %s\n", ntohs(qt), 
	     dns_query_type(ntohs(qt)));
      printf("          query class   %d", ntohs(qc));
      if (ntohs(qc) == 1)
	printf(" (Internet)");
      printf("\n");
      i--;
    }

  /*
   * dump the resource records for the answers
   */ 

  i = ntohs(dns_fixed.dns_num_ans);
  t = i;
  while (i > 0)
    {
      printf("Answer %d: ", t-i+1);
      p = dump_rr(p, bp, ep);
      i--;
    }

  /*
   * dump the resource records for the authoritative answers
   */ 

  i = ntohs(dns_fixed.dns_num_auth);
  t = i;
  while (i > 0)
    {
      printf("Auth %d:   ", t-i+1);
      p = dump_rr(p, bp, ep);
      i--;
    }

  /*
   * dump the resource records for the additional info
   */ 

  i = ntohs(dns_fixed.dns_num_add);
  t = i;
  while (i > 0)
    {
      printf("Adtnl %d:  ", t-i+1);

#ifdef DEBUG
      dump_ascii(p, ep);
#endif

      p = dump_rr(p, bp, ep);
      i--;
    }

  
}


/*----------------------------------------------------------------------------
**
** dns_query_type()
**
** Return a string describing the numeric value of a DNS query type
**
**----------------------------------------------------------------------------
*/

char *dns_query_type(u_int16_t t)
{
  static char answer[32];

  switch(t)
    {
    case 1: strncpy(answer, "(IP address)", 32);
      break;
    case 2: strncpy(answer, "(name server)", 32);
      break;
    case 5: strncpy(answer, "(canonical name)", 32);
      break;
    case 12: strncpy(answer, "(pointer record)", 32);
      break;
    case 13: strncpy(answer, "(host info)", 32);
      break;
    case 15: strncpy(answer, "(MX record)", 32);
      break;
    case 252: strncpy(answer, "(request zone transfer)", 32);
      break;
    case 255: strncpy(answer, "(request all records)", 32);
      break;
    default: answer[0] = '\0';
      break;
    }

  return answer;

}


/*----------------------------------------------------------------------------
**
** dump_rr()
**
** Print the contents of a resource record
**
**----------------------------------------------------------------------------
*/

u_char *dump_rr(u_char *p, u_char *bp, u_char *ep)
{
  int i;
  u_int16_t qt, qc;
  u_int32_t ttl;
  u_int16_t reslen;
  struct in_addr ipaddr;
  u_char *parse_labels(u_char *, u_char *, u_char *);

  printf("server name   ");
  p = parse_labels(p, bp, ep);

  /*
   * Do type and class
   */

  memcpy((void *) &qt, p, sizeof(qt)); 
  p = p + sizeof(qt);
  memcpy((void *) &qc, p, sizeof(qc));
  p = p + sizeof(qc);
  printf("          type          %d %s\n", ntohs(qt), 
	 dns_query_type(ntohs(qt)));
  printf("          class         %d", ntohs(qc));
  if (ntohs(qc) == 1)
    printf(" (Internet)");
  printf("\n");

  /*
   * Do TTL
   */

  memcpy((void *) &ttl, p, sizeof(ttl));
  p = p + sizeof(ttl);
  printf("          ttl           %d seconds\n", ntohs(ttl));

  /*
   * Do resource length
   */

  memcpy((void *) &reslen, p, sizeof(reslen));
  p = p + sizeof(reslen);
  printf("          length        %d\n", ntohs(reslen));

  /*
   * Do resource data.  
   */

  switch(ntohs(qt))
    {
    case 1:
      for (i=1; i<= ntohs(reslen); i += 4)
	{
	  memcpy((void *) &ipaddr, p, sizeof(ipaddr));
	  p = p + sizeof(ipaddr);
	  printf("          IP address    %s\n", inet_ntoa(ipaddr));
	}
      break;

    case 2:
      printf("          auth host     ");
      p = parse_labels(p, bp, ep);
      break;

    case 5:
      printf("          canon host    ");
      p = parse_labels(p, bp, ep);
      break;

    default:
      p = p + ntohs(reslen);
    }

  return p;
}


/*----------------------------------------------------------------------------
**
** parse_labels()
**
** Recursively parse a label entry in a DNS packet
**
**----------------------------------------------------------------------------
*/

u_char *parse_labels(u_char *p, u_char *bp, u_char *ep)
{
  u_int8_t count;
  u_int16_t offset;

  while(1)
    {
      count = (u_int8_t) *p;      
      if (count >= 192)
	{
	  /*
	   * There's a pointer in this label.  Sigh.  Let's grab the 
	   * 14 low-order bits and run with them...
	   */
	  
	  p++;
	  offset = count - 192;
	  offset = offset << 8;
	  offset = offset + *p;
	  p++;
	  parse_labels(bp+offset, bp, ep);
	  return p;
	}
      else 
	  p++;

      if (count == 0) 
	break;
      while (count > 0)
	{
	  if (p <= ep)
	    printf("%c", *p);
	  else
	    {
	      printf("\nPacket length exceeded\n");
	      return p;
	    }
	  p++;
	  count--;
	}
      printf(".");
    }
  printf("\n");
  
  return p;
}




