/**************************************************************************** 
** File: tftp.c
**
** Author: Mike Borella
**
** Comments: TFTP protocol module.
**
** $Id: tftp.c,v 1.1 2001/10/09 23:20:42 mborella 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 "tftp.h"
#include "dynports.h"

extern struct arg_t * my_args;

#define FILENAME_SIZE             128
#define MODE_SIZE                 64

/* TFTP opcodes */
#define TFTP_OPCODE_READ          1
#define TFTP_OPCODE_WRITE         2
#define TFTP_OPCODE_DATA          3
#define TFTP_OPCODE_ACK           4
#define TFTP_OPCODE_ERROR         5

/* TFTP opcode map */
strmap_t tftp_opcode_map[] =
{
  { TFTP_OPCODE_READ,           "read request" },
  { TFTP_OPCODE_WRITE,          "write request" },
  { TFTP_OPCODE_DATA,           "data" },
  { TFTP_OPCODE_ACK,            "acknowledgement" },
  { TFTP_OPCODE_ERROR,          "error" },
  { 0, ""}
};

/*----------------------------------------------------------------------------
**
** dump_tftp()
**
** Parse and display TFTP packets
**
**----------------------------------------------------------------------------
*/

void dump_tftp(packet_t *pkt)
{
  u_int16_t opcode;
  struct timeval t;

  /* Set the layer */
  set_layer(LAYER_APPLICATION);

  /* Get the opcode */
  if (get_packet_bytes((u_int8_t *) &opcode, pkt, 2) == 0)
    return;

  /* Conversions */
  opcode = ntohs(opcode);

  /* Announcement */
  if (!my_args->m)
    display_header_banner("TFTP");
  else
    display_minimal_string("| TFTP ");

  switch(opcode)
    {
    case TFTP_OPCODE_READ:
    case TFTP_OPCODE_WRITE:
      {
	u_int8_t filename [FILENAME_SIZE];
	u_int8_t mode [MODE_SIZE];

	/* read the filename and mode */
	if (get_packet_string(filename, FILENAME_SIZE, pkt) == 0)
	  return;
	if (get_packet_string(mode, MODE_SIZE, pkt) == 0)
	  return;
	
	if (my_args->m)
	  {
	    display_minimal_string(map2str(tftp_opcode_map, opcode));
	    display_minimal_string(" (");
	    display_minimal_string(filename);
	    display_minimal_string(",");
	    display_minimal_string(mode);
	    display_minimal_string(")");
	  }
	else
	  {
	    display_strmap("Opcode", opcode, tftp_opcode_map);
	    display_string("Filename", filename);
	    display_string("Mode", mode);
	  }

	/* set up a dynamic port binding to the source port */
	gettimeofday(&t, NULL);
	dynports_add(state_get_srcport(), dump_tftp, t.tv_sec);
      }
      break;

    case TFTP_OPCODE_DATA:
      {
	u_int16_t  block;
	u_int8_t * data;
	u_int32_t  n;

	/* refresh up a dynamic port binding to the source and dest port */
	dynports_refresh(state_get_srcport(), dump_tftp);
	dynports_refresh(state_get_dstport(), dump_tftp);

	/* read the block # */
	if (get_packet_bytes((u_int8_t *) &block, pkt, 2) == 0)
	  return;

	/* conversions */
	block = ntohs(block);

	/* find out length of data field */
	n = get_packet_apparentbytesleft(pkt);
	if (n <= 0)
	  return;

	/* Allocate memory */
	data = my_malloc(n+1);

	/* read data portion */
	if (get_packet_bytes(data, pkt, n) == 0)
	  return;

	if (my_args->m)
	  {
	    display_minimal_string(map2str(tftp_opcode_map, opcode));
	    display_minimal_string(" block ");
	    display_minimal((u_int8_t *) &block, 2, DISP_DEC);
	  }
	else
	  {
	    display_strmap("Opcode", opcode, tftp_opcode_map);
	    display("Block", (u_int8_t *) &block, 2, DISP_DEC);
	    display("Data", data, n, DISP_HEX_MULTILINE);
	  }
	
	/* free memory for data */
	my_free(data);
      }
      break;
      
    case TFTP_OPCODE_ACK:
      {
	u_int16_t  block;

	/* refresh up a dynamic port binding to the source and dest port */
	dynports_refresh(state_get_srcport(), dump_tftp);
	dynports_refresh(state_get_dstport(), dump_tftp);

	/* read the block # */
	if (get_packet_bytes((u_int8_t *) &block, pkt, 2) == 0)
	  return;

	/* conversions */
	block = ntohs(block);

	if (my_args->m)
	  {
	    display_minimal_string(map2str(tftp_opcode_map, opcode));
	    display_minimal_string(" block ");
	    display_minimal((u_int8_t *) &block, 2, DISP_DEC);
	  }
	else
	  {
	    display_strmap("Opcode", opcode, tftp_opcode_map);
	    display("Block", (u_int8_t *) &block, 2, DISP_DEC);
	  }
      }
      break;

    case TFTP_OPCODE_ERROR:
      {
	u_int16_t  errorcode;
	u_int8_t * msg;
	u_int32_t  n;

	/* refresh up a dynamic port binding to the source and dest port */
	dynports_refresh(state_get_srcport(), dump_tftp);
	dynports_refresh(state_get_dstport(), dump_tftp);

	/* read the block # */
	if (get_packet_bytes((u_int8_t *) &errorcode, pkt, 2) == 0)
	  return;

	/* conversions */
	errorcode = ntohs(errorcode);

	/* find out length of msg field */
	n = get_packet_apparentbytesleft(pkt);
	if (n <= 0)
	  return;

	/* Allocate memory */
	msg = my_malloc(n+1);

	/* read data portion */
	if (get_packet_string(msg, n, pkt) == 0)
	  return;

	if (my_args->m)
	  {
	    display_minimal_string(map2str(tftp_opcode_map, opcode));
	    display_minimal_string(" (");
	    display_minimal((u_int8_t *) &errorcode, 2, DISP_DEC);
	    display_minimal_string(",");
	    display_minimal_string(msg);
	    display_minimal_string(")");
	  }
	else
	  {
	    display_strmap("Opcode", opcode, tftp_opcode_map);
	    display("Error", (u_int8_t *) &error, 2, DISP_DEC);
	    display_string("Message", msg);
	  }
	
	/* free memory for data */
	my_free(msg);
      }
      break;

    default:
      break;
    }
  
  /* dump the hex buffer */
  hexbuffer_flush();

}
