/******************************************************************************
 * IP protocols logger					                      *
 ******************************************************************************
 * Written by Hugo Haas (hugo@debian.org)                                     *
 * Based on the idea of Mike Edulla (medulla@infosoc.com)                     *
 * Copyright (C) 1998-1999 Hugo Haas                                          *
 * See the HISTORY file for the changes					      *
 *                                                                            *
 * Use of getopt_long for argument parsing: Etienne Bernard 1999              *
 ******************************************************************************/

/*
 *  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 <stdio.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <getopt.h>

#include "defines.h"
#include "configuration.h"
#include "icmp.h"
#include "tcp.h"
#include "udp.h"
#include "log.h"
#include "netutils.h"
#include "filter.h"
#include "pidfile.h"

/* Logging mechanism */
struct loginfo log;

/* Do we have to run as a daemon? */
int run_as_daemon = TRUE;

/* Configuration file to use */
char *configuration_file = NULL;

/* Protocols that we log */
extern unsigned short log_protocols;

/* Expiration of DNS data */
extern unsigned int dns_expire;

/* Threads */
pthread_t icmp_t, tcp_t, udp_t;

/*
 * do_help
 *
 * Display the command line options
 */

void do_help() {

  printf("ippl version %s \n\n", VERSION);
  printf("\t-n, --nodaemon:\tdo not run as a daemon and write information to stdout\n");
  printf("\t-c, --config <file>:\tspecify an alternate configuration file\n");
  printf("\t-h, --help:\tdisplay this help and exit\n");
  printf("\nPlease report bugs to ippl@via.ecp.fr\n");
}

/*
 * run_thread
 *
 * Run a thread. Certain signals are blocked.
 *
 */

void run_thread(pthread_t *t, void *(*function)(void*), void *param) {
  pthread_attr_t attr_t;
  sigset_t newset, oldset ;

  sigfillset(&newset);
  pthread_sigmask(SIG_BLOCK, &newset, &oldset);

  pthread_attr_init(&attr_t);
  pthread_attr_setdetachstate(&attr_t, PTHREAD_CREATE_DETACHED);
  pthread_create(t, &attr_t, function, param);
  pthread_attr_destroy(&attr_t);

  pthread_sigmask(SIG_UNBLOCK, &newset, &oldset);
}


/*
 * start_all_threads
 *
 * Parses the config file and launches the threads
 */
void start_all_threads() {
  struct passwd *account;
  extern struct loginfo icmp_log, tcp_log, udp_log;
  extern char *used_user;

  init_loginfo(&icmp_log);
  init_loginfo(&tcp_log);
  init_loginfo(&udp_log);

  parse_config_file(configuration_file);
  
  account = getpwnam(used_user);
  if (!account) {
    log.log(log.level_or_fd, "WARNING: I cannot find the \"%s\" account. Not spawning threads.", used_user);
    log_protocols = NONE;
    return;
  }

  if (!strcmp(used_user, "root")) {
    log.log(log.level_or_fd, "WARNING: Using root account to run threads!");
  }

  if (log_protocols == NONE) {
    log.log(log.level_or_fd, "WARNING: Nothing to log.");
    return;
  }
      
  initialize_dns_cache();

  if (log_protocols & RUN_ICMP) {
    icmp_log.open(&icmp_log.level_or_fd, icmp_log.file);
    run_thread(&icmp_t, log_icmp, (void *)account);
  }
      
  if (log_protocols & RUN_TCP) {
    tcp_log.open(&tcp_log.level_or_fd, tcp_log.file);
    run_thread(&tcp_t, log_tcp, (void *)account);
  }
      
  if (log_protocols & RUN_UDP) {
    udp_log.open(&udp_log.level_or_fd, udp_log.file);
    run_thread(&udp_t, log_udp, (void *)account);
  }

}  


/*
 * reload_configuration
 *
 * Stops the threads and reloads the configuration
 */
void reload_configuration() {
  extern pthread_mutex_t log_mutex, service_mutex, dns_mutex, r_mux, w_mux;
  extern pthread_cond_t w_cond;
  extern int readers;
  extern struct loginfo icmp_log, tcp_log, udp_log;
  extern pthread_mutex_t log_mutex;
  extern int icmp_socket, tcp_socket, udp_socket;

  /* We get all the mutexes that could block the threads,
   * because we must be sure that no thread is in a
   * critical portion of the code. */

  /* First, make sure that no packet is being logged
   * Let's block acces to the filter */
  pthread_mutex_lock(&w_mux);
  pthread_mutex_lock(&r_mux);
  while (readers > 0)
    pthread_cond_wait(&w_cond, &r_mux);
  /* Then, acquire all the others so that you are sure we are in a safe
   * position */
  /* dns cache mutex */
  pthread_mutex_lock(&dns_mutex);
  /* Service query mutex */
  pthread_mutex_lock(&service_mutex);
  /* Logging mutex */
  pthread_mutex_lock(&log_mutex);

  /* Now we can safely kill the threads. */
  if (log_protocols & RUN_ICMP)
    pthread_cancel(icmp_t);    
  if (log_protocols & RUN_TCP)
    pthread_cancel(tcp_t);
  if (log_protocols & RUN_UDP)
    pthread_cancel(udp_t);

  /* Close the sockets */
  close(icmp_socket);
  close(tcp_socket);
  close(udp_socket);

  /* Close log files */
  icmp_log.close(&icmp_log.level_or_fd, icmp_log.file);
  tcp_log.close(&tcp_log.level_or_fd, tcp_log.file);
  udp_log.close(&udp_log.level_or_fd, udp_log.file);

  /* We release the mutexes as soon as possible */
  pthread_mutex_unlock(&log_mutex);
  pthread_mutex_unlock(&service_mutex);
  pthread_mutex_unlock(&dns_mutex);
  pthread_mutex_unlock(&r_mux);
  pthread_mutex_unlock(&w_mux);

  /* We destroy previous configuration */
  destroy_filter();

  /* And we restart all the threads */
  start_all_threads();
}

/*
 * parse_arguments
 *
 * Parse the arguments given on the command line
 */

void parse_arguments(int argc, char **argv) {
  char path[PATH_MAX];
  static struct option long_options[] =
  {
    { "help", 0, 0, 0 },
    { "nodaemon", 0, &run_as_daemon, FALSE },
    { "config", 1, 0, 0 },
    { 0, 0, 0, 0 }
  };

  int c, option_index = 0;

  while ((c = getopt_long(argc, argv, "hnlrc:", long_options,
                          &option_index))!=-1) {
    switch (c) {
    case 0:
      switch (option_index) {
      case 0:
        do_help();
        exit(0);
      case 2:
        configuration_file = strdup(optarg);
        break;
      }
      break;
    case 'h':
      do_help();
      exit(0);
    case 'n':
      run_as_daemon = FALSE;
      break;
    case 'c':
      configuration_file = strdup(optarg);
      break;
    case '?':
      do_help();
      exit(1);
    }
  }

  /* Get absolute path for configuration file */
  if (configuration_file) {
    if (configuration_file[0] != '/') {
      getcwd(path, PATH_MAX);
      strcat(path, "/");
      strcat(path, configuration_file);
      free(configuration_file);
      configuration_file = strdup(path);
    }
  }
}

/*
 * go_background
 *
 * Run as a daemon
 *
 * Based on iplogger (C) Mike Edulla + modifications by Topi Miettinen
 * PID file handling based on sysklogd (C) Martin Schulze
 */

void go_background() {

  /* Check PID */
  if (check_pid(PID_FILE)) {
    log.log(log.level_or_fd, "Already running. Exiting...");
    exit(1);
  }

  if (getppid() != 1) {
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
  }
  daemon(0, 0);
  umask(027);

  /* Write PID file */
  if (!write_pid(PID_FILE)) {
    log.log(log.level_or_fd, "Can't write pid.\n");
    exit(1);
  }

}

/*
 * die
 *
 * Function executed when the daemon is ended
 */

void die(int sig) {
  extern struct loginfo icmp_log, tcp_log, udp_log;
  extern pthread_mutex_t log_mutex;

  /* Stop threads and close log files */

  pthread_mutex_lock(&log_mutex);

  if (log_protocols & RUN_ICMP)
    pthread_cancel(icmp_t);    
  if (log_protocols & RUN_TCP)
    pthread_cancel(tcp_t);
  if (log_protocols & RUN_UDP)
    pthread_cancel(udp_t);

  icmp_log.close(&icmp_log.level_or_fd, icmp_log.file);
  tcp_log.close(&tcp_log.level_or_fd, tcp_log.file);
  udp_log.close(&udp_log.level_or_fd, udp_log.file);

  pthread_mutex_unlock(&log_mutex);

  if (run_as_daemon) {
    (void) remove_pid(PID_FILE);
  }

  log.log(log.level_or_fd, "IP Protocols Logger: stopped (signal %d).", sig);

  exit(0);
}

/*
 * sighup
 *
 * Function executed when we receive a SIHUP signal
 */
void sighup(int sig) {
  reload_configuration();
  log.log(log.level_or_fd, "IP Protocols Logger: reloaded configuration.");
  signal(SIGHUP, sighup);
}

/*
 * handle_signals
 *
 * Define how to handle the different signals
 */

void handle_signals(void) {

  signal(SIGTERM, die);
  if (!run_as_daemon) {
    signal(SIGINT, die);
  }
  signal(SIGHUP, sighup);
}

/*
 * MAIN
 */

int main(int argc, char **argv) {

  parse_arguments(argc, argv);

  setuid(0);
  if (geteuid() != 0) {
    printf("You must be root to run this program.\n");
    exit(1);
  }

  handle_signals();

  if (configuration_file == NULL)
    configuration_file = strdup(CONFIGURATION_FILE);

  init_loginfo(&log);
  log.open(&log.level_or_fd, log.file);

  if (run_as_daemon) {
    go_background();
  }

  start_all_threads();
  log.log(log.level_or_fd, "IP Protocols Logger: started.");

  for(;;) {
    sleep(dns_expire);
    initialize_dns_cache();
    refresh_filter();
  }

  /*@NOTREACHED@*/

  exit(0);
}
