/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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.
 *
 * $Id: log.c,v 1.19.2.4.2.3 2001/09/19 19:22:16 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.7
 * Notes:
 *
 ***************************************************************************/

#include <zorp/log.h>
#include <zorp/thread.h>

#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "Zorp"

GHashTable *class_hash = NULL;
GStaticMutex log_lock = G_STATIC_MUTEX_INIT;
unsigned int verbose_level = 3;
unsigned int log_tags;
gchar *logspec;

gchar fake_session_id[256] = "(noname/nosession)";

#if 0
/*+

  Format and send a debug message with the given verbosity level. 

  Parameters:
    level         the minimal verbosity_level when this message is sent
    format        printf style format specifiers

  Returns: 
    none

  +*/
void 
z_debug(int level, char *format, ...)
{
  va_list l;
  
  va_start(l, format);
  z_logv("core.debug", level, format, l);
  va_end(l);
}

/*+

  Format and send a simple message with the given verbosity level. 

  Parameters:
    level         the minimal verbosity_level when this message is sent
    format        printf style format specifiers

  Returns: 
    none

  +*/
void
z_message(int level, char *format, ...)
{
  va_list l;

  va_start(l, format);
  z_logv("core.message", level, format, l);
  va_end(l);
}

/*+

  Format and send a warning message with the given verbosity level. 

  Parameters:
    level         the minimal verbosity_level when this message is sent
    format        printf style format specifiers

  Returns: 
    none

  +*/
void 
z_warning(int level, char *format, ...)
{
  va_list l;

  if (level <= verbose_level)
    {
      va_start(l, format);
      z_logv("core.warning", level, format, l);
      va_end(l);
    }
}

/*+

  Format and send a fatal error message with the given verbosity level. 

  Parameters:
    level         unused, just to conform to the rest of z_* log functions
    format        printf style format specifiers

  Returns: 
    none

  +*/
void 
z_fatal(int level, char *format, ...)
{
  va_list l;

  va_start(l, format);
  z_logv("core.fatal", level, format, l);
  va_end(l);
  abort();
}
#endif

gboolean
z_log_enabled(gchar *class, int level)
{
  gint verbose;

  if (!class_hash)
    {
      return TRUE;
    }
  g_static_mutex_lock(&log_lock);
  verbose = (gint) g_hash_table_lookup(class_hash, class);
  if (!verbose)
    {
      verbose = z_log_register_class(class);
      if (verbose >= 0)
        g_hash_table_insert(class_hash, class, (gpointer) verbose + 1);
      else
        {
          g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Invalid syntax in logspec line, class=%s", class);
          /* hack to let this message out */
          sleep(1);
          exit(1);
        }
    }
  else
    verbose--;
  g_static_mutex_unlock(&log_lock);
  
  return (level <= verbose);
}

/*+
  
  Used by the Python interface to send logs with the given verbosity
  level and priority.

  Parameters:
    level        verbosity level
    log_flags    log priority
    msg          formatted log message

  Returns:
    none

  +*/
void
z_logv(gchar *class, int level, gchar *format, va_list ap)
{
  int saved_errno = errno;
  
  if (z_log_enabled(class, level))
    {
      if (log_tags)
        {
          gchar msgbuf[2048];
      
          vsnprintf(msgbuf, sizeof(msgbuf), format, ap);
#if ENABLE_TRACE
          g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%p -> %s(%d): %s", g_thread_self(), class, level, msgbuf);
#else
          g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%s(%d): %s", class, level, msgbuf);
#endif
        }
      else
        {
          g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, format, ap);
        }
    }
  errno = saved_errno;
}

void
z_llog(gchar *class, int level, gchar *format, ...)
{
  va_list l;

  va_start(l, format);
  z_logv(class, level, format, l);
  va_end(l);
}

/*+ log handler function to send messages to syslog +*/
static void
z_log_func(const gchar *log_domain,
	   GLogLevelFlags log_flags,
	   const gchar *message,
	   gpointer user_data)
{
  int pri = LOG_INFO;
  if (log_flags & G_LOG_LEVEL_DEBUG)
    pri = LOG_DEBUG;
  else if (log_flags & G_LOG_LEVEL_WARNING)
    pri = LOG_WARNING;
  else if (log_flags & G_LOG_LEVEL_ERROR)
    pri = LOG_ERR;
  syslog(pri, "%s", message);
}

void
z_fetch_stderr_thread(gpointer arg)
{
  gint fd = (gint) arg;
  FILE *log;
  gchar line[1024];
  
  log = fdopen(fd, "r");
  if (!log)
    {
      z_log(NULL, CORE_ERROR, 3, "Error opening stderr pipe; error='%m'");
      return;
    }
  line[1023] = 0;
  while (fgets(line, sizeof(line) - 1, log))
    {
      int len = strlen(line);
      if (len == 0)
        continue;
      if (line[len - 1] == '\n')
        line[len - 1] = 0;
      if (line[0] == 0)
        continue;
      /*NOLOG*/
      z_log(NULL, CORE_STDERR, 3, "%s", line);
    }
}

static gboolean
z_log_glob_match(gchar *glob, gchar *class)
{
  gchar *p1, *p2;
  gint len1, len2;

  p1 = strchr(glob, '.');
  p2 = strchr(class, '.');

  while (p1 && p2)
    {
      len1 = p1 - glob;
      len2 = p2 - class;
      if (((len1 != 1) || (memcmp(glob, "*", 1) != 0)) &&
          ((len1 != len2) || memcmp(glob, class, len1) != 0))
        return FALSE;
      glob = p1 + 1;
      class = p2 + 1;

      p1 = strchr(glob, '.');
      p2 = strchr(class, '.');
    }
  if (p1)
    len1 = p1 - glob;
  else
    len1 = strlen(glob);
  if (p2)
    len2 = p2 - class;
  else
    len2 = strlen(class);
  if (((len1 != 1) || (memcmp(glob, "*", 1) != 0)) &&
      ((len1 != len2) || memcmp(glob, class, len1) != 0))
    return FALSE;
  glob += len1;
  class += len2;
  if (strlen(glob) > strlen(class))
    return FALSE;
  return TRUE;
}

int
z_log_register_class(gchar *class)
{
  gchar *src = logspec;
  gint level = verbose_level, new_level;

  while (*src)
    {
      gchar *glob, *num, *colon, *end;

      while (*src == ',' || *src == ' ')
        src++;

      glob = src;
      while (isalnum(*src) || *src == '.' || *src == '*')
        src++;

      if (*src != ':')
        {
          /* invalid log spec */
          return -1;
        }
      colon = src;
      src++;
      num = src;

//      while (isdigit(*src))
//        src++;

      *colon = 0;
      new_level = strtoul(num, &end, 10);
      if (z_log_glob_match(glob, class))
        level = new_level;
      *colon = ':';
      src = end;
      while (*src && *src != ',')
        src++;
    }
  return level;
}

void
z_log_clear_hash()
{
  g_hash_table_destroy(class_hash);
  class_hash = g_hash_table_new(g_str_hash, g_str_equal);
}

/*+ 

  Initialize Zorp logging with the given name.

  Parameters:
    
    ls           --    log specification
    syslog_name  --    name to use in syslog messages
    use_syslog   --    enable sending messages to syslog
    log_tags     --    log message tags

  Returns:
    none

  +*/
void
z_log_init(gchar *ls, gchar *syslog_name, gboolean use_syslog, gboolean lt)
{
  logspec = ls;
  log_tags = lt;
  class_hash = g_hash_table_new(g_str_hash, g_str_equal);
  if (use_syslog)
    {
      int grab[2];
      
      openlog(syslog_name, LOG_NDELAY | LOG_PID, ZORP_SYSLOG_FACILITY);
      g_log_set_handler(G_LOG_DOMAIN, 0xff, z_log_func, NULL);

      if (pipe(grab) < 0)
        {
          z_log(NULL, CORE_ERROR, 3, "Error creating stderr-syslog pipe;");
        }
      close(0);
      dup2(grab[1], 1);
      dup2(grab[1], 2);
      if (grab[1] != 2 && grab[1] != 1)
        close(grab[1]);

      z_thread_new("stderr log thread", z_fetch_stderr_thread, (gpointer) grab[0]);
    }
}

void
z_log_destroy(void)
{
  g_hash_table_destroy(class_hash);
}

