/**************************************************************************** 
**
** File: ipgrab.c
**
** Author: Mike Borella
**
** Sniffs all packets on the link and dumps the fields of the data link, 
** IP, TCP, and UDP headers.  
**
** $Id: ipgrab.c,v 1.22 2007/06/25 10:47:53 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 "datalink.h"
#include "ipgrab.h"
#include "open_pcap.h"
#include "ip_protocols.h"
#include "error.h"
#include "stats.h"
#include "dynports.h"
#include "file.h"

char          * pcap_cmd;  /* command string for pcap */
pcap_t        * pd;        /* pcap device descriptor */
int             cnt;       /* number of packets to read */
pcap_dumper_t * p;         /* pointer to pcap dump file for writing */
struct arg_t  * my_args;   /* Command line arguments */

RETSIGTYPE cleanup(void);
RETSIGTYPE cleanup_pcap(void);


/*----------------------------------------------------------------------------
 *
 * main()
 *
 *----------------------------------------------------------------------------
 */

int main(int argc, char *argv[])
{
  u_int8_t * userdata;
  int link;
   
  /*
   * Initiatilize the IP family module and the stats collection module
   */

  init_ip_protocols();
  stats_init();

  /*
   * Set the layer
   */

  set_layer(LAYER_NONE);

  /*
   * Clear packet count
   */

  cnt = -1;

  /*
   * Parse command line for options, take care for version and packet count
   */

  my_args = Cmdline(argc, argv);
  if (my_args->v)
    {
      printf("%s\n", VERSION);
      return 0;
    }
  if (my_args->c) 
    cnt = my_args->c;
  
  /*
   * Make stdout buffered, if necessary
   */
  
  if (my_args->b)
#ifdef HAVE_SETLINEBUF
    setlinebuf(stdout);
#else
  setvbuf(stdout, NULL, _IOLBF, 0);
#endif

  /*
   * Parse any dynamic port bindings
   */
  
  if (my_args->P)
    dynports_parse(my_args->P);

  /*
   * Copy filter command into a string only if there is a command.  
   */

  if (my_args->optind)
    pcap_cmd = argv2str (&argv[my_args->optind]);
 
  /*
   * If we're reading a file, do things in a generic fashion.  Otherwise
   * use standard libpcap functions to do a live read.
   */

  if (my_args->r)
    {
      int ret;

      /*Get rid of root privs, not needed for reading files */
      setuid(getuid());

      /*
       * Set the signals so that we can clean up when ctrl-C is pressed
       * or some other reason causes the program to stop
       */
      
      signal(SIGTERM, (sighandler_t) cleanup);
      signal(SIGINT,  (sighandler_t) cleanup);
      signal(SIGQUIT, (sighandler_t) cleanup);
      signal(SIGABRT, (sighandler_t) cleanup);

      /* Try to ID the trace file type */
      ret = file_id(my_args->r);
      if (ret == 0)
	error_fatal("unrecognized file type: %s", my_args->r);

      /* Open the file */
      file_open(my_args->r);

      /* Read the packets */
      file_read(cnt);

      /* When done, dump the stats */
      stats_dump();
    }
  else
    {
      /* This is just for live packet capture, not reading from a file */

      /*
       * Set the signals so that we can clean up when ctrl-C is pressed
       * or some other reason causes the program to stop
       */
      
      signal(SIGTERM, (sighandler_t) cleanup_pcap);
      signal(SIGINT,  (sighandler_t) cleanup_pcap);
      signal(SIGQUIT, (sighandler_t) cleanup_pcap);
      signal(SIGABRT, (sighandler_t) cleanup_pcap);

      
      /*
       * Open the pcap device for sniffing
       */
      
      link = open_pcap();
      
      /*
       * Get rid of root privs
       */
      
      setuid(getuid());
      
      /*
       * Print intro stuff to stderr so output files have consistent 
       * format
       */
      
      fprintf(stderr, "%s %s\n", PACKAGE, VERSION);
      fprintf(stderr, "Listening on device %s ", my_args->i);
      switch(link)
	{
	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%s cannot handle data link type %d", argv[0], link);
	}
      
      /* 
       * Put the link type into a string
       */
      
      userdata = (u_char *) &link;

      /* 
       * 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));
	  
	  /* 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 all packets on the device. Continue until cnt packets read */
	  
	  if (pcap_loop(pd, cnt, (pcap_func_t) datalink_pcap, userdata) < 0)
	    error_fatal("pcap_loop: %s", pcap_geterr(pd));
	}

        cleanup_pcap();
    }

  return 0;
}


/*----------------------------------------------------------------------------
 *
 * cleanup_pcap()
 *
 * Cleanup and collect PCAP statistics
 *
 *----------------------------------------------------------------------------
 */

RETSIGTYPE cleanup_pcap(void)
{
  struct pcap_stat stats;

  /*
   * If we aborted because no device was found, then no need
   * of further processing.
   */

  if (!pd)
    exit ( EXIT_FAILURE );


  if (pcap_stats(pd, &stats) < 0)
    fprintf(stderr, "pcap_stats: %s\n", pcap_geterr(pd));
  else 
    {
      fprintf(stderr, "%d packets received\n", stats.ps_recv);
      fprintf(stderr, "%d packets dropped by kernel\n", stats.ps_drop);
    }

  /* Close the pcap device */
  pcap_close(pd);

  /* If there was a dump file open for writing, close that too */
  if (my_args->w)
    pcap_dump_close(p);

  /* Display any other states that were collected */
  cleanup();
}

/*----------------------------------------------------------------------------
**
** cleanup()
**
** Cleans up any files and displays stats.
**
**----------------------------------------------------------------------------
*/

RETSIGTYPE cleanup (void)
{
  /* Free memory allocated in parse_cl:Cmdline() */
  free_args ( my_args );

  /* Display the stats */
  stats_dump();

  /* Let's close the program with a delimitor */
  display_header_line ( HEADERLINE_DASHES );

  exit(0);
}
