#include <stdlib.h>

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "ssl_conf.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "connection.h"
#include "request.h"
#include "proc_rc.h"

#include "log.h"
#include "common.h"
#include "ticket.h"
#include "utils.h"

#include "config.h"

rc_array_header_t *rc_server_pre_read_config = NULL;
rc_array_header_t *rc_server_post_read_config = NULL;
rc_directive_t *rc_conftree = NULL;
static rc_hash_t *rc_config_hash = NULL;

static void rebuild_conf_hash (struct rovm *r, rc_pool_t *p, int add_prelinked);

typedef struct rc_mod_list_struct rc_mod_list;
struct rc_mod_list_struct 
{
  struct rc_mod_list_struct *next;
  struct rovm *r;
  const command_rec *cmd;
};

char *
rv_server_root_relative (apr_pool_t *p, const char *file)
{
  char *newpath = NULL;
  apr_status_t rv;
  rv = apr_filepath_merge(&newpath, ROVMPREFIX, file,
                          APR_FILEPATH_TRUENAME, p);
  if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
                  || APR_STATUS_IS_ENOENT(rv)
                  || APR_STATUS_IS_ENOTDIR(rv))) 
    return newpath;
  else
    return NULL;
}

static void
rc_add_module_commands (struct rovm *r, apr_pool_t *p)
{
  apr_pool_t *tpool;
  rc_mod_list *mln;
  const command_rec *cmd;
  char *dir;
  
  cmd = r->cmds;
  
  if (rc_config_hash == NULL)
    rebuild_conf_hash (r, p, 0);

  tpool = apr_hash_pool_get (rc_config_hash);

  while (cmd && cmd->name) 
    {
      mln = apr_palloc (tpool, sizeof(rc_mod_list));
      mln->cmd = cmd;
      mln->r = r;
      dir = apr_pstrdup (tpool, cmd->name);
      
      rc_str_tolower (dir);
      
      mln->next = apr_hash_get (rc_config_hash, dir, APR_HASH_KEY_STRING);
      apr_hash_set (rc_config_hash, dir, APR_HASH_KEY_STRING, mln);
      ++cmd;
    }
}

static rc_status_t
reload_conf_hash (void *baton)
{
  rc_config_hash = NULL;

  return RC_SUCCESS;
}

static void 
rebuild_conf_hash (struct rovm *r, rc_pool_t *p, int add_prelinked)
{
  rc_config_hash = apr_hash_make (p);
  
  apr_pool_cleanup_register (p, NULL, reload_conf_hash, apr_pool_cleanup_null);
  
  if (add_prelinked)
    {
      rc_add_module_commands (r, p);
    }
}

/* Structure to be passed to cfg_open_custom(): it contains an
 * index which is incremented from 0 to nelts on each call to
 * cfg_getline() (which in turn calls arr_elts_getstr())
 * and an apr_array_header_t pointer for the string array.
 */
typedef struct 
{
  apr_array_header_t *array;
  int curr_idx;
} arr_elts_param_t;

static cmd_parms default_parms =
  {
    NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, NULL, NULL
  };

/** No directives */
#define OPT_NONE 0
/** Indexes directive */
#define OPT_INDEXES 1
/**  Includes directive */
#define OPT_INCLUDES 2
/**  FollowSymLinks directive */
#define OPT_SYM_LINKS 4
/**  ExecCGI directive */
#define OPT_EXECCGI 8
/**  directive unset */
#define OPT_UNSET 16
/**  IncludesNOEXEC directive */
#define OPT_INCNOEXEC 32
/** SymLinksIfOwnerMatch directive */
#define OPT_SYM_OWNER 64
/** MultiViews directive */
#define OPT_MULTI 128
/**  All directives */
#define OPT_ALL (OPT_INDEXES|OPT_INCLUDES|OPT_SYM_LINKS|OPT_EXECCGI)

/* arr_elts_getstr() returns the next line from the string array. */
static void *
arr_elts_getstr(void *buf, size_t bufsiz, void *param)
{
  arr_elts_param_t *arr_param = (arr_elts_param_t *)param;
  
  /* End of array reached? */
  if (++arr_param->curr_idx > arr_param->array->nelts)
    return NULL;
  
  /* return the line */
  apr_cpystrn(buf, ((char **)arr_param->array->elts)[arr_param->curr_idx - 1], bufsiz);
  
  return buf;
}


/* arr_elts_close(): dummy close routine (makes sure no more lines can be read) */
static int
arr_elts_close(void *param)
{
  arr_elts_param_t *arr_param = (arr_elts_param_t *)param;
  
  arr_param->curr_idx = arr_param->array->nelts;
  
  return 0;
}

/* Allocate a rc_configfile_t handle with user defined functions and params */
rc_configfile_t *
rc_pcfg_open_custom(apr_pool_t *p,
                    const char *descr,
                    void *param,
                    int(*getch)(void *param),
                    void *(*getstr) (void *buf, size_t bufsiz, void *param),
                    int(*close_func)(void *param))
{
  rc_configfile_t *new_cfg = apr_palloc (p, sizeof(*new_cfg));

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, "Opening config handler %s", descr);
  new_cfg->param = param;
  new_cfg->name = descr;
  new_cfg->getch = getch;
  new_cfg->getstr = getstr;
  new_cfg->close = close_func;
  new_cfg->line_number = 0;

  return new_cfg;
}

int 
rc_cfg_closefile (rc_configfile_t *cfp)
{
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
            "Done with config file %s", cfp->name);

  return (cfp->close == NULL) ? 0 : cfp->close(cfp->param);
}

/* Read one line from open rc_configfile_t, strip LF, increase line number */
/* If custom handler does not define a getstr() function, read char by char */
int
rc_cfg_getline (char *buf, size_t bufsize, rc_configfile_t *cfp)
{
  /* If a "get string" function is defined, use it */
  if (cfp->getstr != NULL) 
    {
      char *src, *dst;
      char *cp;
      char *cbuf = buf;
      size_t cbufsize = bufsize;
      
      while (1) 
        {
          ++cfp->line_number;
          if (cfp->getstr(cbuf, cbufsize, cfp->param) == NULL)
            return 1;
          
          /*
           *  check for line continuation,
           *  i.e. match [^\\]\\[\r]\n only
           */
          cp = cbuf;
          while (cp < cbuf+cbufsize && *cp != '\0')
            cp++;
          if (cp > cbuf && cp[-1] == LF) 
            {
              cp--;
              if (cp > cbuf && cp[-1] == CR)
                cp--;
              if (cp > cbuf && cp[-1] == '\\') 
                {
                  cp--;
                  if (!(cp > cbuf && cp[-1] == '\\')) 
                    {
                      /*
                       * line continuation requested -
                       * then remove backslash and continue
                       */
                      cbufsize -= (cp-cbuf);
                      cbuf = cp;
                      continue;
                    }
                  else 
                    {
                      /*
                       * no real continuation because escaped -
                       * then just remove escape character
                       */
                      for ( ; cp < cbuf+cbufsize && *cp != '\0'; cp++)
                        cp[0] = cp[1];
                    }
                }
            }
          break;
        }
      /*
       * Leading and trailing white space is eliminated completely
       */
      src = buf;
      while (apr_isspace(*src))
        ++src;
      /* blast trailing whitespace */
      dst = &src[strlen(src)];
      while (--dst >= src && apr_isspace(*dst))
        *dst = '\0';
      /* Zap leading whitespace by shifting */
      if (src != buf)
        for (dst = buf; (*dst++ = *src++) != '\0'; )
          ;
      
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, "Read config: %s", buf);
      
      return 0;
    }
  else 
    {
      /* No "get string" function defined; read character by character */
      register int c;
      register size_t i = 0;
      
      buf[0] = '\0';
      /* skip leading whitespace */
      do 
        {
          c = cfp->getch(cfp->param);
        } 
      while (c == '\t' || c == ' ');
      
      if (c == EOF)
        return 1;
      
      if(bufsize < 2) 
        {
          /* too small, assume caller is crazy */
          return 1;
        }
      while (1) 
        {
          if ((c == '\t') || (c == ' ')) 
            {
              buf[i++] = ' ';
              while ((c == '\t') || (c == ' '))
                c = cfp->getch(cfp->param);
            }
          if (c == CR) 
            {
              /* silently ignore CR (_assume_ that a LF follows) */
              c = cfp->getch(cfp->param);
            }
          if (c == LF) 
            {
              /* increase line number and return on LF */
              ++cfp->line_number;
            }
          if (c == EOF || c == 0x4 || c == LF || i >= (bufsize - 2)) 
            {
              /*
               *  check for line continuation
               */
              if (i > 0 && buf[i-1] == '\\') 
                {
                  i--;
                  if (!(i > 0 && buf[i-1] == '\\')) 
                    {
                      /* line is continued */
                      c = cfp->getch(cfp->param);
                      continue;
                    }
                  /* else nothing needs be done because
                   * then the backslash is escaped and
                   * we just strip to a single one
                   */
                }
              /* blast trailing whitespace */
              while (i > 0 && apr_isspace(buf[i - 1]))
                --i;
              buf[i] = '\0';

              rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                        "Read config: %s", buf);
              
              return 0;
            }
          buf[i] = c;
          ++i;
          c = cfp->getch(cfp->param);
        }
    }
}

/* Check a string for any ${ENV} environment variable
 * construct and replace each them by the value of
 * that environment variable, if it exists. If the
 * environment value does not exist, leave the ${ENV}
 * construct alone; it means something else.
 */
const char * 
rc_resolve_env (apr_pool_t *p, const char * word)
{
# define SMALL_EXPANSION 5
  struct sll 
  {
    struct sll *next;
    const char *string;
    apr_size_t len;
  } *result, *current, sresult[SMALL_EXPANSION];
  char *res_buf, *cp;
  const char *s, *e, *ep;
  unsigned spc;
  apr_size_t outlen;
  
  s = strchr (word, '$');
  if (!s)
    return word;
  
  /* well, actually something to do */
  ep = word + strlen(word);
  spc = 0;
  result = current = &(sresult[spc++]);
  current->next = NULL;
  current->string = word;
  current->len = s - word;
  outlen = current->len;
  
  do 
    {
      /* prepare next entry */
      if (current->len) 
        {
          current->next = (spc < SMALL_EXPANSION)
            ? &(sresult[spc++])
            : (struct sll *)apr_palloc(p,
                                       sizeof(*current->next));
          current = current->next;
          current->next = NULL;
          current->len = 0;
        }
      
      if (*s == '$') 
        {
          if (s[1] == '{' && (e = strchr (s, '}'))) 
            {
              word = getenv(apr_pstrndup(p, s+2, e-s-2));
              if (word) 
                {
                  current->string = word;
                  current->len = strlen(word);
                  outlen += current->len;
                }
              else 
                {
                  current->string = s;
                  current->len = e - s + 1;
                  outlen += current->len;
                }
              s = e + 1;
            }
          else 
            {
              current->string = s++;
              current->len = 1;
              ++outlen;
            }
        }
      else 
        {
          word = s;
          s = strchr (s, '$');
          current->string = word;
          current->len = s ? s - word : ep - word;
          outlen += current->len;
        }
    } 
  while (s && *s);
  
  /* assemble result */
  res_buf = cp = apr_palloc(p, outlen + 1);
  do 
    {
      if (result->len) 
        {
          memcpy(cp, result->string, result->len);
          cp += result->len;
        }
      result = result->next;
    } 
  while (result);
  res_buf[outlen] = '\0';
  
  return res_buf;
}

/* Get a word, (new) config-file style --- quoted strings and backslashes
 * all honored
 */

static char *
substring_conf(apr_pool_t *p, const char *start, int len, char quote)
{
  char *result = apr_palloc(p, len + 2);
  char *resp = result;
  int i;
  
  for (i = 0; i < len; ++i) 
    {
      if (start[i] == '\\' && (start[i + 1] == '\\'
                               || (quote && start[i + 1] == quote)))
        *resp++ = start[++i];
      else
        *resp++ = start[i];
    }
  
  *resp++ = '\0';
#if RESOLVE_ENV_PER_TOKEN
  return (char *)rc_resolve_env (p, result);
#else
  return result;
#endif
}

char * 
rc_getword_conf (apr_pool_t *p, const char **line)
{
  const char *str = *line, *strend;
  char *res;
  char quote;
  
  while (*str && apr_isspace(*str))
    ++str;
  
  if (!*str) 
    {
      *line = str;
      return "";
    }
  
  if ((quote = *str) == '"' || quote == '\'') 
    {
      strend = str + 1;
      while (*strend && *strend != quote) 
        {
          if (*strend == '\\' && strend[1] &&
              (strend[1] == quote || strend[1] == '\\')) 
            {
              strend += 2;
            }
          else 
            {
              ++strend;
            }
        }
      res = substring_conf (p, str + 1, strend - str - 1, quote);
      
      if (*strend == quote)
        ++strend;
    }
  else 
    {
      strend = str;
      while (*strend && !apr_isspace(*strend))
        ++strend;
      
      res = substring_conf(p, str, strend - str, 0);
    }
  
  while (*strend && apr_isspace(*strend))
    ++strend;
  *line = strend;

  return res;
}

const command_rec *
rc_find_command (const char *name,
                 const command_rec *cmds)
{
  while (cmds->name) 
    {
      if (!strcasecmp(name, cmds->name))
        return cmds;
      
      ++cmds;
    }
  
  return NULL;
}

const command_rec * 
rc_find_command_in_modules (const char *cmd_name, struct rovm *r)
{
  const command_rec *cmdp;

  if (r->cmds && (cmdp = rc_find_command (cmd_name, r->cmds))) 
    return cmdp;

  return NULL;
}

#define RC_MAX_ARGC 64

static const char *
invoke_cmd(const command_rec *cmd, cmd_parms *parms,
           void *mconfig, const char *args)
{
  char *w, *w2, *w3;
  const char *errmsg = NULL;
  
  if ((parms->override & cmd->req_override) == 0)
    return apr_pstrcat(parms->pool, cmd->name, " not allowed here", NULL);
  
  parms->info = cmd->cmd_data;
  parms->cmd = cmd;
  
  switch (cmd->args_how) 
    {
    case RAW_ARGS:
#ifdef RESOLVE_ENV_PER_TOKEN
      args = rc_resolve_env(parms->pool,args);
#endif
      return cmd->RC_RAW_ARGS(parms, mconfig, args);
      
    case TAKE_ARGV:
      {
        char *argv[RC_MAX_ARGC];
        int argc = 0;
        
        do 
          {
            w = rc_getword_conf (parms->pool, &args);
            if (*w == '\0' && *args == '\0') 
              {
                break;
              }
            argv[argc] = w;
            argc++;
          } 
        while (argc < RC_MAX_ARGC && *args != '\0');
        
        return cmd->RC_TAKE_ARGV(parms, mconfig, argc, argv);
      }
      
    case NO_ARGS:
      if (*args != 0)
        return apr_pstrcat(parms->pool, cmd->name, " takes no arguments",
                           NULL);
      
      return cmd->RC_NO_ARGS(parms, mconfig);
      
    case TAKE1:
      w = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name, " takes one argument",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE1(parms, mconfig, w);
      
    case TAKE2:
      w = rc_getword_conf(parms->pool, &args);
      w2 = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || *w2 == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name, " takes two arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE2(parms, mconfig, w, w2);
      
    case TAKE12:
      w = rc_getword_conf(parms->pool, &args);
      w2 = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL);
      
    case TAKE3:
      w = rc_getword_conf(parms->pool, &args);
      w2 = rc_getword_conf(parms->pool, &args);
      w3 = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name, " takes three arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE3(parms, mconfig, w, w2, w3);
      
    case TAKE23:
      w = rc_getword_conf(parms->pool, &args);
      w2 = rc_getword_conf(parms->pool, &args);
      w3 = *args ? rc_getword_conf(parms->pool, &args) : NULL;
      
      if (*w == '\0' || *w2 == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name,
                           " takes two or three arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE3(parms, mconfig, w, w2, w3);
      
    case TAKE123:
      w = rc_getword_conf(parms->pool, &args);
      w2 = *args ? rc_getword_conf(parms->pool, &args) : NULL;
      w3 = *args ? rc_getword_conf(parms->pool, &args) : NULL;
      
      if (*w == '\0' || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name,
                           " takes one, two or three arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE3(parms, mconfig, w, w2, w3);
      
    case TAKE13:
      w = rc_getword_conf(parms->pool, &args);
      w2 = *args ? rc_getword_conf(parms->pool, &args) : NULL;
      w3 = *args ? rc_getword_conf(parms->pool, &args) : NULL;
      
      if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0)
        return apr_pstrcat(parms->pool, cmd->name,
                           " takes one or three arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      return cmd->RC_TAKE3(parms, mconfig, w, w2, w3);
      
    case ITERATE:
      while (*(w = rc_getword_conf(parms->pool, &args)) != '\0') 
        {
          errmsg = cmd->RC_TAKE1(parms, mconfig, w);
          
          if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
            return errmsg;
        }
      
      return errmsg;
    case ITERATE2:
      w = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || *args == 0)
        return apr_pstrcat(parms->pool, cmd->name,
                           " requires at least two arguments",
                           cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
      
      while (*(w2 = rc_getword_conf(parms->pool, &args)) != '\0') 
        {
          errmsg = cmd->RC_TAKE2(parms, mconfig, w, w2);
          
          if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
            return errmsg;
        }
      
      return errmsg;
      
    case FLAG:
      w = rc_getword_conf(parms->pool, &args);
      
      if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off")))
        return apr_pstrcat(parms->pool, cmd->name, " must be On or Off",
                           NULL);
      
      return cmd->RC_FLAG(parms, mconfig, strcasecmp(w, "off") != 0);
      
    default:
      return apr_pstrcat(parms->pool, cmd->name,
                         " is improperly configured internally (server bug)",
                         NULL);
    }
}

static const char *
execute_now(char *cmd_line, const char *args,
            cmd_parms *parms,
            apr_pool_t *p, apr_pool_t *ptemp,
            rc_directive_t **sub_tree,
            rc_directive_t *parent)
{
  const command_rec *cmd;
  rc_mod_list *ml;
  char *dir = apr_pstrdup (parms->pool, cmd_line);
  
  rc_str_tolower (dir);
  
  ml = apr_hash_get(rc_config_hash, dir, APR_HASH_KEY_STRING);
  
  if (ml == NULL) 
    {
      return apr_pstrcat(parms->pool, "Invalid command '",
                         cmd_line,
                         "', perhaps misspelled or defined by a module "
                         "not included in the server configuration",
                         NULL);
    }
  
  for ( ; ml != NULL; ml = ml->next) 
    {
      const char *retval;
      cmd = ml->cmd;
      
      retval = invoke_cmd (cmd, parms, sub_tree, args);
      
      if (retval != NULL) 
        return retval;
    }
  
  return NULL;
}

rc_directive_t *
rc_add_node (rc_directive_t **parent, rc_directive_t *current,
             rc_directive_t *toadd, int child)
{
  if (current == NULL) 
    {
      /* we just started a new parent */
      if (*parent != NULL) 
        {
          (*parent)->first_child = toadd;
          toadd->parent = *parent;
        }
      if (child) 
        {
          /* First item in config file or container is a container */
          *parent = toadd;
          return NULL;
        }
      return toadd;
    }
  current->next = toadd;
  toadd->parent = *parent;
  if (child) 
    {
      /* switch parents, navigate into child */
      *parent = toadd;
      return NULL;
    }
  return toadd;
}

static const char *
rc_build_config_sub(struct rovm *r,
                    apr_pool_t *p, apr_pool_t *temp_pool,
                    const char *l, cmd_parms *parms,
                    rc_directive_t **current,
                    rc_directive_t **curr_parent,
                    rc_directive_t **conftree)
{
  const char *retval = NULL;
  const char *args;
  char *cmd_name;
  rc_directive_t *newdir;
  const command_rec *cmd;
  
  if (*l == '#' || *l == '\0')
    return NULL;
  
#if RESOLVE_ENV_PER_TOKEN
  args = l;
#else
  args = rc_resolve_env (temp_pool, l);
#endif
  
  cmd_name = rc_getword_conf (p, &args);
  if (*cmd_name == '\0') 
    {
      /* Note: this branch should not occur. An empty line should have
       * triggered the exit further above.
       */
      return NULL;
    }
  
  if (cmd_name[1] != '/') 
    {
      char *lastc = cmd_name + strlen(cmd_name) - 1;
      if (*lastc == '>')
        *lastc = '\0' ;
      if (cmd_name[0] == '<' && *args == '\0') 
        args = ">";
    }
  
  newdir = apr_pcalloc(p, sizeof(rc_directive_t));
  newdir->filename = parms->config_file->name;
  newdir->line_num = parms->config_file->line_number;
  newdir->directive = cmd_name;
  newdir->args = apr_pstrdup(p, args);
  
  if ((cmd = rc_find_command_in_modules (cmd_name, r)) != NULL) 
    {
      if (cmd->req_override & EXEC_ON_READ) 
        {
          rc_directive_t *sub_tree = NULL;
          
          parms->err_directive = newdir;
          retval = execute_now(cmd_name, args, parms, p, temp_pool,
                               &sub_tree, *curr_parent);
          if (*current) 
            (*current)->next = sub_tree;
          else 
            {
              *current = sub_tree;
              if (*curr_parent) 
                (*curr_parent)->first_child = (*current);
              if (*current)
                (*current)->parent = (*curr_parent);
            }
          if (*current) 
            {
              if (!*conftree) 
                {
                  /* Before walking *current to the end of the list,
                   * set the head to *current.
                   */
                  *conftree = *current;
                }
              while ((*current)->next != NULL) 
                {
                  (*current) = (*current)->next;
                  (*current)->parent = (*curr_parent);
                }
            }
          return retval;
        }
    }
  
  if (cmd_name[0] == '<') 
    {
      if (cmd_name[1] != '/') 
        {
          (*current) = rc_add_node (curr_parent, *current, newdir, 1);
        }
      else if (*curr_parent == NULL) 
        {
          parms->err_directive = newdir;
          return apr_pstrcat(p, cmd_name,
                             " without matching <", cmd_name + 2,
                             " section", NULL);
        }
      else 
        {
          char *bracket = cmd_name + strlen(cmd_name) - 1;
          
          if (*bracket != '>') 
            {
              parms->err_directive = newdir;
              return apr_pstrcat(p, cmd_name,
                                 "> directive missing closing '>'", NULL);
            }
          
          *bracket = '\0';
          
          if (strcasecmp(cmd_name + 2,
                         (*curr_parent)->directive + 1) != 0) 
            {
              parms->err_directive = newdir;
              return apr_pstrcat(p, "Expected </",
                                 (*curr_parent)->directive + 1, "> but saw ",
                                 cmd_name, ">", NULL);
            }
          
          *bracket = '>';
          
          /* done with this section; move up a level */
          *current = *curr_parent;
          *curr_parent = (*current)->parent;
        }
    }
  else
    *current = rc_add_node (curr_parent, *current, newdir, 0);
  
  return retval;
}

const char * rc_build_config (struct rovm *r, cmd_parms *parms,
                              apr_pool_t *p, apr_pool_t *temp_pool,
                              rc_directive_t **conftree)
{
  rc_directive_t *current = *conftree;
  rc_directive_t *curr_parent = NULL;
  char *l = apr_palloc (temp_pool, MAX_STRING_LEN);
  const char *errmsg;
  
  if (current != NULL) 
    {
      while (current->next)
        current = current->next;
    }
  
  while (!(rc_cfg_getline (l, MAX_STRING_LEN, parms->config_file)))
    {
      errmsg = rc_build_config_sub (r, p, temp_pool, l, parms,
                                    &current, &curr_parent, conftree);
      if (errmsg != NULL)
        return errmsg;
      
      if (*conftree == NULL && curr_parent != NULL)
        *conftree = curr_parent;
      
      if (*conftree == NULL && current != NULL)
        *conftree = current;
    }
  
  if (curr_parent != NULL) 
    {
      errmsg = "";
      
      while (curr_parent != NULL) 
        {
          errmsg = apr_psprintf(p, "%s%s%s:%u: %s> was not closed.",
                                errmsg,
                                *errmsg == '\0' ? "" : APR_EOL_STR,
                                curr_parent->filename,
                                curr_parent->line_num,
                                curr_parent->directive);
          
          parms->err_directive = curr_parent;
          curr_parent = curr_parent->parent;
        }
      
      return errmsg;
    }
  
  return NULL;
}

static const char *
process_command_config (struct rovm *r,
                        rc_array_header_t *arr,
                        rc_directive_t **conftree,
                        rc_pool_t *p,
                        rc_pool_t *ptemp)
{
  const char *errmsg;
  cmd_parms parms;
  arr_elts_param_t arr_parms;
  
  arr_parms.curr_idx = 0;
  arr_parms.array = arr;
  
  if (rc_config_hash == NULL) 
    rebuild_conf_hash (r, ROVM_PCONF (r), 1);

  parms = default_parms;
  parms.pool = p;
  parms.temp_pool = ptemp;
  parms.r = r;
  parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
  parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
  
  parms.config_file = rc_pcfg_open_custom(p, "-c/-C directives",
                                          &arr_parms, NULL,
                                          arr_elts_getstr, arr_elts_close);
  
  errmsg = rc_build_config (r, &parms, p, ptemp, conftree);
  rc_cfg_closefile (parms.config_file);
  
  if (errmsg)
    return apr_pstrcat (p, "Syntax error in -C/-c directive: ", errmsg, NULL);
  
  return NULL;
}

int
init_config_globals (p)
     rc_pool_t *p;
{
  return 0;
}

int 
rv_is_directory (apr_pool_t *p, const char *path)
{
  apr_finfo_t finfo;
  
  if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS)
    return 0;                /* in error condition, just return no */
  
  return (finfo.filetype == APR_DIR);
}

char * 
rv_make_full_path (apr_pool_t *a, const char *src1,
                   const char *src2)
{
  apr_size_t len1, len2;
  char *path;
  
  len1 = strlen(src1);
  len2 = strlen(src2);
  /* allocate +3 for '/' delimiter, trailing NULL and overallocate
   * one extra byte to allow the caller to add a trailing '/'
   */
  path = (char *)apr_palloc(a, len1 + len2 + 3);
  if (len1 == 0) 
    {
      *path = '/';
      memcpy(path + 1, src2, len2 + 1);
    }
  else 
    {
      char *next;
      memcpy(path, src1, len1);
      next = path + len1;
      if (next[-1] != '/')
        *next++ = '/';
      memcpy(next, src2, len2 + 1);
    }
  return path;
}

typedef struct 
{
  char *fname;
} fnames;

static int
fname_alphasort (const void *fn1, const void *fn2)
{
  const fnames *f1 = fn1;
  const fnames *f2 = fn2;
  
  return strcmp(f1->fname, f2->fname);
}

static apr_status_t 
cfg_close(void *param)
{
  apr_file_t *cfp = (apr_file_t *) param;
  return (apr_file_close (cfp));
}

static int
cfg_getch(void *param)
{
  char ch;
  apr_file_t *cfp = (apr_file_t *) param;
  if (apr_file_getc(&ch, cfp) == APR_SUCCESS)
    return ch;
  return (int)EOF;
}

static void *
cfg_getstr(void *buf, size_t bufsiz, void *param)
{
  apr_file_t *cfp = (apr_file_t *) param;
  apr_status_t rv;
  rv = apr_file_gets(buf, bufsiz, cfp);
  if (rv == APR_SUCCESS)
    return buf;

  return NULL;
}

/* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */
rc_status_t 
rv_pcfg_openfile (rc_configfile_t **ret_cfg, rc_pool_t *p, const char *name)
{
  rc_configfile_t *new_cfg;
  apr_file_t *file = NULL;
  apr_finfo_t finfo;
  apr_status_t status;
  char buf[120];
  
  if (name == NULL) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Internal error: pcfg_openfile() called with NULL filename");
      return APR_EBADF;
    }
  
  status = apr_file_open(&file, name, APR_READ | APR_BUFFERED,
                         APR_OS_DEFAULT, p);

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
            "Opening config file %s (%s)",
            name, (status != APR_SUCCESS) ?
            apr_strerror(status, buf, sizeof(buf)) : "successful");

  if (status != APR_SUCCESS)
    return status;
  
  status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file);
  if (status != APR_SUCCESS)
    return status;
  
  if (finfo.filetype != APR_REG &&
#if defined(WIN32) || defined(OS2) || defined(NETWARE)
      strcasecmp(apr_filepath_name_get(name), "nul") != 0) {
#else
    strcmp(name, "/dev/null") != 0) 
    {
#endif /* WIN32 || OS2 */
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Access to file %s denied by server: not a regular file",
                name);
      apr_file_close (file);
      return APR_EBADF;
    }
    
#ifdef WIN32
  /* Some twisted character [no pun intended] at MS decided that a
   * zero width joiner as the lead wide character would be ideal for
   * describing Unicode text files.  This was further convoluted to
   * another MSism that the same character mapped into utf-8, EF BB BF
   * would signify utf-8 text files.
   *
   * Since MS configuration files are all protecting utf-8 encoded
   * Unicode path, file and resource names, we already have the correct
   * WinNT encoding.  But at least eat the stupid three bytes up front.
   */
  {
    unsigned char buf[4];
    apr_size_t len = 3;
    status = apr_file_read(file, buf, &len);
    if ((status != APR_SUCCESS) || (len < 3)
        || memcmp(buf, "\xEF\xBB\xBF", 3) != 0) 
      {
        apr_off_t zero = 0;
        apr_file_seek(file, APR_SET, &zero);
      }
  }
#endif
  
  new_cfg = apr_palloc(p, sizeof(*new_cfg));
  new_cfg->param = file;
  new_cfg->name = apr_pstrdup(p, name);
  new_cfg->getch = (int (*)(void *)) cfg_getch;
  new_cfg->getstr = (void *(*)(void *, size_t, void *)) cfg_getstr;
  new_cfg->close = (int (*)(void *)) cfg_close;
  new_cfg->line_number = 0;
  *ret_cfg = new_cfg;
  return APR_SUCCESS;
}

#define RC_MAX_INCLUDE_DIR_DEPTH (128)

static const char *
process_resource_config_nofnmatch(struct rovm *r,
                                  const char *fname,
                                  rc_directive_t **conftree,
                                  apr_pool_t *p,
                                  apr_pool_t *ptemp,
                                  unsigned depth)
{
  cmd_parms parms;
  rc_configfile_t *cfp;
  const char *error;
  apr_status_t rv;
  
  if (rv_is_directory (p, fname))
    {
      apr_dir_t *dirp;
      apr_finfo_t dirent;
      int current;
      apr_array_header_t *candidates = NULL;
      fnames *fnew;
      char *path = apr_pstrdup (p, fname);
      
      if (++depth > RC_MAX_INCLUDE_DIR_DEPTH) 
        {
          return apr_psprintf(p, "Directory %s exceeds the maximum include "
                              "directory nesting level of %u. You have "
                              "probably a recursion somewhere.", path,
                              RC_MAX_INCLUDE_DIR_DEPTH);
        }
      
      /*
       * first course of business is to grok all the directory
       * entries here and store 'em away. Recall we need full pathnames
       * for this.
       */
      rv = apr_dir_open (&dirp, path, p);
      if (rv != APR_SUCCESS) 
        {
          char errmsg[120];
          return apr_psprintf (p, "Could not open config directory %s: %s",
                               path, apr_strerror (rv, errmsg, sizeof errmsg));
        }
      
      candidates = apr_array_make (p, 1, sizeof(fnames));
      while (apr_dir_read (&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) 
        {
          /* strip out '.' and '..' */
          if (strcmp(dirent.name, ".")
              && strcmp(dirent.name, "..")) 
            {
              fnew = (fnames *) apr_array_push(candidates);
              fnew->fname = rv_make_full_path (p, path, dirent.name);
            }
        }
      
      apr_dir_close (dirp);
      if (candidates->nelts != 0) 
        {
          qsort((void *) candidates->elts, candidates->nelts,
                sizeof(fnames), fname_alphasort);
          
          /*
           * Now recurse these... we handle errors and subdirectories
           * via the recursion, which is nice
           */
          for (current = 0; current < candidates->nelts; ++current) 
            {
              fnew = &((fnames *) candidates->elts)[current];
              error = process_resource_config_nofnmatch (r, fnew->fname, conftree, p, ptemp, depth);
              if (error)
                return error;
            }
        }
      
      return NULL;
    }
  
  /* GCC's initialization extensions are soooo nice here... */
  parms = default_parms;
  parms.pool = p;
  parms.temp_pool = ptemp;
  parms.r = r;
  parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
  parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
  
  rv = rv_pcfg_openfile (&cfp, p, fname);
  if (rv != APR_SUCCESS) 
    {
      char errmsg[120];
      return apr_psprintf(p, "Could not open configuration file %s: %s",
                          fname, apr_strerror(rv, errmsg, sizeof errmsg));
    }
  
  parms.config_file = cfp;
  error = rc_build_config (r, &parms, p, ptemp, conftree);
  rc_cfg_closefile (cfp);
  
  if (error) 
    {
      return apr_psprintf(p, "Syntax error on line %d of %s: %s",
                          parms.err_directive->line_num,
                          parms.err_directive->filename, error);
    }
  
  return NULL;
}

const char * 
rc_process_resource_config (struct rovm *r,
                            const char *fname,
                            rc_directive_t **conftree,
                            apr_pool_t *p,
                            apr_pool_t *ptemp)
{
  /* XXX: lstat() won't work on the wildcard pattern...  */
  
  /* don't require conf/rovm.conf if we have a -C or -c switch */
  if ((rc_server_pre_read_config->nelts
       || rc_server_post_read_config->nelts)
      && !(strcmp (fname, rv_server_root_relative (p, "conf/rovm.conf")))) 
    {
      apr_finfo_t finfo;
      
      if (apr_stat(&finfo, fname, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS)
        return NULL;
    }
  
  if (!apr_fnmatch_test (fname)) 
    return process_resource_config_nofnmatch (r, fname, conftree, p, ptemp, 0);
  else 
    {
      apr_dir_t *dirp;
      apr_finfo_t dirent;
      int current;
      apr_array_header_t *candidates = NULL;
      fnames *fnew;
      apr_status_t rv;
      char *path = apr_pstrdup(p, fname), *pattern = NULL;
      
      pattern = strrchr (path, '/');
      
      *pattern++ = '\0';
      
      if (apr_fnmatch_test(path)) 
        {
          return apr_pstrcat(p, "Wildcard patterns not allowed in Include ",
                             fname, NULL);
        }
      
      if (!rv_is_directory(p, path))
        {
          return apr_pstrcat(p, "Include directory '", path, "' not found",
                             NULL);
        }
      
      if (!apr_fnmatch_test(pattern)) 
        {
          return apr_pstrcat(p, "Must include a wildcard pattern for "
                             "Include ", fname, NULL);
        }
      
      /*
       * first course of business is to grok all the directory
       * entries here and store 'em away. Recall we need full pathnames
       * for this.
       */
      rv = apr_dir_open(&dirp, path, p);
      if (rv != APR_SUCCESS) 
        {
          char errmsg[120];
          return apr_psprintf(p, "Could not open config directory %s: %s",
                              path, apr_strerror(rv, errmsg, sizeof errmsg));
        }
      
      candidates = apr_array_make(p, 1, sizeof(fnames));
      while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) 
        {
          /* strip out '.' and '..' */
          if (strcmp(dirent.name, ".")
              && strcmp(dirent.name, "..")
              && (apr_fnmatch(pattern, dirent.name,
                              APR_FNM_PERIOD) == APR_SUCCESS)) 
            {
              fnew = (fnames *) apr_array_push(candidates);
              fnew->fname = rv_make_full_path (p, path, dirent.name);
            }
        }
      
      apr_dir_close(dirp);
      if (candidates->nelts != 0) 
        {
          const char *error;
          
          qsort((void *) candidates->elts, candidates->nelts,
                sizeof(fnames), fname_alphasort);
          
          /*
           * Now recurse these... we handle errors and subdirectories
           * via the recursion, which is nice
           */
          for (current = 0; current < candidates->nelts; ++current) 
            {
              fnew = &((fnames *) candidates->elts)[current];
              error = process_resource_config_nofnmatch (r, fnew->fname,
                                                         conftree, p,
                                                         ptemp, 0);
              if (error)
                return error;
            }
        }
    }
  
  return NULL;
}

int
rc_read_config (r, ptemp, filename, conftree)
     struct rovm *r;
     rc_pool_t *ptemp;
     const char *filename;
     rc_directive_t **conftree;
{
  const char *confname, *error;
  rc_pool_t *p = ROVM_PCONF (r);
    
  init_config_globals (p);

  error = process_command_config (r, rc_server_pre_read_config, conftree, p, ptemp);
  if (error)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "%s", error);
      return -1;
    }

  /* process_command_config may change the ServerRoot so
     compute this config file name afterwards.  */
  confname = rv_server_root_relative (p, filename);
  if (!confname)
    {
      rovm_log (NULL, ROVMLOG_STARTUP|ROVMLOG_CRIT, ROVMLOG_MARK,
                "Invalid config file path %s", filename);
      return -1;
    }

  error = rc_process_resource_config (r, confname, conftree, p, ptemp);
  if (error)
    {
      rovm_log (NULL, ROVMLOG_STARTUP|ROVMLOG_CRIT, ROVMLOG_MARK, "%s", error);
      return -1;
    }

  error = process_command_config (r, rc_server_post_read_config, conftree, p, ptemp);
  if (error)
    {
      rovm_log (NULL, ROVMLOG_STARTUP|ROVMLOG_CRIT, ROVMLOG_MARK, "%s", error);
      return -1;
    }

  return 0;
}

static const char *
rc_walk_config_sub(struct rovm *r,
                   const rc_directive_t *current,
                   cmd_parms *parms)
{
  const command_rec *cmd;
  rc_mod_list *ml;
  char *dir = apr_pstrdup(parms->pool, current->directive);
  
  rc_str_tolower(dir);
  
  ml = apr_hash_get (rc_config_hash, dir, APR_HASH_KEY_STRING);
  
  if (ml == NULL) 
    {
      parms->err_directive = current;
      return apr_pstrcat(parms->pool, "Invalid command '",
                         current->directive,
                         "', perhaps misspelled or defined by a module "
                         "not included in the server configuration",
                         NULL);
    }
  
  for ( ; ml != NULL; ml = ml->next) 
    {
      const char *retval;
      cmd = ml->cmd;
      
      /* Once was enough? */
      if (cmd->req_override & EXEC_ON_READ) 
        continue;

      retval = invoke_cmd (cmd, parms, NULL, current->args);
      
      if (retval != NULL && strcmp(retval, DECLINE_CMD) != 0) 
        {
          /* If the directive in error has already been set, don't
           * replace it.  Otherwise, an error inside a container
           * will be reported as occuring on the first line of the
           * container.
           */
          if (!parms->err_directive) 
            {
              parms->err_directive = current;
            }
          return retval;
        }
    }
  
  return NULL;
}

const char *
rc_walk_config (struct rovm *r, 
                rc_directive_t *current,
                cmd_parms *parms)
{
  /* scan through all directives, executing each one */
  for (; current != NULL; current = current->next) 
    {
      const char *errmsg;
      
      parms->directive = current;
      
      /* actually parse the command and execute the correct function */
      errmsg = rc_walk_config_sub (r, current, parms);
      if (errmsg != NULL) 
        return errmsg;
    }
  
  return NULL;
}

int
rc_process_config_tree(struct rovm *r,
                       rc_directive_t *conftree,
                       apr_pool_t *p,
                       apr_pool_t *ptemp)
{
  const char *errmsg;
  cmd_parms parms;
  
  parms = default_parms;
  parms.pool = p;
  parms.temp_pool = ptemp;
  parms.r = r;
  parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
  parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
  
  errmsg = rc_walk_config (r, conftree, &parms);
  if (errmsg)
    {
      rovm_log (NULL, ROVMLOG_STARTUP, ROVMLOG_MARK,
                "Syntax error on line %d of %s:",
                parms.err_directive->line_num,
                parms.err_directive->filename);
      rovm_log (NULL, ROVMLOG_STARTUP, ROVMLOG_MARK,
                "%s", errmsg);
      return -1;
    }
  
  return 0;
}
