/* 
logpp (Log PreProcessor) 0.16 - tools.c
Copyright (C) 2006-2008 Risto Vaarandi

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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "common.h"
#include "logpp.h"

/* print_version() prints the version information */

void
print_version(void)
{
  printf("\
logpp (Log PreProcessor) 0.16\n\
Copyright (C) 2006-2008 Risto Vaarandi\n\
This is free software.  You may redistribute copies of it under the terms of\n\
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n\
There is NO WARRANTY, to the extent permitted by law.\n\n\
");
#ifdef HAVE_LIBPCRE
  printf("Compiled with PCRE support (libpcre %s)\n", pcre_version());
#else
  printf("Compiled with POSIX RE support\n");
#endif
}

/* print_usage() prints the usage information */

void
print_usage(char *progname)
{
  printf("\
Usage: %s [OPTION]... CONFFILE...\n\
Options:\n\
-b N  read/write by N-byte blocks during all IO operations\n\
-d    run as a daemon\n\
-f S  use facility S for internal syslog messages\n\
-h    print usage information\n\
-i N  keep N lines in the input buffer of input source\n\
-l S  use logging level S for internal syslog/stderr messages\n\
-p S  store the process ID to file S\n\
-r T  attempt to reopen closed input/output file after T seconds\n\
-s T  sleep for T microseconds if no data were read from input sources\n\
-t S  use tag S for all syslog messages\n\
-v    print version information\n\
", progname);
}

/* get_options() parses the command line and processes the options,
   returning the argv index pointing to the first non-option */

int
get_options(int argc, char **argv)
{
  extern char *optarg;
  extern int optind, opterr, optopt;
  long lconv;
  int c, iconv;
  char *end;

  opterr = 0;

  while ((c = getopt(argc, argv, "b:df:hi:l:p:r:s:t:v")) != -1) {
    switch (c) {
     case 'b':
      lconv = strtol(optarg, &end, 10);
      if (*end || lconv < 1)
        fprintf(stderr, "Illegal value %s for option -b\n", optarg);
      else BLOCKSIZE = lconv;
      break;
     case 'd':
      DAEMON = 1;
      break;
     case 'f':
      iconv = syslog_facility(optarg, strlen(optarg));
      if (iconv < 0)
        fprintf(stderr, "Illegal value %s for option -f\n", optarg);
      else FACILITY = iconv;
      break;
     case 'h':
      print_usage(argv[0]);
      exit(0);
     case 'i':
      lconv = strtol(optarg, &end, 10);
      if (*end || lconv < 1)
        fprintf(stderr, "Illegal value %s for option -i\n", optarg);
      else IBUFSIZE = lconv;
      break;
     case 'l':
      iconv = syslog_level(optarg, strlen(optarg));
      if (iconv < 0)
        fprintf(stderr, "Illegal value %s for option -l\n", optarg);
      else LOGLEVEL = iconv;
      break;
     case 'p':
      PIDFILE = optarg;
      break;
     case 'r':
      lconv = strtol(optarg, &end, 10);
      if (*end || lconv < 1)
        fprintf(stderr, "Illegal value %s for option -r\n", optarg);
      else REOPENINT = lconv;
      break;
     case 's':
      lconv = strtol(optarg, &end, 10);
      if (*end || lconv < 1)
        fprintf(stderr, "Illegal value %s for option -s\n", optarg);
      else {
        SLEEPSEC = lconv / 1000000;
        SLEEPUSEC = lconv % 1000000;
      }
      break;
     case 't':
      strncpy(TAG, optarg, TAGLEN - 1);
      TAG[TAGLEN - 1] = 0;
      break;
     case 'v':
      print_version();
      exit(0);
     case '?':
      fprintf(stderr, "Unknown option -%c\n", optopt);
    }
  }

  return optind;
}

/* daemonize() turns the process into a daemon, returning 1 on success 
   and 0 on failure */

int
daemonize(void)
{
  struct sigaction act;
  int fd, i;

  /* become a session leader without a controlling terminal with fork()
     and setsid(), then fork() again to ensure the process can never
     regain a controlling terminal (SIGHUP is blocked because the first
     child could send it to the second on exit) */

  switch (fork()) {
   case -1:
    log_msg(LOG_ERR, "fork() error (%s)", strerror(errno));
    return 0;
   case 0:
    break;
   default:
    exit(0);
  }

  if (setsid() == -1) {
    log_msg(LOG_ERR, "setsid() error (%s)", strerror(errno));
    return 0;
  }

  act.sa_handler = SIG_IGN;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;

  if (sigaction(SIGHUP, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  switch (fork()) {
   case -1:
    log_msg(LOG_ERR, "fork() error (%s)", strerror(errno));
    return 0;
   case 0:
    break;
   default:
    exit(0);
  }

  /* change to the root directory for not preventing future unmounts */

  while (chdir("/") == -1) {
    if (errno == EINTR) continue;
    log_msg(LOG_ERR, "chdir(/) error (%s)", strerror(errno));
    return 0;
  }

  /* redirect stdin, stdout and stderr to /dev/null */

  for (;;) {
    fd = open("/dev/null", O_RDWR);
    if (fd != -1) break;
    if (errno == EINTR) continue;
    log_msg(LOG_ERR, "open(/dev/null) error (%s)", strerror(errno));
    return 0;
  }

  for (i = 0; i < 3; ++i)
    while (dup2(fd, i) == -1) {
      if (errno == EINTR) continue;
      log_msg(LOG_ERR, "dup2() error (%s)", strerror(errno));
      return 0;
    }

  if (fd > 2) while (close(fd) == -1 && errno == EINTR);

  return 1;
}

/* store_pid() stores the process id to PIDFILE, returning 1 on success 
   and 0 on failure */

int
store_pid(void)
{
  FILE *file;

  log_msg(LOG_DEBUG, "Storing process ID to file %s", PIDFILE);

  file = fopen(PIDFILE, "w");

  if (!file) {
    log_msg(LOG_ERR, "Can't open pid file %s (%s)", PIDFILE, strerror(errno));
    return 0;
  }

  fprintf(file, "%d\n", (int) getpid());
  fclose(file);

  return 1;
}

/* log_msg() logs a message to syslog or stderr, depending whether the
   program was started as a daemon or not. If the level of the message
   is lower than LOGLEVEL, the message will not be logged. */

void
log_msg(int level, const char *format, ...)
{
  va_list args;
#ifndef HAVE_VSYSLOG
  char msg[1024];
#endif

  if (level > LOGLEVEL) return;
  va_start(args, format);
  if (DAEMON) {
#ifndef HAVE_VSYSLOG
    vsnprintf(msg, sizeof(msg), format, args);
    syslog(level, "%s", msg);
#else
    vsyslog(level, format, args);
#endif
  } else { 
    vfprintf(stderr, format, args);
    fprintf(stderr, "\n");
  }
  va_end(args);
}

/* syslog_facility() converts the syslog facility string to the facility 
   code, returning -1 if no match was found */

int
syslog_facility(char *name, size_t n)
{
  /* according to posix, these facilities should be present */
  if (!strncasecmp(name, "local0", n)) return LOG_LOCAL0;
  if (!strncasecmp(name, "local1", n)) return LOG_LOCAL1;
  if (!strncasecmp(name, "local2", n)) return LOG_LOCAL2;
  if (!strncasecmp(name, "local3", n)) return LOG_LOCAL3;
  if (!strncasecmp(name, "local4", n)) return LOG_LOCAL4;
  if (!strncasecmp(name, "local5", n)) return LOG_LOCAL5;
  if (!strncasecmp(name, "local6", n)) return LOG_LOCAL6;
  if (!strncasecmp(name, "local7", n)) return LOG_LOCAL7;
  if (!strncasecmp(name, "user", n)) return LOG_USER;

  /* support other facilities if they are present */
#ifdef LOG_AUTH
  if (!strncasecmp(name, "auth", n)) return LOG_AUTH;
#endif

#ifdef LOG_AUTHPRIV
  if (!strncasecmp(name, "authpriv", n)) return LOG_AUTHPRIV;
#endif

#ifdef LOG_CRON
  if (!strncasecmp(name, "cron", n)) return LOG_CRON;
#endif

#ifdef LOG_DAEMON
  if (!strncasecmp(name, "daemon", n)) return LOG_DAEMON;
#endif

#ifdef LOG_FTP
  if (!strncasecmp(name, "ftp", n)) return LOG_FTP;
#endif

#ifdef LOG_KERN
  if (!strncasecmp(name, "kern", n)) return LOG_KERN;
#endif

#ifdef LOG_LPR
  if (!strncasecmp(name, "lpr", n)) return LOG_LPR;
#endif

#ifdef LOG_MAIL
  if (!strncasecmp(name, "mail", n)) return LOG_MAIL;
#endif

#ifdef LOG_NEWS
  if (!strncasecmp(name, "news", n)) return LOG_NEWS;
#endif

#ifdef LOG_NTP
  if (!strncasecmp(name, "ntp", n)) return LOG_NTP;
#endif

#ifdef LOG_SYSLOG
  if (!strncasecmp(name, "syslog", n)) return LOG_SYSLOG;
#endif

#ifdef LOG_UUCP
  if (!strncasecmp(name, "uucp", n)) return LOG_UUCP;
#endif

  return -1;
}

/* syslog_level() converts the syslog level string to the level
   code, returning -1 if no match was found */

int
syslog_level(char *name, size_t n)
{
  if (!strncasecmp(name, "emerg", n)) return LOG_EMERG;
  if (!strncasecmp(name, "alert", n)) return LOG_ALERT;
  if (!strncasecmp(name, "crit", n)) return LOG_CRIT;
  if (!strncasecmp(name, "err", n)) return LOG_ERR;
  if (!strncasecmp(name, "warning", n)) return LOG_WARNING;
  if (!strncasecmp(name, "notice", n)) return LOG_NOTICE;
  if (!strncasecmp(name, "info", n)) return LOG_INFO;
  if (!strncasecmp(name, "debug", n)) return LOG_DEBUG;
  return -1;
}

/* signal_handler() is a signal handler that sets the global flag 
   variable corresponding to the signal number */

void
signal_handler(int sig)
{
  switch(sig) {
   case SIGHUP:
    GOT_SIGHUP = 1;
    break;
   case SIGTERM:
    GOT_SIGTERM = 1;
    break;
   case SIGUSR1:
    GOT_SIGUSR1 = 1;
    break;
   case SIGUSR2:
    GOT_SIGUSR2 = 1;
    break;
   case SIGCHLD:
    GOT_SIGCHLD = 1;
  }
}

/* set_sighandlers() sets a handler for relevant signals, returning 1
   on success and 0 on failure */

int
set_sighandlers(void)
{
  struct sigaction act;

  /* the process reacts to the following signals */
  act.sa_handler = signal_handler;
  sigemptyset(&act.sa_mask);
  act.sa_flags = SA_NOCLDSTOP;

  if (sigaction(SIGHUP, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  if (sigaction(SIGTERM, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  if (sigaction(SIGUSR1, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  if (sigaction(SIGUSR2, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  if (sigaction(SIGCHLD, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  /* the process ignores SIGPIPE, in order to prevent termination
     when a careless child process closes the communication pipe */
  act.sa_handler = SIG_IGN;

  if (sigaction(SIGPIPE, &act, 0) == -1) {
    log_msg(LOG_ERR, "sigaction() error (%s)", strerror(errno));
    return 0;
  }

  return 1;
}

/* get_time() returns the current system time */

time_t
get_time(void)
{
  struct timeval time;

  if (gettimeofday(&time, 0) == -1) {
    log_msg(LOG_CRIT, "Can't get system time (%s), exiting", strerror(errno));
    exit(1);
  }
  return time.tv_sec;
}

/* my_malloc() calls malloc(), returning the pointer to the allocated
   memory block on success and terminating the process on failure; if 
   'num' is 0, malloc() is not called and 0 is returned, since on many 
   platforms malloc(0) allocates 1-byte block */

void *
my_malloc(size_t num)
{
  void *ptr;

  if (!num) return 0;
  if ((ptr = malloc(num))) return ptr;
  log_msg(LOG_CRIT, "Out of memory, exiting");
  exit(1);
}

/* my_free() calls free(), freeing allocated memory block pointed by 
   'ptr'; if 'ptr' is 0, the function returns without calling free(), 
   since on some platforms free(0) produces unexpected results */

void
my_free(void *ptr)
{
  if (!ptr) return;
  free(ptr);
}

/* create_config_lists() loads configuration data from configuration
   files into memory and arranges the data into lists */

void
create_config_lists(void)
{
  int i;

  INPUTLIST = 0;
  OUTPUTLIST = 0;
  FILTERLIST = 0;
  FLOWLIST = 0;
 
  for (i = 0; i < CFLSIZE; ++i) 
    if (!read_config(CFLIST[i])) 
      log_msg(LOG_ERR, "No valid configuration in file %s", CFLIST[i]);
}

/* free_config_lists() destroys the configuration lists */

void 
free_config_lists(void)
{
  struct input *input, *input2;
  struct fl_elem *elem, *elem2;
  struct output *output, *output2;
  struct filter *filter, *filter2;
  struct flow *flow, *flow2;
  size_t i, j;

  input = INPUTLIST;
  while (input) {
    my_free(input->name);
    for (i = 0; i < input->srcl_size; ++i) my_free(input->srclist[i].name);
    my_free(input->srclist);
    elem = input->flowlist;
    while (elem) {
      elem2 = elem->next;
      my_free(elem);
      elem = elem2;
    }
    input2 = input->next;
    my_free(input);
    input = input2;
  }
  INPUTLIST = 0;

  output = OUTPUTLIST;
  while (output) {
    my_free(output->name);
    for (i = 0; i < output->dstl_size; ++i) my_free(output->dstlist[i].name);
    my_free(output->dstlist);
    output2 = output->next;
    my_free(output);
    output = output2;
  }
  OUTPUTLIST = 0;

  filter = FILTERLIST;
  while (filter) {
    my_free(filter->name);
    for (i = 0; i < filter->condl_size; ++i) {
      my_free(filter->condlist[i].str);
      if (filter->condlist[i].num) {
#ifdef HAVE_LIBPCRE
        pcre_free(filter->condlist[i].regexp);
        if (filter->condlist[i].extra) pcre_free(filter->condlist[i].extra);
#else
        regfree(&filter->condlist[i].regexp);
#endif
      }
      for (j = 0; j < filter->condlist[i].templ_size; ++j)
        my_free(filter->condlist[i].template[j].str);
      my_free(filter->condlist[i].template);
      my_free(filter->condlist[i].templ_str);
    }
    my_free(filter->condlist);
    filter2 = filter->next;
    my_free(filter);
    filter = filter2;
  }
  FILTERLIST = 0;

  flow = FLOWLIST;
  while (flow) {
    my_free(flow->name);
    my_free(flow->flist);
    my_free(flow->olist);
    flow2 = flow->next;
    my_free(flow);
    flow = flow2;
  }
  FLOWLIST = 0;
}

/* dump_config_lists() dumps the configuration lists */

void 
dump_config_lists(void)
{
  struct input *input;
  struct output *output;
  struct filter *filter;
  struct flow *flow;
  size_t i;

  for (input = INPUTLIST; input; input = input->next) {
    log_msg(LOG_INFO, "input %s {", input->name);
    for (i = 0; i < input->srcl_size; ++i) 
      log_msg(LOG_INFO, "file %s (descriptor %d, %lld lines read)",
                        input->srclist[i].name, input->srclist[i].fd, 
                        input->srclist[i].counter);
    log_msg(LOG_INFO, "}");
  }

  for (output = OUTPUTLIST; output; output = output->next) {
    log_msg(LOG_INFO, "output %s {", output->name);
    for (i = 0; i < output->dstl_size; ++i) 
     switch (output->dstlist[i].type) {
      case SRCDST_FILE:
       log_msg(LOG_INFO, "file %s (descriptor %d, %lld events written)",
                         output->dstlist[i].name, output->dstlist[i].fd, 
                         output->dstlist[i].counter);
       break;
      case SRCDST_SYSLOG:
       log_msg(LOG_INFO, "syslog %s (%lld events written)",
                         output->dstlist[i].name, output->dstlist[i].counter);
       break;
      case SRCDST_EXEC:
       log_msg(LOG_INFO, "exec %s (%lld events written)",
                         output->dstlist[i].name, output->dstlist[i].counter);
     }
    log_msg(LOG_INFO, "}");
  }

  for (filter = FILTERLIST; filter; filter = filter->next) {
    log_msg(LOG_INFO, "filter %s {", filter->name);
    for (i = 0; i < filter->condl_size; ++i) {
      if (filter->condlist[i].num) {
        if (filter->condlist[i].negative) 
          log_msg(LOG_INFO, "nregexp%d %s (%lld matches)", 
                            filter->condlist[i].num, filter->condlist[i].str,
                            filter->condlist[i].counter);
        else
          log_msg(LOG_INFO, "regexp%d %s (%lld matches)", 
                            filter->condlist[i].num, filter->condlist[i].str,
                            filter->condlist[i].counter);
      }
      else log_msg(LOG_INFO, "tvalue %s (%lld matches)", 
                             filter->condlist[i].str, 
                             filter->condlist[i].counter);
      if (filter->condlist[i].templ_str)
        log_msg(LOG_INFO, "template %s", filter->condlist[i].templ_str);
    }
    log_msg(LOG_INFO, "}");
  }

  for (flow = FLOWLIST; flow; flow = flow->next) {
    log_msg(LOG_INFO, "flow %s {", flow->name);
    for (i = 0; i < flow->il_size; ++i)
      log_msg(LOG_INFO, "input %s", flow->ilist[i]->name); 
    for (i = 0; i < flow->ol_size; ++i)
      log_msg(LOG_INFO, "output %s", flow->olist[i]->name); 
    for (i = 0; i < flow->fl_size; ++i)
      log_msg(LOG_INFO, "filter %s", flow->flist[i]->name); 
    log_msg(LOG_INFO, "}");
  }
}
