/*
 *  XMail by Davide Libenzi ( Intranet and Internet mail server )
 *  Copyright (C) 1999,..,2004  Davide Libenzi
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Davide Libenzi <davidel@xmailserver.org>
 *
 */

#include "SysInclude.h"
#include "SysDep.h"
#include "SvrDefines.h"
#include "SList.h"
#include "ShBlocks.h"
#include "StrUtils.h"
#include "BuffSock.h"
#include "MiscUtils.h"
#include "ResLocks.h"
#include "BuffSock.h"
#include "SvrUtils.h"
#include "UsrUtils.h"
#include "MessQueue.h"
#include "SMAILUtils.h"
#include "QueueUtils.h"
#include "MailDomains.h"
#include "SMTPUtils.h"
#include "ExtAliases.h"
#include "UsrMailList.h"
#include "Filter.h"
#include "MailConfig.h"
#include "AppDefines.h"
#include "MailSvr.h"

#define FILTER_LOG_FILE             "filters"
#define FILTER_STORAGE_DIR          "filters"
#define FILTER_SELECT_MAX           128
#define FILTER_DB_LINE_MAX          512
#define FILTER_LINE_MAX             1024

struct FilterMsgInfo {
	char szSender[MAX_ADDR_NAME];
	char szRecipient[MAX_ADDR_NAME];
	SYS_INET_ADDR LocalAddr;
	SYS_INET_ADDR RemoteAddr;
	char szSpoolFile[SYS_MAX_PATH];
	char szAuthName[MAX_ADDR_NAME];
};

static int FilLogExec(FilterMsgInfo const &FMI, char const * const *ppszExec,
		      int iExecResult, int iExitCode, char const *pszType,
		      char const *pszInfo);
static int FilLoadMsgInfo(SPLF_HANDLE hFSpool, FilterMsgInfo & FMI);
static void FilFreeMsgInfo(FilterMsgInfo & FMI);
static int FilGetFilePath(char const *pszMode, char *pszFilePath, int iMaxPath);
static int FilAddFilter(char **ppszFilters, int &iNumFilters, char const *pszFilterName);
static int FilSelectFilters(char const *pszFilterFilePath, char const *pszMode,
			    FilterMsgInfo const &FMI, char **ppszFilters, int iMaxFilters);
static void FilFreeFilters(char **ppszFilters, int iNumFilters);
static int FilGetFilterPath(char const *pszFileName, char *pszFilePath, int iMaxPath);
static int FilPreExec(FilterMsgInfo const &FMI, FilterTokens *pToks, char **ppszPEError);
static int FilApplyFilter(char const *pszFilterPath, SPLF_HANDLE hFSpool,
			  QUEUE_HANDLE hQueue, QMSG_HANDLE hMessage, FilterMsgInfo const &FMI,
			  char const *pszType);
static int FilFilterMacroSubstitutes(char **ppszCmdTokens, SPLF_HANDLE hFSpool,
				     FilterMsgInfo const &FMI);


int FilLogFilter(FilterLogInfo const *pFLI)
{

	char szTime[256] = "";

	MscGetTimeNbrString(szTime, sizeof(szTime) - 1);

	RLCK_HANDLE hResLock = RLckLockEX(SVR_LOGS_DIR SYS_SLASH_STR FILTER_LOG_FILE);

	if (hResLock == INVALID_RLCK_HANDLE)
		return (ErrGetErrorCode());

	char szLocIP[128] = "???.???.???.???";
	char szRmtIP[128] = "???.???.???.???";

	MscFileLog(FILTER_LOG_FILE, "\"%s\""
		   "\t\"%s\""
		   "\t\"%s\""
		   "\t\"%s\""
		   "\t\"%s\""
		   "\t\"%s\""
		   "\t\"%s\""
		   "\t\"%d\""
		   "\t\"%d\""
		   "\t\"%s\""
		   "\n", pFLI->pszSender, pFLI->pszRecipient, SysInetNToA(pFLI->LocalAddr, szLocIP),
		   SysInetNToA(pFLI->RemoteAddr, szRmtIP), szTime, pFLI->pszType, pFLI->pszInfo,
		   pFLI->iExecResult, pFLI->iExitCode, pFLI->ppszExec[0]);

	RLckUnlockEX(hResLock);

	return (0);

}

static int FilLogExec(FilterMsgInfo const &FMI, char const * const *ppszExec,
		      int iExecResult, int iExitCode, char const *pszType,
		      char const *pszInfo)
{

	FilterLogInfo FLI;

	FLI.pszSender = FMI.szSender;
	FLI.pszRecipient = FMI.szRecipient;
	FLI.LocalAddr = FMI.LocalAddr;
	FLI.RemoteAddr = FMI.RemoteAddr;
	FLI.ppszExec = ppszExec;
	FLI.iExecResult = iExecResult;
	FLI.iExitCode = iExitCode;
	FLI.pszType = pszType;
	FLI.pszInfo = pszInfo != NULL ? pszInfo: "";

	return (FilLogFilter(&FLI));

}

static int FilLoadMsgInfo(SPLF_HANDLE hFSpool, FilterMsgInfo & FMI)
{

	UserInfo *pUI;
	char const *const *ppszInfo = USmlGetInfo(hFSpool);
	char const *const *ppszFrom = USmlGetMailFrom(hFSpool);
	char const *const *ppszRcpt = USmlGetRcptTo(hFSpool);
	char const *pszSpoolFile = USmlGetSpoolFilePath(hFSpool);
	int iFromDomains = StrStringsCount(ppszFrom);
	int iRcptDomains = StrStringsCount(ppszRcpt);
	char szUser[MAX_ADDR_NAME] = "";
	char szDomain[MAX_ADDR_NAME] = "";

	ZeroData(FMI);

	if ((iFromDomains > 0) &&
	    (USmtpSplitEmailAddr(ppszFrom[iFromDomains - 1], szUser, szDomain) == 0)) {
		if ((pUI = UsrGetUserByNameOrAlias(szDomain, szUser)) != NULL) {
			UsrGetAddress(pUI, FMI.szSender);

			UsrFreeUserInfo(pUI);
		} else
			StrSNCpy(FMI.szSender, ppszFrom[iFromDomains - 1]);
	} else
		SetEmptyString(FMI.szSender);

	if ((iRcptDomains > 0) &&
	    (USmtpSplitEmailAddr(ppszRcpt[iRcptDomains - 1], szUser, szDomain) == 0)) {
		if ((pUI = UsrGetUserByNameOrAlias(szDomain, szUser)) != NULL) {
			UsrGetAddress(pUI, FMI.szRecipient);

			UsrFreeUserInfo(pUI);
		} else
			StrSNCpy(FMI.szRecipient, ppszRcpt[iRcptDomains - 1]);
	} else
		SetEmptyString(FMI.szRecipient);

	if ((MscGetServerAddress(ppszInfo[smiServerAddr], FMI.LocalAddr) < 0) ||
	    (MscGetServerAddress(ppszInfo[smiClientAddr], FMI.RemoteAddr) < 0))
		return (ErrGetErrorCode());

	StrSNCpy(FMI.szSpoolFile, pszSpoolFile);

	if (USmlMessageAuth(hFSpool, FMI.szAuthName, sizeof(FMI.szAuthName) - 1) < 0)
		SetEmptyString(FMI.szAuthName);

	return (0);

}

static void FilFreeMsgInfo(FilterMsgInfo & FMI)
{

}

char *FilGetFilterRejMessage(char const *pszSpoolFile)
{

	FILE *pFile;
	char szRejFilePath[SYS_MAX_PATH] = "";
	char szRejMsg[512] = "";

	SysSNPrintf(szRejFilePath, sizeof(szRejFilePath) - 1, "%s.rej", pszSpoolFile);
	if ((pFile = fopen(szRejFilePath, "rb")) == NULL)
		return NULL;

	MscFGets(szRejMsg, sizeof(szRejMsg) - 1, pFile);

	fclose(pFile);
	SysRemove(szRejFilePath);

	return (SysStrDup(szRejMsg));

}

static int FilGetFilePath(char const *pszMode, char *pszFilePath, int iMaxPath)
{

	char szMailRootPath[SYS_MAX_PATH] = "";

	CfgGetRootPath(szMailRootPath, sizeof(szMailRootPath));

	SysSNPrintf(pszFilePath, iMaxPath - 1, "%sfilters.%s.tab", szMailRootPath, pszMode);

	return (0);

}

static int FilAddFilter(char **ppszFilters, int &iNumFilters, char const *pszFilterName)
{

	for (int ii = 0; ii < iNumFilters; ii++)
		if (strcmp(ppszFilters[ii], pszFilterName) == 0)
			return (0);

	if ((ppszFilters[iNumFilters] = SysStrDup(pszFilterName)) == NULL)
		return (ErrGetErrorCode());

	iNumFilters++;

	return (0);

}

static int FilSelectFilters(char const *pszFilterFilePath, char const *pszMode,
			    FilterMsgInfo const &FMI, char **ppszFilters, int iMaxFilters)
{
///////////////////////////////////////////////////////////////////////////////
//  Share lock the filter table file
///////////////////////////////////////////////////////////////////////////////
	char szResLock[SYS_MAX_PATH] = "";
	RLCK_HANDLE hResLock = RLckLockSH(CfgGetBasedPath(pszFilterFilePath, szResLock,
							  sizeof(szResLock)));

	if (hResLock == INVALID_RLCK_HANDLE)
		return (ErrGetErrorCode());

///////////////////////////////////////////////////////////////////////////////
//  Get local and remote IP addresses
///////////////////////////////////////////////////////////////////////////////
	NET_ADDRESS LocalAddr;
	NET_ADDRESS RemoteAddr;

	SysGetAddrAddress(FMI.LocalAddr, LocalAddr);
	SysGetAddrAddress(FMI.RemoteAddr, RemoteAddr);

///////////////////////////////////////////////////////////////////////////////
//  Open the filter database. Fail smootly if the file does not exist
///////////////////////////////////////////////////////////////////////////////
	FILE *pFile = fopen(pszFilterFilePath, "rt");

	if (pFile == NULL) {
		RLckUnlockSH(hResLock);
		return (0);
	}

	int iNumFilters = 0;
	char szLine[FILTER_DB_LINE_MAX] = "";

	while ((iNumFilters < iMaxFilters) &&
	       (MscGetConfigLine(szLine, sizeof(szLine) - 1, pFile) != NULL)) {
		char **ppszTokens = StrGetTabLineStrings(szLine);

		if (ppszTokens == NULL)
			continue;

		int iFieldsCount = StrStringsCount(ppszTokens);

		if ((iFieldsCount >= filMax) &&
		    StrIWildMatch(FMI.szSender, ppszTokens[filSender]) &&
		    StrIWildMatch(FMI.szRecipient, ppszTokens[filRecipient])) {
			AddressFilter AFRemote;
			AddressFilter AFLocal;

			if ((MscLoadAddressFilter(&ppszTokens[filRemoteAddr], 1, AFRemote) == 0)
			    && MscAddressMatch(AFRemote, RemoteAddr) &&
			    (MscLoadAddressFilter(&ppszTokens[filLocalAddr], 1, AFLocal) == 0) &&
			    MscAddressMatch(AFLocal, LocalAddr)) {

				FilAddFilter(ppszFilters, iNumFilters, ppszTokens[filFileName]);

			}
		}

		StrFreeStrings(ppszTokens);
	}

	fclose(pFile);

	RLckUnlockSH(hResLock);

	return (iNumFilters);

}

static void FilFreeFilters(char **ppszFilters, int iNumFilters)
{

	for (iNumFilters--; iNumFilters >= 0; iNumFilters--)
		SysFree(ppszFilters[iNumFilters]);

}

static int FilGetFilterPath(char const *pszFileName, char *pszFilePath, int iMaxPath)
{

	char szMailRootPath[SYS_MAX_PATH] = "";

	CfgGetRootPath(szMailRootPath, sizeof(szMailRootPath));

	SysSNPrintf(pszFilePath, iMaxPath - 1, "%s%s%s%s",
		    szMailRootPath, FILTER_STORAGE_DIR, SYS_SLASH_STR, pszFileName);

	return (0);

}

int FilExecPreParse(FilterExecCtx *pCtx, char **ppszPEError)
{

	int i;
	char **ppszEToks;
	char const *pszEx = pCtx->pToks->ppszCmdTokens[0];

	if ((pCtx->pToks->iTokenCount < 1) || (*pszEx != '!'))
		return (0);

	pCtx->pToks->iTokenCount--;
	pCtx->pToks->ppszCmdTokens++;

	if ((ppszEToks = StrTokenize(pszEx + 1, ",")) == NULL)
		return (ErrGetErrorCode());
	for (i = 0; ppszEToks[i] != NULL; i++) {
		char *pszVar = ppszEToks[i], *pszVal;

		if ((pszVal = strchr(pszVar, '=')) != NULL)
			*pszVal++ = '\0';
		if (strcmp(pszVar, "aex") == 0) {
			if (!IsEmptyString(pCtx->pszAuthName)) {
				StrFreeStrings(ppszEToks);

				*ppszPEError = SysStrDup("EXCL");

				return (-1);
			}
		}
	}
	StrFreeStrings(ppszEToks);

	return (0);

}

static int FilPreExec(FilterMsgInfo const &FMI, FilterTokens *pToks, char **ppszPEError)
{

	FilterExecCtx FCtx;

	FCtx.pToks = pToks;
	FCtx.pszAuthName = FMI.szAuthName;

	return (FilExecPreParse(&FCtx, ppszPEError));

}

static int FilApplyFilter(char const *pszFilterPath, SPLF_HANDLE hFSpool,
			  QUEUE_HANDLE hQueue, QMSG_HANDLE hMessage, FilterMsgInfo const &FMI,
			  char const *pszType)
{
///////////////////////////////////////////////////////////////////////////////
//  Share lock the filter file
///////////////////////////////////////////////////////////////////////////////
	char szResLock[SYS_MAX_PATH] = "";
	RLCK_HANDLE hResLock = RLckLockSH(CfgGetBasedPath(pszFilterPath, szResLock,
							  sizeof(szResLock)));

	if (hResLock == INVALID_RLCK_HANDLE)
		return (ErrGetErrorCode());

///////////////////////////////////////////////////////////////////////////////
//  This should not happen but if it happens we let the message pass through
///////////////////////////////////////////////////////////////////////////////
	FILE *pFiltFile = fopen(pszFilterPath, "rt");

	if (pFiltFile == NULL) {
		RLckUnlockSH(hResLock);
		return (0);
	}
///////////////////////////////////////////////////////////////////////////////
//  Filter this message
///////////////////////////////////////////////////////////////////////////////
	char szFiltLine[FILTER_LINE_MAX] = "";

	while (MscGetConfigLine(szFiltLine, sizeof(szFiltLine) - 1, pFiltFile) != NULL) {
		char **ppszCmdTokens = StrGetTabLineStrings(szFiltLine);

		if (ppszCmdTokens == NULL)
			continue;

		int iFieldsCount = StrStringsCount(ppszCmdTokens);

		if (iFieldsCount < 1) {
			StrFreeStrings(ppszCmdTokens);
			continue;
		}

///////////////////////////////////////////////////////////////////////////////
//  Perform pre-exec filtering (like exec exclude if authenticated, ...)
///////////////////////////////////////////////////////////////////////////////
		char *pszPEError = NULL;
		FilterTokens Toks;

		Toks.ppszCmdTokens = ppszCmdTokens;
		Toks.iTokenCount = iFieldsCount;

		if (FilPreExec(FMI, &Toks, &pszPEError) < 0) {
			if (bFilterLogEnabled)
				FilLogExec(FMI, Toks.ppszCmdTokens, -1,
					   -1, pszType, pszPEError);
			if (pszPEError != NULL)
				SysFree(pszPEError);
			StrFreeStrings(ppszCmdTokens);
			continue;
		}

///////////////////////////////////////////////////////////////////////////////
//  Do filter line macro substitution
///////////////////////////////////////////////////////////////////////////////
		FilFilterMacroSubstitutes(Toks.ppszCmdTokens, hFSpool, FMI);

///////////////////////////////////////////////////////////////////////////////
//  Time to fire the external executable ...
///////////////////////////////////////////////////////////////////////////////
		int iExitCode = -1;
		int iExitFlags = 0;
		int iExecResult = SysExec(Toks.ppszCmdTokens[0], &Toks.ppszCmdTokens[0],
					  iFilterTimeout, FILTER_PRIORITY, &iExitCode);

///////////////////////////////////////////////////////////////////////////////
//  Log the operation, if requested.
///////////////////////////////////////////////////////////////////////////////
		if (bFilterLogEnabled)
			FilLogExec(FMI, Toks.ppszCmdTokens, iExecResult,
				   iExitCode, pszType, NULL);

		if (iExecResult == 0) {
			SysLogMessage(LOG_LEV_MESSAGE,
				      "Filter run: Sender = \"%s\" Recipient = \"%s\" Filter = \"%s\" Retcode = %d\n",
				      FMI.szSender, FMI.szRecipient, Toks.ppszCmdTokens[0],
				      iExitCode);

///////////////////////////////////////////////////////////////////////////////
//  Separate code from flags
///////////////////////////////////////////////////////////////////////////////
			iExitFlags = iExitCode & FILTER_FLAGS_MASK;
			iExitCode &= ~FILTER_FLAGS_MASK;

			if ((iExitCode == FILTER_OUT_EXITCODE) ||
			    (iExitCode == FILTER_OUT_NN_EXITCODE) ||
			    (iExitCode == FILTER_OUT_NNF_EXITCODE)) {
				fclose(pFiltFile);
				RLckUnlockSH(hResLock);

///////////////////////////////////////////////////////////////////////////////
//  Filter out message
///////////////////////////////////////////////////////////////////////////////
				char *pszRejMsg = FilGetFilterRejMessage(FMI.szSpoolFile);

				if (iExitCode == FILTER_OUT_EXITCODE)
					QueUtNotifyPermErrDelivery(hQueue, hMessage, NULL,
								   (pszRejMsg !=
								    NULL) ? pszRejMsg :
								   ErrGetErrorString
								   (ERR_FILTERED_MESSAGE),
								   NULL, true);
				else if (iExitCode == FILTER_OUT_NN_EXITCODE)
					QueCleanupMessage(hQueue, hMessage,
							  !QueUtRemoveSpoolErrors());
				else
					QueCleanupMessage(hQueue, hMessage, false);

				StrFreeStrings(ppszCmdTokens);
				if (pszRejMsg != NULL)
					SysFree(pszRejMsg);

				ErrSetErrorCode(ERR_FILTERED_MESSAGE);
				return (ERR_FILTERED_MESSAGE);
			} else if (iExitCode == FILTER_MODIFY_EXITCODE) {
///////////////////////////////////////////////////////////////////////////////
//  Filter modified the message, we need to reload the spool handle
///////////////////////////////////////////////////////////////////////////////
				if (USmlReloadHandle(hFSpool) < 0) {
					ErrorPush();
					fclose(pFiltFile);
					RLckUnlockSH(hResLock);

					SysLogMessage(LOG_LEV_MESSAGE,
						      "Filter error [ Modified message corrupted ]: Sender = \"%s\" Recipient = \"%s\" (%s)\n",
						      FMI.szSender, FMI.szRecipient,
						      Toks.ppszCmdTokens[0]);

					QueUtErrLogMessage(hQueue, hMessage,
							   "Filter error [ Modified message corrupted ]: Sender = \"%s\" Recipient = \"%s\" (%s)\n",
							   FMI.szSender, FMI.szRecipient,
							   Toks.ppszCmdTokens[0]);

					QueCleanupMessage(hQueue, hMessage, true);
					StrFreeStrings(ppszCmdTokens);

					return (ErrorPop());
				}
			}
		} else {
			SysLogMessage(LOG_LEV_ERROR,
				      "Filter error (%d): Sender = \"%s\" Recipient = \"%s\" Filter = \"%s\"\n",
				      iExecResult, FMI.szSender, FMI.szRecipient,
				      Toks.ppszCmdTokens[0]);

			QueUtErrLogMessage(hQueue, hMessage,
					   "Filter error (%d): Sender = \"%s\" Recipient = \"%s\" Filter = \"%s\"\n",
					   iExecResult, FMI.szSender, FMI.szRecipient,
					   Toks.ppszCmdTokens[0]);
		}

		StrFreeStrings(ppszCmdTokens);

///////////////////////////////////////////////////////////////////////////////
//  Filter list processing break required ?
///////////////////////////////////////////////////////////////////////////////
		if (iExitFlags & FILTER_FLAGS_BREAK) {
			fclose(pFiltFile);
			RLckUnlockSH(hResLock);
			return (1);
		}
	}

	fclose(pFiltFile);
	RLckUnlockSH(hResLock);

	return (0);

}

int FilFilterMessage(SPLF_HANDLE hFSpool, QUEUE_HANDLE hQueue,
		     QMSG_HANDLE hMessage, char const *pszMode)
{
///////////////////////////////////////////////////////////////////////////////
//  Get filter file path and returns immediately if no file is defined
///////////////////////////////////////////////////////////////////////////////
	char szFilterFilePath[SYS_MAX_PATH] = "";

	FilGetFilePath(pszMode, szFilterFilePath, sizeof(szFilterFilePath));

	if (!SysExistFile(szFilterFilePath))
		return (0);

///////////////////////////////////////////////////////////////////////////////
//  Load the message info
///////////////////////////////////////////////////////////////////////////////
	FilterMsgInfo FMI;

	if (FilLoadMsgInfo(hFSpool, FMI) < 0)
		return (ErrGetErrorCode());

///////////////////////////////////////////////////////////////////////////////
//  Select applicable filters
///////////////////////////////////////////////////////////////////////////////
	int iNumFilters;
	char *pszFilters[FILTER_SELECT_MAX];

	if ((iNumFilters = FilSelectFilters(szFilterFilePath, pszMode, FMI, pszFilters,
					    CountOf(pszFilters))) < 0) {
		ErrorPush();
		FilFreeMsgInfo(FMI);
		return (ErrorPop());
	}
///////////////////////////////////////////////////////////////////////////////
//  Sequentially apply each selected filter
///////////////////////////////////////////////////////////////////////////////
	for (int ii = 0; ii < iNumFilters; ii++) {
		int iFilterResult;
		char szFilterPath[SYS_MAX_PATH] = "";

		FilGetFilterPath(pszFilters[ii], szFilterPath, sizeof(szFilterPath));

		if ((iFilterResult =
		     FilApplyFilter(szFilterPath, hFSpool, hQueue, hMessage,
				    FMI, pszMode)) < 0) {
			ErrorPush();
			FilFreeFilters(pszFilters, iNumFilters);
			FilFreeMsgInfo(FMI);
			return (ErrorPop());
		}
///////////////////////////////////////////////////////////////////////////////
//  A return code greater than zero means exit filter processing loop soon
///////////////////////////////////////////////////////////////////////////////
		if (iFilterResult > 0)
			break;
	}

	FilFreeFilters(pszFilters, iNumFilters);
	FilFreeMsgInfo(FMI);

	return (0);

}

static int FilFilterMacroSubstitutes(char **ppszCmdTokens, SPLF_HANDLE hFSpool,
				     FilterMsgInfo const &FMI)
{

	char const *const *ppszInfo = USmlGetInfo(hFSpool);
	char const *pszSMTPDomain = USmlGetSMTPDomain(hFSpool);
	char const *const *ppszFrom = USmlGetMailFrom(hFSpool);
	char const *const *ppszRcpt = USmlGetRcptTo(hFSpool);
	char const *pszSmtpMessageID = USmlGetSmtpMessageID(hFSpool);
	char const *pszMessageID = USmlGetSpoolFile(hFSpool);
	int iFromDomains = StrStringsCount(ppszFrom);
	int iRcptDomains = StrStringsCount(ppszRcpt);
	FileSection FS;

///////////////////////////////////////////////////////////////////////////////
//  This function retrieve the spool file message section and sync the content.
//  This is necessary before passing the file name to external programs
///////////////////////////////////////////////////////////////////////////////
	if (USmlGetMsgFileSection(hFSpool, FS) < 0)
		return (ErrGetErrorCode());

	for (int ii = 0; ppszCmdTokens[ii] != NULL; ii++) {
		if (strcmp(ppszCmdTokens[ii], "@@FROM") == 0) {
			char *pszNewValue =
				SysStrDup((iFromDomains > 0) ? ppszFrom[iFromDomains - 1] : "");

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@RCPT") == 0) {
			char *pszNewValue =
				SysStrDup((iRcptDomains > 0) ? ppszRcpt[iRcptDomains - 1] : "");

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@RFROM") == 0) {
			char *pszNewValue = SysStrDup(FMI.szSender);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@RRCPT") == 0) {
			char *pszNewValue = SysStrDup(FMI.szRecipient);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@FILE") == 0) {
			char *pszNewValue = SysStrDup(FS.szFilePath);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@MSGID") == 0) {
			char *pszNewValue = SysStrDup(pszMessageID);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@MSGREF") == 0) {
			char *pszNewValue = SysStrDup(pszSmtpMessageID);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@LOCALADDR") == 0) {
			char *pszNewValue = SysStrDup(ppszInfo[smiServerAddr]);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@REMOTEADDR") == 0) {
			char *pszNewValue = SysStrDup(ppszInfo[smiClientAddr]);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		} else if (strcmp(ppszCmdTokens[ii], "@@USERAUTH") == 0) {
			char *pszNewValue = SysStrDup(IsEmptyString(FMI.szAuthName) ? "-":
						      FMI.szAuthName);

			if (pszNewValue == NULL)
				return (ErrGetErrorCode());

			SysFree(ppszCmdTokens[ii]);

			ppszCmdTokens[ii] = pszNewValue;
		}

	}

	return (0);

}
