/**************************************************************************** 
**
** File: file.c
**
** Author: Mike Borella
**
** Comments: Support for various trace file formats.  Currently
** we support:
** - Libpcap
** - Sun Snoop
**
** $Id: file.c,v 1.5 2007/06/25 13:09:56 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 <pcap.h>
#include "file.h"
#include "error.h"
#include "parse_cl.h"
#include "ipgrab.h"
#include "datalink.h"
#include "utilities.h"

#define FILE_PCAP_SIG   0xa1b2c3d4
#define FILE_PCAP_SIG2  0xa1b2cd34

#define FILE_SNOOP_SIG1 0x736e
#define FILE_SNOOP_SIG2 0x6f6f
#define FILE_SNOOP_SIG3 0x7000
#define FILE_SNOOP_SIG4 0x0000

/* 
 * Trace file formats 
 */

typedef enum file_
  {
    FILE_TYPE_NULL = 0,           /* No file type yet assigned */
    FILE_TYPE_PCAP,               /* Libpcap */
    FILE_TYPE_SNOOP,              /* Sun Snoop */
    FILE_TYPE_UNKNOWN             /* File type unknown */
  } file_t;

/*
 * Snoop packet header format
 *
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                        Original Length                        |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                        Included Length                        |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                      Packet Record Length                     |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                        Cumulative Drops                       |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                       Timestamp Seconds                       |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                     Timestamp Microseconds                    |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 */

typedef struct snoop_packet_header
{
  u_int32_t orig_len;
  u_int32_t inc_len;
  u_int32_t rec_len;
  u_int32_t drops;
  u_int32_t ts_sec;
  u_int32_t ts_usec;
} snoop_packet_header_t;

static file_t               file_type = FILE_TYPE_NULL;

extern char *               pcap_cmd;

static pcap_t *             pd;
static struct bpf_program   fcode;
static char                 errorbuf[PCAP_ERRBUF_SIZE];
static int                  mylink;	
static pcap_dumper_t *      p;   
static FILE *               fp;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** file_id()
**
** Attempt to identify a file format by looking at the first few bytes. 
** Most trace file formats begin with a distinctive signature.  
** Libpcap:    a1b3 c3d4 (1 32 bit little endian word)
** Sun Snoop:  736e 6f6f 7000 0000 0000 (5 16 bit big endian words)
** 
** This function returns 1 on successful identification and 0 otherwise.
**
**----------------------------------------------------------------------------
*/

int file_id(char *s)
{
  FILE *    fp;
  u_int32_t word32;
  u_int16_t word16[4];
  int       ret;

  /* Open the file */
  fp = fopen(s, "r");
  if (fp == NULL)
    error_system("can't open %s", s);

  /* Check against libpcap signature */
  ret = fread((void *) &word32, 4, 1, fp);
  if (ret < 1)
    error_fatal("can't read file format signature from %s", s);

#ifdef WORDS_BIGENDIAN
  reverse_byte_order((u_int8_t *) &word32, 4);
#endif

  if (word32 == FILE_PCAP_SIG || word32 == FILE_PCAP_SIG2)
    {
      file_type = FILE_TYPE_PCAP;
      fclose (fp);
      return 1;
    }

  /* Check against Sun Snoop signature (first 3 bytes should be enough) */
  rewind(fp); /* Go to beginning of the file */
  ret = fread((void *) &word16, 2, 4, fp);
  if (ret < 4)
    error_fatal("can't read file format signature from %s", s);
  if (ntohs(word16[0]) == FILE_SNOOP_SIG1 && 
      ntohs(word16[1]) == FILE_SNOOP_SIG2 && 
      ntohs(word16[2]) == FILE_SNOOP_SIG3 &&
      ntohs(word16[3]) == FILE_SNOOP_SIG4)
    {
      file_type = FILE_TYPE_SNOOP;
      fclose (fp);
      return 1;
    }

  /* Can't ID the file, return an error */
  file_type = FILE_TYPE_UNKNOWN;
  fclose(fp);
  return 0;
}

/*----------------------------------------------------------------------------
**
** file_open()
**
** Open an ID'd file
**
**----------------------------------------------------------------------------
*/

int file_open(char *s)
{
  fprintf(stderr, "Reading from file %s", s);

  switch (file_type)
    {
    case FILE_TYPE_PCAP:
      {
	fprintf(stderr, " (libpcap)\n");
	pd = pcap_open_offline(s, errorbuf);
	if (pd == NULL)
	  error_fatal("%s", errorbuf);
	  
	/* Compile command line filter spec info fcode FSM */
	if (pcap_compile(pd, &fcode, pcap_cmd, 0, 0) < 0)
	  error_fatal("pcap_compile: %s", pcap_geterr(pd));

	/* Set the pcap filter with our fcode FSM.  That should do it... */
	if (pcap_setfilter(pd, &fcode) < 0)
	  error_fatal("pcap_setfilter: %s", pcap_geterr(pd));

	/* Get the data link type */
	mylink = pcap_datalink(pd);
	if (mylink < 0) 
	  error_fatal("pcap_datalink: %s", pcap_geterr(pd));
	    
	switch(mylink)
	{
	case DLT_NULL:
	  fprintf(stderr,"(loopback)\n");
	  break;
	  
	case DLT_EN10MB:
	  fprintf(stderr, "(ethernet)\n");
	  break;
	  
	case DLT_SLIP:
	  fprintf(stderr, "(slip)\n");
	  break;
	  
#ifdef DLT_RAW /* Not supported in some arch or older pcap versions */
	case DLT_RAW:
	  fprintf(stderr, "(raw)\n");
	  break;
#endif
	  
	case DLT_PPP:
	  fprintf(stderr, "(ppp)\n");
	  break;
	  
	default:
	  error_fatal("\n cannot handle data link type %d", mylink);
	}
      
	/* Open the file for writing if -w is used */
	if (my_args->w)
	  {
	    p = pcap_dump_open(pd, my_args->w);
	    if (p == NULL)
	      error_system("pcap_dump_open: %s", pcap_geterr(pd));
	  }

	/* Return to the caller w/o reading a packet */
	return 1;
      }
      break;
 
    case FILE_TYPE_SNOOP:
      {
	u_int32_t version;
	int       ret;

	/* Open the file */
	fp = fopen(s, "r");
	if (fp == NULL)
	  error_system("can't open %s", s);
	
	/* Get the first four bytes which are the signature, throw them out */
	fseek(fp, 8, SEEK_SET);

	/* Get the version number */
	ret = fread((void *) &version, 4, 1, fp);
	if (ret < 1)
	  error_fatal("can't read version number from %s", s);
	version = ntohl(version);
	fprintf(stderr, " (snoop v%d)\n", version);

	return 1;
      }
      break;

    default:
      error_fatal("trying to open invalid file %s", my_args->r);
    }

  return 0;
}
/*----------------------------------------------------------------------------
**
** file_read()
**
** Read packets from a file.  The cnt parameter is the number of packets to 
** read.
**
**----------------------------------------------------------------------------
*/

int file_read(int cnt)
{
  
  switch (file_type)
    {
    case FILE_TYPE_PCAP:
      {
	/* Make sure that the file is open */
	if (pd == NULL)
	  error_fatal("trying to read from unopened file %s", my_args->r);
	
	if (my_args->w)
	  {
	    /* Read the specified number of packets */
	    if (pcap_loop(pd, cnt, pcap_dump, (u_char *) p) < 0)
	      error_fatal("pcap_loop: %s", pcap_geterr(pd));
	  }
	else
	  {
	    /* Read until cnt packets read */
	    if (pcap_loop(pd, cnt, (pcap_func_t) datalink_pcap, 
			  (u_char *) &mylink) < 0)
	      error_fatal("pcap_loop: %s", pcap_geterr(pd));
	}
      }
      break;
 
    case FILE_TYPE_SNOOP:
      {
	u_int32_t             linktype;
	int                   ret;
	snoop_packet_header_t header;
	u_int8_t *            packet;
	int                   pad_len;
	struct timeval        tv;

	/* Make sure that the file is open */
	if (fp == NULL)
	  error_fatal("trying to read from unopened file %s", my_args->r);

	/* Read the datalink type */
	ret = fread((void *) &linktype, 4, 1, fp);
	if (ret < 1)
	  error_fatal("can't read link type from %s", my_args->r);
	linktype = ntohl(linktype);
	
	/* Translate the link type to local definitions */
	switch(linktype)
	  {
	  case 4:
	    linktype = DATALINK_TYPE_ETHERNET;
	    break;
	  default:
	    /* We really can handle linktypes better but I'm lazy */
	    error_fatal("unsupported snoop link type %d", linktype);
	  }
   
	/* Loop to read individual packets */
	while(1)
	  {
	    /* Read the header */
	    ret = fread((void *) &header, sizeof(snoop_packet_header_t), 1, 
			fp);
	    if (ret < 1)
	      {
		if (feof(fp))
		  break;
		else
		  error_fatal("can't read packet header from %s", my_args->r);
	      }

	    /* Do conversions */
	    header.orig_len = ntohl(header.orig_len);
	    header.inc_len  = ntohl(header.inc_len);
	    header.rec_len  = ntohl(header.rec_len);
	    header.drops    = ntohl(header.drops);
	    header.ts_sec   = ntohl(header.ts_sec);
	    header.ts_usec  = ntohl(header.ts_usec);

	    /* Get the actual packet */
	    packet = my_malloc(header.inc_len+1);
	    ret = fread((void *) packet, header.inc_len, 1, fp);
	    if (ret < 1)
	      {
		if (feof(fp))
		  break;
		else
		  error_fatal("can't read packet from %s", my_args->r);	    
	      }

	    /* Calculate the pad length, then move the pointer */
	    pad_len = header.rec_len - header.inc_len - 24;
	    fseek(fp, pad_len, SEEK_CUR);

	    /* Set up the timeval */
	    tv.tv_sec = header.ts_sec;
	    tv.tv_usec = header.ts_usec;

	    /* Call the datalink() function */
	    datalink(linktype, tv, header.inc_len, header.inc_len, packet);
	    
	    /* Deallocate memory */
	    my_free(packet);
	  }

	return 1;
      }
      break;

    default:
      error_fatal("trying to read invalid file %s", my_args->r);
    }

  return 0;
}
