/***************************************************************************
 *
 * Copyright (c) 2000 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: main.c,v 1.46.2.13.2.8 2001/09/24 13:47:00 bazsi Exp $
 *
 * Author  : bazsi
 * Auditor : kisza
 * Last version : 1.24
 * Notes   : 
 *
 ***************************************************************************/

#include <zorp/zorp.h>
#include <zorp/io.h>
#include <zorp/stream.h>
#include <zorp/policy.h>
#include <zorp/registry.h>
#include <zorp/thread.h>
#include <zorp/log.h>
#include <zorp/cfg.h>
#include <zorp/pyvars.h>
#include <zorp/freeq.h>
#include <zorp/cap.h>
#include <zorp/ssl.h>
#include <zorp/sysdep.h>

#include <sys/resource.h>
#include <signal.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#ifdef USE_DMALLOC
#include <dmalloc.h>
#endif

void
z_sigterm_handler(int signo)
{
  if (main_loop)
    z_main_quit(1);
  else
    exit(1);
}

void 
z_ignore_signal_handler(int signo)
{
}

#if ENABLE_STACKDUMP
void inline 
z_dump_backtrace(unsigned long eip, unsigned long first_ebp)
{
  /* NOTE: this is i386 specific */
  unsigned long *ebp;
  
  z_log(NULL, CORE_ERROR, 0, "retaddr=0x%lx, ebp=0x%lx", eip, first_ebp);
  
  ebp = (unsigned long *) first_ebp;
  while (ebp > (unsigned long *) &ebp && *ebp) 
    {
      z_log(NULL, CORE_ERROR, 0, "retaddr=0x%lx, ebp=0x%lx", *(ebp+1), *ebp);
      ebp = (unsigned long *) *ebp;
    }
}

void
z_fatal_signal_handler(int signo)
{
  struct sigcontext *p = (struct sigcontext *) (((char *) &p) + 16);

#if ENABLE_MEM_TRACE
  z_mem_trace_stats();
#endif
  z_log(NULL, CORE_ERROR, 0, "Signal (%d) received, stackdump follows; eax='%08lx', ebx='%08lx', ecx='%08lx', edx='%08lx', eip='%08lx'",
        signo, p->eax, p->ebx, p->ecx, p->edx, p->eip);
  z_dump_backtrace(p->eip, p->ebp);
  signal(signo, SIG_DFL);
  kill(getpid(), signo);
  exit(1);
}

#else

void
z_fatal_signal_handler(int signo)
{
  z_log(NULL, CORE_ERROR, 0, "Signal (%d) received", signo);
  exit(1);
}

#endif

int usr1_received = 0;
int usr2_received = 0;

void
z_sigusr1_handler(int signo)
{
  usr1_received = 1;
}

void
z_sigusr2_handler(int signo)
{
  usr2_received = 1;
}

void
z_write_pid_file(char *fname)
{
  FILE *fd = fopen(fname, "w");
  if (fd) 
    {
      fprintf(fd, "%d\n", getpid());
      fclose(fd);
    }
  else
    {
      fprintf(stderr, "Error opening pidfile; fname='%s', error='%m'\n", fname);
    }
}

void
z_remove_pid_file(char *fname)
{
  unlink(fname);
}

void
z_usage(void)
{
  puts("zorp [options] - start a zorp instance\n\n"
       "--as <name>         -a      use <name> as the name of this instance\n"
       "--policy <file>     -p      use <file> as policy\n"
       "--verbose [num]     -v      set verbosity to num\n"
       "--no-syslog         -l      log to console\n"
       "--log-spec <spec>   -s      set log specification to spec\n"
       "--log-tags          -T      log message tags in messages\n"
       "--pidfile <file>    -P      write pid to file\n"
       "--version           -V      query zorp version\n"
       "--threads <num>     -t      specify the maximum number of simultaneous threads\n"
       "--idle-threads <num>-I      specify the maximum number of simultaneous idle threads\n"
       "--uid <uid>         -u      uid to run as\n"
       "--gid <gid>         -g      gid to run as\n"
       "--caps <caps>       -C      capabilities to set\n"
       "--no-caps           -N      don't attempt to set capabilities\n");
}

#define ON_OFF_STR(x) (x ? "on" : "off")

void
z_version(void)
{
  printf("Zorp %s\n"
         "Compile-Date: %s %s\n"
         "Config-Date: %s\n"
         "Trace: %s\n"
         "Debug: %s\n"
         "Caps: %s\n"
         "PacketTrace: %s\n"
         "IPOptions: %s\n"
         "IPChains: %s\n"
         "Netfilter: %s\n"
         "Conntrack: %s\n"
         "IPFilter: %s\n",
         VERSION, __DATE__, __TIME__,
         ZORP_CONFIG_DATE,
         ON_OFF_STR(ENABLE_TRACE),
         ON_OFF_STR(ENABLE_DEBUG),
         ON_OFF_STR(ENABLE_CAPS),
         ON_OFF_STR(ENABLE_PACKET_TRACE),
         ON_OFF_STR(ENABLE_IPOPTIONS),
         ON_OFF_STR(ENABLE_IPCHAINS),
         ON_OFF_STR(ENABLE_NETFILTER),
         ON_OFF_STR(ENABLE_CONNTRACK),
         ON_OFF_STR(ENABLE_IPFILTER));
}

void
z_enable_core(void)
{
  struct rlimit limit;

  limit.rlim_cur = RLIM_INFINITY;
  limit.rlim_max = RLIM_INFINITY;

  chdir("/var/tmp");

  setrlimit(RLIMIT_CORE, &limit);
}

int 
main(int argc, char *argv[])
{
#ifdef HAVE_GETOPT_LONG
  struct option zorp_options[] = 
  {
    { "as", required_argument, NULL, 'a' },
    { "policy", required_argument, NULL, 'p' },
    { "verbose", required_argument, NULL, 'v' },
    { "debug", no_argument, NULL, 'd' },
    { "pidfile", required_argument, NULL, 'P' },
    { "no-syslog", no_argument, NULL, 'l' },
    { "help", no_argument, NULL, 'h' },
    { "version", no_argument, NULL, 'V' },
    { "log-spec", required_argument, NULL, 's' },
    { "logspec", required_argument, NULL, 's' },
    { "log-tags", no_argument, NULL, 'T' },
    { "threads", required_argument, NULL, 't' },
    { "idle-threads", required_argument, NULL, 'I' },
    { "uid", required_argument, NULL, 'u' },
    { "gid", required_argument, NULL, 'g' },
    { "chroot", required_argument, NULL, 'R' },
#if ENABLE_CAPS
    { "caps", required_argument, NULL, 'C' },
    { "no-caps", no_argument, NULL, 'N' },
#endif
    { NULL, 0, NULL, 0 }
  };
#endif
  int opt;
  gchar *instance_name = "zorp";
  gchar *policy_file = ZORP_POLICY_FILE;
  gchar *log_spec = "";
  gchar pid_file[128];
  gint zorp_uid = -1, zorp_gid = -1;
  gboolean use_syslog = 1;
  gboolean log_tags = FALSE;
  gchar *root_dir = NULL;

  pid_file[0] = 0;
  
#if ENABLE_MEM_TRACE
  z_mem_trace_init();
#endif

  z_enable_core();
  
#if HAVE_GETOPT_LONG
  while ((opt = getopt_long(argc, argv, "a:s:p:v:dhlVu:g:C:TNI:R:", zorp_options, NULL)) != -1)
#else
  while ((opt = getopt(argc, argv, "a:s:p:v:dhlVu:g:C:TNI:R:")) != -1)
#endif
    {
      switch (opt)
	{
	case 'a':
	  instance_name = optarg;
	  break;
	case 'p':
	  policy_file = optarg;
	  break;
	case 'v':
	  if (optarg)
	    verbose_level = atoi(optarg);
	  else
	    verbose_level++;
	  break;
	case 'd':
	  printf("--debug is obsolete, use --log-spec instead\n");
	  break;
	case 's':
	  log_spec = optarg;
	  break;
	case 'P':
	  strncpy(pid_file, optarg, sizeof(pid_file));
	  break;
	case 'h':
	  z_usage();
	  return 0;
	case 'l':
	  use_syslog = 0;
	  break;
	case 'T':
	  log_tags = 1;
	  break;
	case 'V':
	  z_version();
	  return 0;
	case 't':
	  max_threads = atoi(optarg);
	  break;
	case 'u':
	  zorp_uid = atoi(optarg);
	  break;
	case 'g':
	  zorp_gid = atoi(optarg);
	  break;
	case 'I':
	  idle_threads = atoi(optarg);
	  break;
	case 'R':
	  root_dir = optarg;
	  break;
#if ENABLE_CAPS
	case 'C':
	  zorp_caps = optarg;
	  break;
	case 'N':
	  zorp_caps = NULL;
	  break;
#endif
	default:
	  break;
	}
    }
  
  if (pid_file[0] == 0)
    {
      g_snprintf(pid_file, sizeof(pid_file), "%s/zorp-%s.pid", ZORP_PID_FILE_DIR, instance_name);
    }
  z_write_pid_file(pid_file);
  
  if (root_dir)
    chroot(root_dir);

#if HAVE_PRCTL && defined(PR_SET_KEEPCAPS)
  prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
#endif
  
  if (zorp_gid != -1)
    {
      setgid(zorp_gid);
    }
    
  if (zorp_uid != -1) 
    {
      setuid(zorp_uid);
    }
    
#if ENABLE_CAPS
  if (zorp_caps) 
    {
      cap_t cap = cap_from_text(zorp_caps);
    
      if (cap == NULL)
        {
          fprintf(stderr, "Error parsing capabilities: %s\n", zorp_caps);
        }
      else
        {
          if (cap_set_proc(cap) == -1)
            {
              fprintf(stderr, "Error setting capabilities due to %m\n");
            }
          cap_free(cap);
        }
    }
#endif


  signal(SIGPIPE, z_ignore_signal_handler);
  signal(SIGINT, z_ignore_signal_handler);
  signal(SIGTERM, z_ignore_signal_handler); 
  z_thread_init();

  z_log_init(log_spec, instance_name, use_syslog, log_tags);
  z_debug(0, "Verbosity level: %d", verbose_level);

  z_sysdep_init();
  z_ssl_init();  

  z_registry_init();
  srand(time(NULL) ^ getpid()); /* used for PORT allocation within a given range */

  if (!z_python_policy_init())
    {
      z_llog(CORE_ERROR, 0, "Error initializing Python policy engine.");
      return 1;
    }
  
  signal(SIGINT, z_sigterm_handler);
  signal(SIGTERM, z_sigterm_handler); 
  signal(SIGSEGV, z_fatal_signal_handler);
  signal(SIGABRT, z_fatal_signal_handler);
  signal(SIGILL, z_fatal_signal_handler);
  signal(SIGUSR1, z_sigusr1_handler);
  signal(SIGUSR2, z_sigusr2_handler);

  z_config_init();

  z_llog(CORE_INFO, 5, "%s version %s starting up", PACKAGE, VERSION);
  
  z_free_queue_init();
  z_log_set_fake_session_id(instance_name);
  
  if (z_policy_read(policy_file))
    {
      main_loop = g_main_new(TRUE);
      if (z_policy_init(instance_name))
        {
          /* g_mem_profile(); */
          /* g_timeout_add(10000, print_threads, NULL); */
          while (g_main_is_running(main_loop))
            {
              g_main_iteration(TRUE);
              z_free_queue_flush();
              if (usr1_received)
                {
                  usr1_received = 0;
                  verbose_level++;
                  z_log_clear_hash();
                  z_log(NULL, CORE_DEBUG, 1, "SIGUSR1 received; verbose_level='%d'", verbose_level);
#if ENABLE_MEM_TRACE
                  z_mem_trace_stats();
#endif
                }
              if (usr2_received)
                {
                  usr2_received = 0;
                  verbose_level--;
                  z_log_clear_hash();
                  z_log(NULL, CORE_DEBUG, 1, "SIGUSR2 received; verbose_level='%d'", verbose_level);
                }
            }
#if ENABLE_MEM_TRACE
          z_mem_trace_dump();
#endif
          g_main_destroy(main_loop);
        }
      else
        {
          z_log(NULL, CORE_ERROR, 0, "Error initializing policy");
        }
    }
  else
    {
      z_log(NULL, CORE_ERROR, 0, "Error opening policy file %s", policy_file);
    }
  z_llog(CORE_INFO, 5, "%s version %s going down.", PACKAGE, VERSION);
  z_thread_destroy();
#if 0
  z_config_destroy();
#endif
  z_policy_destroy();
  z_free_queue_destroy();
  z_sysdep_destroy();
  z_ssl_destroy();
  z_remove_pid_file(pid_file);
#if ENABLE_MEM_TRACE
  z_mem_trace_dump();
#endif
#ifdef USE_DMALLOC
  dmalloc_shutdown();
  /* avoid second dump of dmalloc */
  rename("logfile", "logfile.dm");
#endif
  return exit_code;
}
