/*
Copyright 2001-2018 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 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 LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

// Mail and Chat Server for BPQ32 Packet Switch
//
//	Utility Routines

#include "bpqmail.h"
#ifdef WIN32
#include "Winspool.h"
#else
#include <sys/time.h>
#endif

#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__)
void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line);

BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen);

BOOL Bells;
BOOL FlashOnBell;		// Flash instead of Beep
BOOL StripLF;

BOOL WarnWrap;
BOOL FlashOnConnect;
BOOL WrapInput;
BOOL CloseWindowOnBye;

RECT ConsoleRect;

BOOL OpenConsole;
BOOL OpenMon;

int reportMailEvents = 0;

FBBFilter * Filters = NULL;

extern struct ConsoleInfo BBSConsole;

extern char LOC[7];

extern BOOL MQTT;

//#define BBSIDLETIME 120
//#define USERIDLETIME 300


#define BBSIDLETIME 900
#define USERIDLETIME 900

#ifdef LINBPQ
extern BPQVECSTRUC ** BPQHOSTVECPTR;
UCHAR * GetLogDirectory();
DllExport int APIENTRY SessionStateNoAck(int stream, int * state);
int RefreshWebMailIndex();
struct PORTCONTROL * GetPortTableEntryFromSlot(int portslot);
int GetPortHardwareType(struct PORTCONTROL *PORT);
int GetNumberofPorts();

#else
__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR;
typedef char * (WINAPI FAR *FARPROCZ)();
typedef int (WINAPI FAR *FARPROCX)();
FARPROCZ pGetLOC;
FARPROCX pRefreshWebMailIndex;

#endif

Dll BOOL APIENTRY APISendAPRSMessage(char * Text, char * ToCall);
VOID APIENTRY md5 (char *arg, unsigned char * checksum);
int APIENTRY GetRaw(int stream, char * msg, int * len, int * count);
void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line);
void FreeSemaphore(struct SEM * Semaphore);
int EncryptPass(char * Pass, char * Encrypt);
VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len);
void DeletetoRecycle(char * FN);
VOID DoImportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context);
VOID DoExportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context);
VOID TidyPrompts();
char * ReadMessageFileEx(struct MsgInfo * MsgRec);
char * APIENTRY GetBPQDirectory();
BOOL SendARQMail(CIRCUIT * conn);
int APIENTRY ChangeSessionIdletime(int Stream, int idletime);
int APIENTRY GetApplNum(int Stream);
VOID FormatTime(char * Time, time_t cTime);
BOOL CheckifPacket(char * Via);
char * APIENTRY GetVersionString();
void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename);
void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename);
int GetCMSHash(char * Challenge, char * Password);
BOOL SendAMPRSMTP(CIRCUIT * conn);
VOID ProcessMCASTLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen);
VOID MCastTimer();
VOID MCastConTimer(ConnectionInfo * conn);
int FindFreeBBSNumber();
VOID DoSetMsgNo(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context);
BOOL ProcessYAPPMessage(CIRCUIT * conn);
void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename);
void YAPPSendData(ConnectionInfo * conn);
VOID CheckBBSNumber(int i);
struct UserInfo * FindAMPR();
VOID SaveInt64Value(config_setting_t * group, char * name, long long value);
VOID SaveIntValue(config_setting_t * group, char * name, int value);
VOID SaveStringValue(config_setting_t * group, char * name, char * value);
char *stristr (char *ch1, char *ch2);
BOOL CheckforMessagetoServer(struct MsgInfo * Msg);
void DoHousekeepingCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context);
BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled);
void ListCategories(ConnectionInfo * conn);
void RebuildNNTPList();
long long GetInt64Value(config_setting_t * group, char * name);
void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len);
int ReformatSyncMessage(CIRCUIT * conn);
char * initMultipartUnpack(char ** Input);
char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg);
int decode_quoted_printable(char *ptr, int len);
void decodeblock( unsigned char in[4], unsigned char out[3]);
int encode_quoted_printable(char *s, char * out, int Len);
int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress);
int APIENTRY ChangeSessionCallsign(int Stream, unsigned char * AXCall);
void SendMessageReadEvent(char * call, struct MsgInfo * Msg);
void SendNewMessageEvent(char * call, struct MsgInfo * Msg);
void MQTTMessageEvent(struct MsgInfo * message);

config_t cfg;
config_setting_t * group;

extern ULONG BBSApplMask;

//static int SEMCLASHES = 0;

char SecureMsg[80] = "";			// CMS Secure Signon Response

int	NumberofStreams;

extern char VersionStringWithBuild[50];

#define MaxSockets 64

extern struct SEM OutputSEM;

extern ConnectionInfo Connections[MaxSockets+1];

extern struct UserInfo ** UserRecPtr;
extern int NumberofUsers;

extern struct UserInfo * BBSChain;					// Chain of users that are BBSes

extern struct MsgInfo ** MsgHddrPtr;
extern int NumberofMessages;

extern int FirstMessageIndextoForward;					// Lowest Message wirh a forward bit set - limits search

extern char UserDatabaseName[MAX_PATH];
extern char UserDatabasePath[MAX_PATH];

extern char MsgDatabasePath[MAX_PATH];
extern char MsgDatabaseName[MAX_PATH];

extern char BIDDatabasePath[MAX_PATH];
extern char BIDDatabaseName[MAX_PATH];

extern char WPDatabasePath[MAX_PATH];
extern char WPDatabaseName[MAX_PATH];

extern char BadWordsPath[MAX_PATH];
extern char BadWordsName[MAX_PATH];

extern char BaseDir[MAX_PATH];
extern char BaseDirRaw[MAX_PATH];			// As set in registry - may contain %NAME%
extern char ProperBaseDir[MAX_PATH];		// BPQ Directory/BPQMailChat


extern char MailDir[MAX_PATH];

extern BIDRec ** BIDRecPtr;
extern int NumberofBIDs;

extern BIDRec ** TempBIDRecPtr;
extern int NumberofTempBIDs;

extern WPRec ** WPRecPtr;
extern int NumberofWPrecs;

extern char ** BadWords;
extern int NumberofBadWords;
extern char * BadFile;

extern int LatestMsg;
extern struct SEM MsgNoSemaphore;					// For locking updates to LatestMsg
extern int HighestBBSNumber;

extern int MaxMsgno;
extern int BidLifetime;
extern int MaxAge;
extern int MaintInterval;
extern int MaintTime;

extern int ProgramErrors;

extern BOOL MonBBS;
extern BOOL MonCHAT;
extern BOOL MonTCP;

BOOL SendNewUserMessage = TRUE;
BOOL AllowAnon = FALSE;
BOOL UserCantKillT = FALSE;

typedef int (WINAPI FAR *FARPROCX)();
FARPROCX pRunEventProgram;
FARPROCX pGetPortFrequency;

int RunEventProgram(char * Program, char * Param);


extern BOOL EventsEnabled;

#define BPQHOSTSTREAMS	64

// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63

extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5];

/* --- G7TAJ PG Server--- */

void run_pg( CIRCUIT * conn, struct UserInfo * user );
void startrun_pgThread( RUNPGARGS_PTR Args );

char * SERVERLIST[256][3];

int NUM_SERVERS = 0;

/*------- G7TAJ END ----------*/


#ifdef LINBPQ
extern BPQVECSTRUC ** BPQHOSTVECPTR;
extern char WL2KModes [54][18];


#else

__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR;


char WL2KModes [54][18] = {
	"Packet 1200", "Packet 2400", "Packet 4800", "Packet 9600", "Packet 19200", "Packet 38400", "High Speed Packet", "", "", "", "",
	"", "Pactor 1", "", "", "Pactor 2", "", "Pactor 3", "", "", "Pactor 4", // 10 - 20
	"Winmor 500", "Winmor 1600", "", "", "", "", "", "", "",				// 21 - 29
	"Robust Packet", "", "", "", "", "", "", "", "", "",					// 30 - 39
	"ARDOP 200", "ARDOP 500", "ARDOP 1000", "ARDOP 2000", "ARDOP 2000 FM", "", "", "", "", "",	// 40 - 49
	"VARA", "VARA FM", "VARA FM WIDE", "VARA 500"};
#endif





FILE * LogHandle[4] = {NULL, NULL, NULL, NULL};

time_t LastLogTime[4] = {0, 0, 0, 0};

char FilesNames[4][100] = {"", "", "", ""};

char * Logs[4] = {"BBS", "CHAT", "TCP", "DEBUG"};

extern struct SEM ConfigSEM;


BOOL OpenLogfile(int Flags)
{
	UCHAR FN[MAX_PATH];
	time_t LT;
	struct tm * tm;

	LT = time(NULL);
	tm = gmtime(&LT);	

	sprintf(FN,"%s/logs/log_%02d%02d%02d_%s.txt", GetLogDirectory(), tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Logs[Flags]);

	LogHandle[Flags] = fopen(FN, "ab");
		
#ifndef WIN32

	if (strcmp(FN, &FilesNames[Flags][0]))
	{
		UCHAR SYMLINK[MAX_PATH];

		sprintf(SYMLINK,"%s/logLatest_%s.txt", GetBPQDirectory(), Logs[Flags]);
		unlink(SYMLINK); 
		strcpy(&FilesNames[Flags][0], FN);
		symlink(FN, SYMLINK);
	}

#endif

	return (LogHandle[Flags] != NULL);
}

typedef  int (WINAPI FAR *FARPROCX)();

extern FARPROCX pDllBPQTRACE;

struct SEM LogSEM = {0, 0};

void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags)
{
	char CRLF[2] = {0x0d,0x0a};
	struct tm * tm;
	char Stamp[20];
	time_t LT;
//	struct _EXCEPTION_POINTERS exinfo;

	// Write to Node BPQTRACE system

	if ((Flags == LOG_BBS || Flags == LOG_DEBUG_X) && MsgLen < 250)
	{
		MESSAGE Monframe;
		memset(&Monframe, 0, sizeof(Monframe));

		Monframe.PORT = 64;	
		Monframe.LENGTH = 12 + MsgLen;
		Monframe.DEST[0] = 1;			// Plain Text Monitor

		memcpy(&Monframe.DEST[1], Msg, MsgLen);
		Monframe.DEST[1 + MsgLen] = 0;

		time(&Monframe.Timestamp);
#ifdef LINBPQ
		GetSemaphore(&Semaphore, 88);
		BPQTRACE(&Monframe, FALSE);
		FreeSemaphore(&Semaphore);
#else
		if (pDllBPQTRACE)
			pDllBPQTRACE(&Monframe, FALSE);
#endif
	}
#ifndef LINBPQ
	__try
	{
#endif



#ifndef LINBPQ

	if (hMonitor)
	{
		if (Flags == LOG_TCP && MonTCP)
		{	
			WritetoMonitorWindow((char *)&Flag, 1);
			WritetoMonitorWindow(Msg, MsgLen);
			WritetoMonitorWindow(CRLF , 1);
		}
		else if (Flags == LOG_CHAT && MonCHAT)
		{	
			WritetoMonitorWindow((char *)&Flag, 1);

			if (conn && conn->Callsign[0])
			{
				char call[20];
				sprintf(call, "%s          ", conn->Callsign);
				WritetoMonitorWindow(call, 10);
			}
			else
				WritetoMonitorWindow("          ", 10);

			WritetoMonitorWindow(Msg, MsgLen);
			if (Msg[MsgLen-1] != '\r')
				WritetoMonitorWindow(CRLF , 1);
		}
		else if (Flags == LOG_BBS  && MonBBS)
		{	
			WritetoMonitorWindow((char *)&Flag, 1);
			if (conn && conn->Callsign[0])
			{
				char call[20];
				sprintf(call, "%s          ", conn->Callsign);
				WritetoMonitorWindow(call, 10);
			}
			else
				WritetoMonitorWindow("          ", 10);

			WritetoMonitorWindow(Msg, MsgLen);
			WritetoMonitorWindow(CRLF , 1);
		}
		else if (Flags == LOG_DEBUG_X)
		{	
			WritetoMonitorWindow((char *)&Flag, 1);
			WritetoMonitorWindow(Msg, MsgLen);
			WritetoMonitorWindow(CRLF , 1);
		}
	}
#endif

	if (Flags == LOG_TCP && !LogTCP)
		return;
	if (Flags == LOG_BBS && !LogBBS)
		return;
	if (Flags == LOG_CHAT && !LogCHAT)
		return;

	GetSemaphore(&LogSEM, 0);

	if (LogHandle[Flags] == NULL)
		OpenLogfile(Flags);

	if (LogHandle[Flags] == NULL) 
	{
		FreeSemaphore(&LogSEM);
		return;
	}
	LT = time(NULL);
	tm = gmtime(&LT);	
	
	sprintf(Stamp,"%02d%02d%02d %02d:%02d:%02d %c",
				tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, Flag);

	fwrite(Stamp, 1, strlen(Stamp), LogHandle[Flags]);

	if (conn && conn->Callsign[0])
	{
		char call[20];
		sprintf(call, "%s          ", conn->Callsign);
		fwrite(call, 1, 10, LogHandle[Flags]);
	}
	else
		fwrite("          ", 1, 10, LogHandle[Flags]);

	fwrite(Msg, 1, MsgLen, LogHandle[Flags]);

	if (Flags == LOG_CHAT && Msg[MsgLen-1] == '\r')
		fwrite(&CRLF[1], 1, 1, LogHandle[Flags]);
	else
		fwrite(CRLF, 1, 2, LogHandle[Flags]);

	// Don't close/reopen logs every time

//	if ((LT - LastLogTime[Flags]) > 60)
	{
		LastLogTime[Flags] = LT;
		fclose(LogHandle[Flags]);
		LogHandle[Flags] = NULL;
	}
	FreeSemaphore(&LogSEM);
	
#ifndef LINBPQ
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
	}
#endif
}

int	CriticalErrorHandler(char * error)
{
	Debugprintf("Critical Error %s", error);
	ProgramErrors = 25;
	CheckProgramErrors();				// Force close
	return 0;
}

BOOL CheckForTooManyErrors(ConnectionInfo * conn)
{
	conn->ErrorCount++;

	if (conn->ErrorCount > 4)
	{
		BBSputs(conn, "Too many errors - closing\r");
		conn->CloseAfterFlush = 20;
		return TRUE;
	}
	return FALSE;
}





VOID __cdecl Debugprintf(const char * format, ...)
{
	char Mess[16384];
	va_list(arglist);
	int Len;

	va_start(arglist, format);
	Len = vsprintf(Mess, format, arglist);
#ifndef LINBPQ
	WriteLogLine(NULL, '!',Mess, Len, LOG_DEBUG_X);
#endif
	//	#ifdef _DEBUG 
	strcat(Mess, "\r\n");
	OutputDebugString(Mess);

//	#endif
	return;
}

VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...)
{
	char Mess[1000];
	va_list(arglist);int Len;

	va_start(arglist, format);
	Len = vsprintf(Mess, format, arglist);
	WriteLogLine(conn, InOut, Mess, Len, LogMode);

	return;
}

struct MsgInfo * GetMsgFromNumber(int msgno)
{
	if (msgno < 1 || msgno > 999999)
		return NULL;
	
	return MsgnotoMsg[msgno];
}

int GetMessageSlotFromMessageNumber(int msgno)
{
	int i;

	for (i=1; i <= NumberofMessages; i++)
	{
		if (MsgHddrPtr[i]->number == msgno)
			return i;
	}

	return 0;
}

struct UserInfo * AllocateUserRecord(char * Call)
{
	struct UserInfo * User = zalloc(sizeof (struct UserInfo));
		
	strcpy(User->Call, Call);
	User->Length = sizeof (struct UserInfo);

	GetSemaphore(&AllocSemaphore, 0);

	UserRecPtr=realloc(UserRecPtr,(++NumberofUsers+1) * sizeof(void *));
	UserRecPtr[NumberofUsers]= User;

	FreeSemaphore(&AllocSemaphore);

	return User;
}

struct MsgInfo * AllocateMsgRecord()
{
	struct MsgInfo * Msg = zalloc(sizeof (struct MsgInfo));

	GetSemaphore(&AllocSemaphore, 0);

	MsgHddrPtr=realloc(MsgHddrPtr,(++NumberofMessages+1) * sizeof(void *));
	MsgHddrPtr[NumberofMessages] = Msg;

	FreeSemaphore(&AllocSemaphore);

	return Msg;
}

BIDRec * AllocateBIDRecord()
{
	BIDRec * BID = zalloc(sizeof (BIDRec));
	
	GetSemaphore(&AllocSemaphore, 0);

	BIDRecPtr = realloc(BIDRecPtr,(++NumberofBIDs+1) * sizeof(void *));
	BIDRecPtr[NumberofBIDs] = BID;

	FreeSemaphore(&AllocSemaphore);

	return BID;
}

BIDRec * AllocateTempBIDRecord()
{
	BIDRec * BID = zalloc(sizeof (BIDRec));

	GetSemaphore(&AllocSemaphore, 0);

	TempBIDRecPtr=realloc(TempBIDRecPtr,(++NumberofTempBIDs+1) * sizeof(void *));
	TempBIDRecPtr[NumberofTempBIDs] = BID;

	FreeSemaphore(&AllocSemaphore);

	return BID;
}

struct UserInfo * LookupCall(char * Call)
{
	struct UserInfo * ptr = NULL;
	int i;

	for (i=1; i <= NumberofUsers; i++)
	{
		ptr = UserRecPtr[i];

		if (_stricmp(ptr->Call, Call) == 0) return ptr;

	}

	return NULL;
}

int GetNetInt(char * Line)
{
	char temp[1024];
	char * ptr = strlop(Line, ',');
	int n = atoi(Line);
	if (ptr == NULL)
		Line[0] = 0;
	else
	{	
		strcpy(temp, ptr);
		strcpy(Line, temp);
	}
	return n;
}

VOID GetUserDatabase()
{
	struct UserInfo UserRec;

	FILE * Handle;
	size_t ReadLen;
	struct UserInfo * user;
	time_t UserLimit = time(NULL) - (UserLifetime * 86400); // Oldest user to keep
	int i;

	// See if user config is in main config

	group = config_lookup (&cfg, "BBSUsers");

	if (group)
	{
		// We have User config in the main config file. so use that

		int index = 0;
		char * stats;
		struct MsgStats * Stats;
		char * ptr, * ptr2;

		config_setting_t * entry =  config_setting_get_elem (group, index++);

		// Initialise a new File

		UserRecPtr = malloc(sizeof(void *));
		UserRecPtr[0] = malloc(sizeof (struct UserInfo));
		memset(UserRecPtr[0], 0, sizeof (struct UserInfo));
		UserRecPtr[0]->Length = sizeof (struct UserInfo);

		NumberofUsers = 0;

		while (entry)
		{
			char call[16];

			// entry->name is call, will have * in front if a call stating woth number

			if (entry->name[0] == '*')
				strcpy(call, &entry->name[1]);
			else
				strcpy(call, entry->name);

			user = AllocateUserRecord(call);

			ptr = entry->value.sval;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->Name, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->Address, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->HomeBBS, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->QRA, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->pass, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->ZIP, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) strcpy(user->CMSPass, ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->lastmsg = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->flags = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->PageLen = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->BBSNumber = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->RMSSSIDBits = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->WebSeqNo = atoi(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) user->TimeLastConnected = atol(ptr);
			ptr = ptr2;

			ptr2 = strlop(ptr, '^');
			if (ptr) Stats = &user->Total;
			stats = ptr;

			if (Stats == NULL)
			{
				NumberofUsers--;
				free(user);
				entry =  config_setting_get_elem (group, index++);
				continue;
			}

			Stats->ConnectsIn = GetNetInt(stats);
			Stats->ConnectsOut = GetNetInt(stats);
			Stats->MsgsReceived[0] = GetNetInt(stats);
			Stats->MsgsReceived[1] = GetNetInt(stats);
			Stats->MsgsReceived[2] = GetNetInt(stats);
			Stats->MsgsReceived[3] = GetNetInt(stats);
			Stats->MsgsSent[0] = GetNetInt(stats);
			Stats->MsgsSent[1] = GetNetInt(stats);
			Stats->MsgsSent[2] = GetNetInt(stats);
			Stats->MsgsSent[3] = GetNetInt(stats);
			Stats->MsgsRejectedIn[0] = GetNetInt(stats);
			Stats->MsgsRejectedIn[1] = GetNetInt(stats);
			Stats->MsgsRejectedIn[2] = GetNetInt(stats);
			Stats->MsgsRejectedIn[3] = GetNetInt(stats);
			Stats->MsgsRejectedOut[0] = GetNetInt(stats);
			Stats->MsgsRejectedOut[1] = GetNetInt(stats);
			Stats->MsgsRejectedOut[2] = GetNetInt(stats);
			Stats->MsgsRejectedOut[3] = GetNetInt(stats);
			Stats->BytesForwardedIn[0] = GetNetInt(stats);
			Stats->BytesForwardedIn[1] = GetNetInt(stats);
			Stats->BytesForwardedIn[2] = GetNetInt(stats);
			Stats->BytesForwardedIn[3] = GetNetInt(stats);
			Stats->BytesForwardedOut[0] = GetNetInt(stats);
			Stats->BytesForwardedOut[1] = GetNetInt(stats);
			Stats->BytesForwardedOut[2] = GetNetInt(stats);
			Stats->BytesForwardedOut[3] = GetNetInt(stats);

			Stats = &user->Last;
			stats = ptr2;

			if (Stats == NULL)
			{
				NumberofUsers--;
				free(user);
				entry =  config_setting_get_elem (group, index++);
				continue;
			}

			Stats->ConnectsIn = GetNetInt(stats);
			Stats->ConnectsOut = GetNetInt(stats);
			Stats->MsgsReceived[0] = GetNetInt(stats);
			Stats->MsgsReceived[1] = GetNetInt(stats);
			Stats->MsgsReceived[2] = GetNetInt(stats);
			Stats->MsgsReceived[3] = GetNetInt(stats);
			Stats->MsgsSent[0] = GetNetInt(stats);
			Stats->MsgsSent[1] = GetNetInt(stats);
			Stats->MsgsSent[2] = GetNetInt(stats);
			Stats->MsgsSent[3] = GetNetInt(stats);
			Stats->MsgsRejectedIn[0] = GetNetInt(stats);
			Stats->MsgsRejectedIn[1] = GetNetInt(stats);
			Stats->MsgsRejectedIn[2] = GetNetInt(stats);
			Stats->MsgsRejectedIn[3] = GetNetInt(stats);
			Stats->MsgsRejectedOut[0] = GetNetInt(stats);
			Stats->MsgsRejectedOut[1] = GetNetInt(stats);
			Stats->MsgsRejectedOut[2] = GetNetInt(stats);
			Stats->MsgsRejectedOut[3] = GetNetInt(stats);
			Stats->BytesForwardedIn[0] = GetNetInt(stats);
			Stats->BytesForwardedIn[1] = GetNetInt(stats);
			Stats->BytesForwardedIn[2] = GetNetInt(stats);
			Stats->BytesForwardedIn[3] = GetNetInt(stats);
			Stats->BytesForwardedOut[0] = GetNetInt(stats);
			Stats->BytesForwardedOut[1] = GetNetInt(stats);
			Stats->BytesForwardedOut[2] = GetNetInt(stats);
			Stats->BytesForwardedOut[3] = GetNetInt(stats);


			if ((user->flags & F_BBS) == 0)		// Not BBS - Check Age
			{
				if (UserLifetime && user->TimeLastConnected)	// Dont delete manually added Users that havent yet connected
				{
					if (user->TimeLastConnected < UserLimit)
					{
						// Too Old - ignore

						NumberofUsers--;
						free(user);
						entry =  config_setting_get_elem (group, index++);
						continue;
					}
				}
			}
			user->Temp = zalloc(sizeof (struct TempUserInfo));

			if (user->lastmsg < 0 || user->lastmsg > LatestMsg)
				user->lastmsg = LatestMsg;


			entry =  config_setting_get_elem (group, index++);
		}
	}
	else
	{
		Handle = fopen(UserDatabasePath, "rb");

		if (Handle == NULL)
		{
			// Initialise a new File

			UserRecPtr=malloc(sizeof(void *));
			UserRecPtr[0]= malloc(sizeof (struct UserInfo));
			memset(UserRecPtr[0], 0, sizeof (struct UserInfo));
			UserRecPtr[0]->Length = sizeof (struct UserInfo);

			NumberofUsers = 0;

			return;
		}


		// Get First Record

		ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle);

		if (ReadLen == 0)
		{
			// Duff file

			memset(&UserRec, 0, sizeof (struct UserInfo));
			UserRec.Length = sizeof (struct UserInfo);
		}
		else
		{
			// See if format has changed

			if (UserRec.Length == 0)
			{
				// Old format without a Length field

				struct OldUserInfo * OldRec = (struct OldUserInfo *)&UserRec;
				int Users = OldRec->ConnectsIn;		// User Count in control record
				char Backup1[MAX_PATH];

				//  Create a backup in case reversion is needed and Reposition to first User record

				fclose(Handle);

				strcpy(Backup1, UserDatabasePath);
				strcat(Backup1, ".oldformat");

				CopyFile(UserDatabasePath, Backup1, FALSE);	 // Copy to .bak

				Handle = fopen(UserDatabasePath, "rb");

				ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle);	// Skip Control Record

				// Set up control record

				UserRecPtr=malloc(sizeof(void *));
				UserRecPtr[0]= malloc(sizeof (struct UserInfo));
				memcpy(UserRecPtr[0], &UserRec,  sizeof (UserRec));
				UserRecPtr[0]->Length = sizeof (UserRec);

				NumberofUsers = 0;

OldNext:

				ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle);

				if (ReadLen > 0)
				{
					if (OldRec->Call[0] < '0')
						goto OldNext;					// Blank record

					user = AllocateUserRecord(OldRec->Call);
					user->Temp = zalloc(sizeof (struct TempUserInfo));

					// Copy info from Old record

					user->lastmsg = OldRec->lastmsg;
					user->Total.ConnectsIn = OldRec->ConnectsIn;
					user->TimeLastConnected = OldRec->TimeLastConnected;
					user->flags = OldRec->flags;
					user->PageLen = OldRec->PageLen;
					user->BBSNumber = OldRec->BBSNumber;
					memcpy(user->Name, OldRec->Name, 18);
					memcpy(user->Address, OldRec->Address, 61);
					user->Total.MsgsReceived[0] = OldRec->MsgsReceived;
					user->Total.MsgsSent[0] = OldRec->MsgsSent;
					user->Total.MsgsRejectedIn[0] = OldRec->MsgsRejectedIn;			// Messages we reject
					user->Total.MsgsRejectedOut[0] = OldRec->MsgsRejectedOut;		// Messages Rejectd by other end
					user->Total.BytesForwardedIn[0] = OldRec->BytesForwardedIn;
					user->Total.BytesForwardedOut[0] = OldRec->BytesForwardedOut;
					user->Total.ConnectsOut = OldRec->ConnectsOut;			// Forwarding Connects Out
					user->RMSSSIDBits = OldRec->RMSSSIDBits;			// SSID's to poll in RMS
					memcpy(user->HomeBBS, OldRec->HomeBBS, 41);
					memcpy(user->QRA, OldRec->QRA, 7);
					memcpy(user->pass, OldRec->pass, 13);
					memcpy(user->ZIP, OldRec->ZIP, 9);

					//	Read any forwarding info, even if not a BBS.
					//	This allows a BBS to be temporarily set as a
					//	normal user without loosing forwarding info

					SetupForwardingStruct(user);

					if (user->flags & F_BBS)
					{
						// Defined as BBS - allocate and initialise forwarding structure

						// Add to BBS Chain;

						user->BBSNext = BBSChain;
						BBSChain = user;

						// Save Highest BBS Number

						if (user->BBSNumber > HighestBBSNumber) HighestBBSNumber = user->BBSNumber;
					}
					goto OldNext;
				}

				SortBBSChain();
				fclose(Handle);	

				return;
			}
		}

		// Set up control record

		UserRecPtr=malloc(sizeof(void *));
		UserRecPtr[0]= malloc(sizeof (struct UserInfo));
		memcpy(UserRecPtr[0], &UserRec,  sizeof (UserRec));
		UserRecPtr[0]->Length = sizeof (UserRec);

		NumberofUsers = 0;

Next:

		ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle);

		if (ReadLen > 0)
		{
			if (UserRec.Call[0] < '0')
				goto Next;					// Blank record

			if (UserRec.TimeLastConnected == 0)
				UserRec.TimeLastConnected = UserRec.xTimeLastConnected;

			if ((UserRec.flags & F_BBS) == 0)		// Not BBS - Check Age
				if (UserLifetime)					// if limit set
					if (UserRec.TimeLastConnected)	// Dont delete manually added Users that havent yet connected
						if (UserRec.TimeLastConnected < UserLimit)
							goto Next;			// Too Old - ignore

			user = AllocateUserRecord(UserRec.Call);
			memcpy(user, &UserRec,  sizeof (UserRec));
			user->Temp = zalloc(sizeof (struct TempUserInfo));

			user->ForwardingInfo = NULL;	// In case left behind on crash
			user->BBSNext = NULL;
			user->POP3Locked = FALSE;

			if (user->lastmsg < 0 || user->lastmsg > LatestMsg)
				user->lastmsg = LatestMsg;

			goto Next;
		}
		fclose(Handle);
	}

	// Setting up BBS struct has been moved until all user record
	//	have been read so we can fix corrupt BBSNUmber

	for (i=1; i <= NumberofUsers; i++)
	{
		user = UserRecPtr[i];

		//	Read any forwarding info, even if not a BBS.
		//	This allows a BBS to be temporarily set as a
		//	normal user without loosing forwarding info

		SetupForwardingStruct(user);

		if (user->flags & F_BBS)
		{
			// Add to BBS Chain;

			if (user->BBSNumber == NBBBS)				// Fix corrupt records
			{
				user->BBSNumber = FindFreeBBSNumber();
				if (user->BBSNumber == 0)
					user->BBSNumber = NBBBS;			// cant really do much else
			}

			user->BBSNext = BBSChain;
			BBSChain = user;

//			Logprintf(LOG_BBS, NULL, '?', "BBS %s BBSNumber %d", user->Call, user->BBSNumber);

			// Save Highest BBS Number

			if (user->BBSNumber > HighestBBSNumber)
				HighestBBSNumber = user->BBSNumber;
		}
	}

	// Check for dulicate BBS numbers
	
	for (i=1; i <= NumberofUsers; i++)
	{
		user = UserRecPtr[i];

		if (user->flags & F_BBS)
		{
			if (user->BBSNumber == 0)
				user->BBSNumber = FindFreeBBSNumber();
			
			CheckBBSNumber(user->BBSNumber);
		}
	}

	SortBBSChain();
}

VOID CopyUserDatabase()
{
	return;			// User config now in main config file
/*
	char Backup1[MAX_PATH];
	char Backup2[MAX_PATH];

	// Keep 4 Generations

	strcpy(Backup2, UserDatabasePath);
	strcat(Backup2, ".bak.3");

	strcpy(Backup1, UserDatabasePath);
	strcat(Backup1, ".bak.2");

	DeleteFile(Backup2);			// Remove old .bak.3
	MoveFile(Backup1, Backup2);		// Move .bak.2 to .bak.3

	strcpy(Backup2, UserDatabasePath);
	strcat(Backup2, ".bak.1");

	MoveFile(Backup2, Backup1);		// Move .bak.1 to .bak.2

	strcpy(Backup1, UserDatabasePath);
	strcat(Backup1, ".bak");

	MoveFile(Backup1, Backup2);		//Move .bak to .bak.1

	CopyFile(UserDatabasePath, Backup1, FALSE);	 // Copy to .bak
*/
}

VOID CopyConfigFile(char * ConfigName)
{
	char Backup1[MAX_PATH];
	char Backup2[MAX_PATH];

	// Keep 4 Generations

	strcpy(Backup2, ConfigName);
	strcat(Backup2, ".bak.3");

	strcpy(Backup1, ConfigName);
	strcat(Backup1, ".bak.2");

	DeleteFile(Backup2);			// Remove old .bak.3
	MoveFile(Backup1, Backup2);		// Move .bak.2 to .bak.3

	strcpy(Backup2, ConfigName);
	strcat(Backup2, ".bak.1");

	MoveFile(Backup2, Backup1);		// Move .bak.1 to .bak.2

	strcpy(Backup1, ConfigName);
	strcat(Backup1, ".bak");

	MoveFile(Backup1, Backup2);		// Move .bak to .bak.1

	CopyFile(ConfigName, Backup1, FALSE);	// Copy to .bak
}



VOID SaveUserDatabase()
{
	SaveConfig(ConfigName);			// User config is now in main config file
	GetConfig(ConfigName);

/*
	FILE * Handle;
	size_t WriteLen;
	int i;

	Handle = fopen(UserDatabasePath, "wb");

	UserRecPtr[0]->Total.ConnectsIn = NumberofUsers;

	for (i=0; i <= NumberofUsers; i++)
	{
		WriteLen = fwrite(UserRecPtr[i], 1, (int)sizeof (struct UserInfo), Handle);
	}

	fclose(Handle);
*/
	return;
}

VOID GetMessageDatabase()
{
	struct MsgInfo MsgRec;
	FILE * Handle;
	size_t ReadLen;
	struct MsgInfo * Msg;
	char * MsgBytes;
	int FileRecsize = sizeof(struct MsgInfo);	// May be changed if reformating
	BOOL Reformatting = FALSE;
	char HEX[3] = "";
	int n;

	// See if Message Database is in main config

	group = config_lookup (&cfg, "MSGS");

//	group = 0;

	if (group)
	{
		// We have User config in the main config file. so use that

		int index = 0;
		char * ptr, * ptr2;
		config_setting_t * entry =  config_setting_get_elem (group, index++);

		// Initialise a new File

		MsgHddrPtr=malloc(sizeof(void *));
		MsgHddrPtr[0]= zalloc(sizeof (MsgRec));
		NumberofMessages = 0;
		MsgHddrPtr[0]->status = 2;

		if (entry)
		{
			// First Record has current message number

			ptr = entry->value.sval;
			ptr2 = strlop(ptr, '|');
			ptr2 = strlop(ptr2, '|');
			if (ptr2)
				LatestMsg = atoi(ptr2);
		}

		entry =  config_setting_get_elem (group, index++);

		while (entry)
		{
			// entry->name is MsgNo with 'R' in front

			ptr = entry->value.sval;
			ptr2 = strlop(ptr, '|');

			memset(&MsgRec, 0, sizeof(struct MsgInfo));

			MsgRec.number = atoi(&entry->name[1]);
			MsgRec.type = ptr[0];

			ptr = ptr2;

			if (ptr == NULL)
			{
				entry =  config_setting_get_elem (group, index++);
				continue;
			}

			ptr2 = strlop(ptr, '|');
			MsgRec.status = ptr[0];

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.length = atoi(ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.datereceived = atol(ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.bbsfrom, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.via, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.from, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.to, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.bid, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.B2Flags = atoi(ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.datecreated = atol(ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.datechanged = atol(ptr);

			ptr = ptr2;
			if (ptr) ptr2 = strlop(ptr, '|');

			if (ptr == NULL)
			{
				entry =  config_setting_get_elem (group, index++);
				continue;
			}

			if (ptr[0])
			{
				char String[50] = "00000000000000000000";
				String[20] = 0;
				memcpy(String, ptr, strlen(ptr));
				for (n = 0; n < NBMASK; n++)
				{
					memcpy(HEX, &String[n * 2], 2);
					MsgRec.fbbs[n] = (UCHAR)strtol(HEX, 0, 16);
				}
			}

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');

			if (ptr == NULL)
			{
				entry =  config_setting_get_elem (group, index++);
				continue;
			}

			if (ptr[0])
			{
				char String[50] = "00000000000000000000";
				String[20] = 0;
				memcpy(String, ptr, strlen(ptr));
				for (n = 0; n < NBMASK; n++)
				{
					memcpy(HEX, &String[n * 2], 2);
					MsgRec.forw[n] = (UCHAR)strtol(HEX, 0, 16);
				}
			}

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) strcpy(MsgRec.emailfrom, ptr);

			ptr = ptr2;
			ptr2 = strlop(ptr, '|');
			if (ptr) MsgRec.UTF8 = atoi(ptr);

			ptr = ptr2;

			if (ptr) 
			{
				strcpy(MsgRec.title, ptr);

				MsgBytes = ReadMessageFileEx(&MsgRec);

				if (MsgBytes)
				{
					free(MsgBytes);
					Msg = AllocateMsgRecord();
					memcpy(Msg, &MsgRec, sizeof (MsgRec));

					MsgnotoMsg[Msg->number] = Msg;

					// Fix Corrupted NTS Messages

					if (Msg->type == 'N')
						Msg->type = 'T';

					// Look for corrupt FROM address (ending in @)

					strlop(Msg->from, '@');

					BuildNNTPList(Msg);				// Build NNTP Groups list

					// If any forward bits are set, increment count on corresponding BBS record.

					if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
					{
						if (FirstMessageIndextoForward == 0)
							FirstMessageIndextoForward = NumberofMessages;			// limit search
					}
				}
			}
			entry =  config_setting_get_elem (group, index++);
		}

		if (FirstMessageIndextoForward == 0)
			FirstMessageIndextoForward = NumberofMessages;			// limit search

		return;
	}

	Handle = fopen(MsgDatabasePath, "rb");

	if (Handle == NULL)
	{
		// Initialise a new File

		MsgHddrPtr=malloc(sizeof(void *));
		MsgHddrPtr[0]= zalloc(sizeof (MsgRec));
		NumberofMessages = 0;
		MsgHddrPtr[0]->status = 2;

		return;
	}

	// Get First Record
		
	ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); 

	if (ReadLen == 0)
	{
		// Duff file

		memset(&MsgRec, 0, sizeof (MsgRec));
		MsgRec.status = 2;
	}

	// Set up control record

	MsgHddrPtr=malloc(sizeof(void *));
	MsgHddrPtr[0]= malloc(sizeof (MsgRec));
	memcpy(MsgHddrPtr[0], &MsgRec,  sizeof (MsgRec));

	LatestMsg=MsgHddrPtr[0]->length;

	NumberofMessages = 0;

	if (MsgRec.status == 1)		// Used as file format version
								// 0 = original, 1 = Extra email from addr, 2 = More BBS's.
	{
		char Backup1[MAX_PATH];

			//  Create a backup in case reversion is needed and Reposition to first User record

			fclose(Handle);

			strcpy(Backup1, MsgDatabasePath);
			strcat(Backup1, ".oldformat");

			CopyFile(MsgDatabasePath, Backup1, FALSE);	 // Copy to .oldformat

			Handle = fopen(MsgDatabasePath, "rb");

			FileRecsize = sizeof(struct OldMsgInfo);
			
			ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); 

			MsgHddrPtr[0]->status = 2;
	}

Next: 

	ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); 

	if (ReadLen > 0)
	{
		// Validate Header

		if (FileRecsize == sizeof(struct MsgInfo))
		{
			if (MsgRec.type == 0 || MsgRec.number == 0)
				goto Next;

			MsgBytes = ReadMessageFileEx(&MsgRec);

			if (MsgBytes)
			{
	//			MsgRec.length = strlen(MsgBytes);
				free(MsgBytes);
			}
			else
				goto Next;

			Msg = AllocateMsgRecord();

			memcpy(Msg, &MsgRec, +sizeof (MsgRec));
		}
		else
		{
			// Resizing - record from file is an OldRecInfo
			
			struct OldMsgInfo * OldMessage = (struct OldMsgInfo *) &MsgRec;

			if (OldMessage->type == 0)
				goto Next;

			if (OldMessage->number > 99999 || OldMessage->number < 1)
				goto Next;

			Msg = AllocateMsgRecord();


			Msg->B2Flags = OldMessage->B2Flags;
			memcpy(Msg->bbsfrom, OldMessage->bbsfrom, 7);
			memcpy(Msg->bid, OldMessage->bid, 13);
			Msg->datechanged = OldMessage->datechanged;
			Msg->datecreated = OldMessage->datecreated;
			Msg->datereceived = OldMessage->datereceived;
			memcpy(Msg->emailfrom, OldMessage->emailfrom, 41);
			memcpy(Msg->fbbs , OldMessage->fbbs, 10);
			memcpy(Msg->forw , OldMessage->forw, 10);
			memcpy(Msg->from, OldMessage->from, 7);
			Msg->length = OldMessage->length;
			Msg->nntpnum = OldMessage->nntpnum;
			Msg->number = OldMessage->number;
			Msg->status = OldMessage->status;
			memcpy(Msg->title, OldMessage->title, 61);
			memcpy(Msg->to, OldMessage->to, 7);
			Msg->type = OldMessage->type;
			memcpy(Msg->via, OldMessage->via, 41);
		}

		MsgnotoMsg[Msg->number] = Msg;

		// Fix Corrupted NTS Messages

		if (Msg->type == 'N')
			Msg->type = 'T';

		// Look for corrupt FROM address (ending in @)

		strlop(Msg->from, '@');

		// Move Dates if first run with new format

		if (Msg->datecreated == 0)
			Msg->datecreated = Msg->xdatecreated;

		if (Msg->datereceived == 0)
			Msg->datereceived = Msg->xdatereceived;

		if (Msg->datechanged == 0)
			Msg->datechanged = Msg->xdatechanged;

		BuildNNTPList(Msg);				// Build NNTP Groups list

		Msg->Locked = 0;				// In case left locked
		Msg->Defered = 0;				// In case left set.

		// If any forward bits are set, increment count on corresponding BBS record.

		if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
		{
			if (FirstMessageIndextoForward == 0)
				FirstMessageIndextoForward = NumberofMessages;			// limit search
		}

		goto Next;
	}

	if (FirstMessageIndextoForward == 0)
		FirstMessageIndextoForward = NumberofMessages;			// limit search

	fclose(Handle);
}

VOID CopyMessageDatabase()
{
	char Backup1[MAX_PATH];
	char Backup2[MAX_PATH];

//	return;

	// Keep 4 Generations

	strcpy(Backup2, MsgDatabasePath);
	strcat(Backup2, ".bak.3");

	strcpy(Backup1, MsgDatabasePath);
	strcat(Backup1, ".bak.2");

	DeleteFile(Backup2);			// Remove old .bak.3
	MoveFile(Backup1, Backup2);		// Move .bak.2 to .bak.3

	strcpy(Backup2, MsgDatabasePath);
	strcat(Backup2, ".bak.1");

	MoveFile(Backup2, Backup1);		// Move .bak.1 to .bak.2

	strcpy(Backup1, MsgDatabasePath);
	strcat(Backup1, ".bak");

	MoveFile(Backup1, Backup2);		//Move .bak to .bak.1

	strcpy(Backup2, MsgDatabasePath);
	strcat(Backup2, ".bak");

	CopyFile(MsgDatabasePath, Backup2, FALSE);	// Copy to .bak

}

VOID SaveMessageDatabase()
{
	FILE * Handle;
	size_t WriteLen;
	int i;
	char Key[16];
	struct MsgInfo *Msg;
//	char CfgName[MAX_PATH];
	char HEXString1[64];
	char HEXString2[64];
	int n;
//	char * CfgBuffer;
	char Cfg[1024];
//	int CfgLen = 0;
//	FILE * hFile;

//	SaveConfig(ConfigName);		// Message Headers now in main config
//	return;

#ifdef LINBPQ
	RefreshWebMailIndex();
#else
	if (pRefreshWebMailIndex)
		pRefreshWebMailIndex();
#endif

	Handle = fopen(MsgDatabasePath, "wb");

	if (Handle == NULL)
	{
		CriticalErrorHandler("Failed to open message database");
		return;
	}

	MsgHddrPtr[0]->status = 2;
	MsgHddrPtr[0]->number = NumberofMessages;
	MsgHddrPtr[0]->length = LatestMsg;

	for (i=0; i <= NumberofMessages; i++)
	{
		WriteLen = fwrite(MsgHddrPtr[i], 1, sizeof (struct MsgInfo), Handle);
	
		if (WriteLen != sizeof(struct MsgInfo))
		{
			CriticalErrorHandler("Failed to write message database record");
			return;
		}
	}

	if (fclose(Handle) != 0)
		CriticalErrorHandler("Failed to close message database");

	for (i = 1; i <= NumberofMessages; i++)
	{
		Msg = MsgHddrPtr[i];

		for (n = 0; n < NBMASK; n++)
			sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]);

		n = 39;
		while (n >=0 && HEXString1[n] == '0')
			HEXString1[n--] = 0;

		for (n = 0; n < NBMASK; n++)
			sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]);

		n = 39;
		while (n >= 0 && HEXString2[n] == '0')
			HEXString2[n--] = 0;
		
		sprintf(Key, "R%d:\r\n", i);

		n = sprintf(Cfg, "%c|%c|%d|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status,
		Msg->number, Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0],
		&Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, 
		&Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]);
	}

	return;
}

VOID GetBIDDatabase()
{
	BIDRec BIDRec;
	FILE * Handle;
	size_t ReadLen;
	BIDRecP BID;
	int index = 0;
	char * ptr, * ptr2;

	// If BID info is in main config file, use it

	group = config_lookup (&cfg, "BIDS");

	if (group)
	{
		config_setting_t * entry =  config_setting_get_elem (group, index++);
	
		BIDRecPtr=malloc(sizeof(void *));
		BIDRecPtr[0]= malloc(sizeof (BIDRec));
		memset(BIDRecPtr[0], 0, sizeof (BIDRec));
		NumberofBIDs = 0;

		while (entry)
		{
			// entry->name is Bid with 'R' in front

			ptr = entry->value.sval;
			ptr2 = strlop(ptr, '|');

			if (ptr && ptr2)
			{
				BID = AllocateBIDRecord();
				strcpy(BID->BID, &entry->name[1]);
				BID->mode = atoi(ptr);
				BID->u.timestamp = atoi(ptr2);

				if (BID->u.timestamp == 0) 	
					BID->u.timestamp = LOWORD(time(NULL)/86400);

			}
			entry =  config_setting_get_elem (group, index++);
		}
		return;
	}

	Handle = fopen(BIDDatabasePath, "rb");

	if (Handle == NULL)
	{
		// Initialise a new File

		BIDRecPtr=malloc(sizeof(void *));
		BIDRecPtr[0]= malloc(sizeof (BIDRec));
		memset(BIDRecPtr[0], 0, sizeof (BIDRec));
		NumberofBIDs = 0;

		return;
	}


	// Get First Record
		
	ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); 

	if (ReadLen == 0)
	{
		// Duff file

		memset(&BIDRec, 0, sizeof (BIDRec));
	}

	// Set up control record

	BIDRecPtr = malloc(sizeof(void *));
	BIDRecPtr[0] = malloc(sizeof (BIDRec));
	memcpy(BIDRecPtr[0], &BIDRec,  sizeof (BIDRec));

	NumberofBIDs = 0;

Next:

	ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); 

	if (ReadLen > 0)
	{
		BID = AllocateBIDRecord();
		memcpy(BID, &BIDRec,  sizeof (BIDRec));

		if (BID->u.timestamp == 0) 	
			BID->u.timestamp = LOWORD(time(NULL)/86400);

		goto Next;
	}

	fclose(Handle);
}

VOID CopyBIDDatabase()
{
	char Backup[MAX_PATH];

//	return;


	strcpy(Backup, BIDDatabasePath);
	strcat(Backup, ".bak");

	CopyFile(BIDDatabasePath, Backup, FALSE);
}

VOID SaveBIDDatabase()
{
	FILE * Handle;
	size_t WriteLen;
	int i;

//	return;					// Bids are now in main config and are saved when message is saved

	Handle = fopen(BIDDatabasePath, "wb");

	BIDRecPtr[0]->u.msgno = NumberofBIDs;			// First Record has file size

	for (i=0; i <= NumberofBIDs; i++)
	{
		WriteLen = fwrite(BIDRecPtr[i], 1, sizeof (BIDRec), Handle);
	}

	fclose(Handle);

	return;
}

BIDRec * LookupBID(char * BID)
{
	BIDRec * ptr = NULL;
	int i;

	for (i=1; i <= NumberofBIDs; i++)
	{
		ptr = BIDRecPtr[i];

		if (_stricmp(ptr->BID, BID) == 0)
			return ptr;
	}

	return NULL;
}

BIDRec * LookupTempBID(char * BID)
{
	BIDRec * ptr = NULL;
	int i;

	for (i=1; i <= NumberofTempBIDs; i++)
	{
		ptr = TempBIDRecPtr[i];

		if (_stricmp(ptr->BID, BID) == 0) return ptr;
	}

	return NULL;
}

VOID RemoveTempBIDS(CIRCUIT * conn)
{
	// Remove any Temp BID records for conn. Called when connection closes - Msgs will be complete or failed
	
	if (NumberofTempBIDs == 0)
		return;
	else
	{
		BIDRec * ptr = NULL;
		BIDRec ** NewTempBIDRecPtr = zalloc((NumberofTempBIDs+1) * sizeof(void *));
		int i = 0, n;

		GetSemaphore(&AllocSemaphore, 0);

		for (n = 1; n <= NumberofTempBIDs; n++)
		{
			ptr = TempBIDRecPtr[n];

			if (ptr)
			{
				if (ptr->u.conn == conn)
					// Remove this entry 
					free(ptr);
				else
					NewTempBIDRecPtr[++i] = ptr;
			}
		}

		NumberofTempBIDs = i;

		free(TempBIDRecPtr);

		TempBIDRecPtr = NewTempBIDRecPtr;
		FreeSemaphore(&AllocSemaphore);
	}

}

VOID GetBadWordFile()
{
	FILE * Handle;
	DWORD FileSize;
	char * ptr1, * ptr2;
	struct stat STAT;

	if (stat(BadWordsPath, &STAT) == -1)
		return;

	FileSize = STAT.st_size;

	Handle = fopen(BadWordsPath, "rb");

	if (Handle == NULL)
		return;

	// Release old info in case a re-read

	if (BadWords) free(BadWords);
	if (BadFile) free(BadFile);

	BadWords = NULL;
	BadFile = NULL;
	NumberofBadWords = 0;

	BadFile = malloc(FileSize+1);

	fread(BadFile, 1, FileSize, Handle); 

	fclose(Handle);

	BadFile[FileSize]=0;

	_strlwr(BadFile);								// Compares are case-insensitive

	ptr1 = BadFile;

	while (ptr1)
	{
		if (*ptr1 == '\n') ptr1++;

		ptr2 = strtok_s(NULL, "\r\n", &ptr1);
		if (ptr2)
		{
			if (*ptr2 != '#')
			{
				BadWords = realloc(BadWords,(++NumberofBadWords+1) * sizeof(void *));
				BadWords[NumberofBadWords] = ptr2;
			}
		}
		else
			break;
	}
}

BOOL CheckBadWord(char * Word, char * Msg)
{
	char * ptr1 = Msg, * ptr2;
	size_t len = strlen(Word);

	while (*ptr1)					// Stop at end
	{
		ptr2 = strstr(ptr1, Word);

		if (ptr2 == NULL)
			return FALSE;				// OK

		// Only bad if it ia not part of a longer word

		if ((ptr2 == Msg) || !(isalpha(*(ptr2 - 1))))	// No alpha before
			if (!(isalpha(*(ptr2 + len))))			// No alpha after
				return TRUE;					// Bad word
	
		// Keep searching

		ptr1 = ptr2 + len;
	}

	return FALSE;					// OK
}

BOOL CheckBadWords(char * Msg)
{
	char * dupMsg = _strlwr(_strdup(Msg));
	int i;

	for (i = 1; i <= NumberofBadWords; i++)
	{
		if (CheckBadWord(BadWords[i], dupMsg))
		{
			free(dupMsg);
			return TRUE;			// Bad
		}
	}

	free(dupMsg);
	return FALSE;					// OK

}

VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user)
{
	if (user->flags & F_Expert)
		ExpandAndSendMessage(conn, ExpertWelcomeMsg, LOG_BBS);
	else if (conn->NewUser)
		ExpandAndSendMessage(conn, NewWelcomeMsg, LOG_BBS);
	else
		ExpandAndSendMessage(conn, WelcomeMsg, LOG_BBS);

	if (user->HomeBBS[0] == 0 && !DontNeedHomeBBS)
		BBSputs(conn, "Please enter your Home BBS using the Home command.\rYou may also enter your QTH and ZIP/Postcode using qth and zip commands.\r");

//	if (user->flags & F_Temp_B2_BBS)
//		nodeprintf(conn, "%s CMS >\r", BBSName);
//	else
		SendPrompt(conn, user);
}

VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user)
{
	if (user->Temp->ListSuspended)
		return;						// Dont send prompt if pausing a listing
		
	if (user->flags & F_Expert)
		ExpandAndSendMessage(conn, ExpertPrompt, LOG_BBS);
	else if (conn->NewUser)
		ExpandAndSendMessage(conn, NewPrompt, LOG_BBS);
	else
		ExpandAndSendMessage(conn, Prompt, LOG_BBS);

//	if (user->flags & F_Expert)
//		nodeprintf(conn, "%s\r", ExpertPrompt);
//	else if (conn->NewUser)
//		nodeprintf(conn, "%s\r", NewPrompt);
//	else
//		nodeprintf(conn, "%s\r", Prompt);
}



VOID * _zalloc(size_t len)
{
	// ?? malloc and clear

	void * ptr;

	ptr=malloc(len);
	memset(ptr, 0, len);

	return ptr;
}

BOOL isAMPRMsg(char * Addr)
{
	// See if message is addressed to ampr.org and is either
	// for us or we have SendAMPRDirect (ie don't need RMS or SMTP to send it)

	size_t toLen = strlen(Addr);

	if (_memicmp(&Addr[toLen - 8], "ampr.org", 8) == 0)
	{
		// message is for ampr.org

		char toCall[48];
		char * via;

		strcpy(toCall, _strupr(Addr));

		via = strlop(toCall, '@');

		if (_stricmp(via, AMPRDomain) == 0)
		{
			// message is for us.
			
			return TRUE;
		}
		
		if (SendAMPRDirect)
		{
			// We want to send ampr mail direct to host. Queue to BBS AMPR

			if (FindAMPR())
			{
				// We have bbs AMPR
				
				return TRUE;
			}
		}
	}
	return FALSE;
}

struct UserInfo * FindAMPR()
{
	struct UserInfo * bbs;
	
	for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
	{		
		if (strcmp(bbs->Call, "AMPR") == 0)
			return bbs;
	}
	
	return NULL;
}

struct UserInfo * FindRMS()
{
	struct UserInfo * bbs;
	
	for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
	{		
		if (strcmp(bbs->Call, "RMS") == 0)
			return bbs;
	}
	
	return NULL;
}

struct UserInfo * FindBBS(char * Name)
{
	struct UserInfo * bbs;
	
	for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
	{		
		if (strcmp(bbs->Call, Name) == 0)
			return bbs;
	}
	
	return NULL;
}

int CountConnectionsOnPort(int CheckPort)
{
	int n, Count = 0;
	CIRCUIT * conn;
	int port, sesstype, paclen, maxframe, l4window;
	char callsign[11];

	for (n = 0; n < NumberofStreams; n++)
	{
		conn = &Connections[n];
		
		if (conn->Active)
		{
			GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window);
			if (port == CheckPort)
				Count++;
		}
	}

	return Count;
}

/*
REJECT.SYS (\FBB\SYSTEM).

 This file is in SYSTEM-directory. With this file it is possible to reject or
hold certain types or sizes of messages.

The first letter of each valid line specifies the action :

A = Accept	   : Message is accepted without checking other filters
R = Reject     : The message will not be received.
H = Hold       : The message will be received but held until the sysop reviews.
L = Local Hold : Only messages created on this BBS will be held.

 # File for rejecting messages. They are rejected with N-BID:
 #
 # Type, from, @BBS, to, BID, maximum size:
 #
 # * and ? can be used as wildcards (as in MS-DOS)
 #
 R B TOTO  ALL    TATA  * 0
 R B *     *      VENTE * 0
 R B *     VENTE  *     * 0
 H * P1RAT *      *     * 0
 L B *     *      *     * 0
 */


BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type, int Len)
{
	char ** Calls;
	FBBFilter * p = Filters;
	char ToCopy[256];

	if (Type == 'B' && FilterWPBulls && _stricmp(To, "WP") == 0)
		return TRUE;

	if (RejFrom && From)
	{
		Calls = RejFrom;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], From) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (RejTo && To)
	{
		Calls = RejTo;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], To) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (RejAt && ATBBS)
	{
		Calls = RejAt;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], ATBBS) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (RejBID && BID)
	{
		Calls = RejBID;

		while(Calls[0])
		{
			if (Calls[0][0] == '*')
			{
				if (stristr(BID, &Calls[0][1]))	
					return TRUE;
			}
			else
			{
				if (_stricmp(BID, Calls[0]) == 0)	
					return TRUE;
			}

			Calls++;
		}
	}

	// check fbb reject.sys type filters

	strcpy(ToCopy, To);
	_strupr(ToCopy);

	while (p)
	{
		if (p->Action != 'R' && p->Action != 'A')
			goto Continue;

		if (p->Type != Type && p->Type != '*')
			goto Continue;

		// wildcardcompare returns true on a match

		if (wildcardcompare(From, p->From) == 0)
			goto Continue;

		if (p->TO[0] == '!')
		{
			if (wildcardcompare(ToCopy, &p->TO[1]) == 1)
				goto Continue;
		}
		else
		{
			if (wildcardcompare(ToCopy, p->TO) == 0)
				goto Continue;
		}

		if (ATBBS)
		{
			char AtCopy[256];
			
			strcpy(AtCopy, ATBBS);
			_strupr(AtCopy);

			if (wildcardcompare(AtCopy, p->AT) == 0)
				goto Continue;
		}

		if (BID)
			if (wildcardcompare(BID, p->BID) == 0)
				goto Continue;

		if (p->MaxLen && Len < p->MaxLen)
			goto Continue;

		// if type 'A' matches all rules then accept without checking rest

		if (p->Action == 'A')
			return FALSE;

		return TRUE;			// Hold

Continue:
		p = p->Next;
	}

	return FALSE;		// Ok to accept
}

BOOL CheckValidCall(char * From)
{
	unsigned int i;

	if (DontCheckFromCall)
		return TRUE;
	
	if (strcmp(From, "SYSOP") == 0 || strcmp(From, "SYSTEM") == 0 || strcmp(From, "SERVIC") == 0 || 
		strcmp(From, "IMPORT") == 0 || strcmp(From, "SMTP:") == 0 || strcmp(From, "RMS:") == 0)
		return TRUE;

	for (i = 1; i < strlen(From); i++)		// skip first which may also be digit
	{
		if (isdigit(From[i]))
		{
			// Has a digit. Check Last is not digit

			if (isalpha(From[strlen(From) - 1]))
				return TRUE;
		}
	}

	// No digit, return false

	return FALSE;
}

BOOL wildcardcompare(char * Target, char * Match);

BOOL CheckHoldFilters(struct MsgInfo * Msg, char * From, char * To, char * ATBBS, char * BID)
{
	char ** Calls;
	FBBFilter * p = Filters;
	char ToCopy[256];

	if (HoldFrom && From)
	{
		Calls = HoldFrom;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], From) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (HoldTo && To)
	{
		Calls = HoldTo;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], To) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (HoldAt && ATBBS)
	{
		Calls = HoldAt;

		while(Calls[0])
		{
			if (_stricmp(Calls[0], ATBBS) == 0)	
				return TRUE;

			Calls++;
		}
	}

	if (HoldBID && BID)
	{
		Calls = HoldBID;

		while(Calls[0])
		{
			if (Calls[0][0] == '*')
			{
				if (stristr(BID, &Calls[0][1]))	
					return TRUE;
			}
			else
			{
				if (_stricmp(BID, Calls[0]) == 0)	
					return TRUE;
			}

			Calls++;
		}
	}

	// check fbb reject.sys type filters

	strcpy(ToCopy, To);
	_strupr(ToCopy);

	while (p)
	{
		if (p->Action != 'H')
			goto Continue;

		if (p->Type != Msg->type && p->Type != '*')
			goto Continue;

		if (wildcardcompare(Msg->from, p->From) == 0)
			goto Continue;

		if (p->TO[0] == '!')
		{
			if (wildcardcompare(ToCopy, &p->TO[1]) == 1)
				goto Continue;
		}
		else
		{
			if (wildcardcompare(ToCopy, p->TO) == 0)
				goto Continue;
		}
		if (wildcardcompare(Msg->via, p->AT) == 0)
			goto Continue;

		if (wildcardcompare(Msg->bid, p->BID) == 0)
			goto Continue;

		if (p->MaxLen && Msg->length < p->MaxLen)
			goto Continue;

		return TRUE;			// Hold

Continue:
		p = p->Next;
	}

	return FALSE;		// Ok to accept
}

BOOL CheckifLocalRMSUser(char * FullTo)
{
	struct UserInfo * user = LookupCall(FullTo);

	if (user)
		if (user->flags & F_POLLRMS)
			return TRUE;

	return FALSE;
		
}



int check_fwd_bit(char *mask, int bbsnumber)
{
	if (bbsnumber)
		return (mask[(bbsnumber - 1) / 8] & (1 << ((bbsnumber - 1) % 8)));
	else
		return 0;
}


void set_fwd_bit(char *mask, int bbsnumber)
{
	if (bbsnumber)
		mask[(bbsnumber - 1) / 8] |= (1 << ((bbsnumber - 1) % 8));
}


void clear_fwd_bit (char *mask, int bbsnumber)
{
	if (bbsnumber)
		mask[(bbsnumber - 1) / 8] &= (~(1 << ((bbsnumber - 1) % 8)));
}

VOID BBSputs(CIRCUIT * conn, char * buf)
{
	// Sends to user and logs

	WriteLogLine(conn, '>',buf,  (int)strlen(buf) -1, LOG_BBS);

	QueueMsg(conn, buf, (int)strlen(buf));
}

VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...)
{
	char Mess[1000];
	int len;
	va_list(arglist);

	
	va_start(arglist, format);
	len = vsprintf(Mess, format, arglist);

	QueueMsg(conn, Mess, len);

	WriteLogLine(conn, '>',Mess, len-1, LOG_BBS);

	return;
}

// nodeprintfEx add a LF if NEEFLF is set

VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...)
{
	char Mess[1000];
	int len;
	va_list(arglist);

	
	va_start(arglist, format);
	len = vsprintf(Mess, format, arglist);

	QueueMsg(conn, Mess, len);

	WriteLogLine(conn, '>',Mess, len-1, LOG_BBS);

	if (conn->BBSFlags & NEEDLF)
		QueueMsg(conn, "\r", 1);

	return;
}


int compare( const void *arg1, const void *arg2 );

VOID SortBBSChain()
{
	struct UserInfo * user;
	struct UserInfo * users[161]; 
	int i = 0, n;

	// Get array of addresses

	for (user = BBSChain; user; user = user->BBSNext)
	{
		users[i++] = user;
		if (i > 160) break;
	}

	qsort((void *)users, i, sizeof(void *), compare );

	BBSChain = NULL;

	// Rechain (backwards, as entries ate put on front of chain)

	for (n = i-1; n >= 0; n--)
	{
		users[n]->BBSNext = BBSChain;
		BBSChain = users[n];
	}
}

int compare(const void *arg1, const void *arg2)
{
   // Compare Calls. Fortunately call is at start of stuct

   return _stricmp(*(char**)arg1 , *(char**)arg2);
}

int CountMessagesTo(struct UserInfo * user, int * Unread)
{
	int i, Msgs = 0;
	UCHAR * Call = user->Call;

	*Unread = 0;

	for (i = NumberofMessages; i > 0; i--)
	{
		if (MsgHddrPtr[i]->status == 'K')
			continue;

		if (_stricmp(MsgHddrPtr[i]->to, Call) == 0)
		{
			Msgs++;
			if (MsgHddrPtr[i]->status == 'N')
				*Unread = *Unread + 1;
		}
	}
	return(Msgs);
}



// Custimised message handling routines.
/*
	Variables - a subset of those used by FBB

 $C : Number of the next message.
 $I : First name of the connected user.
 $L : Number of the latest message.
 $N : Number of active messages
 $U : Callsign of the connected user.
 $W : Inserts a carriage return.
 $Z : Last message read by the user (L command).
 %X : Number of messages for the user.
 %x : Number of new messages for the user.
*/

VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG)
{
	char NewMessage[10000];
	char * OldP = Msg;
	char * NewP = NewMessage;
	char * ptr, * pptr;
	size_t len;
	char Dollar[] = "$";
	char CR[] = "\r";
	char num[20];
	int Msgs = 0, Unread = 0;

	ptr = strchr(OldP, '$');

	while (ptr)
	{
		len = ptr - OldP;		// Chars before $
		memcpy(NewP, OldP, len);
		NewP += len;

		switch (*++ptr)
		{
		case 'I': // First name of the connected user.

			pptr = conn->UserPointer->Name;
			break;

		case 'L': // Number of the latest message.

			sprintf(num, "%d", LatestMsg);
			pptr = num;
			break;

		case 'N': // Number of active messages.

			sprintf(num, "%d", NumberofMessages);
			pptr = num;
			break;

		case 'U': // Callsign of the connected user.

			pptr = conn->UserPointer->Call;
			break;

		case 'W': // Inserts a carriage return.

			pptr = CR;
			break;

		case 'Z': // Last message read by the user (L command).

			sprintf(num, "%d", conn->UserPointer->lastmsg);
			pptr = num;
			break;

		case 'X': // Number of messages for the user.

			Msgs = CountMessagesTo(conn->UserPointer, &Unread);
			sprintf(num, "%d", Msgs);
			pptr = num;
			break;

		case 'x': // Number of new messages for the user.

			Msgs = CountMessagesTo(conn->UserPointer, &Unread);
			sprintf(num, "%d", Unread);
			pptr = num;
			break;

		case 'F': // Number of new messages to forward to this BBS.

			Msgs = CountMessagestoForward(conn->UserPointer);
			sprintf(num, "%d", Msgs);
			pptr = num;
			break;

		default:

			pptr = Dollar;		// Just Copy $
		}

		len = strlen(pptr);
		memcpy(NewP, pptr, len);
		NewP += len;

		OldP = ++ptr;
		ptr = strchr(OldP, '$');
	}

	strcpy(NewP, OldP);

	len = RemoveLF(NewMessage, (int)strlen(NewMessage));

	WriteLogLine(conn, '>', NewMessage,  (int)len, LOG);
	QueueMsg(conn, NewMessage, (int)len);
}

BOOL isdigits(char * string)
{
	// Returns TRUE id sting is decimal digits

	size_t i, n = strlen(string);
	
	for (i = 0; i < n; i++)
	{
		if (isdigit(string[i]) == FALSE) return FALSE;
	}
	return TRUE;
}

BOOL wildcardcompare(char * Target, char * Match)
{
	// Do a compare with string *string string* *string*

	// Strings should all be UC

	char Pattern[100];
	char * firststar;

	strcpy(Pattern, Match);
	firststar = strchr(Pattern,'*');

	if (firststar)
	{
		size_t Len = strlen(Pattern);

		if (Pattern[0] == '*' && Pattern[Len - 1] == '*')		// * at start and end
		{
			Pattern[Len - 1] = 0;
			return !(strstr(Target, &Pattern[1]) == NULL);
		}
		if (Pattern[0] == '*')		// * at start
		{
			// Compare the last len - 1 chars of Target

			size_t Targlen = strlen(Target);
			size_t Comparelen = Targlen - (Len - 1);

			if (Len == 1)			// Just *
				return TRUE;

			if (Comparelen < 0)	// Too Short
				return FALSE;

			return (memcmp(&Target[Comparelen], &Pattern[1], Len - 1) == 0);
		}

		// Must be * at end - compare first Len-1 char

		return (memcmp(Target, Pattern, Len - 1) == 0);
	}

	// No WildCards - straight strcmp
	return (strcmp(Target, Pattern) == 0);
}

#ifndef LINBPQ

PrintMessage(HDC hDC, struct MsgInfo * Msg);

PrintMessages(HWND hDlg, int Count, int * Indexes)
{
	int i, CurrentMsgIndex;
	char MsgnoText[10];
	int Msgno;
	struct MsgInfo * Msg;
	int Len = MAX_PATH;
	BOOL hResult;
    PRINTDLG pdx = {0};
	HDC hDC;

//	CHOOSEFONT cf; 
	LOGFONT lf; 
    HFONT hFont; 
 
 
    //  Initialize the PRINTDLG structure.

    pdx.lStructSize = sizeof(PRINTDLG);
    pdx.hwndOwner = hWnd;
    pdx.hDevMode = NULL;
    pdx.hDevNames = NULL;
    pdx.hDC = NULL;
    pdx.Flags = PD_RETURNDC | PD_COLLATE;
    pdx.nMinPage = 1;
    pdx.nMaxPage = 1000;
    pdx.nCopies = 1;
    pdx.hInstance = 0;
    pdx.lpPrintTemplateName = NULL;
    
    //  Invoke the Print property sheet.
    
    hResult = PrintDlg(&pdx);

	memset(&lf, 0, sizeof(LOGFONT));

 /*
 
	// Initialize members of the CHOOSEFONT structure.  
 
    cf.lStructSize = sizeof(CHOOSEFONT); 
    cf.hwndOwner = (HWND)NULL; 
    cf.hDC = pdx.hDC; 
    cf.lpLogFont = &lf; 
    cf.iPointSize = 0; 
    cf.Flags = CF_PRINTERFONTS | CF_FIXEDPITCHONLY; 
    cf.rgbColors = RGB(0,0,0); 
    cf.lCustData = 0L; 
    cf.lpfnHook = (LPCFHOOKPROC)NULL; 
    cf.lpTemplateName = (LPSTR)NULL; 
    cf.hInstance = (HINSTANCE) NULL; 
    cf.lpszStyle = (LPSTR)NULL; 
    cf.nFontType = PRINTER_FONTTYPE; 
    cf.nSizeMin = 0; 
    cf.nSizeMax = 0; 
 
    // Display the CHOOSEFONT common-dialog box.  
 
    ChooseFont(&cf); 
 
    // Create a logical font based on the user's  
    // selection and return a handle identifying  
    // that font. 
*/

	lf.lfHeight =  -56;
	lf.lfWeight = 600;
	lf.lfOutPrecision = 3;
	lf.lfClipPrecision = 2;
	lf.lfQuality = 1;
	lf.lfPitchAndFamily = '1';
	strcpy (lf.lfFaceName, "Courier New");

    hFont = CreateFontIndirect(&lf); 

    if (hResult)
    {
        // User clicked the Print button, so use the DC and other information returned in the 
        // PRINTDLG structure to print the document.

		DOCINFO pdi;

		pdi.cbSize = sizeof(DOCINFO);
		pdi.lpszDocName = "BBS Message Print";
		pdi.lpszOutput = NULL;
		pdi.lpszDatatype = "RAW";
		pdi.fwType = 0;

		hDC = pdx.hDC;

		SelectObject(hDC, hFont);

		StartDoc(hDC, &pdi);
		StartPage(hDC);

		for (i = 0; i < Count; i++)
		{
			SendDlgItemMessage(hDlg, 0, LB_GETTEXT, Indexes[i], (LPARAM)(LPCTSTR)&MsgnoText);
	
			Msgno = atoi(MsgnoText);

			for (CurrentMsgIndex = 1; CurrentMsgIndex <= NumberofMessages; CurrentMsgIndex++)
			{
				Msg = MsgHddrPtr[CurrentMsgIndex];
	
				if (Msg->number == Msgno)
				{
					PrintMessage(hDC, Msg);
					break;
				}
			}
		}
		
		EndDoc(hDC);
	}

    if (pdx.hDevMode != NULL) 
        GlobalFree(pdx.hDevMode); 
    if (pdx.hDevNames != NULL) 
        GlobalFree(pdx.hDevNames); 

    if (pdx.hDC != NULL) 
        DeleteDC(pdx.hDC);

	return 0;
}

PrintMessage(HDC hDC, struct MsgInfo * Msg)
{
	int Len = MAX_PATH;
	char * MsgBytes;
	char * Save;
  	int Msglen;
 
	StartPage(hDC);

	Save = MsgBytes = ReadMessageFile(Msg->number);

	Msglen = Msg->length;

	if (MsgBytes)
	{
		char Hddr[1000];
		char FullTo[100];
		int HRes, VRes;
		char * ptr1, * ptr2;
		int LineLen;

		RECT Rect;

		if (_stricmp(Msg->to, "RMS") == 0)
			 sprintf(FullTo, "RMS:%s", Msg->via);
		else
		if (Msg->to[0] == 0)
			sprintf(FullTo, "smtp:%s", Msg->via);
		else
			strcpy(FullTo, Msg->to);


		sprintf(Hddr, "From: %s%s\r\nTo: %s\r\nType/Status: %c%c\r\nDate/Time: %s\r\nBid: %s\r\nTitle: %s\r\n\r\n",
			Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title);


		if (Msg->B2Flags & B2Msg)
		{
			// Remove B2 Headers (up to the File: Line)
			
			char * ptr;
			ptr = strstr(MsgBytes, "Body:");
			if (ptr)
			{
				Msglen = atoi(ptr + 5);
				ptr = strstr(ptr, "\r\n\r\n");
			}
			if (ptr)
				MsgBytes = ptr + 4;
		}

		HRes = GetDeviceCaps(hDC, HORZRES) - 50;
		VRes = GetDeviceCaps(hDC, VERTRES) - 50;

		Rect.top = 50;
		Rect.left = 50;
		Rect.right = HRes;
		Rect.bottom = VRes;

		DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_CALCRECT | DT_WORDBREAK);
		DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_WORDBREAK);

		// process message a line at a time. When page is full, output a page break

		ptr1 = MsgBytes;
		ptr2 = ptr1;

		while (Msglen-- > 0)
		{	
			if (*ptr1++ == '\r')
			{
				// Output this line

				// First check if it will fit

				Rect.top = Rect.bottom;
				Rect.right = HRes;
				Rect.bottom = VRes;

				LineLen = ptr1 - ptr2 - 1;
			
				if (LineLen == 0)		// Blank line
					Rect.bottom = Rect.top + 40;
				else
					DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK);

				if (Rect.bottom >= VRes)
				{
					EndPage(hDC);
					StartPage(hDC);

					Rect.top = 50;
					Rect.bottom = VRes;
					if (LineLen == 0)		// Blank line
						Rect.bottom = Rect.top + 40;
					else
						DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK);
				}

				if (LineLen == 0)		// Blank line
					Rect.bottom = Rect.top + 40;
				else
					DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_WORDBREAK);

				if (*(ptr1) == '\n')
				{
					ptr1++;
					Msglen--;
				}

				ptr2 = ptr1;
			}
		}
	
		free(Save);
	
		EndPage(hDC);

		}
		return 0;
}

#endif


int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup)
{
	char FileName[MAX_PATH] = "Messages.in";
	int Files = 0;
	int WriteLen=0;
	FILE *in;
	CIRCUIT dummyconn;
	struct UserInfo User;	
	int Index = 0;
			
	char Buffer[100000];
	char *buf = Buffer;

	if (FN[0])			// Name supplled
		strcpy(FileName, FN);

	else
	{
#ifndef LINBPQ
		OPENFILENAME Ofn; 

		memset(&Ofn, 0, sizeof(Ofn));
 
		Ofn.lStructSize = sizeof(OPENFILENAME); 
		Ofn.hInstance = hInst;
		Ofn.hwndOwner = MainWnd; 
		Ofn.lpstrFilter = NULL; 
		Ofn.lpstrFile= FileName; 
		Ofn.nMaxFile = sizeof(FileName)/ sizeof(*FileName); 
		Ofn.lpstrFileTitle = NULL; 
		Ofn.nMaxFileTitle = 0; 
		Ofn.lpstrInitialDir = BaseDir; 
		Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; 
		Ofn.lpstrTitle = NULL;//; 

		if (!GetOpenFileName(&Ofn))
			return 0;
#endif
	}

	in = fopen(FileName, "rb");

	if (!(in))
	{
		char msg[500];
		sprintf_s(msg, sizeof(msg), "Failed to open %s", FileName);
		if (conn)
			nodeprintf(conn, "%s\r", msg);
#ifdef WIN32
		else
			if (Nopopup == FALSE)
				MessageBox(NULL, msg, "BPQMailChat", MB_OK);
#endif
		return 0;
	}

	memset(&dummyconn, 0, sizeof(CIRCUIT));
	memset(&User, 0, sizeof(struct UserInfo));

	if (conn == 0)
	{	
		conn = &dummyconn;

		dummyconn.UserPointer = &User;	// Was SYSOPCall, but I think that is wrong.
		strcpy(User.Call, "IMPORT");
		User.flags |= F_EMAIL;
		dummyconn.sysop = TRUE;
		dummyconn.BBSFlags = BBS;
		
		strcpy(dummyconn.Callsign, "IMPORT");
	}

	while(fgets(Buffer, 99999, in))
	{
		// First line should start SP/SB ?ST?

		char * From = NULL;
		char * BID = NULL;
		char * ATBBS = NULL;
		char seps[] = " \t\r";
		struct MsgInfo * Msg;
		char To[100]= "";
		int msglen;
		char * Context;
		char * Arg1, * Cmd;

NextMessage:

		From = NULL;
		BID = NULL;
		ATBBS = NULL;
		To[0]= 0;

		Sleep(100);

		strlop(Buffer, 10);
		strlop(Buffer, 13);				// Remove cr and/or lf

		if (Buffer[0] == 0)			//Blank Line
			continue;

		WriteLogLine(conn, '>', Buffer, (int)strlen(Buffer), LOG_BBS);

		if (dummyconn.sysop == 0)
		{
			nodeprintf(conn, "%s\r", Buffer);
			Flush(conn);
		}
 
		Cmd = strtok_s(Buffer, seps, &Context);

		if (Cmd == NULL)
		{
			fclose(in);
			return Files;
		}

		Arg1 = strtok_s(NULL, seps, &Context);

		if (Arg1 == NULL)
		{
			if (dummyconn.sysop)
				Debugprintf("Bad Import Line %s", Buffer);
			else
				nodeprintf(conn, "Bad Import Line %s\r", Buffer);

			fclose(in);
			return Files;
		}

		strcpy(To, Arg1);

		if (DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID))
		{
			if (CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL))
			{
				Msg = conn->TempMsg;

				// SP is Ok, read message;

				ClearQueue(conn);

				fgets(Buffer, 99999, in);
				strlop(Buffer, 10);
				strlop(Buffer, 13);				// Remove cr and/or lf
				if (strlen(Buffer) > 60)
					Buffer[60] = 0;

				strcpy(Msg->title, Buffer);

				// Read the lines
		
				conn->Flags |= GETTINGMESSAGE;

				Buffer[0] = 0;

				fgets(Buffer, 99999, in);

				while ((conn->Flags & GETTINGMESSAGE) && Buffer[0])
				{
					strlop(Buffer, 10);
					strlop(Buffer, 13);				// Remove cr and/or lf
					msglen = (int)strlen(Buffer);
					Buffer[msglen++] = 13;
					ProcessMsgLine(conn, conn->UserPointer,Buffer, msglen);
	
					Buffer[0] = 0;
					fgets(Buffer, 99999, in);
				}

				// Message completed (or off end of file)

				Files ++;

				ClearQueue(conn);
		
				if (Buffer[0])
					goto NextMessage;		// We have read the SP/SB line;
				else
				{
					fclose(in);
					return Files;
				}
			}
			else
			{
				// Create failed
			
				Flush(conn);
			}
		}

		// Search for next message 

		Buffer[0] = 0;
		fgets(Buffer, 99999, in);

		while (Buffer[0])
		{
			strlop(Buffer, 10);
			strlop(Buffer, 13);				// Remove cr and/or lf

			if (_stricmp(Buffer, "/EX") == 0)
			{
				// Found end

				Buffer[0] = 0;
				fgets(Buffer, 99999, in);
			
				if (dummyconn.sysop)
					ClearQueue(conn);
				else
					Flush(conn);

				if (Buffer[0])
					goto NextMessage;		// We have read the SP/SB line;
			}
			
			Buffer[0] = 0;
			fgets(Buffer, 99999, in);
		}
	}

	fclose(in);

	if (dummyconn.sysop)
		ClearQueue(conn);
	else
		Flush(conn);

	return Files;
}
char * ReadMessageFileEx(struct MsgInfo * MsgRec)
{
	// Sets Message Size from File Size

	int msgno = MsgRec->number;
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	char * MsgBytes;
	struct stat STAT;
 
	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno);

	if (stat(MsgFile, &STAT) == -1)
		return NULL;

	FileSize = STAT.st_size;

	hFile = fopen(MsgFile, "rb");

	if (hFile == NULL)
		return NULL;

	MsgBytes=malloc(FileSize+1);

	fread(MsgBytes, 1, FileSize, hFile); 

	fclose(hFile);

	MsgBytes[FileSize]=0;
	MsgRec->length = FileSize;

	return MsgBytes;
}

char * ReadMessageFile(int msgno)
{
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	char * MsgBytes;
	struct stat STAT;
 
	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno);

	if (stat(MsgFile, &STAT) == -1)
		return NULL;

	FileSize = STAT.st_size;

	hFile = fopen(MsgFile, "rb");

	if (hFile == NULL)
		return NULL;

	MsgBytes = malloc(FileSize + 100);	// A bit of space for alias substitution on B2

	fread(MsgBytes, 1, FileSize, hFile); 

	fclose(hFile);

	MsgBytes[FileSize]=0;

	return MsgBytes;
}


int QueueMsg(ConnectionInfo * conn, char * msg, int len)
{
	// Add Message to queue for this connection

	//	UCHAR * OutputQueue;		// Messages to user
	//	int OutputQueueLength;		// Total Malloc'ed size. Also Put Pointer for next Message
	//	int OutputGetPointer;		// Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again.

	// Create or extend buffer

	GetSemaphore(&OutputSEM, 0);

	conn->OutputQueue=realloc(conn->OutputQueue, conn->OutputQueueLength + len);

	if (conn->OutputQueue == NULL)
	{
		// relloc failed - should never happen, but clean up

		CriticalErrorHandler("realloc failed to expand output queue");
		FreeSemaphore(&OutputSEM);
		return 0;
	}

	memcpy(&conn->OutputQueue[conn->OutputQueueLength], msg, len);
	conn->OutputQueueLength += len;
	FreeSemaphore(&OutputSEM);

	return len;
}

void TrytoSend()
{
	// call Flush on any connected streams with queued data

	ConnectionInfo * conn;
	struct ConsoleInfo * Cons;

	int n;

	for (n = 0; n < NumberofStreams; n++)
	{
		conn = &Connections[n];
		
		if (conn->Active == TRUE)
		{
			Flush(conn);

			// if an FLARQ mail has been sent see if queues have cleared

			if (conn->BBSFlags & YAPPTX)
			{
				YAPPSendData(conn);
			}
			else if (conn->OutputQueue == NULL && (conn->BBSFlags & ARQMAILACK))
			{
				int n = TXCount(conn->BPQStream);		// All Sent and Acked?
	
				if (n == 0)
				{
					struct MsgInfo * Msg = conn->FwdMsg;

					conn->ARQClearCount--;

					if (conn->ARQClearCount <= 0)
					{
						Logprintf(LOG_BBS, conn, '>', "ARQ Send Complete");

						// Mark mail as sent, and look for more
	
						clear_fwd_bit(Msg->fbbs, conn->UserPointer->BBSNumber);
						set_fwd_bit(Msg->forw, conn->UserPointer->BBSNumber);

						//  Only mark as forwarded if sent to all BBSs that should have it
			
						if (memcmp(Msg->fbbs, zeros, NBMASK) == 0)
						{
							Msg->status = 'F';			// Mark as forwarded
							Msg->datechanged=time(NULL);
						}
	
						conn->BBSFlags &= ~ARQMAILACK;
						conn->UserPointer->ForwardingInfo->MsgCount--;

						SaveMessageDatabase();
						SendARQMail(conn);				// See if any more - close if not
					}
				}
				else
					conn->ARQClearCount = 10;
			}
		}
	}
#ifndef LINBPQ
	for (Cons = ConsHeader[0]; Cons; Cons = Cons->next)
	{
		if (Cons->Console)
			Flush(Cons->Console);
	}
#endif
}


void Flush(CIRCUIT * conn)
{
	int tosend, len, sent;
	
	// Try to send data to user. May be stopped by user paging or node flow control

	//	UCHAR * OutputQueue;		// Messages to user
	//	int OutputQueueLength;		// Total Malloc'ed size. Also Put Pointer for next Message
	//	int OutputGetPointer;		// Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again.

	//	BOOL Paging;				// Set if user wants paging
	//	int LinesSent;				// Count when paging
	//	int PageLen;				// Lines per page


	if (conn->OutputQueue == NULL)
	{
		// Nothing to send. If Close after Flush is set, disconnect

		if (conn->CloseAfterFlush)
		{
			conn->CloseAfterFlush--;
			
			if (conn->CloseAfterFlush)
				return;

			Disconnect(conn->BPQStream);
			conn->ErrorCount = 0;
		}

		return;						// Nothing to send
	}
	tosend = conn->OutputQueueLength - conn->OutputGetPointer;

	sent=0;

	while (tosend > 0)
	{
		if (TXCount(conn->BPQStream) > 15)
			return;						// Busy

		if (conn->BBSFlags & SYSOPCHAT)		// Suspend queued output while sysop chatting
			return;

		if (conn->Paging && (conn->LinesSent >= conn->PageLen))
			return;

		if (tosend <= conn->paclen)
			len=tosend;
		else
			len=conn->paclen;

		GetSemaphore(&OutputSEM, 0);

		if (conn->Paging)
		{
			// look for CR chars in message to send. Increment LinesSent, and stop if at limit

			UCHAR * ptr1 = &conn->OutputQueue[conn->OutputGetPointer];
			UCHAR * ptr2;
			int lenleft = len;

			ptr2 = memchr(ptr1, 0x0d, len);

			while (ptr2)
			{
				conn->LinesSent++;
				ptr2++;
				lenleft = len - (int)(ptr2 - ptr1);

				if (conn->LinesSent >= conn->PageLen)
				{
					len = (int)(ptr2 - &conn->OutputQueue[conn->OutputGetPointer]);
					
					SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len);
					conn->OutputGetPointer+=len;
					conn->bytesSent += len;
					tosend-=len;
					SendUnbuffered(conn->BPQStream, "<A>bort, <CR> Continue..>", 25);
					FreeSemaphore(&OutputSEM);
					return;

				}
				ptr2 = memchr(ptr2, 0x0d, lenleft);
			}
		}

		SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len);
		conn->bytesSent += len;

		conn->OutputGetPointer+=len;

		FreeSemaphore(&OutputSEM);

		tosend-=len;	
		sent++;

		if (sent > 15)
			return;
	}

	// All Sent. Free buffers and reset pointers

	conn->LinesSent = 0;

	ClearQueue(conn);
}

VOID ClearQueue(ConnectionInfo * conn)
{
	if (conn->OutputQueue == NULL)
		return;

	GetSemaphore(&OutputSEM, 0);
	
	free(conn->OutputQueue);

	conn->OutputQueue=NULL;
	conn->OutputGetPointer=0;
	conn->OutputQueueLength=0;

	FreeSemaphore(&OutputSEM);
}



VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB)
{
	struct UserInfo * user;

	Msg->status='K';
	Msg->datechanged=time(NULL);

	// Remove any forwarding references

	if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
	{	
		for (user = BBSChain; user; user = user->BBSNext)
		{
			if (check_fwd_bit(Msg->fbbs, user->BBSNumber))
			{
				user->ForwardingInfo->MsgCount--;
				clear_fwd_bit(Msg->fbbs, user->BBSNumber);
			}
		}
	}
	if (SaveDB)
		SaveMessageDatabase();
	RebuildNNTPList();
#ifndef NOMQTT
	if (MQTT)
		MQTTMessageEvent(Msg);
#endif

}

void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context)
{
	int msgno=-1;
	struct MsgInfo * Msg;
	
	while (Arg1)
	{
		msgno = atoi(Arg1);
				
		if (msgno > 100000)
		{
			BBSputs(conn, "Message Number too high\r");
			return;
		}

		Msg = GetMsgFromNumber(msgno);

		if (Msg == NULL)
		{
			nodeprintf(conn, "Message %d not found\r", msgno);
			goto Next;
		}

		if (Msg->type != 'T')
		{
			nodeprintf(conn, "Message %d not an NTS Message\r", msgno);
			goto Next;
		}

		if (Msg->status == 'N')
			nodeprintf(conn, "Warning - Message has status N\r");

		Msg->status = 'D';
		Msg->datechanged=time(NULL);
		SaveMessageDatabase();

		nodeprintf(conn, "Message #%d Flagged as Delivered\r", msgno);
	Next:
		Arg1 = strtok_s(NULL, " \r", &Context);
	}

	return;
}

void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context)
{
	int msgno=-1;
	int i;
	struct MsgInfo * Msg;
	
	// Param is either ALL or a list of numbers

	if (Arg1 == NULL)
	{
		nodeprintf(conn, "No message number\r");
		return;
	}

	if (_stricmp(Arg1, "ALL") == 0)
	{
		for (i=NumberofMessages; i>0; i--)
		{
			Msg = MsgHddrPtr[i];

			if (Msg->status == 'H')
			{
				if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0)
					Msg->status = '$';				// Has forwarding
				else
					Msg->status = 'N';
				
				nodeprintf(conn, "Message #%d Unheld\r", Msg->number);
			}
		}
		return;
	}

	while (Arg1)
	{
		msgno = atoi(Arg1);
		Msg = GetMsgFromNumber(msgno);
		
		if (Msg)
		{
			if (Msg->status == 'H')
			{
				if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0)
					Msg->status = '$';				// Has forwarding
				else
					Msg->status = 'N';

				nodeprintf(conn, "Message #%d Unheld\r", msgno);
			}
			else
			{
				nodeprintf(conn, "Message #%d was not held\r", msgno);
			}
		}
		else
				nodeprintf(conn, "Message #%d not found\r", msgno);
		
		Arg1 = strtok_s(NULL, " \r", &Context);
	}

	return;
}

void DoKillCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context)
{
	int msgno=-1;
	int i;
	struct MsgInfo * Msg;
	
	switch (toupper(Cmd[1]))
	{

	case 0:					// Just K

		while (Arg1)
		{
			msgno = atoi(Arg1);
			KillMessage(conn, user, msgno);

			Arg1 = strtok_s(NULL, " \r", &Context);
		}

		SaveMessageDatabase();
		return;

	case 'M':					// Kill Mine

		for (i=NumberofMessages; i>0; i--)
		{
			Msg = MsgHddrPtr[i];

			if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM))
			{
				if (Msg->type == 'P' && Msg->status == 'Y')
				{
					FlagAsKilled(Msg, FALSE);
					nodeprintf(conn, "Message #%d Killed\r", Msg->number);
				}
			}
		}

		SaveMessageDatabase();
		return;

	case 'H':					// Kill Held

		if (conn->sysop)
		{
			for (i=NumberofMessages; i>0; i--)
			{
				Msg = MsgHddrPtr[i];

				if (Msg->status == 'H')
				{
					FlagAsKilled(Msg, FALSE);
					nodeprintf(conn, "Message #%d Killed\r", Msg->number);
				}
			}
		}
		SaveMessageDatabase();
		return;

	case '>':			// K> - Kill to 

		if (conn->sysop)
		{
			if (Arg1)
				if (KillMessagesTo(conn, user, Arg1) == 0)
				BBSputs(conn, "No Messages found\r");
		
			return;
		}

	case '<':

		if (conn->sysop)
		{
			if (Arg1)
				if (KillMessagesFrom(conn, user, Arg1) == 0)
					BBSputs(conn, "No Messages found\r");

			return;
		}
	}

	nodeprintf(conn, "*** Error: Invalid Kill option %c\r", Cmd[1]);

	return;

}

int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call)
{
	int i, Msgs = 0;
	struct MsgInfo * Msg;

	for (i=NumberofMessages; i>0; i--)
	{
		Msg = MsgHddrPtr[i];
		if (Msg->status != 'K' && _stricmp(Msg->to, Call) == 0)
		{
			Msgs++;
			KillMessage(conn, user, MsgHddrPtr[i]->number);
		}
	}

	SaveMessageDatabase();
	return(Msgs);
}

int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call)
{
	int i, Msgs = 0;
	struct MsgInfo * Msg;


	for (i=NumberofMessages; i>0; i--)
	{
		Msg = MsgHddrPtr[i];
		if (Msg->status != 'K' && _stricmp(Msg->from, Call) == 0)
		{
			Msgs++;
			KillMessage(conn, user, MsgHddrPtr[i]->number);
		}
	}

	SaveMessageDatabase();	
	return(Msgs);
}

BOOL OkToKillMessage(BOOL SYSOP, char * Call, struct MsgInfo * Msg)
{	
	if (SYSOP || (Msg->type == 'T' && UserCantKillT == FALSE))
		return TRUE;
	
	if (Msg->type == 'P')
		if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0))
			return TRUE;

	if (Msg->type == 'B')
		if (_stricmp(Msg->from, Call) == 0)
			return TRUE;

	return FALSE;
}

void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno)
{
	struct MsgInfo * Msg;

	Msg = GetMsgFromNumber(msgno);

	if (Msg == NULL || Msg->status == 'K')
	{
		nodeprintf(conn, "Message %d not found\r", msgno);
		return;
	}

	if (OkToKillMessage(conn->sysop, user->Call, Msg))
	{
		FlagAsKilled(Msg, FALSE);
		nodeprintf(conn, "Message #%d Killed\r", msgno);
	}
	else
		nodeprintf(conn, "Not your message\r");
}


BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp)
{
	char FullFrom[80];
	char FullTo[80];

	strcpy(FullFrom, Msg->from);

	if ((_stricmp(Msg->from, "RMS:") == 0) || (_stricmp(Msg->from, "SMTP:") == 0) || 
		Temp->SendFullFrom || (_stricmp(Msg->emailfrom, "@winlink.org") == 0))
		strcat(FullFrom, Msg->emailfrom);

	if (_stricmp(Msg->to, "RMS") == 0)
	{
		sprintf(FullTo, "RMS:%s", Msg->via);
		nodeprintf(conn, "%-6d %s %c%c   %5d %-7s %-6s %-s\r",
				Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title);
	}
	else

	if (Msg->to[0] == 0 && Msg->via[0] != 0)
	{
		sprintf(FullTo, "smtp:%s", Msg->via);
		nodeprintf(conn, "%-6d %s %c%c   %5d %-7s %-6s %-s\r",
				Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title);
	}

	else
		if (Msg->via[0] != 0)
		{
			char Via[80];
			strcpy(Via, Msg->via);
			strlop(Via, '.');			// Only show first part of via
			nodeprintf(conn, "%-6d %s %c%c   %5d %-7s@%-6s %-6s %-s\r",
				Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, Via, FullFrom, Msg->title);
		}
		else
		nodeprintf(conn, "%-6d %s %c%c   %5d %-7s        %-6s %-s\r",
				Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, FullFrom, Msg->title);
	
	//	if paging, stop two before page lengh. This lets us send the continue prompt, save status
	//	and exit without triggering the system paging code. We can then read a message then resume listing

	if (Temp->ListActive && conn->Paging)
	{
		Temp->LinesSent++;

		if ((Temp->LinesSent + 1) >= conn->PageLen)
		{
			nodeprintf(conn, "<A>bort, <R Msg(s)>, <CR> = Continue..>");
			Temp->LastListedInPagedMode = Msg->number;
			Temp->ListSuspended = TRUE;
			return TRUE;
		}
	}

	return FALSE;
}

void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context)
{
	struct  TempUserInfo * Temp = user->Temp;
	struct MsgInfo * Msg;

	// Allow compound selection, eg LTN or LFP

	// types P N T
	// Options LL LR L< L> L@ LM LC (L* used internally for just L, ie List New
	// Status N Y H F K D 

	// Allowing options in any order complicates paging. May be best to parse options once and restore if paging.

	Temp->ListActive = TRUE;
	Temp->LinesSent = 0;

	if (Resuming)
	{
		// Entered after a paging pause. Selection fields are already set up

		// We have reentered list command after a pause. The next message to list is in Temp->LastListedInPagedMode

//		Start = Temp->LastListedInPagedMode;
		Temp->ListSuspended = FALSE;
	}
	else
	{
		Temp->ListRangeEnd = LatestMsg;
		Temp->ListRangeStart = 1;
		Temp->LLCount = 0;
		Temp->SendFullFrom = 0;
		Temp->ListType = 0;
		Temp->ListStatus = 0;
		Temp->ListSelector = 0;
		Temp->UpdateLatest = 0;
		Temp->LastListParams[0] = 0;
		Temp->IncludeKilled = 1;			// SYSOP include Killed except LM

		//Analyse L params. 

		_strupr(Cmd);

		if (strcmp(Cmd, "LC") == 0)			// List Bull Categories
		{
			ListCategories(conn);
			return;
		}

		// if command is just L or LR start from last listed

		if (Arg1 == NULL)
		{
			if (strcmp(Cmd, "L") == 0 || strcmp(Cmd, "LR") == 0)
			{
				if (LatestMsg == conn->lastmsg)
				{
					BBSputs(conn, "No New Messages\r");
					return;
				}

				Temp->UpdateLatest = 1;
				Temp->ListRangeStart = conn->lastmsg;
			}
		}

		if (strchr(Cmd, 'V'))					// Verbose
			Temp->SendFullFrom = 'V';

		if (strchr(Cmd, 'R'))
			Temp->ListDirn = 'R';
		else
			Temp->ListDirn = '*';				// Default newest first

		Cmd++;					// skip L

		if (strchr(Cmd, 'T'))
			Temp->ListType = 'T';
		else if (strchr(Cmd, 'P'))
			Temp->ListType = 'P';
		else if (strchr(Cmd, 'B'))
			Temp->ListType = 'B';

		if (strchr(Cmd, 'N'))
			Temp->ListStatus = 'N';
		else if (strchr(Cmd, 'Y'))
			Temp->ListStatus = 'Y';
		else if (strchr(Cmd, 'F'))
			Temp->ListStatus = 'F';
		else if (strchr(Cmd, '$'))
			Temp->ListStatus = '$';
		else if (strchr(Cmd, 'H'))
			Temp->ListStatus = 'H';
		else if (strchr(Cmd, 'K'))
			Temp->ListStatus = 'K';
		else if (strchr(Cmd, 'D'))
			Temp->ListStatus = 'D';

		// H or K only by Sysop

		switch (Temp->ListStatus)
		{
		case 'K':
		case 'H':				// List Status

			if (conn->sysop)
				break;
			
			BBSputs(conn, "LH or LK can only be used by SYSOP\r");
			return;
		}

		if (strchr(Cmd, '<'))
			Temp->ListSelector = '<';
		else if (strchr(Cmd, '>'))
			Temp->ListSelector = '>';
		else if (strchr(Cmd, '@'))
			Temp->ListSelector = '@';
		else if (strchr(Cmd, 'M'))
		{
			Temp->ListSelector = 'M';
			Temp->IncludeKilled = FALSE;
		}

		// Param could be single number, number range or call
		
		if (Arg1)
		{
			if (strchr(Cmd, 'L'))		// List Last 
			{
				// Param is number

				if (Arg1)
					Temp->LLCount = atoi(Arg1);
			}
			else
			{
				// Range nnn-nnn or single value or callsign

				char * Arg2, * Arg3, * Range;
				char seps[] = " \t\r";
				UINT From=LatestMsg, To=0;

				Arg2 = strtok_s(NULL, seps, &Context);
				Arg3 = strtok_s(NULL, seps, &Context);

				if (Temp->ListSelector && Temp->ListSelector != 'M')
				{
					// < > or @ - first param is callsign

					strcpy(Temp->LastListParams, Arg1);

					// Just possible number range

					Arg1 = Arg2;
					Arg2 = Arg3;
					Arg3 = strtok_s(NULL, seps, &Context);			
				}

				if (Arg1)
				{
					Range = strchr(Arg1, '-');
			
					// A number could be a Numeric Bull Dest (eg 44)
					// I think this can only resaonably be >

					if (isdigits(Arg1))
						To = From = atoi(Arg1);

					if (Arg2)
						From = atoi(Arg2);
					else
					{
						if (Range)
						{
							Arg3 = strlop(Arg1, '-');

							To = atoi(Arg1);
							
							if (Arg3 && Arg3[0])
								From = atoi(Arg3);
							else
								From = LatestMsg;
						}
					}
					if (From > 100000 || To > 100000)
					{
						BBSputs(conn, "Message Number too high\r");
						return;
					}
					Temp->ListRangeStart = To;
					Temp->ListRangeEnd = From;
				}
			}
		}
	}

	// Run through all messages (either forwards or backwards) and list any that match all selection criteria

	while (1) 
	{
		if (Temp->ListDirn == 'R')
			Msg = GetMsgFromNumber(Temp->ListRangeStart);
		else
			Msg = GetMsgFromNumber(Temp->ListRangeEnd);


		if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop, Temp->IncludeKilled))		// Check if user is allowed to list this message
		{
			// Check filters

			if (Temp->ListStatus && Temp->ListStatus != Msg->status)
				goto skip;

			if (Temp->ListType && Temp->ListType != Msg->type)
				goto skip;

			if (Temp->ListSelector == '<')
				if (_stricmp(Msg->from, Temp->LastListParams) != 0)
					goto skip;

			if (Temp->ListSelector == '>')
				if (_stricmp(Msg->to, Temp->LastListParams) != 0)
					goto skip;

			if (Temp->ListSelector == '@')
				if (_memicmp(Msg->via, Temp->LastListParams, strlen(Temp->LastListParams)) != 0 &&
					(_stricmp(Temp->LastListParams, "SMTP:") != 0 || Msg->to[0] != 0))
						goto skip;

			if (Temp->ListSelector == 'M')
				if (_stricmp(Msg->to, user->Call) != 0 &&
					(_stricmp(Msg->to, "SYSOP") != 0 || ((user->flags & F_SYSOP_IN_LM) == 0)))

					goto skip;

			if (ListMessage(Msg, conn, Temp))
			{
				if (Temp->ListDirn == 'R')
					Temp->ListRangeStart++;
				else
					Temp->ListRangeEnd--;

				return;			// Hit page limit
			}

			if (Temp->LLCount)
			{
				Temp->LLCount--;
				if (Temp->LLCount == 0)
					return;				// LL count reached
			}
skip:;	
		}

		if (Temp->ListRangeStart == Temp->ListRangeEnd)
		{
			// if using L or LR (list new) update last listed field
			
			if (Temp->UpdateLatest)
				conn->lastmsg = LatestMsg;

			return;
		}

		if (Temp->ListDirn == 'R')
			Temp->ListRangeStart++;
		else
			Temp->ListRangeEnd--;

		if (Temp->ListRangeStart > 100000 || Temp->ListRangeEnd < 0)		// Loop protection!
			return;		

	}

/*

	switch (Cmd[0])
	{

	case '*':					// Just L
	case 'R':				// LR = List Reverse

		if (Arg1)
		{
			// Range nnn-nnn or single value

			char * Arg2, * Arg3;
			char * Context;
			char seps[] = " -\t\r";
			UINT From=LatestMsg, To=0;
			char * Range = strchr(Arg1, '-');
			
			Arg2 = strtok_s(Arg1, seps, &Context);
			Arg3 = strtok_s(NULL, seps, &Context);

			if (Arg2)
				To = From = atoi(Arg2);

			if (Arg3)
				From = atoi(Arg3);
			else
				if (Range)
					From = LatestMsg;

			if (From > 100000 || To > 100000)
			{
				BBSputs(conn, "Message Number too high\r");
				return;
			}

			if (Cmd[1] == 'R')
			{
				if (Start)
					To = Start + 1;

				ListMessagesInRangeForwards(conn, user, user->Call, From, To, Temp->SendFullFrom);
			}
			else
			{
				if (Start)
					From = Start - 1;

				ListMessagesInRange(conn, user, user->Call, From, To, Temp->SendFullFrom);
			}
		}
		else

			if (LatestMsg == conn->lastmsg)
				BBSputs(conn, "No New Messages\r");
			else if (Cmd[1] == 'R')
				ListMessagesInRangeForwards(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom);
			else
				ListMessagesInRange(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom);

			conn->lastmsg = LatestMsg;

		return;


	case 'L':				// List Last

		if (Arg1)
		{
			int i = atoi(Arg1);
			int m = NumberofMessages;

			if (Resuming)
				i = Temp->LLCount;
			else
				Temp->LLCount = i;
				
			for (; i>0 && m != 0; i--)
			{
				m = GetUserMsg(m, user->Call, conn->sysop);

				if (m > 0)
				{
					if (Start && MsgHddrPtr[m]->number >= Start)
					{
						m--;
						i++;
						continue;
					}

					Temp->LLCount--;
					
					if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom))
						return;			// Hit page limit
					m--;
				}
			}
		}
		return;

	case 'M':			// LM - List Mine

		if (ListMessagesTo(conn, user, user->Call, SendFullFrom, Start) == 0)
			BBSputs(conn, "No Messages found\r");
		return;

	case '>':			// L> - List to 

		if (Arg1)
			if (ListMessagesTo(conn, user, Arg1, SendFullFrom, Start) == 0)
				BBSputs(conn, "No Messages found\r");
		
		
		return;

	case '<':

		if (Arg1)
			if (ListMessagesFrom(conn, user, Arg1, SendFullFrom, Start) == 0)
				BBSputs(conn, "No Messages found\r");
		
		return;

	case '@':

		if (Arg1)
			if (ListMessagesAT(conn, user, Arg1, SendFullFrom, Start) == 0)
				BBSputs(conn, "No Messages found\r");
		
		return;

	case 'N':
	case 'Y':
	case 'F':
	case '$':
	case 'D':			// Delivered NTS Traffic can be listed by anyone
		{
			int m = NumberofMessages;
				
			while (m > 0)
			{
				m = GetUserMsg(m, user->Call, conn->sysop);

				if (m > 0)
				{
					if (Start && MsgHddrPtr[m]->number >= Start)
					{
						m--;
						continue;
					}
			
					if (Temp->ListType)
					{
						if (MsgHddrPtr[m]->status == Cmd[1] && MsgHddrPtr[m]->type == Temp->ListType)
							if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom))
								return;			// Hit page limit
					}
					else
					{
						if (MsgHddrPtr[m]->status == toupper(Cmd[1]))
							if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom))
								return;			// Hit page limit
					}
					m--;
				}
			}
		}
		return;

	case 'K':
	case 'H':				// List Status

		if (conn->sysop)
		{
			int i, Msgs = Start;

			for (i=NumberofMessages; i>0; i--)
			{
				if (Start && MsgHddrPtr[i]->number >= Start)
					continue;

				if (MsgHddrPtr[i]->status == toupper(Cmd[1]))
				{
					Msgs++;
					if (ListMessage(MsgHddrPtr[i], conn, SendFullFrom))
						return;			// Hit page limit

				}
			}

			if (Msgs == 0)
				BBSputs(conn, "No Messages found\r");
		}
		else
				BBSputs(conn, "LH or LK can only be used by SYSOP\r");

		return;

	case 'C':
	{
		struct NNTPRec * ptr = FirstNNTPRec;
		char Cat[100];
		char NextCat[100];
		int Line = 0;
		int Count;

		while (ptr)
		{
			// if the next name is the same, combine  counts
			
			strcpy(Cat, ptr->NewsGroup);
			strlop(Cat, '.');
			Count = ptr->Count;
		Catloop:
			if (ptr->Next)
			{
				strcpy(NextCat, ptr->Next->NewsGroup);
				strlop(NextCat, '.');
				if (strcmp(Cat, NextCat) == 0)
				{
					ptr = ptr->Next;
					Count += ptr->Count;
					goto Catloop;
				}
			}

			nodeprintf(conn, "%-6s %-3d", Cat, Count);
			Line += 10;
			if (Line > 80)
			{
				Line = 0;
				nodeprintf(conn, "\r");
			}
			
			ptr = ptr->Next;
		}

		if (Line)
			nodeprintf(conn, "\r\r");
		else
			nodeprintf(conn, "\r");

		return;
	}
	}
	
	// Could be P B or T if specified without a status

	switch (Temp->ListType)
	{
	case 'P':
	case 'B':
	case 'T':			// NTS Traffic can be listed by anyone
	{
			int m = NumberofMessages;
							
			while (m > 0)
			{
				m = GetUserMsg(m, user->Call, conn->sysop);

				if (m > 0)
				{
					if (Start && MsgHddrPtr[m]->number >= Start)
					{
						m--;
						continue;
					}

					if (MsgHddrPtr[m]->type == Temp->ListType)
						if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom))
							return;			// Hit page limit
					m--;
				}
			}

			return;
		}
	}

*/	
	nodeprintf(conn, "*** Error: Invalid List option %c\r", Cmd[1]);

}

void ListCategories(ConnectionInfo * conn)
{
	// list bull categories
	struct NNTPRec * ptr = FirstNNTPRec;
	char Cat[100];
	char NextCat[100];
	int Line = 0;
	int Count;

	while (ptr)
	{
		// if the next name is the same, combine  counts

		strcpy(Cat, ptr->NewsGroup);
		strlop(Cat, '.');
		Count = ptr->Count;
Catloop:
		if (ptr->Next)
		{
			strcpy(NextCat, ptr->Next->NewsGroup);
			strlop(NextCat, '.');
			if (strcmp(Cat, NextCat) == 0)
			{
				ptr = ptr->Next;
				Count += ptr->Count;
				goto Catloop;
			}
		}

		nodeprintf(conn, "%-6s %-3d", Cat, Count);
		Line += 10;
		if (Line > 80)
		{
			Line = 0;
			nodeprintf(conn, "\r");
		}

		ptr = ptr->Next;
	}

	if (Line)
		nodeprintf(conn, "\r\r");
	else
		nodeprintf(conn, "\r");

	return;
}

/*
int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start)
{
	int i, Msgs = Start;

	for (i=NumberofMessages; i>0; i--)
	{
		if (MsgHddrPtr[i]->status == 'K')
			continue;

		if (Start && MsgHddrPtr[i]->number >= Start)
			continue;

		if ((_stricmp(MsgHddrPtr[i]->to, Call) == 0) ||
			((conn->sysop) && _stricmp(Call, SYSOPCall) == 0 &&
			_stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0 && (user->flags & F_SYSOP_IN_LM)))
		{
			Msgs++;
			if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom))
				break;			// Hit page limit
		}
	}
	
	return(Msgs);
}

int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start)
{
	int i, Msgs = 0;

	for (i=NumberofMessages; i>0; i--)
	{
		if (MsgHddrPtr[i]->status == 'K')
			continue;

		if (Start && MsgHddrPtr[i]->number >= Start)
			continue;

		if (_stricmp(MsgHddrPtr[i]->from, Call) == 0)
		{
			Msgs++;
			if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom))
				return Msgs;			// Hit page limit

		}
	}
	
	return(Msgs);
}

int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom,int Start)
{
	int i, Msgs = 0;

	for (i=NumberofMessages; i>0; i--)
	{
		if (MsgHddrPtr[i]->status == 'K')
			continue;

		if (Start && MsgHddrPtr[i]->number >= Start)
			continue;

		if (_memicmp(MsgHddrPtr[i]->via, Call, strlen(Call)) == 0 ||
			(_stricmp(Call, "SMTP:") == 0 && MsgHddrPtr[i]->to[0] == 0))
		{
			Msgs++;
			if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom))
				break;			// Hit page limit
		}
	}
	
	return(Msgs);
}
*/
int GetUserMsg(int m, char * Call, BOOL SYSOP)
{
	struct MsgInfo * Msg;
	
	// Get Next (usually backwards) message which should be shown to this user
	//	ie Not Deleted, and not Private unless to or from Call

	do
	{
		Msg=MsgHddrPtr[m];

		if (SYSOP) return m;			// Sysop can list or read anything
		
		if (Msg->status != 'K')
		{
	
			if (Msg->status != 'H')
			{
				if (Msg->type == 'B' || Msg->type == 'T') return m;

				if (Msg->type == 'P')
				{
					if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0))
						return m;
				}
			}
		}

		m--;

	} while (m> 0);

	return 0;
}


BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled)
{
	// Return TRUE if user is allowed to read message
	
	if (Msg->status == 'K' && IncludeKilled == 0)
		return FALSE;
		
	if (SYSOP)
		return TRUE;			// Sysop can list or read anything

	if ((Msg->status != 'K') && (Msg->status != 'H'))
	{
		if (Msg->type == 'B' || Msg->type == 'T') return TRUE;

		if (Msg->type == 'P')
		{
			if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0))
				return TRUE;
		}
	}

	return FALSE;
}
/*
int GetUserMsgForwards(int m, char * Call, BOOL SYSOP)
{
	struct MsgInfo * Msg;
	
	// Get Next (usually backwards) message which should be shown to this user
	//	ie Not Deleted, and not Private unless to or from Call

	do
	{
		Msg=MsgHddrPtr[m];
		
		if (Msg->status != 'K')
		{
			if (SYSOP) return m;			// Sysop can list or read anything

			if (Msg->status != 'H')
			{
				if (Msg->type == 'B' || Msg->type == 'T') return m;

				if (Msg->type == 'P')
				{
					if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0))
						return m;
				}
			}
		}

		m++;

	} while (m <= NumberofMessages);

	return 0;

}


void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom)
{
	int m;
	struct MsgInfo * Msg;

	for (m = Start; m >= End; m--)
	{
		Msg = GetMsgFromNumber(m);

		if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop))
			if (ListMessage(Msg, conn, Temp->SendFullFrom))
				return;			// Hit page limit

	}
}


void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int End, int Start, BOOL SendFullFrom)
{
	int m;
	struct MsgInfo * Msg;

	for (m = Start; m <= End; m++)
	{
		Msg = GetMsgFromNumber(m);

		if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop))
			if (ListMessage(Msg, conn, Temp->SendFullFrom))
				return;			// Hit page limit
	}
}
*/

void DoReadCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context)
{
	int msgno=-1;
	int i;
	struct MsgInfo * Msg;


	switch (toupper(Cmd[1]))
	{
	case 0:					// Just R

		while (Arg1)
		{
			msgno = atoi(Arg1);
			if (msgno > 100000)
			{
				BBSputs(conn, "Message Number too high\r");
				return;
			}

			ReadMessage(conn, user, msgno);
			Arg1 = strtok_s(NULL, " \r", &Context);
		}

		return;

	case 'M':					// Read Mine (Unread Messages)

		if (toupper(Cmd[2]) == 'R')
		{
			for (i = 1; i <= NumberofMessages; i++)
			{
				Msg = MsgHddrPtr[i];

				if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM))
					if (Msg->status == 'N')
						ReadMessage(conn, user, Msg->number);
			}
		}
		else
		{
			for (i = NumberofMessages; i > 0; i--)
			{
				Msg = MsgHddrPtr[i];

				if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM))
					if (Msg->status == 'N')
						ReadMessage(conn, user, Msg->number);
			}
		}

		return;
	}

	nodeprintf(conn, "*** Error: Invalid Read option %c\r", Cmd[1]);

	return;
}

int RemoveLF(char * Message, int len)
{
	// Remove lf chars and nulls

	char * ptr1, * ptr2;

	ptr1 = ptr2 = Message;

	while (len-- > 0)
	{
		while (*ptr1 == 0 && len)
		{
			ptr1++;
			len--;
		}
		
		*ptr2 = *ptr1;

		if (*ptr1 == '\r')
			if (*(ptr1+1) == '\n')
			{
				ptr1++;
				len--;
			}
		ptr1++;
		ptr2++;
	}

	return (int)(ptr2 - Message);
}



int RemoveNulls(char * Message, int len)
{
	// Remove nulls

	char * ptr1, * ptr2;

	ptr1 = ptr2 = Message;

	while (len-- > 0)
	{
		while (*ptr1 == 0 && len)
		{
			ptr1++;
			len--;
		}
		
		*ptr2 = *ptr1;

		ptr1++;
		ptr2++;
	}

	return (int)(ptr2 - Message);
}

void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno)
{
	struct MsgInfo * Msg;
	char * MsgBytes, * Save;
	char FullTo[100];
	int Index = 0;

	Msg = GetMsgFromNumber(msgno);

	if (Msg == NULL)
	{
		nodeprintf(conn, "Message %d not found\r", msgno);
		return;
	}

	if (!CheckUserMsg(Msg, user->Call, conn->sysop, TRUE))
	{
		nodeprintf(conn, "Message %d not for you\r", msgno);
		return;
	}

	if (_stricmp(Msg->to, "RMS") == 0)
		 sprintf(FullTo, "RMS:%s", Msg->via);
	else
	if (Msg->to[0] == 0)
		sprintf(FullTo, "smtp:%s", Msg->via);
	else
		strcpy(FullTo, Msg->to);


	nodeprintf(conn, "From: %s%s\rTo: %s\rType/Status: %c%c\rDate/Time: %s\rBid: %s\rTitle: %s\r\r",
		Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title);

	MsgBytes = Save = ReadMessageFile(msgno);

	if (Msg->type == 'P')
		Index = PMSG;
	else if (Msg->type == 'B')
		Index = BMSG;
	else if (Msg->type == 'T')
		Index = TMSG;

	if (MsgBytes)
	{
		int Length = Msg->length;

		if (Msg->B2Flags & B2Msg)
		{
			char * ptr;
	
			// if message has attachments, display them if plain text

			if (Msg->B2Flags & Attachments)
			{
				char * FileName[100];
				int FileLen[100];
				int Files = 0;
				int BodyLen, NewLen;
				int i;
				char *ptr2;		
				char Msg[512];
				int Len;
		
				ptr = MsgBytes;
	
				Len = sprintf(Msg, "Message has Attachments\r\r");
				QueueMsg(conn, Msg, Len);

				while(*ptr != 13)
				{
					ptr2 = strchr(ptr, 10);	// Find CR

					if (memcmp(ptr, "Body: ", 6) == 0)
					{
						BodyLen = atoi(&ptr[6]);
					}

					if (memcmp(ptr, "File: ", 6) == 0)
					{
						char * ptr1 = strchr(&ptr[6], ' ');	// Find Space

						FileLen[Files] = atoi(&ptr[6]);

						FileName[Files++] = &ptr1[1];
						*(ptr2 - 1) = 0;
					}
				
					ptr = ptr2;
					ptr++;
				}

				ptr += 2;			// Over Blank Line and Separator 

				NewLen = RemoveLF(ptr, BodyLen);

				QueueMsg(conn, ptr, NewLen);		// Display Body

				ptr += BodyLen + 2;		// to first file

				for (i = 0; i < Files; i++)
				{
					char Msg[512];
					int Len, n;
					char * p = ptr;
					char c;

					// Check if message is probably binary

					int BinCount = 0;

					NewLen = RemoveLF(ptr, FileLen[i]);		// Removes LF agter CR but not on its own

					for (n = 0; n < NewLen; n++)
					{
						c = *p;
						
						if (c == 10)
							*p = 13;

						if (c==0 || (c & 128))
							BinCount++;

						p++;

					}

					if (BinCount > NewLen/10)
					{
						// File is probably Binary

						Len = sprintf(Msg, "\rAttachment %s is a binary file\r", FileName[i]);
						QueueMsg(conn, Msg, Len);
					}
					else
					{
						Len = sprintf(Msg, "\rAttachment %s\r\r", FileName[i]);
						QueueMsg(conn, Msg, Len);

						user->Total.MsgsSent[Index] ++;
						user->Total.BytesForwardedOut[Index] += NewLen;

						QueueMsg(conn, ptr, NewLen);
					}
				
					ptr += FileLen[i];
					ptr +=2;				// Over separator
				}
				goto sendEOM;
			}
			
			// Remove B2 Headers (up to the File: Line)
			
			ptr = strstr(MsgBytes, "Body:");

			if (ptr)
			{
				MsgBytes = ptr;
				Length = (int)strlen(ptr);
			}
		}

		// Remove lf chars

		Length = RemoveLF(MsgBytes, Length);

		user->Total.MsgsSent[Index] ++;
		user->Total.BytesForwardedOut[Index] += Length;

		QueueMsg(conn, MsgBytes, Length);

sendEOM:

		free(Save);

		nodeprintf(conn, "\r\r[End of Message #%d from %s%s]\r", msgno, Msg->from, Msg->emailfrom);

		if ((_stricmp(Msg->to, user->Call) == 0) || ((conn->sysop) && (_stricmp(Msg->to, "SYSOP") == 0)))
		{
			if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D'))
			{
				if (Msg->status != 'Y')
				{
					Msg->status = 'Y';
					Msg->datechanged=time(NULL);
					SaveMessageDatabase();
					SendMessageReadEvent(user->Call, Msg);
#ifndef NOMQTT
					if (MQTT)
						MQTTMessageEvent(Msg);
#endif
				}
			}
		}
	}
	else
	{
		nodeprintf(conn, "File for Message %d not found\r", msgno);
	}
}
 struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop)
 {
	int m=NumberofMessages;

	struct MsgInfo * Msg;

	do
	{
		m = GetUserMsg(m, Call, sysop);

		if (m == 0)
			return NULL;

		Msg=MsgHddrPtr[m];

		if (Msg->number == msgno)
			return Msg;

		m--;

	} while (m> 0);

	return NULL;

}


char * ReadInfoFile(char * File)
{
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	char * MsgBytes;
	struct stat STAT;
	char * ptr1 = 0, * ptr2;
 
	sprintf_s(MsgFile, sizeof(MsgFile), "%s/%s", BaseDir, File);

	if (stat(MsgFile, &STAT) == -1)
		return NULL;

	FileSize = STAT.st_size;

	hFile = fopen(MsgFile, "rb");

	if (hFile == NULL)
		return NULL;

	MsgBytes=malloc(FileSize+1);

	fread(MsgBytes, 1, FileSize, hFile); 

	fclose(hFile);

	MsgBytes[FileSize] = 0;

	ptr1 = MsgBytes;

	// Replace LF or CRLF with CR

	// First remove cr from crlf

	while(ptr2 = strstr(ptr1, "\r\n"))
	{
		memmove(ptr2, ptr2 + 1, strlen(ptr2));
	}

	// Now replace lf with cr

	ptr1 = MsgBytes;

	while (*ptr1)
	{
		if (*ptr1 == '\n')
			*(ptr1) = '\r';

		ptr1++;
	}

	return MsgBytes;
}

char * FormatDateAndTime(time_t Datim, BOOL DateOnly)
{
	struct tm *tm;
	static char Date[]="xx-xxx hh:mmZ";

	tm = gmtime(&Datim);
	
	if (tm)
		sprintf_s(Date, sizeof(Date), "%02d-%3s %02d:%02dZ",
					tm->tm_mday, month[tm->tm_mon], tm->tm_hour, tm->tm_min);

	if (DateOnly)
	{
		Date[6]=0;
		return Date;
	}
	
	return Date;
}

BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID);


BOOL DoSendCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context)
{
	// SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY
	
	char * From = NULL;
	char * BID = NULL;
	char * ATBBS = NULL;
	char seps[] = " \t\r";
	struct MsgInfo * OldMsg;
	char OldTitle[62];
	char NewTitle[62];
	char To[100]= "";
	int msgno;

	if (Cmd[1] == 0) Cmd[1] ='P'; // Just S means SP

	switch (toupper(Cmd[1]))
	{
	case 'B':

		if (RefuseBulls)
		{
			nodeprintf(conn, "*** Error: This system doesn't allow sending Bulls\r");
			return FALSE;
		}

		if (user->flags & F_NOBULLS)
		{
			nodeprintf(conn, "*** Error: You are not allowed to send Bulls\r");
			return FALSE;
		}
		

	case 'P':
	case 'T':
				
		if (Arg1 == NULL)
		{
			nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r");
			return FALSE;
		}

		strcpy(To, Arg1);

		if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID))
			return FALSE;

		return CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL);	

	case 'R':
				
		if (Arg1 == NULL)
		{
			nodeprintf(conn, "*** Error: Message Number is missing\r");
			return FALSE;
		}

		msgno = atoi(Arg1);

		if (msgno > 100000)
		{
			BBSputs(conn, "Message Number too high\r");
			return FALSE;
		}

		OldMsg = FindMessage(user->Call, msgno, conn->sysop);

		if (OldMsg == NULL)
		{
			nodeprintf(conn, "Message %d not found\r", msgno);
			return FALSE;
		}

		Arg1=&OldMsg->from[0];

		strcpy(To, Arg1);

		if (_stricmp(Arg1, "SMTP:") == 0 || _stricmp(Arg1, "RMS:") == 0 || OldMsg->emailfrom)
		{
			// SMTP message. Need to get the real sender from the message

			sprintf(To, "%s%s", Arg1, OldMsg->emailfrom);
		}

		if (!DecodeSendParams(conn, "", &From, To, &ATBBS, &BID))
			return FALSE;

		strcpy(OldTitle, OldMsg->title);

		if (strlen(OldTitle) > 57) OldTitle[57] = 0;

		strcpy(NewTitle, "Re:");
		strcat(NewTitle, OldTitle);

		return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle);	

		return TRUE;

	case 'C':
				
		if (Arg1 == NULL)
		{
			nodeprintf(conn, "*** Error: Message Number is missing\r");
			return FALSE;
		}

		msgno = atoi(Arg1);

		if (msgno > 100000)
		{
			BBSputs(conn, "Message Number too high\r");
			return FALSE;
		}

		Arg1 = strtok_s(NULL, seps, &Context);

		if (Arg1 == NULL)
		{
			nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r");
			return FALSE;
		}

		strcpy(To, Arg1);

		if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID))
			return FALSE;
	
		OldMsg = FindMessage(user->Call, msgno, conn->sysop);

		if (OldMsg == NULL)
		{
			nodeprintf(conn, "Message %d not found\r", msgno);
			return FALSE;
		}

		strcpy(OldTitle, OldMsg->title);

		if (strlen(OldTitle) > 56) OldTitle[56] = 0;

		strcpy(NewTitle, "Fwd:");
		strcat(NewTitle, OldTitle);

		conn->CopyBuffer = ReadMessageFile(msgno);

		return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle);	
	}


	nodeprintf(conn, "*** Error: Invalid Send option %c\r", Cmd[1]);

	return FALSE;
}

char * CheckToAddress(CIRCUIT * conn, char * Addr)
{
	// Check one element of Multiple Address

	if (conn == NULL || !(conn->BBSFlags & BBS))
	{
		// if a normal user, check that TO and/or AT are known and warn if not.

		if (_stricmp(Addr, "SYSOP") == 0)
		{
			return _strdup(Addr);
		}

		if (SendBBStoSYSOPCall)
			if (_stricmp(Addr, BBSName) == 0)
				return _strdup(SYSOPCall);
	

		if (strchr(Addr, '@') == 0)
		{
			// No routing, if not a user and not known to forwarding or WP warn

			struct UserInfo * ToUser = LookupCall(Addr);

			if (ToUser)
			{
				// Local User. If Home BBS is specified, use it

				if (ToUser->HomeBBS[0])
				{
					char * NewAddr = malloc(250);
					if (conn)
						nodeprintf(conn, "Address %s - @%s added from HomeBBS\r", Addr, ToUser->HomeBBS);
					sprintf(NewAddr, "%s@%s", Addr, ToUser->HomeBBS);
					return NewAddr;
				}
			}
			{
				WPRecP WP = LookupWP(Addr);

				if (WP)
				{
					char * NewAddr = malloc(250);

					if (conn)
						nodeprintf(conn, "Address %s - @%s added from WP\r", Addr, WP->first_homebbs);
					sprintf(NewAddr, "%s@%s", Addr, WP->first_homebbs);
					return NewAddr;
				}
			}
		}
	}

	// Check SMTP and RMS Addresses

	if ((_memicmp(Addr, "rms:", 4) == 0) || (_memicmp(Addr, "rms/", 4) == 0))
	{
		Addr[3] = ':';			// Replace RMS/ with RMS:
			
		if (conn && !FindRMS())
		{
			nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r");
			return FALSE;
		}
	}
	else if ((_memicmp(Addr, "smtp:", 5) == 0) || (_memicmp(Addr, "smtp/", 5) == 0))
	{
		Addr[4] = ':';			// Replace smpt/ with smtp:

		if (ISP_Gateway_Enabled)
		{
			if (conn && (conn->UserPointer->flags & F_EMAIL) == 0)
			{
				nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r");
				return FALSE;
			}
		}
		else
		{
			if (conn)
				nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r");
			return FALSE;
		}
	}

	return _strdup(Addr);
}


char Winlink[] = "WINLINK.ORG";

BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char *To, char ** ATBBS, char ** BID)
{
	char * ptr;
	char seps[] = " \t\r";
	WPRecP WP;
	char * ToCopy = _strdup(To);
	int Len;

	conn->ToCount = 0;

	// SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY

	// Having trailing ; will mess up parsing multiple addresses, so remove.

	while (To[strlen(To) - 1] == ';')
		To[strlen(To) - 1] = 0;

	if (strchr(Context, ';') || strchr(To, ';'))
	{
		// Multiple Addresses - put address list back together

		char * p;
		
		To[strlen(To)] = ' ';
		Context = To;

		while (p = strchr(Context, ';'))
		{
			// Multiple Addressees

			To = strtok_s(NULL, ";", &Context);
			Len = (int)strlen(To);
			conn->To = realloc(conn->To, (conn->ToCount+1) * sizeof(void *));
			if (conn->To[conn->ToCount] = CheckToAddress(conn, To))
				conn->ToCount++;
		}

		To = strtok_s(NULL, seps, &Context);

		Len = (int)strlen(To);
		conn->To=realloc(conn->To, (conn->ToCount+1) * sizeof(void *));
		if (conn->To[conn->ToCount] = CheckToAddress(conn, To))
			conn->ToCount++;
	}
	else
	{
		// Single Call

		// accept CALL!CALL for source routed message
		
		if (strchr(To, '@') == 0 && strchr(To, '!')) // Bang route without @
		{
			char * bang = strchr(To, '!');
			
			memmove(bang + 1, bang, strlen(bang));	// Move !call down one
			
			*ATBBS = strlop(To, '!');;
		}

		// Accept call@call (without spaces) - but check for smtp addresses

		if (_memicmp(To, "smtp:", 5) != 0 && _memicmp(To, "rms:", 4) != 0  && _memicmp(To, "rms/", 4) != 0)
		{
			ptr = strchr(To, '@');

			if (ptr)
			{
				// If looks like a valid email address, treat as such

				int tolen;
				*ATBBS = strlop(To, '@');

				strlop(To, '-');		// Cant have SSID on BBS Name

				tolen = (int)strlen(To);

				if (tolen > 6 || !CheckifPacket(*ATBBS))
				{
					// Probably Email address. Add smtp: or rms:

					if (FindRMS() || strchr(*ATBBS, '!')) // have RMS or source route
						sprintf(To, "rms:%s", ToCopy);
					else if (ISP_Gateway_Enabled)
						sprintf(To, "smtp:%s", ToCopy);
					else if (isAMPRMsg(ToCopy))
						sprintf(To, "rms:%s", ToCopy);

				}
			}
		}
	}

	free(ToCopy);

	// Look for Optional fields;

	ptr = strtok_s(NULL, seps, &Context);

	while (ptr)
	{
		if (strcmp(ptr, "@") == 0)
		{
			*ATBBS = _strupr(strtok_s(NULL, seps, &Context));
		}
		else if(strcmp(ptr, "<") == 0)
		{			
			*From = strtok_s(NULL, seps, &Context);
		}
		else if (ptr[0] == '$')
			*BID = &ptr[1];
		else
		{
			nodeprintf(conn, "*** Error: Invalid Format\r");
			return FALSE;
		}
		ptr = strtok_s(NULL, seps, &Context);
	}

	// Only allow < from a BBS

	if (*From)
	{
		if (!(conn->BBSFlags & BBS))
		{
			nodeprintf(conn, "*** < can only be used by a BBS\r");
			return FALSE;
		}
	}

	if (!*From)
		*From = conn->UserPointer->Call;

	if (!(conn->BBSFlags & BBS))
	{
		// if a normal user, check that TO and/or AT are known and warn if not.

		if (_stricmp(To, "SYSOP") == 0)
		{
			conn->LocalMsg = TRUE;
			return TRUE;
		}

		if (!*ATBBS && conn->ToCount == 0)
		{
			// No routing, if not a user and not known to forwarding or WP warn

			struct UserInfo * ToUser = LookupCall(To);

			if (ToUser)
			{
				// Local User. If Home BBS is specified, use it

				if (ToUser->flags & F_RMSREDIRECT)
				{
					// sent to Winlink
				
					*ATBBS = Winlink;
					nodeprintf(conn, "Redirecting to winlink.org\r", *ATBBS);
				}
				else if (ToUser->HomeBBS[0])
				{
					*ATBBS = ToUser->HomeBBS;
					nodeprintf(conn, "Address @%s added from HomeBBS\r", *ATBBS);
				}
				else
				{
					// See if a WP entry

					WP = LookupWP(To);

					if (WP)
					{
						*ATBBS = WP->first_homebbs;
						nodeprintf(conn, "Address @%s added from WP\r", *ATBBS);
						conn->LocalMsg = FALSE;
					}
					else
						conn->LocalMsg = TRUE;
				}
			}
			else
			{
				conn->LocalMsg = FALSE;
				WP = LookupWP(To);

				if (WP)
				{
					*ATBBS = WP->first_homebbs;
					nodeprintf(conn, "Address @%s added from WP\r", *ATBBS);
				}
			}
		}
	}
	return TRUE;
}

BOOL CreateMessage(CIRCUIT * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title)
{
	struct MsgInfo * Msg, * TestMsg;
	char * via = NULL;
	char * FromHA;

	// Create a temp msg header entry

	if (conn->ToCount)
	{
	}
	else
	{
		if (CheckRejFilters(From, ToCall, ATBBS, BID, MsgType, 0))
		{	
			if ((conn->BBSFlags & BBS))
			{
				nodeprintf(conn, "NO - REJECTED\r");
				if (conn->BBSFlags & OUTWARDCONNECT)
					nodeprintf(conn, "F>\r");				// if Outward connect must be reverse forward
				else
					nodeprintf(conn, ">\r");
			}
			else
				nodeprintf(conn, "*** Error - Message Filters prevent sending this message\r");

			return FALSE;
		}
	}

	Msg = malloc(sizeof (struct MsgInfo));

	if (Msg == 0)
	{
		CriticalErrorHandler("malloc failed for new message header");
		return FALSE;
	}
	
	memset(Msg, 0, sizeof (struct MsgInfo));

	conn->TempMsg = Msg;

	Msg->type = MsgType;
	
	if (conn->UserPointer->flags & F_HOLDMAIL)
		Msg->status = 'H';
	else
		Msg->status = 'N';
	
	Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL);

	if (BID)
	{
		BIDRec * TempBID;

		// If P Message, dont immediately reject on a Duplicate BID. Check if we still have the message
		//	If we do, reject  it. If not, accept it again. (do we need some loop protection ???)

		TempBID = LookupBID(BID);
	
		if (TempBID)
		{
			if (MsgType == 'B')
			{
				// Duplicate bid
	
				if ((conn->BBSFlags & BBS))
				{
					nodeprintf(conn, "NO - BID\r");
					if (conn->BBSFlags & OUTWARDCONNECT)
						nodeprintf(conn, "F>\r");				// if Outward connect must be reverse forward
					else
						nodeprintf(conn, ">\r");
				}
				else
					nodeprintf(conn, "*** Error- Duplicate BID\r");

				return FALSE;
			}

			TestMsg = GetMsgFromNumber(TempBID->u.msgno);
 
			// if the same TO we will assume the same message

			if (TestMsg && strcmp(TestMsg->to, ToCall) == 0)
			{
				// We have this message. If we have already forwarded it, we should accept it again

				if ((TestMsg->status == 'N') || (TestMsg->status == 'Y')|| (TestMsg->status == 'H'))
				{
					// Duplicate bid
	
					if ((conn->BBSFlags & BBS))
					{
						nodeprintf(conn, "NO - BID\r");
						if (conn->BBSFlags & OUTWARDCONNECT)
							nodeprintf(conn, "F>\r");				// if Outward connect must be reverse forward
						else
							nodeprintf(conn, ">\r");
					}
					else
						nodeprintf(conn, "*** Error- Duplicate BID\r");

					return FALSE;
				}
			}
		}
		
		if (strlen(BID) > 12) BID[12] = 0;
		strcpy(Msg->bid, BID);

		// Save BID in temp list in case we are offered it again before completion
			
		TempBID = AllocateTempBIDRecord();
		strcpy(TempBID->BID, BID);
		TempBID->u.conn = conn;

	}

	if (conn->ToCount)
	{
	}
	else
	{
		if (_memicmp(ToCall, "rms:", 4) == 0)
		{
			// Could be ampr.org message 

			if (!isAMPRMsg(ToCall))
			{
				if (!FindRMS())
				{
					nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r");
					return FALSE;
				}
			}
			via=strlop(ToCall, ':');
			_strupr(ToCall);

		}
		else if (_memicmp(ToCall, "rms/", 4) == 0)
		{
			if (!FindRMS())
			{
				nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r");
				return FALSE;
			}

			via=strlop(ToCall, '/');
			_strupr(ToCall);
		}
		else if (_memicmp(ToCall, "smtp:", 5) == 0)
		{
			if (ISP_Gateway_Enabled)
			{
				if ((conn->UserPointer->flags & F_EMAIL) == 0)
				{
					nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r");
					return FALSE;
				}
				via=strlop(ToCall, ':');
				ToCall[0] = 0;
			}
			else
			{
				nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r");
				return FALSE;
			}
		}
		else
		{
			_strupr(ToCall);
			if (ATBBS)
				via=_strupr(ATBBS);
		}

		strlop(ToCall, '-');						// Remove any (illegal) ssid
		if (strlen(ToCall) > 6) ToCall[6] = 0;
	
		strcpy(Msg->to, ToCall);
		
		if (SendBBStoSYSOPCall)
			if (_stricmp(ToCall, BBSName) == 0)
				strcpy(Msg->to, SYSOPCall);

		if (via)
		{
			if (strlen(via) > 40) via[40] = 0;

			strcpy(Msg->via, via);
		}

	}		// End of Multiple Dests

	// Look for HA in From (even if we shouldn't be getting it!)

	FromHA = strlop(From, '@');


	strlop(From, '-');						// Remove any (illegal) ssid
	if (strlen(From) > 6) From[6] = 0;
	strcpy(Msg->from, From);

	if (FromHA)
	{
		if (strlen(FromHA) > 39) FromHA[39] = 0;
		Msg->emailfrom[0] = '@';
		strcpy(&Msg->emailfrom[1], _strupr(FromHA));
	}

	if (Title)					// Only used by SR and SC
	{
		strcpy(Msg->title, Title);
		conn->Flags |= GETTINGMESSAGE;

		// Create initial buffer of 10K. Expand if needed later

		conn->MailBuffer=malloc(10000);
		conn->MailBufferSize=10000;

		nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r");
		return TRUE;
	}

	if (conn->BBSFlags & FLARQMODE)
		return TRUE;

	if (!(conn->BBSFlags & FBBCompressed))
		conn->Flags |= GETTINGTITLE;

	if (!(conn->BBSFlags & BBS))
		nodeprintf(conn, "Enter Title (only):\r");
	else
		if (!(conn->BBSFlags & FBBForwarding))
			nodeprintf(conn, "OK\r");

	return TRUE;
}

VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int msglen)
{
		
	conn->Flags &= ~GETTINGTITLE;

	if (msglen == 1)
	{
		nodeprintf(conn, "*** Message Cancelled\r");
		SendPrompt(conn, user);
		return;
	}

	if (msglen > 60) msglen = 60;

	Buffer[msglen-1] = 0;

	strcpy(conn->TempMsg->title, Buffer);

	// Create initial buffer of 10K. Expand if needed later

	conn->MailBuffer=malloc(10000);
	conn->MailBufferSize=10000;

	if (conn->MailBuffer == NULL)
	{
		nodeprintf(conn, "Failed to create Message Buffer\r");
		return;
	}

	conn->Flags |= GETTINGMESSAGE;

	if (!conn->BBSFlags & BBS)
		nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r");

}

VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int msglen)
{
	char * ptr2 = NULL;
	int Index = 0;
	int MsgHasCtrlZ = 0;
	char * ptr;

	// AEA TNC sends 2f 45 1a 3e 0d  (/ E <ctrl/z> > <cr>) if the original message was input using /ex as terminator



	if (((msglen < 4) && (Buffer[0] == 0x1a))  || ((msglen == 4 && _memicmp(Buffer, "/ex", 3) == 0)) || memcmp(Buffer, "/E\0x1a>\0x0d", 5) == 0 || memcmp(Buffer, "/e\0x1a>\0x0d", 5) == 0)
	{
gotCtrlZ:			
		if (conn->TempMsg->type == 'P')
			Index = PMSG;
		else if (conn->TempMsg->type == 'B')
			Index = BMSG;
		else if (conn->TempMsg->type == 'T')
			Index = TMSG;
		
		conn->Flags &= ~GETTINGMESSAGE;

		user->Total.MsgsReceived[Index]++;
		user->Total.BytesForwardedIn[Index] += conn->TempMsg->length;

		if (conn->ToCount)
		{
			// Multiple recipients

			struct MsgInfo * Msg = conn->TempMsg;
			int i;
			struct MsgInfo * SaveMsg = Msg;
			char * SaveBody = conn->MailBuffer;
			int SaveMsgLen = Msg->length; 
			BOOL SentToRMS = FALSE;
			int ToLen = 0;
			char * ToString = zalloc(conn->ToCount * 100);

			// If no BID provided, allocate one
			
			if (Msg->bid[0] == 0)
				sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg + 1, BBSName);

			for (i = 0; i < conn->ToCount; i++)
			{
				char * Addr = conn->To[i];
				char * Via;

				if (_memicmp (Addr, "SMTP:", 5) == 0)
				{
					// For Email

					conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo));
					memcpy(Msg, SaveMsg, sizeof(struct MsgInfo));
	
					conn->MailBuffer = malloc(SaveMsgLen + 10);
					memcpy(conn->MailBuffer, SaveBody, SaveMsgLen);

					Msg->to[0] = 0;
					strcpy(Msg->via, &Addr[5]);

					CreateMessageFromBuffer(conn);
					continue;
				}

				if (_memicmp (Addr, "RMS:", 4) == 0)
				{
					// Add to B2 Message for RMS

					Addr+=4;
					
					Via = strlop(Addr, '@');
				
					if (Via && _stricmp(Via, "winlink.org") == 0)
					{
						if (CheckifLocalRMSUser(Addr))
						{
							// Local RMS - Leave Here
	
							Via = 0;							// Drop Through
							goto PktMsg;
						}
						else
						{
							ToLen = sprintf(&ToString[strlen(ToString)], "To: %s\r\n", Addr);
							continue;
						}
					}

					ToLen = sprintf(&ToString[strlen(ToString)], "To: %s@%s\r\n", Addr, Via);
					continue;
				}

				_strupr(Addr);
				
				Via = strlop(Addr, '@');

				if (Via && _stricmp(Via, "winlink.org") == 0)
				{
					if (CheckifLocalRMSUser(Addr))
					{
						// Local RMS - Leave Here

						Via = 0;							// Drop Through
					}
					else
					{
						ToLen = sprintf(&ToString[strlen(ToString)], "To: %s\r\n", Addr);

						// Add to B2 Message for RMS

						continue;
					}
				}

			PktMsg:		
				
				conn->LocalMsg = FALSE;

				// Normal BBS Message

				if (_stricmp(Addr, "SYSOP") == 0)
					conn->LocalMsg = TRUE;
				else
				{
					struct UserInfo * ToUser = LookupCall(Addr);

					if (ToUser)
						conn->LocalMsg = TRUE;
				}

				conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo));
				memcpy(Msg, SaveMsg, sizeof(struct MsgInfo));
	
				conn->MailBuffer = malloc(SaveMsgLen + 10);
				memcpy(conn->MailBuffer, SaveBody, SaveMsgLen);

				strcpy(Msg->to, Addr);

				if (Via)
				{
					Msg->bid[0] = 0;					// if we are forwarding it, we must change BID to be safe
					strcpy(Msg->via, Via);
				}

				CreateMessageFromBuffer(conn);
			}

			if (ToLen)
			{
				char * B2Hddr = zalloc(ToLen + 1000);
				int B2HddrLen;
				char DateString[80];
				struct tm * tm;
				time_t Date = time(NULL);
				char Type[16] = "Private";
					
				// Get Type
	
				if (conn->TempMsg->type == 'B')
					strcpy(Type, "Bulletin");
				else if (conn->TempMsg->type == 'T')
					strcpy(Type, "Traffic");

				tm = gmtime(&Date);	
	
				sprintf(DateString, "%04d/%02d/%02d %02d:%02d",
					tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);

				conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo));
				memcpy(Msg, SaveMsg, sizeof(struct MsgInfo));
	
				conn->MailBuffer = malloc(SaveMsgLen + 1000 + ToLen);

				Msg->B2Flags = B2Msg;

				B2HddrLen = sprintf(B2Hddr,
					"MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\n%sSubject: %s\r\nMbo: %s\r\nBody: %d\r\n\r\n",
					SaveMsg->bid, DateString, Type,
					SaveMsg->from, ToString, SaveMsg->title, BBSName, SaveMsgLen);

				memcpy(conn->MailBuffer, B2Hddr, B2HddrLen);
				memcpy(&conn->MailBuffer[B2HddrLen], SaveBody, SaveMsgLen);

				Msg->length += B2HddrLen;

				strcpy(Msg->to, "RMS");

				CreateMessageFromBuffer(conn);

				free(B2Hddr);
			}

			free(SaveMsg);
			free(SaveBody);
			conn->MailBuffer = NULL;
			conn->MailBufferSize=0;

			if (!(conn->BBSFlags & BBS))
				SendPrompt(conn, conn->UserPointer);
			else
				if (!(conn->BBSFlags & FBBForwarding))
				{
					if (conn->BBSFlags & OUTWARDCONNECT)
						BBSputs(conn, "F>\r");				// if Outward connect must be reverse forward
					else
						BBSputs(conn, ">\r");
				}

			/*
			// From a client - Create one copy with all RMS recipients, and another for each packet recipient	

			// Merge all RMS To: lines 

			ToLen = 0;
			ToString[0] = 0;

			for (i = 0; i < Recipients; i++)
			{
				if (LocalMsg[i])
					continue;						// For a local RMS user
				
				if (_stricmp(Via[i], "WINLINK.ORG") == 0 || _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 ||
					_stricmp(RecpTo[i], "RMS") == 0)
				{
					ToLen += strlen(HddrTo[i]);
					strcat(ToString, HddrTo[i]);
				}
			}

			if (ToLen)
			{
				conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo));
				memcpy(Msg, SaveMsg, sizeof(struct MsgInfo));
	
				conn->MailBuffer = malloc(SaveMsgLen + 1000);
				memcpy(conn->MailBuffer, SaveBody, SaveMsgLen);


				memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count);
				memcpy(&conn->MailBuffer[B2To], ToString, ToLen); 

				conn->TempMsg->length += ToLen;

				strcpy(Msg->to, "RMS");
				strcpy(Msg->via, "winlink.org");

				// Must Change the BID

				Msg->bid[0] = 0;

				CreateMessageFromBuffer(conn);
			}

			}

			free(ToString);

			for (i = 0; i < Recipients; i++)
			{
				// Only Process Non - RMS Dests or local RMS Users

				if (LocalMsg[i] == 0)
					if (_stricmp (Via[i], "WINLINK.ORG") == 0 ||
						_memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 ||
						_stricmp(RecpTo[i], "RMS") == 0)		
					continue;

				conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo));
				memcpy(Msg, SaveMsg, sizeof(struct MsgInfo));
	
				conn->MailBuffer = malloc(SaveMsgLen + 1000);
				memcpy(conn->MailBuffer, SaveBody, SaveMsgLen);

				// Add our To: 

				ToLen = strlen(HddrTo[i]);

				if (_memicmp(HddrTo[i], "CC", 2) == 0)	// Replace CC: with TO:
					memcpy(HddrTo[i], "To", 2);

				memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count);
				memcpy(&conn->MailBuffer[B2To], HddrTo[i], ToLen); 

				conn->TempMsg->length += ToLen;

				strcpy(Msg->to, RecpTo[i]);
				strcpy(Msg->via, Via[i]);
				
				Msg->bid[0] = 0;

				CreateMessageFromBuffer(conn);
			}
			}	// End not from RMS

			free(SaveMsg);
			free(SaveBody);
			conn->MailBuffer = NULL;
			conn->MailBufferSize=0;

			SetupNextFBBMessage(conn);
			return;
	
			} My__except_Routine("Process Multiple Destinations");

			BBSputs(conn, "*** Program Error Processing Multiple Destinations\r");
			Flush(conn);
			conn->CloseAfterFlush = 20;			// 2 Secs

			return;
*/

			conn->ToCount = 0;

			return;
		}


		CreateMessageFromBuffer(conn);
		return;

	}

	// AEA TNC dosn't always send ctrl/z at start of packet.

	Buffer[msglen] = 0;

	ptr = strchr(Buffer, 26);		//Ctrl/Z

	if (ptr)
	{
		MsgHasCtrlZ =1;
		*(ptr) = 13;			// replace with CR
	}


	Buffer[msglen++] = 0x0a;

	if ((conn->TempMsg->length + msglen) > conn->MailBufferSize)
	{
		conn->MailBufferSize += 10000;
		conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize);
	
		if (conn->MailBuffer == NULL)
		{
			nodeprintf(conn, "Failed to extend Message Buffer\r");

			conn->Flags &= ~GETTINGMESSAGE;
			return;
		}
	}

	memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, msglen);

	conn->TempMsg->length += msglen;

	if (MsgHasCtrlZ)
		goto gotCtrlZ;

}

VOID CreateMessageFromBuffer(CIRCUIT * conn)
{
	struct MsgInfo * Msg;
	BIDRec * BIDRec;
	char * ptr1, * ptr2 = NULL;
	char * ptr3, * ptr4;
	int FWDCount = 0;
	char OldMess[] = "\r\n\r\nOriginal Message:\r\n\r\n";
	time_t Age;
	int OurCount;
	char * HoldReason = "User has Hold Messages flag set";
	struct UserInfo * user;


#ifndef LINBPQ
	struct _EXCEPTION_POINTERS exinfo;
#endif

	// If doing SC, Append Old Message

	if (conn->CopyBuffer)
	{
		if ((conn->TempMsg->length + (int) strlen(conn->CopyBuffer) + 80 )> conn->MailBufferSize)
		{
			conn->MailBufferSize += (int)strlen(conn->CopyBuffer) + 80;
			conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize);
	
			if (conn->MailBuffer == NULL)
			{
				nodeprintf(conn, "Failed to extend Message Buffer\r");

				conn->Flags &= ~GETTINGMESSAGE;
				return;
			}
		}

		memcpy(&conn->MailBuffer[conn->TempMsg->length], OldMess, strlen(OldMess));

		conn->TempMsg->length += (int)strlen(OldMess);

		memcpy(&conn->MailBuffer[conn->TempMsg->length], conn->CopyBuffer, strlen(conn->CopyBuffer));

		conn->TempMsg->length += (int)strlen(conn->CopyBuffer);

		free(conn->CopyBuffer);
		conn->CopyBuffer = NULL;
	}
	
	// Allocate a message Record slot
	
	Msg = AllocateMsgRecord();
	memcpy(Msg, conn->TempMsg, sizeof(struct MsgInfo));

	free(conn->TempMsg);

	// Set number here so they remain in sequence
		
	GetSemaphore(&MsgNoSemaphore, 0);
	Msg->number = ++LatestMsg;
	FreeSemaphore(&MsgNoSemaphore);
	MsgnotoMsg[Msg->number] = Msg;

	if (Msg->status == 0)
		Msg->status = 'N';

	// Create BID if non supplied

	if (Msg->bid[0] == 0)
		sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName);

	// if message body had R: lines, get date created from last (not very accurate, but best we can do)

	// Also check if we have had message before to detect loops

	ptr1 = conn->MailBuffer;
	OurCount = 0;

	// If it is a B2 Message, Must Skip B2 Header

	if (Msg->B2Flags & B2Msg)
	{
		ptr1 = strstr(ptr1, "\r\n\r\n");
		if (ptr1)
			ptr1 += 4;
		else
			ptr1 = conn->MailBuffer;
	}

nextline:

	if (memcmp(ptr1, "R:", 2) == 0)
	{
		// Is if ours?

		// BPQ RLINE Format R:090920/1041Z 6542@N4JOA.#WPBFL.FL.USA.NOAM BPQ1.0.2

		ptr3 = strchr(ptr1, '@');
		ptr4 = strchr(ptr1, '.');

		if (ptr3 && ptr4 && (ptr4 > ptr3))
		{
			if (memcmp(ptr3+1, BBSName, ptr4-ptr3-1) == 0)
				OurCount++;
		}

		GetWPBBSInfo(ptr1);		// Create WP /I record from R: Line

		// see if another

		ptr2 = ptr1;			// save
		ptr1 = strchr(ptr1, '\r');
		if (ptr1 == 0)
		{
			Debugprintf("Corrupt Message %s from %s - truncated within R: line", Msg->bid, Msg->from);
			return;
		}
		ptr1++;
		if (*ptr1 == '\n') ptr1++;

		goto nextline;
	}

	// ptr2 points to last R: line (if any)

	if (ptr2)
	{
		struct tm rtime;
		time_t result;

		memset(&rtime, 0, sizeof(struct tm));

		if (ptr2[10] == '/')
		{
			// Dodgy 4 char year

			sscanf(&ptr2[2], "%04d%02d%02d/%02d%02d",
				&rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min);
			rtime.tm_year -= 1900;
			rtime.tm_mon--;
		}
		else if (ptr2[8] == '/')
		{
			sscanf(&ptr2[2], "%02d%02d%02d/%02d%02d",
				&rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min);

			if (rtime.tm_year < 90)
				rtime.tm_year += 100;		// Range 1990-2089
			rtime.tm_mon--;
		}

		// Otherwise leave date as zero, which should be rejected

		//			result = _mkgmtime(&rtime);

		if ((result = mktime(&rtime)) != (time_t)-1 )
		{
			result -= (time_t)_MYTIMEZONE;

			Msg->datecreated =  result;	
			Age = (time(NULL) - result)/86400;

			if ( Age < -7)
			{
				Msg->status = 'H';
				HoldReason = "Suspect Date Sent";
			}
			else if (Age > BidLifetime || Age > MaxAge)
			{
				Msg->status = 'H';
				HoldReason = "Message too old";

			}
			else
				GetWPInfoFromRLine(Msg->from, ptr2, result);
		}
		else
		{
			// Can't decode R: Datestamp

			Msg->status = 'H';
			HoldReason = "Corrupt R: Line - can't determine age";
		}

		if (OurCount > 1)
		{
			// Message is looping 

			Msg->status = 'H';
			HoldReason = "Message may be looping";

		}
	}

	if (strcmp(Msg->to, "WP") == 0)
	{
		// If Reject WP Bulls is set, Kill message here.
		// It should only get here if B2 - otherwise it should be
		// rejected earlier

		if (Msg->type == 'B' && FilterWPBulls)
			Msg->status = 'K';

	}

	conn->MailBuffer[Msg->length] = 0;

	if (CheckBadWords(Msg->title) || CheckBadWords(conn->MailBuffer))
	{
		Msg->status = 'H';
		HoldReason = "Bad word in title or body";
	}

	if (CheckHoldFilters(Msg, Msg->from, Msg->to, Msg->via, Msg->bid))
	{
		Msg->status = 'H';
		HoldReason = "Matched Hold Filters";
	}

	if (CheckValidCall(Msg->from) == 0)
	{
		Msg->status = 'H';
		HoldReason = "Probable Invalid From Call";
	}

	// Process any WP Messages
	
	if (strcmp(Msg->to, "WP") == 0)
	{	
		if (Msg->status == 'N')
		{
			ProcessWPMsg(conn->MailBuffer, Msg->length, ptr2);

			if (Msg->type == 'P')			// Kill any processed private WP messages.
			{
				char VIA[80];

				strcpy(VIA, Msg->via);
				strlop(VIA, '.');

				if (strcmp(VIA, BBSName) == 0)
					Msg->status = 'K';
			}
		}
	}
	
	CreateMessageFile(conn, Msg);

	BIDRec = AllocateBIDRecord();

	strcpy(BIDRec->BID, Msg->bid);
	BIDRec->mode = Msg->type;
	BIDRec->u.msgno = LOWORD(Msg->number);
	BIDRec->u.timestamp = LOWORD(time(NULL)/86400);

	if (Msg->length > MaxTXSize)
	{
		Msg->status = 'H';
		HoldReason = "Message too long";

		if (!(conn->BBSFlags & BBS))
			nodeprintf(conn, "*** Warning Message length exceeds sysop-defined maximum of %d - Message will be held\r", MaxTXSize);
	}

	// Check for message to internal server

	if (Msg->via[0] == 0 
		|| _stricmp(Msg->via, BBSName) == 0		// our BBS a
		|| _stricmp(Msg->via, AMPRDomain) == 0)	// our AMPR Address
	{
		if (CheckforMessagetoServer(Msg))
		{
			// Flag as killed and send prompt

			FlagAsKilled(Msg, TRUE);

			if (!(conn->BBSFlags & BBS))
			{
				nodeprintf(conn, "Message %d to Server Processed and Killed.\r", Msg->number);
				SendPrompt(conn, conn->UserPointer);
			}
			return;				// no need to process further
		}
	}

	if (Msg->to[0])
		FWDCount = MatchMessagetoBBSList(Msg, conn);
	else
	{
		// If addressed @winlink.org, and to a local user, Keep here.

		char * Call;
		char * AT;

		// smtp or rms - don't warn no route

		FWDCount = 1;

		Call = _strupr(_strdup(Msg->via));
		AT = strlop(Call, '@');

		if (AT && _stricmp(AT, "WINLINK.ORG") == 0)
		{
			struct UserInfo * user = LookupCall(Call);

			if (user)
			{
				if (user->flags & F_POLLRMS)
				{
					Logprintf(LOG_BBS, conn, '?', "SMTP Message @ winlink.org, but local RMS user - leave here");
					strcpy(Msg->to, Call);
					strcpy(Msg->via, AT);
					if (user->flags & F_BBS)	// User is a BBS, so set FWD bit so he can get it
						set_fwd_bit(Msg->fbbs, user->BBSNumber);

				}
			}
		}
		free(Call);
	}

	// Warn SYSOP if P or T forwarded in, and has nowhere to go

	if ((conn->BBSFlags & BBS) && Msg->type != 'B' && FWDCount == 0 && WarnNoRoute &&
		strcmp(Msg->to, "SYSOP") && strcmp(Msg->to, "WP"))
	{
		if (Msg->via[0])
		{	
			if (_stricmp(Msg->via, BBSName))		// Not for our BBS a
				if (_stricmp(Msg->via, AMPRDomain))	// Not for our AMPR Address
					SendWarningToSYSOP(Msg);
		}
		else
		{
			// No via - is it for a local user?

			if (LookupCall(Msg->to) == 0)
				SendWarningToSYSOP(Msg);
		}
	}

	if ((conn->BBSFlags & SYNCMODE) == 0)
	{
		if (!(conn->BBSFlags & BBS))
		{
			nodeprintf(conn, "Message: %d Bid:  %s Size: %d\r", Msg->number, Msg->bid, Msg->length);

			if (Msg->via[0])
			{
				if (_stricmp(Msg->via, BBSName))		// Not for our BBS a
					if (_stricmp(Msg->via, AMPRDomain))	// Not for our AMPR Address

						if (FWDCount ==  0 &&  Msg->to[0] != 0)		// unless smtp msg
							nodeprintf(conn, "@BBS specified, but no forwarding info is available - msg may not be delivered\r");
			}
			else
			{
				if (FWDCount ==  0 && conn->LocalMsg == 0 && Msg->type != 'B')
					// Not Local and no forward route
					nodeprintf(conn, "Message is not for a local user, and no forwarding info is available - msg may not be delivered\r");
			}
			if (conn->ToCount == 0)
				SendPrompt(conn, conn->UserPointer);
		}
		else
		{
			if (!(conn->BBSFlags & FBBForwarding))
			{
				if (conn->ToCount == 0)
					if (conn->BBSFlags & OUTWARDCONNECT)
						nodeprintf(conn, "F>\r");				// if Outward connect must be reverse forward
					else
						nodeprintf(conn, ">\r");
			}					
		}
	}

	if(Msg->to[0] == 0)
		SMTPMsgCreated=TRUE;

	if (Msg->status != 'H' && Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0)
		Msg->status = '$';				// Has forwarding

	if (Msg->status == 'H')
	{
		int Length=0;
		char * MailBuffer = malloc(100);
		char Title[100];

		Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number);
		sprintf(Title, "Message %d Held - %s", Msg->number, HoldReason);
		SendMessageToSYSOP(Title, MailBuffer, Length);
	}

	BuildNNTPList(Msg);				// Build NNTP Groups list

	SaveMessageDatabase();
	SaveBIDDatabase();

	// If Event Notifications enabled report a new message event

	user = LookupCall(Msg->to);

	SendNewMessageEvent(user->Call, Msg);
#ifndef NOMQTT
	if (MQTT)
		MQTTMessageEvent(Msg);
#endif

	if (EnableUI)
#ifdef LINBPQ
		SendMsgUI(Msg);	
#else
		__try
	{
		SendMsgUI(Msg);
	}
	My__except_Routine("SendMsgUI");
#endif

	if (user && (user->flags & F_APRSMFOR))
	{
		char APRS[128];
		char Call[16];
		int SSID = (user->flags >> 28) & 15;	// on some platforms this is treated as signed ??

		if (SSID)
			sprintf(Call, "%s-%d", Msg->to, SSID);
		else
			strcpy(Call, Msg->to);

		sprintf(APRS, "New BBS Message %s From %s", Msg->title, Msg->from);
		APISendAPRSMessage(APRS, Call);
	}

	return;
}

VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg)
{
	char MsgFile[MAX_PATH];
	FILE * hFile;
	size_t WriteLen=0;
	char Mess[255];
	int len;
	BOOL AutoImport = FALSE;

	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number);
	
	//	If title is "Batched messages for AutoImport from BBS xxxxxx and first line is S? and it is
	//  for this BBS, Import file and set message as Killed. May need to strip B2 Header and R: lines

	if (strcmp(Msg->to, BBSName) == 0 && strstr(Msg->title, "Batched messages for AutoImport from BBS "))
	{
		UCHAR * ptr = conn->MailBuffer;

		// If it is a B2 Message, Must Skip B2 Header

		if (Msg->B2Flags & B2Msg)
		{
			ptr = strstr(ptr, "\r\n\r\n");
			if (ptr)
				ptr += 4;
			else
				ptr = conn->MailBuffer;
		}

		if (memcmp(ptr, "R:", 2) == 0)
		{
			ptr = strstr(ptr, "\r\n\r\n");		//And remove R: Lines
			if (ptr)
				ptr += 4;
		}

		if (*(ptr) == 'S' && ptr[2] == ' ')
		{
			int HeaderLen = (int)(ptr - conn->MailBuffer);
			Msg->length -= HeaderLen;
			memmove(conn->MailBuffer, ptr, Msg->length);
			Msg->status = 'K';
			AutoImport = TRUE;
		}
	}

	hFile = fopen(MsgFile, "wb");

	if (hFile)
	{
		WriteLen = fwrite(conn->MailBuffer, 1, Msg->length, hFile);
		fclose(hFile);
	}

	if (AutoImport)
		ImportMessages(NULL, MsgFile, TRUE);

	free(conn->MailBuffer);
	conn->MailBufferSize=0;
	conn->MailBuffer=0;

	if (WriteLen != Msg->length)
	{
		len = sprintf_s(Mess, sizeof(Mess), "Failed to create Message File\r");
		QueueMsg(conn, Mess, len);
		Debugprintf(Mess);
	}
	return;
}


VOID SendUnbuffered(int stream, char * msg, int len)
{
#ifndef LINBPQ
	if (stream < 0)
		WritetoConsoleWindow(stream, msg, len);
	else
#endif
		SendMsg(stream, msg, len);
}

BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen);

BOOL FindMessagestoForward (CIRCUIT * conn)
{
	struct UserInfo * user = conn->UserPointer;

#ifndef LINBPQ

	struct _EXCEPTION_POINTERS exinfo;

	__try {
#endif

	// This is a hack so Winpack or WLE users can use forwarding
	// protocols to get their messages without being defined as a BBS

	// !!IMPORTANT Getting this wrong can see message repeatedly proposed !!
	// !! Anything sent using this must be killed if sent or rejected.

	// I'm not sure about this. I think I only need the PacLinkCalls
	// if from RMS Express, and it always sends an FW; line
	// Ah, no. What about WinPack ??
	// If from RMS Express must have Temp_B2 or BBS set or SID will
	// be rejected. So maybe just Temp_B2 && BBS = 0??
	// No, someone may have Temp_B2 set and not be using WLE ?? So what ??

	if ((user->flags & F_Temp_B2_BBS) && ((user->flags & F_BBS) == 0) || conn->RMSExpress || conn->PAT)
	{
		if (conn->PacLinkCalls == NULL)
		{
			// create a list with just the user call

			char * ptr1;

			conn->PacLinkCalls = zalloc(30);

			ptr1 = (char *)conn->PacLinkCalls;
			ptr1 += 16;		// Must be room for a null pointer on end (64 bit bug)
			strcpy(ptr1, user->Call);

			conn->PacLinkCalls[0] = ptr1;
		}
	}

	if (conn->SendT && FindMessagestoForwardLoop(conn, 'T', conn->MaxTLen))
	{
		conn->LastForwardType = 'T';
		return TRUE;
	}

	if (conn->LastForwardType == 'T')
		conn->NextMessagetoForward = FirstMessageIndextoForward;

	if (conn->SendP && FindMessagestoForwardLoop(conn, 'P', conn->MaxPLen))
	{
		conn->LastForwardType = 'P';
		return TRUE;
	}

	if (conn->LastForwardType == 'P')
		conn->NextMessagetoForward = FirstMessageIndextoForward;

	if (conn->SendB && FindMessagestoForwardLoop(conn, 'B', conn->MaxBLen))
	{
		conn->LastForwardType = 'B';
		return TRUE;
	}

	conn->LastForwardType = 0;
	return FALSE;
#ifndef LINBPQ
	} My__except_Routine("FindMessagestoForward");
#endif
	return FALSE;

}


BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen)
{
	// See if any messages are queued for this BBS

	int m;
	struct MsgInfo * Msg;
	struct UserInfo * user = conn->UserPointer;
	struct FBBHeaderLine * FBBHeader;
	BOOL Found = FALSE;
	char RLine[100];
	int TotalSize = 0;
	time_t NOW = time(NULL);

//	Debugprintf("FMTF entered Call %s Type %c Maxlen %d NextMsg = %d BBSNo = %d",
//		conn->Callsign, Type, MaxLen, conn->NextMessagetoForward, user->BBSNumber);

	if (conn->PacLinkCalls || (conn->UserPointer->flags & F_NTSMPS))	// Looking for all messages, so reset 
		conn->NextMessagetoForward = 1;

	conn->FBBIndex = 0;

	for (m = conn->NextMessagetoForward; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		//	If an NTS MPS, see if anything matches

		if (Type == 'T' && (conn->UserPointer->flags & F_NTSMPS))
		{
			struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo;
			int depth;
				
			if (Msg->type == 'T' && Msg->status == 'N' && Msg->length <= MaxLen && ForwardingInfo)
			{
				depth = CheckBBSToForNTS(Msg, ForwardingInfo);

				if (depth > -1 && Msg->Locked == 0)
					goto Forwardit;
						
				depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via);

				if (depth && Msg->Locked == 0)
					goto Forwardit;

				depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via);

				if (depth > -1 && Msg->Locked == 0)
					goto Forwardit;
			}
		}

		// If forwarding to Paclink or RMS Express, look for any message matching the
		// requested call list with status 'N' (maybe should also be 'P' ??)

		if (conn->PacLinkCalls)
		{
			int index = 1;

			char * Call = conn->PacLinkCalls[0];

			while (Call)
			{
				if (Msg->type == Type && Msg->status == 'N')
				{
//				Debugprintf("Comparing RMS Call %s %s", Msg->to, Call);
				if (_stricmp(Msg->to, Call) == 0)
					if (Msg->status == 'N' && Msg->type == Type && Msg->length <= MaxLen) 
						goto Forwardit;
					else
						Debugprintf("Call Match but Wrong Type/Len %c %d", Msg->type, Msg->length);
				}
				Call = conn->PacLinkCalls[index++];
			}
//			continue;
		}

		if (Msg->type == Type && Msg->length <= MaxLen && (Msg->status != 'H')
			&& (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber))
		{
			// Message to be sent - do a consistancy check (State, etc)

		Forwardit:

			if (Msg->Defered > 0)			// = response received
			{
				Msg->Defered--;
				Debugprintf("Message %d deferred", Msg->number);
				continue;
			}

			if ((Msg->from[0] == 0) || (Msg->to[0] == 0))
			{
				int Length=0;
				char * MailBuffer = malloc(100);
				char Title[100];

				Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number);
				sprintf(Title, "Message %d Held - %s", Msg->number, "Missing From: or To: field");
				SendMessageToSYSOP(Title, MailBuffer, Length);
			
				Msg->status = 'H';
				continue;
			}

			conn->NextMessagetoForward = m + 1;			// So we don't offer again if defered

			Msg->Locked = 1;				// So other MPS can't pick it up

			// if FBB forwarding add to list, eise save pointer

			if (conn->BBSFlags & FBBForwarding)
			{
				struct tm *tm;
				time_t temp;

				FBBHeader = &conn->FBBHeaders[conn->FBBIndex++];

				FBBHeader->FwdMsg = Msg;
				FBBHeader->MsgType = Msg->type;
				FBBHeader->Size = Msg->length;
				TotalSize += Msg->length;
				strcpy(FBBHeader->From, Msg->from);
				strcpy(FBBHeader->To, Msg->to);
				strcpy(FBBHeader->ATBBS, Msg->via);
				strcpy(FBBHeader->BID, Msg->bid);

				// Set up R:Line, so se can add its length to the sise

				memcpy(&temp, &Msg->datereceived, sizeof(time_t));
				tm = gmtime(&temp);	

				FBBHeader->Size += sprintf_s(RLine, sizeof(RLine),"R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n",
					tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min,
					Msg->number, BBSName, HRoute, RlineVer);

				// If using B2 forwarding we need the message size and Compressed size for FC proposal

				if (conn->BBSFlags & FBBB2Mode) 
				{
					if (CreateB2Message(conn, FBBHeader, RLine) == FALSE)
					{
						char * MailBuffer = malloc(100);
						char Title[100];
						int Length;
						
						// Corrupt B2 Message
						
						Debugprintf("Corrupt B2 Message found - Message %d will be held", Msg->number);
						Msg->status = 'H';
						SaveMessageDatabase();

						conn->FBBIndex--;
						TotalSize -= Msg->length;
						memset(&conn->FBBHeaders[conn->FBBIndex], 0, sizeof(struct FBBHeaderLine));

						Length = sprintf(MailBuffer, "Message %d Held\r\n", Msg->number);
						sprintf(Title, "Message %d Held - %s", Msg->number, "Corrupt B2 Message");
						SendMessageToSYSOP(Title, MailBuffer, Length);
			
						continue;
					}
				}

				if (conn->FBBIndex == 5  || TotalSize > user->ForwardingInfo->MaxFBBBlockSize)
					return TRUE;							// Got max number or too big

				Found = TRUE;								// Remember we have some
			}
			else
			{
				conn->FwdMsg = Msg;
				return TRUE;
			}
		}
	}

	return Found;
}

BOOL SeeifMessagestoForward (int BBSNumber, CIRCUIT * conn)
{
	// See if any messages are queued for this BBS

	// if Conn is not NULL, also check Msg Type

	int m;
	struct MsgInfo * Msg;

	for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber))
		{
			if (conn)
			{
				char Type = Msg->type;

				if ((conn->SendB && Type == 'B') || (conn->SendP && Type == 'P') || (conn->SendT && Type == 'T'))
				{
//					Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number);
					return TRUE;
				}
			}
			else
			{
//				Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number);	
				return TRUE;
			}
		}
	}

	return FALSE;
}

int CountMessagestoForward (struct UserInfo * user)
{
	// See if any messages are queued for this BBS

	int m, n=0;
	struct MsgInfo * Msg;
	int BBSNumber = user->BBSNumber;
	int FirstMessage = FirstMessageIndextoForward;

	if ((user->flags & F_NTSMPS))
		FirstMessage = 1;

	for (m = FirstMessage; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber))
		{
			n++;
			continue;			// So we dont count twice if Flag set and NTS MPS
		}

		// if an NTS MPS, also check for any matches

		if (Msg->type == 'T' && (user->flags & F_NTSMPS))
		{
			struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo;
			int depth;
				
			if (Msg->status == 'N' && ForwardingInfo)
			{
				depth = CheckBBSToForNTS(Msg, ForwardingInfo);

				if (depth > -1 && Msg->Locked == 0)
				{
					n++;
					continue;
				}						
				depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via);

				if (depth && Msg->Locked == 0)
				{
					n++;
					continue;
				}						

				depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via);

				if (depth > -1 && Msg->Locked == 0)
				{
					n++;
					continue;
				}
			}
		}
	}

	return n;
}

int CountBytestoForward (struct UserInfo * user)
{
	// See if any messages are queued for this BBS. If so return total bytes queued

	int m, n=0;
	struct MsgInfo * Msg;
	int BBSNumber = user->BBSNumber;
	int FirstMessage = FirstMessageIndextoForward;

	if ((user->flags & F_NTSMPS))
		FirstMessage = 1;

	for (m = FirstMessage; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber))
		{
			n += Msg->length;
			continue;			// So we dont count twice if Flag set and NTS MPS
		}

		// if an NTS MPS, also check for any matches

		if (Msg->type == 'T' && (user->flags & F_NTSMPS))
		{
			struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo;
			int depth;
				
			if (Msg->status == 'N' && ForwardingInfo)
			{
				depth = CheckBBSToForNTS(Msg, ForwardingInfo);

				if (depth > -1 && Msg->Locked == 0)
				{
					n += Msg->length;
					continue;
				}						
				depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via);

				if (depth && Msg->Locked == 0)
				{
					n += Msg->length;
					continue;
				}						

				depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via);

				if (depth > -1 && Msg->Locked == 0)
				{
					n += Msg->length;
					continue;
				}
			}
		}
	}

	return n;
}

int ListMessagestoForward(CIRCUIT * conn, struct UserInfo * user)
{
	// See if any messages are queued for this BBS

	int m, n=0;
	struct MsgInfo * Msg;
	int BBSNumber = user->BBSNumber;
	int FirstMessage = FirstMessageIndextoForward;

	if ((user->flags & F_NTSMPS))
		FirstMessage = 1;

	for (m = FirstMessage; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber))
		{
			nodeprintf(conn, "%d %s\r", Msg->number, Msg->title);
			continue;			// So we dont count twice in Flag set and NTS MPS
		}

		// if an NTS MPS, also check for any matches

		if (Msg->type == 'T' && (user->flags & F_NTSMPS))
		{
			struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo;
			int depth;
				
			if (Msg->status == 'N' && ForwardingInfo)
			{
				depth = CheckBBSToForNTS(Msg, ForwardingInfo);

				if (depth > -1 && Msg->Locked == 0)
				{
					nodeprintf(conn, "%d %s\r", Msg->number, Msg->title);
					continue;
				}						
				depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via);

				if (depth && Msg->Locked == 0)
				{
					nodeprintf(conn, "%d %s\r", Msg->number, Msg->title);
					continue;
				}						

				depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via);

				if (depth > -1 && Msg->Locked == 0)
				{
					nodeprintf(conn, "%d %s\r", Msg->number, Msg->title);
					continue;
				}
			}
		}
	}

	return n;
}

VOID SendWarningToSYSOP(struct MsgInfo * Msg)
{
	int Length=0;
	char * MailBuffer = malloc(100);
	char Title[100];

	Length += sprintf(MailBuffer, "Warning - Message %d has nowhere to go\r\n%s@%s", Msg->number, Msg->to, Msg->via);
	sprintf(Title, "Warning - Message %d has nowhere to go", Msg->number);
	SendMessageToSYSOP(Title, MailBuffer, Length);
}



VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length)
{
	struct MsgInfo * Msg = AllocateMsgRecord();
	BIDRec * BIDRec;

	char MsgFile[MAX_PATH];
	FILE * hFile;
	size_t WriteLen=0;

	Msg->length = Length;

	GetSemaphore(&MsgNoSemaphore, 0);
	Msg->number = ++LatestMsg;
	MsgnotoMsg[Msg->number] = Msg;

	FreeSemaphore(&MsgNoSemaphore);
 
	strcpy(Msg->from, "SYSTEM");
	if (SendSYStoSYSOPCall)
		strcpy(Msg->to, SYSOPCall);
	else
		strcpy(Msg->to, "SYSOP");

	strcpy(Msg->title, Title);

	Msg->type = 'P';
	Msg->status = 'N';
	Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL);

	sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName);

	BIDRec = AllocateBIDRecord();
	strcpy(BIDRec->BID, Msg->bid);
	BIDRec->mode = Msg->type;
	BIDRec->u.msgno = LOWORD(Msg->number);
	BIDRec->u.timestamp = LOWORD(time(NULL)/86400);

	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number);
	
	hFile = fopen(MsgFile, "wb");

	if (hFile)
	{
		WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile);
		fclose(hFile);
	}

	MatchMessagetoBBSList(Msg, NULL);
	free(MailBuffer);
}

VOID CheckBBSNumber(int i)
{
	// Make sure number is unique

	int Count = 0;
	struct UserInfo * user;

	for (user = BBSChain; user; user = user->BBSNext)
	{	
		if (user->BBSNumber == i)
		{
			Count++;

			if (Count > 1)
			{
				// Second with same number - Renumber this one

				user->BBSNumber = FindFreeBBSNumber();

				if (user->BBSNumber == 0)
					user->BBSNumber = NBBBS;			// cant really do much else

				Logprintf(LOG_BBS, NULL, '?', "Duplicate BBS Number found. BBS %s Old BBSNumber %d New BBS Number %d", user->Call, i, user->BBSNumber);

			}
		}
	}
}


int FindFreeBBSNumber()
{
	// returns the lowest number not used by any bbs or message.

	struct MsgInfo * Msg;
	struct UserInfo * user;
	int i, m;

	for (i = 1; i<= NBBBS; i++)
	{
		for (user = BBSChain; user; user = user->BBSNext)
		{
			if (user->BBSNumber == i)
				goto nexti;				// In use
		}

		// Not used by BBS - check messages

		for (m = 1; m <= NumberofMessages; m++)
		{
			Msg=MsgHddrPtr[m];

			if (check_fwd_bit(Msg->fbbs, i))
				goto nexti;				// In use

			if (check_fwd_bit(Msg->forw, i))
				goto nexti;				// In use
		}

		// Not in Use

		return i;
	
nexti:;

	}

	return 0;		// All used
}

BOOL SetupNewBBS(struct UserInfo * user)
{
	user->BBSNumber = FindFreeBBSNumber();

	if (user->BBSNumber == 0)
		return FALSE;

	user->BBSNext = BBSChain;
	BBSChain = user;

	SortBBSChain();

	ReinitializeFWDStruct(user);

	return TRUE;
}

VOID DeleteBBS(struct UserInfo * user)
{
	struct UserInfo * BBSRec, * PrevBBS = NULL;

#ifndef LINBPQ
	RemoveMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, MF_BYCOMMAND); 
#endif
	for (BBSRec = BBSChain; BBSRec; PrevBBS = BBSRec, BBSRec = BBSRec->BBSNext)
	{
		if (user == BBSRec)
		{
			if (PrevBBS == NULL)		// First in chain;
			{
				BBSChain = BBSRec->BBSNext;
				break;
			}
			PrevBBS->BBSNext = BBSRec->BBSNext;
			break;
		}
	}
}


VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo);

VOID SetupForwardingStruct(struct UserInfo * user)
{
	struct	BBSForwardingInfo * ForwardingInfo;

	char Key[100] =  "BBSForwarding.";
	char Temp[100];

	HKEY hKey=0;
	char RegKey[100] =  "SOFTWARE\\G8BPQ\\BPQ32\\BPQMailChat\\BBSForwarding\\";

	int m;
	struct MsgInfo * Msg;

	ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo));

	if (UsingingRegConfig == 0)
	{
		//	Config from file

		if (isdigit(user->Call[0]) || user->Call[0] == '_')
			strcat(Key, "*");

		strcat(Key, user->Call);

		group = config_lookup (&cfg, Key);

		if (group == NULL)			// No info
			return;
		else
		{
			ForwardingInfo->TOCalls = GetMultiStringValue(group,  "TOCalls");
			ForwardingInfo->ConnectScript = GetMultiStringValue(group,  "ConnectScript");
			ForwardingInfo->ATCalls = GetMultiStringValue(group,  "ATCalls");
			ForwardingInfo->Haddresses = GetMultiStringValue(group,  "HRoutes");
			ForwardingInfo->HaddressesP = GetMultiStringValue(group,  "HRoutesP");
			ForwardingInfo->FWDTimes = GetMultiStringValue(group,  "FWDTimes");

			ForwardingInfo->Enabled = GetIntValue(group, "Enabled");
			ForwardingInfo->ReverseFlag = GetIntValue(group, "RequestReverse");
			ForwardingInfo->AllowBlocked = GetIntValue(group, "AllowBlocked");
			ForwardingInfo->AllowCompressed = GetIntValue(group, "AllowCompressed");
			ForwardingInfo->AllowB1 = GetIntValue(group, "UseB1Protocol");
			ForwardingInfo->AllowB2 = GetIntValue(group, "UseB2Protocol");
			ForwardingInfo->SendCTRLZ = GetIntValue(group, "SendCTRLZ");

			if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2)
				ForwardingInfo->AllowCompressed = TRUE;

			if (ForwardingInfo->AllowCompressed)
				ForwardingInfo->AllowBlocked = TRUE;

			ForwardingInfo->PersonalOnly = GetIntValue(group, "FWDPersonalsOnly");
			ForwardingInfo->SendNew = GetIntValue(group, "FWDNewImmediately");
			ForwardingInfo->FwdInterval = GetIntValue(group, "FwdInterval");
			ForwardingInfo->RevFwdInterval = GetIntValue(group, "RevFWDInterval");
			ForwardingInfo->MaxFBBBlockSize = GetIntValue(group, "MaxFBBBlock");
			ForwardingInfo->ConTimeout = GetIntValue(group, "ConTimeout");

			if (ForwardingInfo->MaxFBBBlockSize == 0)
				ForwardingInfo->MaxFBBBlockSize = 10000;

			if (ForwardingInfo->FwdInterval == 0)
				ForwardingInfo->FwdInterval = 3600;

			if (ForwardingInfo->ConTimeout == 0)
				ForwardingInfo->ConTimeout = 120;

			GetStringValue(group, "BBSHA", Temp, 100);
		
			if (Temp[0])
				ForwardingInfo->BBSHA = _strdup(Temp);
			else
				ForwardingInfo->BBSHA = _strdup("");
		}
	}
	else
	{
#ifndef	LINBPQ

		int retCode,Type,Vallen;

		strcat(RegKey, user->Call);
		retCode = RegOpenKeyEx (REGTREE, RegKey, 0, KEY_QUERY_VALUE, &hKey);

		if (retCode != ERROR_SUCCESS)
			return;
		else
		{
			ForwardingInfo->ConnectScript = RegGetMultiStringValue(hKey,  "Connect Script");
			ForwardingInfo->TOCalls = RegGetMultiStringValue(hKey,  "TOCalls");
			ForwardingInfo->ATCalls = RegGetMultiStringValue(hKey,  "ATCalls");
			ForwardingInfo->Haddresses = RegGetMultiStringValue(hKey,  "HRoutes");
			ForwardingInfo->HaddressesP = RegGetMultiStringValue(hKey,  "HRoutesP");
			ForwardingInfo->FWDTimes = RegGetMultiStringValue(hKey,  "FWD Times");

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "Enabled", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->Enabled,(ULONG *)&Vallen);
				
			Vallen=4;
			retCode += RegQueryValueEx(hKey, "RequestReverse", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->ReverseFlag,(ULONG *)&Vallen);

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "AllowCompressed", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowCompressed,(ULONG *)&Vallen);

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "Use B1 Protocol", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB1,(ULONG *)&Vallen);

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "Use B2 Protocol", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB2,(ULONG *)&Vallen);

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "SendCTRLZ", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendCTRLZ,(ULONG *)&Vallen);

			if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2)
				ForwardingInfo->AllowCompressed = TRUE;
	
			Vallen=4;
			retCode += RegQueryValueEx(hKey, "FWD Personals Only", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->PersonalOnly,(ULONG *)&Vallen);

			Vallen=4;
			retCode += RegQueryValueEx(hKey, "FWD New Immediately", 0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendNew,(ULONG *)&Vallen);

			Vallen=4;
			RegQueryValueEx(hKey,"FWDInterval",0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->FwdInterval,(ULONG *)&Vallen);

			Vallen=4;
			RegQueryValueEx(hKey,"RevFWDInterval",0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->RevFwdInterval,(ULONG *)&Vallen);

			RegQueryValueEx(hKey,"MaxFBBBlock",0,			
				(ULONG *)&Type,(UCHAR *)&ForwardingInfo->MaxFBBBlockSize,(ULONG *)&Vallen);

			if (ForwardingInfo->MaxFBBBlockSize == 0)
				ForwardingInfo->MaxFBBBlockSize = 10000;

			if (ForwardingInfo->FwdInterval == 0)
				ForwardingInfo->FwdInterval = 3600;

			Vallen=0;
			retCode = RegQueryValueEx(hKey,"BBSHA",0 , (ULONG *)&Type,NULL, (ULONG *)&Vallen);

			if (retCode != 0)
			{
				// No Key - Get from WP??
				
				WPRec * ptr = LookupWP(user->Call);

				if (ptr)
				{
					if (ptr->first_homebbs)
					{
						ForwardingInfo->BBSHA = _strdup(ptr->first_homebbs);
					}
				}
			}

			if (Vallen)
			{
				ForwardingInfo->BBSHA = malloc(Vallen);
				RegQueryValueEx(hKey, "BBSHA", 0, (ULONG *)&Type, ForwardingInfo->BBSHA,(ULONG *)&Vallen);
			}

			RegCloseKey(hKey);
		}
#endif
	}

	// Convert FWD Times and H Addresses

	if (ForwardingInfo->FWDTimes)
			SetupFwdTimes(ForwardingInfo);

	if (ForwardingInfo->Haddresses)
			SetupHAddreses(ForwardingInfo);

	if (ForwardingInfo->HaddressesP)
			SetupHAddresesP(ForwardingInfo);

	if (ForwardingInfo->BBSHA)
	{
			if (ForwardingInfo->BBSHA[0])
				SetupHAElements(ForwardingInfo);
			else
			{
				free(ForwardingInfo->BBSHA);
				ForwardingInfo->BBSHA = NULL;
			}
	}

	for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++)
	{
		Msg=MsgHddrPtr[m];

		// If any forward bits are set, increment count on  BBS record.

		if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
		{
			if (Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber))
			{
				user->ForwardingInfo->MsgCount++;
			}
		}
	}
}

VOID * GetMultiStringValue(config_setting_t * group, char * ValueName)
{
	char * ptr1;
	char * MultiString = NULL;
	const char * ptr;
	int Count = 0;
	char ** Value;
	config_setting_t *setting;
	char * Save;

	Value = zalloc(sizeof(void *));				// always NULL entry on end even if no values
	Value[0] = NULL;

	setting = config_setting_get_member (group, ValueName);
	
	if (setting)
	{
		ptr =  config_setting_get_string (setting);

		Save = _strdup(ptr);			// DOnt want to change config string
		ptr = Save;
	
		while (ptr && strlen(ptr))
		{
			ptr1 = strchr(ptr, '|');
			
			if (ptr1)
				*(ptr1++) = 0;

			if (strlen(ptr))		// ignore null elements
			{
				Value = realloc(Value, (Count+2) * sizeof(void *));
				Value[Count++] = _strdup(ptr);
			}
			ptr = ptr1;
		}
		free(Save);
	}

	Value[Count] = NULL;
	return Value;
}


VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName)
{
#ifdef LINBPQ
	return NULL;
#else
	int retCode,Type,Vallen;
	char * MultiString = NULL;
	int ptr, len;
	int Count = 0;
	char ** Value;

	Value = zalloc(sizeof(void *));				// always NULL entry on end even if no values

	Value[0] = NULL;

	Vallen=0;


	retCode = RegQueryValueEx(hKey, ValueName, 0, (ULONG *)&Type, NULL, (ULONG *)&Vallen);

	if ((retCode != 0)  || (Vallen < 3))		// Two nulls means empty multistring
	{
		free(Value);
		return FALSE;
	}

	MultiString = malloc(Vallen);

	retCode = RegQueryValueEx(hKey, ValueName, 0,			
			(ULONG *)&Type,(UCHAR *)MultiString,(ULONG *)&Vallen);

	ptr=0;

	while (MultiString[ptr])
	{
		len=strlen(&MultiString[ptr]);

		Value = realloc(Value, (Count+2) * sizeof(void *));
		Value[Count++] = _strdup(&MultiString[ptr]);
		ptr+= (len + 1);
	}

	Value[Count] = NULL;

	free(MultiString);

	return Value;
#endif
}

VOID FreeForwardingStruct(struct UserInfo * user)
{
	struct	BBSForwardingInfo * ForwardingInfo;
	int i;


	ForwardingInfo = user->ForwardingInfo;

	FreeList(ForwardingInfo->TOCalls);
	FreeList(ForwardingInfo->ATCalls);
	FreeList(ForwardingInfo->Haddresses);
	FreeList(ForwardingInfo->HaddressesP);

	i=0;
	if(ForwardingInfo->HADDRS)
	{
		while(ForwardingInfo->HADDRS[i])
		{
			FreeList(ForwardingInfo->HADDRS[i]);
			i++;
		}
		free(ForwardingInfo->HADDRS);
		free(ForwardingInfo->HADDROffet);
	}

	i=0;
	if(ForwardingInfo->HADDRSP)
	{
		while(ForwardingInfo->HADDRSP[i])
		{
			FreeList(ForwardingInfo->HADDRSP[i]);
			i++;
		}
		free(ForwardingInfo->HADDRSP);
	}

	FreeList(ForwardingInfo->ConnectScript);
	FreeList(ForwardingInfo->FWDTimes);
	if (ForwardingInfo->FWDBands)
	{
		i=0;
		while(ForwardingInfo->FWDBands[i])
		{
			free(ForwardingInfo->FWDBands[i]);
			i++;
		}
		free(ForwardingInfo->FWDBands);
	}
	if (ForwardingInfo->BBSHAElements)
	{
		i=0;
		while(ForwardingInfo->BBSHAElements[i])
		{
			free(ForwardingInfo->BBSHAElements[i]);
			i++;
		}
		free(ForwardingInfo->BBSHAElements);
	}
	free(ForwardingInfo->BBSHA);

}

VOID FreeList(char ** Hddr)
{
	VOID ** Save;
	
	if (Hddr)
	{
		Save = (void **)Hddr;
		while(Hddr[0])
		{
			free(Hddr[0]);
			Hddr++;
		}	
		free(Save);
	}
}


VOID ReinitializeFWDStruct(struct UserInfo * user)
{
	if (user->ForwardingInfo)
	{
		FreeForwardingStruct(user);
		free(user->ForwardingInfo); 
	}

	SetupForwardingStruct(user);

}

VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo)
{
	char ** Times = ForwardingInfo->FWDTimes;
	int Start, End;
	int Count = 0;

	ForwardingInfo->FWDBands = zalloc(sizeof(struct FWDBAND));

	if (Times)
	{
		while(Times[0])
		{
			ForwardingInfo->FWDBands = realloc(ForwardingInfo->FWDBands, (Count+2)* sizeof(struct FWDBAND));
			ForwardingInfo->FWDBands[Count] = zalloc(sizeof(struct FWDBAND));

			Start = atoi(Times[0]);
			End = atoi(&Times[0][5]);

			ForwardingInfo->FWDBands[Count]->FWDStartBand =  (time_t)(Start / 100) * 3600 + (Start % 100) * 60; 
			ForwardingInfo->FWDBands[Count]->FWDEndBand =  (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; 

			Count++;
			Times++;
		}
		ForwardingInfo->FWDBands[Count] = NULL;
	}
}
void StartForwarding(int BBSNumber, char ** TempScript)
{
	struct UserInfo * user;
	struct	BBSForwardingInfo * ForwardingInfo ;
	time_t NOW = time(NULL);


	for (user = BBSChain; user; user = user->BBSNext)
	{
		// See if any messages are queued for this BBS

		ForwardingInfo = user->ForwardingInfo;

		if ((BBSNumber == 0) || (user->BBSNumber == BBSNumber))
			if (ForwardingInfo)
				if (ForwardingInfo->Enabled || BBSNumber)		// Menu Command overrides enable
					if (ForwardingInfo->ConnectScript  && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0])
						if (BBSNumber || SeeifMessagestoForward(user->BBSNumber, NULL) ||
							(ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) // Menu Command overrides Reverse
						{
							user->ForwardingInfo->ScriptIndex = -1;	// Incremented before being used
						
							// See if TempScript requested
							
							if (user->ForwardingInfo->TempConnectScript)
								FreeList(user->ForwardingInfo->TempConnectScript);

							user->ForwardingInfo->TempConnectScript = TempScript;
						
							if (ConnecttoBBS(user))
								ForwardingInfo->Forwarding = TRUE;
						}
	}

	return;
}

size_t fwritex(CIRCUIT * conn, void * _Str, size_t _Size, size_t _Count, FILE * _File)
{
	if (_File)
		return fwrite(_Str, _Size, _Count, _File);

	// Appending to MailBuffer

	memcpy(&conn->MailBuffer[conn->InputLen], _Str, _Count);
	conn->InputLen += (int)_Count;

	return _Count;
}


BOOL ForwardMessagestoFile(CIRCUIT * conn, char * FN)
{
	BOOL AddCRLF = FALSE;
	BOOL AutoImport = FALSE;
	FILE * Handle = NULL;
	char * Context;
	BOOL Email = FALSE;
	time_t now = time(NULL);
	char * param;

	FN = strtok_s(FN, " ,", &Context); 

	param = strtok_s(NULL, " ,", &Context); 

	if (param)
	{
		if (_stricmp(param, "ADDCRLF") == 0)
			AddCRLF = TRUE;

		if (_stricmp(param, "AutoImport") == 0)
			AutoImport = TRUE;

		param = strtok_s(NULL, " ,", &Context); 

		if (param)
		{
			if (_stricmp(param, "ADDCRLF") == 0)
				AddCRLF = TRUE;

			if (_stricmp(param, "AutoImport") == 0)
				AutoImport = TRUE;

		}
	}
	// If FN is an email address, write to a temp file, and send via rms or emali gateway
	
	if (strchr(FN, '@') || _memicmp(FN, "RMS:", 4) == 0)
	{
		Email = TRUE;
		AddCRLF =TRUE;
		conn->MailBuffer=malloc(100000);
		conn->MailBufferSize=100000;
		conn->InputLen = 0;
	}
	else
	{
		Handle = fopen(FN, "ab");

		if (Handle == NULL)
		{
			int err = GetLastError();
			Logprintf(LOG_BBS, conn, '!', "Failed to open Export File %s", FN);
			return FALSE;
		}
	}

	while (FindMessagestoForward(conn))
	{
		struct MsgInfo * Msg;
		struct tm * tm;
		time_t temp;
		char * MsgBytes = ReadMessageFile(conn->FwdMsg->number);
		int MsgLen;
		char * MsgPtr;
		char Line[256];
		int len;
		struct UserInfo * user = conn->UserPointer;
		int Index = 0;

		Msg = conn->FwdMsg;

		if (Email)
			if (conn->InputLen + Msg->length + 500 > conn->MailBufferSize)
				break;

		if (Msg->type == 'P')
			Index = PMSG;
		else if (Msg->type == 'B')
			Index = BMSG;
		else if (Msg->type == 'T')
			Index = TMSG;


		if (Msg->via[0])
			len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to,
						Msg->via, Msg->from, Msg->bid);
		else
			len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid);
	
		fwritex(conn, Line, 1, len, Handle);

		len = sprintf(Line, "%s\r\n", Msg->title);
		fwritex(conn, Line, 1, len, Handle);
		
		if (MsgBytes == 0)
		{
			MsgBytes = _strdup("Message file not found\r\n");
			conn->FwdMsg->length = (int)strlen(MsgBytes);
		}

		MsgPtr = MsgBytes;
		MsgLen = conn->FwdMsg->length;

		// If a B2 Message, remove B2 Header

		if (conn->FwdMsg->B2Flags & B2Msg)
		{		
			// Remove all B2 Headers, and all but the first part.
					
			MsgPtr = strstr(MsgBytes, "Body:");
			
			if (MsgPtr)
			{
				MsgLen = atoi(&MsgPtr[5]);
				MsgPtr = strstr(MsgBytes, "\r\n\r\n");		// Blank Line after headers
	
				if (MsgPtr)
					MsgPtr +=4;
				else
					MsgPtr = MsgBytes;
			
			}
			else
				MsgPtr = MsgBytes;
		}

		memcpy(&temp, &Msg->datereceived, sizeof(time_t));
		tm = gmtime(&temp);	

		len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n",
				tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min,
				conn->FwdMsg->number, BBSName, HRoute, RlineVer);

		fwritex(conn, Line, 1, len, Handle);

		if (memcmp(MsgPtr, "R:", 2) != 0)    // No R line, so must be our message - put blank line after header
			fwritex(conn, "\r\n", 1, 2, Handle);

		fwritex(conn, MsgPtr, 1, MsgLen, Handle);

		if (MsgPtr[MsgLen - 2] == '\r')
			fwritex(conn, "/EX\r\n", 1, 5, Handle);
		else
			fwritex(conn, "\r\n/EX\r\n", 1, 7, Handle);

		if (AddCRLF)
			fwritex(conn, "\r\n", 1, 2, Handle);

		free(MsgBytes);
			
		user->Total.MsgsSent[Index]++;
		user->Total.BytesForwardedOut[Index] += MsgLen;

		Msg->datechanged = now;
			
		clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber);
		set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber);

		//  Only mark as forwarded if sent to all BBSs that should have it
			
		if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0)
		{
			conn->FwdMsg->status = 'F';			// Mark as forwarded
			conn->FwdMsg->datechanged=time(NULL);
		}

		conn->UserPointer->ForwardingInfo->MsgCount--;
	}

	if (Email)
	{
		struct MsgInfo * Msg;
		BIDRec * BIDRec;

		if (conn->InputLen == 0)
		{
			free(conn->MailBuffer);
			conn->MailBufferSize=0;
			conn->MailBuffer=0;

			return TRUE;
		}

		// Allocate a message Record slot

		Msg = AllocateMsgRecord();

		// Set number here so they remain in sequence
		
		GetSemaphore(&MsgNoSemaphore, 0);
		Msg->number = ++LatestMsg;
		FreeSemaphore(&MsgNoSemaphore);
		MsgnotoMsg[Msg->number] = Msg;

		Msg->type = 'P';
		Msg->status = 'N';
		Msg->datecreated = Msg->datechanged = Msg->datereceived = now;

		strcpy(Msg->from, BBSName);

		sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName);

		if (AutoImport)
			sprintf(Msg->title, "Batched messages for AutoImport from BBS %s",  BBSName);
		else
			sprintf(Msg->title, "Batched messages from BBS %s",  BBSName);

		Msg->length = conn->InputLen; 
		CreateMessageFile(conn, Msg);

		BIDRec = AllocateBIDRecord();

		strcpy(BIDRec->BID, Msg->bid);
		BIDRec->mode = Msg->type;
		BIDRec->u.msgno = LOWORD(Msg->number);
		BIDRec->u.timestamp = LOWORD(time(NULL)/86400);

		if (_memicmp(FN, "SMTP:", 5) == 0)
		{
			strcpy(Msg->via, &FN[5]);
			SMTPMsgCreated=TRUE;
		}
		else
		{
			strcpy(Msg->to, "RMS");
			if (_memicmp(FN, "RMS:", 4) == 0)
				strcpy(Msg->via, &FN[4]);
			else
				strcpy(Msg->via, FN);
		}

		MatchMessagetoBBSList(Msg, conn);

		SaveMessageDatabase();
		SaveBIDDatabase();
	}
	else
		fclose(Handle);

	SaveMessageDatabase();
	return TRUE;
}

BOOL ForwardMessagetoFile(struct MsgInfo * Msg, FILE * Handle)
{
	struct tm * tm;
	time_t temp;

	char * MsgBytes = ReadMessageFile(Msg->number);
	char * MsgPtr;
	char Line[256];
	int len;
	int MsgLen = Msg->length;

	if (Msg->via[0])
		len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to,
			Msg->via, Msg->from, Msg->bid);
	else
		len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid);
	
	fwrite(Line, 1, len, Handle);

	len = sprintf(Line, "%s\r\n", Msg->title);
	fwrite(Line, 1, len, Handle);
		
	if (MsgBytes == 0)
	{
		MsgBytes = _strdup("Message file not found\r\n");
		MsgLen = (int)strlen(MsgBytes);
		}

	MsgPtr = MsgBytes;

	// If a B2 Message, remove B2 Header

	if (Msg->B2Flags & B2Msg)
	{		
		// Remove all B2 Headers, and all but the first part.
					
		MsgPtr = strstr(MsgBytes, "Body:");
			
		if (MsgPtr)
		{
			MsgLen = atoi(&MsgPtr[5]);
			
			MsgPtr= strstr(MsgBytes, "\r\n\r\n");		// Blank Line after headers
	
			if (MsgPtr)
				MsgPtr +=4;
			else
				MsgPtr = MsgBytes;
			
		}
		else
			MsgPtr = MsgBytes;
	}

	memcpy(&temp, &Msg->datereceived, sizeof(time_t));
	tm = gmtime(&temp);	

	len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n",
			tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min,
			Msg->number, BBSName, HRoute, RlineVer);

	fwrite(Line, 1, len, Handle);

	if (memcmp(MsgPtr, "R:", 2) != 0)    // No R line, so must be our message - put blank line after header
		fwrite("\r\n", 1, 2, Handle);

	fwrite(MsgPtr, 1, MsgLen, Handle);

	if (MsgPtr[MsgLen - 2] == '\r')
		fwrite("/EX\r\n", 1, 5, Handle);
	else
		fwrite("\r\n/EX\r\n", 1, 7, Handle);

	free(MsgBytes);
		
	return TRUE;

}

BOOL ConnecttoBBS (struct UserInfo * user)
{
	int n, p;
	CIRCUIT * conn;
	struct	BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo;

	for (n = NumberofStreams-1; n >= 0 ; n--)
	{
		conn = &Connections[n];
		
		if (conn->Active == FALSE)
		{
			p = conn->BPQStream;
			memset(conn, 0, sizeof(ConnectionInfo));		// Clear everything
			conn->BPQStream = p;

			// Can't set Active until Connected or Stuck Session detertor can clear session.
			// But must set Active before Connected() runs or will appear is Incoming Connect.
			// Connected() is semaphored, so get semaphore before ConnectUsingAppl
			// Probably better to semaphore lost session code instead


			strcpy(conn->Callsign, user->Call); 
			conn->BBSFlags |= (RunningConnectScript | OUTWARDCONNECT);
			conn->UserPointer = user;

			Logprintf(LOG_BBS, conn, '|', "Connecting to BBS %s", user->Call);

			ForwardingInfo->MoreLines = TRUE;
			
			GetSemaphore(&ConSemaphore, 1);
			conn->Active = TRUE;
			ConnectUsingAppl(conn->BPQStream, BBSApplMask);
			FreeSemaphore(&ConSemaphore);

			// If we are sending to a dumb pms we may need to connect using the message sender's callsign.
			// But we wont know until we run the connect script, which is a bit late to change call. Could add
			// flag to forwarding config, but easier to look for SETCALLTOSENDER in the connect script.

			if (strstr(ForwardingInfo->ConnectScript[0], "SETCALLTOSENDER"))
			{
				conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE;
				conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999;
				
				if (FindMessagestoForward(conn) && conn->FwdMsg)
				{
					// We have a message to send

					struct MsgInfo * Msg;
					unsigned char AXCall[7];

					Msg = conn->FwdMsg;
					ConvToAX25(Msg->from, AXCall);
					ChangeSessionCallsign(p, AXCall);

					conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER | NEWPACCOM;
					conn->NextMessagetoForward = 0;		// was set by FindMessages
				}
				conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE;
			}
#ifdef LINBPQ
			{
				BPQVECSTRUC * SESS;	
				SESS = &BPQHOSTVECTOR[conn->BPQStream - 1];

				if (SESS->HOSTSESSION == NULL)
				{
					Logprintf(LOG_BBS, NULL, '|', "No L4 Sessions for connect to BBS %s", user->Call);
					return FALSE;
				}

				SESS->HOSTSESSION->Secure_Session = 1;
			}
#endif

			strcpy(conn->Callsign, user->Call);

			//	Connected Event will trigger connect to remote system

			RefreshMainWindow();

			return TRUE;
		}
	}

	Logprintf(LOG_BBS, NULL, '|', "No Free Streams for connect to BBS %s", user->Call);

	return FALSE;
	
}

struct DelayParam
{
	struct UserInfo * User;
	CIRCUIT * conn;
	int Delay;
};

struct DelayParam DParam;		// Not 100% safe, but near enough

VOID ConnectDelayThread(struct DelayParam * DParam)
{
	struct UserInfo * User = DParam->User;
	int Delay = DParam->Delay;

	User->ForwardingInfo->Forwarding = TRUE;		// Minimize window for two connects

	Sleep(Delay);

	User->ForwardingInfo->Forwarding = TRUE;
	ConnecttoBBS(User);
	
	return;
}

VOID ConnectPauseThread(struct DelayParam * DParam)
{
	CIRCUIT * conn = DParam->conn;
	int Delay = DParam->Delay;
	char Msg[] = "Pause Ok\r    ";

	Sleep(Delay);

	ProcessBBSConnectScript(conn, Msg, 9);
	
	return;
}


/*
BOOL ProcessBBSConnectScriptInner(CIRCUIT * conn, char * Buffer, int len);


BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len)
{
	BOOL Ret;
	GetSemaphore(&ScriptSEM);
	Ret = ProcessBBSConnectScriptInner(conn, Buffer, len);
	FreeSemaphore(&ScriptSEM);

	return Ret;
}
*/

BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len)
{
	struct	BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo;
	char ** Scripts;
	char callsign[10];
	int port, sesstype, paclen, maxframe, l4window;
	char * ptr, * ptr2;

	WriteLogLine(conn, '<',Buffer, len-1, LOG_BBS);

	Buffer[len]=0;
	_strupr(Buffer);

	if (ForwardingInfo->TempConnectScript)
		Scripts = ForwardingInfo->TempConnectScript;
	else
		Scripts = ForwardingInfo->ConnectScript;	

	if (ForwardingInfo->ScriptIndex == -1)
	{
		// First Entry - if first line is TIMES, check and skip forward if necessary
	
		int n = 0;
		int Start, End;
		time_t now = time(NULL), StartSecs, EndSecs;
		char * Line;

		if (Localtime)
			now -= (time_t)_MYTIMEZONE; 

		now %= 86400;
		Line = Scripts[n];

		// Skip comments

		while (Line && ((strcmp(Line, " ") == 0 || Line[0] == ';' || Line[0] == '#')))
		{
			n++;
			Line = Scripts[n];
		}

		if (Line == NULL)
		{
			// No more lines - Disconnect

			conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
			Disconnect(conn->BPQStream);
			return FALSE;
		}

		if (_memicmp(Line, "TIMES", 5) == 0)
		{
		NextBand:
			Start = atoi(&Line[6]);
			End = atoi(&Line[11]);

			StartSecs =  (time_t)(Start / 100) * 3600 + (Start % 100) * 60; 
			EndSecs =  (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59;

			if ((StartSecs <= now) && (EndSecs >= now))
				goto InBand;	// In band

			// Look for next TIME
		NextLine:
			Line = Scripts[++n];

			if (Line == NULL)
			{
				// No more lines - Disconnect

				conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
				Disconnect(conn->BPQStream);
				return FALSE;
			}

			if (_memicmp(Line, "TIMES", 5) != 0)
				goto NextLine;
			else
				goto NextBand;
InBand:
			ForwardingInfo->ScriptIndex = n;	
		}

	}
	else
	{
		// Dont check first time through

		if (strcmp(Buffer, "*** CONNECTED  ") != 0)
		{
			if (Scripts[ForwardingInfo->ScriptIndex] == NULL ||
				_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0	||		// Only Check until script is finished
				_memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0)			// Only Check until script is finished
			{
				ForwardingInfo->MoreLines = FALSE;
			}
			if (!ForwardingInfo->MoreLines)
				goto CheckForSID;
			}
	}

	if (strstr(Buffer, "BUSY") || strstr(Buffer, "FAILURE") ||
		(strstr(Buffer, "DOWNLINK") && strstr(Buffer, "ATTEMPTING") == 0) ||
		strstr(Buffer, "SORRY") || strstr(Buffer, "INVALID") || strstr(Buffer, "RETRIED") ||
		strstr(Buffer, "NO CONNECTION TO") || strstr(Buffer, "ERROR - ") ||
		strstr(Buffer, "UNABLE TO CONNECT") ||  strstr(Buffer, "DISCONNECTED") ||
		strstr(Buffer, "FAILED TO CONNECT") ||	strstr(Buffer, "REJECTED"))
	{
		// Connect Failed

		char * Cmd = Scripts[++ForwardingInfo->ScriptIndex];
		int Delay = 1000;
	
		// Look for an alternative connect block (Starting with ELSE)

	ElseLoop:

		// Skip any comments
		
		while (Cmd && ((strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#')))
			Cmd = Scripts[++ForwardingInfo->ScriptIndex];

		// TIMES terminates a script

		if (Cmd == 0 || _memicmp(Cmd, "TIMES", 5) == 0)			// Only Check until script is finished
		{
			conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
			Disconnect(conn->BPQStream);
			return FALSE;
		}

		if (_memicmp(Cmd, "ELSE", 4) != 0)
		{
			Cmd = Scripts[++ForwardingInfo->ScriptIndex];
			goto ElseLoop;
		}

		if (_memicmp(&Cmd[5], "DELAY", 5) == 0)
			Delay = atoi(&Cmd[10]) * 1000;
		else
			Delay = 1000;


		conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
		Disconnect(conn->BPQStream);

		DParam.Delay = Delay;
		DParam.User = conn->UserPointer;

		_beginthread((void (*)(void *))ConnectDelayThread, 0, &DParam);
		
		return FALSE;
	}

	// The pointer is only updated when we get the connect, so we can tell when the last line is acked
	// The first entry is always from Connected event, so don't have to worry about testing entry -1 below


	// NETROM to  KA node returns

	//c 1 milsw
	//WIRAC:N9PMO-2} Connected to MILSW
	//###CONNECTED TO NODE MILSW(N9ZXS) CHANNEL A
	//You have reached N9ZXS's KA-Node MILSW
	//ENTER COMMAND: B,C,J,N, or Help ?

	//C KB9PRF-7
	//###LINK MADE
	//###CONNECTED TO NODE KB9PRF-7(KB9PRF-4) CHANNEL A

	// Look for (Space)Connected so we aren't fooled by ###CONNECTED TO NODE, which is not
	// an indication of a connect.

	if (strstr(Buffer, " CONNECTED") || strstr(Buffer, "PACLEN") || strstr(Buffer, "IDLETIME") ||
		strstr(Buffer, "OK") || strstr(Buffer, "###LINK MADE") || strstr(Buffer, "VIRTUAL CIRCUIT ESTABLISHED"))
	{
		// If connected to SYNC, save IP address and port

		char * Cmd;

		if (strstr(Buffer, "*** CONNECTED TO SYNC"))
		{
			char * IPAddr = &Buffer[22];
			char * Port = strlop(IPAddr, ':');

			if (Port)
			{
				if (conn->SyncHost)
					free(conn->SyncHost);

				conn->SyncHost = _strdup(IPAddr);
				conn->SyncPort = atoi(Port);
			}
		}

		if (conn->SkipConn)
		{
			conn->SkipConn = FALSE;
			return TRUE;
		}

	LoopBack:

		Cmd = Scripts[++ForwardingInfo->ScriptIndex];

		// Only Check until script is finished
		
		if (Cmd && (strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#'))
			goto LoopBack;			// Blank line 

		if (Cmd && _memicmp(Cmd, "TIMES", 5) != 0 && _memicmp(Cmd, "ELSE", 4) != 0)			// Only Check until script is finished
		{
			if (_memicmp(Cmd, "MSGTYPE", 7) == 0)
			{
				char * ptr;
				
				// Select Types to send. Only send types in param. Only reverse if R in param

				_strupr(Cmd);
				
				Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd);

				conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE;

				strcpy(conn->MSGTYPES, &Cmd[8]);

				if (strchr(&Cmd[8], 'R')) conn->DoReverse = TRUE;

				ptr = strchr(&Cmd[8], 'B');
	
				if (ptr)
				{
					conn->SendB = TRUE;
					conn->MaxBLen = atoi(++ptr);
					if (conn->MaxBLen == 0) conn->MaxBLen = 99999999;
				}

				ptr = strchr(&Cmd[8], 'T');
	
				if (ptr)
				{
					conn->SendT = TRUE;
					conn->MaxTLen = atoi(++ptr);
					if (conn->MaxTLen == 0) conn->MaxTLen = 99999999;
				}
				ptr = strchr(&Cmd[8], 'P');

				if (ptr)
				{
					conn->SendP = TRUE;
					conn->MaxPLen = atoi(++ptr);
					if (conn->MaxPLen == 0) conn->MaxPLen = 99999999;
				}

				// If nothing to do, terminate script

				if (conn->DoReverse || SeeifMessagestoForward(conn->UserPointer->BBSNumber, conn))
					goto LoopBack;

				Logprintf(LOG_BBS, conn, '?', "Nothing to do - quitting");
				conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
				Disconnect(conn->BPQStream);
				return FALSE;
			}

			if (_memicmp(Cmd, "INTERLOCK ", 10) == 0)
			{
				// Used to limit connects on a port to 1

				int Port;
				char Option[80];

				Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd);

				sscanf(&Cmd[10], "%d %s", &Port, &Option[0]);

				if (CountConnectionsOnPort(Port))
				{								
					Logprintf(LOG_BBS, conn, '?', "Interlocked Port is busy - quitting");
					conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
					Disconnect(conn->BPQStream);
					return FALSE;
				}

				goto LoopBack;
			}

			if (_memicmp(Cmd, "IDLETIME ", 9) == 0)
			{
				int idle = atoi(&Cmd[9]);

				ChangeSessionIdletime(conn->BPQStream, idle);
				goto LoopBack;
			}

			if (_memicmp(Cmd, "RADIO AUTH", 10) == 0)
			{
				// Generate a Password to enable RADIO commands on a remote node
				char AuthCommand[80];

				_strupr(Cmd);
				strcpy(AuthCommand, Cmd);

				CreateOneTimePassword(&AuthCommand[11], &Cmd[11], 0); 

				nodeprintf(conn, "%s\r", AuthCommand);
				return TRUE;
			}

			if (_memicmp(Cmd, "SKIPCON", 7) == 0)
			{
				// Remote Node sends Connected in CTEXT - we need to swallow it

				conn->SkipConn = TRUE;
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "SendWL2KPM", 10) == 0|| _memicmp(Cmd, "SendWL2KFW", 10) == 0)
			{
				// Send ;FW: command

				conn->SendWL2KFW = TRUE;
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "SKIPPROMPT", 10) == 0)
			{
				// Remote Node sends > at end of CTEXT - we need to swallow it

				conn->SkipPrompt++;
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "TEXTFORWARDING", 10) == 0)
			{
				conn->BBSFlags |= TEXTFORWARDING;			
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "SETCALLTOSENDER", 15) == 0)
			{
				conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER;			
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "RADIOONLY", 9) == 0)
			{
				conn->BBSFlags |= WINLINKRO;			
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "SYNC", 4) == 0)
			{
				conn->BBSFlags |= SYNCMODE;			
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "NEEDLF", 6) == 0)
			{
				conn->BBSFlags |= NEEDLF;			
				goto CheckForEnd;
			}

			if (_memicmp(Cmd, "MCASTRX", 6) == 0)
			{
				conn->BBSFlags |= MCASTRX;	
				conn->MCastListenTime = atoi(&Cmd[7]) * 6; // Time to run session for *6 as value is mins put timer ticks 10 secs

				// send MCAST to Node
				
				nodeprintfEx(conn, "MCAST\r");
				return TRUE;
			}

			if (_memicmp(Cmd, "FLARQ", 5) == 0)
			{
				conn->BBSFlags |= FLARQMAIL;

		CheckForEnd:
				if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL ||
						memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0	||		// Only Check until script is finished
						memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0)			// Only Check until script is finished
					ForwardingInfo->MoreLines = FALSE;
			
				goto LoopBack;
			}
			if (_memicmp(Cmd, "PAUSE", 5) == 0)
			{
				// Pause script

				Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd);

				DParam.Delay = atoi(&Cmd[6]) * 1000;
				DParam.conn = conn;

				_beginthread((void (*)(void *))ConnectPauseThread, 0, &DParam);

				return TRUE;
			}

			if (_memicmp(Cmd, "FILE", 4) == 0)
			{
				if (Cmd[4] == 0)
				{
					// Missing Filename
					
					Logprintf(LOG_BBS, conn, '!', "Export file name missing");
				}
				else
					ForwardMessagestoFile(conn, &Cmd[5]);
				
				conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
				Disconnect(conn->BPQStream);
				return FALSE;
			}

			if (_memicmp(Cmd, "SMTP", 4) == 0)
			{
				conn->NextMessagetoForward = FirstMessageIndextoForward;
				conn->UserPointer->Total.ConnectsOut++;

				SendAMPRSMTP(conn);
				conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
				Disconnect(conn->BPQStream);
				return FALSE;
			}


			if (_memicmp(Cmd, "IMPORT", 6) == 0)
			{
				char * File, * Context;
				int Num;
				char * Temp = _strdup(Cmd);

				File = strtok_s(&Temp[6], " ", &Context);

				if (File && File[0]) 
				{
					Num = ImportMessages(NULL, File, TRUE);

					Logprintf(LOG_BBS, NULL, '|', "Imported %d Message(s) from %s", Num, File);

					if (Context && _stricmp(Context, "delete") == 0)
						DeleteFile(File);
				}
				free(Temp);

				if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL ||
						memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0	||		// Only Check until script is finished
						memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0)			// Only Check until script is finished
				{
					conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
					Disconnect(conn->BPQStream);
					return FALSE;
				}
				goto LoopBack;
			}

			// Anything else is sent to Node

			// Replace \ with # so can send commands starting with #

			if (Cmd[0] == '\\')
			{
				Cmd[0] = '#';
				nodeprintfEx(conn, "%s\r", Cmd);
				Cmd[0] = '\\';						// Put \ back in script
			}
			else
				nodeprintfEx(conn, "%s\r", Cmd);
			
			return TRUE;
		}

		// End of script.

		ForwardingInfo->MoreLines = FALSE;

		if (conn->BBSFlags & MCASTRX)
		{
			// No session with Multicast, so no SID

			conn->BBSFlags &= ~RunningConnectScript;
			return TRUE;
		}

		if (conn->BBSFlags & FLARQMAIL)
		{
			// FLARQ doesnt send a prompt - Just send message(es)

			conn->UserPointer->Total.ConnectsOut++;
			conn->BBSFlags &= ~RunningConnectScript;
			ForwardingInfo->LastReverseForward = time(NULL);

			//	Update Paclen
					
			GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window);
		
			if (paclen > 0)
				conn->paclen = paclen;

			SendARQMail(conn);
			return TRUE;
		}


		return TRUE;
	}

	ptr = strchr(Buffer, '}');

	if (ptr && ForwardingInfo->MoreLines) // Beware it could be part of ctext
	{
		// Could be respsonse to Node Command 

		ptr+=2;
		
		ptr2 = strchr(&ptr[0], ' ');

		if (ptr2)
		{
			if (_memicmp(ptr, Scripts[ForwardingInfo->ScriptIndex], ptr2-ptr) == 0)	// Reply to last sscript command
			{
				if (Scripts[ForwardingInfo->ScriptIndex+1] && _memicmp(Scripts[ForwardingInfo->ScriptIndex+1], "else", 4) == 0)
				{
					// stray match or misconfigured

					return TRUE;
				}

				ForwardingInfo->ScriptIndex++;
		
				if (Scripts[ForwardingInfo->ScriptIndex])
					if (_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) != 0)	
					nodeprintf(conn, "%s\r", Scripts[ForwardingInfo->ScriptIndex]);

				return TRUE;
			}
		}
	}

	// Not Success or Fail. If last line is still outstanding, wait fot Response
	//		else look for SID or Prompt

	if (conn->SkipPrompt && Buffer[len-2] == '>')
	{
		conn->SkipPrompt--;
		return TRUE;
	}

	if (ForwardingInfo->MoreLines)
		return TRUE;

	// No more steps, Look for SID or Prompt

CheckForSID:

	if (strstr(Buffer, "POSYNCHELLO"))			// RMS RELAY Sync process
	{
		conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
		conn->NextMessagetoForward = FirstMessageIndextoForward;
		conn->UserPointer->Total.ConnectsOut++;
		ForwardingInfo->LastReverseForward = time(NULL);

		ProcessLine(conn, 0, Buffer, len);
		return FALSE;
	}

	if (strstr(Buffer, "SORRY, NO"))			// URONODE
	{
		conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
		Disconnect(conn->BPQStream);
		return FALSE;
	}

	if (memcmp(Buffer, ";PQ: ", 5) == 0)
	{
		// Secure CMS challenge

		int Len;
		struct UserInfo * User = conn->UserPointer;
		char * Pass = User->CMSPass;
		int Response ;
		char RespString[12];
		char ConnectingCall[10];

#ifdef LINBPQ
		BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0];
#else
		BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR;
#endif

		SESS += conn->BPQStream - 1;

		ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall);

		strlop(ConnectingCall, ' ');

		if (Pass[0] == 0)
		{
			Pass = User->pass;		// Old Way
			if (Pass[0] == 0)
			{
				strlop(ConnectingCall, '-');
				User = LookupCall(ConnectingCall);
				if (User)
					Pass = User->CMSPass;
			}
		}

		// 

		Response = GetCMSHash(&Buffer[5], Pass);

		sprintf(RespString, "%010d", Response);

		Len = sprintf(conn->SecureMsg, ";PR: %s\r", &RespString[2]);

		// Save challengs in case needed for FW lines

		strcpy(conn->PQChallenge, &Buffer[5]);

		return FALSE;
	}

/*	if (memcmp(Buffer, "[AEA-", 4) == 0 || memcmp(Buffer, "[AEA PK", 7) == 0 || (conn->BBSFlags & TEXTFORWARDING))
	{
		// PK232. Don't send a SID, and switch to Text Mode

		conn->BBSFlags |= (BBS | TEXTFORWARDING);
		conn->Flags |= SENDTITLE;

		// Send Message. There is no mechanism for reverse forwarding

		if (FindMessagestoForward(conn) && conn->FwdMsg)
		{
			struct MsgInfo * Msg;

			// Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ 

			Msg = conn->FwdMsg;

			if ((conn->BBSFlags & SETCALLTOSENDER))
				nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to,
				(Msg->via[0]) ? Msg->via : conn->UserPointer->Call);
			else
				nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to,
				(Msg->via[0]) ? Msg->via : conn->UserPointer->Call, 
				Msg->from, Msg->bid);
		}
		else
		{
			conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
			Disconnect(conn->BPQStream);
			return FALSE;
		}

		return TRUE;
	}
*/

	if (Buffer[0] == '[' && Buffer[len-2] == ']')		// SID
	{
		// Update PACLEN

		GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window);

		if (paclen > 0)
			conn->paclen = paclen;

		
		Parse_SID(conn, &Buffer[1], len-4);
			
		if (conn->BBSFlags & FBBForwarding)
		{
			conn->FBBIndex = 0;		// ready for first block;
			memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine));
			conn->FBBChecksum = 0;
		}

		return TRUE;
	}

	if (memcmp(Buffer, "[PAKET ", 7) == 0)
	{
		conn->BBSFlags |= BBS;
		conn->BBSFlags |= MBLFORWARDING;
	}

	if (Buffer[len-2] == '>')
	{
		if (conn->SkipPrompt)
		{
			conn->SkipPrompt--;
			return TRUE;
		}

		conn->NextMessagetoForward = FirstMessageIndextoForward;
		conn->UserPointer->Total.ConnectsOut++;
		conn->BBSFlags &= ~RunningConnectScript;
		ForwardingInfo->LastReverseForward = time(NULL);


		// I don't think this is needed any more but will leave in for now
/*		if (memcmp(Buffer, "[AEA-", 4) == 0 || memcmp(Buffer, "(AEA PK", 7) == 0 || (conn->BBSFlags & TEXTFORWARDING))
		{
			// PK232. Don't send a SID, and switch to Text Mode

			conn->BBSFlags |= (BBS | TEXTFORWARDING);
			conn->Flags |= SENDTITLE;

			// Send Message. There is no mechanism for reverse forwarding

			if (FindMessagestoForward(conn) && conn->FwdMsg)
			{
				struct MsgInfo * Msg;
				
				// Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ 

				Msg = conn->FwdMsg;
		
				if ((conn->BBSFlags & SETCALLTOSENDER))
					nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to,
						(Msg->via[0]) ? Msg->via : conn->UserPointer->Call);
				else
					nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to,
						(Msg->via[0]) ? Msg->via : conn->UserPointer->Call, 
						Msg->from, Msg->bid);
			}
			else
			{
				conn->BBSFlags &= ~RunningConnectScript;	// so it doesn't get reentered
				Disconnect(conn->BPQStream);
				return FALSE;
			}
			
			return TRUE;
		}
*/
		if (strcmp(conn->Callsign, "RMS") == 0 || conn->SendWL2KFW)
		{
			// Build a ;FW: line with all calls with PollRMS Set

			// According to Lee if you use secure login the first
			// must be the BBS call
			//	Actually I don't think we need the first,
			//	as that is implied

			//	If a secure password is available send the new 
			//	call|response format.

			//	I think this should use the session callsign, which 
			//	normally will be the BBS ApplCall, and not the BBS Name, 
			//	but coudl be changed by *** LINKED

			int i, s;
			char FWLine[10000] = ";FW: ";
			struct UserInfo * user;
			char RMSCall[20];
			char ConnectingCall[10];

#ifdef LINBPQ
			BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0];
#else
			BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR;
#endif

			SESS += conn->BPQStream - 1;

			ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall);
			strlop(ConnectingCall, ' ');

			strcat (FWLine, ConnectingCall);
			
			for (i = 0; i <= NumberofUsers; i++)
			{
				user = UserRecPtr[i];

				if (user->flags & F_POLLRMS)
				{
					if (user->RMSSSIDBits == 0) user->RMSSSIDBits = 1;

					for (s = 0; s < 16; s++)
					{
						if (user->RMSSSIDBits & (1 << s))
						{
							if (s)
								sprintf(RMSCall, "%s-%d", user->Call, s);
							else
								sprintf(RMSCall, "%s", user->Call);

							// We added connectingcall at front
							
							if (strcmp(RMSCall, ConnectingCall) != 0)
							{
								strcat(FWLine, " ");
								strcat(FWLine, RMSCall);
					
								if (user->CMSPass[0])
								{
									int Response = GetCMSHash(conn->PQChallenge, user->CMSPass);
									char RespString[12];
	
									sprintf(RespString, "%010d", Response);
									strcat(FWLine, "|");
									strcat(FWLine, &RespString[2]);
								}
							}
						}
					}
				}
			}
			
			strcat(FWLine, "\r");	

			nodeprintf(conn, FWLine);
		}

		// Only declare B1 and B2 if other end did, and we are configued for it

		nodeprintfEx(conn, BBSSID, "BPQ-",
			Ver[0], Ver[1], Ver[2], Ver[3],
			(conn->BBSFlags & FBBCompressed) ? "B" : "", 
			(conn->BBSFlags & FBBB1Mode && !(conn->BBSFlags & FBBB2Mode)) ? "1" : "",
			(conn->BBSFlags & FBBB2Mode) ? "2" : "",
			(conn->BBSFlags & FBBForwarding) ? "F" : "", 
			(conn->BBSFlags & WINLINKRO) ? "" : "J"); 

		if (conn->SecureMsg[0])
		{
			struct UserInfo * user;
			BBSputs(conn, conn->SecureMsg);
			conn->SecureMsg[0] = 0;

			// Also send a Location Comment Line

			//; GM8BPQ-10 DE G8BPQ (IO92KX)<cr>
			//; WL2K DE GM8BPQ ()<cr>			(PAT)

			user = LookupCall(BBSName);

			if (LOC && LOC[0])
				nodeprintf(conn, "; WL2K DE %s (%s)\r", BBSName, LOC);
		}

		if (conn->BPQBBS && conn->MSGTYPES[0])

			// Send a ; MSGTYPES to control what he sends us

			nodeprintf(conn, "; MSGTYPES %s\r", conn->MSGTYPES);

		if (conn->BBSFlags & FBBForwarding)
		{
			if (!FBBDoForward(conn))				// Send proposal if anthing to forward
			{
				if (conn->DoReverse)
					FBBputs(conn, "FF\r");
				else
				{
					FBBputs(conn, "FQ\r");
					conn->CloseAfterFlush = 20;			// 2 Secs
				}
			}

			return TRUE;
		}

		return TRUE;
	}

	return TRUE;
}

#ifndef LINBPQ
extern FARPROCX pGetPortHardwareType;
#endif

VOID Parse_SID(CIRCUIT * conn, char * SID, int len)
{
	ChangeSessionIdletime(conn->BPQStream, BBSIDLETIME);		// Default Idletime for BBS Sessions

	// scan backwards for first '-'

	if (strstr(SID, "BPQCHATSERVER"))
	{
		Disconnect(conn->BPQStream);
		return;
	}

	if (strstr(SID, "RMS Ex") || strstr(SID, "Winlink Ex"))
	{
		conn->RMSExpress = TRUE;
		conn->Paclink = FALSE;
		conn->PAT = FALSE;

		// Set new RMS Users as RMS User

		if (conn->NewUser)
			conn->UserPointer->flags |= F_Temp_B2_BBS;
	}

	if (stristr(SID, "PAT"))
	{
		// Set new PAT Users as RMS User

		conn->RMSExpress = FALSE;
		conn->Paclink = FALSE;
		conn->PAT = TRUE;

		if (conn->NewUser)
			conn->UserPointer->flags |= F_Temp_B2_BBS;
	}
	if (strstr(SID, "Paclink"))
	{
		conn->RMSExpress = FALSE;
		conn->Paclink = TRUE;
	}

	if (strstr(SID, "WL2K-"))
	{
		conn->WL2K = TRUE;
		conn->BBSFlags |= WINLINKRO;
	}

	if (strstr(SID, "MFJ-"))
	{
		conn->BBSFlags |= MFJMODE;
	}

	if (_memicmp(SID, "OpenBCM", 7) == 0)
	{
		// We should really only do this on Telnet Connections, as OpenBCM flag is used to remove relnet transparency

		// See if Telnet	

		struct PORTCONTROL * PORT;
		char Call[11] = "";
		int port, dummy;

		GetConnectionInfo(conn->BPQStream, Call, &port, &dummy, &dummy, &dummy, &dummy);

		PORT = GetPortTableEntryFromSlot(0);		// Get first entry

		do
		{
			if (PORT->PORTNUMBER == port)
				break;

			PORT=PORT->PORTPOINTER;
			
		} while (PORT);

#define H_TELNET 6

#ifndef LINBPQ

		if (pGetPortHardwareType)	
		{
			if (PORT && GetPortHardwareType(PORT) == H_TELNET)
				conn->OpenBCM = TRUE;
		}
		else
			conn->OpenBCM = TRUE;			// Old Node version so follow old behavoiur and treat all as telnet
#else
		if (PORT && GetPortHardwareType(PORT) == H_TELNET)
			conn->OpenBCM = TRUE;
#endif

	}


	if (_memicmp(SID, "PMS-3.2", 7) == 0)
	{
		// Paccom TNC that doesn't send newline prompt ater receiving subject

		conn->BBSFlags |= NEWPACCOM;
	}

	// See if BPQ for selective forwarding 

	if (strstr(SID, "BPQ"))
		conn->BPQBBS = TRUE;

	while (len > 0)
	{
		switch (SID[len--])
		{
		case '-':

			len=0;
			break;

		case '$':

			conn->BBSFlags |= BBS | MBLFORWARDING;
			conn->Paging = FALSE;

			break;

		case 'F':			// FBB Blocked Forwarding

			// We now support blocked uncompressed. Not necessarily compatible with FBB		

			if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS))
			{
				// We need to allocate a forwarding structure

					conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo));
					conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE;
					conn->UserPointer->ForwardingInfo->AllowBlocked = TRUE;
					conn->UserPointer->BBSNumber = NBBBS;
			}

			if (conn->UserPointer->ForwardingInfo->AllowBlocked)
			{
				conn->BBSFlags |= FBBForwarding | BBS;
				conn->BBSFlags &= ~MBLFORWARDING;
		
				conn->Paging = FALSE;

				if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS))
				{
					// We need to allocate a forwarding structure

						conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo));
						conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE;
						conn->UserPointer->BBSNumber = NBBBS;
				}
			
				// Allocate a Header Block

				conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine));
			}
			break;

		case 'J':

			// Suspected to be associated with Winlink Radio Only

			conn->BBSFlags &= ~WINLINKRO;
			break;

		case 'B':

			if (conn->UserPointer->ForwardingInfo->AllowCompressed)
			{
				conn->BBSFlags |= FBBCompressed;
				conn->DontSaveRestartData = FALSE;		// Allow restarts
				
				// Look for 1 or 2 or 12 as next 2 chars

				if (SID[len+2] == '1')
				{
					if (conn->UserPointer->ForwardingInfo->AllowB1 ||
						conn->UserPointer->ForwardingInfo->AllowB2)		// B2 implies B1
						conn->BBSFlags |= FBBB1Mode;

					if (SID[len+3] == '2')
						if (conn->UserPointer->ForwardingInfo->AllowB2)
							conn->BBSFlags |= FBBB1Mode | FBBB2Mode;	// B2 uses B1 mode (crc on front of file)

					break;
				}

				if (SID[len+2] == '2')
				{
					if (conn->UserPointer->ForwardingInfo->AllowB2)
							conn->BBSFlags |= FBBB1Mode | FBBB2Mode;	// B2 uses B1 mode (crc on front of file)
	
					if (conn->UserPointer->ForwardingInfo->AllowB1)
							conn->BBSFlags |= FBBB1Mode;				// B2 should allow fallback to B1 (but RMS doesnt!)

				}
				break;
			}

			break;
		}
	}

	// Only allow blocked non-binary to other BPQ Nodes

	if ((conn->BBSFlags & FBBForwarding) && ((conn->BBSFlags & FBBCompressed) == 0) && (conn->BPQBBS == 0))
	{
		// Switch back to MBL

		conn->BBSFlags |= MBLFORWARDING;
		conn->BBSFlags &= ~FBBForwarding;	// Turn off FBB Blocked
	}

	return;
}

VOID BBSSlowTimer()
{
	ConnectionInfo * conn;
	int n;

	// Called every 10 seconds

	MCastTimer();


	for (n = 0; n < NumberofStreams; n++)
	{
		conn = &Connections[n];
		
		if (conn->Active == TRUE)
		{
			// Check for stuck BBS sessions (BBS session but no Node Session)

			int state;
				
			GetSemaphore(&ConSemaphore, 1);
			SessionStateNoAck(conn->BPQStream, &state);
			FreeSemaphore(&ConSemaphore);

			if (state == 0)		// No Node Session
			{
				// is it safe just to clear Active ??

				conn->InputMode = 0;		// So Disconnect wont save partial transfer	
				conn->BBSFlags = 0;
				Disconnected (conn->BPQStream);
				continue;
			}

			if (conn->BBSFlags & MCASTRX)
				MCastConTimer(conn);


			//	Check SIDTImers - used to detect failure to compete SID Handshake

			if (conn->SIDResponseTimer)
			{
				conn->SIDResponseTimer--;
				if (conn->SIDResponseTimer == 0)
				{
					// Disconnect Session

					Disconnect(conn->BPQStream);
				}
			}
		}
	}

	// Flush logs

	for (n = 0; n < 4; n++)
	{
		if (LogHandle[n])
		{
			time_t LT = time(NULL);
			if ((LT - LastLogTime[n]) > 30)
			{
				LastLogTime[n] = LT;
				fclose(LogHandle[n]);
				LogHandle[n] = NULL;
			}
		}
	}
}


VOID FWDTimerProc()
{
	struct UserInfo * user;
	struct	BBSForwardingInfo * ForwardingInfo ;
	time_t NOW = time(NULL);

	// Entered every 2 seconds

	for (user = BBSChain; user; user = user->BBSNext)
	{
		// See if any messages are queued for this BBS

		ForwardingInfo = user->ForwardingInfo;
		ForwardingInfo->FwdTimer += 2;

		if (ForwardingInfo->FwdTimer >= ForwardingInfo->FwdInterval)
		{
			ForwardingInfo->FwdTimer=0;

			if (ForwardingInfo->FWDBands && ForwardingInfo->FWDBands[0])
			{
				// Check Timebands

				struct FWDBAND ** Bands = ForwardingInfo->FWDBands;
				int Count = 0;
				time_t now = time(NULL);
						
				if (Localtime)
					now -= (time_t)_MYTIMEZONE; 

				now %= 86400;		// Secs in day

				while(Bands[Count])
				{
					if ((Bands[Count]->FWDStartBand < now) && (Bands[Count]->FWDEndBand >= now))
						goto FWD;	// In band

				Count++;
				}
				continue;				// Out of bands
			}
		FWD:	
			if (ForwardingInfo->Enabled)
			{
				if (ForwardingInfo->ConnectScript  && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0])
				{
					//Temp Debug Code

//					Debugprintf("ReverseFlag = %d, Msgs to Forward Flag %d Msgs to Forward Count %d",
//						ForwardingInfo->ReverseFlag,
//						SeeifMessagestoForward(user->BBSNumber, NULL),
//						CountMessagestoForward(user));
						
					if (SeeifMessagestoForward(user->BBSNumber, NULL) ||
						(ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval)))

					{
						user->ForwardingInfo->ScriptIndex = -1;			 // Incremented before being used


						// remove any old TempScript

						if (user->ForwardingInfo->TempConnectScript)
						{
							FreeList(user->ForwardingInfo->TempConnectScript);
							user->ForwardingInfo->TempConnectScript = NULL;
						}

						if (ConnecttoBBS(user))
							ForwardingInfo->Forwarding = TRUE;					
					}
				}
			}
		}
	}
}

VOID * _zalloc_dbg(size_t len, int type, char * file, int line)
{
	// ?? malloc and clear

	void * ptr;

#ifdef WIN32
	ptr=_malloc_dbg(len, type, file, line);
#else
	ptr = malloc(len);
#endif
	if (ptr)
		memset(ptr, 0, len);

	return ptr;
}

struct MsgInfo * FindMessageByNumber(int msgno)
 {
	int m=NumberofMessages;

	struct MsgInfo * Msg;

	do
	{
		Msg=MsgHddrPtr[m];

		if (Msg->number == msgno)
			return Msg;

		if (Msg->number && Msg->number < msgno)		// sometimes get zero msg number
			return NULL;			// Not found

		m--;

	} while (m > 0);

	return NULL;
}

struct MsgInfo * FindMessageByBID(char * BID)
{
	int m = NumberofMessages;

	struct MsgInfo * Msg;

	while (m > 0)
	{
		Msg = MsgHddrPtr[m];

		if (strcmp(Msg->bid, BID) == 0)
			return Msg;

		m--;
	}

	return NULL;
}

VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len)
{
	unsigned char hash[50];
	unsigned char key[100];
	unsigned int i, j = 0, val1, val2;
	unsigned char hostname[100]="";

	gethostname(hostname, 100);

	strcpy(key, hostname);
	strcat(key, ISPPOP3Name);

	md5(key, hash);
	memcpy(&hash[16], hash, 16);	// in case very long password

	// String is now encoded as hex pairs, but still need to decode old format

	for (i=0; i < len; i++)
	{
		if (Encrypt[i] < '0' || Encrypt[i] > 'F')
			goto OldFormat;
	}

	// Only '0' to 'F'

	for (i=0; i < len; i++)
	{
		val1 = Encrypt[i++];
		val1 -= '0';
		if (val1 > 9)
			val1 -= 7;

		val2 = Encrypt[i];
		val2 -= '0';
		if (val2 > 9)
			val2 -= 7;

		Pass[j] =  (val1 << 4) | val2;
		Pass[j] ^= hash[j];
		j++;
	}

	return;

OldFormat:

	for (i=0; i < len; i++)
	{
		Pass[i] =  Encrypt[i] ^ hash[i];
	}

	return;
}

int EncryptPass(char * Pass, char * Encrypt)
{
	unsigned char hash[50];
	unsigned char key[100];
	unsigned int i, val;
	unsigned char hostname[100];
	unsigned char extendedpass[100];
	unsigned int passlen;
	unsigned char * ptr;

	gethostname(hostname, 100);

	strcpy(key, hostname);
	strcat(key, ISPPOP3Name);

	md5(key, hash);
	memcpy(&hash[16], hash, 16);	// in case very long password

	// if password is less than 16 chars, extend with zeros

	passlen=(int)strlen(Pass);

	strcpy(extendedpass, Pass);

	if (passlen < 16)
	{
		for  (i=passlen+1; i <= 16; i++)
		{
			extendedpass[i] = 0;
		}

		passlen = 16;
	}

	ptr = Encrypt;
	Encrypt[0] = 0;

	for (i=0; i < passlen; i++)
	{
		val = extendedpass[i] ^ hash[i];
		ptr += sprintf(ptr, "%02X", val);
	}

	return passlen * 2;
}



VOID SaveIntValue(config_setting_t * group, char * name, int value)
{
	config_setting_t *setting;
	
	setting = config_setting_add(group, name, CONFIG_TYPE_INT);
	if(setting)
		config_setting_set_int(setting, value);
}

VOID SaveInt64Value(config_setting_t * group, char * name, long long value)
{
	config_setting_t *setting;
	
	setting = config_setting_add(group, name, CONFIG_TYPE_INT64);
	if(setting)
		config_setting_set_int64(setting, value);
}

VOID SaveFloatValue(config_setting_t * group, char * name, double value)
{
	config_setting_t *setting;

	setting = config_setting_add(group, name, CONFIG_TYPE_FLOAT);
	if (setting)
		config_setting_set_float(setting, value);
}

VOID SaveStringValue(config_setting_t * group, char * name, char * value)
{
	config_setting_t *setting;

	setting = config_setting_add(group, name, CONFIG_TYPE_STRING);
	if (setting)
		config_setting_set_string(setting, value);

}


VOID SaveOverride(config_setting_t * group, char * name, struct Override ** values)
{
	config_setting_t *setting;
	struct Override ** Calls;
	char Multi[10000];
	char * ptr = &Multi[1];

	*ptr = 0;

	if (values)
	{
		Calls = values;

		while(Calls[0])
		{
			ptr += sprintf(ptr, "%s, %d|", Calls[0]->Call, Calls[0]->Days);
			Calls++;
		}
		*(--ptr) = 0;
	}

	setting = config_setting_add(group, name, CONFIG_TYPE_STRING);
	if (setting)
		config_setting_set_string(setting, &Multi[1]);

}


VOID SaveMultiStringValue(config_setting_t * group, char * name, char ** values)
{
	config_setting_t *setting;
	char ** Calls;
	char Multi[100000];
	char * ptr = &Multi[1];

	*ptr = 0;

	if (values)
	{
		Calls = values;

		while(Calls[0])
		{
			strcpy(ptr, Calls[0]);
			ptr += strlen(Calls[0]);
			*(ptr++) = '|';
			Calls++;
		}
		*(--ptr) = 0;
	}

	setting = config_setting_add(group, name, CONFIG_TYPE_STRING);
	if (setting)
		config_setting_set_string(setting, &Multi[1]);

}

int configSaved = 0;

VOID SaveConfig(char * ConfigName)
{
	struct UserInfo * user;
	struct	BBSForwardingInfo * ForwardingInfo ;
	config_setting_t *root, *group, *bbs;
	int i;
	char Size[80];
	struct BBSForwardingInfo DummyForwardingInfo;
	char Line[1024];
	char FBBString[8192]= "";
	FBBFilter * p = Filters;
	char * ptr = FBBString;
	
	GetSemaphore(&ConfigSEM, 60);

	if (configSaved == 0)
	{
		// only create backup once per run

		CopyConfigFile(ConfigName);
		configSaved = 1;
	}

	memset(&DummyForwardingInfo, 0, sizeof(struct BBSForwardingInfo));

	//	Get rid of old config before saving

	config_destroy(&cfg);

	memset((void *)&cfg, 0, sizeof(config_t));

	config_init(&cfg);

	root = config_root_setting(&cfg);

	group = config_setting_add(root, "main", CONFIG_TYPE_GROUP);

	SaveIntValue(group, "Streams", MaxStreams);
	SaveIntValue(group, "BBSApplNum", BBSApplNum);
	SaveStringValue(group, "BBSName", BBSName);
	SaveStringValue(group, "SYSOPCall", SYSOPCall);
	SaveStringValue(group, "H-Route", HRoute);
	SaveStringValue(group, "AMPRDomain", AMPRDomain);
	SaveIntValue(group, "EnableUI", EnableUI);
	SaveIntValue(group, "RefuseBulls", RefuseBulls);
	SaveIntValue(group, "OnlyKnown", OnlyKnown);
	SaveIntValue(group, "reportMailEvents", reportMailEvents);
	SaveIntValue(group, "SendSYStoSYSOPCall", SendSYStoSYSOPCall);
	SaveIntValue(group, "SendBBStoSYSOPCall", SendBBStoSYSOPCall);
	SaveIntValue(group, "DontHoldNewUsers", DontHoldNewUsers);
	SaveIntValue(group, "DefaultNoWINLINK", DefaultNoWINLINK);
	SaveIntValue(group, "AllowAnon", AllowAnon);
	SaveIntValue(group, "DontNeedHomeBBS", DontNeedHomeBBS);
	SaveIntValue(group, "DontCheckFromCall", DontCheckFromCall);
	SaveIntValue(group, "UserCantKillT", UserCantKillT);

	SaveIntValue(group, "ForwardToMe", ForwardToMe);
	SaveIntValue(group, "SMTPPort", SMTPInPort);
	SaveIntValue(group, "POP3Port", POP3InPort);
	SaveIntValue(group, "NNTPPort", NNTPInPort);
	SaveIntValue(group, "RemoteEmail", RemoteEmail);
	SaveIntValue(group, "SendAMPRDirect", SendAMPRDirect);

	SaveIntValue(group, "MailForInterval", MailForInterval);
	SaveStringValue(group, "MailForText", MailForText);

	EncryptedPassLen = EncryptPass(ISPAccountPass, EncryptedISPAccountPass);

	SaveIntValue(group, "AuthenticateSMTP", SMTPAuthNeeded);

	SaveIntValue(group, "MulticastRX", MulticastRX);

	SaveIntValue(group, "SMTPGatewayEnabled", ISP_Gateway_Enabled);
	SaveIntValue(group, "ISPSMTPPort", ISPSMTPPort);
	SaveIntValue(group, "ISPPOP3Port", ISPPOP3Port);
	SaveIntValue(group, "POP3PollingInterval", ISPPOP3Interval);

	SaveStringValue(group, "MyDomain", MyDomain);
	SaveStringValue(group, "ISPSMTPName", ISPSMTPName);
	SaveStringValue(group, "ISPEHLOName", ISPEHLOName);
	SaveStringValue(group, "ISPPOP3Name", ISPPOP3Name);
	SaveStringValue(group, "ISPAccountName", ISPAccountName);
	SaveStringValue(group, "ISPAccountPass", EncryptedISPAccountPass);


	//	Save Window Sizes
	
#ifndef LINBPQ

	if (ConsoleRect.right)
	{
		sprintf(Size,"%d,%d,%d,%d",ConsoleRect.left, ConsoleRect.right,
			ConsoleRect.top, ConsoleRect.bottom);

		SaveStringValue(group, "ConsoleSize", Size);
	}
	
	sprintf(Size,"%d,%d,%d,%d,%d",MonitorRect.left,MonitorRect.right,MonitorRect.top,MonitorRect.bottom, hMonitor ? 1 : 0);
	SaveStringValue(group, "MonitorSize", Size);

	sprintf(Size,"%d,%d,%d,%d",MainRect.left,MainRect.right,MainRect.top,MainRect.bottom);
	SaveStringValue(group, "WindowSize", Size);

	SaveIntValue(group, "Bells", Bells);
	SaveIntValue(group, "FlashOnBell", FlashOnBell);
	SaveIntValue(group, "StripLF", StripLF);
	SaveIntValue(group, "WarnWrap", WarnWrap);
	SaveIntValue(group, "WrapInput", WrapInput);
	SaveIntValue(group, "FlashOnConnect", FlashOnConnect);
	SaveIntValue(group, "CloseWindowOnBye", CloseWindowOnBye);

#endif

	SaveIntValue(group, "Log_BBS", LogBBS);
	SaveIntValue(group, "Log_TCP", LogTCP);

	sprintf(Size,"%d,%d,%d,%d", Ver[0], Ver[1], Ver[2], Ver[3]);
	SaveStringValue(group, "Version", Size);

	// Save Welcome Messages and prompts

	SaveStringValue(group, "WelcomeMsg", WelcomeMsg);
	SaveStringValue(group, "NewUserWelcomeMsg", NewWelcomeMsg);
	SaveStringValue(group, "ExpertWelcomeMsg", ExpertWelcomeMsg);
	
	SaveStringValue(group, "Prompt", Prompt);
	SaveStringValue(group, "NewUserPrompt", NewPrompt);
	SaveStringValue(group, "ExpertPrompt", ExpertPrompt);
	SaveStringValue(group, "SignoffMsg", SignoffMsg);

	SaveMultiStringValue(group,  "RejFrom", RejFrom);
	SaveMultiStringValue(group,  "RejTo", RejTo);
	SaveMultiStringValue(group,  "RejAt", RejAt);
	SaveMultiStringValue(group,  "RejBID", RejBID);

	SaveMultiStringValue(group,  "HoldFrom", HoldFrom);
	SaveMultiStringValue(group,  "HoldTo", HoldTo);
	SaveMultiStringValue(group,  "HoldAt", HoldAt);
	SaveMultiStringValue(group,  "HoldBID", HoldBID);

	// Save FBB Filters

	while (p)
	{
		ptr += sprintf(ptr, "%c|%c|%s|%s|%s|%s|%d|",
			p->Action, p->Type, p->From, p->TO, p->AT, p->BID, p->MaxLen);

		p = p->Next;
	}

	SaveStringValue(group, "FBBFilters", FBBString);

	SaveIntValue(group, "SendWP", SendWP);
	SaveIntValue(group, "SendWPType", SendWPType);
	SaveIntValue(group, "FilterWPBulls", FilterWPBulls);
	SaveIntValue(group, "NoWPGuesses", NoWPGuesses);

	SaveStringValue(group, "SendWPTO", SendWPTO);
	SaveStringValue(group, "SendWPVIA", SendWPVIA);

	SaveMultiStringValue(group, "SendWPAddrs", SendWPAddrs); 

	// Save Forwarding Config

	// Interval and Max Sizes and Aliases are not user specific

	SaveIntValue(group, "MaxTXSize", MaxTXSize);
	SaveIntValue(group, "MaxRXSize", MaxRXSize);
	SaveIntValue(group, "ReaddressLocal", ReaddressLocal);
	SaveIntValue(group, "ReaddressReceived", ReaddressReceived);
	SaveIntValue(group, "WarnNoRoute", WarnNoRoute);
	SaveIntValue(group, "Localtime", Localtime);
	SaveIntValue(group, "SendPtoMultiple", SendPtoMultiple);
	SaveIntValue(group, "FOURCHARCONT", FOURCHARCONT);

	SaveMultiStringValue(group, "FWDAliases", AliasText);

	bbs = config_setting_add(root, "BBSForwarding", CONFIG_TYPE_GROUP);

	for (i=1; i <= NumberofUsers; i++)
	{
		user = UserRecPtr[i];
		ForwardingInfo = user->ForwardingInfo;

		if (ForwardingInfo == NULL)
			continue;

		if (memcmp(ForwardingInfo, &DummyForwardingInfo, sizeof(struct BBSForwardingInfo)) == 0)
			continue;		// Ignore empty records;

		if (isdigit(user->Call[0]) || user->Call[0] == '_')
		{
			char Key[20] = "*";
			strcat (Key, user->Call); 
			group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP);
		}
		else
			group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP);

		SaveMultiStringValue(group, "TOCalls", ForwardingInfo->TOCalls);
		SaveMultiStringValue(group, "ConnectScript", ForwardingInfo->ConnectScript);
		SaveMultiStringValue(group, "ATCalls", ForwardingInfo->ATCalls);
		SaveMultiStringValue(group, "HRoutes", ForwardingInfo->Haddresses);
		SaveMultiStringValue(group, "HRoutesP", ForwardingInfo->HaddressesP);
		SaveMultiStringValue(group, "FWDTimes", ForwardingInfo->FWDTimes);
	
		SaveIntValue(group, "Enabled", ForwardingInfo->Enabled);
		SaveIntValue(group, "RequestReverse", ForwardingInfo->ReverseFlag);
		SaveIntValue(group, "AllowBlocked", ForwardingInfo->AllowBlocked);
		SaveIntValue(group, "AllowCompressed", ForwardingInfo->AllowCompressed);
		SaveIntValue(group, "UseB1Protocol", ForwardingInfo->AllowB1);
		SaveIntValue(group, "UseB2Protocol", ForwardingInfo->AllowB2);
		SaveIntValue(group, "SendCTRLZ", ForwardingInfo->SendCTRLZ);
				
		SaveIntValue(group, "FWDPersonalsOnly", ForwardingInfo->PersonalOnly);
		SaveIntValue(group, "FWDNewImmediately", ForwardingInfo->SendNew);
		SaveIntValue(group, "FwdInterval", ForwardingInfo->FwdInterval);
		SaveIntValue(group, "RevFWDInterval", ForwardingInfo->RevFwdInterval);
		SaveIntValue(group, "MaxFBBBlock", ForwardingInfo->MaxFBBBlockSize);
		SaveIntValue(group, "ConTimeout", ForwardingInfo->ConTimeout);

		SaveStringValue(group, "BBSHA", ForwardingInfo->BBSHA);
	}


	// Save Housekeeping config

	group = config_setting_add(root, "Housekeeping", CONFIG_TYPE_GROUP);

	SaveInt64Value(group, "LastHouseKeepingTime", LastHouseKeepingTime);
	SaveInt64Value(group, "LastTrafficTime", LastTrafficTime);
	SaveIntValue(group, "MaxMsgno", MaxMsgno);
	SaveIntValue(group, "BidLifetime", BidLifetime);
	SaveIntValue(group, "MaxAge", MaxAge);
	SaveIntValue(group, "LogLifetime", BBSLogAge);
	SaveIntValue(group, "LogLifetime", BBSLogAge);
	SaveIntValue(group, "MaintInterval", MaintInterval);
	SaveIntValue(group, "UserLifetime", UserLifetime);
	SaveIntValue(group, "MaintTime", MaintTime);
	SaveFloatValue(group, "PR", PR);
	SaveFloatValue(group, "PUR", PUR);
	SaveFloatValue(group, "PF", PF);
	SaveFloatValue(group, "PNF", PNF);
	SaveIntValue(group, "BF", BF);
	SaveIntValue(group, "BNF", BNF);
	SaveIntValue(group, "NTSD", NTSD);
	SaveIntValue(group, "NTSF", NTSF);
	SaveIntValue(group, "NTSU", NTSU);
//	SaveIntValue(group, "AP", AP);
//	SaveIntValue(group, "AB", AB);
	SaveIntValue(group, "DeletetoRecycleBin", DeletetoRecycleBin);
	SaveIntValue(group, "SuppressMaintEmail", SuppressMaintEmail);
	SaveIntValue(group, "MaintSaveReg", SaveRegDuringMaint);
	SaveIntValue(group, "OverrideUnsent", OverrideUnsent);
	SaveIntValue(group, "SendNonDeliveryMsgs", SendNonDeliveryMsgs);
	SaveIntValue(group, "GenerateTrafficReport", GenerateTrafficReport);

	SaveOverride(group, "LTFROM", LTFROM);
	SaveOverride(group, "LTTO", LTTO);
	SaveOverride(group, "LTAT", LTAT);

	// Save UI config

	for (i=1; i <= GetNumberofPorts(); i++)
	{
		char Key[100];
			
		sprintf(Key, "UIPort%d", i);

		group = config_setting_add(root, Key, CONFIG_TYPE_GROUP);

		if (group)
		{
			SaveIntValue(group, "Enabled", UIEnabled[i]);
			SaveIntValue(group, "SendMF", UIMF[i]);
			SaveIntValue(group, "SendHDDR", UIHDDR[i]);
			SaveIntValue(group, "SendNull", UINull[i]);
	
			if (UIDigi[i])
				SaveStringValue(group, "Digis", UIDigi[i]);
		}
	}

	// Save User Config

	bbs = config_setting_add(root, "BBSUsers", CONFIG_TYPE_GROUP);

	for (i=1; i <= NumberofUsers; i++)
	{
		char stats[256], stats2[256];
		struct MsgStats * Stats;
		char Key[20] = "*";

		user = UserRecPtr[i];

		if (isdigit(user->Call[0]) || user->Call[0] == '_')
		{
			strcat (Key, user->Call); 
//			group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP);
		}
		else
		{
			strcpy(Key, user->Call);
//			group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP);
		}
		/*
		SaveStringValue(group, "Name", user->Name);
		SaveStringValue(group, "Address", user->Address);
		SaveStringValue(group, "HomeBBS", user->HomeBBS);
		SaveStringValue(group, "QRA", user->QRA);
		SaveStringValue(group, "pass", user->pass);
		SaveStringValue(group, "ZIP", user->ZIP);
		SaveStringValue(group, "CMSPass", user->CMSPass);

		SaveIntValue(group, "lastmsg", user->lastmsg);
		SaveIntValue(group, "flags", user->flags);
		SaveIntValue(group, "PageLen", user->PageLen);
		SaveIntValue(group, "BBSNumber", user->BBSNumber);
		SaveIntValue(group, "RMSSSIDBits", user->RMSSSIDBits);
		SaveIntValue(group, "WebSeqNo", user->WebSeqNo);
		
		SaveInt64Value(group, "TimeLastConnected", user->TimeLastConnected);
*/
		Stats = &user->Total;

//		sprintf(stats, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
		sprintf(stats, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d",
			Stats->ConnectsIn, Stats->ConnectsOut,
			Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], 
			Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], 
			Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], 
			Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], 
			Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], 
			Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]);

//		SaveStringValue(group, "Totsl", stats);

		Stats = &user->Last;

		sprintf(stats2, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d",
//		sprintf(stats2, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
			Stats->ConnectsIn, Stats->ConnectsOut,
			Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], 
			Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], 
			Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], 
			Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], 
			Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], 
			Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]);

//		SaveStringValue(group, "Last", stats2);

		sprintf(Line,"%s^%s^%s^%s^%s^%s^%s^%d^%d^%d^%d^%d^%d^%lld^%s^%s",
			user->Name, user->Address, user->HomeBBS, user->QRA, user->pass, user->ZIP, user->CMSPass,
			user->lastmsg, user->flags, user->PageLen, user->BBSNumber, user->RMSSSIDBits, user->WebSeqNo,
			user->TimeLastConnected, stats, stats2);

		if (strlen(Line) < 10)
			continue;

		SaveStringValue(bbs, Key, Line);
	}

/*
	wp = config_setting_add(root, "WP", CONFIG_TYPE_GROUP);

	for (i = 0; i <= NumberofWPrecs; i++)
	{
		char WPString[1024];
		long long val1, val2;

		WP = WPRecPtr[i];
		val1 = WP->last_modif;
		val2 = WP->last_seen;

		sprintf(Key, "R%d", i);

		sprintf(WPString, "%s|%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%ld|%ld",
			&WP->callsign[0], &WP->name[0], WP->Type, WP->changed, WP->seen, &WP->first_homebbs[0],
			&WP->secnd_homebbs[0], &WP->first_zip[0], &WP->secnd_zip[0], &WP->first_qth[0], &WP->secnd_qth[0],
			val1, val2);

		SaveStringValue(wp, Key, WPString);
	}

	// Save Message Headers 

	msgs = config_setting_add(root, "MSGS", CONFIG_TYPE_GROUP);

	memset(MsgHddrPtr[0], 0, sizeof(struct MsgInfo));

	MsgHddrPtr[0]->type = 'X';
	MsgHddrPtr[0]->status = '2';
	MsgHddrPtr[0]->number = 0;
	MsgHddrPtr[0]->length = LatestMsg;


	for (i = 0; i <= NumberofMessages; i++)
	{
		Msg = MsgHddrPtr[i];

		for (n = 0; n < NBMASK; n++)
			sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]);

		n = 39;
		while (n >=0 && HEXString1[n] == '0')
			HEXString1[n--] = 0;

		for (n = 0; n < NBMASK; n++)
			sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]);

		n = 39;
		while (n >= 0 && HEXString2[n] == '0')
			HEXString2[n--] = 0;
		
		sprintf(Key, "R%d", Msg->number);

		n = sprintf(Line, "%c|%c|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status,
		Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0],
		&Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, 
		&Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]);

		SaveStringValue(msgs, Key, Line);
	}

	// Save Bids  

	msgs = config_setting_add(root, "BIDS", CONFIG_TYPE_GROUP);

	for (i=1; i <= NumberofBIDs; i++)
	{
		sprintf(Key, "R%s", BIDRecPtr[i]->BID);
		sprintf(Line, "%d|%d", BIDRecPtr[i]->mode, BIDRecPtr[i]->u.timestamp);
		SaveStringValue(msgs, Key, Line);
	}

#ifdef LINBPQ

	if(!config_write_file(&cfg,"/dev/shm/linmail.cfg.temp" ))
	{
		print("Error while writing file.\n");
		config_destroy(&cfg);
		FreeSemaphore(&ConfigSEM);
		return;
	}

	CopyFile("/dev/shm/linmail.cfg.temp", ConfigName, FALSE);

#else
*/
	if(! config_write_file(&cfg, ConfigName))
	{
		fprintf(stderr, "Error while writing file.\n");
		config_destroy(&cfg);
		FreeSemaphore(&ConfigSEM);

		return;
	}

//#endif

	config_destroy(&cfg);

/*

#ifndef LINBPQ

	//	Save a copy with current Date/Time Stamp for debugging

	{
		char Backup[MAX_PATH];
		time_t LT;
		struct tm * tm;

		LT = time(NULL);
		tm = gmtime(&LT);	

		sprintf(Backup,"%s.%02d%02d%02d%02d%02d.save", ConfigName, tm->tm_year-100, tm->tm_mon+1,
			tm->tm_mday, tm->tm_hour, tm->tm_min);

		CopyFile(ConfigName, Backup, FALSE);	// Copy to .bak
	}
#endif
*/

	FreeSemaphore(&ConfigSEM);
}

int GetIntValue(config_setting_t * group, char * name)
{
	config_setting_t *setting;

	setting = config_setting_get_member (group, name);
	if (setting)
		return config_setting_get_int (setting);

	return 0;
}

long long GetInt64Value(config_setting_t * group, char * name)
{
	config_setting_t *setting;

	setting = config_setting_get_member (group, name);
	if (setting)
		return config_setting_get_int64 (setting);

	return 0;
}

double GetFloatValue(config_setting_t * group, char * name)
{
	config_setting_t *setting;
			
	setting = config_setting_get_member (group, name);

	if (setting)
	{
		return config_setting_get_float (setting);
	}
	return 0;
}

int GetIntValueWithDefault(config_setting_t * group, char * name, int Default)
{
	config_setting_t *setting;

	setting = config_setting_get_member (group, name);
	if (setting)
		return config_setting_get_int (setting);

	return Default;
}


BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen)
{
	char * str;
	config_setting_t *setting;

	setting = config_setting_get_member (group, name);
	if (setting)
	{
		str =  (char *)config_setting_get_string (setting);
		if (strlen(str) > maxlen)
		{
			Debugprintf("Suspect config record %s", str);
			str[maxlen] = 0;
		}
		strcpy(value, str);
		return TRUE;
	}
	value[0] = 0;
	return FALSE;
}

BOOL GetConfig(char * ConfigName)
{
	int i;
	char Size[80];
	config_setting_t *setting;
	char * ptr1;
	char FBBString[8192]= "";
	FBBFilter f;
	config_init(&cfg);

	/* Read the file. If there is an error, report it and exit. */
	
	if(! config_read_file(&cfg, ConfigName))
	{
		char Msg[256];
		sprintf(Msg, "Config File Line %d - %s\n",
			config_error_line(&cfg), config_error_text(&cfg));
#ifdef WIN32
		MessageBox(NULL, Msg, "BPQMail", MB_ICONSTOP);
#else
		printf("%s", Msg);
#endif
		config_destroy(&cfg);
		return(EXIT_FAILURE);
	}
/*
#if LIBCONFIG_VER_MINOR > 5
	config_set_option(&cfg, CONFIG_OPTION_AUTOCONVERT, 1);
#else
	config_set_auto_convert (&cfg, 1);
#endif
*/
	group = config_lookup (&cfg, "main");

	if (group == NULL)
		return EXIT_FAILURE;

	SMTPInPort = GetIntValue(group, "SMTPPort");
	POP3InPort = GetIntValue(group, "POP3Port");
	NNTPInPort = GetIntValue(group, "NNTPPort");
	RemoteEmail = GetIntValue(group, "RemoteEmail");
	MaxStreams = GetIntValue(group, "Streams");
	BBSApplNum = GetIntValue(group, "BBSApplNum");
	EnableUI = GetIntValue(group, "EnableUI");
	MailForInterval = GetIntValue(group, "MailForInterval");
	RefuseBulls = GetIntValue(group, "RefuseBulls");
	OnlyKnown = GetIntValue(group, "OnlyKnown");
	reportMailEvents = GetIntValue(group, "reportMailEvents");

	SendSYStoSYSOPCall =  GetIntValue(group, "SendSYStoSYSOPCall");
	SendBBStoSYSOPCall =  GetIntValue(group, "SendBBStoSYSOPCall");
	DontHoldNewUsers =  GetIntValue(group, "DontHoldNewUsers");
	DefaultNoWINLINK =  GetIntValue(group, "DefaultNoWINLINK");
	ForwardToMe =  GetIntValue(group, "ForwardToMe");
	AllowAnon =  GetIntValue(group, "AllowAnon");
	UserCantKillT = GetIntValue(group, "UserCantKillT");

	DontNeedHomeBBS =  GetIntValue(group, "DontNeedHomeBBS");
	DontCheckFromCall =  GetIntValue(group, "DontCheckFromCall");
	MaxTXSize =  GetIntValue(group, "MaxTXSize");
	MaxRXSize =  GetIntValue(group, "MaxRXSize");
	ReaddressLocal =  GetIntValue(group, "ReaddressLocal");
	ReaddressReceived =  GetIntValue(group, "ReaddressReceived");
	WarnNoRoute =  GetIntValue(group, "WarnNoRoute");
	SendPtoMultiple =  GetIntValue(group, "SendPtoMultiple");
	FOURCHARCONT =  GetIntValue(group, "FOURCHARCONT");

	Localtime =  GetIntValue(group, "Localtime");
	AliasText = GetMultiStringValue(group, "FWDAliases");
	GetStringValue(group, "BBSName", BBSName, 100);
	GetStringValue(group, "MailForText", MailForText, 100);
	GetStringValue(group, "SYSOPCall", SYSOPCall, 100);
	GetStringValue(group, "H-Route", HRoute, 100);
	GetStringValue(group, "AMPRDomain", AMPRDomain, 100);
	SendAMPRDirect = GetIntValue(group, "SendAMPRDirect");
	ISP_Gateway_Enabled =  GetIntValue(group, "SMTPGatewayEnabled");
	ISPPOP3Interval =  GetIntValue(group, "POP3PollingInterval");
	GetStringValue(group, "MyDomain", MyDomain, 50);
	GetStringValue(group, "ISPSMTPName", ISPSMTPName, 50);
	GetStringValue(group, "ISPPOP3Name", ISPPOP3Name, 50);
	ISPSMTPPort = GetIntValue(group, "ISPSMTPPort");
	ISPPOP3Port = GetIntValue(group, "ISPPOP3Port");
	GetStringValue(group, "ISPAccountName", ISPAccountName, 50);
	GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass, 100);

	sprintf(SignoffMsg, "73 de %s\r", BBSName);					// Default
	GetStringValue(group, "SignoffMsg", SignoffMsg, 50);

	DecryptPass(EncryptedISPAccountPass, ISPAccountPass, (int)strlen(EncryptedISPAccountPass));

	SMTPAuthNeeded = GetIntValue(group, "AuthenticateSMTP");
	LogBBS = GetIntValue(group, "Log_BBS");
	LogTCP = GetIntValue(group, "Log_TCP");

	MulticastRX = GetIntValue(group, "MulticastRX");

#ifndef LINBPQ

	GetStringValue(group, "MonitorSize", Size, sizeof(Size));
	sscanf(Size,"%d,%d,%d,%d,%d",&MonitorRect.left,&MonitorRect.right,&MonitorRect.top,&MonitorRect.bottom,&OpenMon);
	
	GetStringValue(group, "WindowSize", Size, sizeof(Size));
	sscanf(Size,"%d,%d,%d,%d",&MainRect.left,&MainRect.right,&MainRect.top,&MainRect.bottom);

	Bells = GetIntValue(group, "Bells");

	FlashOnBell = GetIntValue(group, "FlashOnBell");			

	StripLF	 = GetIntValue(group, "StripLF");	
	CloseWindowOnBye = GetIntValue(group, "CloseWindowOnBye");			
	WarnWrap = GetIntValue(group, "WarnWrap");
	WrapInput = GetIntValue(group, "WrapInput");			
	FlashOnConnect = GetIntValue(group, "FlashOnConnect");			
	
	GetStringValue(group, "ConsoleSize", Size, 80);
	sscanf(Size,"%d,%d,%d,%d,%d", &ConsoleRect.left, &ConsoleRect.right,
			&ConsoleRect.top, &ConsoleRect.bottom,&OpenConsole);

#endif

	// Get Welcome Messages

	setting = config_setting_get_member (group, "WelcomeMsg");

	if (setting && setting->value.sval[0])
	{
		WelcomeMsg = _strdup(config_setting_get_string (setting));
	}
	else
		WelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n");


	setting = config_setting_get_member (group, "NewUserWelcomeMsg");
	
	if (setting && setting->value.sval[0])
		NewWelcomeMsg = _strdup(config_setting_get_string (setting));
	else
		NewWelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n");


	setting = config_setting_get_member (group, "ExpertWelcomeMsg");
	
	if (setting && setting->value.sval[0])
		ExpertWelcomeMsg = _strdup(config_setting_get_string (setting));
	else
		ExpertWelcomeMsg = _strdup("");

	// Get Prompts

	setting = config_setting_get_member (group, "Prompt");
	
	if (setting && setting->value.sval[0])
		Prompt = _strdup(config_setting_get_string (setting));
	else
	{
		Prompt = malloc(20);
		sprintf(Prompt, "de %s>\r\n", BBSName);
	}

	setting = config_setting_get_member (group, "NewUserPrompt");
	
	if (setting && setting->value.sval[0])
		NewPrompt = _strdup(config_setting_get_string (setting));
	else
	{
		NewPrompt = malloc(20);
		sprintf(NewPrompt, "de %s>\r\n", BBSName);
	}

	setting = config_setting_get_member (group, "ExpertPrompt");
	
	if (setting && setting->value.sval[0])
		ExpertPrompt = _strdup(config_setting_get_string (setting));
	else
	{
		ExpertPrompt = malloc(20);
		sprintf(ExpertPrompt, "de %s>\r\n", BBSName);
	}

	TidyPrompts();

	RejFrom = GetMultiStringValue(group,  "RejFrom");
	RejTo = GetMultiStringValue(group,  "RejTo");
	RejAt = GetMultiStringValue(group,  "RejAt");
	RejBID = GetMultiStringValue(group,  "RejBID");

	HoldFrom = GetMultiStringValue(group,  "HoldFrom");
	HoldTo = GetMultiStringValue(group,  "HoldTo");
	HoldAt = GetMultiStringValue(group,  "HoldAt");
	HoldBID = GetMultiStringValue(group,  "HoldBID");

		// Get FBB Filters

	GetStringValue(group, "FBBFilters", FBBString, sizeof(FBBString));

	ptr1 = FBBString;

	// delete old list

	while(Filters && Filters->Next)
	{
		FBBFilter * next = Filters->Next;
		free(Filters);
		Filters = next;
	}

	free(Filters);
	Filters = NULL;

	while (ptr1 && ptr1[0])
	{
		FBBFilter * PFilter;

		f.Action = ptr1[0];
		f.Type = ptr1[2];
		ptr1 = &ptr1[4];

		memcpy(f.From, ptr1, 10);
		strlop(f.From, '|');
		ptr1 = strlop(ptr1, '|');

		memcpy(f.TO, ptr1, 10);
		strlop(f.TO, '|');
		ptr1 = strlop(ptr1, '|');

		memcpy(f.AT, ptr1, 10);
		strlop(f.AT, '|');
		ptr1 = strlop(ptr1, '|');

		memcpy(f.BID, ptr1, 10);
		strlop(f.BID, '|');
		ptr1 = strlop(ptr1, '|');

		f.MaxLen = atoi(ptr1);

		// add to list

		f.Next = 0;

		PFilter = zalloc(sizeof(FBBFilter));

		memcpy(PFilter, &f, sizeof(FBBFilter));

		if (Filters == 0)
			Filters = PFilter;
		else
		{
			FBBFilter * p = Filters;

			while (p->Next)
				p = p->Next;

			p->Next = PFilter;
		}

		ptr1 = strlop(ptr1, '|');
	}





//f.Action, f.Type, f.From, f.TO, f.AT, f.BID, &f.MaxLen);

/*	while (p)
	{
		ptr += sprintf(ptr, "%c|%c|%s|%s|%s|%s|%d|",
			p->Action, p->Type, p->From, p->TO, p->AT, p->BID, p->MaxLen);

		p = p->Next;
	}

*/

	// Send WP Params
	
	SendWP = GetIntValue(group, "SendWP");
	SendWPType = GetIntValue(group, "SendWPType");

	GetStringValue(group, "SendWPTO", SendWPTO, sizeof(SendWPTO));
	GetStringValue(group, "SendWPVIA", SendWPVIA, sizeof(SendWPVIA));

	SendWPAddrs = GetMultiStringValue(group,  "SendWPAddrs"); 

	FilterWPBulls = GetIntValue(group, "FilterWPBulls");
	NoWPGuesses = GetIntValue(group, "NoWPGuesses");

	if (SendWPAddrs[0] == NULL && SendWPTO[0])
	{
		// convert old format TO and VIA to entry in SendWPAddrs
	
		SendWPAddrs = realloc(SendWPAddrs, 8);		// Add entry

		if (SendWPVIA[0])
		{
			char WP[256];
	
			sprintf(WP, "%s@%s", SendWPTO, SendWPVIA);
			SendWPAddrs[0] = _strdup(WP);
		}
		else
			SendWPAddrs[0] = _strdup(SendWPTO);


		SendWPAddrs[1] = 0;

		SendWPTO[0] = 0;
		SendWPVIA[0] = 0;
	}

	GetStringValue(group, "Version", Size, sizeof(Size));
	sscanf(Size,"%d,%d,%d,%d", &LastVer[0], &LastVer[1], &LastVer[2], &LastVer[3]);

	for (i =1 ; i <= GetNumberofPorts(); i++)
	{
		char Key[100];
			
		sprintf(Key, "UIPort%d", i);

		group = config_lookup (&cfg, Key);

		if (group)
		{
			UIEnabled[i] = GetIntValue(group, "Enabled");
			UIMF[i] = GetIntValueWithDefault(group, "SendMF", UIEnabled[i]);
			UIHDDR[i] = GetIntValueWithDefault(group, "SendHDDR", UIEnabled[i]);
			UINull[i] = GetIntValue(group, "SendNull");
			Size[0] = 0;
			GetStringValue(group, "Digis", Size, sizeof(Size));
			if (Size[0])
				UIDigi[i] = _strdup(Size);
		}
	}

	 group = config_lookup (&cfg, "Housekeeping");

	 if (group)
	 {
		 LastHouseKeepingTime = GetIntValue(group, "LastHouseKeepingTime");
		 LastTrafficTime = GetIntValue(group, "LastTrafficTime");
		 MaxMsgno = GetIntValue(group, "MaxMsgno");
		 BBSLogAge = GetIntValue(group, "LogLifetime");
		 BidLifetime = GetIntValue(group, "BidLifetime");
		 MaxAge = GetIntValue(group, "MaxAge");
		 if (MaxAge == 0)
			 MaxAge = 30;
		 UserLifetime = GetIntValue(group, "UserLifetime");
		 MaintInterval = GetIntValue(group, "MaintInterval");

		 if (MaintInterval == 0)
			 MaintInterval = 24;

		 MaintTime = GetIntValue(group, "MaintTime");
	
		 PR = GetFloatValue(group, "PR");
		 PUR = GetFloatValue(group, "PUR");
		 PF = GetFloatValue(group, "PF");
		 PNF = GetFloatValue(group, "PNF");

		 BF = GetIntValue(group, "BF");
		 BNF = GetIntValue(group, "BNF");
		 NTSD = GetIntValue(group, "NTSD");
		 NTSU = GetIntValue(group, "NTSU");
		 NTSF = GetIntValue(group, "NTSF");
//		 AP = GetIntValue(group, "AP");
//		 AB = GetIntValue(group, "AB");
		 DeletetoRecycleBin = GetIntValue(group, "DeletetoRecycleBin");
		 SuppressMaintEmail = GetIntValue(group, "SuppressMaintEmail");
		 SaveRegDuringMaint = GetIntValue(group, "MaintSaveReg");
		 OverrideUnsent = GetIntValue(group, "OverrideUnsent");
		 SendNonDeliveryMsgs = GetIntValue(group, "SendNonDeliveryMsgs");
		 OverrideUnsent = GetIntValue(group, "OverrideUnsent");
		 GenerateTrafficReport = GetIntValueWithDefault(group, "GenerateTrafficReport", 1);

		 LTFROM = GetOverrides(group,  "LTFROM");
		 LTTO = GetOverrides(group,  "LTTO");
		 LTAT = GetOverrides(group,  "LTAT");
	}

	return EXIT_SUCCESS;
}


int Connected(int Stream)
{
	int n, Mask;
	CIRCUIT * conn;
	struct UserInfo * user = NULL;
	char callsign[10];
	int port, paclen, maxframe, l4window;
	char ConnectedMsg[] = "*** CONNECTED    ";
	char Msg[256];
	char Title[100];
	int64_t Freq = 0;
	int Mode = 0;
	BPQVECSTRUC * SESS;
	TRANSPORTENTRY * Sess1 = NULL, * Sess2;	

	for (n = 0; n < NumberofStreams; n++)
	{
  		conn = &Connections[n];
		
		if (Stream == conn->BPQStream)
		{
			if (conn->Active)
			{
				// Probably an outgoing connect
		
				ChangeSessionIdletime(Stream, USERIDLETIME);		// Default Idletime for BBS Sessions
				conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE;
				conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999;
				conn->ErrorCount = 0;

				if (conn->BBSFlags & RunningConnectScript)
				{
					// BBS Outgoing Connect

					conn->paclen = 236;

					// Run first line of connect script

					ChangeSessionIdletime(Stream, BBSIDLETIME);		// Default Idletime for BBS Sessions
					ProcessBBSConnectScript(conn, ConnectedMsg, 15);
					return 0;
				}
			}

			// Incoming Connect

			// Try to find port, freq, mode, etc

#ifdef LINBPQ
			SESS = &BPQHOSTVECTOR[0];
#else
			SESS = (BPQVECSTRUC *)BPQHOSTVECPTR;
#endif
			SESS +=(Stream - 1);

			if (SESS)
				Sess1 = SESS->HOSTSESSION;

			if (Sess1)
			{
				Sess2 = Sess1->L4CROSSLINK;

				if (Sess2)
				{
					// See if L2 session - if so, get info from WL2K report line

					// if Session has report info, use it

					if (Sess2->Mode)
					{
						Freq = Sess2->Frequency;
						Mode = Sess2->Mode;
					}
					else if (Sess2->L4CIRCUITTYPE & L2LINK)	
					{
						LINKTABLE * LINK = Sess2->L4TARGET.LINK;
						PORTCONTROLX * PORT = LINK->LINKPORT;

						Freq = PORT->WL2KInfo.Freq;
						Mode = PORT->WL2KInfo.mode;
					}
					else
					{
						if (Sess2->RMSCall[0])
						{
							Freq = Sess2->Frequency;
							Mode = Sess2->Mode;
						}
					}
				}
			}

			if (Mode < 0 || Mode > 54)
				Mode = 0;

			if (Freq < 0 || Freq > 11000000000)
				Freq = 0;

			memset(conn, 0, sizeof(ConnectionInfo));		// Clear everything
			conn->Active = TRUE;
			conn->BPQStream = Stream;
			ChangeSessionIdletime(Stream, USERIDLETIME);	// Default Idletime for BBS Sessions

			conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE;
			conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999;
			conn->ErrorCount = 0;

			conn->Secure_Session = GetConnectionInfo(Stream, callsign,
				&port, &conn->SessType, &paclen, &maxframe, &l4window);

			strlop(callsign, ' ');		// Remove trailing spaces

			if (strcmp(&callsign[strlen(callsign) - 2], "-T") == 0)
				conn->RadioOnlyMode = 'T';
			else if (strcmp(&callsign[strlen(callsign) - 2], "-R") == 0)
				conn->RadioOnlyMode = 'R';
			else
				conn->RadioOnlyMode = 0;

			memcpy(conn->Callsign, callsign, 10);

			strlop(callsign, '-');		// Remove any SSID

			user = LookupCall(callsign);

			if (user == NULL)
			{
				int Length=0;

				if (OnlyKnown)
				{
					// Unknown users not allowed

					n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from unknown user %s Rejected", callsign);
					WriteLogLine(conn, '|',Msg, n, LOG_BBS);

					Disconnect(Stream);
					return 0;
				}

				user = AllocateUserRecord(callsign);
				user->Temp = zalloc(sizeof (struct TempUserInfo));

				if (SendNewUserMessage)
				{
					int64_t LongFreq = Freq;

					char * MailBuffer = malloc(100);

					if (Freq == 0 && port)
					{
						// Get Port Freq if available

						char FreqString[256];

#ifdef WIN32
						if (pGetPortFrequency)
							LongFreq = pGetPortFrequency(port, FreqString);
#else
							LongFreq = GetPortFrequency(port, FreqString);
#endif
					}
					Length += sprintf(MailBuffer, "New User %s Connected to Mailbox on Port %d Freq %lld Mode %d\r\n", callsign, port, LongFreq, Mode);

					sprintf(Title, "New User %s", callsign);

					SendMessageToSYSOP(Title, MailBuffer, Length);
				}

				if (user == NULL) return 0; //		Cant happen??

				if (!DontHoldNewUsers)
					user->flags |= F_HOLDMAIL;

				if (DefaultNoWINLINK)
					user->flags |= F_NOWINLINK;

				// Always set WLE User - can't see it doing any harm

				user->flags |= F_Temp_B2_BBS;

				conn->NewUser = TRUE;
			}

			user->TimeLastConnected = time(NULL);
			user->Total.ConnectsIn++;

			conn->UserPointer = user;

			conn->lastmsg = user->lastmsg;

			conn->NextMessagetoForward = FirstMessageIndextoForward;

			if (paclen == 0)
			{
				paclen = 236;
	
				if (conn->SessType & Sess_PACTOR)
					paclen = 100;
			}

			conn->paclen = paclen;

			//	Set SYSOP flag if user is defined as SYSOP and Host Session 
			
			if (((conn->SessType & Sess_BPQHOST) == Sess_BPQHOST) && (user->flags & F_SYSOP))
				conn->sysop = TRUE;

			if (conn->Secure_Session && (user->flags & F_SYSOP))
				conn->sysop = TRUE;

			Mask = 1 << (GetApplNum(Stream) - 1);

			if (user->flags & F_Excluded)
			{
				n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s Rejected by Exclude Flag", user->Call);
				WriteLogLine(conn, '|',Msg, n, LOG_BBS);
				Disconnect(Stream);
				return 0;
			}

			if (port)
				n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s on Port %d Freq %lld Mode %s",
					user->Call,  port, Freq, WL2KModes[Mode]);
			else
				n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s", user->Call);
			
			// Send SID and Prompt (Unless Sync)

			if (user->ForwardingInfo && user->ForwardingInfo->ConTimeout)
				conn->SIDResponseTimer = user->ForwardingInfo->ConTimeout / 10;			// 10 sec ticks
			else
				conn->SIDResponseTimer =  12;				// Allow a couple of minutes for response to SID

			{
				BOOL B1 = FALSE, B2 = FALSE, BIN = FALSE, BLOCKED = FALSE;
				BOOL WL2KRO = FALSE;

				struct	BBSForwardingInfo * ForwardingInfo;

				if (conn->RadioOnlyMode == 'R')
					WL2KRO = 1;

				conn->PageLen = user->PageLen;
				conn->Paging = (user->PageLen > 0);

				if (user->flags & F_Temp_B2_BBS)
				{
					// An RMS Express user that needs a temporary BBS struct

					if (user->ForwardingInfo == NULL)
					{
						// we now save the Forwarding info if BBS flag is cleared,
						// so there may already be a ForwardingInfo

						user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo));
					}

					if (user->BBSNumber == 0)
						user->BBSNumber = NBBBS;

					ForwardingInfo = user->ForwardingInfo;

					ForwardingInfo->AllowCompressed = TRUE;
					B1 = ForwardingInfo->AllowB1 = FALSE;
					B2 = ForwardingInfo->AllowB2 = TRUE;
					BLOCKED = ForwardingInfo->AllowBlocked = TRUE;
				}

				if (conn->NewUser)
				{
					BLOCKED = TRUE;
					BIN = TRUE;
					B2 = TRUE;
				}

				if (user->ForwardingInfo)
				{
					BLOCKED = user->ForwardingInfo->AllowBlocked;
					if (BLOCKED)
					{
						BIN = user->ForwardingInfo->AllowCompressed;
						B1 = user->ForwardingInfo->AllowB1;
						B2 = user->ForwardingInfo->AllowB2;
					}
				}

				WriteLogLine(conn, '|',Msg, n, LOG_BBS);

				if (conn->RadioOnlyMode)
					nodeprintf(conn,";WL2K-Radio/Internet_Network\r");

				if (!(conn->BBSFlags & SYNCMODE))
				{

					nodeprintf(conn, BBSSID, "BPQ-",
						Ver[0], Ver[1], Ver[2], Ver[3],
						BIN ? "B" : "", B1 ? "1" : "", B2 ? "2" : "",
						BLOCKED ? "FW": "", WL2KRO ? "" : "J");

					//				 if (user->flags & F_Temp_B2_BBS)
					//					 nodeprintf(conn,";PQ: 66427529\r");

					//			nodeprintf(conn,"[WL2K-BPQ.1.0.4.39-B2FWIHJM$]\r");
				}
			}

			if ((user->Name[0] == 0) & AllowAnon)
				strcpy(user->Name, user->Call);

			if (!(conn->BBSFlags & SYNCMODE))
			{
				if (user->Name[0] == 0)
				{
					conn->Flags |= GETTINGUSER;
					BBSputs(conn, NewUserPrompt);
				}
				else
					SendWelcomeMsg(Stream, conn, user);
			}
			else
			{
				// Seems to be a timing problem - see if this fixes it

				Sleep(500);
			}

			RefreshMainWindow();

			return 0;
		}
	}

	return 0;
}

int Disconnected (int Stream)
{
	struct UserInfo * user = NULL;
	CIRCUIT * conn;
	int n;
	char Msg[255];
	int len;
	char DiscMsg[] = "DISCONNECTED    ";

	for (n = 0; n <= NumberofStreams-1; n++)
	{
		conn=&Connections[n];

		if (Stream == conn->BPQStream)
		{
			if (conn->Active == FALSE)
				return 0;

			// if still running connect script, reenter it to see if
			// there is an else

			if (conn->BBSFlags & RunningConnectScript)
			{
				// We need to see if we got as far as connnected,
				// as if we have we need to reset the connect script 
				// over the ELSE

				struct	BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo;
				char ** Scripts;

				if (ForwardingInfo->TempConnectScript)
					Scripts = ForwardingInfo->TempConnectScript;
				else
					Scripts = ForwardingInfo->ConnectScript;	

				// First see if any script left

				if (Scripts[ForwardingInfo->ScriptIndex])
				{
					if (ForwardingInfo->MoreLines == FALSE)
					{
						// Have reached end of script, so need to set back over ELSE
					
						ForwardingInfo->ScriptIndex--;
						ForwardingInfo->MoreLines = TRUE;
					}
					
			//	if (Scripts[ForwardingInfo->ScriptIndex] == NULL ||
			//	_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0	||		// Only Check until script is finished
			//	_memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0)			// Only Check until script is finished
			
				
					ProcessBBSConnectScript(conn, DiscMsg, 15);
					return 0;
				}
			}

			user = conn->UserPointer;

			if (user && (conn->lastmsg > user->lastmsg))
			{
				user->lastmsg = conn->lastmsg;
				SaveUserDatabase();
			}


			// if sysop was chatting to user clear link
#ifndef LINBPQ
			if (conn->BBSFlags & SYSOPCHAT)
			{
				SendUnbuffered(-1, "User has disconnected\n", 23);
				BBSConsole.Console->SysopChatStream = 0;
			}
#endif
			ClearQueue(conn);

			if (conn->PacLinkCalls)
				free(conn->PacLinkCalls);

			if (conn->InputBuffer)
			{
				free(conn->InputBuffer);
				conn->InputBuffer = NULL;
				conn->InputBufferLen = 0;
			}

			/* ---- G7TAJ PG SERVER ---- */
			if (conn->UserPointer && conn->UserPointer->Temp && conn->UserPointer->Temp->RUNPGPARAMS)
			{
				Debugprintf("Freeing RUNPGPARAMS");
				free(conn->UserPointer->Temp->RUNPGPARAMS);
				conn->UserPointer->Temp->RUNPGPARAMS = NULL;
			}

			/*------- G7TAJ END --------- */

			if (conn->InputMode == 'B')
			{
				// Save partly received message for a restart
						
				if (conn->BBSFlags & FBBB1Mode)
					if (conn->Paclink == 0)			// Paclink doesn't do restarts
						if (strcmp(conn->Callsign, "RMS") != 0)	// Neither does RMS Packet.
							if (conn->DontSaveRestartData == FALSE)
								SaveFBBBinary(conn);		
			}

			conn->Active = FALSE;

			if (conn->FwdMsg)
				conn->FwdMsg->Locked = 0;	// Unlock

			RefreshMainWindow();

			RemoveTempBIDS(conn);

			len=sprintf_s(Msg, sizeof(Msg), "%s Disconnected", conn->Callsign);
			WriteLogLine(conn, '|',Msg, len, LOG_BBS);

			if (conn->FBBHeaders)
			{
				struct FBBHeaderLine * FBBHeader;
				int n;

				for (n = 0; n < 5; n++)
				{
					FBBHeader = &conn->FBBHeaders[n];
					
					if (FBBHeader->FwdMsg)
						FBBHeader->FwdMsg->Locked = 0;	// Unlock

				}

				free(conn->FBBHeaders);
				conn->FBBHeaders = NULL;
			}

			if (conn->UserPointer)
			{
				struct	BBSForwardingInfo * FWDInfo = conn->UserPointer->ForwardingInfo;

				if (FWDInfo)
				{
					FWDInfo->Forwarding = FALSE;

//					if (FWDInfo->UserCall[0])			// Will be set if RMS
//					{
//						FindNextRMSUser(FWDInfo);
//					}
//					else
						FWDInfo->FwdTimer = 0;
				}
			}
			
			conn->BBSFlags = 0;				// Clear ARQ Mode

			return 0;
		}
	}
	return 0;
}

int DoReceivedData(int Stream)
{
	int count, InputLen;
	size_t MsgLen;
	int n;
	CIRCUIT * conn;
	struct UserInfo * user;
	char * ptr, * ptr2;
	char * Buffer;

	for (n = 0; n < NumberofStreams; n++)
	{
		conn = &Connections[n];

		if (Stream == conn->BPQStream)
		{
			conn->SIDResponseTimer = 0;		// Got a message, so cancel timeout.
			do
			{ 
				// May have several messages per packet, or message split over packets

			OuterLoop:
				if (conn->InputLen + 1000 > conn->InputBufferLen )	// Shouldnt have lines longer  than this in text mode
				{
					conn->InputBufferLen += 1000;
					conn->InputBuffer = realloc(conn->InputBuffer, conn->InputBufferLen);
				}
				
				GetMsg(Stream, &conn->InputBuffer[conn->InputLen], &InputLen, &count);

				if (InputLen == 0 && conn->InputMode != 'Y')
					return 0;

				conn->bytesRxed += InputLen;
				conn->InputLen += InputLen;

				if (conn->InputLen == 0) return 0;

				conn->Watchdog = 900;				// 15 Minutes

				if (conn->InputMode == 'Y')			// YAPP
				{
					if (ProcessYAPPMessage(conn))	// Returns TRUE if there could be more to process
						goto OuterLoop;

					return 0;
				}


			/* ---------- G7TAJ START - PG server  --------- */

				if (conn->InputMode == 'P')			// Inside PG Server
				{
					user = conn->UserPointer;
					run_pg(conn, user);
					return 0;
				}
			/* ---------- G7TAJ END --------- */

				if (conn->InputMode == 'B')
				{
					// if in OpenBCM mode, remove FF transparency

					if (conn->OpenBCM)			// Telnet, so escape any 0xFF
					{
						unsigned char * ptr1 = conn->InputBuffer;
						unsigned char * ptr2;
						int Len;
						unsigned char c;

						// We can come through here again for the
						// same data as we wait for a full packet
						// So only check last InputLen bytes

						ptr1 += (conn->InputLen - InputLen);
						ptr2 = ptr1;
						Len = InputLen;

						while (Len--)
						{
							c = *(ptr1++);

							if (conn->InTelnetExcape)	// Last char was ff
							{
								conn->InTelnetExcape = FALSE;
								continue;
							}

							*(ptr2++) = c;

							if (c == 0xff)		// 
								conn->InTelnetExcape = TRUE;
						}

						conn->InputLen = (int)(ptr2 - conn->InputBuffer);
					}

					UnpackFBBBinary(conn);
					goto OuterLoop;
				}
				else
				{

			loop:

				if (conn->InputLen == 1 && conn->InputBuffer[0] == 0)		// Single Null
				{
					conn->InputLen = 0;
					return 0;
				}

				user = conn->UserPointer;

				if (conn->BBSFlags & (MCASTRX | SYNCMODE))
				{
					//	MCAST and SYNCMODE deliver full packets

					if (conn->BBSFlags & RunningConnectScript)
						ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen);
					else
						ProcessLine(conn, user, conn->InputBuffer, conn->InputLen);
					
					conn->InputLen=0;
					continue;
				}

				// This looks for CR, CRLF, LF or CR/Null and removes any LF or NULL,
				// but this relies on both arriving in same packet.
				// Need to check for LF and start of packet and ignore it
				// But what if client is only using LF??
				// (WLE sends SID with CRLF, other packets with CR only)

				// We don't get here on the data part of a binary transfer, so
				// don't need to worry about messing up binary data.

				ptr = memchr(conn->InputBuffer, '\r', conn->InputLen);
				ptr2 = memchr(conn->InputBuffer, '\n', conn->InputLen);

				if (ptr)
					conn->usingCR = 1;
				
				if ((ptr2 && ptr2 < ptr) || ptr == 0)		// LF before CR, or no CR
					ptr = ptr2;								// Use LF

				if (ptr)				// CR or LF in buffer
				{
					conn->lastLineEnd = *(ptr);

					*(ptr) = '\r';		// In case was LF
				
					ptr2 = &conn->InputBuffer[conn->InputLen];
					
					if (++ptr == ptr2)
					{
						// Usual Case - single msg in buffer

						// if Length is 1 and Term is LF and normal line end is CR
						// this is from a split CRLF - Ignore it

						if (conn->InputLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR)
							Debugprintf("BPQMail split Line End Detected");
						else
						{
							if (conn->BBSFlags & RunningConnectScript)
								ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen);
							else
								ProcessLine(conn, user, conn->InputBuffer, conn->InputLen);
						}
						conn->InputLen=0;
					}
					else
					{
						// buffer contains more that 1 message

						MsgLen = conn->InputLen - (ptr2-ptr);

						Buffer = malloc(MsgLen + 100);

						memcpy(Buffer, conn->InputBuffer, MsgLen);
					
						// if Length is 1 and Term is LF and normal line end is CR
						// this is from a split CRLF - Ignore it

						if (MsgLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR)
							Debugprintf("BPQMail split Line End Detected");
						else
						{
							if (conn->BBSFlags & RunningConnectScript)
								ProcessBBSConnectScript(conn, Buffer, (int)MsgLen);
							else
								ProcessLine(conn, user, Buffer, (int)MsgLen);
						}						
						free(Buffer);

						if (*ptr == 0 || *ptr == '\n')
						{
							/// CR LF or CR Null

							ptr++;
							conn->InputLen--;
						}

						memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen);

						conn->InputLen -= (int)MsgLen;

						goto loop;

					}
				}
				else
				{
					// Could be a YAPP Header


					if (conn->InputLen == 2 && conn->InputBuffer[0] == ENQ  && conn->InputBuffer[1] == 1)		// YAPP Send_Init
					{
						UCHAR YAPPRR[2];
						YAPPRR[0] = ACK;
						YAPPRR[1] = 1;

						conn->InputMode = 'Y';
						QueueMsg(conn, YAPPRR, 2);

						conn->InputLen = 0;
						return 0;
					}
				}
				}

			} while (count > 0);

			return 0;
		}
	}

	// Socket not found

	return 0;

}
int DoBBSMonitorData(int Stream)
{
//	UCHAR Buffer[1000];
	UCHAR buff[500];

	int len = 0,count=0;
	int stamp;
	
		do
		{ 
			stamp=GetRaw(Stream, buff,&len,&count);

			if (len == 0) return 0;

			SeeifBBSUIFrame((struct _MESSAGEX *)buff, len);	
		}
		
		while (count > 0);	
 		

	return 0;

}

VOID ProcessFLARQLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen)
{
	Buffer[MsgLen] = 0;

	if (MsgLen == 1 && Buffer[0] == 13)
		return;

	if (strcmp(Buffer, "ARQ::ETX\r") == 0)
	{
		// Decode it. 

		UCHAR * ptr1, * ptr2, * ptr3;
		int len, linelen;
		struct MsgInfo * Msg = conn->TempMsg;
		time_t Date;
		char FullTo[100];
		char FullFrom[100];
		char ** RecpTo = NULL;				// May be several Recipients
		char ** HddrTo = NULL;				// May be several Recipients
		char ** Via = NULL;					// May be several Recipients
		int LocalMsg[1000]	;				// Set if Recipient is a local wl2k address

		int B2To;							// Offset to To: fields in B2 header
		int Recipients = 0;
		int RMSMsgs = 0, BBSMsgs = 0;

//		Msg->B2Flags |= B2Msg;
				

		ptr1 = conn->MailBuffer;
		len = Msg->length;
		ptr1[len] = 0;

		if (strstr(ptr1, "ARQ:ENCODING::"))
		{
			// a file, not a message. If is called  "BBSPOLL" do a reverse forward else Ignore for now

			_strupr(conn->MailBuffer);
			if (strstr(conn->MailBuffer, "BBSPOLL"))
			{
				SendARQMail(conn);
			}

			free(conn->MailBuffer);
			conn->MailBuffer = NULL;
			conn->MailBufferSize = 0;

			return;
		}
	Loop:
		ptr2 = strchr(ptr1, '\r');

		linelen = (int)(ptr2 - ptr1);

		if (_memicmp(ptr1, "From:", 5) == 0 && linelen > 6)			// Can have empty From:
		{
			char SaveFrom[100];
			char * FromHA;

			memcpy(FullFrom, ptr1, linelen);
			FullFrom[linelen] = 0;

			// B2 From may now contain an @BBS 

			strcpy(SaveFrom, FullFrom);
				
			FromHA = strlop(SaveFrom, '@');

			if (strlen(SaveFrom) > 12) SaveFrom[12] = 0;
			
			strcpy(Msg->from, &SaveFrom[6]);

			if (FromHA)
			{
				if (strlen(FromHA) > 39) FromHA[39] = 0;
				Msg->emailfrom[0] = '@';
				strcpy(&Msg->emailfrom[1], _strupr(FromHA));
			}

			// Remove any SSID

			ptr3 = strchr(Msg->from, '-');
				if (ptr3) *ptr3 = 0;
		
		}
		else if (_memicmp(ptr1, "To:", 3) == 0 || _memicmp(ptr1, "cc:", 3) == 0)
		{
			HddrTo=realloc(HddrTo, (Recipients+1) * sizeof(void *));
			HddrTo[Recipients] = zalloc(100);

			memset(FullTo, 0, 99);
			memcpy(FullTo, &ptr1[4], linelen-4);
			memcpy(HddrTo[Recipients], ptr1, linelen+2);
			LocalMsg[Recipients] = FALSE;

			_strupr(FullTo);

			B2To = (int)(ptr1 - conn->MailBuffer);

			if (_memicmp(FullTo, "RMS:", 4) == 0)
			{
				// remove RMS and add @winlink.org

				strcpy(FullTo, "RMS");
				strcpy(Msg->via, &FullTo[4]);
			}
			else
			{
				ptr3 = strchr(FullTo, '@');

				if (ptr3)
				{
					*ptr3++ = 0;
					strcpy(Msg->via, ptr3);
				}
				else
					Msg->via[0] = 0;
			}
		
			if (_memicmp(&ptr1[4], "SMTP:", 5) == 0)
			{
				// Airmail Sends MARS messages as SMTP
					
				if (CheckifPacket(Msg->via))
				{
					// Packet Message

					memmove(FullTo, &FullTo[5], strlen(FullTo) - 4);
					_strupr(FullTo);
					_strupr(Msg->via);
						
					// Update the saved to: line (remove the smtp:)

					strcpy(&HddrTo[Recipients][4], &HddrTo[Recipients][9]);
					BBSMsgs++;
					goto BBSMsg;
				}

				// If a winlink.org address we need to convert to call

				if (_stricmp(Msg->via, "winlink.org") == 0)
				{
					memmove(FullTo, &FullTo[5], strlen(FullTo) - 4);
					_strupr(FullTo);
					LocalMsg[Recipients] = CheckifLocalRMSUser(FullTo);
				}
				else
				{
					memcpy(Msg->via, &ptr1[9], linelen);
					Msg->via[linelen - 9] = 0;
					strcpy(FullTo,"RMS");
				}
//					FullTo[0] = 0;

		BBSMsg:		
				_strupr(FullTo);
				_strupr(Msg->via);
			}

			if (memcmp(FullTo, "RMS:", 4) == 0)
			{
				// remove RMS and add @winlink.org

				memmove(FullTo, &FullTo[4], strlen(FullTo) - 3);
				strcpy(Msg->via, "winlink.org");
				sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo);
			}

			if (strcmp(Msg->via, "RMS") == 0)
			{
				// replace RMS with @winlink.org

				strcpy(Msg->via, "winlink.org");
				sprintf(HddrTo[Recipients], "To: %s@winlink.org\r\n", FullTo);
			}

			if (strlen(FullTo) > 6)
				FullTo[6] = 0;

			strlop(FullTo, '-');

			strcpy(Msg->to, FullTo);

			if (SendBBStoSYSOPCall)
				if (_stricmp(FullTo, BBSName) == 0)
					strcpy(Msg->to, SYSOPCall);

			if ((Msg->via[0] == 0 || strcmp(Msg->via, "BPQ") == 0 || strcmp(Msg->via, "BBS") == 0))
			{
				// No routing - check @BBS and WP

				struct UserInfo * ToUser = LookupCall(FullTo);

				Msg->via[0] = 0;				// In case BPQ and not found

				if (ToUser)
				{
					// Local User. If Home BBS is specified, use it

					if (ToUser->HomeBBS[0])
					{
						strcpy(Msg->via, ToUser->HomeBBS); 
					}
				}
				else
				{
					WPRecP WP = LookupWP(FullTo);

					if (WP)
					{
						strcpy(Msg->via, WP->first_homebbs);
			
					}
				}

				// Fix To: address in B2 Header

				if (Msg->via[0])
					sprintf(HddrTo[Recipients], "To: %s@%s\r\n", FullTo, Msg->via);
				else
					sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo);

			}

			RecpTo=realloc(RecpTo, (Recipients+1) * sizeof(void *));
			RecpTo[Recipients] = zalloc(10);

			Via=realloc(Via, (Recipients+1) * sizeof(void *));
			Via[Recipients] = zalloc(50);

			strcpy(Via[Recipients], Msg->via);
			strcpy(RecpTo[Recipients++], FullTo);

			// Remove the To: Line from the buffer
			
		}
		else if (_memicmp(ptr1, "Type:", 4) == 0)
		{
			if (ptr1[6] == 'N')
				Msg->type = 'T';				// NTS
			else
				Msg->type = ptr1[6];
		}
		else if (_memicmp(ptr1, "Subject:", 8) == 0)
		{
			size_t Subjlen = ptr2 - &ptr1[9];
			if (Subjlen > 60) Subjlen = 60;
			memcpy(Msg->title, &ptr1[9], Subjlen);

			goto ProcessBody;
		}
//		else if (_memicmp(ptr1, "Body:", 4) == 0)
//		{
//			MsgLen = atoi(&ptr1[5]);
//			StartofMsg = ptr1;
//		}
		else if (_memicmp(ptr1, "File:", 5) == 0)
		{
			Msg->B2Flags |= Attachments;
		}
		else if (_memicmp(ptr1, "Date:", 5) == 0)
		{
			struct tm rtime;
			char seps[] = " ,\t\r";

			memset(&rtime, 0, sizeof(struct tm));

			// Date: 2009/07/25 10:08
	
			sscanf(&ptr1[5], "%04d/%02d/%02d %02d:%02d:%02d",
					&rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec);

			sscanf(&ptr1[5], "%02d/%02d/%04d %02d:%02d:%02d",
					&rtime.tm_mday, &rtime.tm_mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec);

			rtime.tm_year -= 1900;

			Date = mktime(&rtime) - (time_t)_MYTIMEZONE;
	
			if (Date == (time_t)-1)
				Date = time(NULL);

			Msg->datecreated = Date;

		}

		if (linelen)			// Not Null line
		{
			ptr1 = ptr2 + 2;		// Skip cr
			goto Loop;
		}
	
		
		// Processed all headers
ProcessBody:

		ptr2 +=2;					// skip crlf

		Msg->length = (int)(&conn->MailBuffer[Msg->length] - ptr2);

		memmove(conn->MailBuffer, ptr2, Msg->length);

		CreateMessageFromBuffer(conn);

		conn->BBSFlags = 0;				// Clear ARQ Mode
		return;
	}

	// File away the data

	Buffer[MsgLen++] = 0x0a;			// BBS Msgs stored with crlf

	if ((conn->TempMsg->length + MsgLen) > conn->MailBufferSize)
	{
		conn->MailBufferSize += 10000;
		conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize);
	
		if (conn->MailBuffer == NULL)
		{
			BBSputs(conn, "*** Failed to extend Message Buffer\r");
			conn->CloseAfterFlush = 20;			// 2 Secs

			return;
		}
	}

	memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, MsgLen);

	conn->TempMsg->length += MsgLen;

	return;

	// Not sure what to do yet with files, but will process emails (using text style forwarding

/*
ARQ:FILE::flarqmail-1.eml
ARQ:EMAIL::
ARQ:SIZE::96
ARQ::STX
//FLARQ COMPOSER
Date: 16/01/2014 22:26:06
To: g8bpq
From: 
Subject: test message

Hello
Hello

ARQ::ETX
*/

	return;
}

VOID ProcessTextFwdLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int len)
{
	Buffer[len] = 0;
//	Debugprintf(Buffer);

	// With TNC2 body prompt is a single CR, so that shouldn't be ignored.

	// If thia causes problems with other TNC PMS implementations I'll have to revisit this

//	if (len == 1 && Buffer[0] == 13)
//		return;

	if (conn->Flags & SENDTITLE)
	{	
		// Waiting for Subject: prompt

		struct MsgInfo * Msg = conn->FwdMsg;
		
		nodeprintf(conn, "%s\r", Msg->title);
		
		conn->Flags &= ~SENDTITLE;
		conn->Flags |= SENDBODY;

		// New Paccom PMS (V3.2) doesn't prompt for body so drop through and send it
		if ((conn->BBSFlags & NEWPACCOM) == 0)
			return;

	}
	
	if (conn->Flags & SENDBODY)
	{
		// Waiting for Enter Message Prompt

		struct tm * tm;
		time_t temp;

		char * MsgBytes = ReadMessageFile(conn->FwdMsg->number);
		char * MsgPtr;
		int MsgLen;
		int Index = 0;

		if (MsgBytes == 0)
		{
			MsgBytes = _strdup("Message file not found\r");
			conn->FwdMsg->length = (int)strlen(MsgBytes);
		}

		MsgPtr = MsgBytes;
		MsgLen = conn->FwdMsg->length;

		// If a B2 Message, remove B2 Header

		if (conn->FwdMsg->B2Flags & B2Msg)
		{		
			// Remove all B2 Headers, and all but the first part.
					
			MsgPtr = strstr(MsgBytes, "Body:");
			
			if (MsgPtr)
			{
				MsgLen = atoi(&MsgPtr[5]);
				MsgPtr= strstr(MsgBytes, "\r\n\r\n");		// Blank Line after headers
	
				if (MsgPtr)
					MsgPtr +=4;
				else
					MsgPtr = MsgBytes;
			
			}
			else
				MsgPtr = MsgBytes;
		}

		memcpy(&temp, &conn->FwdMsg->datereceived, sizeof(time_t));
		tm = gmtime(&temp);	

		nodeprintf(conn, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r",
				tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min,
				conn->FwdMsg->number, BBSName, HRoute, RlineVer);

		if (memcmp(MsgPtr, "R:", 2) != 0)    // No R line, so must be our message - put blank line after header
			BBSputs(conn, "\r");

		MsgLen = RemoveLF(MsgPtr, MsgLen);

		QueueMsg(conn, MsgPtr, MsgLen);

		if (user->ForwardingInfo->SendCTRLZ)
			nodeprintf(conn, "\r\x1a");
		else
			nodeprintf(conn, "\r/ex\r");

		free(MsgBytes);
			
		conn->FBBMsgsSent = TRUE;

		
		if (conn->FwdMsg->type == 'P')
			Index = PMSG;
		else if (conn->FwdMsg->type == 'B')
			Index = BMSG;
		else if (conn->FwdMsg->type == 'T')
			Index = TMSG;

		user->Total.MsgsSent[Index]++;
		user->Total.BytesForwardedOut[Index] += MsgLen;
			
		conn->Flags &= ~SENDBODY;
		conn->Flags |= WAITPROMPT;

		return;
	}

	if (conn->Flags & WAITPROMPT)
	{
		if (Buffer[len-2] != '>')
			return;

		conn->Flags &= ~WAITPROMPT;

		clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber);
		set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber);

		//  Only mark as forwarded if sent to all BBSs that should have it
			
		if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0)
		{
			conn->FwdMsg->status = 'F';			// Mark as forwarded
			conn->FwdMsg->datechanged=time(NULL);
		}

		SaveMessageDatabase();

#ifndef NOMQTT
		if (MQTT)
			MQTTMessageEvent(conn->FwdMsg);
#endif
	
		conn->UserPointer->ForwardingInfo->MsgCount--;

		// See if any more to forward

		if (FindMessagestoForward(conn) && conn->FwdMsg)
		{
			struct MsgInfo * Msg;

			// If we are using SETCALLTOSENDER make sure this message is from the same sender

#ifdef LINBPQ
			BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0];
#else
			BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR;
#endif
			unsigned char AXCall[7];

			Msg = conn->FwdMsg;	
			ConvToAX25(Msg->from, AXCall);
			if (memcmp(SESS[conn->BPQStream - 1].HOSTSESSION->L4USER, AXCall, 7) != 0)
			{
				Disconnect(conn->BPQStream);
				return;
			}

			// Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ 

			conn->Flags |= SENDTITLE;


			if ((conn->BBSFlags & SETCALLTOSENDER))
				nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to,
						(Msg->via[0]) ? Msg->via : conn->UserPointer->Call);
			else
				nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to,
						(Msg->via[0]) ? Msg->via : conn->UserPointer->Call, 
						Msg->from, Msg->bid);
		}
		else
		{
			Disconnect(conn->BPQStream);
		}
		return;
	}
}


#define N               2048    /* buffer size */
#define F               60      /* lookahead buffer size */
#define THRESHOLD       2
#define NIL             N       /* leaf of tree */

extern UCHAR * infile;

BOOL CheckforMIME(SocketConn * sockptr, char * Msg, char ** Body, int * MsgLen);

/* ---G7TAJ PG Server --- */
#ifndef WIN32

#define verbose 1
#define TRUE 1
#define FALSE 0
#include <sys/wait.h>
#include <signal.h>

// G8BPQ Version of Steve G7TAJ's code

int pgret = 9999;
int pindex = 0;

void sigchild_handler(int sig , siginfo_t * siginfo, void * ucontext)
{
/* •  SIGCHLD fills in si_pid, si_uid, si_status, si_utime, and
          si_stime, providing information about the child.  The si_pid
          field is the process ID of the child; si_uid is the child's
          real user ID.  The si_status field contains the exit status of
          the child (if si_code is CLD_EXITED), or the signal number
          that caused the process to change state. 
*/
//	printf("SIGCHLD PID %d Code %d status %d\n", siginfo->si_pid, siginfo->si_code, siginfo->si_status);
	pgret = siginfo->si_status;
}


void run_pg(CIRCUIT * conn, struct UserInfo * user)
{
	register char *cp;
	FILE *iop;
	int argc, pdes[2];
	pid_t pid;

	pgret = 9999;

	int index = user->Temp->PG_INDEX;

	iop = NULL;

	conn->InputBuffer[conn->InputLen] = 0;
	strlop(conn->InputBuffer, 13);
	conn->InputLen = 0;

	if (!user->Temp->RUNPGPARAMS)
		user->Temp->RUNPGPARAMS = (RUNPGARGS_PTR) zalloc(sizeof(RUNPGARGS));

	user->Temp->RUNPGPARAMS->user = user;
	user->Temp->RUNPGPARAMS->conn = conn;
	strncpy(user->Temp->RUNPGPARAMS->InputBuffer, conn->InputBuffer, 80); // needs to be length of actual input!
	user->Temp->RUNPGPARAMS->Len = conn->InputLen;

	if (conn == 0 || user == 0)
	{
		Debugprintf("run_pg conn or user null");
		return;
	}

	// Build command line. Parmas are:

	// - Callsign (format as F6FBB-8).
	// - Level number (0 is the first time, up to 99).
	// - Flags of the user (binary number as user`s mask of INIT.SRV).
	// - Record number of the user in INF.SYS.
	// - Received data (each word is a new argument).

	// BPQ doesn't support params 3 and 4 (but may supply copy of user record later)

	char cmd[20];
	char *ptr = cmd;
	char pg_dir[MAX_PATH];
	char log_file[50] = "pg.log";
	char call[10];
	char data[80];
	char line[80];
	size_t bufsize = 80;

	strcpy(pg_dir, BaseDir);
	strcat(pg_dir, "/PG/");
	sprintf(cmd, "./%s", SERVERLIST[user->Temp->PG_SERVER][1] );

	sprintf(line, "%s%s", pg_dir, SERVERLIST[user->Temp->PG_SERVER][1]);
//	printf("PG Prog %s%s\n", pg_dir, SERVERLIST[user->Temp->PG_SERVER][1]);

	// check file exists and is executable

	if (access(line, F_OK) == -1 || access(line, X_OK) == -1)
	{
		Debugprintf("%s FileNotFound || not executable", line);
		BBSputs(conn, "Error running PG Server\r");
		conn->InputMode=0;
		SendPrompt(conn, user);
		return;
	}

	strcpy(call, conn->UserPointer->Call);
	index = user->Temp->PG_INDEX;

	// remove ';' from input for security reasons 

	ptr = strchr(user->Temp->RUNPGPARAMS->InputBuffer, ';');
	if (ptr)
		*ptr = '\0';

	sprintf(data, "%s %d 0 0 %s", call, index, user->Temp->RUNPGPARAMS->InputBuffer);
//	printf("PG Params %s\n", data);

	conn->InputBufferLen = 0;

	char buf[256];

	sprintf (buf, "%s %s", line, data); // buf is command to exec
//	printf ("PG exec cmd %s\n", buf); 

	// Create pipe for reading PG program STDOUT

	if (pipe(pdes) < 0)
	{
		Debugprintf("run_pg pipe failed");
		BBSputs(conn, "Error running PG Server (pipe() failed)\r");
		conn->InputMode=0;
		SendPrompt(conn, user);
		return;
	}

	// We will just fork and execute program. For now don't create a new thread

	// Trap sigchild so we can tell when it exits and get return code

	struct sigaction act;
	memset(&act, 0, sizeof(struct sigaction));
	act.sa_flags = SA_RESETHAND | SA_SIGINFO;		// Restore default handler when called
	act.sa_sigaction = sigchild_handler;
	sigaction(SIGCHLD, &act, NULL);

	switch(pid = fork())
	{
	case -1:			/* error */
		(void)close(pdes[0]);
		(void)close(pdes[1]);
		Debugprintf("run_pg fork failed");
		BBSputs(conn, "Error running PG Server (fork() failed)\r");
		conn->InputMode=0;
		SendPrompt(conn, user);

		return;

	case 0:				/* child */

		if (pdes[1] != 1)
		{
			dup2(pdes[1], 1);
			dup2(pdes[1], 2);
			(void)close(pdes[1]);
		}
		(void)close(pdes[0]);
	
		setpgid(0, pid);

		char *args[] = {"sh", "-c", buf, NULL};
		execve("/bin/sh", args, NULL);

		_exit(1);
	}

	/* parent */

//	printf("child PID %d\n", pid);

	struct timespec  duration;
	duration.tv_sec = 5;
	duration.tv_nsec = 0;

    nanosleep(&duration, &duration);   // Will be interrupted by SIGCHLD

//	printf("PG retcode %d\n", pgret);

	if (pgret == 9999)		// Process still running
	{
		BBSputs(conn, "PG Program Looping\r");
		kill(pid, SIGKILL);
		user->Temp->PG_INDEX = 0;
		conn->InputMode=0;
		SendPrompt(conn, user);
		return;
	}

	if (pgret > 127)
	{
		// Probably killed by signal

		int err = pgret - 128;
		char errmsg[256];

		sprintf(errmsg, "PG Signal %s received\n", strsignal(err));

		BBSputs(conn, errmsg);
		user->Temp->PG_INDEX = 0;
		conn->InputMode=0;
		SendPrompt(conn, user);
		return;
	}

	// Send STDOUT from PG program to BBS user

	iop = fdopen(pdes[0], "r");
	(void)close(pdes[1]);

  	char buffer[128];
	while (fgets(buffer, sizeof(buffer), iop) != NULL)
	{
		BBSputs(conn, buffer);
		buffer[0] = '\0';
  	}

	switch (pgret)
	{
		case -1:	// ERROR or forced closed
		case 0:
			index=0;			// Goodbye/Exit
			conn->InputMode=0;
			SendPrompt(conn, user);
			break;
	case 1:
		index++;			// inc & keep in PG
		break;

	case 2:
		
		index=0;			// disconnect
		conn->InputMode=0;
		Disconnect(conn->BPQStream);
		break;

	case 3:
		Debugprintf("data->BBS & end");
		break;

	case 4:
		Debugprintf("data->BBS and inc %d", pindex++);
		break;
	case 5:
		Debugprintf("call no inc %d", pgret);
		break;

	default:
		BBSputs(conn, "PG unexexpected response\r");
		user->Temp->PG_INDEX = 0;
		conn->InputMode=0;
		SendPrompt(conn, user);
		return;
	}


	user->Temp->PG_INDEX = index;

//	printf("runpg return index = %d\n", index);
}


/*---- G7TAJ END ----- */

#else

#define BUFSIZE 4096 
 
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

HANDLE g_hInputFile = NULL;
 
int CreateChildProcess(void); 
void WriteToPipe(void); 
void ReadFromPipe(void);
	

void run_pg( CIRCUIT * conn, struct UserInfo * user )
{
	// Run PG program, read anything from program's stdout to the user

	int retcode = -1;
	SECURITY_ATTRIBUTES saAttr; 
	char szCmdline[256] = "C:\\test\\hello.exe g8bpq 0";
	PROCESS_INFORMATION piProcInfo; 
	STARTUPINFO siStartInfo;
	BOOL bSuccess = FALSE; 
	DWORD dwRead; 
	CHAR chBuf[BUFSIZE]; 
	int index = 0;
	int ret = 0;

	// if first entry allocate RUNPGPARAMS
	if (!user->Temp->RUNPGPARAMS)
	{
		user->Temp->RUNPGPARAMS = (RUNPGARGS_PTR) zalloc(sizeof(RUNPGARGS));
	}

	user->Temp->RUNPGPARAMS->user = user;
	user->Temp->RUNPGPARAMS->conn = conn;
	strncpy(user->Temp->RUNPGPARAMS->InputBuffer, conn->InputBuffer, 80); // needs to be length of actual input!
	user->Temp->RUNPGPARAMS->Len = conn->InputLen;
	index = user->Temp->PG_INDEX;

	conn->InputBuffer[conn->InputLen] = 0;
	strlop(conn->InputBuffer, 13);
	conn->InputLen = 0;

	// Build command line. Parmas are:

	// - Callsign (format as F6FBB-8).
	// - Level number (0 is the first time, up to 99).
	// - Flags of the user (binary number as user`s mask of INIT.SRV).
	// - Record number of the user in INF.SYS.
	// - Received data (each word is a new argument).

	// BPQ doesn't support params 3 and 4 (but may supply copy of user record later)

	sprintf(szCmdline, "%s/PG/%s %s %d 0 0 %s", BaseDir,
		SERVERLIST[user->Temp->PG_SERVER][1], user->Call, index, conn->InputBuffer);

	// Set the bInheritHandle flag so pipe handles are inherited. 

	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE; 
	saAttr.lpSecurityDescriptor = NULL; 

	// Create a pipe for the child process's STDOUT. 

	if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) 
		return; 

	// Ensure the read handle to the pipe for STDOUT is not inherited.

	if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) )
		return;

	// Create the child process. 


	// Set up members of the PROCESS_INFORMATION structure. 

	ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

	// Set up members of the STARTUPINFO structure. 
	// This structure specifies the STDIN and STDOUT handles for redirection.

	ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO); 
	siStartInfo.hStdError = g_hChildStd_OUT_Wr;
	siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
	siStartInfo.hStdInput = g_hChildStd_IN_Rd;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

	// Create the child process. 

	bSuccess = CreateProcess(NULL, 
		szCmdline,     // command line 
		NULL,          // process security attributes 
		NULL,          // primary thread security attributes 
		TRUE,          // handles are inherited 
		0,             // creation flags 
		NULL,          // use parent's environment 
		NULL,          // use parent's current directory 
		&siStartInfo,  // STARTUPINFO pointer 
		&piProcInfo);  // receives PROCESS_INFORMATION 

	// If an error occurs, exit the application. 

	if (!bSuccess) 
		retcode = -1;
	else 
	{
		// Wait until child process exits.

		if (WaitForSingleObject(piProcInfo.hProcess, 5000) == 0)		// Wait max 5 seconds
		{
			// Success

			GetExitCodeProcess(piProcInfo.hProcess, &retcode);
		}
		else
		{
			// Failed or ran too long - kill

			TerminateProcess(piProcInfo.hProcess, 0);
		}

		// Close handles to the child process and its primary thread.
		// Some applications might keep these handles to monitor the status
		// of the child process, for example. 

		CloseHandle(piProcInfo.hProcess);
		CloseHandle(piProcInfo.hThread);

		// Close handles to the stdin and stdout pipes no longer needed by the child process.
		// If they are not explicitly closed, there is no way to recognize that the child process has ended.

		CloseHandle(g_hChildStd_OUT_Wr);

		// Send output to User

		for (;;) 
		{ 
			bSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
			if( ! bSuccess || dwRead == 0 ) break; 

			chBuf[dwRead] = 0;
			
			if (retcode == 4)
				ProcessLine(conn, user, chBuf, dwRead);
			else
				BBSputs(conn, chBuf);

			if (! bSuccess ) break; 
		}
	}


	switch (retcode)
	{
	case -1:	// ERROR or forced closed

		BBSputs(conn, "Problem running PG program\r");
		index=0;
		conn->InputMode=0;
		SendPrompt(conn, user);
		break;

	case 0:

		// Goodbye/Exit

		index=0;
		conn->InputMode=0;
		SendPrompt(conn, user);
		break;

	case 1:
		index++;			// inc & keep in PG
		break;

	case 2:
		index=0;			// disconnect
		conn->InputMode=0;
		Disconnect(conn->BPQStream);
		break;

	case 3:
		printf("data->BBS & end\n");
		break;

	case 4: 

		// Send Output to BBS - was done above
		break;

	case 5:
		printf("call no inc %d\n", ret);
		break;
	}

	user->Temp->PG_INDEX=index;

	// The remaining open handles are cleaned up when this process terminates. 
	// To avoid resource leaks in a larger application, close handles explicitly. 

	return; 
}


 
#endif


VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len)
{
	char * Cmd, * Arg1;
	char * Context;
	char seps[] = " \t\r";
	int CmdLen;

	if (_memicmp(Buffer, "POSYNCLOGON", 11) == 0)
	{
		WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
		conn->BBSFlags |= SYNCMODE;
		conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine));

		Sleep(500);

		BBSputs(conn, "OK\r");
		Flush(conn);
		return;
	}

	if (_memicmp(Buffer, "POSYNCHELLO", 11) == 0)
	{
		// This is first message received after connecting to SYNC
		// Save Callsign

		char Reply[32];
		conn->BBSFlags |= SYNCMODE;
		conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine));

		sprintf(Reply, "POSYNCLOGON %s\r", BBSName);
		BBSputs(conn, Reply);
		return;
	}

	if (conn->BBSFlags & SYNCMODE)
	{
		ProcessSyncModeMessage(conn, user, Buffer, len);
		return;
	}

	WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);

	// A few messages should be trapped here and result in an immediate disconnect, whatever mode I think the session is in (it could be wrong)

	// *** Protocol Error
	// Already Connected
	// Invalid Command

	if (_memicmp(Buffer, "Already Connected", 17) == 0 ||
		_memicmp(Buffer, "Invalid Command", 15) == 0 ||
		_memicmp(Buffer, "*** Protocol Error", 18) == 0)
	{
		conn->BBSFlags |= DISCONNECTING;
		Disconnect(conn->BPQStream);
		return;
	}

	if (conn->BBSFlags & FBBForwarding)
	{
		ProcessFBBLine(conn, user, Buffer, len);
		return;
	}

	if (conn->BBSFlags & FLARQMODE)
	{
		ProcessFLARQLine(conn, user, Buffer, len);
		return;
	}

	if (conn->BBSFlags & MCASTRX)
	{
		ProcessMCASTLine(conn, user, Buffer, len);
		return;
	}


	if (conn->BBSFlags & TEXTFORWARDING)
	{
		ProcessTextFwdLine(conn, user, Buffer, len);
		return;
	}

	// if chatting to sysop pass message to BBS console

	if (conn->BBSFlags & SYSOPCHAT)
	{
		SendUnbuffered(-1, Buffer,len);
		return;
	}

	if (conn->Flags & GETTINGMESSAGE)
	{
		ProcessMsgLine(conn, user, Buffer, len);
		return;
	}
	if (conn->Flags & GETTINGTITLE)
	{
		ProcessMsgTitle(conn, user, Buffer, len);
		return;
	}

	if (conn->BBSFlags & MBLFORWARDING)
	{
		ProcessMBLLine(conn, user, Buffer, len);
		return;
	}

	if (conn->Flags & GETTINGUSER || conn->NewUser)		// Could be new user but dont need name
	{
		if (memcmp(Buffer, ";FW:", 4) == 0 || Buffer[0] == '[')
		{
			struct	BBSForwardingInfo * ForwardingInfo;
			
			conn->Flags &= ~GETTINGUSER;

			// New User is a BBS - create a temp struct for it

			if ((user->flags & (F_BBS | F_Temp_B2_BBS)) == 0)			// It could already be a BBS without a user name
			{
				// Not defined as BBS - allocate and initialise forwarding structure
		
				user->flags |= F_Temp_B2_BBS;

				// An RMS Express user that needs a temporary BBS struct

				ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo));

				ForwardingInfo->AllowCompressed = TRUE;
				ForwardingInfo->AllowBlocked = TRUE;
				conn->UserPointer->ForwardingInfo->AllowB2 = TRUE;
			}
			SaveUserDatabase();
		}
		else
		{
			if (conn->Flags & GETTINGUSER)
			{
				conn->Flags &= ~GETTINGUSER;
				if (len > 18)
					len = 18;

				memcpy(user->Name, Buffer, len-1);
				SendWelcomeMsg(conn->BPQStream, conn, user);
				SaveUserDatabase();
				UpdateWPWithUserInfo(user);
				return;
			}
		}
	}

	// Process Command

	if (conn->Paging && (conn->LinesSent >= conn->PageLen))
	{
		// Waiting for paging prompt

		if (len > 1)
		{
			if (_memicmp(Buffer, "Abort", 1) == 0)
			{
				ClearQueue(conn);
				conn->LinesSent = 0;

				nodeprintf(conn, AbortedMsg);

				if (conn->UserPointer->Temp->ListSuspended)
					nodeprintf(conn, "<A>bort, <R Msg(s)>, <CR> = Continue..>");

				SendPrompt(conn, user);
				return;
			}
		}

		conn->LinesSent = 0;
		return;
	}

	if (user->Temp->ListSuspended)
	{
		// Paging limit hit when listing. User may abort, continue, or read one or more messages

		ProcessSuspendedListCommand(conn, user, Buffer, len);
		return;
	}
	if (len == 1)
	{
		SendPrompt(conn, user);
		return;
	}

	Buffer[len] = 0;

	if (strstr(Buffer, "ARQ:FILE:"))
	{
		// Message from FLARQ

		conn->BBSFlags |= FLARQMODE;
		strcpy(conn->ARQFilename, &Buffer[10]);			// Will need name when we decide what to do with files

		// Create a Temp Messge Stucture

		CreateMessage(conn, conn->Callsign, "", "", 'P', NULL, NULL);

		Buffer[len++] = 0x0a;			// BBS Msgs stored with crlf

		if ((conn->TempMsg->length + len) > conn->MailBufferSize)
		{
			conn->MailBufferSize += 10000;
			conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize);
	
			if (conn->MailBuffer == NULL)
			{
				BBSputs(conn, "*** Failed to extend Message Buffer\r");
				conn->CloseAfterFlush = 20;			// 2 Secs

				return;
			}
		}

		memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len);

		conn->TempMsg->length += len;

		return;
	}
	if (Buffer[0] == ';')			// WL2K Comment
	{
		if (memcmp(Buffer, ";FW:", 4) == 0)
		{
			// Paclink User Select (poll for list)
		
			char * ptr1,* ptr2, * ptr3;
			int index=0;

			// Convert string to Multistring
		
			Buffer[len-1] = 0;

			conn->PacLinkCalls = zalloc(len*3);

			ptr1 = &Buffer[5];
			ptr2 = (char *)conn->PacLinkCalls;
			ptr2 += (len * 2);
			strcpy(ptr2, ptr1);

			while (ptr2)
			{
				ptr3 = strlop(ptr2, ' ');

				if (strlen(ptr2))
					conn->PacLinkCalls[index++] = ptr2;
		
				ptr2 = ptr3;
			}
	
			return;	
		}

		if (memcmp(Buffer, ";FR:", 4) == 0)
		{
			// New Message from TriMode - Just igonre till I know what to do with it

			return;
		}

		// Ignore other ';' message

		return;
	}



	if (Buffer[0] == '[' && Buffer[len-2] == ']')		// SID
	{
		// If a BBS, set BBS Flag

		if (user->flags & ( F_BBS | F_Temp_B2_BBS))
		{
			if (user->ForwardingInfo)
			{
				if (user->ForwardingInfo->Forwarding && ((conn->BBSFlags & OUTWARDCONNECT) == 0))
				{
					BBSputs(conn, "Already Connected\r");
					Flush(conn);
					Sleep(500);
					Disconnect(conn->BPQStream);
					return;
				}
			}

			if (user->ForwardingInfo)
			{
				user->ForwardingInfo->Forwarding = TRUE;
				user->ForwardingInfo->FwdTimer = 0;			// So we dont send to immediately
			}
		}

		if (user->flags & ( F_BBS | F_PMS | F_Temp_B2_BBS))
		{
			Parse_SID(conn, &Buffer[1], len-4);
			
			if (conn->BBSFlags & FBBForwarding)
			{
				conn->FBBIndex = 0;		// ready for first block;
				conn->FBBChecksum = 0;
				memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine));
			}
			else
				FBBputs(conn, ">\r");

		}

		return;
	}

	Cmd = strtok_s(Buffer, seps, &Context);

	if (Cmd == NULL)
	{
		if (!CheckForTooManyErrors(conn))
			BBSputs(conn, "Invalid Command\r");

		SendPrompt(conn, user);
		return;
	}

	Arg1 = strtok_s(NULL, seps, &Context);
	CmdLen = (int)strlen(Cmd);

	// Check List first. If any other, save last listed to user record.

	if (_memicmp(Cmd, "L", 1) == 0 && _memicmp(Cmd, "LISTFILES", 3) != 0)
	{
		DoListCommand(conn, user, Cmd, Arg1, FALSE, Context);
		SendPrompt(conn, user);
		return;
	}

	if (conn->lastmsg > user->lastmsg)
	{
		user->lastmsg = conn->lastmsg;
		SaveUserDatabase();
	}

	if (_stricmp(Cmd, "SHOWRMSPOLL") == 0)
	{
		DoShowRMSCmd(conn, user, Arg1, Context);
		return;
	}

	if (_stricmp(Cmd, "AUTH") == 0)
	{
		DoAuthCmd(conn, user, Arg1, Context);
		return;
	}

	if (_memicmp(Cmd, "Abort", 1) == 0)
	{
		ClearQueue(conn);
		conn->LinesSent = 0;

		nodeprintf(conn, AbortedMsg);

		if (conn->UserPointer->Temp->ListSuspended)
			nodeprintf(conn, "<A>bort, <R Msg(s)>, <CR> = Continue..>");

		SendPrompt(conn, user);
		return;
	}
	if (_memicmp(Cmd, "Bye", CmdLen) == 0 || _stricmp(Cmd, "ELSE") == 0)
	{
		ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS);
		Flush(conn);
		Sleep(1000);

		if (conn->BPQStream > 0)
			Disconnect(conn->BPQStream);
#ifndef LINBPQ
		else
			CloseConsole(conn->BPQStream);
#endif
		return;
	}
	if (_memicmp(Cmd, "Node", 4) == 0)
	{
		ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS);
		Flush(conn);
		Sleep(1000);
			
		if (conn->BPQStream > 0)
			ReturntoNode(conn->BPQStream);
#ifndef LINBPQ
		else
			CloseConsole(conn->BPQStream);
#endif
		return;						
	}

	if (_memicmp(Cmd, "IDLETIME", 4) == 0)
	{
		DoSetIdleTime(conn, user, Arg1, Context);
		return;
	}

	if (_stricmp(Cmd, "SETNEXTMESSAGENUMBER") == 0)
	{
		DoSetMsgNo(conn, user, Arg1, Context);
		return;
	}

	if (strlen(Cmd) < 12 && _memicmp(Cmd, "D", 1) == 0)
	{
		DoDeliveredCommand(conn, user, Cmd, Arg1, Context);
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "K", 1) == 0)
	{
		DoKillCommand(conn, user, Cmd, Arg1, Context);
		SendPrompt(conn, user);
		return;
	}


	if (_memicmp(Cmd, "LISTFILES", 3) == 0 || _memicmp(Cmd, "FILES", 5) == 0)
	{
		ListFiles(conn, user, Arg1);
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "READFILE", 4) == 0)
	{
		ReadBBSFile(conn, user, Arg1);
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "REROUTEMSGS", 7) == 0)
	{
		if (conn->sysop == 0)
			nodeprintf(conn, "Reroute Messages needs SYSOP status\r");
		else
		{
			ReRouteMessages();
			nodeprintf(conn, "Ok\r");
		}
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "YAPP", 4) == 0)
	{
		YAPPSendFile(conn, user, Arg1);
		return;
	}

	if (_memicmp(Cmd, "UH", 2) == 0 && conn->sysop)
	{
		DoUnholdCommand(conn, user, Cmd, Arg1, Context);
		SendPrompt(conn, user);
		return;
	}

	if (_stricmp(Cmd, "IMPORT") == 0)
	{
		DoImportCmd(conn, user, Arg1, Context);
		return;
	}

	if (_stricmp(Cmd, "EXPORT") == 0)
	{
		DoExportCmd(conn, user, Arg1, Context);
		return;
	}

	if (_memicmp(Cmd, "I", 1) == 0)
	{
		char * Save;
		char * MsgBytes;

		if (Arg1)
		{
			// User WP lookup

			DoWPLookup(conn, user, Cmd[1], Arg1);
			SendPrompt(conn, user);
			return;	
		}


		MsgBytes = Save = ReadInfoFile("info.txt");
		if (MsgBytes)
		{
			int Length;

			// Remove lf chars

			Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes));

			QueueMsg(conn, MsgBytes, Length);
			free(Save);
		}
		else
			BBSputs(conn, "SYSOP has not created an INFO file\r");


		SendPrompt(conn, user);
		return;	
	}


	if (_memicmp(Cmd, "Name", CmdLen) == 0)
	{
		if (Arg1)
		{
			if (strlen(Arg1) > 17)
				Arg1[17] = 0;

			strcpy(user->Name, Arg1);
			UpdateWPWithUserInfo(user);

		}

		SendWelcomeMsg(conn->BPQStream, conn, user);
		SaveUserDatabase();

		return;
	}

	if (_memicmp(Cmd, "OP", 2) == 0)
	{
		int Lines;
		
		// Paging Control. Param is number of lines per page

		if (Arg1)
		{
			Lines = atoi(Arg1);
			
			if (Lines)				// Sanity Check
			{
				if (Lines < 10)
				{
					nodeprintf(conn,"Page Length %d is too short\r", Lines);
					SendPrompt(conn, user);
					return;
				}
			}

			user->PageLen = Lines;
			conn->PageLen = Lines;
			conn->Paging = (Lines > 0);
			SaveUserDatabase();
		}
		
		nodeprintf(conn,"Page Length is %d\r", user->PageLen);
		SendPrompt(conn, user);

		return;
	}

	if (_memicmp(Cmd, "QTH", CmdLen) == 0)
	{
		if (Arg1)
		{
			// QTH may contain spaces, so put back together, and just split at cr
			
			Arg1[strlen(Arg1)] = ' ';
			strtok_s(Arg1, "\r", &Context);

			if (strlen(Arg1) > 60)
				Arg1[60] = 0;

			strcpy(user->Address, Arg1);
			UpdateWPWithUserInfo(user);

		}

		nodeprintf(conn,"QTH is %s\r", user->Address);
		SendPrompt(conn, user);

		SaveUserDatabase();

		return;
	}

	if (_memicmp(Cmd, "ZIP", CmdLen) == 0)
	{
		if (Arg1)
		{
			if (strlen(Arg1) > 8)
				Arg1[8] = 0;

			strcpy(user->ZIP, _strupr(Arg1));
			UpdateWPWithUserInfo(user);
		}

		nodeprintf(conn,"ZIP is %s\r", user->ZIP);
		SendPrompt(conn, user);

		SaveUserDatabase();

		return;
	}

	if (_memicmp(Cmd, "CMSPASS", 7) == 0)
	{
		if (Arg1 == 0)
		{
			nodeprintf(conn,"Must specify a password\r");
		}
		else
		{
			if (strlen(Arg1) > 15)
				Arg1[15] = 0;

			strcpy(user->CMSPass, Arg1);
			nodeprintf(conn,"CMS Password Set\r");
			SaveUserDatabase();
		}

		SendPrompt(conn, user);

		return;
	}

	if (_memicmp(Cmd, "PASS", CmdLen) == 0)
	{
		if (Arg1 == 0)
		{
			nodeprintf(conn,"Must specify a password\r");
		}
		else
		{
			if (strlen(Arg1) > 12)
				Arg1[12] = 0;

			strcpy(user->pass, Arg1);
			nodeprintf(conn,"BBS Password Set\r");
			SaveUserDatabase();
		}

		SendPrompt(conn, user);

		return;
	}


	if (_memicmp(Cmd, "R", 1) == 0)
	{
		DoReadCommand(conn, user, Cmd, Arg1, Context);
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "S", 1) == 0)
	{
		if (!DoSendCommand(conn, user, Cmd, Arg1, Context))
			SendPrompt(conn, user);
		return;
	}

	if ((_memicmp(Cmd, "Help", CmdLen) == 0) || (_memicmp(Cmd, "?", 1) == 0))
	{
		char * Save;
		char * MsgBytes = Save = ReadInfoFile("help.txt");

		if (MsgBytes)
		{
			int Length;

			// Remove lf chars

			Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes));

			QueueMsg(conn, MsgBytes, Length);
			free(Save);
		}
		else
		{
			BBSputs(conn, "A - Abort Output\r");
			BBSputs(conn, "B - Logoff\r");
			BBSputs(conn, "CMSPASS Password - Set CMS Password\r");
			BBSputs(conn, "D - Flag NTS Message(s) as Delivered - D num\r");
			BBSputs(conn, "HOMEBBS - Display or get HomeBBS\r");
			BBSputs(conn, "INFO - Display information about this BBS\r");
			BBSputs(conn, "I CALL - Lookup CALL in WP Allows *CALL CALL* *CALL* wildcards\r");
			BBSputs(conn, "I@ PARAM - Lookup @BBS in WP\r");
			BBSputs(conn, "IZ PARAM - Lookup Zip Codes in WP\r");
			BBSputs(conn, "IH PARAM - Lookup HA elements in WP - eg USA EU etc\r");

			BBSputs(conn, "K - Kill Message(s) - K num, KM (Kill my read messages)\r");
			BBSputs(conn, "L - List Message(s) - \r");
			BBSputs(conn, "   L = List New, LR = List New (Oldest first)\r");
			BBSputs(conn, "   LM = List Mine, L> Call, L< Call, L@ = List to, from or at\r");
			BBSputs(conn, "   LL num = List msg num, L num-num = List Range\r");
			BBSputs(conn, "   LN LY LH LK LF L$ LD = List Message with corresponding Status\r");
			BBSputs(conn, "   LB LP LT = List Mesaage with corresponding Type\r");
			BBSputs(conn, "   LC = List TO fields of all active bulletins\r");
			BBSputs(conn, "   You can combine most selections eg LMP, LMN LB< G8BPQ\r"); 
			BBSputs(conn, "LISTFILES or FILES - List files available for download\r");

			BBSputs(conn, "N Name - Set Name\r");
			BBSputs(conn, "NODE - Return to Node\r");
			BBSputs(conn, "OP n - Set Page Length (Output will pause every n lines)\r");
			BBSputs(conn, "PASS Password - Set BBS Password\r");
			BBSputs(conn, "POLLRMS - Manage Polling for messages from RMS \r");
			BBSputs(conn, "Q QTH - Set QTH\r");
			BBSputs(conn, "R - Read Message(s) - R num \r");
			BBSputs(conn, "                      RM (Read new messages to me), RMR (RM oldest first)\r");
			BBSputs(conn, "READ Name - Read File\r");

			BBSputs(conn, "S - Send Message - S or SP Send Personal, SB Send Bull, ST Send NTS,\r");
			BBSputs(conn, "                   SR Num - Send Reply, SC Num - Send Copy\r");
			BBSputs(conn, "X - Toggle Expert Mode\r");
			BBSputs(conn, "YAPP - Download file from BBS using YAPP protocol\r");
			if (conn->sysop)
			{
				BBSputs(conn, "DOHOUSEKEEPING - Run Housekeeping process\r");
				BBSputs(conn, "EU - Edit User Flags - Type EU for Help\r");
				BBSputs(conn, "EXPORT - Export messages to file - Type EXPORT for Help\r");
				BBSputs(conn, "FWD - Control Forwarding - Type FWD for Help\r");
				BBSputs(conn, "IMPORT - Import messages from file - Type IMPORT for Help\r");
				BBSputs(conn, "REROUTEMSGS - Rerun message routing process\r");
				BBSputs(conn, "SETNEXTMESSAGENUMBER - Sets next message number\r");
				BBSputs(conn, "SHOWRMSPOLL - Displays your RMS polling list\r");
				BBSputs(conn, "UH - Unhold Message(s) - UH ALL or UH num num num...\r");
			}
		}

		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "Ver", CmdLen) == 0)
	{
		nodeprintf(conn, "BBS Version %s\rNode Version %s\r", VersionStringWithBuild, GetVersionString());

		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Cmd, "HOMEBBS", CmdLen) == 0)
	{
		if (Arg1)
		{
			if (strlen(Arg1) > 40) Arg1[40] = 0;

			strcpy(user->HomeBBS, _strupr(Arg1));
			UpdateWPWithUserInfo(user);
	
			if (!strchr(Arg1, '.'))
				BBSputs(conn, "Please enter HA with HomeBBS eg g8bpq.gbr.eu - this will help message routing\r");
		}

		nodeprintf(conn,"HomeBBS is %s\r", user->HomeBBS);
		SendPrompt(conn, user);

		SaveUserDatabase();

		return;
	}

	if ((_memicmp(Cmd, "EDITUSER", 5) == 0) || (_memicmp(Cmd, "EU", 2) == 0))
	{
		DoEditUserCmd(conn, user, Arg1, Context);
		return;
	}

	if (_stricmp(Cmd, "POLLRMS") == 0)
	{
		DoPollRMSCmd(conn, user, Arg1, Context);
		return;
	}

	if (_stricmp(Cmd, "DOHOUSEKEEPING") == 0)
	{
		DoHousekeepingCmd(conn, user, Arg1, Context);
		return;
	}


	if (_stricmp(Cmd, "FWD") == 0)
	{
		DoFwdCmd(conn, user, Arg1, Context);
		return;
	}

	if (_memicmp(Cmd, "X", 1) == 0)
	{
		user->flags ^= F_Expert;

		if (user->flags & F_Expert)
			BBSputs(conn, "Expert Mode\r");
		else
			BBSputs(conn, "Expert Mode off\r");

		SaveUserDatabase();
		SendPrompt(conn, user);
		return;
	}

	/*---- G7TAJ PG Server ----- */


	if (_stricmp(Cmd, "PG") == 0)
	{
		if ( NUM_SERVERS == 0 )
		{
			BBSputs(conn, "No PG Servers currently defined\r");
			SendPrompt(conn, user);
			Flush(conn);
			return;
		}

		if ( !Arg1 ) 
		{
			char reply[80];
			int i;
			for (i=0; i< NUM_SERVERS; i++ )
			{
				sprintf(reply, "%s   ->   %s\r", SERVERLIST[i][0], SERVERLIST[i][2]);
				BBSputs(conn, reply);
			}
			SendPrompt(conn, user);
			return;
		}
		else
		{
			int i;
			for (i=0; i < NUM_SERVERS; i++ )
			{
				if ( _stricmp( _strupr(Arg1), SERVERLIST[i][0] ) == 0 ) {
					user->Temp->PG_SERVER = i;	// index to server to run
					user->Temp->PG_INDEX  = 0;	// newly starting PG
					conn->InputMode = 'P';	// Inside PG Server

					// conn->InputBuffer is altered above and split into Cmd,Arg1,Context
					// so put it back and call PG (removing PG)
					sprintf( conn->InputBuffer, "%s", Context);
					conn->InputLen = strlen(Context);
					run_pg( conn, user );
					return;
				}
			}
			BBSputs(conn, "No server found\r");
			SendPrompt(conn, user);
			return;
		}
	}

	/*---- G7TAJ END ---- */

	if (conn->Flags == 0)
	{
		if (!CheckForTooManyErrors(conn))
			BBSputs(conn, "Invalid Command\r");

		SendPrompt(conn, user);
	}

	//	Send if possible

	Flush(conn);
}

VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...)
{
	// seems to be printf to a socket

	char buff[600];
	va_list(arglist);
	
	va_start(arglist, format);
	vsprintf(buff, format, arglist);

	BBSputs(conn, buff);
}

// Code to delete obsolete files from Mail folder

#ifdef WIN32

int DeleteRedundantMessages()
{
   WIN32_FIND_DATA ffd;

   char szDir[MAX_PATH];
   char File[MAX_PATH];
   HANDLE hFind = INVALID_HANDLE_VALUE;
   int Msgno;

   // Prepare string for use with FindFile functions.  First, copy the
   // string to a buffer, then append '\*' to the directory name.

   strcpy(szDir, MailDir);
   strcat(szDir, "\\*.mes");



   // Find the first file in the directory.

   hFind = FindFirstFile(szDir, &ffd);

   if (INVALID_HANDLE_VALUE == hFind) 
   {
      return 0;
   } 
   
   do
   {
      if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      {
         OutputDebugString(ffd.cFileName);
      }
      else
      {
		 Msgno = atoi(&ffd.cFileName[2]);

		 if (MsgnotoMsg[Msgno] == 0)
		 {
			 sprintf(File, "%s/%s%c", MailDir, ffd.cFileName, 0);
			 Debugprintf("Tidy Mail - Delete %s\n", File);

//			 if (DeletetoRecycleBin)
				DeletetoRecycle(File);
//			 else
//				 DeleteFile(File);
		 }
      }
   }
   while (FindNextFile(hFind, &ffd) != 0);
 
   FindClose(hFind);
   return 0;
}

#else

#include <dirent.h>

int MsgFilter(const struct dirent * dir)
{
	return (strstr(dir->d_name, ".mes") != 0);
}

int DeleteRedundantMessages()
{
	struct dirent **namelist;
    int n;
	struct stat STAT;
	int Msgno = 0, res;
	char File[100];
     	
    n = scandir("Mail", &namelist, MsgFilter, alphasort);

	if (n < 0) 
		perror("scandir");
	else  
	{ 
		while(n--)
		{
			if (stat(namelist[n]->d_name, &STAT) == 0)
			{
				Msgno = atoi(&namelist[n]->d_name[2]);

				if (MsgnotoMsg[Msgno] == 0)
				{
					sprintf(File, "Mail/%s", namelist[n]->d_name);
					printf("Deleting %s\n", File);
					unlink(File);
				}
			}
			free(namelist[n]);
		}
		free(namelist);
    }
	return 0;
}
#endif

VOID TidyWelcomeMsg(char ** pPrompt)
{
	// Make sure Welcome Message doesn't ends with >

	char * Prompt = *pPrompt;

	int i = (int)strlen(Prompt) - 1;

	*pPrompt = realloc(Prompt, i + 5);	// In case we need to expand it

	Prompt = *pPrompt;

	while (Prompt[i] == 10 || Prompt[i] == 13)
	{
		Prompt[i--] = 0;
	}

	while (i >= 0 && Prompt[i] == '>')
		Prompt[i--] = 0;

	strcat(Prompt, "\r\n");
}

VOID TidyPrompt(char ** pPrompt)
{
	// Make sure prompt ends > CR LF

	char * Prompt = *pPrompt;

	int i = (int)strlen(Prompt) - 1;

	*pPrompt = realloc(Prompt, i + 5);	// In case we need to expand it

	Prompt = *pPrompt;

	while (Prompt[i] == 10 || Prompt[i] == 13)
	{
		Prompt[i--] = 0;
	}

	if (Prompt[i] != '>')
		strcat(Prompt, ">");

	strcat(Prompt, "\r\n");
}

VOID TidyPrompts()
{
	TidyPrompt(&Prompt);
	TidyPrompt(&NewPrompt);
	TidyPrompt(&ExpertPrompt);
}

BOOL SendARQMail(CIRCUIT * conn)
{
	conn->NextMessagetoForward = FirstMessageIndextoForward;

	// Send Message. There is no mechanism for reverse forwarding

	if (FindMessagestoForward(conn))
	{
		struct MsgInfo * Msg;
		char MsgHddr[512];
		int HddrLen;
		char TimeString[64];
		char * WholeMessage;

		char * MsgBytes = ReadMessageFile(conn->FwdMsg->number);
		int MsgLen;
		
		if (MsgBytes == 0)
		{
			MsgBytes = _strdup("Message file not found\r");
			conn->FwdMsg->length = (int)strlen(MsgBytes);
		}

		Msg = conn->FwdMsg;
		WholeMessage = malloc(Msg->length + 512);

		FormatTime(TimeString, (time_t)Msg->datecreated);

/*
ARQ:FILE::flarqmail-1.eml
ARQ:EMAIL::
ARQ:SIZE::96
ARQ::STX
//FLARQ COMPOSER
Date: 16/01/2014 22:26:06
To: g8bpq
From: 
Subject: test message

Hello
Hello

ARQ::ETX		
*/
		Logprintf(LOG_BBS, conn, '>', "ARQ Send Msg %d From %s To %s", Msg->number, Msg->from, Msg->to);

		HddrLen = sprintf(MsgHddr, "Date: %s\nTo: %s\nFrom: %s\nSubject %s\n\n",
			TimeString, Msg->to, Msg->from, Msg->title);
				
		MsgLen = sprintf(WholeMessage, "ARQ:FILE::Msg%s_%d\nARQ:EMAIL::\nARQ:SIZE::%d\nARQ::STX\n%s%s\nARQ::ETX\n",
			BBSName, Msg->number, (int)(HddrLen + strlen(MsgBytes)), MsgHddr, MsgBytes);

		WholeMessage[MsgLen] = 0;
		QueueMsg(conn,WholeMessage, MsgLen);

		free(WholeMessage);
		free(MsgBytes);

		// FLARQ doesn't ACK the message, so set flag to look for all acked
				
		conn->BBSFlags |= ARQMAILACK;
		conn->ARQClearCount = 10;		// To make sure clear isn't reported too soon

		return TRUE;
	}

	// Nothing to send - close

	Logprintf(LOG_BBS, conn, '>', "ARQ Send -  Nothing to Send - Closing");

	conn->CloseAfterFlush = 20;
	return FALSE;
}

char *stristr (char *ch1, char *ch2)
{
	char	*chN1, *chN2;
	char	*chNdx;
	char	*chRet				= NULL;

	chN1 = _strdup (ch1);
	chN2 = _strdup (ch2);
	if (chN1 && chN2)
	{
		chNdx = chN1;
		while (*chNdx)
		{
			*chNdx = (char) tolower (*chNdx);
			chNdx ++;
		}
		chNdx = chN2;
		while (*chNdx)
		{
			*chNdx = (char) tolower (*chNdx);
			chNdx ++;
		}

		chNdx = strstr (chN1, chN2);
		if (chNdx)
			chRet = ch1 + (chNdx - chN1);
	}
	free (chN1);
	free (chN2);
	return chRet;
}

#ifdef WIN32

void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename)
{

   WIN32_FIND_DATA ffd;

   char szDir[MAX_PATH];
   HANDLE hFind = INVALID_HANDLE_VALUE;
 
   // Prepare string for use with FindFile functions.  First, copy the
   // string to a buffer, then append '\*' to the directory name.

   strcpy(szDir, GetBPQDirectory());
   strcat(szDir, "\\BPQMailChat\\Files\\*.*");

   // Find the first file in the directory.

   hFind = FindFirstFile(szDir, &ffd);

   if (INVALID_HANDLE_VALUE == hFind) 
   {
      nodeprintf(conn, "No Files\r");
	  return;
   } 
   
   // List all the files in the directory with some info about them.

	do
	{
		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{}
		else
		{
			if (filename == NULL || stristr(ffd.cFileName, filename))
				nodeprintf(conn, "%s %d\r", ffd.cFileName, ffd.nFileSizeLow);
		}
	}
	while (FindNextFile(hFind, &ffd) != 0);
	
	FindClose(hFind);
}

#else

#include <dirent.h>

void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename)
{
	struct dirent **namelist;
    int n, i;
	struct stat STAT;
	time_t now = time(NULL);
	int Age = 0, res;
	char FN[256];
     	
    n = scandir("Files", &namelist, NULL, alphasort);

	if (n < 0) 
		perror("scandir");
	else  
	{ 
		for (i = 0; i < n; i++)
		{
			sprintf(FN, "Files/%s", namelist[i]->d_name);

			if (filename == NULL || stristr(namelist[i]->d_name, filename))
				if (FN[6] != '.' && stat(FN, &STAT) == 0)
					nodeprintf(conn, "%s %d\r", namelist[i]->d_name, STAT.st_size);
			
			free(namelist[i]);
		}
		free(namelist);
    }
	return;
}
#endif

void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename)
{
	char * MsgBytes;
	
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	struct stat STAT;

	if (filename == NULL)
	{
		nodeprintf(conn, "Missing Filename\r");
		return;
	}

	if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\'))
	{
		nodeprintf(conn, "Invalid filename\r");
		return;
	}

	if (BaseDir[0])
		sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename);
	else
		sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename);

	if (stat(MsgFile, &STAT) != -1)
	{
		FileSize = STAT.st_size;

		hFile = fopen(MsgFile, "rb");

		if (hFile)
		{
			int Length;
	
			MsgBytes=malloc(FileSize+1);
			fread(MsgBytes, 1, FileSize, hFile); 
			fclose(hFile);

			MsgBytes[FileSize]=0;

			// Remove lf chars

			Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes));

			QueueMsg(conn, MsgBytes, Length);
			free(MsgBytes);

			nodeprintf(conn, "\r\r[End of File %s]\r", filename);
			return;
		}
	}

	nodeprintf(conn, "File %s not found\r", filename);
}

VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len)
{
	struct  TempUserInfo * Temp = user->Temp;

	Buffer[len] = 0;

	//	Command entered during listing pause. May be A R or C (or <CR>)

	if (Buffer[0] == 'A' || Buffer[0] == 'a')
	{
		// Abort

		Temp->ListActive = Temp->ListSuspended = FALSE;
		SendPrompt(conn, user);
		return;
	}

	if (_memicmp(Buffer, "R ", 2) == 0)
	{
		// Read Message(es)

		int msgno;
		char * ptr;
		char * Context;

		ptr = strtok_s(&Buffer[2], " ", &Context);

		while (ptr)
		{
			msgno = atoi(ptr);
			ReadMessage(conn, user, msgno);

			ptr = strtok_s(NULL, " ", &Context);
		}

		nodeprintf(conn, "<A>bort, <R Msg(s)>, <CR> = Continue..>");
		return;
	}

	if (Buffer[0] == 'C' || Buffer[0] == 'c' || Buffer[0] == '\r' )
	{
		//	Resume Listing from where we left off

		DoListCommand(conn, user, Temp->LastListCommand, Temp->LastListParams, TRUE, "");
		SendPrompt(conn, user);
		return;
	}

	nodeprintf(conn, "<A>bort, <R Message>, <CR> = Continue..>");

}
/*
CreateMessageWithAttachments()
{
	int i;
	char * ptr, * ptr2, * ptr3, * ptr4;
	char Boundary[1000];
	BOOL Multipart = FALSE;
	BOOL ALT = FALSE;
	int Partlen;
	char * Save;
	BOOL Base64 = FALSE;
	BOOL QuotedP = FALSE;
	
	char FileName[100][250] = {""};
	int FileLen[100];
	char * FileBody[100];
	char * MallocSave[100];
	UCHAR * NewMsg;

	int Files = 0;

	ptr = Msg;

	if ((sockptr->MailSize + 2000) > sockptr->MailBufferSize)
	{
		sockptr->MailBufferSize += 2000;
		sockptr->MailBuffer = realloc(sockptr->MailBuffer, sockptr->MailBufferSize);
	
		if (sockptr->MailBuffer == NULL)
		{
			CriticalErrorHandler("Failed to extend Message Buffer");
			shutdown(sockptr->socket, 0);
			return FALSE;
		}
	}


	NewMsg = sockptr->MailBuffer + 1000;

	NewMsg += sprintf(NewMsg, "Body: %d\r\n", FileLen[0]);

	for (i = 1; i < Files; i++)
	{
		NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[i], FileName[i]);
	}

	NewMsg += sprintf(NewMsg, "\r\n");

	for (i = 0; i < Files; i++)
	{
		memcpy(NewMsg, FileBody[i], FileLen[i]);
		NewMsg += FileLen[i];
		free(MallocSave[i]);
		NewMsg += sprintf(NewMsg, "\r\n");
	}

	*MsgLen = NewMsg - (sockptr->MailBuffer + 1000);
	*Body = sockptr->MailBuffer + 1000;

	return TRUE;		// B2 Message
}

*/
VOID CreateUserReport()
{
	struct UserInfo * User;
	int i;
	char Line[200];
	int len;
	char File[MAX_PATH];
	FILE * hFile;

	sprintf(File, "%s/UserList.csv", BaseDir);
	
	hFile = fopen(File, "wb");

	if (hFile == NULL)
	{
		Debugprintf("Failed to create UserList.csv");
		return;
	}
	
	for (i=1; i <= NumberofUsers; i++)
	{
		User = UserRecPtr[i];

		len = sprintf(Line, "%s,%d,%s,%x,%s,\"%s\",%x,%s,%s,%s\r\n",
			User->Call,
			User->lastmsg,
			FormatDateAndTime((time_t)User->TimeLastConnected, FALSE),
			User->flags,
			User->Name,
			User->Address,
			User->RMSSSIDBits,
			User->HomeBBS,
			User->QRA,
			User->ZIP
//	struct MsgStats Total;
//	struct MsgStats	Last;
			);
			fwrite(Line, 1, len, hFile);
	}

	fclose(hFile);
}

BOOL ProcessYAPPMessage(CIRCUIT * conn)
{
	int Len = conn->InputLen;
	UCHAR * Msg = conn->InputBuffer;
	int pktLen = Msg[1];
	char Reply[2] = {ACK};
	int NameLen, SizeLen, OptLen;
	char * ptr;
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	char Mess[255];
	int len;
	char * FN = &Msg[2];

	switch (Msg[0])
	{
	case ENQ: // YAPP Send_Init

		// Shouldn't occur in session. Reset state
				
		Mess[0] = ACK;
		Mess[1] = 1;
		QueueMsg(conn, Mess, 2);
		Flush(conn);
		conn->InputLen = 0;
		if (conn->MailBuffer)
		{
			free(conn->MailBuffer);
			conn->MailBufferSize=0;
			conn->MailBuffer=0;
		}
		return TRUE;

	case SOH:

		// HD Send_Hdr     SOH  len  (Filename)  NUL  (File Size in ASCII)  NUL (Opt) 

		// YAPPC has date/time in dos format

		if (Len < Msg[1] + 1)
			return 0;

		NameLen = (int)strlen(FN);
		strcpy(conn->ARQFilename, FN);
		ptr = &Msg[3 + NameLen];
		SizeLen = (int)strlen(ptr);
		FileSize = atoi(ptr);

		// Check file name for unsafe characters (.. / \)

		if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\'))
		{
			Mess[0] = NAK;
			Mess[1] = 0;
			QueueMsg(conn, Mess, 2);
			Flush(conn);
			len = sprintf_s(Mess, sizeof(Mess), "YAPP File Name %s invalid\r", FN);
			QueueMsg(conn, Mess, len);
			SendPrompt(conn, conn->UserPointer);
			WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);

			conn->InputLen = 0;
			conn->InputMode = 0;

			return FALSE;
		}

		OptLen = pktLen - (NameLen + SizeLen + 2);

		conn->YAPPDate = 0;

		if (OptLen >= 8)		// We have a Date/Time for YAPPC
		{
			ptr = ptr + SizeLen + 1;
			conn->YAPPDate = strtol(ptr, NULL, 16);
		}

		// Check Size

		if (FileSize > MaxRXSize)
		{
			Mess[0] = NAK;
			Mess[1] = sprintf(&Mess[2], "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize);
			QueueMsg(conn, Mess, Mess[1] + 2);
	
			Flush(conn);

			len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize);
			QueueMsg(conn, Mess, len);
			SendPrompt(conn, conn->UserPointer);
			WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);

			conn->InputLen = 0;
			conn->InputMode = 0;

			return FALSE;
		}
		
		// Make sure file does not exist

	
		sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename);
	
		hFile = fopen(MsgFile, "rb");

		if (hFile)
		{
			Mess[0] = NAK;
			Mess[1] = sprintf(&Mess[2], "YAPP File %s already exists\r", conn->ARQFilename);;
			QueueMsg(conn, Mess, Mess[1] + 2);
	
			Flush(conn);
	
			len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s already exists\r", conn->ARQFilename);
			QueueMsg(conn, Mess, len);
			SendPrompt(conn, conn->UserPointer);
			WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);
			fclose(hFile);

			conn->InputLen = 0;
			conn->InputMode = 0;

			return FALSE;
		}


		conn->MailBufferSize = FileSize;
		conn->MailBuffer=malloc(FileSize);
		conn->YAPPLen = 0;

		if (conn->YAPPDate)			// If present use YAPPC
			Reply[1] = ACK;			//Receive_TPK
		else
			Reply[1] = 2;			//Rcv_File

		QueueMsg(conn, Reply, 2);

		len = sprintf_s(Mess, sizeof(Mess), "YAPP upload to %s started", conn->ARQFilename);
		WriteLogLine(conn, '!', Mess, len, LOG_BBS);

		conn->InputLen = 0;
		return FALSE;
		
	case STX:

		// Data Packet

		// Check we have it all

		if (conn->YAPPDate)			// If present use YAPPC so have checksum
		{
			if (pktLen > (Len - 3))		// -3 for header and checksum
				return 0;				// Wait for rest
		}
		else
		{
			if (pktLen > (Len - 2))		// -2 for header
				return 0;				// Wait for rest
		}

		// Save data and remove from buffer

		// if YAPPC check checksum

		if (conn->YAPPDate)
		{
			UCHAR Sum = 0;
			int i;
			UCHAR * uptr = &Msg[2];

			i = pktLen;

			while(i--)
				Sum += *(uptr++);

			if (Sum != *uptr)
			{
				// Checksum Error

				Mess[0] = CAN;
				Mess[1] = 0;
				QueueMsg(conn, Mess, 2);
				Flush(conn);
				len = sprintf_s(Mess, sizeof(Mess), "YAPPC Checksum Error\r");
				QueueMsg(conn, Mess, len);
				WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);
				conn->InputLen = 0;
				conn->InputMode = 0;
				return TRUE;
			}
		}

		if ((conn->YAPPLen) + pktLen > conn->MailBufferSize)
		{
			// Too Big ??

			Mess[0] = CAN;
			Mess[1] = 0;
			QueueMsg(conn, Mess, 2);
			Flush(conn);
			len = sprintf_s(Mess, sizeof(Mess), "YAPP Too much data received\r");
			QueueMsg(conn, Mess, len);
			WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);
			conn->InputLen = 0;
			conn->InputMode = 0;
			return TRUE;
		}


		memcpy(&conn->MailBuffer[conn->YAPPLen], &Msg[2], pktLen);
		conn->YAPPLen += pktLen;

		if (conn->YAPPDate)
			++pktLen;				// Add Checksum

		conn->InputLen -= (pktLen + 2);
		memmove(conn->InputBuffer, &conn->InputBuffer[pktLen + 2], conn->InputLen);

		return TRUE;

	case ETX:

		// End Data



		if (conn->YAPPLen == conn->MailBufferSize)
		{
			// All received

			int ret;
			DWORD Written = 0;

			sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename);
	
#ifdef WIN32
			hFile = CreateFile(MsgFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

			if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
			{	
				ret = WriteFile(hFile, conn->MailBuffer, conn->YAPPLen, &Written, NULL);
 
				if (conn->YAPPDate)
				{
					FILETIME FileTime;
					struct tm TM;
					struct timeval times[2];
					time_t TT;
/*			
					The MS-DOS date. The date is a packed value with the following format.

					cant use DosDateTimeToFileTime on Linux
		
					Bits	Description
					0-4	Day of the month (1–31)
					5-8	Month (1 = January, 2 = February, and so on)
					9-15	Year offset from 1980 (add 1980 to get actual year)
					wFatTime
					The MS-DOS time. The time is a packed value with the following format.
					Bits	Description
					0-4	Second divided by 2
					5-10	Minute (0–59)
					11-15	Hour (0–23 on a 24-hour clock)
*/
					memset(&TM, 0, sizeof(TM));

					TM.tm_sec = (conn->YAPPDate & 0x1f) << 1;
					TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f);
					TM.tm_hour =  ((conn->YAPPDate >> 11) & 0x1f);

					TM.tm_mday =  ((conn->YAPPDate >> 16) & 0x1f);
					TM.tm_mon =  ((conn->YAPPDate >> 21) & 0xf) - 1;
					TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80;

					Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec);

					TT = mktime(&TM);
					times[0].tv_sec = times[1].tv_sec = 
					times[0].tv_usec = times[1].tv_usec = 0;

					DosDateTimeToFileTime((WORD)(conn->YAPPDate >> 16), (WORD)conn->YAPPDate & 0xFFFF, &FileTime);
					ret = SetFileTime(hFile, &FileTime, &FileTime, &FileTime);
					ret = GetLastError();

				}
				CloseHandle(hFile);
			}
#else

			hFile = fopen(MsgFile, "wb");
			if (hFile)
			{
				Written = fwrite(conn->MailBuffer, 1, conn->YAPPLen, hFile);
				fclose(hFile);

				if (conn->YAPPDate)
				{
					struct tm TM;
					struct timeval times[2];
/*			
					The MS-DOS date. The date is a packed value with the following format.

					cant use DosDateTimeToFileTime on Linux
		
					Bits	Description
					0-4	Day of the month (1–31)
					5-8	Month (1 = January, 2 = February, and so on)
					9-15	Year offset from 1980 (add 1980 to get actual year)
					wFatTime
					The MS-DOS time. The time is a packed value with the following format.
					Bits	Description
					0-4	Second divided by 2
					5-10	Minute (0–59)
					11-15	Hour (0–23 on a 24-hour clock)
*/
					memset(&TM, 0, sizeof(TM));

					TM.tm_sec = (conn->YAPPDate & 0x1f) << 1;
					TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f);
					TM.tm_hour =  ((conn->YAPPDate >> 11) & 0x1f);

					TM.tm_mday =  ((conn->YAPPDate >> 16) & 0x1f);
					TM.tm_mon =  ((conn->YAPPDate >> 21) & 0xf) - 1;
					TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80;

					Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec);

					times[0].tv_sec = times[1].tv_sec = mktime(&TM);
					times[0].tv_usec = times[1].tv_usec = 0;
				}
			}
#endif

			free(conn->MailBuffer);
			conn->MailBufferSize=0;
			conn->MailBuffer=0;

			if (Written != conn->YAPPLen)
			{
				Mess[0] = CAN;
				Mess[1] = 0;
				QueueMsg(conn, Mess, 2);
				Flush(conn);
				len = sprintf_s(Mess, sizeof(Mess), "Failed to save YAPP File\r");
				QueueMsg(conn, Mess, len);
				WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);
				conn->InputLen = 0;
				conn->InputMode = 0;
			}
		}

		Reply[1] = 3;		//Ack_EOF
		QueueMsg(conn, Reply, 2);
		Flush(conn);	
		conn->InputLen = 0;

		return TRUE;

	case EOT:

		// End Session

		Reply[1] = 4;		// Ack_EOT
		QueueMsg(conn, Reply, 2);
		Flush(conn);
		conn->InputLen = 0;
		conn->InputMode = 0;
	
		len = sprintf_s(Mess, sizeof(Mess), "YAPP file %s received\r", conn->ARQFilename);
		WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);
		QueueMsg(conn, Mess, len);
		SendPrompt(conn, conn->UserPointer);

		return TRUE;

	case CAN:

		// Abort

		Mess[0] = ACK;
		Mess[1] = 5;			// CAN Ack
		QueueMsg(conn, Mess, 2);
		Flush(conn);

		if (conn->MailBuffer)
		{
			free(conn->MailBuffer);
			conn->MailBufferSize=0;
			conn->MailBuffer=0;
		}

		// There may be a reason after the CAN

		len = Msg[1];

		if (len)
		{
			char * errormsg = &Msg[2];
			errormsg[len] = 0;
			nodeprintf(conn, "File Rejected - %s\r", errormsg);
		}
		else
			
			nodeprintf(conn, "File Rejected\r");


		len = sprintf_s(Mess, sizeof(Mess), "YAPP Transfer cancelled by Terminal\r");
		WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS);

		conn->InputLen = 0;
		conn->InputMode = 0;
		conn->BBSFlags &= ~YAPPTX;

		return FALSE;

	case ACK:

		switch (Msg[1])
		{
		case 1:					// Rcv_Rdy

			// HD Send_Hdr     SOH  len  (Filename)  NUL  (File Size in ASCII)  NUL (Opt)
			
			len = (int)strlen(conn->ARQFilename) + 3;
		
			strcpy(&Mess[2], conn->ARQFilename);
			len += sprintf(&Mess[len], "%d", conn->MailBufferSize);
			len++;					// include null
			Mess[0] = SOH;
			Mess[1] = len - 2;

			QueueMsg(conn, Mess, len);
			Flush(conn);
			conn->InputLen = 0;

			return FALSE;

		case 2:

			//	Start sending message

			YAPPSendData(conn);
			conn->InputLen = 0;
			return FALSE;

		case 3:

			// ACK EOF - Send EOT

			
			Mess[0] = EOT;
			Mess[1] = 1;
			QueueMsg(conn, Mess, 2);
			Flush(conn);

			conn->InputLen = 0;
			return FALSE;
	
		case 4:

			// ACK EOT

			conn->InputMode = 0;
			conn->BBSFlags &= ~YAPPTX;

			conn->InputLen = 0;
			return FALSE;

		default:
			conn->InputLen = 0;
			return FALSE;



		}
	
	case NAK:

		// Either Reject or Restart

		// RE Resume       NAK  len  R  NULL  (File size in ASCII)  NULL

		if (conn->InputLen  > 2 && Msg[2] == 'R' && Msg[3] == 0)
		{
			int posn = atoi(&Msg[4]);
			
			conn->YAPPLen += posn;
			conn->MailBufferSize -= posn;

			YAPPSendData(conn);
			conn->InputLen = 0;
			return FALSE;

		}

		// There may be a reason after the ack

		len = Msg[1];

		if (len)
		{
			char * errormsg = &Msg[2];
			errormsg[len] = 0;
			nodeprintf(conn, "File Rejected - %s\r", errormsg);
		}
		else
			
			nodeprintf(conn, "File Rejected\r");

		conn->InputMode = 0;
		conn->BBSFlags &= ~YAPPTX;
		conn->InputLen = 0;
		SendPrompt(conn, conn->UserPointer);
		return FALSE;
	}

	nodeprintf(conn, "Unexpected message during YAPP Transfer. Transfer canncelled\r");

	conn->InputMode = 0;
	conn->BBSFlags &= ~YAPPTX;
	conn->InputLen = 0;
	SendPrompt(conn, conn->UserPointer);

	return FALSE;

}

void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename)
{
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	struct stat STAT;

	if (filename == NULL)
	{
		nodeprintf(conn, "Filename missing\r");
		SendPrompt(conn, user);
		return;
	}

	if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\'))
	{
		nodeprintf(conn, "Invalid filename\r");
		SendPrompt(conn, user);
		return;
	}

	if (BaseDir[0])
		sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename);
	else
		sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename);

	if (stat(MsgFile, &STAT) != -1)
	{
		FileSize = STAT.st_size;

		hFile = fopen(MsgFile, "rb");

		if (hFile)
		{	
			char Mess[255];
			strcpy(conn->ARQFilename, filename);
			conn->MailBuffer = malloc(FileSize);
			conn->MailBufferSize = FileSize;
			conn->YAPPLen = 0;
			fread(conn->MailBuffer, 1, FileSize, hFile); 
			fclose(hFile);
	
			Mess[0] = ENQ;
			Mess[1] = 1;

			QueueMsg(conn, Mess, 2);
			Flush(conn);

			conn->InputMode = 'Y';

			return;
		}
	}

	nodeprintf(conn, "File %s not found\r", filename);
	SendPrompt(conn, user);
}

void YAPPSendData(ConnectionInfo * conn)
{
	char Mess[258];

	conn->BBSFlags |= YAPPTX;

	while (TXCount(conn->BPQStream) < 15)
	{
		int Left = conn->MailBufferSize;

		if (Left == 0)
		{
			// Finished - send End Data

			Mess[0] = ETX;
			Mess[1] = 1;
			
			QueueMsg(conn, Mess, 2);
			Flush(conn);

			conn->BBSFlags &= ~YAPPTX;
			break;
		}

		if (Left > conn->paclen - 2)		// 2 byte header
			Left = conn->paclen -2;

		memcpy(&Mess[2], &conn->MailBuffer[conn->YAPPLen], Left);
		Mess[0] = STX;
		Mess[1] = Left;
				
		QueueMsg(conn, Mess, Left + 2);
		Flush(conn);

		conn->YAPPLen += Left;
		conn->MailBufferSize -= Left;
	}
}

char * AddUser(char * Call, char * password, BOOL BBSFlag)
{
	struct UserInfo * USER;

	strlop(Call, '-');

	if (strlen(Call) > 6)
		Call[6] = 0;

	_strupr(Call);
		
	if (Call[0] == 0 || LookupCall(Call))
	{
		return("User already exists\r\n");
	}

	USER = AllocateUserRecord(Call);
	USER->Temp = zalloc(sizeof (struct TempUserInfo));

	if (strlen(password) > 12)
		password[12] = 0;

	strcpy(USER->pass, password);

	if (BBSFlag)
	{
		if(SetupNewBBS(USER))
			USER->flags |= F_BBS;
		else
			printf("Cannot set user to be a BBS - you already have 160 BBS's defined\r\n");
	}

	SaveUserDatabase();
	UpdateWPWithUserInfo(USER);

	return("User added\r\n");
}

// Server Support Code

// For the moment only internal REQDIR and REQFIL.

// May add WPSERV and user implemented servers
/*
F6FBB BBS >
 SP REQDIR @ F6ABJ.FRA.EU
 Title of message :
 YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU
 Text of message :
 /EX

 F6FBB BBS >
 SP REQFIL @ F6ABJ.FRA.EU
 Title of message :
 DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU
 Text of message :
 /EX

 Note Text not used.

*/

VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To);

BOOL ProcessReqDir(struct MsgInfo * Msg)
{
	char * Buffer;
	int Len = 0;
	char * ptr;

	// Parse title - gives directory and return address

	// YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU

	// At the moment we don't allow subdirectories but no harm handling here

	char Pattern[64];
	char * Address;
	char * filename = NULL; // ?? Pattern Match ??

#ifdef WIN32

   WIN32_FIND_DATA ffd;

   char szDir[MAX_PATH];
   HANDLE hFind = INVALID_HANDLE_VALUE;

#else

	#include <dirent.h>

	struct dirent **namelist;
    int n, i;
	struct stat STAT;
	int res;
	char FN[256];
     	
#endif

	strcpy(Pattern, Msg->title);

	ptr = strchr(Pattern, '@');

	if (ptr == NULL)

		// if we don't have return address no point
		// but could we default to sender??

		return FALSE;

	*ptr++ = 0;			// Terminate Path

	strlop(Pattern, ' ');

	while (*ptr == ' ')
		ptr++;			// accept with or without spaces round @

	Address = ptr;

	ptr = Buffer = malloc(MaxTXSize);

#ifdef WIN32

   // Prepare string for use with FindFile functions.  First, copy the
   // string to a buffer, then append '\*' to the directory name.

   strcpy(szDir, GetBPQDirectory());
   strcat(szDir, "\\BPQMailChat\\Files\\");
   strcat(szDir, Pattern);

   // Find the first file in the directory.

   hFind = FindFirstFile(szDir, &ffd);

   if (INVALID_HANDLE_VALUE == hFind) 
   {
      Len = sprintf(Buffer, "No Files\r");
   } 
   else
   {
		do
		{
			if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{}
			else	
			{
				if (filename == NULL || stristr(ffd.cFileName, filename))
					Len += sprintf(&Buffer[Len], "%s %d\r", ffd.cFileName, ffd.nFileSizeLow);
			}
		}
		while (FindNextFile(hFind, &ffd) != 0);
	
		FindClose(hFind);
	}

#else

    n = scandir("Files", &namelist, NULL, alphasort);

	if (n < 0) 
		perror("scandir");
	else  
	{ 
		for (i = 0; i < n; i++)
		{
			sprintf(FN, "Files/%s", namelist[i]->d_name);

			if (filename == NULL || stristr(namelist[i]->d_name, filename))
				if (FN[6] != '.' && stat(FN, &STAT) == 0)
					Len += sprintf(&Buffer[Len], "%s %d\r", namelist[i]->d_name, STAT.st_size);
			
			free(namelist[i]);
		}
		free(namelist);
    }

#endif

	// Build Message

	SendServerReply("REQDIR Reply", Buffer, Len, _strupr(Address));
	return TRUE;
}

/*
      '  Augment Message ID with the Message Pickup Station we're directing this message to.
        '
        Dim strAugmentedMessageID As String
        If GetMidRMS(MessageId) <> "" Then
            ' The MPS RMS is already set on the message ID
            strAugmentedMessageID = MessageId
            strMPS = GetMidRMS(MessageId)
            ' "@R" at the end of the MID means route message only via radio
            If GetMidForwarding(MessageId) = "" And (blnRadioOnly Or UploadThroughInternet()) Then
                strAugmentedMessageID &= "@" & strHFOnlyFlag
            End If
        ElseIf strMPS <> "" Then
            ' Add MPS to the message ID
            strAugmentedMessageID = MessageId & "@" & strMPS
            ' "@R" at the end of the MID means route message only via radio
            If blnRadioOnly Or UploadThroughInternet() Then
                strAugmentedMessageID &= "@" & strHFOnlyFlag
            End If
        Else
            strAugmentedMessageID = MessageId
        End If
  
*/

void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len)
{
	Buffer[len] = 0;

	if (conn->Flags & GETTINGSYNCMESSAGE)
	{
		// Data

		if ((conn->TempMsg->length + len) > conn->MailBufferSize)
		{
			conn->MailBufferSize += 10000;
			conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize);

			if (conn->MailBuffer == NULL)
			{
				BBSputs(conn, "*** Failed to extend Message Buffer\r");
				conn->CloseAfterFlush = 20;			// 2 Secs

				return;
			}
		}

		memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len);

		conn->TempMsg->length += len;

		if (conn->TempMsg->length >= conn->SyncCompressedLen)
		{
			// Complete - decompress it

			conn->BBSFlags |= FBBCompressed;
			Decode(conn, 1);

			conn->Flags &= !GETTINGSYNCMESSAGE;

			BBSputs(conn, "OK\r");	
			return;
		}
		return;
	}

	if (conn->Flags & PROPOSINGSYNCMSG)
	{
		// Waiting for response to TR AddMessage

		if (strcmp(Buffer, "OK\r") == 0)
		{
			char Msg[256];
			int n;

			WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
	
			// Send the message, it has already been built

			conn->Flags &= !PROPOSINGSYNCMSG;
			conn->Flags |= SENDINGSYNCMSG;

			n = sprintf_s(Msg, sizeof(Msg), "Sending SYNC message %s", conn->FwdMsg->bid);
			WriteLogLine(conn, '|',Msg, n, LOG_BBS);

			QueueMsg(conn, conn->SyncMessage, conn->SyncCompressedLen);
			return;
		}

		if (strcmp(Buffer, "NO\r") == 0)
		{
			WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
	
			// Message Rejected - ? duplicate

			if (conn->FwdMsg)
			{
				// Zap the entry

				clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber);
				set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber);
				conn->UserPointer->ForwardingInfo->MsgCount--;

				//  Only mark as forwarded if sent to all BBSs that should have it

				if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0)
				{
					conn->FwdMsg->status = 'F';			// Mark as forwarded
					conn->FwdMsg->datechanged=time(NULL);
				}

				conn->FwdMsg->Locked = 0;	// Unlock
			}
		}

		BBSputs(conn, "BYE\r");
		conn->CloseAfterFlush = 20;			// 2 Secs
		conn->Flags &= !PROPOSINGSYNCMSG;
		conn->BBSFlags &= ~SYNCMODE;
		return;
	}

	if (conn->Flags & SENDINGSYNCMSG)
	{
		if (strcmp(Buffer, "OK\r") == 0)
		{
			// Message Sent

			conn->Flags &= !SENDINGSYNCMSG;
			free(conn->SyncMessage);

			if (conn->FwdMsg)
			{
				char Msg[256];
				int n;

				n = sprintf_s(Msg, sizeof(Msg), "SYNC message %s Sent", conn->FwdMsg->bid);
				WriteLogLine(conn, '|',Msg, n, LOG_BBS);

				clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber);
				set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber);
				conn->UserPointer->ForwardingInfo->MsgCount--;

				//  Only mark as forwarded if sent to all BBSs that should have it

				if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0)
				{
					conn->FwdMsg->status = 'F';			// Mark as forwarded
					conn->FwdMsg->datechanged=time(NULL);
				}

				conn->FwdMsg->Locked = 0;	// Unlock
			}

			// drop through to send any more
		}
		else
		{
			WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
	
			conn->Flags &= !SENDINGSYNCMSG;
			free(conn->SyncMessage);

			BBSputs(conn, "BYE\r");
			conn->CloseAfterFlush = 20;			// 2 Secs
			conn->BBSFlags &= ~SYNCMODE;

			return;
		}
	}

	if (strcmp(Buffer, "OK\r") == 0)
	{
		WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);

		// Send Message(?s) to RMS Relay SYNC

/*
<POSYNCLOGON G8BPQ
>OK
>TR AddMessage_V5JLSGH591JR 786 1219 522 True
<OK
.. message
<OK
?? repeat
>BYE*/
		if (FindMessagestoForward(conn) && conn->FwdMsg)
		{
			struct MsgInfo * Msg = conn->FwdMsg;
			char Buffer[128];
			char * Message;

			Message = FormatSYNCMessage(conn, Msg);

			// Need to compress it

			conn->SyncMessage = malloc(conn->SyncXMLLen + conn->SyncMsgLen + 4096);

			conn->SyncCompressedLen = Encode(Message, conn->SyncMessage, conn->SyncXMLLen + conn->SyncMsgLen, 0, 1);

			sprintf(Buffer, "TR AddMessage_%s %d %d %d True\r",		// The True on end indicates compressed
				Msg->bid, conn->SyncCompressedLen, conn->SyncXMLLen, conn->SyncMsgLen);

			free(Message);

			conn->Flags |= PROPOSINGSYNCMSG;

			BBSputs(conn, Buffer);
			return;
		}
				

		BBSputs(conn, "BYE\r");
		conn->CloseAfterFlush = 20;			// 2 Secs
		conn->BBSFlags &= ~SYNCMODE;
		return;
	}

	if (memcmp(Buffer, "TR ", 2) == 0) 
	{
		// Messages have TR_COMMAND_BID Compressed Len XML Len Bosy Len

		char * Command;
		char * BIDptr;

		BIDRec * BID;
		char *ptr2, *context;

		//		TR AddMessage_1145_G8BPQ 727 1202 440 True

		WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
		
		Command = strtok_s(&Buffer[3], "_", &context);
		BIDptr = strtok_s(NULL, " ", &context);
		ptr2 =  strtok_s(NULL, " ", &context);
		conn->SyncCompressedLen = atoi(ptr2);
		ptr2 =  strtok_s(NULL, " ", &context);
		conn->SyncXMLLen = atoi(ptr2);
		ptr2 =  strtok_s(NULL, " ", &context);
		conn->SyncMsgLen = atoi(ptr2);
		ptr2 =  strtok_s(NULL, " ", &context);

		// If addmessage need to check bid doesn't exist

		if (strcmp(Command, "AddMessage") == 0)
		{
			strlop(BIDptr, '@');			// sometimes has @CALL@R
			if (strlen(BIDptr) > 12)
				BIDptr[12] = 0;

			BID = LookupBID(BIDptr);

			if (BID)
			{
				BBSputs(conn, "Rejected - Duplicate BID\r");
				return;
			}
		}

		conn->TempMsg = zalloc(sizeof(struct MsgInfo));

		conn->Flags |= GETTINGSYNCMESSAGE;

		BBSputs(conn, "OK\r");
		return;
	}

	if (memcmp(Buffer, "BYE\r", 4) == 0)
	{
		WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
		conn->CloseAfterFlush = 20;			// 2 Secs
		conn->BBSFlags &= ~SYNCMODE;
		return;
	}

	if (memcmp(Buffer, "BBS\r", 4) == 0)
	{
		// Out of Sync

		WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
		conn->BBSFlags &= ~SYNCMODE;
		return;
	}

	WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS);
	WriteLogLine(conn, '<', "Unexpected SYNC Message", 23, LOG_BBS);

	BBSputs(conn, "BYE\r");
	conn->CloseAfterFlush = 20;			// 2 Secs
	conn->BBSFlags &= ~SYNCMODE;
	return;	
}
BOOL ProcessReqFile(struct MsgInfo * Msg)
{
	char FN[128];
	char * Buffer;
	int Len = 0;
	char * ptr;
	struct stat STAT;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	int FileSize;
	char * MsgBytes;

	// Parse title - gives file and return address

	// DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU

	// At the moment we don't allow subdirectories but no harm handling here

	char * Address;
	char * filename = NULL; // ?? Pattern Match ??

	strcpy(FN, Msg->title);

	ptr = strchr(FN, '@');

	if (ptr == NULL)

		// if we don't have return address no point
		// but could we default to sender??

		return FALSE;

	*ptr++ = 0;			// Terminate Path

	strlop(FN, ' ');

	while (*ptr == ' ')
		ptr++;			// accept with or without spaces round @

	Address = ptr;

	ptr = Buffer = malloc(MaxTXSize + 1);	// Allow terminating Null

	// Build Message

	if (FN == NULL)
	{
		Len = sprintf(Buffer, "Missing Filename\r");
	}
	else if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\'))
	{
		Len = sprintf(Buffer,"Invalid filename %s\r", FN);
	}
	else
	{
		if (BaseDir[0])
			sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, FN);
		else
			sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", FN);

		if (stat(MsgFile, &STAT) != -1)
		{
			FileSize = STAT.st_size;

			hFile = fopen(MsgFile, "rb");

			if (hFile)
			{
				int Length;

				if (FileSize > MaxTXSize)
					FileSize = MaxTXSize;		// Truncate to max size
	
				MsgBytes=malloc(FileSize+1);
				fread(MsgBytes, 1, FileSize, hFile); 
				fclose(hFile);

				MsgBytes[FileSize]=0;

				// Remove lf chars

				Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes));

				Len = sprintf(Buffer, "%s", MsgBytes);
				free(MsgBytes);
			}
		}
		else
			Len = sprintf(Buffer, "File %s not found\r", FN);
	}

	SendServerReply("REQFIL Reply", Buffer, Len, _strupr(Address));
	return TRUE;
}

BOOL CheckforMessagetoServer(struct MsgInfo * Msg)
{
	if (_stricmp(Msg->to, "REQDIR") == 0)
		return ProcessReqDir(Msg);

	if (_stricmp(Msg->to, "REQFIL") == 0)
		return ProcessReqFile(Msg);

	return FALSE;
}

VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To)
{
	struct MsgInfo * Msg = AllocateMsgRecord();
	BIDRec * BIDRec;
	char * Via;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	size_t WriteLen=0;

	Msg->length = Length;

	GetSemaphore(&MsgNoSemaphore, 0);
	Msg->number = ++LatestMsg;
	MsgnotoMsg[Msg->number] = Msg;

	FreeSemaphore(&MsgNoSemaphore);
 
	strcpy(Msg->from, BBSName);
	Via = strlop(To, '@');

	if (Via)
		strcpy(Msg->via, Via);

	strcpy(Msg->to, To);
	strcpy(Msg->title, Title);

	Msg->type = 'P';
	Msg->status = 'N';
	Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL);

	sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName);

	BIDRec = AllocateBIDRecord();
	strcpy(BIDRec->BID, Msg->bid);
	BIDRec->mode = Msg->type;
	BIDRec->u.msgno = LOWORD(Msg->number);
	BIDRec->u.timestamp = LOWORD(time(NULL)/86400);

	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number);
	
	hFile = fopen(MsgFile, "wb");

	if (hFile)
	{
		WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile);
		fclose(hFile);
	}

	MatchMessagetoBBSList(Msg, NULL);
	free(MailBuffer);
}

void SendRequestSync(CIRCUIT * conn)
{
	// Only need XML Header

	char * Buffer = malloc(4096);
	int Len = 0;
	
	struct tm *tm;
	char Date[32];
	char MsgTime[32];
	time_t Time = time(NULL);

	tm = gmtime(&Time);

	sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);

	sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min);

	Len += sprintf(&Buffer[Len], "<?xml version=\"1.0\"?>\r\n");

	Len += sprintf(&Buffer[Len], "<sync_record>\r\n");
	Len += sprintf(&Buffer[Len], "  <po_sync>\r\n");
    Len += sprintf(&Buffer[Len], "    <transaction_type>request_sync</transaction_type>\r\n");
    Len += sprintf(&Buffer[Len], "    <timestamp>%s</timestamp>\r\n", Date);
    Len += sprintf(&Buffer[Len], "    <originating_station>%s</originating_station>\r\n", BBSName);
	Len += sprintf(&Buffer[Len], "  </po_sync>\r\n");
 	Len += sprintf(&Buffer[Len], "  <request_sync>\r\n");
 	Len += sprintf(&Buffer[Len], "    <callsign>BBSName</callsign>\r\n");
 	Len += sprintf(&Buffer[Len], "    <password></password>\r\n");
	Len += sprintf(&Buffer[Len], "    <ip_address>%s</ip_address>\r\n", conn->SyncHost);
	Len += sprintf(&Buffer[Len], "    <ip_port>%d</ip_port>\r\n", conn->SyncPort);
 	Len += sprintf(&Buffer[Len], "    <note></note>\r\n");
 	Len += sprintf(&Buffer[Len], "  </request_sync>\r\n");
 	Len += sprintf(&Buffer[Len], "</sync_record>\r\n");
 
/*
<?xml version="1.0"?>
<sync_record>
  <po_sync>
    <transaction_type>request_sync</transaction_type>
    <timestamp>20230205100652</timestamp>
    <originating_station>GI8BPQ</originating_station>
  </po_sync>
  <request_sync>
    <callsign>GI8BPQ</callsign>
    <password></password>
    <ip_address>127.0.0.1</ip_address>
    <ip_port>8780</ip_port>
    <note></note>
  </request_sync>
</sync_record>
*/

	// Need to compress it

	conn->SyncXMLLen = Len;
	conn->SyncMsgLen = 0;

	conn->SyncMessage = malloc(conn->SyncXMLLen + 4096);

	conn->SyncCompressedLen = Encode(Buffer, conn->SyncMessage, conn->SyncXMLLen, 0, 1);

	sprintf(Buffer, "TR RequestSync_%s_%d %d %d 0 True\r",		// The True on end indicates compressed
				50, conn->SyncCompressedLen, conn->SyncXMLLen);

	free(Buffer);

	conn->Flags |= REQUESTINGSYNC;

	BBSputs(conn, Buffer);
	return;
}


void ProcessSyncXML(CIRCUIT * conn, char * XML)
{
	// Process XML from RMS Relay Sync

	// All seem to start 

	//<?xml version="1.0"?>
	//<sync_record>
	// <po_sync>
	//  <transaction_type>

	char * Type = strstr(XML, "<transaction_type>");

	if (Type == NULL)
		return;

	Type += strlen("<transaction_type>");

	if (memcmp(Type, "rms_location", 12) == 0)
	{
		return;
	}


	if (memcmp(Type, "request_sync", 12) == 0)
	{
		char * Call;
		struct UserInfo * BBSREC;

		// This isn't requesting a poll, it is asking to be added as a sync partner

		Call = strstr(Type, "<callsign>");

		if (Call == NULL)
			return;
	
		Call += 10;
		strlop(Call, '<');
		BBSREC = FindBBS(Call);

		if (BBSREC == NULL)
			return;

		if (BBSREC->ForwardingInfo->Forwarding == 0)
			StartForwarding(BBSREC->BBSNumber, NULL);

		return;
	}

	if (memcmp(Type, "remove_message", 14) == 0)
	{
		char * MID = strstr(Type, "<MessageId>");
		struct MsgInfo * Msg;

		if (MID == NULL)
			return;
	
		MID += 11;
		strlop(MID, '<');

		strlop(MID, '@');			// sometimes has @CALL@R
		if (strlen(MID) > 12)
			MID[12] = 0;

		Msg = FindMessageByBID(MID);

		if (Msg == NULL)
			return;

		Logprintf(LOG_BBS, conn, '|', "Killing Msg %d %s", Msg->number, Msg->bid);

		FlagAsKilled(Msg, TRUE);
		return;
	}

	if (memcmp(Type, "delivered", 9) == 0)
	{
		char * MID = strstr(Type, "<MessageId>");
		struct MsgInfo * Msg;

		if (MID == NULL)
			return;
	
		MID += 11;
		strlop(MID, '<');

		strlop(MID, '@');			// sometimes has @CALL@R
		if (strlen(MID) > 12)
			MID[12] = 0;

		Msg = FindMessageByBID(MID);

		if (Msg == NULL)
			return;

		Logprintf(LOG_BBS, conn, '|', "Message Msg %d %s Delivered", Msg->number, Msg->bid);
		return;
	}

	Debugprintf(Type);
	return;

/*
<?xml version="1.0"?>
<sync_record>
  <po_sync>
    <transaction_type>request_sync</transaction_type>
    <timestamp>20230205100652</timestamp>
    <originating_station>GI8BPQ</originating_station>
  </po_sync>
  <request_sync>
    <callsign>GI8BPQ</callsign>
    <password></password>
    <ip_address>127.0.0.1</ip_address>
    <ip_port>8780</ip_port>
    <note></note>
  </request_sync>
</sync_record>
}

<sync_record>
  <po_sync>
    <transaction_type>delivered</transaction_type>
    <timestamp>20230205093113</timestamp>
    <originating_station>G8BPQ</originating_station>
  </po_sync>
  <delivered>
    <MessageId>10845_GM8BPB</MessageId>
    <Destination>G8BPQ</Destination>
    <ForwardedTo>G8BPQ</ForwardedTo>
    <DeliveredVia>3</DeliveredVia>
  </delivered>
</sync_record>

	Public Enum MessageDeliveryMethod
    '
    ' Method used to deliver a message.  None if the message hasn't been delivered.
    '
    Unspecified = -1
    None = 0
    Telnet = 1
    CMS = 2
    Radio = 3
    Email = 4
End Enum
*/
}

int ReformatSyncMessage(CIRCUIT * conn)
{
	// Message has been decompressed - reformat to look like a WLE message

	char * MsgBit;
	char *ptr1, *ptr2;
	int linelen;
	char FullFrom[80];
	char FullTo[80];
	char BID[80];
	time_t Date;
	char Mon[80];
	char Subject[80];
	int i = 0;
	char * Boundary;
	char * Input;
	char * via = NULL;
	char * NewMsg = conn->MailBuffer;
	char * SaveMsg = NewMsg;
	char DateString[80];
	struct tm * tm;
	char Type[16] = "Private";
	char * part[100] = {""};
	char * partname[100];
	int partLen[100];
	char xml[4096];

	// Message has an XML header then the message

	// The XML may have control info, so examine it.
 
	/*
	Date: Mon, 25 Oct 2021 10:22:00 -0000
	From: GM8BPQ
	Subject: Test
	To: 2E1BGT
	Message-ID: ALYJQJRXVQAO
	X-Source: GM8BPQ
	X-Relay: G8BPQ
	MIME-Version: 1.0
	MIME-Version: 1.0
	Content-Type: multipart/mixed; boundary="boundaryBSoxlw=="

	--boundaryBSoxlw==
	Content-Type: text/plain; charset="iso-8859-1"
	Content-Transfer-Encoding: quoted-printable

	Hello Hello

	--boundaryBSoxlw==--
	*/

	// I think the best way is to reformat as if from Winlink Express, then pass 
	//through the normal B2 code.

//	WriteLogLine(conn, '<', conn->MailBuffer, conn->TempMsg->length, LOG_BBS);

	// display the message  for testing

	conn->MailBuffer[conn->TempMsg->length] = 0;

//	OutputDebugString(conn->MailBuffer);
	memcpy(xml, conn->MailBuffer, conn->SyncXMLLen);
	xml[conn->SyncXMLLen] = 0;

	if (conn->SyncMsgLen == 0)
	{
		// No message, Just xml. Looks like a status report

		ProcessSyncXML(conn, xml);
		return 0;
	}
	
	MsgBit = &conn->MailBuffer[conn->SyncXMLLen];
	conn->TempMsg->length -= conn->SyncXMLLen;

	ptr1 = MsgBit;

Loop:

	ptr2 = strchr(ptr1, '\r');

	linelen = (int)(ptr2 - ptr1);

	if (_memicmp(ptr1, "From:", 5) == 0)
	{
		memcpy(FullFrom, &ptr1[6], linelen - 6);
		FullFrom[linelen - 6] = 0;
	}

	if (_memicmp(ptr1, "To:", 3) == 0)
	{
		memcpy(FullTo, &ptr1[4], linelen - 4);
		FullTo[linelen - 4] = 0;
	}

	else if (_memicmp(ptr1, "Subject:", 8) == 0)
	{
		memcpy(Subject, &ptr1[9], linelen - 9);
		Subject[linelen - 9] = 0;
	}

	else if (_memicmp(ptr1, "Message-ID", 10) == 0)
	{
		memcpy(BID, &ptr1[12], linelen - 12);
		BID[linelen - 12] = 0;
	}

	else if (_memicmp(ptr1, "Date:", 5) == 0)
	{
		struct tm rtime;
		char seps[] = " ,\t\r";

		memset(&rtime, 0, sizeof(struct tm));

		// Date: Mon, 25 Oct 2021 10:22:00 -0000

		sscanf(&ptr1[11], "%02d %s %04d %02d:%02d:%02d",
			&rtime.tm_mday, &Mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec);

		rtime.tm_year -= 1900;

		for (i = 0; i < 12; i++)
		{
			if (strcmp(Mon, month[i]) == 0)
				break;
		}

		rtime.tm_mon = i;

		Date = mktime(&rtime) - (time_t)_MYTIMEZONE;

		if (Date == (time_t)-1)
			Date = time(NULL);

	}

	if (linelen)			// Not Null line
	{
		ptr1 = ptr2 + 2;		// Skip crlf
		goto Loop;
	}

	// Unpack Body - seems to be multipart even if only one

	// Can't we just send the whole body through ??
	// No, Attachment format is different

	// Mbo: GM8BPQ
	// Body: 17
	// File: 1471 leadercoeffs.txt

	Input = MsgBit;
	Boundary = initMultipartUnpack(&Input);
	
	i = 0;

	if (Boundary)
	{
		// input should be start of part

		// Find End of part - ie -- Boundary + CRLF or --

		char * ptr, * saveptr;
		char * Msgptr;
		size_t BLen = strlen(Boundary);
		size_t Partlen;

		saveptr = Msgptr = ptr = Input;

		while(ptr)				// Just in case we run off end
		{
			if (*ptr == '-' && *(ptr+1) == '-')
			{
				if (memcmp(&ptr[2], Boundary, BLen) == 0)
				{
					// Found Boundary

					char * p1, *p2, *ptr3, *ptr4;
					int llen;
					int Base64 = 0;
					int QuotedP = 0;
					char * BoundaryStart = ptr;

					Partlen = ptr - Msgptr;

					ptr += (BLen + 2);			// End of Boundary

					if (*ptr == '-')			// Terminating Boundary
						Input = NULL;
					else
						Input = ptr + 2;

					// Will check for quoted printable

					p1 = Msgptr;
Loop2:
					p2 = strchr(p1, '\r');
					llen = (int)(p2 - p1);

					if (llen)
					{

						if (_memicmp(p1, "Content-Transfer-Encoding:", 26) == 0)
						{
							if (_memicmp(&p1[27], "base64", 6) == 0)
								Base64 = TRUE;
							else if (_memicmp(&p1[27], "quoted", 6) == 0)
								QuotedP = TRUE;
						}
						else if (_memicmp(p1, "Content-Disposition: ", 21) == 0)
						{
							ptr3 = strstr(&p1[21], "name");

							if (ptr3)
							{
								ptr3 += 5;
								if (*ptr3 == '"') ptr3++;
								ptr4 = strchr(ptr3, '"');
								if (ptr4) *ptr4 = 0;

								partname[i] = ptr3;
							}
						}

						if (llen)			// Not Null line
						{
							p1 = p2 + 2;		// Skip crlf
							goto Loop2;
						}
					}

					part[i] = strstr(p2, "\r\n");	// Over separator
			
					if (part[i])
					{
						part[i] += 2;
						partLen[i] = BoundaryStart - part[i] - 2;
						if (QuotedP)
							partLen[i] = decode_quoted_printable(part[i], partLen[i]);
						else if (Base64)
						{
							int Len = partLen[i], NewLen;
							char * ptr = part[i];
							char * ptr2 = part[i];

							// WLE sends base64 with embedded crlf, so remove them

							while (Len-- > 0)
							{
								if ((*ptr) != 10 && (*ptr) != 13)
									*(ptr2++) = *(ptr++);
								else
									ptr ++;
							}

							Len = ptr2 - part[i];
							ptr = part[i];
							ptr2 = part[i];
							
							while (Len > 0)
							{
								decodeblock(ptr, ptr2);
								ptr += 4;
								ptr2 += 3;
								Len -= 4;
							}

							NewLen = (int)(ptr2 - part[i]);

							if (*(ptr-1) == '=')
								NewLen--;

							if (*(ptr-2) == '=')
								NewLen--;

							partLen[i] = NewLen;
						}
					}
					Msgptr = ptr = Input;
					i++;
					continue;				}

				// See if more parts
			}
			ptr++;
		}
		ptr++;
	}


	// Build the message

	tm = gmtime(&Date);	

	sprintf(DateString, "%04d/%02d/%02d %02d:%02d",
		tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);

	NewMsg += sprintf(NewMsg,
		"MID: %s\r\n"
		"Date: %s\r\n"
		"Type: %s\r\n"
		"From: %s\r\n",
			BID, DateString, Type, FullFrom);

//	if (ToCalls)
//	{
//		int i;

//		for (i = 0; i < Calls; i++)
//			NewMsg += sprintf(NewMsg, "To: %s\r\n",	ToCalls[i]);

//	}
//	else
	{
		NewMsg += sprintf(NewMsg, "To: %s\r\n",
			FullTo);
	}
//	if (WebMail->CC && WebMail->CC[0])
//		NewMsg += sprintf(NewMsg, "CC: %s\r\n", WebMail->CC);

	NewMsg += sprintf(NewMsg,
		"Subject: %s\r\n"
		"Mbo: %s\r\n",
		Subject, BBSName);

	// Write the Body: line and any File Lines

	NewMsg += sprintf(NewMsg, "Body: %d\r\n", partLen[0]);

	i = 1;

	while (part[i])
	{
		NewMsg += sprintf(NewMsg, "File: %d %s\r\n",
			partLen[i], partname[i]);

		i++;
	}

	NewMsg += sprintf(NewMsg, "\r\n");		// Blank Line to end header

	// Now add parts

	i = 0;

	while (part[i])
	{
		memmove(NewMsg, part[i], partLen[i]);
		NewMsg += partLen[i];
		i++;
		NewMsg += sprintf(NewMsg, "\r\n");		// Blank Line between attachments
	}

	conn->TempMsg->length = NewMsg - SaveMsg;
	conn->TempMsg->datereceived = conn->TempMsg->datechanged = time(NULL);
	conn->TempMsg->datecreated = Date;
	strcpy(conn->TempMsg->bid, BID);

	if (strlen(Subject) > 60)
		Subject[60] = 0;

	strcpy(conn->TempMsg->title, Subject);

	return TRUE;
}

char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg)
{
	// First an XML Header

	char * Buffer = malloc(4096 + Msg->length);
	int Len = 0;
	
	struct tm *tm;
	char Date[32];
	char MsgTime[32];
	char Separator[33]="";
	time_t Time = time(NULL);
	char * MailBuffer;
	int BodyLen;
	char * Encoded;

	// Get the message - may need length in header

	MailBuffer = ReadMessageFile(Msg->number);

	BodyLen = Msg->length;

	// Remove any B2 Header

	if (Msg->B2Flags & B2Msg)
	{
		// Remove B2 Headers (up to the File: Line)
			
		char * ptr;
		ptr = strstr(MailBuffer, "Body:");
		if (ptr)
		{
			BodyLen = atoi(ptr + 5);
			ptr = strstr(ptr, "\r\n\r\n");
		}
		if (ptr)
		{
			memcpy(MailBuffer, ptr + 4, BodyLen);
			MailBuffer[BodyLen] = 0;
		}
	}

	// encode body as quoted printable;

	Encoded = malloc(Msg->length * 3);

	BodyLen = encode_quoted_printable(MailBuffer, Encoded, BodyLen);

	// Create multipart Boundary
	
	CreateOneTimePassword(&Separator[0], "Key", 0); 
	CreateOneTimePassword(&Separator[16], "Key", 1); 


	tm = gmtime(&Time);

	sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);

	tm = gmtime((time_t *)&Msg->datecreated);

	sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min);

	Len += sprintf(&Buffer[Len], "<?xml version=\"1.0\"?>\r\n");

	Len += sprintf(&Buffer[Len], "<sync_record>\r\n");
	Len += sprintf(&Buffer[Len], "  <po_sync>\r\n");
    Len += sprintf(&Buffer[Len], "    <transaction_type>add_message</transaction_type>\r\n");
    Len += sprintf(&Buffer[Len], "    <timestamp>%s</timestamp>\r\n", Date);
    Len += sprintf(&Buffer[Len], "    <originating_station>%s</originating_station>\r\n", Msg->from);
	Len += sprintf(&Buffer[Len], "  </po_sync>\r\n");
 	Len += sprintf(&Buffer[Len], " <add_message>\r\n");
 	Len += sprintf(&Buffer[Len], "   <register_entry>\r\n");
	Len += sprintf(&Buffer[Len], "     <MessageId>%s</MessageId>\r\n", Msg->bid);
 	Len += sprintf(&Buffer[Len], "     <Time>%s</Time>\r\n", MsgTime);
 	Len += sprintf(&Buffer[Len], "     <Sender>%s</Sender>\r\n", Msg->from);
  	Len += sprintf(&Buffer[Len], "     <Source>%s</Source>\r\n", Msg->from);
	Len += sprintf(&Buffer[Len], "     <Precedence>2</Precedence>\r\n");
	Len += sprintf(&Buffer[Len], "     <Attachment>%s</Attachment>\r\n", (Msg->B2Flags & Attachments) ? "true" : "false");
	Len += sprintf(&Buffer[Len], "     <CSize>%d</CSize>\r\n", BodyLen);
	Len += sprintf(&Buffer[Len], "     <Subject>%s</Subject>\r\n", Msg->title);
	Len += sprintf(&Buffer[Len], "     <RMSOriginator></RMSOriginator>\r\n");
	Len += sprintf(&Buffer[Len], "     <RMSDestination></RMSDestination>\r\n");
	Len += sprintf(&Buffer[Len], "   </register_entry>\r\n");
	Len += sprintf(&Buffer[Len], "   <destinations>\r\n");
 	Len += sprintf(&Buffer[Len], "    <destination>\r\n");
 	Len += sprintf(&Buffer[Len], "      <MessageId>%s</MessageId>\r\n", Msg->bid);
	Len += sprintf(&Buffer[Len], "      <Priority>450443</Priority>\r\n");
	Len += sprintf(&Buffer[Len], "      <Destination>%s</Destination>\r\n", Msg->to);
 	Len += sprintf(&Buffer[Len], "      <ForwardedTo></ForwardedTo>\r\n");
 	Len += sprintf(&Buffer[Len], "      <DeliveredVia>0</DeliveredVia>\r\n");
	Len += sprintf(&Buffer[Len], "      <SentToCMS>False</SentToCMS>\r\n");
	Len += sprintf(&Buffer[Len], "      <SentViaRadio>False</SentViaRadio>\r\n");
	Len += sprintf(&Buffer[Len], "      <SentViaTelnet>False</SentViaTelnet>\r\n");
	Len += sprintf(&Buffer[Len], "      <LocalOnly>False</LocalOnly>\r\n");
	Len += sprintf(&Buffer[Len], "      </destination>\r\n");
	Len += sprintf(&Buffer[Len], "    </destinations>\r\n");
 	Len += sprintf(&Buffer[Len], "   <misc>\r\n");
	Len += sprintf(&Buffer[Len], "      <Local>True</Local>\r\n");
	Len += sprintf(&Buffer[Len], "      <LocalOnly>False</LocalOnly>\r\n");
	Len += sprintf(&Buffer[Len], "    </misc>\r\n");
	Len += sprintf(&Buffer[Len], "  </add_message>\r\n");
	Len += sprintf(&Buffer[Len], "</sync_record>\r\n");

//	Debugprintf(Buffer);

	conn->SyncXMLLen = Len;

	Len += sprintf(&Buffer[Len], "Date: Sat, 04 Feb 2023 11:19:00 +0000\r\n");
	Len += sprintf(&Buffer[Len], "From: %s\r\n", Msg->from);
	Len += sprintf(&Buffer[Len], "Subject: %s\r\n", Msg->title);
	Len += sprintf(&Buffer[Len], "To: %s\r\n", Msg->to);
	Len += sprintf(&Buffer[Len], "Message-ID: %s\r\n", Msg->bid);
//	Len += sprintf(&Buffer[Len], "X-Source: G8BPQ\r\n");
//	Len += sprintf(&Buffer[Len], "X-Location: 52.979167N, 1.125000W (GRID SQUARE)\r\n");
//	Len += sprintf(&Buffer[Len], "X-RMS-Originator: G8BPQ\r\n");
//	Len += sprintf(&Buffer[Len], "X-RMS-Path: G8BPQ@2023-02-04-11:19:29\r\n");
	Len += sprintf(&Buffer[Len], "X-Relay: %s\r\n", BBSName);

	Len += sprintf(&Buffer[Len], "MIME-Version: 1.0\r\n");
	Len += sprintf(&Buffer[Len], "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", Separator);

	Len += sprintf(&Buffer[Len], "\r\n");		// Blank line before separator
	Len += sprintf(&Buffer[Len], "--%s\r\n", Separator);
	Len += sprintf(&Buffer[Len], "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n");
	Len += sprintf(&Buffer[Len], "Content-Transfer-Encoding: quoted-printable\r\n");
	Len += sprintf(&Buffer[Len], "\r\n");		// Blank line before body

	Len += sprintf(&Buffer[Len], "%s\r\n", Encoded);	
	Len += sprintf(&Buffer[Len], "--%s--\r\n", Separator);

	conn->SyncMsgLen = Len - conn->SyncXMLLen;

	free(Encoded);
	free(MailBuffer);

	return Buffer;
}

int encode_quoted_printable(char *s, char * out, int Len)
{
  int n = 0;
  char * start = out;

  while(Len--)
  {
    if (n >= 73 && *s != 10 && *s != 13)
		{strcpy(out, "=\r\n"); n = 0; out +=3;}
    if (*s == 10 || *s == 13) {putchar(*s); n = 0;}
    else if (*s<32 || *s==61 || *s>126)
		out += sprintf(out, "=%02x", (unsigned char)*s);
    else if (*s != 32 || (*(s+1) != 10 && *(s+1) != 13))
		{*(out++) = *s; n++;}
    else n += printf("=20");

	s++;
  }
  *out = 0;

  return out - start;
}

int decode_quoted_printable(char *ptr, int len)
{
	// overwrite input with decoded version

	char * ptr2 = ptr;
	char * End = ptr + len;
	char * Start = ptr;

	while (ptr < End)
	{
		if ((*ptr) == '=')
		{
			char c = *(++ptr);
			char d;

			c = c - 48;
			if (c < 0)
			{
				// = CRLF as a soft break

				ptr += 2;
				continue;
			}
			if (c > 9) c -= 7;
			d  = *(++ptr);
			d = d - 48;
			if (d > 9) d -= 7;

			*(ptr2) = c << 4 | d;
			ptr2++;	
			ptr++;
		}
		else
			*ptr2++ = *ptr++;
	}
	return ptr2 - Start;
}


VOID GetPGConfig()
{
	char FN[256];
	FILE *file;
	char buf[256],errbuf[256];
	char * p_prog, * p_name, * p_desc;
	int n = 0;
	int i = 0;


	strcpy(FN, BaseDir);
	strcat(FN, "/");
	strcat(FN, "PG/PGList.txt");

	if ((file = fopen(FN, "r")) == NULL)
	{
		return;
	}

	while(fgets(buf, 255, file) != NULL)
	{
		if ( buf[0] == '#')
			continue;

		strcpy(errbuf,buf);			// save in case of error

		p_prog = strtok(buf, ",\n\r");
		p_name = strtok(NULL, ",\n\r");
		p_desc = strtok(NULL, ",\n\r");


		if (p_desc && p_desc[0])
		{
			while(*(p_name) == ' ')		// Remove leading spaces
				p_name++;
			while(*(p_desc) == ' ')
				p_desc++;
			SERVERLIST[n][0] = _strdup(p_prog);
			SERVERLIST[n][1] = _strdup(p_name);
			SERVERLIST[n++][2] = _strdup(p_desc);
		}
		if (n > 255)
			break;
	}


	NUM_SERVERS = n;
	fclose(file);
	
	/*------- G7TAJ PG SERVER ----------*/
	Debugprintf("Number of PG Servers = %d", NUM_SERVERS );
	for (i=0; i< NUM_SERVERS; i++ )
	{
		Debugprintf("Server #%d,%s,%s,%s", i, SERVERLIST[i][0], SERVERLIST[i][1], SERVERLIST[i][2]);
	}
	/*------- G7TAJ END ----------*/

}

void SendMessageReadEvent(char * call, struct MsgInfo * Msg)
{
	if (reportMailEvents)
	{
		char msg[512];

		//12345 B 2053 TEST@ALL F6FBB 920325 This is the subject

		struct tm *tm = gmtime((time_t *)&Msg->datecreated);	

		sprintf_s(msg, sizeof(msg),"%-6d %c %c %6d %-13s %-6s %02d%02d%02d %s\r",
			Msg->number, Msg->type, Msg->status, Msg->length, Msg->to,
			Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title);

//		sprintf(msg, "%s Read %d\r", user->Call, Msg->number);

#ifdef WIN32
		if (pRunEventProgram)
			pRunEventProgram("MailMsgRead.exe", msg);
#else
		{
			char prog[256];
			sprintf(prog, "%s/%s", BPQDirectory, "MailMsgRead");
			RunEventProgram(prog, msg);
		}
#endif
	}
}

void SendMessageForwardedToM0LTE(char * call, struct MsgInfo * Msg)
{
}


void SendNewMessageEvent(char * call, struct MsgInfo * Msg)
{
	if (reportMailEvents)
	{
		char msg[512];

		//12345 B 2053 TEST@ALL F6FBB 920325 This is the subject

		struct tm *tm = gmtime((time_t *)&Msg->datecreated);	

		sprintf_s(msg, sizeof(msg),"%-6d %c %c %6d %-13s %-6s %02d%02d%02d %s\r",
			Msg->number, Msg->type, Msg->status, Msg->length, Msg->to,
			Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title);

#ifdef WIN32
		if (pRunEventProgram)
			pRunEventProgram("MailNewMsg.exe", msg);
#else
		{
			char prog[256];
			sprintf(prog, "%s/%s", BPQDirectory, "MailNewMsg");
			RunEventProgram(prog, msg);
		}
#endif
	}
}



