/*
 * IRC - Internet Relay Chat, ircd/crule.c
 * Copyright (C) Tony Vencill <vencill@bga.com>
 *
 * 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 3, 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.
 */
/**
 * @file
 * @brief Connection rule parser and checker
 * @version $Id: crule.c 2156 2008-06-10 00:46:03Z sirvulcan $
 */

/*
 * SmartRoute phase 1
 * connection rule patch
 * by Tony Vencill (Tonto on IRC) <vencill@bga.com>
 *
 * The majority of this file is a recusive descent parser used to convert
 * connection rules into expression trees when the conf file is read.
 * All parsing structures and types are hidden in the interest of good
 * programming style and to make possible future data structure changes
 * without affecting the interface between this patch and the rest of the
 * server.  The only functions accessible externally are crule_parse,
 * crule_free, and crule_eval.  Prototypes for these functions can be
 * found in h.h.
 *
 * Please direct any connection rule or SmartRoute questions to Tonto on
 * IRC or by email to vencill@bga.com.
 *
 * For parser testing, defining CR_DEBUG generates a stand-alone parser
 * that takes rules from stdin and prints out memory allocation
 * information and the parsed rule.  This stand alone parser is ignorant
 * of the irc server and thus cannot do rule evaluation.  Do not define
 * this flag when compiling the server!  If you wish to generate the
 * test parser, compile from the ircd directory with a line similar to
 * cc -o parser -DCR_DEBUG crule.c
 *
 * The define CR_CHKCONF is provided to generate routines needed in
 * chkconf.  These consist of the parser, a different crule_parse that
 * prints errors to stderr, and crule_free (just for good style and to
 * more closely simulate the actual ircd environment).  crule_eval and
 * the rule functions are made empty functions as in the stand-alone
 * test parser.
 *
 */
#include "config.h"

#include "crule.h"
#ifndef CR_DEBUG

/* ircd functions and types we need */
#include "client.h"
#include "ircd.h"
#include "ircd_alloc.h"
#include "ircd_chattr.h"
#include "ircd_string.h"
#include "match.h"
#include "s_bsd.h"
#include "s_debug.h"
#include "ircd_struct.h"

#include <stdio.h>
#include <stdlib.h>

#else /* includes and defines to make the stand-alone test parser */

#include <stdio.h>
#include <stdlib.h>

#define BadPtr(x) (!(x) || (*(x) == '\0'))
#define DupString(x,y) \
        do { \
          x = (char*) MyMalloc(strlen(y)+1); \
        strcpy(x,y); \
        } while(0)

/* We don't care about collation discrepacies here, it seems.... */
#define ircd_strcmp strcasecmp

#endif

#include <string.h>


#if defined(CR_DEBUG) || defined(CR_CHKCONF)
#undef MyMalloc
#undef malloc
#define MyMalloc malloc
#undef MyFree
#undef free
#define MyFree free
#endif

/* some constants and shared data types */
#define CR_MAXARGLEN 80         /**< Maximum arg length (must be > HOSTLEN) */
#define CR_MAXARGS 3            /**< Maximum number of args for a rule */

/*
 * Some symbols for easy reading
 */

/** Input scanner tokens. */
enum crule_token {
  CR_UNKNOWN,    /**< Unknown token type. */
  CR_END,        /**< End of input ('\\0' or ':'). */
  CR_AND,        /**< Logical and operator (&&). */
  CR_OR,         /**< Logical or operator (||). */
  CR_NOT,        /**< Logical not operator (!). */
  CR_OPENPAREN,  /**< Open parenthesis. */
  CR_CLOSEPAREN, /**< Close parenthesis. */
  CR_COMMA,      /**< Comma. */
  CR_WORD        /**< Something that looks like a hostmask (alphanumerics, "*?.-"). */
};

/** Parser error codes. */
enum crule_errcode {
  CR_NOERR,      /**< No error. */
  CR_UNEXPCTTOK, /**< Invalid token given context. */
  CR_UNKNWTOK,   /**< Input did not form a valid token. */
  CR_EXPCTAND,   /**< Did not see expected && operator. */
  CR_EXPCTOR,    /**< Did not see expected || operator. */
  CR_EXPCTPRIM,  /**< Expected a primitive (parentheses, ! or word). */
  CR_EXPCTOPEN,  /**< Expected an open parenthesis after function name. */
  CR_EXPCTCLOSE, /**< Expected a close parenthesis to match open parenthesis. */
  CR_UNKNWFUNC,  /**< Attempt to use an unknown function. */
  CR_ARGMISMAT   /**< Wrong number of arguments to function. */
};

/*
 * Expression tree structure, function pointer, and tree pointer local!
 */
/** Evaluation function for a connection rule. */
typedef int (*crule_funcptr) (int, void **);

/** Node in a connection rule tree. */
struct CRuleNode {
  crule_funcptr funcptr; /**< Evaluation function for this node. */
  int numargs;           /**< Number of arguments. */
  void *arg[CR_MAXARGS]; /**< Array of arguments.  For operators, each arg
                            is a tree element; for functions, each arg is
                            a string. */
};

/** Typedef to save typing effort. */
typedef struct CRuleNode* CRuleNodePtr;

/* local rule function prototypes */
static int crule_connected(int, void **);
static int crule_directcon(int, void **);
static int crule_via(int, void **);
static int crule_directop(int, void **);
static int crule__andor(int, void **);
static int crule__not(int, void **);

/* local parsing function prototypes */
static int crule_gettoken(int* token, const char** str);
static void crule_getword(char*, int*, size_t, const char**);
static int crule_parseandexpr(CRuleNodePtr*, int *, const char**);
static int crule_parseorexpr(CRuleNodePtr*, int *, const char**);
static int crule_parseprimary(CRuleNodePtr*, int *, const char**);
static int crule_parsefunction(CRuleNodePtr*, int *, const char**);
static int crule_parsearglist(CRuleNodePtr, int *, const char**);

#if defined(CR_DEBUG) || defined(CR_CHKCONF)
/*
 * Prototypes for the test parser; if not debugging,
 * these are defined in h.h
 */
struct CRuleNode* crule_parse(const char*);
void crule_free(struct CRuleNode**);
#ifdef CR_DEBUG
void print_tree(CRuleNodePtr);
#endif
#endif

/** Error messages, indexed by the corresponding crule_errcode. */
char *crule_errstr[] = {
  "Unknown error",              /* NOERR? - for completeness */
  "Unexpected token",           /* UNEXPCTTOK */
  "Unknown token",              /* UNKNWTOK */
  "And expr expected",          /* EXPCTAND */
  "Or expr expected",           /* EXPCTOR */
  "Primary expected",           /* EXPCTPRIM */
  "( expected",                 /* EXPCTOPEN */
  ") expected",                 /* EXPCTCLOSE */
  "Unknown function",           /* UNKNWFUNC */
  "Argument mismatch"           /* ARGMISMAT */
};

/** Connection rule function table entry. */
struct crule_funclistent {
  char name[15];         /**< Function name. */
  int reqnumargs;        /**< Required number of arguments. */
  crule_funcptr funcptr; /**< Handler function. */
};

/** Defined connection rules. */
struct crule_funclistent crule_funclist[] = {
  /* maximum function name length is 14 chars */
  {"connected", 1, crule_connected},
  {"directcon", 1, crule_directcon},
  {"via", 2, crule_via},
  {"directop", 0, crule_directop},
  {"", 0, NULL}                 /* this must be here to mark end of list */
};

/** Check whether any connected server matches crulearg[0].
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule_connected(int numargs, void *crulearg[])
{
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
  struct Client *acptr;

  /* taken from m_links */
  for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr))
  {
    if (!IsServer(acptr) && !IsMe(acptr))
      continue;
    if (match((char *)crulearg[0], cli_name(acptr)))
      continue;
    return (1);
  }
#endif
  return (0);
}

/** Check whether any directly connected server matches crulearg[0].
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule_directcon(int numargs, void *crulearg[])
{
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
  int i;
  struct Client *acptr;

  /* adapted from m_trace and exit_one_client */
  for (i = 0; i <= HighestFd; i++)
  {
    if (!(acptr = LocalClientArray[i]) || !IsServer(acptr))
      continue;
    if (match((char *)crulearg[0], cli_name(acptr)))
      continue;
    return (1);
  }
#endif
  return (0);
}

/** Check whether a connected server matching crulearg[1] is
 * connnected to me behind one matching crulearg[0].
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule_via(int numargs, void *crulearg[])
{
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
  struct Client *acptr;

  /* adapted from m_links */
  for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr))
  {
    if (!IsServer(acptr) && !IsMe(acptr))
      continue;
    if (match((char *)crulearg[1], cli_name(acptr)))
      continue;
    if (match((char *)crulearg[0],
	      cli_name(LocalClientArray[cli_fd(cli_from(acptr))])))
      continue;
    return (1);
  }
#endif
  return (0);
}

/** Check whether we have a local IRC operator.
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule_directop(int numargs, void **crulearg)
{
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
  int i;
  struct Client *acptr;

  /* adapted from m_trace */
  for (i = 0; i <= HighestFd; i++)
  {
    if (!(acptr = LocalClientArray[i]) || !IsAnOper(acptr))
      continue;
    return (1);
  }
#endif
  return (0);
}

/** Perform an and-or-or test on crulearg[0] and crulearg[1].
 * If crulearg[2] is non-NULL, it means do OR; if it is NULL, do AND.
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule__andor(int numargs, void *crulearg[])
{
  int result1;

  result1 = ((CRuleNodePtr) crulearg[0])->funcptr
      (((CRuleNodePtr) crulearg[0])->numargs,
      ((CRuleNodePtr) crulearg[0])->arg);
  if (crulearg[2])              /* or */
    return (result1 ||
        ((CRuleNodePtr) crulearg[1])->funcptr
        (((CRuleNodePtr) crulearg[1])->numargs,
        ((CRuleNodePtr) crulearg[1])->arg));
  else
    return (result1 &&
        ((CRuleNodePtr) crulearg[1])->funcptr
        (((CRuleNodePtr) crulearg[1])->numargs,
        ((CRuleNodePtr) crulearg[1])->arg));
}

/** Logically invert the result of crulearg[0].
 * @param[in] numargs Number of valid args in \a crulearg.
 * @param[in] crulearg Argument array.
 * @return Non-zero if the condition is true, zero if not.
 */
static int crule__not(int numargs, void *crulearg[])
{
  return (!((CRuleNodePtr) crulearg[0])->funcptr
      (((CRuleNodePtr) crulearg[0])->numargs,
      ((CRuleNodePtr) crulearg[0])->arg));
}

#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
/** Evaluate a connection rule.
 * @param[in] rule Rule to evalute.
 * @return Non-zero if the rule allows the connection, zero otherwise.
 */
int crule_eval(struct CRuleNode* rule)
{
  return (rule->funcptr(rule->numargs, rule->arg));
}
#endif

/** Scan an input token from \a ruleptr.
 * @param[out] next_tokp Receives type of next token.
 * @param[in,out] ruleptr Next readable character from input.
 * @return Either CR_UNKNWTOK if the input was unrecognizable, else CR_NOERR.
 */
static int crule_gettoken(int* next_tokp, const char** ruleptr)
{
  char pending = '\0';

  *next_tokp = CR_UNKNOWN;
  while (*next_tokp == CR_UNKNOWN)
    switch (*(*ruleptr)++)
    {
      case ' ':
      case '\t':
        break;
      case '&':
        if (pending == '\0')
          pending = '&';
        else if (pending == '&')
          *next_tokp = CR_AND;
        else
          return (CR_UNKNWTOK);
        break;
      case '|':
        if (pending == '\0')
          pending = '|';
        else if (pending == '|')
          *next_tokp = CR_OR;
        else
          return (CR_UNKNWTOK);
        break;
      case '!':
        *next_tokp = CR_NOT;
        break;
      case '(':
        *next_tokp = CR_OPENPAREN;
        break;
      case ')':
        *next_tokp = CR_CLOSEPAREN;
        break;
      case ',':
        *next_tokp = CR_COMMA;
        break;
      case '\0':
        (*ruleptr)--;
        *next_tokp = CR_END;
        break;
      case ':':
        *next_tokp = CR_END;
        break;
      default:
        if ((IsAlpha(*(--(*ruleptr)))) || (**ruleptr == '*') ||
            (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-'))
          *next_tokp = CR_WORD;
        else
          return (CR_UNKNWTOK);
        break;
    }
  return CR_NOERR;
}

/** Scan a word from \a ruleptr.
 * @param[out] word Output buffer.
 * @param[out] wordlenp Length of word written to \a word (not including terminating NUL).
 * @param[in] maxlen Maximum number of bytes writable to \a word.
 * @param[in,out] ruleptr Next readable character from input.
 */
static void crule_getword(char* word, int* wordlenp, size_t maxlen, const char** ruleptr)
{
  char *word_ptr;

  word_ptr = word;
  while ((size_t)(word_ptr - word) < maxlen
      && (IsAlnum(**ruleptr)
      || **ruleptr == '*' || **ruleptr == '?'
      || **ruleptr == '.' || **ruleptr == '-'))
    *word_ptr++ = *(*ruleptr)++;
  *word_ptr = '\0';
  *wordlenp = word_ptr - word;
}

/** Parse an entire rule.
 * @param[in] rule Text form of rule.
 * @return CRuleNode for rule, or NULL if there was a parse error.
 */
struct CRuleNode* crule_parse(const char *rule)
{
  const char* ruleptr = rule;
  int next_tok;
  struct CRuleNode* ruleroot = 0;
  int errcode = CR_NOERR;

  if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
    if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) {
      if (ruleroot != NULL) {
        if (next_tok == CR_END)
          return (ruleroot);
        else
          errcode = CR_UNEXPCTTOK;
      }
      else
        errcode = CR_EXPCTOR;
    }
  }
  if (ruleroot != NULL)
    crule_free(&ruleroot);
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
  Debug((DEBUG_ERROR, "%s in rule: %s", crule_errstr[errcode], rule));
#else
  fprintf(stderr, "%s in rule: %s\n", crule_errstr[errcode], rule);
#endif
  return 0;
}

/** Parse an or expression.
 * @param[out] orrootp Receives parsed node.
 * @param[in,out] next_tokp Next input token type.
 * @param[in,out] ruleptr Next input character.
 * @return A crule_errcode value.
 */
static int crule_parseorexpr(CRuleNodePtr * orrootp, int *next_tokp, const char** ruleptr)
{
  int errcode = CR_NOERR;
  CRuleNodePtr andexpr;
  CRuleNodePtr orptr;

  *orrootp = NULL;
  while (errcode == CR_NOERR)
  {
    errcode = crule_parseandexpr(&andexpr, next_tokp, ruleptr);
    if ((errcode == CR_NOERR) && (*next_tokp == CR_OR))
    {
      orptr = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
#ifdef CR_DEBUG
      fprintf(stderr, "allocating or element at %ld\n", orptr);
#endif
      orptr->funcptr = crule__andor;
      orptr->numargs = 3;
      orptr->arg[2] = (void *)1;
      if (*orrootp != NULL)
      {
        (*orrootp)->arg[1] = andexpr;
        orptr->arg[0] = *orrootp;
      }
      else
        orptr->arg[0] = andexpr;
      *orrootp = orptr;
    }
    else
    {
      if (*orrootp != NULL)
      {
        if (andexpr != NULL)
        {
          (*orrootp)->arg[1] = andexpr;
          return (errcode);
        }
        else
        {
          (*orrootp)->arg[1] = NULL;    /* so free doesn't seg fault */
          return (CR_EXPCTAND);
        }
      }
      else
      {
        *orrootp = andexpr;
        return (errcode);
      }
    }
    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
      return (errcode);
  }
  return (errcode);
}

/** Parse an and expression.
 * @param[out] androotp Receives parsed node.
 * @param[in,out] next_tokp Next input token type.
 * @param[in,out] ruleptr Next input character.
 * @return A crule_errcode value.
 */
static int crule_parseandexpr(CRuleNodePtr * androotp, int *next_tokp, const char** ruleptr)
{
  int errcode = CR_NOERR;
  CRuleNodePtr primary;
  CRuleNodePtr andptr;

  *androotp = NULL;
  while (errcode == CR_NOERR)
  {
    errcode = crule_parseprimary(&primary, next_tokp, ruleptr);
    if ((errcode == CR_NOERR) && (*next_tokp == CR_AND))
    {
      andptr = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
#ifdef CR_DEBUG
      fprintf(stderr, "allocating and element at %ld\n", andptr);
#endif
      andptr->funcptr = crule__andor;
      andptr->numargs = 3;
      andptr->arg[2] = (void *)0;
      if (*androotp != NULL)
      {
        (*androotp)->arg[1] = primary;
        andptr->arg[0] = *androotp;
      }
      else
        andptr->arg[0] = primary;
      *androotp = andptr;
    }
    else
    {
      if (*androotp != NULL)
      {
        if (primary != NULL)
        {
          (*androotp)->arg[1] = primary;
          return (errcode);
        }
        else
        {
          (*androotp)->arg[1] = NULL;   /* so free doesn't seg fault */
          return (CR_EXPCTPRIM);
        }
      }
      else
      {
        *androotp = primary;
        return (errcode);
      }
    }
    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
      return (errcode);
  }
  return (errcode);
}

/** Parse a primary expression.
 * @param[out] primrootp Receives parsed node.
 * @param[in,out] next_tokp Next input token type.
 * @param[in,out] ruleptr Next input character.
 * @return A crule_errcode value.
 */
static int crule_parseprimary(CRuleNodePtr* primrootp, int *next_tokp, const char** ruleptr)
{
  CRuleNodePtr *insertionp;
  int errcode = CR_NOERR;

  *primrootp = NULL;
  insertionp = primrootp;
  while (errcode == CR_NOERR)
  {
    switch (*next_tokp)
    {
      case CR_OPENPAREN:
        if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
          break;
        if ((errcode = crule_parseorexpr(insertionp, next_tokp, ruleptr)) != CR_NOERR)
          break;
        if (*insertionp == NULL)
        {
          errcode = CR_EXPCTAND;
          break;
        }
        if (*next_tokp != CR_CLOSEPAREN)
        {
          errcode = CR_EXPCTCLOSE;
          break;
        }
        errcode = crule_gettoken(next_tokp, ruleptr);
        break;
      case CR_NOT:
        *insertionp = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
#ifdef CR_DEBUG
        fprintf(stderr, "allocating primary element at %ld\n", *insertionp);
#endif
        (*insertionp)->funcptr = crule__not;
        (*insertionp)->numargs = 1;
        (*insertionp)->arg[0] = NULL;
        insertionp = (CRuleNodePtr *) & ((*insertionp)->arg[0]);
        if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
          break;
        continue;
      case CR_WORD:
        errcode = crule_parsefunction(insertionp, next_tokp, ruleptr);
        break;
      default:
        if (*primrootp == NULL)
          errcode = CR_NOERR;
        else
          errcode = CR_EXPCTPRIM;
        break;
    }
    return (errcode);
  }
  return (errcode);
}

/** Parse a function call.
 * @param[out] funcrootp Receives parsed node.
 * @param[in,out] next_tokp Next input token type.
 * @param[in,out] ruleptr Next input character.
 * @return A crule_errcode value.
 */
static int crule_parsefunction(CRuleNodePtr* funcrootp, int* next_tokp, const char** ruleptr)
{
  int errcode = CR_NOERR;
  char funcname[CR_MAXARGLEN];
  int namelen;
  int funcnum;

  *funcrootp = NULL;
  crule_getword(funcname, &namelen, CR_MAXARGLEN - 1, ruleptr);
  if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
    return (errcode);
  if (*next_tokp == CR_OPENPAREN)
  {
    for (funcnum = 0;; funcnum++)
    {
      if (0 == ircd_strcmp(crule_funclist[funcnum].name, funcname))
        break;
      if (crule_funclist[funcnum].name[0] == '\0')
        return (CR_UNKNWFUNC);
    }
    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
      return (errcode);
    *funcrootp = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
#ifdef CR_DEBUG
    fprintf(stderr, "allocating function element at %ld\n", *funcrootp);
#endif
    (*funcrootp)->funcptr = NULL;       /* for freeing aborted trees */
    if ((errcode =
        crule_parsearglist(*funcrootp, next_tokp, ruleptr)) != CR_NOERR)
      return (errcode);
    if (*next_tokp != CR_CLOSEPAREN)
      return (CR_EXPCTCLOSE);
    if ((crule_funclist[funcnum].reqnumargs != (*funcrootp)->numargs) &&
        (crule_funclist[funcnum].reqnumargs != -1))
      return (CR_ARGMISMAT);
    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
      return (errcode);
    (*funcrootp)->funcptr = crule_funclist[funcnum].funcptr;
    return (CR_NOERR);
  }
  else
    return (CR_EXPCTOPEN);
}

/** Parse the argument list to a CRuleNode.
 * @param[in,out] argrootp Node whos argument list is being populated.
 * @param[in,out] next_tokp Next input token type.
 * @param[in,out] ruleptr Next input character.
 * @return A crule_errcode value.
 */
static int crule_parsearglist(CRuleNodePtr argrootp, int *next_tokp, const char** ruleptr)
{
  int errcode = CR_NOERR;
  char *argelemp = NULL;
  char currarg[CR_MAXARGLEN];
  int arglen = 0;
  char word[CR_MAXARGLEN];
  int wordlen = 0;

  argrootp->numargs = 0;
  currarg[0] = '\0';
  while (errcode == CR_NOERR)
  {
    switch (*next_tokp)
    {
      case CR_WORD:
        crule_getword(word, &wordlen, CR_MAXARGLEN - 1, ruleptr);
        if (currarg[0] != '\0')
        {
          if ((arglen + wordlen) < (CR_MAXARGLEN - 1))
          {
            strcat(currarg, " ");
            strcat(currarg, word);
            arglen += wordlen + 1;
          }
        }
        else
        {
          strcpy(currarg, word);
          arglen = wordlen;
        }
        errcode = crule_gettoken(next_tokp, ruleptr);
        break;
      default:
#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
        collapse(currarg);
#endif
        if (!BadPtr(currarg))
        {
          DupString(argelemp, currarg);
          argrootp->arg[argrootp->numargs++] = (void *)argelemp;
        }
        if (*next_tokp != CR_COMMA)
          return (CR_NOERR);
        currarg[0] = '\0';
        errcode = crule_gettoken(next_tokp, ruleptr);
        break;
    }
  }
  return (errcode);
}

/*
 * This function is recursive..  I wish I knew a nonrecursive way but
 * I dont.  anyway, recursion is fun..  :)
 * DO NOT CALL THIS FUNTION WITH A POINTER TO A NULL POINTER
 * (ie: If *elem is NULL, you're doing it wrong - seg fault)
 */
/** Free a connection rule and all its children.
 * @param[in,out] elem Pointer to pointer to element to free.  MUST NOT BE NULL.
 */
void crule_free(struct CRuleNode** elem)
{
  int arg, numargs;

  if ((*(elem))->funcptr == crule__not)
  {
    /* type conversions and ()'s are fun! ;)  here have an asprin.. */
    if ((*(elem))->arg[0] != NULL)
      crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
  }
  else if ((*(elem))->funcptr == crule__andor)
  {
    crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
    if ((*(elem))->arg[1] != NULL)
      crule_free((struct CRuleNode**) &((*(elem))->arg[1]));
  }
  else
  {
    numargs = (*(elem))->numargs;
    for (arg = 0; arg < numargs; arg++)
      MyFree((*(elem))->arg[arg]);
  }
#ifdef CR_DEBUG
  fprintf(stderr, "freeing element at %ld\n", *elem);
#endif
  MyFree(*elem);
  *elem = 0;
}

#ifdef CR_DEBUG
/** Display a connection rule as text.
 * @param[in] printelem Connection rule to display.
 */
static void print_tree(CRuleNodePtr printelem)
{
  int funcnum, arg;

  if (printelem->funcptr == crule__not)
  {
    printf("!( ");
    print_tree((CRuleNodePtr) printelem->arg[0]);
    printf(") ");
  }
  else if (printelem->funcptr == crule__andor)
  {
    printf("( ");
    print_tree((CRuleNodePtr) printelem->arg[0]);
    if (printelem->arg[2])
      printf("|| ");
    else
      printf("&& ");
    print_tree((CRuleNodePtr) printelem->arg[1]);
    printf(") ");
  }
  else
  {
    for (funcnum = 0;; funcnum++)
    {
      if (printelem->funcptr == crule_funclist[funcnum].funcptr)
        break;
      if (crule_funclist[funcnum].funcptr == NULL)
        MyCoreDump;
    }
    printf("%s(", crule_funclist[funcnum].name);
    for (arg = 0; arg < printelem->numargs; arg++)
    {
      if (arg != 0)
        printf(",");
      printf("%s", (char *)printelem->arg[arg]);
    }
    printf(") ");
  }
}

#endif

#ifdef CR_DEBUG
/** Read connection rules from stdin and display parsed forms as text.
 * @return Zero.
 */
int main(void)
{
  char indata[256];
  CRuleNode* rule;

  printf("rule: ");
  while (fgets(indata, 256, stdin) != NULL)
  {
    indata[strlen(indata) - 1] = '\0';  /* lose the newline */
    if ((rule = crule_parse(indata)) != NULL)
    {
      printf("equivalent rule: ");
      print_tree((CRuleNodePtr) rule);
      printf("\n");
      crule_free(&rule);
    }
    printf("\nrule: ");
  }
  printf("\n");

  return 0;
}

#endif
