/* 
logpp (Log PreProcessor) 0.16 - readconf.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"

/* a type for configuration file parsing */

struct keyval {
  char *key;
  char *val;
  struct keyval *next;
};

/* resize_conf_array() attempts to free unused memory from the end of
   the configuration array pointed by 'ptr' and having 'num' elements;
   the function returns a pointer to the configuration array */

void *
resize_conf_array(void *ptr, size_t num)
{
  void *new;

  /* if the array has 0 elements, free the whole array */
  if (!num) {
    my_free(ptr);
    return 0;
  }
  /* otherwise use realloc() for shortening the array */
  new = realloc(ptr, num);
  if (new) return new; else return ptr;
}

/* parse_line() will split the line into the keyword and value, and return
   them in 'key' and 'val' strings. Leading and trailing whitespace will
   be removed from the line, the keyword is the first substring that does
   not contain whitespace, and value is the substring that begins with
   a non-whitespace character and is separated from the keyword by at least
   one whitespace character. For example, the line "  aaa   b c " will
   set 'key' to "aaa" and 'val' to "b c". The function will return 0 if
   the line is empty or comment, 1 if only keyword is present, and 2 if
   both keyword and value are present */
   
int
parse_line(char *line, char *key, char *val)
{
  size_t i, j;

  for (i = 0; line[i] && isspace((int) line[i]); ++i);
  if (!line[i]) return 0;
  if (line[i] == COMMENT) return 0;

  for (j = i; line[j] && !isspace((int) line[j]); ++j);
  strncpy(key, line + i, j - i);
  key[j - i] = 0;

  for (i = j; line[i] && isspace((int) line[i]); ++i);
  if (!line[i]) return 1;

  strcpy(val, line + i);
  for (i = strlen(val); isspace((int) val[i - 1]); --i);
  val[i] = 0;

  return 2;
}

/* parse_input() will process the input definition and add it to
   the list of inputs; the function returns the number of valid elements
   in the definition */

size_t
parse_input(struct keyval *rec, char *name, char *filename)
{
  struct input *input, *last;
  struct keyval *ptr;
  size_t num;

  log_msg(LOG_DEBUG, "%s: Parsing input %s", filename, name);

  /* check if the input with the same name already exists - if not,
     add the new input definition to the list */
  if (INPUTLIST) {
    for (input = INPUTLIST; input; input = input->next) {
      if (!strcmp(input->name, name)) {
        log_msg(LOG_ERR, "%s: Input %s already defined", filename, name);
        return 0;
      }
      last = input;
    }
    last->next = (struct input *) my_malloc(sizeof(struct input));
    input = last->next;
  } else {
    INPUTLIST = (struct input *) my_malloc(sizeof(struct input));
    input = INPUTLIST;
  }

  /* estimate the memory needs - set 'num' to the number of sources */
  for (ptr = rec, num = 0; ptr; ptr = ptr->next)
    if (!strcasecmp(ptr->key, "file")) ++num;
    else log_msg(LOG_ERR, "%s: Illegal keyword %s in input %s", 
                          filename, ptr->key, name);

  /* allocate memory and fill some fields in the input definition */
  input->name = (char *) my_malloc(sizeof(char) * (strlen(name) + 1));
  strcpy(input->name, name);
  input->srclist = (struct src *) my_malloc(sizeof(struct src) * num);
  input->srcl_size = 0;
  input->flowlist = 0;
  input->next = 0;
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for input %s", 
                     filename, num, name);

  /* if the definition is empty ('num' is 0), return */
  if (!num) return 0;

  /* process the key-value pairs (sources) */

  for (ptr = rec; ptr; ptr = ptr->next) {
    log_msg(LOG_DEBUG, "%s: Parsing keyword '%s' value '%s' in input %s",
                       filename, ptr->key, ptr->val, name);
    if (!strcasecmp(ptr->key, "file")) {
      /* store the name of the file to the destination list */
      input->srclist[input->srcl_size].type = SRCDST_FILE;
      input->srclist[input->srcl_size].name = 
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(input->srclist[input->srcl_size].name, ptr->val);
      input->srclist[input->srcl_size].counter = 0;
      ++input->srcl_size;
    }
  }

  return input->srcl_size;
}

/* parse_output() will process the output definition and add it to
   the list of outputs; the function returns the number of valid elements
   in the definition */

size_t
parse_output(struct keyval *rec, char *name, char *filename)
{
  struct output *output, *last;
  struct keyval *ptr;
  int facility, level;
  size_t num;
  char *sep;

  log_msg(LOG_DEBUG, "%s: Parsing output %s", filename, name);

  /* check if the output with the same name already exists - if not,
     add the new output definition to the list */
  if (OUTPUTLIST) {
    for (output = OUTPUTLIST; output; output = output->next) {
      if (!strcmp(output->name, name)) {
        log_msg(LOG_ERR, "%s: Output %s already defined", filename, name);
        return 0;
      }
      last = output;
    }
    last->next = (struct output *) my_malloc(sizeof(struct output));
    output = last->next;
  } else {
    OUTPUTLIST = (struct output *) my_malloc(sizeof(struct output));
    output = OUTPUTLIST;
  }

  /* estimate the memory needs - set 'num' to the number of destinations */
  for (ptr = rec, num = 0; ptr; ptr = ptr->next)
    if (!strcasecmp(ptr->key, "file") || 
        !strcasecmp(ptr->key, "syslog") ||
        !strcasecmp(ptr->key, "exec")) ++num;
    else log_msg(LOG_ERR, "%s: Illegal keyword %s in output %s", 
                          filename, ptr->key, name);

  /* allocate memory and fill some fields in the output definition */
  output->name = (char *) my_malloc(sizeof(char) * (strlen(name) + 1));
  strcpy(output->name, name);
  output->dstlist = (struct dst *) my_malloc(sizeof(struct dst) * num);
  output->dstl_size = 0;
  output->next = 0;
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for output %s", 
                     filename, num, name);

  /* if the definition is empty ('num' is 0), return */
  if (!num) return 0;

  /* process the key-value pairs (destinations) */

  for (ptr = rec; ptr; ptr = ptr->next) {
    log_msg(LOG_DEBUG, "%s: Parsing keyword '%s' value '%s' in output %s",
                       filename, ptr->key, ptr->val, name);
    if (!strcasecmp(ptr->key, "file")) {
      /* store the name of the file to the destination list */
      output->dstlist[output->dstl_size].type = SRCDST_FILE;
      output->dstlist[output->dstl_size].name = 
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(output->dstlist[output->dstl_size].name, ptr->val);
      output->dstlist[output->dstl_size].counter = 0;
      ++output->dstl_size;
    }
    else if (!strcasecmp(ptr->key, "syslog")) {
      /* find the separating dot in the syslog priority definion */
      sep = strchr(ptr->val, '.');      
      if (!sep) {
        log_msg(LOG_ERR, "%s: Priority %s has no '.' in output %s",
                         filename, ptr->val, name);
        continue;
      }
      /* find the facility code from the syslog priority definion */
      facility = syslog_facility(ptr->val, sep - ptr->val);
      if (facility == -1) {
        log_msg(LOG_ERR, "%s: Priority %s has invalid facility in output %s",
                         filename, ptr->val, name);
        continue;
      }
      /* find the level code from the syslog priority definion */
      level = syslog_level(sep + 1, strlen(sep + 1));
      if (level == -1) {
        log_msg(LOG_ERR, "%s: Priority %s has invalid level in output %s",
                         filename, ptr->val, name);
        continue;
      }
      /* store the syslog priority code to the destination list */
      output->dstlist[output->dstl_size].type = SRCDST_SYSLOG;
      output->dstlist[output->dstl_size].name = 
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(output->dstlist[output->dstl_size].name, ptr->val);
      output->dstlist[output->dstl_size].priority = facility | level;
      output->dstlist[output->dstl_size].counter = 0;
      ++output->dstl_size;
    }
    else if (!strcasecmp(ptr->key, "exec")) {
      /* store the name of the program to the destination list */
      output->dstlist[output->dstl_size].type = SRCDST_EXEC;
      output->dstlist[output->dstl_size].name = 
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(output->dstlist[output->dstl_size].name, ptr->val);
      output->dstlist[output->dstl_size].counter = 0;
      ++output->dstl_size;
    }
  }

  output->dstlist = (struct dst *) resize_conf_array(output->dstlist,
                                   sizeof(struct dst) * output->dstl_size);
  log_msg(LOG_DEBUG, "%s: Reallocated %d slots for output %s", 
                     filename, output->dstl_size, name);
  return output->dstl_size;
}

/* parse_filter() will process the filter definition and add it to
   the list of filters; the function returns the number of valid elements
   in the definition */

size_t
parse_filter(struct keyval *rec, char *name, char *filename)
{
  struct filter *filter, *last;
  struct template_elem *template;
  struct keyval *ptr;
  char *numeral, *st, *end;
  char pattern_present;
  size_t num, len;
  long convbuf;
#ifdef HAVE_LIBPCRE
  pcre *regexp;
  const char *msg;
  int offset;
#else
  char *msg;
  int err;
#endif

  log_msg(LOG_DEBUG, "%s: Parsing filter %s", filename, name);

  /* check if the filter with the same name already exists - if not,
     add the new filter definition to the list */
  if (FILTERLIST) {
    for (filter = FILTERLIST; filter; filter = filter->next) {
      if (!strcmp(filter->name, name)) {
        log_msg(LOG_ERR, "%s: Filter %s already defined", filename, name);
        return 0;
      }
      last = filter;
    }
    last->next = (struct filter *) my_malloc(sizeof(struct filter));
    filter = last->next;
  } else {
    FILTERLIST = (struct filter *) my_malloc(sizeof(struct filter));
    filter = FILTERLIST;
  }

  /* estimate the memory needs - set 'num' to the number of conditions */
  for (ptr = rec, num = 0; ptr; ptr = ptr->next)
    if (!strncasecmp(ptr->key, "regexp", 6) || 
        !strncasecmp(ptr->key, "nregexp", 7) ||
        !strcasecmp(ptr->key, "tvalue")) ++num;
    else if (strcasecmp(ptr->key, "template"))
      log_msg(LOG_ERR, "%s: Illegal keyword %s in filter %s", 
                       filename, ptr->key, name);

  /* allocate memory and fill some fields in the filter definition */
  filter->name = (char *) my_malloc(sizeof(char) * (strlen(name) + 1));
  strcpy(filter->name, name);
  filter->condlist = (struct cond *) my_malloc(sizeof(struct cond) * num);
  filter->condl_size = 0;
  filter->next = 0;
  pattern_present = 0;
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for filter %s", 
                     filename, num, name);

  /* if the definition is empty ('num' is 0), return */
  if (!num) return 0;

  /* process the key-value pairs (conditions and templates) */

  for (ptr = rec; ptr; ptr = ptr->next) {

    log_msg(LOG_DEBUG, "%s: Parsing keyword '%s' value '%s' in filter %s",
                       filename, ptr->key, ptr->val, name);

    /* handle the "template" keyword */

    if (!strcasecmp(ptr->key, "template")) {
      /* if there is no preceding valid pattern, ignore the template */
      if (!pattern_present) {
        log_msg(LOG_ERR, "%s: Template %s has no pattern in filter %s",
                         filename, ptr->val, name);
        continue;
      }

      /* store the string representation of the template */
      filter->condlist[filter->condl_size - 1].templ_str =
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(filter->condlist[filter->condl_size - 1].templ_str, ptr->val);

      /* estimate the memory needs for the template (if there are N
         variables, the template can have at most 2*N+1 parts), and
         allocate the memory */
      for (st = ptr->val, num = 0; (st = strchr(st, MATCHVAR)); ++st) ++num;
      template = (struct template_elem *) 
                 my_malloc(sizeof(struct template_elem) * (2*num + 1));
      log_msg(LOG_DEBUG, 
              "%s: Allocated %d slots for template %s in filter %s", 
              filename, 2*num + 1, ptr->val, name);

      /* template parsing */
      st = ptr->val;
      num = 0;
      while ((end = strchr(st, MATCHVAR))) {
        /* store the characters before MATCHVAR (if any) as a string */
        len = end - st;
        if (len) {
          template[num].str = (char *) my_malloc(sizeof(char) * (len + 1));
          strncpy(template[num].str, st, len);
          template[num].str[len] = 0;
          template[num].len = len;
          ++num;
        }
        /* if MATCHVAR is followed by IFILEVAR, set variable number to -1
           and continue with the character after IFILEVAR ('end' + 2); 
           otherwise convert characters after MATCHVAR to variable number */
        if (*(end + 1) == IFILEVAR) {
          template[num].num = -1;
          template[num].str = 0;
          st = end + 2;
        } else {
          st = end + 1;
          convbuf = strtol(st, &end, 10);
          /* if the conversion failed (the string begins with a non-digit
             that strtol would pass like '+' or whitespace, or the result is 
             out of range), store MATCHVAR as a 1-char string and continue 
             the processing from the next character ('st') */
          if (!isdigit((int) *st) || 
              convbuf < 0 || convbuf > MVARBUFSIZE - 1) {
            if (isdigit((int) *st))
              log_msg(LOG_ERR, 
                      "%s: Match variable $%ld out of range 0..%d in filter %s", 
                      filename, convbuf, MVARBUFSIZE - 1, name);
            template[num].str = (char *) my_malloc(sizeof(char) * 2);
            template[num].str[0] = MATCHVAR;
            template[num].str[1] = 0;
            template[num].len = 1;
          }
          /* if the conversion was successful, save the number and jump
             to the next character after the number ('end') */
          else {
            template[num].num = convbuf;
            template[num].str = 0;
            st = end;
          }
        }
        ++num;
      }
      /* store the trailing characters (if any) as a string */
      len = strlen(st);
      if (len) {
        template[num].str = (char *) my_malloc(sizeof(char) * (len + 1));
        strcpy(template[num].str, st);
        template[num].str[len] = 0;
        template[num].len = len;
        ++num;
      }

      /* store the template, its size, and go to the next key-value pair */
      template = (struct template_elem *) resize_conf_array(template, 
                                          sizeof(struct template_elem) * num);
      filter->condlist[filter->condl_size - 1].template = template;
      filter->condlist[filter->condl_size - 1].templ_size = num;
      pattern_present = 0;
      log_msg(LOG_DEBUG, 
              "%s: Reallocated %d slots for template %s in filter %s", 
              filename, num, ptr->val, name);
      continue;
    }

    /* handle the "tvalue" keyword */

    if (!strcasecmp(ptr->key, "tvalue")) {
      /* check if the truth value is true or false */
      if (!strcasecmp(ptr->val, "true"))
        filter->condlist[filter->condl_size].negative = 0;
      else if (!strcasecmp(ptr->val, "false"))
        filter->condlist[filter->condl_size].negative = 1;
      else {
        log_msg(LOG_ERR, "%s: Illegal truth value %s in filter %s", 
                         filename, ptr->val, name);
        pattern_present = 0;
        continue;
      }
      /* store the string representation of the truth value */
      filter->condlist[filter->condl_size].str =
        (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
      strcpy(filter->condlist[filter->condl_size].str, ptr->val);
      /* for truth values the 'num' field of the condition is 0 */
      filter->condlist[filter->condl_size].num = 0;
      /* set the template and its size to 0 (the next "template" keyword 
         could change it) and go to the next key-value pair */
      pattern_present = 1;
      filter->condlist[filter->condl_size].template = 0;
      filter->condlist[filter->condl_size].templ_size = 0;
      filter->condlist[filter->condl_size].templ_str = 0;
      filter->condlist[filter->condl_size].counter = 0;
      ++filter->condl_size;
      continue;
    }

    /* handle the "(n)regexp<digits>" keyword */

    pattern_present = 0;

    /* check if the regexp is positive or negative */
    if (!strncasecmp(ptr->key, "regexp", 6)) {
      numeral = ptr->key + 6;
      filter->condlist[filter->condl_size].negative = 0;
    }
    else if (!strncasecmp(ptr->key, "nregexp", 7)) {
      numeral = ptr->key + 7;
      filter->condlist[filter->condl_size].negative = 1;
    }
    else continue;

    /* check how many lines the regexp matches - if the keyword has a
       suffix, the suffix must not begin with a non-digit (like '+'), and
       the whole suffix must be a valid number (*end == terminating 0) */
    if (*numeral) {
      convbuf = strtol(numeral, &end, 10);
      if (!isdigit((int) *numeral) || *end || 
          convbuf < 1 || convbuf > IBUFSIZE) {
        log_msg(LOG_ERR, "%s: Illegal line count %s in filter %s",
                         filename, numeral, name);
        continue;
      }
      filter->condlist[filter->condl_size].num = convbuf;
    }
    else filter->condlist[filter->condl_size].num = 1;

    /* compile the regexp */
#ifdef HAVE_LIBPCRE
    regexp = pcre_compile(ptr->val, 0, &msg, &offset, 0);
    if (!regexp) {
      log_msg(LOG_ERR, "%s: Illegal regexp %s in filter %s (%s - %s)", 
                       filename, ptr->val, name, ptr->val + offset, msg);
      continue;
    }
    /* pcre_study() returns 0 both for error and no additional speed-up
       info - since the regexp compilation has already succeeded, don't
       check 'msg' for possible errors */
    filter->condlist[filter->condl_size].regexp = regexp;
    filter->condlist[filter->condl_size].extra = pcre_study(regexp, 0, &msg);
#else
    if ((err = regcomp(&filter->condlist[filter->condl_size].regexp, 
                       ptr->val, POSIX_REGEX_FLAGS))) {
      len = regerror(err, &filter->condlist[filter->condl_size].regexp, 0, 0);
      msg = (char *) my_malloc(sizeof(char) * len);
      regerror(err, &filter->condlist[filter->condl_size].regexp, msg, len);
      log_msg(LOG_ERR, "%s: Illegal regexp %s in filter %s (%s)", 
                       filename, ptr->val, name, msg);
      my_free(msg);
      continue;
    }
#endif

    /* store the string representation of the regular expression */
    filter->condlist[filter->condl_size].str =
      (char *) my_malloc(sizeof(char) * (strlen(ptr->val) + 1));
    strcpy(filter->condlist[filter->condl_size].str, ptr->val);
    /* set the template and its size to 0 (the next "template" keyword 
       could change it) and go to the next key-value pair */
    pattern_present = 1;
    filter->condlist[filter->condl_size].template = 0;
    filter->condlist[filter->condl_size].templ_size = 0;
    filter->condlist[filter->condl_size].templ_str = 0;
    filter->condlist[filter->condl_size].counter = 0;
    ++filter->condl_size;
  }

  filter->condlist = (struct cond *) resize_conf_array(filter->condlist,
                                     sizeof(struct cond) * filter->condl_size);
  log_msg(LOG_DEBUG, "%s: Reallocated %d slots for filter %s", 
                     filename, filter->condl_size, name);
  return filter->condl_size;
}

/* parse_flow() will process the flow definition and add it to the list of 
   flows; the function returns the number of valid elements in the 
   definition */

size_t
parse_flow(struct keyval *rec, char *name, char *filename)
{
  struct flow *flow, *last;
  struct keyval *ptr;
  struct input *input;
  struct output *output;
  struct filter *filter;
  struct fl_elem *elem;
  size_t fnum, onum, inum;

  log_msg(LOG_DEBUG, "%s: Parsing flow %s", filename, name);

  /* check if the flow with the same name already exists - if not,
     add the new flow definition to the list */
  if (FLOWLIST) {
    for (flow = FLOWLIST; flow; flow = flow->next) {
      if (!strcmp(flow->name, name)) {
        log_msg(LOG_ERR, "%s: Flow %s already defined", filename, name);
        return 0;
      }
      last = flow;
    }
    last->next = (struct flow *) my_malloc(sizeof(struct flow));
    flow = last->next;
  } else {
    FLOWLIST = (struct flow *) my_malloc(sizeof(struct flow));
    flow = FLOWLIST;
  }

  /* estimate the memory needs - set 'fnum', 'onum', and 'inum' to 
     the number of filters, outputs, and inputs, respectively */
  for (ptr = rec, fnum = onum = inum = 0; ptr; ptr = ptr->next)
    if (!strcasecmp(ptr->key, "filter")) ++fnum;
    else if (!strcasecmp(ptr->key, "output")) ++onum;
    else if (!strcasecmp(ptr->key, "input")) ++inum;
    else log_msg(LOG_ERR, "%s: Illegal keyword %s in flow %s", 
                          filename, ptr->key, name);

  /* allocate memory and fill some fields in the flow definition */
  flow->name = (char *) my_malloc(sizeof(char) * (strlen(name) + 1));
  strcpy(flow->name, name);
  flow->flist = (struct filter **) my_malloc(sizeof(struct filter *) * fnum);
  flow->fl_size = 0;
  flow->olist = (struct output **) my_malloc(sizeof(struct output *) * onum);
  flow->ol_size = 0;
  flow->ilist = (struct input **) my_malloc(sizeof(struct input *) * inum);
  flow->il_size = 0;
  flow->next = 0;
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for filters in flow %s", 
                     filename, fnum, name);
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for outputs in flow %s", 
                     filename, onum, name);
  log_msg(LOG_DEBUG, "%s: Allocated %d slots for inputs in flow %s", 
                     filename, inum, name);

  /* process the key-value pairs (inputs, outputs and filters) */

  for (ptr = rec; ptr; ptr = ptr->next) {
    log_msg(LOG_DEBUG, "%s: Parsing keyword '%s' value '%s' in flow %s",
                       filename, ptr->key, ptr->val, name);
    if (!strcasecmp(ptr->key, "input")) {
      /* find the input from the list of inputs */
      for (input = INPUTLIST; input; input = input->next)
        if (!strcmp(ptr->val, input->name)) break;
      if (!input) {
        log_msg(LOG_ERR, "%s: Unknown input %s in flow %s", 
                         filename, ptr->val, name);
        continue;
      }
      /* add the current flow to the flowlist of the input */
      if (input->flowlist) {
        for (elem = input->flowlist; elem->next; elem = elem->next);
        elem->next = (struct fl_elem *) my_malloc(sizeof(struct fl_elem));
        elem->next->flow = flow;
        elem->next->next = 0;
      }
      else {
        input->flowlist = (struct fl_elem *) my_malloc(sizeof(struct fl_elem));
        input->flowlist->flow = flow;
        input->flowlist->next = 0;
      }
      /* store the input to the input list */
      flow->ilist[flow->il_size++] = input;
    }
    else if (!strcasecmp(ptr->key, "output")) {
      /* find the output from the list of outputs */
      for (output = OUTPUTLIST; output; output = output->next)
        if (!strcmp(ptr->val, output->name)) break;
      if (!output) {
        log_msg(LOG_ERR, "%s: Unknown output %s in flow %s", 
                         filename, ptr->val, name);
        continue;
      }
      /* store the output to the output list */
      flow->olist[flow->ol_size++] = output;
    }
    else if (!strcasecmp(ptr->key, "filter")) {
      /* find the filter from the list of filters */
      for (filter = FILTERLIST; filter; filter = filter->next)
        if (!strcmp(ptr->val, filter->name)) break;
      if (!filter) {
        log_msg(LOG_ERR, "%s: Unknown filter %s in flow %s", 
                         filename, ptr->val, name);
        continue;
      }
      /* store the filter to the filter list */
      flow->flist[flow->fl_size++] = filter;
    }
  }

  flow->flist = (struct filter **) resize_conf_array(flow->flist,
                                   sizeof(struct filter *) * flow->fl_size);
  flow->olist = (struct output **) resize_conf_array(flow->olist,
                                   sizeof(struct output *) * flow->ol_size);
  flow->ilist = (struct input **) resize_conf_array(flow->ilist,
                                  sizeof(struct input *) * flow->il_size);
  log_msg(LOG_DEBUG, "%s: Reallocated %d slots for filters in flow %s", 
                     filename, flow->fl_size, name);
  log_msg(LOG_DEBUG, "%s: Reallocated %d slots for outputs in flow %s", 
                     filename, flow->ol_size, name);
  log_msg(LOG_DEBUG, "%s: Reallocated %d slots for inputs in flow %s", 
                     filename, flow->il_size, name);

  return (flow->il_size + flow->ol_size + flow->fl_size);
}

/* read_config() will parse the configuration file and invoke handler
   functions for individual record definitions */

int
read_config(char *filename)
{
  FILE *file;
  char *line, *key, *val, *name;
  struct keyval *rec, *ptr;
  size_t (*rechandler)(struct keyval *, char *, char *);
  size_t len;
  int ret;

  log_msg(LOG_DEBUG, "Parsing configuration file %s", filename);

  file = fopen(filename, "r");

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

  /* create buffers for configuration file parsing */
  line = (char *) my_malloc(sizeof(char) * BLOCKSIZE);
  key = (char *) my_malloc(sizeof(char) * BLOCKSIZE);
  val = (char *) my_malloc(sizeof(char) * BLOCKSIZE);
  name = (char *) my_malloc(sizeof(char) * BLOCKSIZE);

  /* set the record handler, record pointer and return value to 0 */
  rechandler = 0;
  rec = 0;
  ret = 0;

  while (fgets(line, BLOCKSIZE, file)) {

    /* remove newline from the end of the line */
    len = strlen(line);
    if (len && line[len - 1] == NEWLINE) line[len - 1] = 0;

    /* if record handler is zero, we must be on the top level */
    if (!rechandler) {
      switch (parse_line(line, key, val)) {
       case 0:
        /* ignore comments and empty lines */
        continue;

       case 1:
        /* the line has no value part */
        log_msg(LOG_ERR, "%s: Keyword %s has no value in line '%s'", 
                         filename, key, line);
        continue;

       case 2:
        /* check if the value part has the trailing '{' */
        len = strlen(val);
        if (val[len - 1] != '{') {
          log_msg(LOG_ERR, "%s: Keyword %s has no opening '{' in line '%s'", 
                           filename, key, line);
          continue;
        }

        /* if the trailing '{' is present, this is the start of a new
           record definition. Remove '{' and save the name of the record
           to the name variable, then check the type of the record and
           set the record handler accordingly */
        for (--len; len && isspace((int) val[len - 1]); --len);
        if (!len) {
          log_msg(LOG_ERR, "%s: Keyword %s has no following name in line '%s'", 
                           filename, key, line);
          continue;
        }
        val[len] = 0;
        strcpy(name, val);

        if (!strcasecmp(key, "input")) rechandler = parse_input;
        else if (!strcasecmp(key, "output")) rechandler = parse_output;
        else if (!strcasecmp(key, "filter")) rechandler = parse_filter;
        else if (!strcasecmp(key, "flow")) rechandler = parse_flow;
        else log_msg(LOG_ERR, "%s: Illegal keyword %s in line '%s'", 
                              filename, key, line);
      }
    }
    /* if record handler is not zero, we must be on the record level */
    else
      switch (parse_line(line, key, val)) {
       case 0:
        /* ignore comments and empty lines */
        continue;

       case 1:
        /* if the value is missing and the keyword is not '}',
           the line is invalid */
        if (strcmp(key, "}")) {
          log_msg(LOG_ERR, "%s: Keyword %s has no value in line '%s'", 
                           filename, key, line);
          continue;
        }
        /* if the value is missing and the keyword is '}', this ends 
           the record definition. Process the collected lines with
           the record handler and set return value to 1 if the record
           contained valid elements */ 
        if (rechandler(rec, name, filename)) ret = 1;
        /* free the record memory and reset the handler */
        while (rec) {
          my_free(rec->key);
          my_free(rec->val);
          ptr = rec->next;
          my_free(rec);
          rec = ptr;
        }
        rechandler = 0;
        continue;

       case 2:
        /* store the keyword-value pairs into the 'rec' list, so that
           they can be processed later */
        if (rec) {
          for (ptr = rec; ptr->next; ptr = ptr->next);
          ptr->next = (struct keyval *) my_malloc(sizeof(struct keyval));
          ptr = ptr->next;
        } else {
          rec = (struct keyval *) my_malloc(sizeof(struct keyval));
          ptr = rec;
        }
        ptr->key = (char *) my_malloc(sizeof(char) * (strlen(key) + 1));
        ptr->val = (char *) my_malloc(sizeof(char) * (strlen(val) + 1));
        ptr->next = 0;
        strcpy(ptr->key, key);
        strcpy(ptr->val, val);
      }
  }

  /* if we have a valid 'rec' around, the last definition was invalid */
  if (rec) {
    log_msg(LOG_ERR, "%s: Last definition has no closing '}'", filename);
    if (rechandler(rec, name, filename)) ret = 1;
    while (rec) {
      my_free(rec->key);
      my_free(rec->val);
      ptr = rec->next;
      my_free(rec);
      rec = ptr;
    }
  }

  /* free the buffers created for configuration parsing */
  my_free(line);
  my_free(key);
  my_free(val);
  my_free(name);

  /* close the configuration file and return */
  fclose(file);
  return ret;
}
