/*******************************************************
 * Sieve (mail filter) parsing.
 *
 * Filename:      sieve.c
 * Author:        Randall Gellens
 * Last Modified: 4 November 1998
 * Version:       
 *
 * Copyright:     1998, QUALCOMM Incorporated,
 *                all rights reserved
 *******************************************************/


/*-- standard header files --*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>


/*-- local header files --*/
#include "skanx.h"
#include "sieve-struct.h"
#include "sieve-parse.h"

/*-- DAA trace utility --*/
#include "daa_trace.h"


/*--- external variables ---*/
extern int  iAllocCount;
extern char szActionTypeNames[],
            szOpTypeNames    [],
            szTestTypeNames  [],
            szTokenTypeNames [];



/*---------------------------------------------------------------
 *    m a c r o s
 *---------------------------------------------------------------
 */
#define TRACE_IT1(L,X)   if (psPF->debug >= L) tracef(__LINE__, FALSE, "%.250s",X)
#define WARN(X)          syntax_error(TRUE,  X, psPF, __LINE__)
#define TILT(X)          syntax_error(FALSE, X, psPF, __LINE__)
#define OOPS(X)          syntax_error(FALSE, X, NULL, __LINE__)
#define MALLOC(S,W)      my_malloc(S, psPF, W, __LINE__)
#define FREE(P)          my_free(P, debug, __LINE__)
#define GET_TOKEN        get_token_file(psPF, __LINE__)
#define BACKUP_TOKEN(D)  my_backup_token(psPF, D, __LINE__)
#define GET_STRING(F)    my_get_string(psPF, F, __LINE__)



/*---------------------------------------------------------------
 *   c o n s t a n t s
 *---------------------------------------------------------------
 */
#define LINES_CHUNK     1024  /* size to allocate strLines chunks */



/*---------------------------------------------------------------
 *    p r i v a t e   f u n c t i o n   p r o t o t y p e s
 *---------------------------------------------------------------
 */
       
static token_type  get_token_file (strParseFile *psPF, int ln);
static void        my_backup_token(strParseFile *psPF, short depth, int ln);
static char       *my_get_string  (strParseFile *psPF, int fEmptyOK, int ln);
static char       *get_line_file  (strParseFile *psPF, int ln);
static strStmt    *get_stmt       (strParseFile *psPF, int debug, int ln);
static strIf      *get_if         (strParseFile *psPF, int debug, int ln);
static strTest    *get_test       (strParseFile *psPF, int debug, int ln);
static strAction  *get_action     (strParseFile *psPF, int debug, int ln);
static strText    *get_string_list(strParseFile *psPF, int debug, int ln);
static int         get_strOrMsg   (strAction *theAction, strParseFile *psPF, int ln);



/*---------------------------------------------------------------
 *    f u n c t i o n s
 *---------------------------------------------------------------
 */


/*******************************************************************************/

/* 
 * Parses a script and checks syntax.
 *
 * Parameters:
 *    fname:   pointer to full path and file name of script file.
 *    work:    pointer to an IMDA structure for the incoming message.
 *    flags:   PARSE_ flags.
 *    debug:   current debug value.
 *
 * Returns:
 *    0 if all went well, error code otherwise.
 *
 */
int check_syntax ( char *fname, int flags, int debug)
    {
    FILE      *fScript;
    strScript *theScript;


    if ( debug > 1 )
        tracef(__LINE__, FALSE, "check_syntax starting; flags: 0x%X; file: %.255s", 
               flags, fname);

    /* open script */

    fScript = fopen ( fname, "r" );
    if ( !fScript )
        return ( -1 );

    /* parse script file into script object */

    theScript = new_script ( fScript, fname, flags, debug, __LINE__ );
    if ( !theScript )
        {
        fclose ( fScript );
        return ( -2 );
        }

    /* close the script */

    fclose ( fScript );

    /* throw away parsed script object */

    free_script ( theScript, debug );

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "check_syntax exiting normally; iAllocCount = %d", 
                 iAllocCount );
    return ( 0 );
    } /* end check_syntax */


/*******************************************************************************/

/* 
 * Parse a script file into a script structure.
 *
 * Parameters:
 *    fscr:    pointer to opened script file.
 *    fname:   pointer to name of opened script file.
 *    flags:   PARSE_ flags.
 *    debug:   current debug value.
 *    ln:      line number whence called.
 *
 * Returns:
 *    pointer to a newly-created script sctructure, or NULL.
 *
 * Notes:
 *    caller must call free_script.
 *
 */
strScript *new_script( FILE *fscr, const char *fname, int flags, int debug, int ln )
    {
    strParseFile  parse_stuff,          /* items used in parsing script */
                 *psPF         = NULL; 
    strScript    *the_script   = NULL;  /* pointer to strScript structure we are building */
    strStmt      *the_stmt     = NULL;  /* pointer to statement being built */

    
    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "new_script [%d] starting", ln );

    psPF = &parse_stuff;

    the_script = malloc ( sizeof(strScript) );
    if ( !the_script )
        {
        TILT("unable to malloc the_script");
        goto Exit;
        }
    the_script->start      = NULL;
    the_script->fname[0]   = 0;
    the_script->iMalloc    = 0;
	the_script->errCnt     = 0;
	the_script->warnCnt    = 0;
	the_script->errText    = NULL;
	the_script->warnText   = NULL;
    strcpy ( the_script->fname, fname );

    parse_stuff.psPL      = &(parse_stuff.ParseLine);
    parse_stuff.pfScript  = fscr;
    parse_stuff.iLineNum  = 0;
    parse_stuff.szLine[0] = 0;
    parse_stuff.fname[0]  = 0;
    parse_stuff.stamp[0]  = 0;
    parse_stuff.debug     = debug;
    parse_stuff.errCnt    = 0;
    parse_stuff.warnCnt   = 0;
	parse_stuff.errText   = NULL;
	parse_stuff.warnText  = NULL;
	parse_stuff.errSize   = 0;
	parse_stuff.warnSize  = 0;
	parse_stuff.errLeft   = 0;
	parse_stuff.warnLeft  = 0;
    parse_stuff.iHiMalloc = 0;
    parse_stuff.iHiSize   = 0;
    parse_stuff.iScanned  = 0;
    strcpy ( parse_stuff.fname, fname );

    init_strParseLine ( &(parse_stuff.ParseLine), '#', '\\', 0 );
    setup_new_line    ( &(parse_stuff.ParseLine), parse_stuff.szLine );

    if ( GET_TOKEN == tkEnd )
		{
		if ( debug > 2 )
			tracef ( __LINE__, FALSE, "empty script" );
		free_script ( the_script, debug );
		the_script = NULL;
		goto Exit;
		}

    the_script->start = get_stmt ( psPF, debug, __LINE__ );
    the_stmt = the_script->start;
    if ( !the_stmt )
        {
        free_script ( the_script, debug );
        the_script = NULL;
        goto Exit;
        }

    while ( the_stmt && GET_TOKEN != tkEnd )
        {   /* process tokens loop */
        the_stmt->next_stmt = get_stmt ( psPF, debug, __LINE__ );
        the_stmt = the_stmt->next_stmt;
        }   /* end of process tokens loop */

       
    /* check for syntax errors */

    if ( (flags & PARSE_ABORT_ON_ERROR) && parse_stuff.errCnt > 0 )
        {
        if ( debug >= 2 )
            tracef ( __LINE__, FALSE, 
                     "*** %i syntax errors found; script processing aborted",
                     parse_stuff.errCnt );
        free_script ( the_script, debug );
        the_script = NULL;
        goto Exit;
        }

    /* check for syntax warnings (e.g., unsupported extensions) */

    if ( (flags & PARSE_ABORT_ON_WARNING) && parse_stuff.warnCnt > 0 )
        {
        if ( debug >= 2 )
            tracef ( __LINE__, FALSE, 
                     "*** %i syntax warnings found; script processing aborted",
                     parse_stuff.warnCnt );
        free_script ( the_script, debug );
        the_script = NULL;
        goto Exit;
        }
    
	/* copy errors and warnings from parse_stuff to the_script (if PARSE_ABORT flags not set) */

	the_script->errCnt   = parse_stuff.errCnt;
    the_script->warnCnt  = parse_stuff.warnCnt;
	if ( parse_stuff.errText != NULL )
		the_script->errText  = strdup ( parse_stuff.errText  );
	if ( parse_stuff.warnText != NULL )
		the_script->warnText = strdup ( parse_stuff.warnText );

    /* check for disk read errors */

    if ( ferror(psPF->pfScript) )
        {
        TILT ("I/O error reading sieve script");
        free_script ( the_script, debug );
        the_script = NULL;
        goto Exit;
        }

    /* we made it through the script */

    if ( debug >= 3 )
        {
        tracef ( __LINE__, FALSE, 
                 "script \"%.255s\" parsed; %i syntax error%.*s; %i warning%.*s; %i syntactic item%.*s found; %d item%.*s allocated (%i byte%.*s)",
                 parse_stuff.fname, 
                 parse_stuff.errCnt,    (parse_stuff.errCnt    != 1), "s",
                 parse_stuff.warnCnt,   (parse_stuff.warnCnt   != 1), "s",
                 parse_stuff.iScanned,  (parse_stuff.iScanned  != 1), "s",
                 parse_stuff.iHiMalloc, (parse_stuff.iHiMalloc != 1), "s",
                 parse_stuff.iHiSize,   (parse_stuff.iHiSize   != 1), "s" );
        }

Exit:

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "new_script returning; the_script = %p", the_script );

    return ( the_script );

    } /* end new_script */


/******************************************************************************/

/*
 * Scan for next token from input file.
 *
 * Parameters:
 *    psPF: pointer to strParseFile structure
 *    ln:   line number whence called.
 *
 * Returns:
 *     type of token found (could be tkEend if end-of-file reached).
 *
 */
static token_type get_token_file ( strParseFile *psPF, int ln )
    {
    do
        { /* get_next_token loop */
        if ( get_next_token(psPF->psPL, gtNormal) == tkEnd )
            { /* end of current line */
            if ( fgets(psPF->szLine, sizeof(psPF->szLine)-1, psPF->pfScript) == NULL )
                {
                TRACE_IT1(1, "hit script EOF");
                return ( tkEnd );
                }

            psPF->iLineNum++;

            /* see if we didn't get the entire line */
            /* we really should not use fgets, but instead get chunks and
               parse for CRLFs ourselves */

            if ( *(psPF->szLine + (strlen(psPF->szLine)-1)) != '\n' )
                WARN ( "line too long; possible data loss" );

            if ( setup_new_line(psPF->psPL, psPF->szLine) != 0 )
                {
                TRACE_IT1(1, "setup_new_line error");
                return ( tkEnd );
                }
            } /* end of current line */
        } /* get_next_token loop */
    while ( psPF->psPL->TokenType == tkEnd );
    
    psPF->iScanned++;

    if ( psPF->debug > 1 )
        tracef ( __LINE__, FALSE, "::scan:: [%d] type = %.25s (%d), len = %d, val = %d, '%.*s'",
                 ln,
                 szTOKEN_TYPE_NAME(psPF->psPL->TokenType),
                 psPF->psPL->TokenType,
                 psPF->psPL->iTokenLen,
                 psPF->psPL->lTokenVal,
                 psPF->psPL->iTokenLen,
                 psPF->psPL->pTokenS );

    return ( psPF->psPL->TokenType );
    } /* end get_token_file */



/******************************************************************************/

/*
 * Jacket for backup_token.
 *
 * Parameters:
 *    psPF:   pointer to strParseFile structure.
 *    depth:  how far back to go.
 *    ln:     line number whence called.
 *
 */
static void my_backup_token ( strParseFile *psPF, short depth, int ln )
    {
	int rslt = 0;


    if ( psPF->debug > 1 )
        tracef ( __LINE__, FALSE, "::backup:: [%d] depth = %i", ln, depth );

    rslt = backup_token ( psPF->psPL, depth );
	assert ( rslt == 0 );
    } /* end my_backup_token */


/*--------------------------------------------------------------
 * Jacket for get_string.
 *
 * Parameters:
 *    psPF:	    	pointer to strParseFile structure.
 *    fEmptyOK:     flag: TRUE if an empty string is permitted.
 *    ln:           line number whence called
 *
 * Result:
 *    Pointer to newly-allocated char buffer, or NULL.
 *
 * Notes:
 *    Caller must FREE buffer.
 */
char *my_get_string ( strParseFile *psPF, int fEmptyOK, int ln )
    {
    char *p = NULL;

	/* make sure we have a string token, then call the scan utility to do the work */

	if ( psPF->psPL->TokenType == tkStr || GET_TOKEN == tkStr )
		p = get_string ( psPF->psPL, fEmptyOK );

    if ( p )
        {
        if ( psPF->debug > 3 )
            tracef ( ln, FALSE, "allocated (in get_string) %u bytes; ptr=%p \"%.250s\"", 
                     psPF->psPL->iTokenLen -1, p, p );
        psPF->iHiMalloc ++;
        psPF->iHiSize   += psPF->psPL->iTokenLen -1;

        if ( DO_ALLOC_COUNT )
            iAllocCount++;
        }

    if ( psPF->debug > 1 )
        tracef ( __LINE__, FALSE, "::scan (string):: (%d) type=%.25s(%d), len=%d, val=%d, '%.*s'",
                 ln,
                 szTOKEN_TYPE_NAME(psPF->psPL->TokenType),
                 psPF->psPL->TokenType,
                 psPF->psPL->iTokenLen,
                 psPF->psPL->lTokenVal,
                 psPF->psPL->iTokenLen,
                 psPF->psPL->pTokenS );

    return ( p );
    } /* end my_get_string */


/******************************************************************************/

/*
 * Gets next line from input file.
 *
 * Parameters:
 *    psPF: pointer to strParseFile structure
 *    ln:   line number whence called.
 *
 * Returns:
 *     pointer to start of next line, null terminated, CRLF-stripped.
 *     NULL if EOF reached.
 *
 */
static char *get_line_file ( strParseFile *psPF, int ln )
    {
    char *p, *q;

    
    p = psPF->szLine;

    if ( fgets(p, sizeof(psPF->szLine)-1, psPF->pfScript) == NULL )
        {
        TRACE_IT1(1, "get_line_file hit script EOF");
        return ( NULL );
        }

    psPF->iLineNum++;

    q = p + ( strlen ( p ) - 1 );
    while ( *q == '\n' || *q == '\r' )
        *q-- = 0;

    if ( !setup_new_line(psPF->psPL, p) )
        {
        TRACE_IT1(1, "setup_new_line error");
        }
                
    if ( psPF->debug > 1 )
        tracef ( __LINE__, FALSE, "::line:: (%d) '%.250s'",
                 ln, p );

    return ( p );
    } /* end get_line_file;




/*******************************************************************************/

/*
 * Parses a statement.
 *
 * Parameters:
 *    psPF:  pointer to strParseFile structure.
 *    debug: desired trace level.
 *    ln:    line number whence called.
 *
 * Returns:
 *    pointer to newly-allocated strStmt structure, or NULL.
 *
 * Notes:
 *    on entry, current token is "IF" or action.
 *
 */
static strStmt *get_stmt ( strParseFile *psPF, int debug, int ln )
    {
    strStmt *theStmt    = NULL,
            *value      = NULL;
    int      iBrackets  = 0;


    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_stmt starting [%d]", ln );

    theStmt = new_stmt ( psPF );
    if ( !theStmt )
        {
        TILT ( "unable to allocate STMT" );
        goto Exit;
        }
    value = theStmt;

    do
        { /* statement loop */
        if ( psPF->psPL->TokenType == tkSpec && psPF->psPL->lTokenVal == '{' )
            {
            iBrackets++;
            if ( psPF->debug >= 3 )
                tracef ( __LINE__, FALSE, "found open brace; incrementing iBrackets to %d",
                       iBrackets);
            GET_TOKEN;
            }

        if ( check_token("if", psPF->psPL) )
            { /* IF */
			theStmt->stmtType = stmtIf;
            theStmt->stmt.this_if = get_if ( psPF, debug, __LINE__ );
            if ( theStmt->stmt.this_if == NULL )
                {
                TILT ("error parsing IF");
                free_stmt ( value, debug );
                value = NULL;
                goto Exit;
                }
            } /* IF */
        else
            { /* not an IF, must be an action */
			theStmt->stmtType = stmtAction;
            theStmt->stmt.action = get_action( psPF, debug, __LINE__ );
            } /* not an IF, must be an action */

        if ( iBrackets > 0 )
            {
            if ( GET_TOKEN == tkSpec && psPF->psPL->lTokenVal == '}' )
                {
                iBrackets--;
                if ( psPF->debug >= 3 )
                    tracef ( __LINE__, FALSE, 
                             "found close brace; decrementing iBrackets to %d",
                             iBrackets );
                }
            }

        if ( iBrackets > 0 )
            {
            theStmt->next_stmt = new_stmt ( psPF );
            if ( !theStmt->next_stmt )
                {
                TILT ( "unable to allocate STMT" );
                free_stmt ( value, debug );
                value = NULL;
                goto Exit;
                }

            theStmt = theStmt->next_stmt;
            }
        } /* statement loop */
    while ( iBrackets > 0 );

    if ( iBrackets != 0 )
        {
        TILT ( "brace error" );
        free_stmt ( value, debug );
        value = NULL;
        goto Exit;
        }

Exit:
    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_stmt returning;  value=%p", value );
    return ( value );
    } /* end get_stmt */




/*******************************************************************************/

/*
 * Parses an IF statement.
 *
 * Parameters:
 *    psPF:  pointer to strParseFile structure.
 *    debug: desired trace level.
 *    ln:    line number whence called.
 *
 * Returns:
 *    pointer to newly-created strIf structure, or NULL.
 *
 * Notes:
 *    On entry, current token is "IF".
 *    On exit, current token is whatever follows the IF.
 *
 *    Caller must call free_if.
 *
 */
static strIf *get_if ( strParseFile *psPF, int debug, int ln )
    {
    strIf       *theIf = NULL;


    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_if starting [%d]", ln );

    /* allocate a new IF structure */

    theIf = new_if ( psPF );
    if ( !theIf )
        {
        TILT("unable to allocate IF");
        goto Exit;
        }
 
    if ( GET_TOKEN == tkEnd )
        {
        TILT ( "unexpected EOF parsing IF");
        free_if ( theIf, debug );
        theIf = NULL;
        goto Exit;
        }

    /* get the test */

    theIf->test = get_test ( psPF, debug, __LINE__ );
    if ( !theIf->test )
        {
        TILT("error parsing test");
        free_if ( theIf, debug );
        theIf = NULL;
        goto Exit;
        }
 
    /* get the actions */

    GET_TOKEN;

    theIf->this_stmt = get_stmt ( psPF, debug, __LINE__ );
    if ( theIf->this_stmt )
        GET_TOKEN;

    if ( check_token("else", psPF->psPL) )
        {
        GET_TOKEN;
        theIf->else_stmt = get_stmt ( psPF, debug, __LINE__ );
        }
    else
        {
        BACKUP_TOKEN ( 0 );
        }

Exit:
    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_if returning;  theIf=%p", theIf );
    return ( theIf );
    } /* end get_if */


/*******************************************************************************/

/*
 * Parses a test.
 *
 * Parameters:
 *    psPF:  pointer to strParseFile structure.
 *    debug: desired trace level.
 *    ln:    line number whence called.
 *
 * Returns:
 *    pointer to newly-created strTest structure, or NULL.
 *
 * Notes:
 *    On entry, current token is start of test.
 *    On exit, current token is whatever follows test.
 *
 *    Caller must call free_test.
 *
 */
static strTest *get_test ( strParseFile *psPF, int debug, int ln )
    {
    strTest     *theTest    = NULL,
                *curTest    = NULL,
				**pTest     = NULL;
    int          theSize    = -1;
    op_type      theOp;
    strText     *theText    = NULL, 
                *theText2   = NULL;
    int          iParens    =  0;
    char         envName[5] = "";
 

    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_test starting [%d]", ln );

    if ( check_token("allof", psPF->psPL) )
        theTest = new_test(tstAllOf, psPF);
    else if ( check_token("anyof", psPF->psPL) )
        theTest = new_test(tstAnyOf, psPF);
    else if ( check_token("exists", psPF->psPL) )
        theTest = new_test(tstExists, psPF);
    else if ( check_token("header", psPF->psPL) )
        theTest = new_test(tstHeader, psPF);
    else if ( check_token("true", psPF->psPL) )
        theTest = new_test(tstAlwaysTrue, psPF);
    else if ( check_token("false", psPF->psPL) )
        theTest = new_test(tstAlwaysFalse, psPF);
    else if ( check_token("not", psPF->psPL) )
        theTest = new_test(tstNot, psPF);
    else if ( check_token("size", psPF->psPL) )
        theTest = new_test(tstSize, psPF);
    else if ( check_token("support", psPF->psPL) )
        theTest = new_test(tstSupport, psPF);
    else if ( check_token("envelope", psPF->psPL) )
        theTest = new_test(tstEnvelope, psPF);
    else
        {
        TILT("test expected");
        goto Exit;
        }

    if ( !theTest )
        {
        TILT("unable to allocate test");
        goto Exit;
        }

    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_test creating test; type = %.100s (%d)", 
		         szTEST_TYPE_NAME(theTest->tstType), theTest->tstType );

    switch ( theTest->tstType )
        {

        /*------------------------------------------------------ size */
        case tstSize:

            if ( (  GET_TOKEN != tkID                 )  ||
                 ( (!check_token("over",  psPF->psPL) ) &&
                 (  !check_token("under", psPF->psPL) )     )
               )
                {
                TILT("\"OVER\" or \"UNDER\" expected");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }
        
            if ( check_token("over", psPF->psPL) )
                theOp = opOver;
            else
                theOp = opUnder;

            if ( GET_TOKEN != tkNum)
                {
                TILT("expected size");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            theSize = get_integer(psPF->psPL);
            if ( theSize == 0xFFFF )
                {
                TILT("expected size");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            theTest->compare = new_compare ( NULL, theOp, cprOctet, NULL, theSize, psPF );
            if ( psPF->debug >= 3 )
                tracef ( __LINE__, FALSE, "get_test set compare to: op=%d  size=%ld",
                         theTest->compare->op, theTest->compare->num);
            
            break;

        /*------------------------------------------------------ any-of */
        /*------------------------------------------------------ all-of */
        case tstAnyOf:
        case tstAllOf:

            if ( GET_TOKEN == tkSpec && psPF->psPL->lTokenVal == '(' )
                {
                iParens++;
                if ( psPF->debug >= 3 )
                    tracef ( __LINE__, FALSE, 
                             "found open paren; incrementing iParens to %i",
                             iParens );
                }
            else
                {
                TILT ( "open parenthesis expected" );
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

			/* we build a chain of tests, using curTest as the current one */

            curTest = theTest;

			/* the head of the chain is at this_test; the entries are linked using next_test */

			pTest   = &curTest->this_test;

            while ( iParens > 0 )
                { /* get tests loop */
                GET_TOKEN;
                *pTest = get_test ( psPF, debug, __LINE__ );
                if ( *pTest == NULL )
                    {
                    free_test ( theTest, debug );
                    theTest = NULL;
                    goto Exit;
                    }

                if ( GET_TOKEN != tkSpec          &&
                     psPF->psPL->lTokenVal != ',' &&
                     psPF->psPL->lTokenVal != ')'   )
                    {
                    TILT ( "\",\" or \")\" expected" );
                    free_test ( theTest, debug );
                    theTest = NULL;
                    goto Exit;
                    }

                if ( psPF->psPL->lTokenVal == ')' )
                    {
                    iParens--;
                    if ( psPF->debug >= 3 )
                        tracef ( __LINE__, FALSE, 
                                 "found close paren; decrementing iParens to %i",
                                 iParens );
                    }

                curTest = *pTest;
				pTest = &curTest->next_test;

                } /* get tests loop */

            break;

		/*------------------------------------------------------ not */
        case tstNot:
            
            GET_TOKEN;
            theTest->this_test = get_test ( psPF, debug, __LINE__ );
            if ( theTest->this_test == NULL )
                {
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }
    
            break;

        /*------------------------------------------------------ support */
        case tstSupport:

            theText = MALLOC ( sizeof(strText), "strText" );
            if ( !theText )
                {
                TILT("unable to allocate text");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            theText->text = GET_STRING ( FALSE );
                {
                TILT("unable to get string");
                FREE ( theText );
                theText = NULL;
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            theTest->compare = new_compare ( NULL, opIs, cprOctet, theText, -1, psPF );
            if ( theTest->compare == NULL )
                {
                TILT ("error creating compare");
                FREE ( theText->text );
                FREE ( theText );
                theText = NULL;
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            break;

        /*------------------------------------------------------ exists */
        case tstExists:

            theText = get_string_list ( psPF, debug, __LINE__ );
            if ( theText == NULL )
                {
                TILT("error parsing string or string list for EXISTS");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            theTest->compare = new_compare ( theText, opExists, cprOctet, NULL, -1, psPF );
            if ( theTest->compare == NULL )
                {
                TILT ("error creating compare");
                FREE ( theText->text );
                FREE ( theText );
                theText = NULL;
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            break;

        /*------------------------------------------------------ header */
        case tstHeader:

            theText = get_string_list ( psPF, debug, __LINE__ );
            if ( !theText )
                {
                TILT("error parsing header string or string list for HEADER");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            if ( GET_TOKEN != tkID )
                {
                TILT ( "match keyword expected" );
                free_text ( theText, debug );
                free_test ( theTest, debug );
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            if ( check_token("is", psPF->psPL) )
                theOp = opIs;
            else if ( check_token("matches", psPF->psPL) )
                theOp = opMatches;
            else if ( check_token("contains", psPF->psPL) )
                theOp = opContains;
            else
                {
                TILT ( "match keyword expected" );
                free_text ( theText, debug );
                free_test ( theTest, debug );
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            theText2 = get_string_list ( psPF, debug, __LINE__ );
            if ( !theText )
                {
                TILT("error parsing text string list for HEADER match");
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            theTest->compare = new_compare ( theText, theOp, cprOctet, theText2, -1, psPF );
            if ( !(theTest->compare) )
                {
                TILT ("error creating compare");
                free_text ( theText , debug );
                free_text ( theText2, debug );
                free_test ( theTest , debug );
                theText2 = NULL;
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            break;

            /*------------------------------------------------------ envelope */
        case tstEnvelope:

            if ( GET_TOKEN != tkSpec || psPF->psPL->lTokenVal != '.' )
                {
                TILT("\".\" expected");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            if ( GET_TOKEN != tkID )
                {
                TILT("Envelope element name expected");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            if ( (!check_token("rcpt", psPF->psPL) ) &&
                 (!check_token("from", psPF->psPL) )     )
                {
                WARN("Envelope element is not \"RCPT\" or \"FROM\"");
                }
            
            memcpy ( envName, psPF->psPL->pTokenS, psPF->psPL->iTokenLen );
            envName[psPF->psPL->iTokenLen] = 0;
            theText = new_text ( envName, psPF );
            if ( !theText )
                {
                TILT("unable to allocate strText for envelope element name");
                free_test ( theTest, debug );
                theTest = NULL;
                goto Exit;
                }

            if ( GET_TOKEN != tkID )
                {
                TILT ( "match keyword expected" );
                free_text ( theText, debug );
                free_test ( theTest, debug );
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            if ( check_token("is", psPF->psPL) )
                theOp = opIs;
            else if ( check_token("matches", psPF->psPL) )
                theOp = opMatches;
            else if ( check_token("contains", psPF->psPL) )
                theOp = opContains;
            else
                {
                TILT ( "match keyword expected" );
                free_text ( theText, debug );
                free_test ( theTest, debug );
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            theText2 = get_string_list ( psPF, debug, __LINE__ );
            if ( !theText )
                {
                TILT("error parsing text string list for ENVELOPE match");
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            theTest->compare = new_compare ( theText, theOp, cprOctet, theText2, -1, psPF );
            if ( !(theTest->compare) )
                {
                TILT ("error creating compare");
                free_text ( theText , debug );
                free_text ( theText2, debug );
                free_test ( theTest , debug );
                theText2 = NULL;
                theText = NULL;
                theTest = NULL;
                goto Exit;
                }

            break;

            } /* end switch */

Exit:
    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_test returning; theTest=%p", theTest );
    return ( theTest );
    } /* end get_test */





/*******************************************************************************/

/*
 * Parses a string list.
 *
 * Parameters:
 *    psPF:  pointer to strParseFile structure.
 *    debug: desired trace level.
 *    ln:    line number whence called.
 *
 * Returns:
 *    pointer to newly-created strText structure, or NULL.
 *
 * Notes:
 *    On entry, current token is prior to start of first string or "(".
 *    On exit, current token is last token of string or string list.
 *
 *    Caller must call free_text.
 *
 */
static strText *get_string_list ( strParseFile *psPF, int debug, int ln )
    {
    strText  *theText = NULL,
             *curText = NULL;
    int       pcount = 0;  /* how deep are we in parens? */


    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_string_list starting [%d]", ln );

    if ( GET_TOKEN != tkStr && 
         psPF->psPL->TokenType != tkSpec )
        {
        TILT ("string or string list expected" );
        goto Exit;
        }

    theText = new_text ( NULL, psPF );
    if ( !theText )
        goto Exit;

    if ( psPF->psPL->TokenType == tkStr )
        {
        theText->text = GET_STRING ( FALSE );
        if ( !theText->text )
            {
            free_text ( theText, debug );
            theText = NULL;
            goto Exit;
            }
        }
    else if ( psPF->psPL->lTokenVal == '(' )
        {
        pcount++;
        if ( psPF->debug >= 3 )
            tracef ( __LINE__, FALSE, "found open paren; incrementing pcount to %d",
                     pcount);

        theText->text = GET_STRING ( TRUE );
        if ( !theText->text )
            {
            TILT ("string expected");
            free_text ( theText, debug );
            theText = NULL;
            goto Exit;
            }
        }
    
    curText = theText;

    while ( pcount > 0 )
        { /* unfinished business */
        GET_TOKEN;

        if ( (   psPF->psPL->TokenType != tkSpec ) ||
             ( ( psPF->psPL->lTokenVal != ',' ) &&
               ( psPF->psPL->lTokenVal != ')' )  )  )
            {
            TILT ("\",\" or \")\" expected");
            FREE (theText->text);
            FREE (theText);
            theText = NULL;
            goto Exit;
            }

        if ( psPF->psPL->lTokenVal == ',' )
            { /* comma */
            curText->next = new_text ( NULL, psPF );
            if ( !curText->next )
                {
                free_text ( theText, debug );
                theText = NULL;
                goto Exit;
                }
            curText->next->text = GET_STRING ( TRUE );
            if ( !curText->next->text )
                {
                TILT ( "error parsing string list" );
                free_text ( theText, debug );
                theText = NULL;
                goto Exit;
                }
            curText = curText->next;
            } /* comma */
        
        else if ( psPF->psPL->lTokenVal == ')' )
            {
            pcount--;
            if ( psPF->debug >= 3 )
                tracef ( __LINE__, FALSE, 
                         "found close paren; decrementing pcount to %d",
                         pcount );
            }
        } /* unfinished business */

Exit:

    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_string_list returning; theText=%p", theText );
 
    return ( theText );
    } /* end get_string_list */




/*******************************************************************************/

/*
 * Parses an action.
 *
 * Parameters:
 *    psPF:  pointer to strParseFile structure.
 *    debug: desired trace level.
 *    ln:    line number whence called.
 *
 * Returns:
 *    pointer to newly-created strAction structure, or NULL.
 *
 * Notes:
 *    On entry, current token is start of action.
 *    On exit, current token is last token of action.
 *
 *    Caller must call free_action.
 *
 */
static strAction *get_action ( strParseFile *psPF, int debug, int ln )
    {
    strAction *theAction = NULL;


    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_action starting [%d]", ln );

    if ( check_token("reject", psPF->psPL) )
        {
        theAction = new_action(acnReject, psPF);
        if ( get_strOrMsg(theAction, psPF, __LINE__) )
            {
            TILT ( "reject string/message expected" );
            free_action ( theAction, debug );
            theAction = NULL;
            goto Exit;
            }
        }
    else if ( check_token("fileinto", psPF->psPL) )
        {
        theAction = new_action(acnFileInto, psPF);
        theAction->text = GET_STRING ( TRUE );
        if ( !theAction->text )
            {
            TILT ( "folder expected");
            free_action ( theAction, debug );
            goto Exit;
            }
        }
    else if ( check_token("forward", psPF->psPL) )
        {
        theAction = new_action(acnForward, psPF);
        theAction->text = GET_STRING ( FALSE );
        if ( !theAction->text )
            {
            TILT ( "address expected" );
            free_action ( theAction, debug );
            theAction = NULL;
            goto Exit;
            }
        /*TBD: check for syntactally valid address */
        }
    else if ( check_token("keep", psPF->psPL) )
        {
        theAction = new_action(acnKeep, psPF);
        }
    else if ( check_token("reply", psPF->psPL) )
        {
        theAction = new_action(acnReply, psPF);
        if ( get_strOrMsg(theAction, psPF, __LINE__) )
            {
            TILT ( "reply string/message expected" );
            free_action ( theAction, debug );
            theAction = NULL;
            goto Exit;
            }
        }
    else if ( check_token("mark", psPF->psPL) )
        {
        theAction = new_action(acnMark, psPF);
        theAction->text = GET_STRING ( FALSE );
        if ( !theAction->text )
            {
            TILT ( "header for MARK expected" );
            free_action ( theAction, debug );
            theAction = NULL;
            goto Exit;
            }
        }
    else if ( check_token("stop", psPF->psPL) )
        {
        theAction = new_action(acnStop, psPF);
        }
    else if ( check_token("discard", psPF->psPL) )
        {
        theAction = new_action(acnDiscard, psPF);
        }
	else if ( psPF->psPL->TokenType == tkSpec && ( psPF->psPL->lTokenVal == ';' ||
												   psPF->psPL->lTokenVal == '}' )
	        )
		{
		theAction = new_action(acnVoid, psPF);
		BACKUP_TOKEN ( 0 );
		}
    else
        {
        TILT ("action expected");
		free_action ( theAction, debug );
		theAction = NULL;
        goto Exit;
        }

    if ( GET_TOKEN == tkSpec && psPF->psPL->lTokenVal == ';' )
        {
        TRACE_IT1 ( 3, "found terminating semicolon" );
        }
    else if ( theAction->actnType == acnVoid && psPF->psPL->TokenType == tkSpec &&
		                                        psPF->psPL->lTokenVal == '}'       )
		{
		/* an empty statement (inside braces) does not need a terminating semicolon */

	    BACKUP_TOKEN ( 0 );
		}
	else
        {
        TILT ( "semicolon expected" );
        /*TEMP!
        free_action ( theAction );
        theAction = NULL;
        goto Exit;
        */
        }

    if ( psPF->debug >= 3 )
            tracef ( __LINE__, FALSE, "get_action exiting normally; type=%d (%.100s)",
                     theAction->actnType, szACTION_TYPE_NAME(theAction->actnType) );

Exit:

    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_action returning; theAction=%p", theAction );
    return ( theAction );
    } /* end get_action */


/*******************************************************************************/

/*
 * Gets a string or message from the token stream.
 *
 * Parameters:
 *    theAction:  pointer to an strAction structure.
 *    psPF:       pointer to strParseFile structure.
 *    ln:         line number whence called.
 *
 * Returns:
 *    0 if all went well, error code otherwise.
 *    If non-error, message field of theAction is filled-in with strLines object.
 *
 * Notes:
 *    On entry, current token is prior to expected string or message.
 *    On exit, current token is last token of string or message.
 *
 *
 */
static int get_strOrMsg ( strAction *theAction, strParseFile *psPF, int ln )
    {
    int   value  = -100;  /* our result */
    char *ptr    = NULL;  /* lines element of strLines structure */


    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_strOrMsg starting [%d]", ln );

    if ( GET_TOKEN       != tkStr && 
         psPF->psPL->TokenType != tkID )
        {
        TILT ( "string or message expected" );
        value = -1;
        goto Exit;
        }

    if ( psPF->psPL->TokenType == tkStr )
        {
        ptr = GET_STRING ( TRUE );
        if ( !ptr )
            {
            TILT ( "error getting string" );
            value = -2;
            goto Exit;
            }
        theAction->message = new_lines ( ptr, psPF );
        if ( !theAction->message )
            {
            TILT ( "unable to allocate strLines structure" );
            value = -3;
            goto Exit;
            }
        }
    else if ( check_token("text", psPF->psPL) )
        { /* multi-line text */

        char *p      = NULL;  /* current line read from input file */
        char *temp   = NULL;  /* temp use when reallocating ptr */
        long  length = 0;     /* characters in ptr, not counting null */
        long  size   = 0;     /* bytes allocated for ptr */
        int   j      = 0;     /* length of current line */


        if ( GET_TOKEN != tkSpec || psPF->psPL->lTokenVal != ':' )
            {
            TILT ( "colon expected after 'TEXT'" );
            value = -4;
            goto Exit;
            }

        do
            {
 
            /* get next line from input file */

            p = get_line_file ( psPF, __LINE__ );
            if ( !p )
                {
                TILT ( "EOF encountered while looking for 'text' termination" );
                value = -5;
                free ( ptr );
                goto Exit;
                }

            j = strlen ( p );

            if ( psPF->debug >= 3 )
                tracef ( __LINE__, FALSE, "get_strOrMsg read line; p=%p; line (%d)=%.250s",
                         p, j, p );

            /* make sure we haven't hit EOF or the termination octet */

            if ( p != NULL && !(j == 1 && *p == '.') )
                {

                /* unstuff any leading dot */

                if ( j > 1 && *p == '.' && *(p+1) == '.' )
                    {
                    p++;
                    j--;
                    }

				/* translate any embedded escape sequences */

				translate_esc ( p, j, psPF->psPL->cEscape );
				j = strlen ( p );

                /* see if we need to increase the buffer */

                if ( (size-length) <= (j+2) )
                    {
                    size += LINES_CHUNK;
                    temp = realloc ( ptr, size );
                    if ( !temp )
                        {
                        TILT ( "unable to allocate strLines chunk" );
                        value = -6;
                        free ( ptr );
                        goto Exit;
                        }
                    ptr = temp;

                    if ( psPF->debug >= 3 )
                        tracef ( __LINE__, FALSE, "increased ptr to %d bytes", size );
                    }

                /* add CRLF if this isn't the first line; NULL if it is */

                if ( length > 0 )
                    {
                    strcat ( ptr + length, "\r\n" );
                    length += 2;
                    }
                else
                    *ptr = 0;

                /* append the new line to the buffer */

                strcat ( ptr + length, p );
                length += j;

                if ( psPF->debug >=3 )
                    tracef ( __LINE__, FALSE, "length now %d", length );
                }
            }
        while ( p != NULL && !(j == 1 && *p == '.') );

        if ( p != NULL && *p == '.' )
            GET_TOKEN; /* consume the termination octet */

        if ( psPF->debug >= 3 )
            tracef ( __LINE__, FALSE, "ptr=%p; size=%ld; length=%ld", ptr, size, length );

        /* see if we can cut back the size of the object */

        if ( size > (length + 1) )
            {
            size = (length + 1);
            temp = realloc ( ptr, size );
            if ( !temp )
                {
                TILT ( "unable to reduce strLines size" );
                value = -7;
                free ( ptr );
                goto Exit;
                }
            ptr = temp;
            }

        /* update allocation counters */

        psPF->iHiMalloc ++;
        psPF->iHiSize   += size;

        if ( DO_ALLOC_COUNT )
            iAllocCount++;

        if ( psPF->debug > 3 )
            tracef ( __LINE__, FALSE, "allocated %u bytes for new strLines (%p)", 
                     size, ptr );

        /* we're about done */

        if ( psPF->debug >= 4 )
            tracef ( __LINE__, FALSE, "lines:\n%.250s\n", ptr );

        theAction->message = new_lines ( NULL, psPF );
        theAction->message->lines  = ptr;
        theAction->message->length = length;
        } /* multi-line text */
    else
        {
        TILT ( "quoted string or \"TEXT\" expected" );
        value = -8;
        goto Exit;
        }

    value = 0;

Exit:

    if ( psPF->debug >= 3 )
        tracef ( __LINE__, FALSE, "get_strOrMsg returning;  value = %d", value );

    return ( value );
    } /* end get_strOrMsg */