/*
  init.c - ipfm initialisation functions
 
  (c) 1999      Andres Krapf <dae@via.ecp.fr>
  (c) 2000      Samuel Hocevar <sam@via.ecp.fr>
  (c) 2002      Robert Chéramy <robert@cheramy.net>

  200010 : sam   : pid file & daemon mode handling
  200011 : tibob : SIGUSR1 handling
  200012 : tibob : modifications to have a correct SIGHUP behaviour.
  200210 : tibob : command-line configurable pid file.
  
 */

/*
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "config.h"

#include <unistd.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#ifdef __OS_LINUX__
#include <getopt.h>
#endif /* __OS_LINUX__ */

#include "data.h"
#include "filter.h"
#include "init.h"
#include "pcap.h"
#include "utils.h"

char *device = NULL;
int promisc = DEFAULT_OPTIONS_PROMISC;
extern int SigHup;
extern int SigDump;
extern int checkdump;
extern ipfm_timezone tz;
extern struct AllLogsType *pAllLogs;
int run_as_daemon = 1;

int yyparse(void);
extern int line;
extern char *device;
extern struct OptionsType Options;
extern FILE *yyin;
extern int yyfailed;

struct OptionsType Options;

void print_help() {
  printf("ipfm version %s\n\n", VERSION);
#ifdef __OS_LINUX__
  printf("\t-n, --nodaemon:\tdo not run as a daemon\n");
  printf("\t-c <file>, --config <file>:\tspecify an alternate configuration file\n");
  printf("\t-h, --help:\tdisplay this help and exit\n");
  printf("\t-p <file>, --pid <file>:\tspecify an alternate pid file\n");
#else
  printf("\t-n:\tdo not run as a daemon\n");
  printf("\t-c <file>:\tspecify an alternate configuration file\n");
  printf("\t-h:\tdisplay this help and exit\n");
  printf("\t-p <file>:\tspecify an alternate pid file\n");
#endif /* __OS_LINUX__ */
  printf("\nPlease report bugs to ipfm@via.ecp.fr\n");
}

void ParseCmdLine(int argc, char * argv[]) {
  int ch;
#ifdef __OS_LINUX__
  int option_index = 0;
#endif /* __OS_LINUX__ */
  extern char *optarg;

#ifdef __OS_LINUX__
  static struct option long_options[] = {
    { "config", 1, NULL, 0 },
    { "help", 0, NULL, 0 },
    { "nodaemon", 0, &run_as_daemon, 0 },
    { "pidfile", 1, NULL, 0 },
    { NULL, 0, NULL, 0 }
  };
#endif /* __OS_LINUX__ */
	
  Options.ConfigFile = NULL;
  Options.PidFile = NULL;

#ifdef __OS_LINUX__
  while (EOF != (ch = getopt_long(argc, argv, "hnc:p:", long_options,
                                 &option_index))) {
#else
  while (EOF != (ch = getopt(argc, argv, "hnc:p:"))) {
#endif /* __OS_LINUX__ */
    switch(ch) {	  
#ifdef __OS_LINUX__
    case 0:
      switch (option_index) {
      case 0:
        Options.ConfigFile = xstrdup(optarg);
        break;
      case 1:
        print_help();
        exit(0);
        break;
      case 2:
        run_as_daemon = 0;
        break;
      case 3:
        Options.PidFile = xstrdup(optarg);
        break;
      }
      break;
#endif /* __OS_LINUX__ */
    case 'c':
      Options.ConfigFile = xstrdup(optarg);
      break;
    case 'n':
      run_as_daemon = 0;
      break;
    case 'p':
      Options.PidFile = xstrdup(optarg);
      break;
    case 'h':
    case '?':
      print_help();
      exit(0);
    }
  }
}

void InitOnce()
{
  signal(SIGHUP,  SigHupHandler);
  signal(SIGTERM, SigTermHandler);
  signal(SIGINT,  SigIntHandler);
  signal(SIGCHLD, SigChldHandler);
  signal(SIGUSR1, SigUsr1Handler);

  if (NULL == Options.ConfigFile) {
    Options.ConfigFile = xstrdup(DEFAULT_OPTIONS_CONFIGFILE);
  }
  if (NULL == Options.PidFile) {
    Options.PidFile = xstrdup(DEFAULT_OPTIONS_PIDFILE);
  }
}

void ReInit()
{
  struct AllLogsType *pNewLog;

  pNewLog = xmalloc (sizeof(struct AllLogsType));
  pNewLog->Next = NULL;
  pNewLog->Filter = NULL;
  pNewLog->Data = NULL;
  pNewLog->DataSize = 0;
  pNewLog->DumpInterval = DEFAULT_OPTIONS_DUMPINTERVAL;
  pNewLog->ClearInterval = DEFAULT_OPTIONS_CLEARINTERVAL;
  pNewLog->ClearCounter = DEFAULT_OPTIONS_CLEARCOUNTER;
  pNewLog->NextDump = ((time(NULL) / DEFAULT_OPTIONS_DUMPINTERVAL) + 1) * DEFAULT_OPTIONS_DUMPINTERVAL;
  pNewLog->LogFile = xstrdup(DEFAULT_OPTIONS_LOGFILE);
  pNewLog->Sort = DEFAULT_OPTIONS_SORT;
  pNewLog->SortFunc = NULL;
  pNewLog->ReverseLookup = DEFAULT_OPTIONS_REVERSELOOKUP;
  pNewLog->Append = DEFAULT_OPTIONS_APPEND;

  pAllLogs = pNewLog;

  tz = DEFAULT_OPTIONS_TIMEZONE;
  checkdump = 2;

  configure();

  TestLogs();

  if (NULL == device) {
    device = xstrdup(pcap_default_device());
  }

  if(!openpcap(device, promisc)) {
    fprintf(stderr, "[ipfm] Unable to open pcap descriptor on device %s\n", device);
    Exit(-1);
  }
}

void Init()
{
  InitOnce();
  ReInit();
}

void Clean()
{
  struct AllLogsType *pTempLog;
  struct AllLogsType *pTempLogFree;
  struct ipfm_filter *pTempRule;

/* clean the rules (deallocate the chained list) */
  
  for (pTempLog = pAllLogs; NULL != pTempLog;) {
    while (NULL != pTempLog->Filter) {
      pTempRule = pTempLog->Filter;
      pTempLog->Filter = pTempLog->Filter->next;
      if (pTempRule)
	xfree(pTempRule);
      else
	fprintf(stderr, "[init.c] BUG!\n");
    }
  
    /* clean the stats  */
    data_clear(pTempLog);

    pTempLogFree = pTempLog;
    pTempLog = pTempLog->Next;
    xfree(pTempLogFree);
  }
  pAllLogs = NULL;

/* close the pcap descriptor */
   closepcap();

  if (device) {
    xfree(device);
    device = NULL;
  }
}

void Exit(int errcode)
{
  Clean();

  if (Options.ConfigFile)
    xfree(Options.ConfigFile);

  if (run_as_daemon) {
    remove_pid (Options.PidFile);
  }

  exit(errcode);
}


void SigHupHandler(int a)
{
  SigHup = 1;

  /* For linux libc5 backward compatibility */
  /* This is dirty and ./configure should check for sysV or BSD behaviour */
  signal(SIGHUP, SigHupHandler);
}

/* 
 * SIGTERM and SIGINT dump the data and exit.
 */

void SigTermHandler(int a)
{
  struct AllLogsType *pTempLog;

  for (pTempLog = pAllLogs; NULL != pTempLog; pTempLog = pTempLog->Next) {
    data_dump(pTempLog);
    data_clear(pTempLog);
  }
  Exit(0);
}

void SigIntHandler(int a)
{
  struct AllLogsType *pTempLog;

  for (pTempLog = pAllLogs; NULL != pTempLog; pTempLog = pTempLog->Next) {
    data_dump(pTempLog);
    data_clear(pTempLog);
  }
  Exit(0);
}

/*
 * we don't want any zombies left, and we don't do anything special when 
 * children die
 */

void SigUsr1Handler(int a)
{
  SigDump = 1;

  /* For linux libc5 backward compatibility */
  /* This is dirty and ./configure should check for sysV or BSD behaviour */
  signal(SIGUSR1, SigUsr1Handler);
}

void SigChldHandler(int a)
{
  pid_t Pid = 1;
  int Status;
 
  while (Pid > 0)
    Pid = waitpid(-1, &Status, WNOHANG);

  /* For linux libc5 backward compatibility */
  /* This is dirty and ./configure should check for sysV or BSD behaviour */
  signal(SIGCHLD, SigChldHandler);
}

void TestLogs()
{
  struct AllLogsType *pTempLog;
  struct AllLogsType *pPrevLog;
  struct AllLogsType *pNextLog;

  pPrevLog = NULL;
  for (pTempLog = pAllLogs; NULL != pTempLog; pTempLog = pNextLog)
  {
    pNextLog = pTempLog->Next;
    if (NULL == pTempLog->Filter)
      /* wipe this entry out*/
    {
      if (pTempLog->LogFile) {
	xfree (pTempLog->LogFile);
      }

      if (NULL == pPrevLog) {
	pAllLogs = pTempLog->Next;
      } else {
	pPrevLog->Next = pTempLog->Next;
      }

      xfree (pTempLog);
    } else {
      /* this entry is kept */
      pPrevLog = pTempLog;
    }
  }
}


void configure() {
  yyin = fopen(Options.ConfigFile, "r");
  if (NULL == yyin) {
    printf("Cannot open configuration file %s.\n", Options.ConfigFile);
    exit(1);
  }

  line = 0;
  yyfailed = 0;

  if (0 != yyparse() || yyfailed != 0) {
    fprintf(stderr, "[ipfm] Error parsing configuration file. Exiting.\n");
    fclose(yyin);

    Exit(1);
  }
  
  fclose(yyin);
}

