/***************************************************************************
 *   Copyright (C) 2004-2010 by Giovanni Venturi                           *
 *   giovanni@ksniffer.org                                                 *
 *                                                                         *
 *   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 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.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02110-1301, USA.          *
 ***************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>

#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#ifndef _WIN32
#include <netinet/in.h>
#endif

#include <arpa/inet.h>
#include <iostream>
#include <pcap.h>

using namespace std;

#include "../packetstructures.h"
#include "../version.h"

#include "consts.h"
#include "parseconfig.h"
#include "ksniff.h"

void logging(const char *str)
{
  syslog(LOG_AUTHPRIV | LOG_NOTICE, "%s", str);
}

void loggingInt(const char *str, int i)
{
  char msg[100];

  sprintf(msg, "%s %d", str, i);
  logging(msg);
}


void loggingString(const char *str, char *str2)
{
  char msg[100];

  sprintf(msg, "%s %s", str, str2);
  logging(msg);
}


void initSniff(ParseConfig *config)
{
  signal( SIGTERM, exitHandler );
  signal( SIGINT, exitHandler );

  // SIGUSR1 pause and resume the sniffing
  signal( SIGUSR1, pauseContinueSniffHandler );

  m_canSniff = true;
  m_bPauseSniffing = false;
  cout << endl << endl << APPNAME << "You choose to use interface: '" << config->interface() << "'" << endl;
  cout << APPNAME << "sniffing in: '" << config->sniffFile() << "'" << endl;
  cout << APPNAME << "type of capture: " << config->sniffType() << endl;
  if (config->sniffType() != STOP_CAPTURE_MANUALLY)
    cout << APPNAME << "PackSizeTime parameter: " << config->nPackSizeTime() << endl;
  switch (config->sniffType())
  {
    case STOP_CAPTURE_MANUALLY:
      cout << APPNAME << "the capture will be stopped manually by the user" << endl;
      break;
    case STOP_AFTER_X_PACKETS:
      cout << APPNAME << "the packets capture will stop after: " << config->nPackSizeTime() * config->nUnits() << " packets" << endl;
      break;
    case STOP_AFTER_X_BYTES:
      cout << APPNAME << "the packets capture will stop after: " << config->nPackSizeTime() * config->nUnits()  << " bytes" << endl;
      break;
    case STOP_AFTER_X_SECONDS:
      cout << APPNAME << "the packets capture will stop after: " << config->nPackSizeTime() * config->nUnits()  << " seconds" << endl;
      break;
  }
  fflush( stderr );
}


void startSniffing(ParseConfig *config)
{
  // set all parameters to prepare sniffing structures
  char errbuf[PCAP_ERRBUF_SIZE];
  int frameLength, frameType;

  // needed to avoid calculate delay in sniffing
  long limit = config->nPackSizeTime() * config->nUnits();
  int sniffType = config->sniffType();

  long packetsSize = 24;  // total size (in byte) of all captured packets

  #ifdef MUST_DO_SELECT
    int selRet = 0;
    m_pcap_fd = 0;
  #endif

  // delete the file, to avoid problem if cause of some strange reason (crash?) it was not deleted at the KSniffer quit
  cout << APPNAME << "deleting " << config->sniffFile() << endl;
  fflush(stderr);
  if ( unlink(config->sniffFile()) == -1 )
    // if we don't reset errno and the file was not delete because all was ok on last quit
    // we cannot start sniffing: we'll get an annoying error on the wrong statement
    errno = 0;

  // open libpcap using ifname interface
  if (!strcmp(config->interface(), "any"))
    // promiscue mode not supported by "any" interface
    m_pcapfp = pcap_open_live("any", PKTMAX, 0, -1, errbuf);
  else
    m_pcapfp = pcap_open_live(config->interface(), PKTMAX, 1, -1, errbuf);

  if (m_pcapfp == NULL)
  {
    cerr << APPNAME << "Error opening libpcap: " << errbuf << endl;
    fflush(stderr);
    return;
  }
  else
  {
    m_pdump = pcap_dump_open( m_pcapfp, config->sniffFile() );
    chmod(config->sniffFile(), S_IRUSR|S_IWUSR|S_IRGRP);
    cout << APPNAME << "writing packets into " << config->sniffFile() << endl;
    fflush(stderr);
    loggingString( "capturing from interface:", config->interface() );
    loggingString( "writing packets into file:", config->sniffFile() );
  }
#ifdef MUST_DO_SELECT

#ifdef HAVE_PCAP_GET_SELECTABLE_FD
    m_pcap_fd = pcap_get_selectable_fd(m_pcapfp);
#else
    m_pcap_fd = pcap_fileno(m_pcapfp);
#endif

#endif

  if (m_pcap_fd == -1)
  {
    cerr << APPNAME << "Error getting selectable FD: " << pcap_geterr(m_pcapfp) << endl;
    fflush(stderr);
    return;
  }

  // reset the number of counted packets
  m_countPackets = 0;

  // start reading packets on the selected source interface
  time_t rawtime;
  time ( &rawtime );
  long startTime = rawtime;
  struct pcap_pkthdr *hdr;
  unsigned char *p;
  int result = 0;

  char pipeFileName[100];

  // open the FIFO pipe so to let know GUI a network packet is ready to be read
  strncpy(pipeFileName, config->sniffFile(), 100);
  strncat(pipeFileName, ".fifo", 100);
  cerr << "going to open: '" << pipeFileName << "'" << endl;
  fp_fifo_channel = fopen(pipeFileName, "w");
  cerr << "opened: '" << pipeFileName << "'" << endl;
  if (fp_fifo_channel == NULL)
  {
    perror("fopen");
    exit(1);
  }
  while (m_canSniff)
  {
#ifdef MUST_DO_SELECT
    /*
     * snipped from ethereal code.
     */

    FD_ZERO(&m_fdset);
    FD_SET(m_pcap_fd, &m_fdset);
    m_fdtimeout.tv_sec = 0;
    m_fdtimeout.tv_usec = CAP_READ_TIMEOUT*1000;
    selRet = select(m_pcap_fd+1, &m_fdset, NULL, NULL, &m_fdtimeout);
    if (selRet > 0)
    {
      /*
       * "select()" says we can read from it without blocking; go for
       * it.
       */
      result = pcap_next_ex(m_pcapfp, &hdr, (const u_char **)&p);
    }
    else
    {
       p = NULL;
       if ((selRet < 0) && (errno != EINTR))
       {
         cerr << APPNAME << "Unexpected error from select: " << strerror(errno) << endl;
         fflush(stderr);
         break;
       }
    }
#else
    // get next tail packet calling pcap_next_ex()
    result = pcap_next_ex(m_pcapfp, &hdr, (const u_char **)&p);
#endif /* MUST_DO_SELECT */

    if ((result == -1) && m_canSniff)
    {
      cerr << APPNAME << "an error occurred. Code " << errno << ": " << strerror(errno) << endl;
      cout << APPNAME << "Quitting..." << endl;
      fflush(stderr);
      m_returnValue = SNIFF_NETWORK_ERROR;
      m_canSniff = false;
      break;
    }

    if (p == NULL)
    {
      if (config->sniffType() == STOP_AFTER_X_SECONDS)
      {
        time ( &rawtime );  // evaluing time again to know how much seconds has spent
        if (rawtime - startTime > limit)
          m_canSniff = false;
        else
          continue;  // we didn't get any packet
      }
      else
        continue;
    }
    else if (!m_bPauseSniffing)
    {
      // if the capture is not paused the packet can be saved into the libpcap file
      // if the capture condition is respected: not excedeed capure time or packets size
      time ( &rawtime );
      switch (sniffType)
      {
        case STOP_AFTER_X_PACKETS:
          if (m_countPackets >= limit)
            m_canSniff = false;
          break;
        case STOP_AFTER_X_BYTES:
          if (limit <= packetsSize)
            m_canSniff = false;
          break;
        case STOP_AFTER_X_SECONDS:
          if (rawtime - startTime > limit)
          {
            cout << APPNAME << "Ok capturing time's ended..." << endl;
            fflush(stderr);
            m_canSniff = false;
          }
          break;
      }
      if (m_canSniff)
      {
        pcap_dump((u_char *)m_pdump, hdr, p);
        pcap_dump_flush( m_pdump );
cerr << "got a packet" << endl;
        m_countPackets++;

        // got a packet: raw bytes (p), the timestamp and the captured packet length and the real packet length (hdr)
        /**
          *  hdr is a struct pcap_pkthdr:
          *    ts - a struct timeval containing the time when the packet was captured
          *    caplen - a bpf_u_int32 giving the number of bytes of the packet that are available from the capture
          *    len - a bpf_u_int32 giving the length of the packet, in bytes (which might be more than the number
          *      of bytes available from the capture, if the length of the packet is larger than the maximum
          *      number of bytes to capture)
          */

        setHeaderData(m_pcapfp, frameType, frameLength);
        if (frameLength < 0)
        {
          cout << APPNAME << "unknown packet" << endl;
          fflush(stderr);
          continue;
        }
        else
          packetsSize += hdr->caplen + 16;

        // say to the GUI that there is a packet
        fputs("+got+", fp_fifo_channel);
        fflush(fp_fifo_channel);
      }
    }
  }

  // close FIFO pipe
  fclose(fp_fifo_channel);

  // release libpcap resource
  pcap_close(m_pcapfp);

  // now can close dumped file
  pcap_dump_close(m_pdump);

  loggingInt( "total captured packets:", m_countPackets );
  cout << APPNAME << "Thread stopped. Got " << m_countPackets << " packets" << endl;
  fflush(stderr);
  cout << endl << APPNAME << "total captured packets: " << m_countPackets << endl;
  fflush(stderr);
  if (m_countPackets == 0)
  {
    loggingInt( "total captured packets size:", 0 );
    cout << APPNAME << "total captured packets size: 0" << endl;
    fflush(stderr);
  }
  else
  {
    loggingInt( "total captured packets size:",  packetsSize);
    cout << APPNAME << "total captured packets size: " << packetsSize << endl;
    fflush(stderr);
  }
}


void setHeaderData(pcap_t *pHandler, int &frameType, int &hdrLen)
{
  frameType = pcap_datalink(pHandler);

  switch(frameType)
  {
    case DLT_EN10MB:
      hdrLen = 14;
      break;
    case DLT_LINUX_SLL:
      hdrLen = 16;
      break;
    case DLT_LOOP:
      hdrLen = 4;
      break;
    case DLT_NULL:
      hdrLen = 4;
      break;
    case DLT_RAW:
      hdrLen = 0;
      break;
    default:
      hdrLen = -1;
      break;
  }
}


void stopSniffing()
{
  m_canSniff = false;
}


void exitHandler( int )
{
  // thread wil be stopped as soon as
  m_canSniff = false;
}


void pauseContinueSniffHandler( int )
{
  if (!m_bPauseSniffing)
  {
    m_bPauseSniffing = true;
    cout << APPNAME << "Thread paused." << endl;
    fflush(stderr);
    logging( "paused sniffing..." );
  }
  else
  {
    m_bPauseSniffing = false;
    cout << APPNAME << "Thread resumed." << endl;
    fflush(stderr);
    logging( "resumed sniffing..." );
  }
}


int main( int argc, char *argv[] )
{
  if ( argc == 2 )
  {
    cout << argv[0] << " is using " << pcap_lib_version() << endl;
    logging( "starting...");
    if (strcmp(argv[1], "devs") == 0)
    {
      pcap_if_t *alldevs, *d;
      char errbuf[PCAP_ERRBUF_SIZE] = "";

      if (pcap_findalldevs(&alldevs, errbuf) == 0)
      {
        cout << APPNAME << "found capturable devices:" << endl;
        fflush(stderr);
      }
      else
      {
        cout << APPNAME << "errbuf: '" << errbuf << "'" << endl;
        fflush(stderr);
        m_returnValue = SNIFF_NO_SNIFFING_DEVICE_FOUND;
        return m_returnValue;
      }

      // Print the list
      for(d=alldevs; d; d=d->next)
      {
        cout << APPNAME << " " << d->name;
        if (d->description)
          cout << " (" << d->description << ")" << endl;
        else
          cout << " (No description available)" << endl;
      }

/*      while (alldevs)
      {
        cout << APPNAME << "****************************************" << endl;
        cout << APPNAME << "interface: " << alldevs->name << endl;
        if (alldevs->description != NULL)
          cout << APPNAME << "description: " << alldevs->description << endl;
        else
          cout << APPNAME << "no description available" << endl;
        if (alldevs->addresses != NULL)
        {
          cout << APPNAME << "addresses not NULL" << endl;
          if (alldevs->addresses->addr != NULL)
            switch (alldevs->addresses->addr->sa_family)
            {
              case AF_INET:
                cout << APPNAME << "I'm AF_INET" << endl;
                ai = (struct sockaddr_in *)alldevs->addresses->addr;
                cout << inet_ntoa(ai->sin_addr) << ":" << ai->sin_addr.s_addr << endl;
                break;
            }
          else
            cout << APPNAME << "addr is NULL" << endl;
          if (alldevs->addresses->netmask != NULL)
            switch (alldevs->addresses->addr->sa_family)
            {
              case AF_INET:
                cout << APPNAME << "I'm AF_INET" << endl;
                ai = (struct sockaddr_in *)alldevs->addresses->netmask;
                cout << inet_ntoa(ai->sin_addr) << ":" << ai->sin_addr.s_addr << endl;
                break;
            }
          else
            cout << APPNAME << "netmask is NULL" << endl;
          if (alldevs->addresses->broadaddr != NULL)
            switch (alldevs->addresses->addr->sa_family)
            {
              case AF_INET:
                cout << APPNAME << "I'm AF_INET" << endl;
                ai = (struct sockaddr_in *)alldevs->addresses->broadaddr;
                cout << inet_ntoa(ai->sin_addr) << ":" << ai->sin_addr.s_addr << endl;
                break;
            }
          else
            cout << APPNAME << "broadaddr is NULL" << endl;
        }
        alldevs = alldevs->next;
        cout << endl;
        fflush(stderr);
      }*/
      pcap_freealldevs(alldevs);
    }
    else
    {
      FILE *fp;
      if ((fp = fopen(argv[1], "r")) == NULL)
      {
        cerr << endl << APPNAME << "The file '" << argv[1] << "' doesn't exist!" << endl;
        fflush(stderr);
        m_returnValue = XML_FILE_DOES_NOT_EXIST;
      }
      else
      {
        fclose(fp);
        ParseConfig *config = new ParseConfig(argv[1]);
        if (config->error())
        {
          cerr << endl << APPNAME << "Error parsing the XML file " << argv[1] << ": check its tag syntax!" << endl;
          fflush(stderr);
          char str[100];
          sprintf(str, "The XML file has a syntax error or file '%s' doesn't exist!", argv[1]);
          logging(str);
          m_returnValue = XML_FILE_PARSING_ERROR;
        }
        else
        {
          initSniff(config);
          startSniffing(config);
          logging( "quitting..." );
        }
      }
    }
  }
  else
  {
    cerr << APPNAME << "Usage: 'ksniff xml-config-file' OR 'ksniff devs'" << endl;
    m_returnValue = XML_FILE_DOES_NOT_EXIST;
  }

  cout << APPNAME << "Quitting with error code: " << m_returnValue << endl;
  fflush(stderr);

  return m_returnValue;
}
