/*

Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef LINUX
#define __FAVOR_BSD
#endif

#include <libnd.h>

#ifdef HOST_BIGELNDIAN
static int host_bigendian = 1;
#else
static int host_bigendian = 0;
#endif

#define	SWAP_LONG(y) \
        ((((y)&0xff)<<24) | (((y)&0xff00)<<8) | (((y)&0xff0000)>>8) | (((y)>>24)&0xff))
#define	SWAP_SHORT(y) \
	((((y)&0xff)<<8) | ((u_short)((y)&0xff00)>>8))

static GList *observers; /* GList<LND_TraceObserver> */

static void
trace_cleanup_state_cb(LND_Protocol *proto, void *user_data)
{
  if (proto->is_stateful)
    proto->free_state((LND_Trace *) user_data);
}

void    
libnd_trace_free(LND_Trace *trace)
{
  if (!trace)
    return;

  D_ENTER;

  libnd_proto_registry_foreach_proto(trace_cleanup_state_cb, trace);

  libnd_tcpdump_close(trace);
  libnd_reg_free(trace->registry);
  libnd_tpm_free(trace->tpm);

  g_list_free(trace->filters);

  g_free(trace->filename);
  g_free(trace->unnamed);    
  g_free(trace);

  D_RETURN;
}


LND_Trace *
libnd_trace_new(const char *filename)
{
  LND_Trace            *trace;

  D_ENTER;
  
  if (! (trace = g_new0(LND_Trace, 1)))
    {
      D(("Out of memory.\n"));
      D_RETURN_(NULL);
    }

  /* Create the trace data registry: */
  trace->registry = libnd_reg_new("trace_user_data");
  
  if (!filename)
    {
      trace->unnamed = g_strdup(libnd_misc_get_unnamed_string());

      trace->tpm = libnd_tpm_new(trace);

      /* Reset filter stuff */
      trace->filter_mode = LND_FILTER_MODE_AND;    
      trace->iterator_mode = LND_PACKET_IT_AREA_R;
  
      /* Initialize iterator area to the entire trace */
      libnd_trace_set_area(trace, NULL);

      D_RETURN_(trace);
    }

  if (! libnd_trace_init(trace, filename))
    {
      libnd_trace_free(trace);
      D_RETURN_(NULL);
    }
  
  D_RETURN_(trace);
}


gboolean
libnd_trace_init(LND_Trace *trace, const char *filename)
{
  FILE *f;
  struct pcap_file_header *pfh;

  D_ENTER;
  
  if (!trace || !filename || !*filename)
    {
      D(("Input error.\n"));
      D_RETURN_(FALSE);
    }
  
  if (!libnd_misc_can_read(filename))
    {
      D(("Source file %s unreadable.\n", filename));
      D_RETURN_(FALSE);
    }
  
  if (!libnd_misc_is_tcpdump_file(filename))
    {
      D(("%s is not a tcpdump trace file.\n", filename));
      D_RETURN_(FALSE);
    }

  g_free(trace->filename);
  trace->filename = g_strdup(filename);
  
  /* Set up the trace part manager: */
  libnd_tpm_free(trace->tpm);
  trace->tpm = libnd_tpm_new(trace);
  
  pfh = &(trace->tcpdump.pfh);

  /* Check if we can read the tracefile */
  if ( (f = fopen(trace->filename, "r")) == NULL)
    {
      fprintf(stderr, "tcpdump init error: %s\n", strerror(errno));
      D_RETURN_(FALSE);
    }

  /* Read trace file header */
  if (fread(pfh, sizeof(struct pcap_file_header), 1, f) != 1)
    {
      fprintf(stderr, "tcpdump init error: %s\n", strerror(errno));
      fclose(f);
      D_RETURN_(FALSE);
    }
  
  /* Swap endianness if necessary */
  if (((pfh->magic == 0xd4c3b2a1) && !host_bigendian) ||
      ((pfh->magic == 0xa1b2c3d4) && host_bigendian)  ||
      ((pfh->magic == 0x34cdb2a1) && !host_bigendian) ||
      ((pfh->magic == 0xa1b2cd34) && host_bigendian))
    {
      /* We need to swap the header: */
      pfh->magic = SWAP_LONG(pfh->magic);
      pfh->version_major = SWAP_SHORT(pfh->version_major);
      pfh->version_minor = SWAP_SHORT(pfh->version_minor);
      pfh->thiszone = SWAP_LONG(pfh->thiszone);
      pfh->sigfigs  = SWAP_LONG(pfh->sigfigs);
      pfh->snaplen  = SWAP_LONG(pfh->snaplen);
      pfh->linktype = SWAP_LONG(pfh->linktype);
    }

  pfh->magic = 0xa1b2c3d4;
  fclose(f);

  /* Reset filter stuff */
  libnd_trace_clear_filters(trace);
  trace->filter_mode = LND_FILTER_MODE_AND;    
  trace->iterator_mode = LND_PACKET_IT_AREA_R;
  
  /* Initialize iterator area to the entire trace */
  libnd_trace_set_area(trace, NULL);
    
  D_RETURN_(TRUE);
}


gboolean       
libnd_trace_initialized(const LND_Trace *trace)
{
  if (!trace)
    return FALSE;

  return (trace->tcpdump.pfh.magic   != 0   &&
	  trace->tcpdump.pfh.snaplen != 0);
}


void           
libnd_trace_set_iterator_mode(LND_Trace *trace,
			      LND_PacketIteratorMode mode)
{
  D_ENTER;

  if (!trace)
    D_RETURN;

  trace->iterator_mode = mode;
  D_RETURN;
}


LND_PacketIteratorMode 
libnd_trace_get_iterator_mode(const LND_Trace *trace)
{
  if (!trace)
    return LND_PACKET_IT_SEL_R;

  return trace->iterator_mode;
}


void
libnd_trace_set_area(LND_Trace *trace,
		     LND_TraceArea *area)
{
  if (!trace)
    return;

  if (area)
    {
      trace->area = *area;

      if (area->mode == LND_TPM_TIME)
	{
	  D(("Set trace area to time range.\n"));
	  if (trace->tpm && trace->tpm->base)
	    {
	      if (pcapnav_timeval_cmp(&area->area_time_start,
				      &trace->tpm->base->start_ts) < 0)
		trace->area.area_time_start = trace->tpm->base->start_ts;

	      if (pcapnav_timeval_cmp(&trace->tpm->base->end_ts,
				      &area->area_time_end) < 0)
				      
		trace->area.area_time_end = trace->tpm->base->end_ts;				      
	    }
	}
      else
	{
	  D(("Set trace area to space %f %f.\n",
	     area->area_space_start, area->area_space_end));
	}
    }
  else
    {
      memset(&trace->area, 0, sizeof(LND_TraceArea));
      trace->area.mode = LND_TPM_SPACE;
      trace->area.area_space_start = 0.0;
      trace->area.area_space_end   = 1.0;
    }

  libnd_trace_tell_observers(trace, LND_TRACE_IT_AREA_SET);
}


void
libnd_trace_get_area(LND_Trace *trace,
		     LND_TraceArea *area)
{
  if (!trace || !area)
    return;

  *area = trace->area;
}


pcap_t        *
libnd_trace_get_pcap_handle(LND_Trace *trace)
{
  D_ENTER;

  if (trace && trace->tpm && trace->tpm->base && trace->tpm->base->pcn)
    {
      pcap_t *result = pcapnav_pcap(trace->tpm->base->pcn);
      D_RETURN_(result);
    }

  D_RETURN_(NULL);
}


const char    *
libnd_trace_get_name(const LND_Trace *trace)
{
  if (!trace)
    return "netdude.trace";

  return (trace->filename ? trace->filename : trace->unnamed);
}


gboolean
libnd_trace_save(LND_Trace *trace)
{
  char tmp_name[MAXPATHLEN];
  char *output_name;
  off_t offset;

  D_ENTER;

  if (!trace)
    D_RETURN_(FALSE);

  if (!trace->filename)
    D_RETURN_(FALSE);

  if (! libnd_trace_initialized(trace))
    {
      D(("Cannot save uninitialized trace -- aborting.\n"));
      D_RETURN_(FALSE);
    }
  
  output_name = trace->filename;

  /* There's one caveat when just saving a trace: since iterating
   * over the packets in the trace may include unmodified packets
   * from the original trace, there's the possibility of reading
   * from and writing to the same file -- not what we want.
   * So we check if the file name we're writing to is the same 
   * as the base's input filename. If they are identical, we
   * first save to a temporary file on the same device and then
   * just rename() to the desired output name.
   */
  if (trace->tpm->base->in_filename &&  /* May be NULL when new trace */
      ! strcmp(trace->filename, trace->tpm->base->in_filename))
    {
      g_snprintf(tmp_name, MAXPATHLEN, "%s_", trace->filename);
      output_name = tmp_name;
      D(("Saving to original file name -- using temp file '%s'\n", output_name));
    }

  /* Remember current offset -- we're about to replace the TPM
   * with a new one for the sync'd file so we need to jump back to
   * this offset afterwards.
   */
  offset = 
    libnd_tpm_map_loc_to_offset(trace->tpm, &trace->tpm->current->start);

  libnd_tpm_set_output_file(trace->tpm, output_name);

  if (! libnd_trace_sync(trace))
    {
      D(("Saving trace %s to %s failed.\n", trace->filename, output_name));
      D_RETURN_(FALSE);
    }

  libnd_trace_set_dirty(trace, FALSE);

  if (output_name == tmp_name)
    {
      LND_TraceLoc loc;

      D(("Renaming tmp file from '%s' to '%s'.\n",
	 tmp_name, trace->filename));

      if (rename(tmp_name, trace->filename) != 0)
	{
	  D(("Renaming failed.\n"));
	  D_RETURN_(FALSE);
	}

      libnd_tpm_free(trace->tpm);
      trace->tpm = libnd_tpm_new(trace);
      
      /* Re-adjust to original output name. */
      libnd_tpm_map_offset_to_loc(trace->tpm, offset, &loc);
      libnd_tpm_goto_loc(trace->tpm, &loc);
    }

  D_RETURN_(TRUE);
}


gboolean      
libnd_trace_sync(LND_Trace *trace)
{
  LND_PacketIterator  pit;
  LND_TraceArea area_all, area_old;
  LND_TraceLoc loc;
  off_t offset;
  pcapnav_result_t result;

  D_ENTER;

  if (!trace)
    D_RETURN_(FALSE);

#ifdef LIBND_DEBUG
  if (libnd_runtime_options.debug)
    {
      D(("Parts before syncing trace\n"));
      libnd_tpm_dump_parts(trace->tpm);
    }
#endif

  /* Get global start offset of current trace part */
  offset = libnd_tpm_map_loc_to_offset(trace->tpm, &trace->tpm->current->start);

  /* Copy out the old iterator area, and put one for the entire trace in. */
  libnd_trace_get_area(trace, &area_old);
  libnd_trace_area_init_space(&area_all, 0, 1);
  libnd_trace_set_area(trace, &area_all);
  
  /* Iterate over the entire trace, thus syncing it, and apply all queued
   * operations in sequence to every single packet. Make sure that operations
   * are not applied to the packets belonging to trace->tpm->current, as these
   * already have the queued operations applied!
   */
  if (! libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW))
    {
      D(("Resetting trace area to previous one:\n"));
      libnd_trace_set_area(trace, &area_old);
      D_RETURN_(FALSE);
    }
  
  /* Make sure we output to the correct file name. This sets
   * the output file name of the current trace part (which will
   * be used during the iteration) to the TPM's output name:
   */
  libnd_tp_set_output_file(trace->tpm->current, trace->tpm->output_name);
  
  /* Note here that the packet iterator, given that it's in AREA_RW mode,
   * automatically takes care of the basefile management
   */
  for ( ; libnd_pit_get(&pit); libnd_pit_next(&pit))
    ;

  /* Make sure we jump to the location now corresponding to the old
   * current one (calculated via offsets).
   */
  result = libnd_tpm_map_offset_to_loc(trace->tpm, offset, &loc);
  
  if (result == PCAPNAV_DEFINITELY)
    libnd_tpm_goto_loc(trace->tpm, &loc);

  /* Also restore the old trace area */
  libnd_trace_set_area(trace, &area_old);

  D_RETURN_(TRUE);
}


gboolean
libnd_trace_save_as(LND_Trace *trace, const char *filename)
{
  gboolean result;

  D_ENTER;

  if (!trace || !filename)
    D_RETURN_(FALSE);

  g_free(trace->filename);
  trace->filename = g_strdup(filename);
  result = libnd_trace_save(trace);

  D_RETURN_(result);
}


void        
libnd_trace_set_dirty(LND_Trace *trace, gboolean dirty)
{
  D_ENTER;

  if (!trace)
    D_RETURN;

  if (dirty)
    {
      if (!trace->dirty)
	{
	  trace->dirty = TRUE;
	  libnd_trace_tell_observers(trace, LND_TRACE_MODIFIED);
	}

      /* The current trace part may have changed since the last
       * time we called this, so set in any case.
       */
      libnd_tp_set_dirty(trace->tpm->current, TRUE);
    }
  else if (!dirty && trace->dirty)
    {
      trace->dirty = FALSE;
      libnd_trace_tell_observers(trace, LND_TRACE_CLEANED);
    }  

  D_RETURN;
}


LND_Packet     *
libnd_trace_get_packets(const LND_Trace *trace)
{
  if (!trace || !trace->tpm)
    return NULL;

  return libnd_tpm_get_packets(trace->tpm);
}


void          
libnd_trace_add_filter(LND_Trace *trace, LND_Filter *filter)
{
  if (!trace || !filter)
    return;

  if (g_list_find(trace->filters, filter))
    return;

  trace->filters = g_list_prepend(trace->filters, filter);
}


void          
libnd_trace_remove_filter(LND_Trace *trace, LND_Filter *filter)
{
  if (!trace || !filter)
    return;

  trace->filters = g_list_remove(trace->filters, filter);
}


void          
libnd_trace_clear_filters(LND_Trace *trace)
{
  if (!trace)
    return;

  g_list_free(trace->filters);
  trace->filters = NULL;
}


gboolean      
libnd_trace_has_filter(LND_Trace *trace, LND_Filter *filter)
{
  if (g_list_find(trace->filters, filter))
    return TRUE;

  return FALSE;
}


guint64
libnd_trace_apply_filters(LND_Trace *trace)
{
  LND_PacketIterator pit;
  LND_PacketIteratorMode mode;

  if (!trace)
    return 0;

  /* All filtering is recognized by packet iterators. So all
   * we need to do to apply a trace's current filter settings
   * is to iterate over the trace; however, we do not want to
   * iterate over the entire trace -- the filtering for other
   * parts of the trace will get applied once we actually visit
   * these areas. So we reduce the LND_PACKET_IT_AREA_... settings
   * to LND_PACKET_IT_PART.
   */
  mode = trace->iterator_mode;

  if (mode != LND_PACKET_IT_SEL_R ||
      mode != LND_PACKET_IT_SEL_RW)
    mode = LND_PACKET_IT_PART_R;

  for (libnd_pit_init_mode(&pit, trace, mode); libnd_pit_get(&pit); libnd_pit_next(&pit))
    ;
  
  return libnd_pit_get_count(&pit);
}


LND_TraceObserver *
libnd_trace_observer_new(void)
{
  return g_new0(LND_TraceObserver, 1);
}


void               
libnd_trace_observer_free(LND_TraceObserver *ob)
{
  g_free(ob);
}


void           
libnd_trace_add_observer(LND_TraceObserver *observer)
{
  if (!observer)
    return;

  observers = g_list_prepend(observers, observer);
}


void           
libnd_trace_del_observer(LND_TraceObserver *observer)
{
  if (!observer)
    return;

  observers = g_list_remove(observers, observer);
}


void           
libnd_trace_tell_observers(LND_Trace *trace, LND_TraceObserverOp op)
{
  GList *l;
  LND_TraceObserver *ob;

  if (!trace)
    return;

  if (trace->trace_observer_blocks & op)
    return;

  for (l = observers; l; l = g_list_next(l))
    {
      ob = (LND_TraceObserver *) l->data;

      switch (op)
	{
	case LND_TRACE_MODIFIED:
	  if (ob->trace_modified)
	    ob->trace_modified(trace);
	  break;

	case LND_TRACE_CLEANED:
	  if (ob->trace_cleaned)
	    ob->trace_cleaned(trace);
	  break;

	case LND_TRACE_RELOAD:
	  if (ob->trace_reload)
	    ob->trace_reload(trace);
	  break;

	case LND_TRACE_JUMPED:
	  if (ob->trace_jumped)
	    ob->trace_jumped(trace);
	  break;

	case LND_TRACE_CLEAR:
	  if (ob->trace_clear)
	    ob->trace_clear(trace);
	  break;

	case LND_TRACE_IT_AREA_SET:
	  if (ob->trace_it_area_set)
	    ob->trace_it_area_set(trace);
	  break;

	default:
	  D(("Unknown trace operation\n"));
	}
    }
}


void
libnd_trace_block_packet_observer_op(LND_Trace *trace,
				     LND_PacketObserverOp op)
{
  if (!trace)
    return;

  trace->packet_observer_blocks |= op;
}


void                
libnd_trace_unblock_packet_observer_op(LND_Trace *trace,
				       LND_PacketObserverOp op)
{
  if (!trace)
    return;

  trace->packet_observer_blocks &= ~op;
}


void
libnd_trace_block_trace_observer_op(LND_Trace *trace,
				    LND_TraceObserverOp op)
{
  if (!trace)
    return;

  trace->trace_observer_blocks |= op;
}


void                
libnd_trace_unblock_trace_observer_op(LND_Trace *trace,
				      LND_TraceObserverOp op)
{
  if (!trace)
    return;
  
  trace->trace_observer_blocks &= ~op;
}


void                
libnd_trace_area_init_time(LND_TraceArea *area,
			   struct bpf_timeval *tv_start,
			   struct bpf_timeval *tv_end)
{
  struct bpf_timeval tmp;

  if (!area || !tv_start || !tv_end)
    return;
  
  if (pcapnav_timeval_cmp(tv_start, tv_end) > 0)
    {
      tmp = *tv_start;
      *tv_start = *tv_end;
      *tv_end = tmp;
    }
  
  memset(area, 0, sizeof(LND_TraceArea));
  area->mode = LND_TPM_TIME;
  area->area_time_start = *tv_start;
  area->area_time_end   = *tv_end;
}


void                
libnd_trace_area_init_space(LND_TraceArea *area,
			    double start, double end)
{
  double tmp;

  if (!area)
    return;
  
  if (end < start)
    {
      tmp = start;
      start = end;
      end = tmp;
    }

  memset(area, 0, sizeof(LND_TraceArea));
  area->mode = LND_TPM_SPACE;
  area->area_space_start = start;
  area->area_space_end = end;
}




syntax highlighted by Code2HTML, v. 0.9.1