/*******************************************************
 * Sieve (mail filter) execution.
 *
 * Filename:      sieve-execute.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>

/*-- DAA header file --*/
#include "daa_headers.h"

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



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



/*---------------------------------------------------------------
 *    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__)




/*---------------------------------------------------------------
 *    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 int      interpret_script   (strScript  *, Context *, int *, int     );
static int      do_stmt            (strStmt    *, Context *, int *, int, int);
static int      do_if              (strIf      *, Context *, int *, int, int);
static int      do_action          (strAction  *, Context *, int *, int, int);
static int      do_test            (strTest    *, Context *,        int, int);
static int      do_compare         (strCompare *, Context *,        int, int);
static int      do_envelope_compare(strCompare *, Context *,        int, int);
static int      compare_string     (char *, strText *, op_type, Context *, int, int);
static void     do_annotate_msg    (char *,                     Context *, int, int); 



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






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

/* 
 * Interprets a script against an incoming message, opening and parsing script
 *    file if needed.
 *
 * Parameters:
 *    fname:   pointer to full path and file name of script file.
 *    work:    pointer to an Context structure for the incoming message.
 *    action:  pointer to int which will be set to determine fate of message.
 *    flags:   PARSE_ flags.
 *    debug:   current debug value.
 *
 * Returns:
 *    0 if all went well, error code otherwise.
 *    Action is set to DAA_DISCARD, or left unchanged.
 *
 * Notes:
 *    caller must call free_script.
 *
 */
int do_script ( char *fname, Context *work, int *action, int flags, int debug)
    {
    FILE      *fScript;
    strScript *theScript;


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

    /*TBD: see if script object exists in cache (beware of thread vs memory issues) */

    if ( 1 ) /* cache not yet implemented */
        {
        /* 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 );
            }

        fclose ( fScript );
        }

    /* interpret script against incoming message */

    if ( interpret_script(theScript, work, action, debug) )
        {
        free_script ( theScript, debug );
        return ( -3 );
        }


    /*TBD: keep script in cache */

    free_script ( theScript, debug );

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



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

/*
 * Interpret (already parsed) script against incoming message.
 *
 * Parameters:
 *    theScript:  pointer to strScript structure.
 *    work:       pointer to Context structure for incoming message.
 *    action:     pointer to int for message fate.
 *    debug:      current debug value.
 *
 * Returns:
 *    0 if all went well.  Error code otherwise.
 *    Action is set to DAA_DISCARD, or left unchanged.
 *
 */
static int interpret_script ( strScript *theScript, Context *work, int *action, int debug )
    {
    int rslt = 0;


    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "interpret_script starting; action=%i", *action );

    if ( !theScript )
        return ( -1 );

    rslt = do_stmt ( theScript->start, work, action, debug, __LINE__ );

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "interpret_script returning %i; action=%i", rslt, *action );

    if ( rslt > 0 )
        rslt = 0;

    return ( rslt );
    } /* interpret_script */


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

/*
 * Execute a statement.
 *
 * Parameters:
 *    theStmt:    pointer to strStmt structure.
 *    work:       pointer to Context structure for incoming message.
 *    fate:       pointer to int for fate of message.
 *    debug:      current debug value.
 *    ln:         line number whence called.
 *
 * Returns:
 *    TRUE if the script should not be further processed; FALSE if
 *    we should continue; negative error code otherwise.
 *
 *    Fate is left unchanged, or set to DAA_DISCARD.
 *
 */
static int do_stmt ( strStmt *theStmt, Context *work, int *fate, int debug, int ln )
    {
    int rslt = FALSE;


    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_stmt starting [%d]  fate = %i", ln, *fate );

    while ( theStmt != NULL && rslt == FALSE )
        {
        if ( debug > 3 )
            tracef ( __LINE__, FALSE, "top of STMT loop; stmtType = %.25s (%d)", 
			                           szSTMT_TYPE_NAME(theStmt->stmtType), theStmt->stmtType );

        if ( theStmt->stmtType == stmtIf )
            rslt = do_if     ( theStmt->stmt.this_if, work, fate, debug, __LINE__ );
        else if ( theStmt->stmtType == stmtAction )
            rslt = do_action ( theStmt->stmt.action,  work, fate, debug, __LINE__ );
		else
			{
			if ( debug )
				tracef ( __LINE__, FALSE, "*** statement is neither IF nor action: stmtType=%d  If=%d  Action=%d",
				                          theStmt->stmtType, stmtIf, stmtAction );
			return ( __LINE__ * -1 );
			}
 
        theStmt = theStmt->next_stmt;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_stmt returning %i; fate = %i", rslt, *fate );

    return ( rslt );
    } /* end do_stmt */



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

/* 
 * Executes an IF statement.
 *
 * Parameters:
 *    theIf:   pointer to IF structure.
 *    work:    pointer to an Context structure for the incoming message.
 *    fate:    pointer to int for message fate.
 *    debug:   current debug value.
 *    ln:      line number whence called.
 *
 * Returns:
 *    FALSE if all went well; TRUE if we should stop processing the script; 
 *    negative error code otherwise.
 *
 *    Fate is left unchanged, or set to DAA_DISCARD.
 *
 */
static int do_if ( strIf *theIf, Context *work, int *fate, int debug, int ln )
    {
    int rslt = FALSE;


    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_if starting [%d]  fate = %i", ln, *fate );

    if ( do_test(theIf->test, work, debug, __LINE__) )
        rslt = do_stmt ( theIf->this_stmt, work, fate, debug, __LINE__ );
    else
        rslt = do_stmt ( theIf->else_stmt, work, fate, debug, __LINE__ );

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_if returning %i; fate = %i", rslt, *fate );

    return ( rslt );
    } /* end do_if */


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

/* 
 * Performs an action.
 *
 * Parameters:
 *    theAction:   pointer to ACTION structure.
 *    work:        pointer to an Context structure for the incoming message.
 *    fate:        pointer to int for message fate.
 *    debug:       current debug value.
 *    ln:          line number whence called.
 *
 * Returns:
 *    TRUE if we should stop processing; FALSE otherwise.
 *
 *    Fate is set to unchanged, or set to DAA_DISCARD.
 *
 */
static int do_action ( strAction *theAction, Context *work, int *fate, int debug, int ln )
    {
    int rslt = FALSE;


    if ( !theAction )
        {
        tracef ( __LINE__, FALSE, "** null action passed to do_action [%d] **", ln );
        rslt = __LINE__ * -1;
        goto Exit;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_action starting [%d]; action = %.25s (%d)  fate = %i", 
                 ln, szACTION_TYPE_NAME(theAction->actnType), theAction->actnType, *fate );

    switch ( theAction->actnType )
        {
        case acnReject:         /* discard and issue DSN with text as human-readable text */
                                /* message is reply text */
            /*TBD: implement reject */

            *fate = DAA_DISCARD;
            break;
        
        case acnDiscard:        /* throw away */
            *fate = DAA_DISCARD;
            break;
        
        case acnFileInto:       /* keep, but into folder other than default */
                                /* text is folder name */
            /*TBD: verify folder exists */
            if ( theAction->text )
                {
                if ( debug >= 3 )
                    tracef ( __LINE__, FALSE, "file-into %.250s (instead of %.250s)", 
                             theAction->text, get_msg_folder(work) );
                
				set_msg_folder ( work, theAction->text, __LINE__, debug );
                }
            else
                {
                tracef ( __LINE__, FALSE, "** null folder name for file-into action **" );
                rslt = __LINE__ * -1;
                }

            break;
        
        case acnForward:        /* re-inject into SMTP stream, text is recipient */
            if ( debug >= 3 )
                tracef ( __LINE__, FALSE, "forwarding msg to %.250s", theAction->text );

            forward_msg ( work, theAction->text, __LINE__, debug );
            /*TBD: report error */

            /* do we keep or discard original?  I say keep, as it is safer. */

            break;
        
        case acnKeep:           /* deliver as normal */
            *fate = DAA_DELIVER;
            break;
        
        case acnReply:          /* generate new message, message is reply text */
            if ( debug >= 3 )
                tracef ( __LINE__, FALSE, "sending reply" );
            
            reply_msg ( work, theAction->message->lines, 0, __LINE__, debug );
            /*TBD: report error */

            break;

        case acnMark:           /* add header to message */
            do_annotate_msg ( theAction->text, work, __LINE__, debug );
            break;
            
        case acnStop:           /* terminate further processing */
            rslt = TRUE;
            break;
        } /* switch */

Exit:

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_action returning %i; fate = %i", rslt, *fate );

    return ( rslt );
    } /* end do_action */



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

/* 
 * Evaluates a test.
 *
 * Parameters:
 *    theTest:   pointer to TEST structure.
 *    work:      pointer to an Context structure for the incoming message.
 *    debug:     current debug value.
 *
 * Returns:
 *    Result of test evaluation: TRUE or FALSE.
 *
 */
static int do_test ( strTest *theTest, Context *work, int debug, int ln )
    {
    int		 rslt    = FALSE;
	strTest *curTest = NULL;


    if ( !theTest )
        {
        tracef ( __LINE__, FALSE, "*** null test passed to do_test [%i] ***", ln );
        rslt = FALSE;
        goto Exit;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_test starting [%i]; tstType =  %.25s (%d)", 
                 ln, szTEST_TYPE_NAME(theTest->tstType), theTest->tstType );

    switch ( theTest->tstType )
        {
        case tstAlwaysFalse:    /* FALSE */
            rslt = FALSE;
            break;
        
        case tstAlwaysTrue:     /* TRUE */
            rslt = TRUE;
            break;

        case tstNot:            /* NOT */
            rslt = !do_test(theTest->this_test, work, debug, __LINE__);
            break;

		/* any-of and all-of have chains of tests; the head is at theTest->this_test, and the
		   tests are linked using next_test */

        case tstAnyOf:          /* ANY OF */
			curTest = theTest->this_test;
			while ( rslt == FALSE && curTest != NULL )
				{
				rslt = do_test ( curTest, work, debug, __LINE__ );
				curTest = curTest->next_test;
				}
            break;

        case tstAllOf:          /* ALL OF */
			curTest = theTest ->this_test;
			rslt = TRUE;
			while ( rslt == TRUE && curTest != NULL )
				{
				rslt = do_test ( theTest->this_test, work, debug, __LINE__ );
				curTest = curTest->next_test;
				}
            break;

        case tstEnvelope:       /* ENVELOPE */
            rslt = do_envelope_compare ( theTest->compare, work, debug, __LINE__ );
            break;

        case tstSize:           /* SIZE */
        case tstSupport:        /* SUPPORT */
        case tstExists:         /* EXISTS */
        case tstHeader:         /* HEADER */
            rslt = do_compare ( theTest->compare, work, debug, __LINE__ );
            break;
        } /* switch */

Exit:
    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_test returning %d", rslt );

    return ( rslt );
    } /* do_test */




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

/* 
 * Evaluates a comparison.
 *
 * Parameters:
 *    theCompare:   pointer to COMPARE structure.
 *    work:         pointer to an Context structure for the incoming message.
 *    debug:        current debug value.
 *    ln:           line number whence called.
 *
 * Returns:
 *    Result of evaluation: TRUE or FALSE.
 *
 */
static int do_compare ( strCompare *theCompare, Context *work, int debug, int ln )
    {
    int        rslt  = FALSE;
    char      *ptr   = NULL;
    strText   *pHdr  = NULL,
              *pTxt  = NULL;
    ll_Line   *pLine = NULL;


    if ( !theCompare )
        {
        tracef ( __LINE__, FALSE, "*** null compare passed to do_compare [%d] ***", ln );
        rslt = FALSE;
        goto Exit;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_compare starting [%d]; op = %.25s (%d)", 
                 ln, szOP_TYPE_NAME(theCompare->op), theCompare->op );

    switch ( theCompare->op )
        {
        case opIs:              /* header(s) exactly match string(s) */
        case opContains:        /* header(s) contain string(s) (any match) */
            rslt  = FALSE;
            pHdr  = theCompare->hdr;

            while ( rslt == FALSE && pHdr != NULL )
                { /* header loop */
                if ( debug > 3 )
                    tracef ( __LINE__, FALSE, 
                             "looking for occurance of header '%.250s'", pHdr->text );

                pLine = work->message.headers.first;
                do
                    { /* header occurance loop */
                    ptr = DAA_get_next_rfc822_header(work, &pLine, pHdr->text);
                    if ( debug >= 3 )
                            tracef ( __LINE__, FALSE, 
                                    "search for next header '%.250s' (DAA returned %p): (%d) '%.250s'", 
                                    pHdr->text, 
									ptr,
									ptr != NULL ? strlen(ptr) : 0,
									ptr != NULL ? ptr         : "*NULL*" );
                    if ( ptr )
                        { /* found occurance of header */
                        rslt = compare_string ( ptr, 
                                                theCompare->text,
                                                theCompare->op,
                                                work,
                                                debug,
                                                __LINE__ );
                        } /* found occurance of header */
                    } /* header occurance loop */
                while ( rslt == FALSE && ptr != NULL );

                pHdr = pHdr->next;
                } /* header loop */
            break;

        case opMatches:         /* header(s) match string(s) using wildcards (any match) */
            /*TBD: implement wildcard match */
            break;

        case opExists:          /* header(s) exist (all must exist) */
            rslt = TRUE;
            pHdr = theCompare->hdr;
            do
                {
                ptr  = DAA_get_rfc822_header(work, &work->message.headers, pHdr->text);
                rslt = ( ptr != NULL );
                if ( debug >= 3 )
                    tracef ( __LINE__, FALSE, 
                             "header '%.250s' exists = %d  (DAA returned %p)", 
                             pHdr->text, rslt, ptr );
                pHdr = pHdr->next;
                }
            while ( pHdr != NULL && rslt == TRUE );
            break;

        case opOver:            /* message size > value */
            rslt = ( work->msg_size > theCompare->num );
            if ( debug >= 3 )
                tracef ( __LINE__, FALSE, "size (%d) over %d = %d",
                                   work->msg_size, theCompare->num, rslt );
            break;

        case opUnder:           /* message size < value */
            rslt = ( work->msg_size < theCompare->num );
            if ( debug >= 3 )
                tracef ( __LINE__, FALSE, "size (%d) under %d = %d",
                                   work->msg_size, theCompare->num, rslt );
            break;
        } /* switch */


Exit:
    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_compare returning %d", rslt );

    return ( rslt );
    } /* do_compare */



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

/* 
 * Evaluates an envelope comparison.
 *
 * Parameters:
 *    theCompare:   pointer to COMPARE structure.
 *    work:         pointer to an Context structure for the incoming message.
 *    debug:        current debug value.
 *    ln:           line number whence called.
 *
 * Returns:
 *    Result of evaluation: TRUE or FALSE.
 *
 */
static int do_envelope_compare ( strCompare *theCompare, Context *work, int debug, int ln )
    {
    int               rslt  = FALSE;
    struct ll_recipT *pTarg = NULL;
    char             *ptr   = NULL;
    strText          *pTxt  = NULL;
    char              env [ LOCALSZ + DOMAINSZ + 3 ] = "";    


    if ( !theCompare )
        {
        tracef ( __LINE__, FALSE, "*** null compare passed to do_envelope_compare [%d] ***", ln );
        rslt = FALSE;
        goto Exit;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_envelope_compare starting [%d]; op = %.25s (%d)", 
                 ln, szOP_TYPE_NAME(theCompare->op), theCompare->op );

    switch ( theCompare->op )
        {
        case opIs:              /* envelope element exactly matches string(s) */            
        case opContains:        /* envelope element contains string(s) (any match) */
        case opMatches:         /* envelope element matches string(s) using wildcards (any match) */

            if ( str_comp( theCompare->hdr->text, "FROM" ) == 0 )
                {
                sprintf ( env, "%.*s@%.*s", 
                          LOCALSZ,  work->msg_orig.local, 
                          DOMAINSZ, work->msg_orig.domain );
                rslt = compare_string ( env, 
                                        theCompare->text, 
                                        theCompare->op, 
                                        work, 
                                        debug, 
                                        __LINE__ );
                }

            else if ( str_comp ( theCompare->hdr->text, "RCPT" ) == 0 )
                {
                pTarg = &work->msg_recip;
                while ( rslt == FALSE && pTarg != NULL )
                    { /* envelope element loop */
                    sprintf ( env, "%.*s@%.*s", 
						      LOCALSZ,  pTarg->name.local, 
							  DOMAINSZ, pTarg->name.domain );
                    rslt = compare_string ( env, 
                                            theCompare->text, 
                                            theCompare->op,
                                            work, 
                                            debug, 
                                            __LINE__ );
                    pTarg = pTarg->next;
                    } /* envelope element loop */
                }

            else
                {
                /*TBD: error */
                }
            
            break;

        case opExists:          /* not valid for envelope elements */
                                /* this might be useful for esoteric stuff */
            /*TBD: error */
            break;

        case opOver:            /* not valid for envelope elements */
                                /* this might be useful for number of recips */
            /*TBD: error */
            break;

        case opUnder:           /* not valid for envelope elements */
            /*TBD: error */
            break;
        } /* switch */


Exit:
    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_envelope_compare returning %d", rslt );

    return ( rslt );
    } /* do_envelope_compare */


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

/* 
 * Performs a string comparison.
 *
 * Parameters:
 *    str:          pointer to null-terminated character string (e.g., header text)
 *    pTxt:         pointer to TEXT element (of COMPARE structure).
 *    op:           which type of comparison to do.
 *    work:         pointer to an Context structure for the incoming message.
 *    debug:        current debug value.
 *    ln:           line number whence called.
 *
 * Returns:
 *    Result of evaluation: TRUE or FALSE.
 *
 */
static int compare_string ( char     *str, 
                            strText  *pTxt, 
                            op_type   op, 
                            Context  *work, 
                            int       debug, 
                            int       ln )
    {
    int          rslt  = FALSE;


    if ( pTxt == NULL || str == NULL )
        {
        tracef ( __LINE__, FALSE, "*** null parameter passed to compare_string [%d] ***", ln );
        rslt = FALSE;
        goto Exit;
        }

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

    while ( rslt == FALSE && pTxt != NULL )
        { /* string loop */
		if ( debug >= 4 )
			tracef ( __LINE__, FALSE, "...loop top; op: (%d) %.25s; pTxt->text: (%d) '%.250s'; str: (%d) '%.250s'",
			                           op, szOP_TYPE_NAME(op),
									   pTxt->text != NULL ? strlen(pTxt->text) : 0,
									   pTxt->text != NULL ? pTxt->text         : "*NULL*",
									   str        != NULL ? strlen(str)        : 0,
									   str        != NULL ? str                : "*NULL*"   );
        switch ( op )
            {
            case opContains:
                rslt = ( find_substring(str, pTxt->text) != NULL );
                break;

            case opIs:
                rslt = ( str_comp(str, pTxt->text) == 0 );
                break;

            case opMatches:
                /*TBD: implement wildcard matches */
                break;
            }
        
        if ( debug >= 4 )
            tracef ( __LINE__, FALSE, "(%d)'%.100s' is/contains/matches (%d)'%.100s' = %d",
                    strlen(str), str, strlen(pTxt->text), pTxt->text, rslt );

        pTxt = pTxt->next;
        } /* string loop */
           
Exit:
    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "compare_string returning %d", rslt );

    return ( rslt );
    } /* compare_string */


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

/* 
 * Annotates a message, expanding symbols ^r to envelope recipient and
 * ^f to envelope originator (from).
 *
 * Parameters:
 *    str:          pointer to null-terminated character string to be appended.
 *    work:         pointer to an Context structure for the incoming message.
 *    debug:        current debug value.
 *    ln:           line number whence called.
 *
 *
 */
static void do_annotate_msg ( char *str, Context *work, int debug, int ln )
	{
	char *buf,
		 *p,
		 *q;
	int   len;


	if ( str == NULL )
        {
        tracef ( __LINE__, FALSE, "*** null parameter passed to do_annotate_msg [%d] ***", ln );
        return;
        }

    if ( debug >= 3 )
        tracef ( __LINE__, FALSE, "do_annotate_msg starting; str=%.250s [%d]", str, ln );

	len = strlen(str) + (LOCALSZ * 3) + (DOMAINSZ * 3) + 9;
	buf = malloc ( len );
	if ( buf == NULL )
		return;
	if ( debug > 3 )
		tracef ( __LINE__, FALSE, "allocated %i bytes for buf", len );

	p = str;
	q = buf;

	while ( *p )
		{
		if ( *p == '^' )
			{
			if ( len - (q - buf) < LOCALSZ + DOMAINSZ + 3 )
				{
				len += (LOCALSZ * 3) + (DOMAINSZ * 3) + 9;
				if ( realloc ( buf, len ) == NULL )
					{
					if ( debug )
						tracef ( __LINE__, FALSE, "unable to increase buf to %i", len );
					free ( buf );
					buf = q = NULL;
					return;
					}
				if ( debug > 3 )
					tracef ( __LINE__, FALSE, "increased buf to %i bytes", len );
				}
			p++;
			
			if ( *p == 'f' || *p == 'F' )
				{
				p++;
				q += sprintf ( q, "%.*s@%.*s", 
					           LOCALSZ,  work->msg_orig.local, 
							   DOMAINSZ, work->msg_orig.domain );
				}
			else if ( *p == 'r' || *p == 'R' )
				{
				p++;
				q += sprintf ( q, "%.*s@%.*s", 
					           LOCALSZ,  work->msg_recip.name.local, 
							   DOMAINSZ, work->msg_recip.name.domain );
				}
			else if ( *p )
				{
				*q++ = '^';
				*q++ = *p++;
				}
			}
		else
			*q++ = *p++;
		}
	*q = 0;

	annotate_msg ( work, buf, ln, debug );
	}
