/*-
 * Copyright (c) 2004 Free (Olivier Beyssac)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include "utils.h"
#include "options.h"

#define CONFIG_BUFSIZE (1024)


extern struct options opt;


/* Append a host to the notify_hosts list */
static int notify_hosts_append(int index, char *value)
{
  char *c, svc;
  size_t svl, len;

  len = sizeof(char *) * ((index + 1) * 2 + 1);
  
  if ((opt.notify_hosts = realloc(opt.notify_hosts, len)) == NULL) {
    if (opt.syslog)
      syslog(LOG_ERR, "%s: unable to calloc %d bytes to split host\n",
	     opt.progname, sizeof(char *) * 3);
    else
      fprintf(stderr, "%s: unable to calloc %d bytes to split host\n",
	      opt.progname, sizeof(char *) * 3);

    return 0;
  }

  opt.notify_hosts[index * 2 + 2] = NULL;
  
  if ((c = strchr(value, ':')) == NULL) {
    opt.notify_hosts[index*2] = value;
    opt.notify_hosts[index*2+1] = "2905";
  } else {
    savebufpos(svc, svl, value, strlen(value) - strlen(c));
    if ((opt.notify_hosts[index*2] = strdup(value)) == NULL) {
      if (opt.syslog)
	syslog(LOG_ERR, "%s: strdup failed\n", opt.progname);
      else
	fprintf(stderr, "%s: strdup failed\n", opt.progname);

      return 0;
    }
    
    restorebuf(svc, svl, value);
    
    if ((opt.notify_hosts[index*2+1] = strdup(c + 1)) == NULL) {
      if (opt.syslog)
	syslog(LOG_ERR, "%s: strdup failed\n", opt.progname);
      else
	fprintf(stderr, "%s: strdup failed\n", opt.progname);
      
      return 0;
    }
  }
  
  return 1;
}


/* Read comma separated hosts list and fill relevant structure */
static int read_hosts_list(char *value)
{
  char *c, *p;
  int i = 0;
  
  p = c = value;
  while ((c = strchr(c, ','))) {
    *c = '\0';
    for (c++; *c == ',' || *c == ' ' || *c == '\t'; c++)
    notify_hosts_append(i++, p);
    p = c;
  }

  notify_hosts_append(i++, p);

  return 1;
}


/* Set option structure given variable name and its value */
static int set_option(const char *variable, char *value)
{
  if (!strcmp(variable, "daemon")) {
    opt.daemon = xstrtol(value);
    return options_check(DAEMON_CHK, opt.daemon);
  }

  if (!strcmp(variable, "max_submissions")) {
    opt.max_req = xstrtol(value);
    return options_check(MAX_REQ_CHK, opt.max_req);
  }

  if (!strcmp(variable, "min_interval")) {
    opt.interval = xstrtol(value);
    return options_check(INTERVAL_CHK, opt.interval);
  }

  if (!strcmp(variable, "ip_list_size")) {
    opt.list_size = xstrtol(value);
    return options_check(LIST_SIZE_CHK, opt.list_size);
  }

  if (!strcmp(variable, "blacklist_size")) {
    opt.blacklist_size = xstrtol(value);
    return options_check(BLACKLIST_SIZE_CHK, opt.blacklist_size);
  }

  if (!strcmp(variable, "blacklist_expiration")) {
    opt.blacklist_expiration = xstrtol(value);
    return options_check(BLACKLIST_EXPIRATION_CHK, opt.blacklist_expiration);
  }

  if (!strcmp(variable, "client_timeout")) {
    opt.client_timeout = xstrtol(value);
    return options_check(CLIENT_TIMEOUT_CHK, opt.client_timeout);
  }
  
  if (!strcmp(variable, "address"))
    opt.listening_ip = value;
  else if (!strcmp(variable, "port"))
    opt.port = value;
  else if (!strcmp(variable, "log_level"))
    opt.log_level = xstrtol(value);
  else if (!strcmp(variable, "user"))
    opt.uid = value;
  else if (!strcmp(variable, "group"))
    opt.gid = value;
  else if (!strcmp(variable, "iplist_dump"))
    opt.wl_filename = value;
  else if (!strcmp(variable, "blacklist_dump"))
    opt.bl_filename = value;
  else if (!strcmp(variable, "pid_filename"))
    opt.pid_filename = value;
  else if (!strcmp(variable, "acl_filename"))
    opt.acl_filename = value;
  else if (!strcmp(variable, "whitelist_filename"))
    opt.whitelist_filename = value;
  else if (!strcmp(variable, "notifies_to"))
    return read_hosts_list(value);
  else return 0;

  return 1;
}


/* Open the config file and parse it.  Return 0 on error */
extern int parse_config(void)
{
  FILE *fd;
  char buf[CONFIG_BUFSIZE];
  char *variable;
  char *value;
  size_t i, end, len;
  int lineno = 0;
  char svc;
  int svl;
  
  if ((fd = fopen(opt.config_file, "r")) == NULL) {
    if (errno == ENOENT)
      return 1;

    if (opt.syslog)
      syslog(LOG_ERR, "%s: error while opening %s\n",
	      opt.progname, opt.config_file);
    else
      fprintf(stderr, "%s: error while opening %s\n",
	      opt.progname, opt.config_file);
    
    perror("fopen");
    return 0;
  }

  while (fgets(buf, CONFIG_BUFSIZE, fd)) {
    lineno++;
    len = strlen(buf);
    
    if (buf[0] == '#' || buf[0] == '\n')
      continue;
    variable = NULL;
    value = NULL;

    /* Search for '=' */
    for (i = 1; i < len && buf[i] != '='; i++);
    if (i >= len - 2) {
      if (opt.syslog)
	syslog(LOG_ERR, "%s:%s:%i: invalid line: %s",
		opt.progname, opt.config_file, lineno, buf);
      else
	fprintf(stderr, "%s:%s:%i: invalid line: %s",
		opt.progname, opt.config_file, lineno, buf);
      return 0;
    }
    
    /* Go back to the real end of the variable and copy it */
    end = i - 1;
    while (buf[end] == ' ' || buf[end] == '\t')
      end--;
    variable = buf;
    savebufpos(svc, svl, buf, end + 1);
    buf[end+1] = '\0';
    
    /* Get the value now */
    for (i++; i < len && (buf[i] == ' ' || buf[i] == '\t'); i++);
    
    for (end = len - 1; end > i && (buf[end] == ' ' || buf[end] == '\t'
				    || buf[end] == '\n' || buf[end] == '\r');
         end--);
    if ((value = xstrndup(buf + i, end - i + 1)) == NULL) {
      if (opt.syslog)
	syslog(LOG_ERR, "%s: parse_config failed to alloc %d bytes",
	      opt.progname, end - i);
      else
	fprintf(stderr, "%s: parse_config failed to alloc %d bytes",
		opt.progname, end - i);

      return 0;
    }
    
    if (!set_option(variable, value)) {
      restorebuf(svc, svl, buf);
      if (opt.syslog)
	syslog(LOG_ERR, "%s: invalid line in config file %s:%d\n%s",
	       opt.progname, opt.config_file, lineno, buf);
      else
	fprintf(stderr, "%s: invalid line in config file %s:%d\n%s",
		opt.progname, opt.config_file, lineno, buf);

      return 0;
    }
  }
  
  fclose(fd);

  return 1;
}
