/*
 * TCPVIEW
 *
 * Author:	Martin Hunt
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: martinh@cac.washington.edu
 *
 *
 * Copyright 1992 by the University of Washington
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  This software is made
 * available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#ifndef lint
static char rcsid[] =
    "@(#) $Header: /usr/staff/martinh/tcpview/RCS/sniffer_file.c,v 1.3 1993/04/22 20:35:19 martinh Exp $ (UW)";
#endif

/*
 * routines to write and playback a Network General "Sniffer" compatible file
 * through tcpdump.  Most of the information about Sniffer files came from the
 * Sniffer documentation.  Some was gained by squinting at hex dumps.
 * 
 * This code is brought to you by Corey Satten, corey@cac.wasnington.edu
 * and Martin Hunt, martinh@cac.washington.edu.  The ifdefs are optional 
 * and for speed, of course.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <math.h>
#include <string.h>
#include <net/bpf.h>
#include "version.h"
#include "savefile.h"

#define MAXPKT 1600				/* >= any ether pkt */

static short byte_order = 0x100;
static int low_offset;				/* where is low byte in short */
static int high_offset;				/* where is hi byte in short */

/*
 * The following moves byte-order determination from runtime to compile-time
 * if known.  Don't worry, if you don't know, it will still work.
 */
#if defined(vax)||defined(mips)||defined(i386)
# define LOW_OFFSET 0
# define HIGH_OFFSET 1
#endif
#if defined(mc68000)||defined(sparc)
# define LOW_OFFSET 1
# define HIGH_OFFSET 0
#endif
#if !defined(HIGH_OFFSET)||!defined(LOW_OFFSET)
# define LOW_OFFSET low_offset
# define HIGH_OFFSET high_offset
#endif

/*
 * x should be of static storage class.  If HIGH/LOW_OFFSET is constant,
 * these should result in compile-time access to the proper byte.
 */
#define low(x) (*(((char *)&(x))+LOW_OFFSET))
#define high(x) (*(((char *)&(x))+HIGH_OFFSET))
#define putbs(x,f) putc(low(x),f); putc(high(x),f)
#define getbs(x,f) low(x)=getc(f); high(x)=getc(f)

static void hdr_load(), hdr_print(), dh_print(), ver_print(), dh_load();

static FILE *sfd;		/* sniffer output file descriptor */
static FILE *rfd;		/* sniffer input file descriptor  */

static int TimeZoneOffset;	/* number of seconds from GMT */

static int frame_count = 0;	/* number of records written to sniffer file */
static long last_offset = 0;	/* position of start of last complete record */

static char s_magic[] = {	/* magic header */
'T', 'R', 'S', 'N', 'I', 'F', 'F', ' ', 'd', 'a', 't', 'a', ' ', ' ', ' ', ' ',
0x1a };
 
/*
 * putting the two structures all in one big structure allows us to
 * fwrite the entire structure at once for a substantial speedup on
 * little-endian machines (vax, etc)
 */
static struct s {
  struct s_hdr {			/* data record header */
    short type;
    short len;
    short rsvd;
  } s_hdr;
  
  struct s_dh {			/* record data header */
    ushort time_low;
    ushort time_med;
    ushort time_high;
    short  size;
    char   fs;
    char   flags;
    short  truesize;
    short  rsvd;
  } s_dh;
  
} s = { {4, 0, 0}, {0, 0, 0, 0, 0, 0x1c, 0, 0} };

static struct v {
  short maj_vers;     /* major version number */
  short min_vers;     /* minor version number */
  short time;         
  short date;
  char type;
  char network;       /* network type */
  char format;
  char timeunit;      /* timestamp units */
  short rsvd[3];      /* reserved */
} V;

/* values for V.timeunit */
double Usec[] = { 15.0, 0.838096, 15.0, 0.5, 2.0 };
double Timeunit;

/* initializes reading.  
** returns SFERR_BADF if incorrect file type, -1 on open failure, 0 otherwise
*/
int
sniff_rinit(name, linktypep, thiszonep, snaplenp, precisionp)
char *name;
int *linktypep, *thiszonep, *snaplenp, *precisionp;
{
  char buf[128];

  void print2hexwindow();

  /* Sniffer saves times in local format */
  *thiszonep = 0;

  low_offset = *(((char *)&(byte_order))+0);
  high_offset = *(((char *)&(byte_order))+1);

  /* the Sniffer file format doesn't give the max frame size */
  /* Each frame has a true size and a stored size, so we */
  /* have to set 'snaplen' to the max possible size */
  *snaplenp = MAXPKT;		

  if (name[0]=='-' && name[1]=='\0')
    rfd = stdin;
  else if ( !(rfd=fopen(name, "r")) ) {
    fprintf(stderr,"tcpdump: fopen: ");
    perror(name);
    return(1);
  }

  /* read in magic header */
  fread(buf,sizeof(s_magic),1,rfd);
  if(strncmp(buf,s_magic,sizeof(s_magic)))
    return SFERR_BADF;  /* no magic header found */

  hdr_load();
  if (s.s_hdr.type != 1 )       /* version record should be next */
    return SFERR_BADF;
  getbs(V.maj_vers, rfd);
  getbs(V.min_vers, rfd);
  getbs(V.time, rfd);
  getbs(V.date, rfd);
  V.type = getc(rfd);
  V.network = getc(rfd);
  V.format = getc(rfd);
  V.timeunit = getc(rfd);
  getbs(V.rsvd[0], rfd);
  getbs(V.rsvd[1], rfd);
  getbs(V.rsvd[2], rfd);
  
  sprintf(buf,"Sniffer save file\nCreated by Version %d.%d on %d-%d-%d at %d:%d:%d",
	 V.maj_vers,V.min_vers,
	 ((V.date&0x1e0)>>5),(V.date&0x1f),
	 ((V.date&0xfe00)>>9)+1980,
	 (V.time&0xfc00)>>11,
	 (V.time&0x7e0)>>5,
	 (V.time&0x1f)<<1);
#ifdef TCPVIEW
  print2hexwindow(buf);
#else
  fputs(buf,stderr);
#endif

  if(V.timeunit==2)
    *precisionp = 5;
  else
    *precisionp = 6;
  
  Timeunit = Usec[V.timeunit];
  
  if (linktypep) {
    switch (V.network) {
    case 1:
      *linktypep = DLT_EN10MB;
      break;
    case 2:
      *linktypep = DLT_ARCNET;
      break;
    case 7:
      *linktypep = DLT_PPP;
      break;
    case 8:
      *linktypep = DLT_SLIP;
      break;
    default:
      *linktypep = 0;
    }
  }
  return 0;
}

int sniff_winit(name, linktype, thiszone, snaplen, precision)
char *name;
int linktype, thiszone, snaplen, precision;
{
  time_t tt;
  struct tm *t;

  TimeZoneOffset = thiszone;

  low_offset = *(((char *)&(byte_order))+0);
  high_offset = *(((char *)&(byte_order))+1);

  frame_count = 0;

  if (name[0]=='-' && name[1]=='\0')
    sfd = stdout;
  else if ( !(sfd=fopen(name ? name : "snif.enc", "w")) ) {
    fprintf(stderr,"tcpdump: fopen: ");
    perror(name);
#ifdef TCPVIEW
    return(1);
#else
    exit(1);
#endif
  }

  /* write the magic header */
  fwrite(s_magic, sizeof(s_magic), 1, sfd);

  /* write a version header */
  s.s_hdr.type = 1;
  s.s_hdr.len = 18;
  hdr_print();

  /* write the version record */
  V.maj_vers = 3;
  V.min_vers = 9;
  (void)time( &tt );
  t = localtime( &tt );
  V.time = t->tm_sec>>1 | t->tm_min<<5 | t->tm_hour<<11;
  V.date = t->tm_mday | (t->tm_mon+1)<<5 | (t->tm_year-80)<<9;
  V.type = (char)4;
  
  switch ( linktype ) {
  case DLT_EN10MB:
    V.network = (char)1;
    break;
  case DLT_ARCNET:
    V.network = (char)2;
    break;
  case DLT_SLIP:
    V.network = (char)7;
    break;
  case DLT_PPP:
    V.network = (char)8;
    break;
  default:
    V.network = (char)9;
    break;
  }
  V.format = (char)1;
  if( precision > 5)
    V.timeunit = (char)4;
  else
    V.timeunit = (char)2;
  Timeunit = Usec[V.timeunit];
  ver_print();
  
  /* write fake type 6 record */
  /*    fwrite(s_sixrec, sizeof(s_sixrec), 1, sfd);
  last_offset = sizeof(s_magic) + sizeof(s_sixrec) + sizeof(struct v) + 6; */

  last_offset = sizeof(s_magic) + sizeof(struct v) + 6;
  s.s_hdr.type = 4;
  return 0;
}

void sniff_end(mode)
char *mode;
{
  if (*mode == 'w') {	/* end writing sniffer file */
    s.s_hdr.type = 3;	/* EOF */
    s.s_hdr.len = 0;
    fseek(sfd, last_offset, 0);	/* undo any partial records */
    hdr_print();
    fclose(sfd);
    fprintf(stderr,"wrote %d sniffer frames\n", frame_count); 
  }
  if (*mode == 'r') {	/* end re-reading a sniffer file */
    fclose(rfd);
  }
}

void
sniff_write(sp, tvp, length, caplen)
     u_char *sp;
     struct timeval *tvp;
     int length;
     int caplen;
{
  double t, x;
  int days;
  extern int Wflag;

  tvp->tv_sec += TimeZoneOffset;
  x = 4.0*(double)(1<<30);

  t = (double)(tvp->tv_sec)*1.0e6 + (double)(tvp->tv_usec);
  days = t/8.64e10;
  t -= days*8.64e10;

  t /= Timeunit;
  s.s_dh.time_high = (u_short)(t/x);
  t -= (double)(s.s_dh.time_high)*x;
  s.s_dh.time_med = (u_short)(t/(double)(1<<16));
  s.s_dh.time_low = (u_short)(t - (double)(s.s_dh.time_med)*(double)(1<<16));
  s.s_hdr.len = (s.s_dh.size = caplen) + 14;
  s.s_dh.truesize = s.s_dh.size < length ? length : 0;
  
  if (HIGH_OFFSET)
    fwrite(&s, sizeof(s), 1, sfd);
  else
    hdr_print(), dh_print();
  fwrite(sp, s.s_dh.size, 1, sfd);
  last_offset += sizeof(s.s_dh) + sizeof(s.s_hdr) + s.s_dh.size;
  ++frame_count;

#ifndef TCPVIEW
  switch (Wflag) {
  case 2:                         /* a dot to stderr */
    write(2,".",1);
    break;
  case 3:                         /* the normal print too */
    ether_if_print(sp, tvp, length, caplen);
    break;
  }
#endif

  return;
}

#ifndef TCPVIEW

int sniff_read(filtp, cnt, snaplen, printit)
struct bpf_program *filtp;
int cnt, snaplen;
void (*printit)();
{
  struct packet_header h;
  u_char *buf;
  struct bpf_insn *fcode = filtp->bf_insns;

  buf = (u_char *)malloc(snaplen);
  if (buf==NULL)
    return 1;

  while(sniff_next_packet(&h, buf) == 0) {
    if (bpf_filter(fcode,buf,h.len,h.caplen)) {
      if (cnt >= 0 && --cnt < 0 )
	break;
      (*printit)(buf, &h.ts, h.len, h.caplen);
    }
  }
  free((char *)buf);
  return 0;
}
#endif	/* not TCPVIEW */

int
sniff_next_packet(hdr, buf, buflen)
struct packet_header *hdr;
u_char *buf;
int buflen;
{
  double t,x;

  hdr_load();

  if (s.s_hdr.type == 3 || s.s_hdr.type == EOF )
    return 1;

  if (s.s_hdr.type == 6 ) {      /* infamous type 6 header detected */
    if(fread(buf,s.s_hdr.len,1,rfd)==0)
      return 1;
    return (sniff_next_packet(hdr,buf));
  }

  dh_load();

  if(fread(buf, s.s_dh.size, 1, rfd)==0)
    return 1;

  x = 4.0*(double)(1<<30);
  t = (double)s.s_dh.time_low+(double)(s.s_dh.time_med)*65536.0 +
    (double)s.s_dh.time_high*x;
  t = t/1000000.0*Timeunit; /* set t equal to number of secs */

  hdr->ts.tv_sec = (long)t;
  hdr->ts.tv_usec = (u_long)((t-(double)(hdr->ts.tv_sec))*1.0e6);

  hdr->len = s.s_dh.truesize ? s.s_dh.truesize : s.s_dh.size;
  hdr->caplen = s.s_dh.size;

  return 0;
}

static void
hdr_print() {
	register FILE *f = sfd;

	putbs(s.s_hdr.type, f);
	putbs(s.s_hdr.len, f);
	putbs(s.s_hdr.rsvd, f);
}

static void
hdr_load() {
	register FILE *f = rfd;

	getbs(s.s_hdr.type, f);
	getbs(s.s_hdr.len, f);
	getbs(s.s_hdr.rsvd, f);
}

static void
dh_print() {
	register FILE *f = sfd;

	putbs(s.s_dh.time_low, f);
	putbs(s.s_dh.time_med, f);
	putbs(s.s_dh.time_high, f);
	putbs(s.s_dh.size, f);
	putc(s.s_dh.fs, f);
	putc(s.s_dh.flags, f);
	putbs(s.s_dh.truesize, f);
	putbs(s.s_dh.rsvd, f);
}

static void
ver_print() {
	register FILE *f = sfd;

	putbs(V.maj_vers, f);
	putbs(V.min_vers, f);
	putbs(V.time, f);
	putbs(V.date, f);
	putc(V.type, f);
	putc(V.network, f);
	putc(V.format, f);
	putc(V.timeunit, f);
	putbs(V.rsvd[1], f);
	putbs(V.rsvd[2], f);
	putbs(V.rsvd[3], f);
}

static void
dh_load() {
	register FILE *f = rfd;

	getbs(s.s_dh.time_low, f);
	getbs(s.s_dh.time_med, f);
	getbs(s.s_dh.time_high, f);
	getbs(s.s_dh.size, f);
	s.s_dh.fs = getc(f);
	s.s_dh.flags = getc(f);
	getbs(s.s_dh.truesize, f);
	getbs(s.s_dh.rsvd, f);
}




syntax highlighted by Code2HTML, v. 0.9.1