#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <popt.h>

#include "netconsole.h"
#include "netdumpclient.h"
#include "configuration.h"
#include "server.h"

NetdumpConfiguration config;

char *netdump_dir_prefix = NETDUMP_DIR_PREFIX;

enum ConfigType {
  CONFIG_NONE,
  CONFIG_BOOLEAN,
  CONFIG_INT,
  CONFIG_UINT32,
  CONFIG_UINT16,
  CONFIG_STRING
};

struct ConfigData {
  char *name;
  enum ConfigType type;
  int offset;
  void *default_value;
};


/* retrive a structure offset */
#ifdef offsetof
#define CONFIG_OFFSET(field)        ((int) offsetof (NetdumpConfiguration, field))
#else /* !offsetof */
#define CONFIG_OFFSET(field)        ((int) ((char*) &((NetdumpConfiguration *) 0)->field))
#endif /* !offsetof */


struct ConfigData config_data[] =
{
  { "port", CONFIG_UINT16, CONFIG_OFFSET(port), GINT_TO_POINTER (NETDUMP_PORT) },
  { "max_concurrent_dumps", CONFIG_INT, CONFIG_OFFSET(max_concurrent_dumps), GINT_TO_POINTER (4) },
  { "daemon", CONFIG_BOOLEAN, CONFIG_OFFSET(daemon), GINT_TO_POINTER (0) },
  { "pidfile", CONFIG_STRING, CONFIG_OFFSET(pidfile), NULL },
  { "dumpdirprefix", CONFIG_STRING, CONFIG_OFFSET(dumpdirprefix), NETDUMP_DIR_PREFIX },
  { "secure", CONFIG_INT, CONFIG_OFFSET(secure), GINT_TO_POINTER (1) },
  { "space_check", CONFIG_INT, CONFIG_OFFSET(space_check), GINT_TO_POINTER (1) },
};

#define NUM_CONFIG_DATA (sizeof(config_data)/sizeof(struct ConfigData))

static const GScannerConfig netdump_config_scanner_config =
{
  (
   " \t\n"
   )                    /* cset_skip_characters */,
  (
   G_CSET_a_2_z
   "_"
   G_CSET_A_2_Z
   )                    /* cset_identifier_first */,
  (
   G_CSET_a_2_z
   "_-0123456789"
   G_CSET_A_2_Z
   )                    /* cset_identifier_nth */,
  ( "#\n" )             /* cpair_comment_single */,
  
  TRUE                  /* case_sensitive */,
  
  FALSE                 /* skip_comment_multi */,
  TRUE                  /* skip_comment_single */,
  FALSE                 /* scan_comment_multi */,
  TRUE                  /* scan_identifier */,
  TRUE                  /* scan_identifier_1char */,
  FALSE                 /* scan_identifier_NULL */,
  TRUE                  /* scan_symbols */,
  FALSE                 /* scan_binary */,
  FALSE                 /* scan_octal */,
  TRUE                  /* scan_float */,
  TRUE                  /* scan_hex */,
  FALSE                 /* scan_hex_dollar */,
  FALSE                 /* scan_string_sq */,
  TRUE                  /* scan_string_dq */,
  TRUE                  /* numbers_2_int */,
  FALSE                 /* int_2_float */,
  FALSE                 /* identifier_2_string */,
  TRUE                  /* char_2_token */,
  FALSE                 /* symbol_2_token */,
  FALSE                 /* scope_0_fallback */,
};

typedef enum {
  CONFIG_TOKEN_BOOLEAN = G_TOKEN_LAST
} PrefsTokenType;

static void
config_set_defaults(void)
{
  int i;
  char *ptr;

  for (i=0;i<NUM_CONFIG_DATA;i++) {
    ptr = (char *)&config + config_data[i].offset;

    switch (config_data[i].type) {
    case CONFIG_BOOLEAN:
    case CONFIG_INT:
      *(int *)ptr = GPOINTER_TO_INT (config_data[i].default_value);
      break;
    case CONFIG_UINT32:
      *(guint32 *)ptr = GPOINTER_TO_INT (config_data[i].default_value);
      break;
    case CONFIG_UINT16:
      *(guint16 *)ptr = GPOINTER_TO_INT (config_data[i].default_value);
      break;
    case CONFIG_STRING:
      if (config_data[i].default_value != NULL) {
        *(char **)ptr = (char *)malloc(strlen(config_data[i].default_value) + 1);
	if (*(char **)ptr != NULL) {
		strncpy(*(char **)ptr, config_data[i].default_value, strlen(config_data[i].default_value));
		(*(char **)ptr)[strlen(config_data[i].default_value)] = '\0';
	}
	else {
		syslog(LOG_ERR, "Cannot malloc\n");
		exit(1);
	}
      }
      else
	ptr = (char *)NULL;

      break;
    case CONFIG_NONE:
      break;
    }
  }
}

static guint
config_parse_line(GScanner *scanner)
{
  guint token;
  int symbol_nr;
  char *ptr;
  
  token = g_scanner_get_next_token(scanner);
  if (token != G_TOKEN_SYMBOL)
    return G_TOKEN_SYMBOL;

  symbol_nr = GPOINTER_TO_INT(scanner->value.v_symbol);
  
  token = g_scanner_get_next_token(scanner);
  if (token != G_TOKEN_EQUAL_SIGN)
    return G_TOKEN_EQUAL_SIGN;

  token = g_scanner_get_next_token(scanner);

  ptr = (unsigned char *)&config + config_data[symbol_nr].offset;
  
  switch (config_data[symbol_nr].type) {
  case CONFIG_BOOLEAN:
    if (token != G_TOKEN_IDENTIFIER)
      return G_TOKEN_IDENTIFIER;
    
    if (strcasecmp(scanner->value.v_string, "true")==0)
      *(int *)ptr = 1;
    else
      *(int *)ptr = 0;
    break;
    
  case CONFIG_INT:
    if (token != G_TOKEN_INT)
      return G_TOKEN_INT;
    
    *(int *)ptr = scanner->value.v_int;
    break;
  case CONFIG_UINT32:
    if (token != G_TOKEN_INT)
      return G_TOKEN_INT;
    
    *(guint32 *)ptr = scanner->value.v_int;
    break;
  case CONFIG_UINT16:
    if (token != G_TOKEN_INT)
      return G_TOKEN_INT;
    
    *(guint16 *)ptr = scanner->value.v_int;
    break;

  case CONFIG_STRING:
    if (token != G_TOKEN_STRING)
      return G_TOKEN_STRING;

    if (*(char **)ptr != NULL)
	free(*(char **)ptr);
    *(char **)ptr = (char *)malloc(strlen(scanner->value.v_string) + 1);
	if (*(char **)ptr != NULL) {
		strncpy(*(char **)ptr, scanner->value.v_string, strlen(scanner->value.v_string));
		(*(char **)ptr)[strlen(scanner->value.v_string)] = '\0';
	}
	else {
		syslog(LOG_ERR, "Cannot malloc\n");
		exit(1);
	}

    break;
  case CONFIG_NONE:
    break;
  }

  return G_TOKEN_NONE;
}


static void
msg_handler (GScanner		*scanner,
	     gchar		*message,
	     gint		 is_error)
{
  char *msg;
  g_return_if_fail (scanner != NULL);

  msg = g_strdup_printf ("Error parsing %s at line %d: %s", scanner->input_name, scanner->line, message);
  if (is_error)
    syslog (LOG_ERR, "%s", msg);
  else
    syslog (LOG_WARNING, "%s", msg);

  fprintf (stderr, "%s\n", msg);
  
  g_free (msg);
}

static void
config_load (const char *filename)
{
  int i;
  int fd;
  GScanner *scanner;
  guint expected_token;

  fd = open(filename, O_RDONLY);

  if (fd < 0) {
    return;
  }

  scanner = g_scanner_new ((GScannerConfig *) &netdump_config_scanner_config);
 
  g_scanner_input_file (scanner, fd);

  scanner->input_name = filename;
  scanner->msg_handler = msg_handler;
    
  g_scanner_freeze_symbol_table(scanner);
  for (i = 0; i < NUM_CONFIG_DATA; i++)
    if (config_data[i].type != CONFIG_NONE) {
      g_scanner_add_symbol(scanner, config_data[i].name,
			   GINT_TO_POINTER(i));
    }
  g_scanner_thaw_symbol_table(scanner);
  
  while (1) {
    if (g_scanner_peek_next_token(scanner) == G_TOKEN_EOF) {
      break;
    } 

    expected_token = config_parse_line(scanner);
      
    if (expected_token != G_TOKEN_NONE) {
      gchar *symbol_name;
      gchar *msg;
      
      msg = NULL;
      symbol_name = NULL;
      g_scanner_unexp_token (scanner,
			     expected_token,
			     NULL,
			     "keyword",
			     symbol_name,
			     msg,
			     TRUE);
    }
  }
  
  g_scanner_destroy (scanner);
  close(fd);
}

static int intarg;

static struct poptOption optionsTable[] = {
    { "port", 'p', POPT_ARG_INT, &intarg, 'p',
      "ip port to listen on", "6666" },
    { "concurrent", 'c', POPT_ARG_INT, &config.max_concurrent_dumps, 0,
      "max number of concurrent dumps", "4" },
    { "daemon", 'd', POPT_ARG_NONE, &config.daemon, 0,
      "run in background as a daemon", NULL },
    { "pidfile",  'P', POPT_ARG_STRING, &config.pidfile, 0,
      "file in which to store the pid", "path" },
    { "dumpdirprefix",  'D', POPT_ARG_STRING, &config.dumpdirprefix, 0,
      "dir in which to store dumps", "/var/spool/netdump" },
    { "secure", 's', POPT_ARG_INT, &config.secure, 0,
      "use ssh to send client identification", "1" },
    { "space_check", 'S', POPT_ARG_INT, &config.space_check, 0,
      "verify that space is available for the dumpfile", "1" },
      POPT_AUTOHELP
    { NULL, 0, 0, NULL, 0 }
};

static void
parse_argument (char c)
{
  switch (c)
    {
    case 'p':
      config.port = intarg;
      break;
    }
}

void
config_init (int argc, char *argv[])
{
  signed char c;
  poptContext optCon;   /* context for parsing command-line options */
  
  /* Set the default values */
  config_set_defaults();

  config_load ("/usr/local/etc/netdump.conf");
  
  optCon = poptGetContext("netdump-server", argc, (const char **)argv,
			  optionsTable, 0);
  while ((c = poptGetNextOpt(optCon)) >= 0)
    parse_argument (c);
}


syntax highlighted by Code2HTML, v. 0.9.1