/**************************************************************************** 
** File: netbios_ns.c
**
** Author: Mike Borella
**
** Dump netbios name service data
**
** $Id: netbios_ns.c,v 1.9 2007/06/25 12:53:31 farooq-i-azam Exp $
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Library General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "global.h"
#include "netbios_ns.h"
#include "ns_labels.h"

#define HOLDER_SIZE 256

extern struct arg_t *my_args;

/*
 * R flag map
 */

strmap_t netbios_ns_r_map [] =
{
  { NETBIOS_NS_R_QUERY,     "query" },
  { NETBIOS_NS_R_RESPONSE,  "response" },
  { 0, "" }
};

/*
 * Opcode map
 */

strmap_t netbios_ns_opcode_map [] =
{
  { NETBIOS_NS_OPCODE_QUERY,         "query" },
  { NETBIOS_NS_OPCODE_REGISTRATION,  "registration" },
  { NETBIOS_NS_OPCODE_RELEASE,       "release" },
  { NETBIOS_NS_OPCODE_WACK,          "WACK" },
  { NETBIOS_NS_OPCODE_REFRESH,       "refresh" },
  { 0, "" }
};

/*
 * Rcode map
 */

strmap_t netbios_ns_rcode_map [] =
{
  { NETBIOS_NS_RCODE_NOERROR,     "no error" },
  { 0, "" }
};

/*
 * Query type map
 */

strmap_t netbios_ns_querytype_map [] =
{
  { NETBIOS_NS_QUERYTYPE_IPADDR,     "A - IP address" },
  { NETBIOS_NS_QUERYTYPE_NAMESERVER, "NS - name server" },
  { NETBIOS_NS_QUERYTYPE_NULL,       "NULL" },
  { NETBIOS_NS_QUERYTYPE_GENERAL,    "NB - general" },
  { NETBIOS_NS_QUERYTYPE_NODESTATUS, "NBSTAT - node status" },
  { 0, "" }
};

/*
 * Query class map
 */

strmap_t netbios_ns_queryclass_map [] =
{
  { NETBIOS_NS_QUERYCLASS_IP,  "Internet" },
  { 0, "" }
};

/*----------------------------------------------------------------------------
**
** translate_netbios_ns_format()
**
** Translate the NETBIOS name format to something that makes sense to humans
**
** We do the reverse of this:
**

                         0 1 2 3 4 5 6 7
                        +-+-+-+-+-+-+-+-+
                        |a b c d|w x y z|          ORIGINAL BYTE
                        +-+-+-+-+-+-+-+-+
                            |       |
                   +--------+       +--------+
                   |                         |     SPLIT THE NIBBLES
                   v                         v
            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
           |0 0 0 0 a b c d|         |0 0 0 0 w x y z|
           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
                   |                         |
                   +                         +     ADD 'A'
                   |                         |
            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
           |0 1 0 0 0 0 0 1|         |0 1 0 0 0 0 0 1|
           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
**----------------------------------------------------------------------------
*/

void translate_netbios_name(char *s)
{
  char output[32];
  int output_ptr = 0;
  int input_ptr = 0;
  int input_length = strlen(s);
  char *ptr;

  while(input_ptr < input_length)
    {
      char high_char, low_char;

      /* get next two chars */
      high_char = s[input_ptr];
      input_ptr ++;
      low_char = s[input_ptr];
      input_ptr ++;

      /* subtract A from each */
      high_char = high_char - 'A';
      low_char = low_char - 'A';

      /* shift the high char left by 4 */
      high_char = high_char << 4;

      /* combine them into the output */
      output[output_ptr] = high_char | low_char;
      output_ptr++;
    }

  /* null terminate, eliminate spaces, and copy back to the input string */
  output[output_ptr] = '\0';
  strcat(output, " ");
  ptr = strchr(output, ' ');
  *ptr = '\0';
  strcpy(s, output);
}

/*----------------------------------------------------------------------------
**
** dump_netbios_ns_questions()
**
** Parse NETBIOS name service questions and display them
**
**----------------------------------------------------------------------------
*/

void dump_netbios_ns_questions(packet_t *pkt, u_int16_t num)
{
  char holder[HOLDER_SIZE];
  u_int16_t query_type, query_class;

  while(num > 0)
    {
      /*
       * Parse the name
       */
      
      parse_ns_labels(pkt, holder);
      
      /* 
       * The name is stored in wacky NETBIOS format, with each 
       * character spread across 2 bytes.  Let's fix that.
       * 
       */
      
      /*
       * Parse the query type and class
       */
      
      if (get_packet_bytes((u_int8_t *) &query_type, pkt, 2) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &query_class, pkt, 2) == 0)
	return;
      
      /*
       * Conversions
       */
      
      query_type = ntohs(query_type);
      query_class = ntohs(query_class);
      
      /* 
       * The name may be stored in wacky NETBIOS format, with each 
       * character spread across 2 bytes.  Let's fix that.
       */

      if (query_type == NETBIOS_NS_QUERYTYPE_GENERAL)
	translate_netbios_name(holder);

      /*
       * Dump the info
       */
      
      if (my_args->m)
	{
	  display_minimal_string(holder);
	  display_minimal_string(" ");
	}
      else
	{
	  display_string("Query", holder); 
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", query_type, 
		   map2str(netbios_ns_querytype_map, query_type));
	  display_string("  Type", holder); 
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", query_class, 
		   map2str(netbios_ns_queryclass_map, query_class));
	  display_string("  Query class", holder); 
	}

      num --;
    }
}


/*----------------------------------------------------------------------------
**
** dump_netbios_ns_answers()
**
** Parse NETBIOS NS answers and display them
**
**----------------------------------------------------------------------------
*/
void dump_netbios_ns_answers(packet_t *pkt, u_int8_t num, char *answer_type)
{
  char holder[HOLDER_SIZE];
  u_int16_t query_type, query_class;
  u_int32_t ttl;
  u_int16_t rdl;
  u_int8_t resource_data[64];
  u_int16_t nb_flags;

  while(num > 0)
    {
      /*
       * Parse the name
       */
      
      parse_ns_labels(pkt, holder);
      
      /*
       * Parse the query type and class
       */
      
      if (get_packet_bytes((u_int8_t *) &query_type, pkt, 2) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &query_class, pkt, 2) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &ttl, pkt, 4) == 0)
	return;
      if (get_packet_bytes((u_int8_t *) &rdl, pkt, 2) == 0)
	return;
      
      /*
       * Conversions
       */
      
      query_type = ntohs(query_type);
      query_class = ntohs(query_class);
      ttl = ntohl(ttl);
      rdl = ntohs(rdl);

      
      /* 
       * The name may be stored in wacky NETBIOS format, with each 
       * character spread across 2 bytes.  Let's fix that.
       */

      if (query_type == NETBIOS_NS_QUERYTYPE_GENERAL)
	translate_netbios_name(holder);

      /*
       * Get the resource data
       */

      switch (query_type)
	{
	case NETBIOS_NS_QUERYTYPE_NAMESERVER:
	  parse_ns_labels(pkt, resource_data);
	  break;

	case NETBIOS_NS_QUERYTYPE_IPADDR:
	case NETBIOS_NS_QUERYTYPE_GENERAL:
	default:
	  if (get_packet_bytes((u_int8_t *) &nb_flags, pkt, 2) == 0)
	    return;
	  if (get_packet_bytes((u_int8_t *) &resource_data, pkt, 4) == 0)
	    return;
	  break;
	  
	}

      /*
       * Dump the info
       */
      
      if (my_args->m)
	{
	  if (query_type == NETBIOS_NS_QUERYTYPE_GENERAL && 
	      query_class == NETBIOS_NS_QUERYCLASS_IP)
	    {
	      display_minimal_ipv4((u_int8_t *) resource_data);
	      display_minimal_string(" ");
	    }
	}
      else
	{
	  display_string(answer_type, holder); 
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", query_type, 
		   map2str(netbios_ns_querytype_map, query_type));
	  display_string("  Query type", holder); 
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", query_class, 
		  map2str(netbios_ns_queryclass_map, query_class));
	  display_string("  Query class", holder); 
	  display("  TTL", (u_int8_t *) &ttl, 4, DISP_DEC);
	  display("  Resource data length", (u_int8_t *) &rdl, 2, DISP_DEC);
	  switch(query_type)
	    {
	    case NETBIOS_NS_QUERYTYPE_IPADDR:
	    case NETBIOS_NS_QUERYTYPE_GENERAL:
	      display("  NB flags", (u_int8_t *) &nb_flags, 2, DISP_HEX);
	      display_ipv4("  Resource data", (u_int8_t *) resource_data);
	      break;

	    case NETBIOS_NS_QUERYTYPE_NAMESERVER:
	      display_string("  Resource data", resource_data);
	      break;
	      
	    default:
	      display("  Resource data", (u_int8_t *) resource_data, rdl, 
		      DISP_HEX);	      
	    }
	}
      
      num --;
    }

}

/*----------------------------------------------------------------------------
**
** dump_netbios_ns()
**
** Parse netbios name service header and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_netbios_ns(packet_t *pkt)
{
  netbios_ns_t nb_ns;
  u_int8_t     holder[64];
  u_int8_t     r, opcode, flags, rcode;
  u_int8_t     flag_aa, flag_tc, flag_rd, flag_ra, flag_zeros, flag_b;
 
  /* Set the layer */
  set_layer(LAYER_APPLICATION);

  /*
   * Mark the beginning of the NS portion so that labels can be stored
   * properly
   */

  set_packet_mark(pkt);

  /*
   * Reset the NS label module
   */

  reset_nslabels();

  /*
   * Read the header
   */

  if (get_packet_bytes((u_int8_t *) &nb_ns, pkt, sizeof(netbios_ns_t)) == 0)
    return;

  /*
   * Conversions
   */

  nb_ns.transaction_id = ntohs(nb_ns.transaction_id);
  r = nb_ns.r;
  opcode = nb_ns.opcode;
  flags = (nb_ns.flags_high << 4) || nb_ns.flags_low;
  flag_aa = (flags & 0x40) >> 6;
  flag_tc = (flags & 0x20) >> 5;
  flag_rd = (flags & 0x10) >> 4;
  flag_ra = (flags & 0x08) >> 3;
  flag_zeros = (flags & 0x06) >> 1;
  flag_b = (flags & 0x01);
  rcode = nb_ns.rcode;
  nb_ns.questions = ntohs(nb_ns.questions);
  nb_ns.answers = ntohs(nb_ns.answers);
  nb_ns.auth_answers = ntohs(nb_ns.auth_answers);
  nb_ns.add_answers = ntohs(nb_ns.add_answers);

  /*
   * Dump header
   */

  if (my_args->m)
    {
      display_minimal_string("| NETBIOS NS ");
      display_minimal_string(map2str(netbios_ns_r_map, r));
      display_minimal_string(" ");
    }
  else
    {
      /* announcement */
      display_header_banner("NETBIOS Name Service");
      
      /* fields */
      display("Transaction ID", (u_int8_t *) &nb_ns.transaction_id, 2,
	      DISP_DEC);
      snprintf(holder, HOLDER_SIZE, "%d (%s)", r, 
	       map2str(netbios_ns_r_map, r));
      display_string("R", holder);
      snprintf(holder, HOLDER_SIZE, "%d (%s)", opcode, 
	       map2str(netbios_ns_opcode_map, opcode));
      display_string("Opcode", holder);
      snprintf(holder, HOLDER_SIZE, "%d (%s)", rcode, 
	       map2str(netbios_ns_rcode_map, rcode));
      display_string("Rcode", holder);
      
      /* number of questions and answers */
      display("Questions", (u_int8_t *) &nb_ns.questions, 2, DISP_DEC);
      display("Answers", (u_int8_t *) &nb_ns.answers, 2, DISP_DEC);
      display("Auth answers", (u_int8_t *) &nb_ns.auth_answers, 2, DISP_DEC);
      display("Add answers", (u_int8_t *) &nb_ns.add_answers, 2, DISP_DEC);
    }
  
  /*
   * Parse the question and answers
   */

  dump_netbios_ns_questions(pkt, nb_ns.questions);
  dump_netbios_ns_answers(pkt, nb_ns.answers, "Answers");
  dump_netbios_ns_answers(pkt, nb_ns.auth_answers, "Auth answers");
  dump_netbios_ns_answers(pkt, nb_ns.add_answers, "Addtl answers");
  
  /* dump the hex buffer */
  hexbuffer_flush();
  
}

