/*
 *	Copyright 1990 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#include "hostenv.h"
#include "mailer.h"
#include "prototypes.h"

#undef STATIC
#define STATIC	static
#undef ATHACK	/* 'user at host' == 'user@host' */

#include "rfc822.sst.h"
STATIC		/* This will apply to the array in the .sst file */
#include "rfc822.sst.c"

extern void	EmitToken __((struct token *t, ComponentClass ac, struct address *ap));
STATIC char	*Input822TokenName __(());

/* private semantics mechanism variables: */
STATIC ComponentClass currentTokenType;
STATIC struct token *ptrNextInputToken, *ptrAcceptedToken;
STATIC struct token *headPending, **pending;
STATIC struct token *headDeferred, **deferred;

/* Table Walker State */
STATIC int	processing;		/* are we running the table walker? */
STATIC int	inAddress;		/* are we collecting an address? */
STATIC int	sslPointer;		/* index into S/SL table */
STATIC TableOperation	operation;	/* the current operation */

/* convenience */
STATIC struct address nullAddr = { NULL, NULL, newAddress, 0, NULL, NULL };
#ifdef ATHACK
STATIC struct token atsigntoken = { (u_char *)"@", 1, Special, NULL };
#endif

/* Tracing Control */
STATIC FILE	*tracefp;		/* if non-null, trace output here */

/* Abort flag */
STATIC int	aborted;

/*
 * The Rule Call Stack implements Syntax/Semantic Language rule call and return.
 * Each time an oCall operation is executed, the table return address is pushed
 * onto the Rule Call Stack.  When an oReturn is executed, the return address
 * is popped from the stack.  An oReturn executed when the Rule Call Stack is
 * empty terminates table execution.
 */

STATIC int	sslStack[127];		/* The S/SL Rule Call Stack */
#define	sslStackSize	(sizeof sslStack/sizeof sslStack[0])
STATIC int	sslTop;

/*
 * Set by the Choice Handler to indicate whether a match was made or the
 * otherwise path was taken.  Set to true if a match was made and false
 * otherwise.  This flag is used in input choices to indicate whether the
 * choice input token should be accepted or not.
 */

STATIC int	choiceTagMatched;	/* Choice Match Flag */

/*
 * These are used to hold the decoded parameter value to a parameterized
 * semantic operation and the result value returned by a choice semantic
 * operation or rule respectively.					 
 */

STATIC int	parameterValue;		/* Parameter for Semantic Operation */
STATIC int	resultValue;		/* Result from Semantic Operation */

/* S/SL System Failure Codes */

typedef enum {
	fSemanticChoiceFailed,			/* 0 */
	fChoiceRuleFailed			/* 1 */
} FailureCodes;				/* S/SL System Failure Code Type */


#define	maxErrors	20
STATIC int	noErrors;		/* Error Counter */
STATIC ErrorCodes	firstFatalErrorCode = eSslStackOverflow;  /* fix */

/* Input Interface */
STATIC InputTokens	nextInputToken = tNewLine;
STATIC InputTokens	acceptedToken;

struct receivedtag {
	char		*name;
	int		len;
	InputTokens	value;
};

STATIC struct receivedtag rt[] = {
	{	"from",		4,	tFrom		},
	{	"by",		2,	tBy		},
	{	"id",		2,	tId		},
	{	"with",		4,	tWith		},
	{	"via",		3,	tVia		},
	{	"for",		3,	tFor		},
	{	"convert",	7,	tConvert	},
	{	0,		0,	tSyntaxError	}
};

/* Variables Used in Syntax Error Recovery */
STATIC InputTokens	savedInputToken;
STATIC int		RunningSslRecovery = 0;

STATIC struct { ErrorCodes ec; char *msg; } ear[] = {
{ eSyntaxError,			"Syntax error"				},
{ ePrematureEndOfFile,		"Unexpected end of file"		},
{ eExtraneousProgramText,	"Extraneous program text"		},
{ eSslStackOverflow,		"Nesting too deep"			},
{ eExtraneousTokensInAddress,	"extraneous tokens in address"		},
{ eExtraneousTokensInMailbox,	"extraneous tokens in mailbox"		},
{ eMissingSemicolonToEndGroup,	"missing semicolon to end mail group"	},
{ eMissingSemicolonInReceived,	"missing semicolon before timestamp"	},
{ eMissingEndOfAddress,		"missing end of address"		},
{ eMissingEndOfMailbox,		"missing end of mailbox"		},
{ eIllegalWordInPhrase,		"illegal word in phrase"	},
{ eIllegalSpecialInPhrase,	"illegal special character in phrase"	},
{ eIllegalPeriodInPhrase,	"illegal period in phrase"		},
{ eIllegalPhraseMustBeQuoted,	"phrases containing '.' must be quoted"	},
{ eIllegalSubdomainInDomain,	"illegal subdomain in domain, propably extra '.' at the end of the address"	},
{ eIllegalTokenInRoute,		"illegal token in route"		},
{ eIllegalWordInLocalPart,	"illegal word in localpart, propably extra '.' at the end of the address"	},
{ eIllegalStartOfMessageId,	"illegal start of message identification"},
{ eIllegalEndOfMessageId,	"illegal end of message identification"	},
{ eIllegalEncryptionIdentifier,	"illegal encryption Identifier"		},
{ eIllegalAddressSeparator,	"illegal address separator"		},
{ eIllegalMailboxSeparator,	"illegal mailbox separator"		},
{ eIllegalMessageIDSeparator,	"illegal message-id separator"		},
{ eExpectedWord,		"expected word"				},
{ eIllegalStartOfRouteAddress,	"illegal start of route address"	},
{ eIllegalEndOfRouteAddress,	"illegal end of route address"		},
{ eIllegalSpecialInValue,	"illegal special in value"		},
{ eIllegalReferencesSeparator,	"illegal reference separator"		},
};

/* This procedure emits the error message associated with errCode */

STATIC void
SslError(errCode, ap)
	ErrorCodes errCode;
	struct address *ap;
{
	struct token *t;
	int i;

	if (errCode == eNoError)
		abort();

	for (t = NULL, i = 0; i < (sizeof ear/sizeof ear[0]); ++i) {
		if (ear[i].ec == errCode) {
			t = makeToken((u_char *)ear[i].msg, strlen(ear[i].msg));
			break;
		}
	}
	if (t == NULL)
		t = makeToken((u_char *)"Unknown", sizeof("Unknown")-1);
	t->t_type = Error;
	EmitToken(t, cError, ap);

	++noErrors;

	if ((int)(errCode) >= (int)(firstFatalErrorCode)
	    || noErrors == maxErrors) {
		aborted = 1;
		processing = 0;
	}
}

/*
 * This procedure provides the interface to the previous pass.
 * It is reponsible for handling all input including line number
 * indicators and the values and text associated with input tokens.  
 */

STATIC void
AcceptInputToken(tlistp, ap)
	struct token **tlistp;
	struct address *ap;
{

	if (acceptedToken == tEndOfHeader && nextInputToken == tEndOfHeader)
		abort();

	if (nextInputToken == tSyntaxError) {
		acceptedToken = tSyntaxError;
		/* ptrAcceptedToken = NULL; */
		nextInputToken = savedInputToken;
		goto ok;
	} else {
		/* Accept Token */
		acceptedToken = nextInputToken;
		ptrAcceptedToken = ptrNextInputToken;
	}

	/* Read Next Input Token */
	do {
next:
		if (*tlistp == NULL) {
			nextInputToken = tEndOfHeader;
			ptrNextInputToken = NULL;
		} else {
			ptrNextInputToken = *tlistp;
			switch ((*tlistp)->t_type) {
			case Error:
			case Comment:
				if (ptrAcceptedToken == NULL && ap != NULL) {
					EmitToken(*tlistp,
						(*tlistp)->t_type == Error ?
							cError : cComment, ap);
				} else {
					(*pending) = copyToken(*tlistp);
					pending = &((*pending)->t_next);
				}
				(*tlistp) = (*tlistp)->t_next;
				goto next;
			case Space:
#if 0
# if 0
				if (RunningSslRecovery) {
				  /* Copy it only during SslRecovery */
				  if (ptrAcceptedToken == NULL && ap != NULL) {
				    EmitToken(*tlistp, cSpace, ap);
				  } else {
				    (*pending) = copyToken(*tlistp);
				    pending = &((*pending)->t_next);
				  }
				}
# else
				nextInputToken = tSpace;
				(*tlistp) = (*tlistp)->t_next;
				break;
# endif
#endif
				/* fall through */
			case Fold:
				(*tlistp) = (*tlistp)->t_next;
				goto next;
			case Special:
				switch ((*tlistp)->t_pname[0]) {
				case ';':
					nextInputToken = tSemicolon;
					break;
				case ',':
					nextInputToken = tComma;
					break;
				case '.':
					nextInputToken = tPeriod;
					break;
				case ':':
					if ((*tlistp)->t_pname[1] == ':')
					  nextInputToken = tDoubleColon;
					else
					  nextInputToken = tColon;
					break;
				case '<':
					nextInputToken = tLeftAngle;
					break;
				case '>':
					nextInputToken = tRightAngle;
					break;
				case '@':
					nextInputToken = tAtSign;
					break;
				default:
					nextInputToken = tOtherSpecial;
					break;
				}
				*tlistp = (*tlistp)->t_next;
				break;
			case String:
				nextInputToken = tQuotedString;
				*tlistp = (*tlistp)->t_next;
				break;
			case Atom:
				nextInputToken = tAtom;
				*tlistp = (*tlistp)->t_next;
				break;
			case DomainLiteral:
				nextInputToken = tDomainLiteral;
				*tlistp = (*tlistp)->t_next;
				break;
			case Word:
			case Empty:
				abort();
			default:
				break;
			}
		}
	} while (nextInputToken == tNewLine);

ok:
	/* Trace Input */
	if (tracefp)
	    printf("Input token accepted %s (%d)  Next input token %s (%d)\n",
		   Input822TokenName((int)acceptedToken), acceptedToken,
		   Input822TokenName((int)nextInputToken), nextInputToken);
}

/*
 * The constants, variables, types, modules and procedures used in
 * implementing the Semantic Mechanisms of the pass go here.  These
 * implement the facilities used in the semantic operations.
 */

/* Syntax Error Handling */

/*
 * This procedure handles syntax errors in the input to the Parser pass,
 *
 * Syntax error recovery:
 * When a mismatch occurs between the the next input token and the syntax
 * table, the following recovery is employed.
 *
 * If the expected token is tEndOfHeader then if there has been no previous
 * syntax error on the line, ignore the error.  (A missing logical end of
 * header is not a real error.)
 *
 * If the expected token is tComma or tSemicolon or tEndOfHeader and a syntax
 * error has already been detected in the current logical address (flagged
 * by nextInputToken == tSyntaxError), then flush the input and exit when
 * any of these tokens are found.
 *
 * Otherwise, if this is the first syntax error detected on the line
 * (flagged by nextInputToken != tSyntaxError), then if the input token is
 * tEndOfHeader then emit the ePrematureEndOfFile error code and terminate
 * execution.  Otherwise, emit the eSyntaxError error code and set the
 * nextInputToken to tSyntaxError to prevent further input, and exit when the
 * expected ( ?? ) input is tSemicolon or tNewLine.
 *
 * If the expected token is not tSemicolon nor tNewLine and a syntax error
 * has already been detected on the current line (flagged by nextInputToken ==
 * tSyntaxError), then do nothing and continue as if the expected token had
 * been matched.
 */

STATIC void
SslSyntaxError(tlistp, ap)
	struct token **tlistp;
	struct address *ap;
{
	if (operation != oInput && operation != oInputAny)
		abort();

	if (nextInputToken == tSyntaxError) {
		/* Currently recovering from syntax error */
		/* Complete recovery by synchronizing input to a new address */
		nextInputToken = savedInputToken;
		RunningSslRecovery = 1;
		while (nextInputToken != tSemicolon
			&& nextInputToken != tComma
			&& nextInputToken != tEndOfHeader) {
			AcceptInputToken(tlistp, ap);
			EmitToken(ptrAcceptedToken, cResync, ap);
		}
		RunningSslRecovery = 0;
		if (sslTop == 0)
			/* Return from main S/SL procedure */
			processing = 0;
		else
			sslPointer = sslStack[sslTop--];
	} else {
		/* First syntax error on the line */
		if (sslTable[sslPointer] == (int)(tEndOfHeader)) {
			/* Ignore missing logical newlines */
		} else {
			/* mark syntax error, gobble tokens, jump to resync */
			savedInputToken = nextInputToken;
			nextInputToken = tSyntaxError;
		}
	}
}

STATIC char *
Input822TokenName(in)
	int in;
{
	switch ((InputTokens)in) {
	case tSyntaxError:	return "tSyntaxError";
	case tIdent:		return "tIdent";
	case tString:		return "tString";
	case tInteger:		return "tInteger";
	case tSemicolon:	return "tSemicolon";
	case tComma:		return "tComma";
	case tPeriod:		return "tPeriod";
	case tDoubleColon:	return "tDoubleColon";
	case tColon:		return "tColon";
	case tLeftAngle:	return "tLeftAngle";
	case tRightAngle:	return "tRightAngle";
	case tAtSign:		return "tAtSign";
	case tOtherSpecial:	return "tOtherSpecial";
	case tAtom:		return "tAtom";
	case tBy:		return "tBy";
	case tDomainLiteral:	return "tDomainLiteral";
	case tFor:		return "tFor";
	case tFrom:		return "tFrom";
	case tId:		return "tId";
	case tQuotedString:	return "tQuotedString";
	case tVia:		return "tVia";
	case tWith:		return "tWith";
	case tNewLine:		return "tNewLine";
	case tEndOfHeader:	return "tEndOfHeader";
	default: break;
	}
	return "unknown input token";
}

STATIC char *
TableOperationName(op)
	TableOperation	op;
{
	switch (op) {
	case oCall:		return "oCall";
	case oReturn:		return "oReturn";
	case oRuleEnd:		return "oRuleEnd";
	case oJump:		return "oJump";
	case oInput:		return "oInput";
	case oInputAny:		return "oInputAny";
	case oInputChoice:	return "oInputChoice";
	case oEmit:		return "oEmit";
	case oError:		return "oError";
	case oChoice:		return "oChoice";
	case oChoiceEnd:	return "oChoiceEnd";
	case oSetParameter:	return "oSetParameter";
	case oSetResult:	return "oSetResult";
	case oSetResultFromInput:	return "oSetResultFromInput";
	case oSetComponentType:	return "oSetComponentType";
	case oEmitToken:	return "oEmitToken";
	case oEmitTokenCurType:	return "oEmitTokenCurType";
	case oOpenAddress:	return "oOpenAddress";
	case oAppendAddress:	return "oAppendAddress";
	case oCloseAddress:	return "oCloseAddress";
	case oDateTime:		return "oDateTime";
	case oEnterReceived:	return "oEnterReceived";
	case oExitReceived:	return "oExitReceived";
	case oSetReturnType:	return "oSetReturnType";
	case oRewind:		return "oRewind";
	default: break;
	}
	return "unknown operation";
}

STATIC void
SslTrace()
{
	printf("Table index %d  Operation %d (%s) Argument %d\n",
	       sslPointer-1, operation, TableOperationName(operation),
	       sslTable[sslPointer]);
}


STATIC void
SslFailure(failCode)
	FailureCodes failCode;
{
	printf("### S/SL program failure:  ");

	switch (failCode) {
	case fSemanticChoiceFailed:
		printf("Semantic choice failed\n");
		break;
	case fChoiceRuleFailed:
		printf("Choice rule returned without a value\n");
		break;
	}

	SslTrace();
	abort();
}


/*
 * This procedure performs both input and semantic choices.  It sequentially
 * tests each alternative value against the tag value, and when a match is
 * found, performs a branch to the corresponding alternative path.  If none
 * of the alternative values matches the tag value, sslTable interpretation
 * proceeds to the operation immediately following the list of alternatives
 * (normally the otherwise path).  The flag choiceTagMatched is set to true
 * if a match is found and false otherwise.
 */

STATIC void
SslChoice(choiceTag)
	int choiceTag;
{
	int	numberOfChoices, choicePointer;

	choicePointer = sslTable[sslPointer];
	choiceTagMatched = 0;

	for (numberOfChoices = sslTable[choicePointer++];
	     numberOfChoices > 0;
	     choicePointer += 2, --numberOfChoices) {
		if (tracefp)
			printf("Matching %s (%d) with %s (%d)\n",
				Input822TokenName(choiceTag), choiceTag,
				Input822TokenName(sslTable[choicePointer]),
				sslTable[choicePointer]);
		if (sslTable[choicePointer] == choiceTag
#ifdef	ATHACK
		    || (choiceTag == (int)tAtom
				  && sslTable[choicePointer] == (int)tAtSign
				  && ptrNextInputToken != NULL
				  && ptrNextInputToken->t_len == 2
				  && (ptrNextInputToken->t_pname[0] == 'a'
				      || ptrNextInputToken->t_pname[0] == 'A')
				  && (ptrNextInputToken->t_pname[1] == 't'
				      || ptrNextInputToken->t_pname[1] == 'T')
				  && (ptrNextInputToken = &atsigntoken))
#endif	/* ATHACK */
		    ) {
			sslPointer = sslTable[choicePointer+1];
			choiceTagMatched = 1;
			if (tracefp)
				printf("Matched!\n");
			return;
		}
	}
	if (tracefp)
		printf("No match!\n");
	sslPointer = choicePointer;
}

void
EmitToken(t, ac, ap)
	struct token *t;
	ComponentClass ac;
	struct address *ap;
{
	struct token *nt, **ptp;
	struct addr *na;
	AddrComponent type;

	if (deferred != NULL) {
		if (ac == cError)  {	/* prepend errors to deferred tokens */
			nt = copyToken(t);
			ptp = &headDeferred;
			for(t = headDeferred; t ; ptp = &t->t_next, t = *ptp)
				if (t->t_type != Error)
					break;
			nt->t_next = *ptp;
			*ptp = nt;
			if (deferred == &headDeferred)
				deferred = &nt->t_next;
		} else {
			(*deferred) = copyToken(t);
			deferred = &((*deferred)->t_next);
		}
		if (headPending) {
			/* printf("append pending\n"); */
			(*deferred) = headPending;
			deferred = pending;
			(*pending) = NULL;
			pending = &headPending;
			headPending = 0;
		}
		return;
	}
	switch (ac) {
	case cPhrase:	type = aPhrase; break;
	case cComment:	type = aComment; break;
	case cSpecial:	type = aSpecial; break;
	case cGroup:	type = aGroup; break;
	case cAddress:	type = anAddress; break;
	case cDomain:	type = aDomain; break;
	case cWord:	type = aWord; break;
#if 0
	case cSpace:	type = aSpace; break;
#endif
	case cResync:	type = reSync; ap->a_stamp = BadAddress; break;
	case cError:
	default:	type = anError; ap->a_stamp = BadAddress; break;
	}
	/*
	 * For efficiency, we just keep prepending tokens. The
	 * reverseComponent() function will eventually do the reversal.
	 */
	if (ap->a_tokens != NULL && ap->a_tokens->p_type == type) {
		/* prepend now -- reverse later */
		nt = copyToken(t);
		nt->t_next = ap->a_tokens->p_tokens;
		ap->a_tokens->p_tokens = nt;
	} else {
		na = (struct addr *)tmalloc(sizeof (struct addr));
		/* prepend now -- reverse later */
		nt = copyToken(t);
		nt->t_next = NULL;
		na->p_tokens = nt;
		na->p_next = ap->a_tokens;
		na->p_type = type;
		ap->a_tokens = na;
	}
	if (headPending) {
		(*pending) = NULL;
		for (t = headPending, headPending = NULL;
		     t != NULL; t = t->t_next)
			EmitToken(t, t->t_type==Error ? cError : cComment, ap);
		pending = &headPending;
	}
}

struct address *
revaddress(ap)
	struct address *ap;
{
	register struct token	*rprev, *rnext, *r;
	register struct addr	*pprev, *pnext, *p;

	/* reverse order of address components and tokens */
	if (ap == NULL)
		return NULL;
	/* reverse the various linked lists */
	pprev = NULL;
	for (p = ap->a_tokens; p != NULL; p = pnext) {
		pnext = p->p_next;
		p->p_next = pprev;
		pprev = p;
		rprev = NULL;
		for (r = p->p_tokens; r != NULL; r = rnext) {
			rnext = r->t_next;
			r->t_next = rprev;
			rprev = r;
		}
		p->p_tokens = rprev;
	}
	ap->a_tokens = pprev;
	return ap;
}

void
revappend(tprev,ap)
struct token **tprev;
struct address *ap;
{
	struct addr *p;
	struct token *t;

	ap = revaddress(ap);
	/* append all the tokens together */
	if (ap == NULL || ap->a_tokens == NULL
	    || ap->a_stamp == BadAddress)
		return;
	for (p = ap->a_tokens; p ; p = p->p_next) {
		*tprev = p->p_tokens;
		for (t = p->p_tokens; t ; t = t->t_next)
			tprev = &(t->t_next);
	}
	*tprev = NULL;
}

/*
 * The Parser.
 *
 * entry	- where in the S/SL table to start executing.
 * tlistp	- pointer to raw token list as returned by the scanner.
 * tfp		- trace file pointer (for debugging output)
 *
 * The parser can return any of the union misc type values, however the
 * usual case is when trying to recognize an address or mailbox of some kind.
 * In this case, the parser can return a list of address structures, each
 * of which describes an address or mailbox as appropriate. This description
 * consists of a categorization of which scanner tokens correspond to which
 * RFC822 tokens. Certain non-terminals in the RFC822 grammar can correspond
 * to simple word lists, for example phrases or comments. These appear as
 * token classes (the AddrComponent enum type) alongside Atoms and Specials
 * in what the parser returns.
 */

union misc
parse822(entry, tlistp, ltmp, tfp)
	HeaderSemantics	entry;
	struct token **tlistp;
	struct tm *ltmp;
	FILE *tfp;
{
	int		inReceived, i;
	struct token	*t, *torig;
	struct addr	*p;
	struct address	*ap, *na, *pap;
	struct received	rcvd;
	union misc	retval;
	ReturnValue	returnType;

	tracefp = tfp;
	torig = *tlistp;
	pending = &headPending;
	deferred = NULL;
	inReceived = 0;
	inAddress = 0;
	ap = NULL;
	processing = 1;
	aborted = 0;
	sslTop = 0;
	noErrors = 0;
	nextInputToken = tNewLine;
	acceptedToken = tNewLine;
	returnType = rAddress;
	currentTokenType = cAddress;

	if (tracefp) {
	  sslPointer = ((int)entry) + 1;
	  operation = (TableOperation)-1; /* Invalid value! */
	  SslTrace();
	}

	/* Initialize Input/Output */
	AcceptInputToken(tlistp, ap);
	if (nextInputToken == tEndOfHeader) {
		retval.a = NULL;
		return retval;
	}

	/* Walk the S/SL Table */

	sslPointer = (int)entry;
	while (processing || inAddress) {
		if (processing)
			operation = (TableOperation)(sslTable[sslPointer++]);
		else
			operation = oCloseAddress;

		/* Trace Execution */
		if (tracefp)
			SslTrace();

		switch (operation) {
		case oCall:
			if (sslTop < sslStackSize) {
				sslStack[++sslTop] = sslPointer + 1;
				sslPointer = sslTable[sslPointer];
			} else {
				if (ap == NULL) {
					ap = (struct address *)
					    tmalloc(sizeof (struct address));
					*ap = nullAddr;
					inAddress = 1;
				}
				SslError(eSslStackOverflow, ap);
				processing = 0;
			}
			break;
		case oReturn:
			if (sslTop == 0)
				/* Return from main S/SL procedure */
				processing = 0;
			else
				sslPointer = sslStack[sslTop--];
			break;
		case oRuleEnd:
			SslFailure(fChoiceRuleFailed);
			break;
		case oJump:
			sslPointer = sslTable[sslPointer];
			break;
		case oInput:
			if (sslTable[sslPointer] == (int)(nextInputToken)
#ifdef	ATHACK
			    || (nextInputToken == tAtom
				&& sslTable[sslPointer] == (int)tAtSign
				&& ptrNextInputToken != NULL
				&& ptrNextInputToken->t_len == 2
				&& (ptrNextInputToken->t_pname[0] == 'a'
				    || ptrNextInputToken->t_pname[0] == 'A')
				&& (ptrNextInputToken->t_pname[1] == 't'
				    || ptrNextInputToken->t_pname[1] == 'T')
				&& (ptrNextInputToken = &atsigntoken))
#endif	/* ATHACK */
				) {
				AcceptInputToken(tlistp, ap);
				++sslPointer;
			} else {
				++sslPointer;
				/* Syntax error in input */
				if (ap == NULL) {
					ap = (struct address *)
					    tmalloc(sizeof (struct address));
					*ap = nullAddr;
					inAddress = 1;
				}
				SslSyntaxError(tlistp, ap);
			}
			break;
		case oInputAny:
			if (nextInputToken != tEndOfHeader)
				AcceptInputToken(tlistp, ap);
			else {
				/* Premature end of file */
				if (ap == NULL) {
					ap = (struct address *)
					    tmalloc(sizeof (struct address));
					*ap = nullAddr;
					inAddress = 1;
				}
				SslSyntaxError(tlistp, ap);
			}
			break;
		case oInputChoice:
			if (inReceived && nextInputToken == tAtom) {
				t = ptrNextInputToken;
				for (i = 0; rt[i].name != NULL; ++i) {
					if (TOKENLEN(t) == strlen(rt[i].name)
					    && CISTREQN(t->t_pname,
						    rt[i].name, rt[i].len)) {
						nextInputToken = rt[i].value;
						break;
					}
				}
			}
			SslChoice((int)nextInputToken);

			if (choiceTagMatched)
				AcceptInputToken(tlistp, ap);
			break;
		case oEmit:
			++sslPointer;
			break;
		case oError:
			++sslPointer;
			if (ap == NULL) {
				ap = (struct address *)
					tmalloc(sizeof (struct address));
				*ap = nullAddr;
				inAddress = 1;
			}
			SslError((ErrorCodes)sslTable[sslPointer-1], ap);
			break;
		case oChoice:
			if (inReceived && resultValue == (int)tAtom) {
				t = ptrNextInputToken;
				for (i = 0; rt[i].name != NULL; ++i) {
					if (TOKENLEN(t) == strlen(rt[i].name)
					    && CISTREQN(t->t_pname,
						    rt[i].name, rt[i].len)) {
						resultValue = (int)rt[i].value;
						break;
					}
				}
			}
			SslChoice(resultValue);
			break;
		case oChoiceEnd:
			SslFailure(fSemanticChoiceFailed);
			break;
		case oSetParameter:
			parameterValue = sslTable[sslPointer++];
			break;
		case oSetResult:
			resultValue = sslTable[sslPointer++];
			break;
		case oSetResultFromInput:
			resultValue = (int)nextInputToken;
			break;

		/* The Following Are Pass Dependent Semantic Mechanisms */

		case oSetComponentType:
			currentTokenType = (ComponentClass)parameterValue;
			break;
		case oEmitTokenCurType:
			parameterValue = (int)currentTokenType;
			/* fall through */
		case oEmitToken:
			if (ap == NULL) {
				ap = (struct address *)
					tmalloc(sizeof (struct address));
				*ap = nullAddr;
				inAddress = 1;
			}
			EmitToken(ptrAcceptedToken,
				  (ComponentClass)parameterValue, ap);
			break;
		case oDeferEmitToken:
			if (deferred != NULL)
				abort();
			deferred = &headDeferred;
			if (headPending) {
				if (ap == NULL) {
					ap = (struct address *)
					    tmalloc(sizeof (struct address));
					*ap = nullAddr;
					inAddress = 1;
				}
				(*pending) = NULL;
				for (t = headPending, headPending = NULL;
				     t != NULL; t = t->t_next)
					EmitToken(t, t->t_type == Error ?
						     cError : cComment, ap);
				pending = &headPending;
			}
			break;
		case oReleaseEmitToken:	/* comments... */
			if (ap == NULL) {
				ap = (struct address *)
					tmalloc(sizeof (struct address));
				*ap = nullAddr;
				inAddress = 1;
			}
			t = headDeferred;
			headDeferred = NULL;
			(*deferred) = NULL;
			deferred = NULL;
			for (; t != NULL; t = t->t_next)
				if (t->t_type == Comment)
					EmitToken(t, cComment, ap);
				else if (t->t_type == Error)
					EmitToken(t, cError, ap);
				else
					EmitToken(t,
						(ComponentClass)parameterValue,
						ap);
			break;
		case oOpenAddress:
			if (inAddress)
				break;
			na = (struct address *)tmalloc(sizeof (struct address));
			*na = nullAddr;
			na->a_next = ap;
			ap = na;
			inAddress = 1;
			break;
		case oAppendAddress:
			if (inAddress)
				break;
			ap = revaddress(ap);
			inAddress = 1;
			break;
		case oCloseAddress:
			if (!inAddress)
				break;
			ap = revaddress(ap);
			inAddress = 0;
			break;
		case oDateTime:
			if (ptrNextInputToken != NULL) {
				retval.d = dateParse(ltmp, ptrNextInputToken);
			} else
				retval.d = 0;
			break;
		case oEnterReceived:
			inReceived = 1;
			rcvd.r_from = rcvd.r_by = rcvd.r_id = rcvd.r_for = NULL;
			rcvd.r_via = rcvd.r_with = rcvd.r_convert = NULL;
			rcvd.r_time = 0L;
			break;
		case oExitReceived:
			inReceived = 0;
			break;
		case oSaveReceivedComponent:
			switch ((ReceivedComponent)parameterValue) {
			case rcFrom:
				rcvd.r_from = revaddress(ap);
				break;
			case rcBy:
				rcvd.r_by = revaddress(ap);
				break;
			case rcVia:
				/* grab a single token */
				if (ap != NULL
				    && ap->a_stamp != BadAddress
				    && ap->a_tokens != NULL
				    && ap->a_tokens->p_tokens != NULL) {
					rcvd.r_via = ap->a_tokens->p_tokens;
					rcvd.r_via->t_next = NULL; /* in case */
				}
				break;
			case rcWith:
				revappend(&rcvd.r_with,ap);
				break;
			case rcConvert:
				revappend(&rcvd.r_convert,ap);
				break;
			case rcId:
				rcvd.r_id = revaddress(ap);
				break;
			case rcFor:
				rcvd.r_for = revaddress(ap);
				break;
			case rcDate:
				rcvd.r_time = retval.d;
				break;
			default:
				break;
			}
			ap = NULL;
			break;
		case oSetReturnType:
			returnType = (ReturnValue)parameterValue;
			break;
		case oRewind:
			if (ap == NULL
			    || (ap->a_tokens == NULL && ap->a_next == NULL)) {
				*tlistp = torig;
				pending = &headPending;
				deferred = NULL;
				nextInputToken = tNewLine;
				acceptedToken = tNewLine;
				AcceptInputToken(tlistp, ap);
				resultValue = (int)Success;
			} else
				resultValue = (int)Failure;
			break;
		    
		default:
			printf("Unknown operation %d\n", (int)operation);
			abort();
		}
	}

	if (returnType == rAddress) {
		if (nextInputToken != tEndOfHeader && !aborted && ap != NULL)
			SslError(eExtraneousProgramText, ap);
		/* reverse order of addresses */
		for (na = NULL; ap != NULL; na = ap, ap = pap) {
			pap = ap->a_next;
			ap->a_next = na;
		}
		retval.a = na;
	} else if (returnType == rReceived) {
		retval.r = (struct received *)tmalloc(sizeof (struct received));
		*(retval.r) = rcvd;
	}
	return retval;
}
