/*
Copyright 2001-2022 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
*/	

//
//	DLL to provide interface to allow G8BPQ switch to use WINMOR as a Port Driver 
//
//	Uses BPQ EXTERNAL interface
//


//  Version 1.0 January 2009 - Initial Version
//

// March 22 2010

// Send FAULTS to Monitor Window
// Force PROTOCOL = WINMOR/PACTOR (to simplifiy Config)

// July 2010
// Support up to 32 BPQ Ports
// Support up to 32 Applications

// Version 1.2.1.2 August 2010 

// Save Minimized State
// Handle new "BLOCKED by Busy channel" message from TNC

// Version 1.2.1.4 August 2010 

// Add Scan control of BW setting
// Reset TNC if stuck in Disconnecting
// Add option to send reports to WL2K
// Disconnect if appl not available

// Version 1.2.1.5 August 2010 

// Updates to WL2K Reporting
// Send Watchdog polls every minute and restart if no response.
// Don't connect if channel is busy (unless specifically overridden)

// Version 1.2.1.6 September 2010

// Add option to kill and restart TNC after each transfer
// Fix PTT operation after Node reconfig

// Version 1.2.2.1 September 2010

// Add option to get config from bpq32.cfg
// Merge with BPQ32.dll


#define _CRT_SECURE_NO_DEPRECATE

#include <stdio.h>
#include <time.h>

#include "cheaders.h"

#ifdef WIN32
#include <Psapi.h>
#endif

extern int (WINAPI FAR *GetModuleFileNameExPtr)();
extern int (WINAPI FAR *EnumProcessesPtr)();


#define SD_RECEIVE      0x00
#define SD_SEND         0x01
#define SD_BOTH         0x02

#include "bpq32.h"

#include "tncinfo.h"


#define WSA_ACCEPT WM_USER + 1
#define WSA_DATA WM_USER + 2
#define WSA_CONNECT WM_USER + 3

static int Socket_Data(int sock, int error, int eventcode);

int KillTNC(struct TNCINFO * TNC);
int RestartTNC(struct TNCINFO * TNC);
int KillPopups(struct TNCINFO * TNC);
VOID MoveWindows(struct TNCINFO * TNC);
int SendReporttoWL2K(struct TNCINFO * TNC);
char * CheckAppl(struct TNCINFO * TNC, char * Appl);
int DoScanLine(struct TNCINFO * TNC, char * Buff, int Len);
BOOL KillOldTNC(char * Path);
int standardParams(struct TNCINFO * TNC, char * buf);

static char ClassName[]="WINMORSTATUS";
static char WindowTitle[] = "WINMOR";
static int RigControlRow = 165;

#define WINMOR
#define NARROWMODE 21
#define WIDEMODE 22

#ifndef LINBPQ
#include <commctrl.h>
#endif

extern int SemHeldByAPI;

static RECT Rect;

static int ProcessLine(char * buf, int Port);

// RIGCONTROL COM60 19200 ICOM IC706 5e 4 14.103/U1w 14.112/u1 18.1/U1n 10.12/l1

// There seem to be timing issues when calling SendMessage from multiple threads.
// Queue and process in main thread

UINT * WINMORTraceQ;
UINT * SetWindowTextQ;

VOID WritetoTraceSupport(struct TNCINFO * TNC, char * Msg, int Len)
{
	int index = 0;
	UCHAR * ptr1 = Msg, * ptr2;
	UCHAR Line[1000];
	int LineLen, i;
	UCHAR Save;
	int SaveLen = Len;
	char Time[16];
	time_t T;
	struct tm * tm;

	if (Len < 0)
		return;

	Save = Msg[Len];
	Msg[Len] = 0;

#ifndef LINBPQ
	index=SendMessage(TNC->hMonitor, LB_SETCURSEL, -1, 0);
#endif

lineloop:

	if (Len > 0)
	{
		//	copy text to control a line at a time	

		ptr2 = memchr(ptr1, 13, Len);

		if (ptr2)
		{
			ptr2++;
			LineLen = (int)(ptr2 - ptr1);
			Len -= LineLen;
			memcpy(Line, ptr1, LineLen);
			memcpy(&Line[LineLen - 1], "<cr>", 4);
			LineLen += 3;

			if ((*ptr2) == 10)
			{
				memcpy(&Line[LineLen], "<lf>", 4);
				LineLen += 4;
				ptr2++;
				Len --;
			}

			Line[LineLen] = 0;

			// If line contains any data above 7f, assume binary and dont display

			for (i = 0; i < LineLen; i++)
			{
				if (Line[i] > 126 || Line[i] < 32)
					goto Skip;
			}

			// We now also pass to Monitor Window

			if (strlen(Line) < 250)
			{
				MESSAGE Monframe;
				memset(&Monframe, 0, sizeof(Monframe));

				Monframe.PORT = TNC->Port;	
				Monframe.LENGTH = 12 + strlen(Line);
				Monframe.DEST[0] = 1;			// Plain Text Monitor
				strcpy(&Monframe.DEST[1], Line);

				time(&Monframe.Timestamp);
				BPQTRACE((MESSAGE *)&Monframe, FALSE);
			}

#ifdef LINBPQ
#else
			index=SendMessage(TNC->hMonitor, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) Line);
#endif
			// Write to Web Buffer

			T = time(NULL);
			tm = gmtime(&T);
	
			sprintf_s(Time, sizeof(Time),"%02d:%02d ", tm->tm_hour, tm->tm_min);

			strcat(TNC->WebBuffer, Time);
			strcat(TNC->WebBuffer, Line);
			strcat(TNC->WebBuffer, "\r\n");
			if (strlen(TNC->WebBuffer) > 4500)
				memmove(TNC->WebBuffer, &TNC->WebBuffer[500], strlen(&TNC->WebBuffer[500]) + 1);	// Make sure null is moved
		Skip:
			ptr1 = ptr2;

			goto lineloop;

		}

		// Process incomplete line

		for (i = 0; i < Len; i++)
		{
			if (ptr1[i] > 126 || ptr1[i] < 32)
				break;
		}

		if (i == Len)
		{
			if (Len < 250)
			{
				MESSAGE Monframe;
				memset(&Monframe, 0, sizeof(Monframe));

				Monframe.PORT = TNC->Port;	
				Monframe.LENGTH = 12 + Len;
				Monframe.DEST[0] = 1;			// Plain Text Monitor

				memcpy(&Monframe.DEST[1], ptr1, Len);
				Monframe.DEST[1 + Len] = 0;

				time(&Monframe.Timestamp);
				BPQTRACE((MESSAGE *)&Monframe, FALSE);
			}


#ifdef LINBPQ
#else
			index=SendMessage(TNC->hMonitor, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) ptr1 );
#endif
			T = time(NULL);
			tm = gmtime(&T);
	
			sprintf_s(Time, sizeof(Time),"%02d:%02d ", tm->tm_hour, tm->tm_min);
			strcat(TNC->WebBuffer, Time);

			strcat(TNC->WebBuffer, ptr1);
			strcat(TNC->WebBuffer, "\r\n");
			if (strlen(TNC->WebBuffer) > 4500)
				memmove(TNC->WebBuffer, &TNC->WebBuffer[500], strlen(&TNC->WebBuffer[500]) + 1);	// Make sure null is moved
		}
	}

#ifdef LINBPQ
#else

	if (index > 1200)					
	do
		index=index=SendMessage(TNC->hMonitor, LB_DELETESTRING, 0, 0);
	while (index > 1000);

	if (index > -1)
		index=SendMessage(TNC->hMonitor, LB_SETCARETINDEX,(WPARAM) index, MAKELPARAM(FALSE, 0));
#endif
	Msg[SaveLen] = Save;

}
				
VOID MySetWindowTextWithSem(HWND hWnd, char * Msg)
{
#ifndef LINBPQ

	PMSGWITHLEN buffptr;

	buffptr = GetBuff();

	if (buffptr)
	{
		buffptr->Len= (UINT)hWnd;
		memcpy(&buffptr->Data[0], Msg, strlen(Msg) + 1);
	
		C_Q_ADD(&SetWindowTextQ, buffptr);
	}

#endif
}

int C_Q_ADD_NP(VOID *PQ, VOID *PBUFF);

struct SEM SetWindTextSem = {0, 0, 0, 0};

VOID MySetWindowText(HWND hWnd, char * Msg)
{
#ifndef LINBPQ

	PMSGWITHLEN buffptr;

	GetSemaphore(&SetWindTextSem, 61);
	buffptr = zalloc(400);

	if (buffptr)
	{
		buffptr->Len= (UINT)hWnd;
		memcpy(&buffptr->Data[0], Msg, strlen(Msg) + 1);
	
		C_Q_ADD_NP(&SetWindowTextQ, buffptr);
	}

	FreeSemaphore(&SetWindTextSem);
#endif
}

VOID SetWindowTextSupport()
{
	PMSGWITHLEN Buffer;

	while (SetWindowTextQ)
	{
		GetSemaphore(&SetWindTextSem, 61);
		Buffer = Q_REM_NP(&SetWindowTextQ);
		SetWindowText((HWND)Buffer->Len, Buffer->Data);
		FreeSemaphore(&SetWindTextSem);
		free(Buffer);
	}
}


VOID WritetoTrace(struct TNCINFO * TNC, char * Msg, int Len)
{
	//	It seems writing from multiple threads can cause problems in Windows
	//	Queue and process in main thread

#ifdef LINBPQ
	WritetoTraceSupport(TNC, Msg, Len);
}
#else
	UINT * buffptr;
	BOOL Sem = FALSE;

	if (Len < 0)
		return;

	// Get semaphore if it isn't set

	if (InterlockedExchange(&Semaphore.Flag, 1) == 0) 
	{
		Sem = TRUE;
		Semaphore.Gets++;
	}

	buffptr = GetBuff();

	if (buffptr)
	{
		if (Len > 340)
			Len = 340;

		buffptr[1] = (UINT)TNC;
		buffptr[2] = (UINT)Len;
		memcpy(&buffptr[3], Msg, Len + 1);	

		C_Q_ADD(&WINMORTraceQ, buffptr);
	}

	if (Sem)
		FreeSemaphore(&Semaphore);

}
#endif

static int ProcessLine(char * buf, int Port)
{
	UCHAR * ptr,* p_cmd;
	char * p_ipad = 0;
	char * p_port = 0;
	unsigned short WINMORport = 0;
	int BPQport;
	int len=510;
	struct TNCINFO * TNC;
	char errbuf[256];

	strcpy(errbuf, buf);

	ptr = strtok(buf, " \t\n\r");

	if(ptr == NULL) return (TRUE);

	if(*ptr =='#') return (TRUE);			// comment

	if(*ptr ==';') return (TRUE);			// comment

	if (_stricmp(buf, "ADDR"))
		return FALSE;						// Must start with ADDR

	ptr = strtok(NULL, " \t\n\r");

	BPQport = Port;
	p_ipad = ptr;

	TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO));
	memset(TNC, 0, sizeof(struct TNCINFO));

		TNC->InitScript = malloc(1000);
		TNC->InitScript[0] = 0;
	
		if (p_ipad == NULL)
			p_ipad = strtok(NULL, " \t\n\r");

		if (p_ipad == NULL) return (FALSE);
	
		p_port = strtok(NULL, " \t\n\r");
			
		if (p_port == NULL) return (FALSE);

		WINMORport = atoi(p_port);

		TNC->destaddr.sin_family = AF_INET;
		TNC->destaddr.sin_port = htons(WINMORport);
		TNC->Datadestaddr.sin_family = AF_INET;
		TNC->Datadestaddr.sin_port = htons(WINMORport+1);

		TNC->HostName = malloc(strlen(p_ipad)+1);

		if (TNC->HostName == NULL) return TRUE;

		strcpy(TNC->HostName,p_ipad);

		ptr = strtok(NULL, " \t\n\r");

		if (ptr)
		{
			if (_stricmp(ptr, "PTT") == 0)
			{
				ptr = strtok(NULL, " \t\n\r");

				if (ptr)
				{
					DecodePTTString(TNC, ptr);
					ptr = strtok(NULL, " \t\n\r");
				}
			}
		}
		
		if (ptr)
		{
			if (_memicmp(ptr, "PATH", 4) == 0)
			{
				p_cmd = strtok(NULL, "\n\r");
				if (p_cmd) TNC->ProgramPath = _strdup(p_cmd);
			}
		}

		// Read Initialisation lines

		while(TRUE)
		{
			if (GetLine(buf) == 0)
				return TRUE;

			strcpy(errbuf, buf);

			if (memcmp(buf, "****", 4) == 0)
				return TRUE;

			ptr = strchr(buf, ';');
			if (ptr)
			{
				*ptr++ = 13;
				*ptr = 0;
			}
				
			if ((_memicmp(buf, "CAPTURE", 7) == 0) || (_memicmp(buf, "PLAYBACK", 8) == 0))
			{}		// Ignore
			else
/*
			if (_memicmp(buf, "PATH", 4) == 0)
			{
				char * Context;
				p_cmd = strtok_s(&buf[5], "\n\r", &Context);
				if (p_cmd) TNC->ProgramPath = _strdup(p_cmd);
			}
			else
*/
			if (_memicmp(buf, "STARTINROBUST", 13) == 0)
				TNC->StartInRobust = TRUE;
			
			else
			if (_memicmp(buf, "ROBUST", 6) == 0)
			{
				if (_memicmp(&buf[7], "TRUE", 4) == 0)
					TNC->Robust = TRUE;
				
				strcat (TNC->InitScript, buf);
			}
			else if (standardParams(TNC, buf) == FALSE)
				strcat (TNC->InitScript, buf);
		}


	return (TRUE);	
}



void WINMORThread(void * portptr);
VOID ProcessDataSocketData(int port);
int ConnecttoWINMOR(int port);
static int ProcessReceivedData(struct TNCINFO * TNC);
int V4ProcessReceivedData(struct TNCINFO * TNC);
VOID ReleaseTNC(struct TNCINFO * TNC);
VOID SuspendOtherPorts(struct TNCINFO * ThisTNC);
VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC);
VOID WritetoTrace(struct TNCINFO * TNC, char * Msg, int Len);

static time_t ltime;


static SOCKADDR_IN sinx; 
static SOCKADDR_IN rxaddr;

static int addrlen=sizeof(sinx);



VOID ChangeMYC(struct TNCINFO * TNC, char * Call)
{
	UCHAR TXMsg[100];
	int datalen;

	if (strcmp(Call, TNC->CurrentMYC) == 0)
		return;								// No Change

	strcpy(TNC->CurrentMYC, Call);

//	send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);

	datalen = sprintf(TXMsg, "MYC %s\r\n", Call);
	send(TNC->TCPSock,TXMsg, datalen, 0);

//	send(TNC->TCPSock, "CODEC TRUE\r\n", 12, 0);
//	TNC->StartSent = TRUE;

	send(TNC->TCPSock, "MYC\r\n", 5, 0);
}

static size_t ExtProc(int fn, int port, PDATAMESSAGE buff)
{
	int i,winerr;
	size_t datalen;
	PMSGWITHLEN buffptr;
	char txbuff[500];
	unsigned int bytes;
	size_t txlen = 0;
	char ErrMsg[255];
	size_t Param;
	HKEY hKey=0;
	struct TNCINFO * TNC = TNCInfo[port];
	struct STREAMINFO * STREAM = &TNC->Streams[0];
	struct ScanEntry * Scan;
	fd_set readfs;
	fd_set writefs;
	fd_set errorfs;
	struct timeval timeout;

	if (TNC == NULL)
		return 0;							// Port not defined

	switch (fn)
	{
	case 1:				// poll

		// Check session limit timer

		if ((STREAM->Connecting || STREAM->Connected) && !STREAM->Disconnecting)
		{
			if (TNC->SessionTimeLimit && STREAM->ConnectTime && time(NULL) > (TNC->SessionTimeLimit + STREAM->ConnectTime))
			{
				send(TNC->TCPSock,"DISCONNECT\r\n", 12, 0);
				STREAM->Disconnecting = TRUE;
			}
		}

		while (TNC->PortRecord->UI_Q)			// Release anything accidentally put on UI_Q
		{
			buffptr = Q_REM(&TNC->PortRecord->UI_Q);
			ReleaseBuffer(buffptr);
		}


		if (TNC->Busy)							//  Count down to clear
		{
			if ((TNC->BusyFlags & CDBusy) == 0)	// TNC Has reported not busy
			{
				TNC->Busy--;
				if (TNC->Busy == 0)
					SetWindowText(TNC->xIDC_CHANSTATE, "Clear");
					strcpy(TNC->WEB_CHANSTATE, "Clear");
			}
		}

		if (TNC->ConnectCmd && TNC->BusyDelay)
		{
			// Still Busy?

			if (InterlockedCheckBusy(TNC) == FALSE)
			{
				// No, so send

				send(TNC->TCPSock, TNC->ConnectCmd, (int)strlen(TNC->ConnectCmd), 0);
				TNC->Streams[0].Connecting = TRUE;

				memset(TNC->Streams[0].RemoteCall, 0, 10);
				memcpy(TNC->Streams[0].RemoteCall, &TNC->ConnectCmd[8], strlen(TNC->ConnectCmd)-10);

				sprintf(TNC->WEB_TNCSTATE, "%s Connecting to %s", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall);
				SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

				free(TNC->ConnectCmd);
				TNC->ConnectCmd = 0;

				TNC->BusyDelay = 0;
			}
			else
			{
				// Wait Longer

				TNC->BusyDelay--;

				if (TNC->BusyDelay == 0)
				{
					// Timed out - Send Error Response

					PMSGWITHLEN buffptr = GetBuff();

					if (buffptr == 0) return (0);			// No buffers, so ignore

					buffptr->Len = 39;
					memcpy(buffptr->Data,"Sorry, Can't Connect - Channel is busy\r", 39);

					C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
					free(TNC->ConnectCmd);

					sprintf(TNC->WEB_TNCSTATE, "In Use by %s", TNC->Streams[0].MyCall);
					SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

				}
			}
		}

		if (TNC->HeartBeat++ > 600 || (TNC->Streams[0].Connected && TNC->HeartBeat > 50))			// Every Minute unless connected
		{	
			if (TNC->HeartBeat > 600 && TNC->hWnd)
			{
				char wtext[100];
				sprintf (wtext, "WINMOR Sound Card TNC - BPQ %s", TNC->PortRecord->PORTCONTROL.PORTDESCRIPTION);
				MySetWindowText(TNC->hWnd, wtext);
			}

			TNC->HeartBeat = 0;

			if (TNC->CONNECTED)
			{
				// Probe link

				if (TNC->Streams[0].Connecting || TNC->Streams[0].Connected)
					send(TNC->TCPSock, "MODE\r\n", 6, 0);
				else
				{
					if (time(NULL) - TNC->WinmorRestartCodecTimer > 900)	// 15 mins
					{
						send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);
						send(TNC->TCPSock, "CODEC TRUE\r\n", 12, 0);
					}
					else
						send(TNC->TCPSock, "STATE\r\n", 7, 0);
				}
			}
		}

		if (TNC->FECMode)
		{
			if (TNC->FECIDTimer++ > 6000)		// ID every 10 Mins
			{
				if (!TNC->Busy)
				{
					TNC->FECIDTimer = 0;
					send(TNC->TCPSock, "SENDID 0\r\n", 10, 0);
				}
			}
			if (TNC->FECPending)	// Check if FEC Send needed
			{
				if (!TNC->Busy)
				{
					TNC->FECPending = 0;

					if (TNC->FEC1600)
						send(TNC->TCPSock,"FECSEND 1600\r\n", 14, 0);
					else
						send(TNC->TCPSock,"FECSEND 500\r\n", 13, 0);
				}
			}
		}

		if (STREAM->NeedDisc)
		{
			STREAM->NeedDisc--;

			if (STREAM->NeedDisc == 0)
			{
				// Send the DISCONNECT

				send(TNC->TCPSock, "DISCONNECT\r\n", 12, 0);
			}
		}

		if (TNC->DiscPending)
		{
			TNC->DiscPending--;

			if (TNC->DiscPending == 0)
			{
				// Too long in Disc Pending - Kill and Restart TNC

				if (TNC->PID)
				{
					KillTNC(TNC);
					RestartTNC(TNC);
				}
			}
		}

		if (TNC->TimeSinceLast++ > 800)			// Allow 10 secs for Keepalive
		{
			// Restart TNC
		
			if (TNC->ProgramPath && TNC->CONNECTED)
			{
				if (strstr(TNC->ProgramPath, "WINMOR TNC"))
				{
					struct tm * tm;
					char Time[80];
				
					TNC->Restarts++;
					TNC->LastRestart = time(NULL);

					tm = gmtime(&TNC->LastRestart);	
				
					sprintf_s(Time, sizeof(Time),"%04d/%02d/%02d %02d:%02dZ",
						tm->tm_year +1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);

					SetWindowText(TNC->xIDC_RESTARTTIME, Time);
					strcpy(TNC->WEB_RESTARTTIME, Time);

					sprintf_s(Time, sizeof(Time),"%d", TNC->Restarts);
					SetWindowText(TNC->xIDC_RESTARTS, Time);
					strcpy(TNC->WEB_RESTARTS, Time);
	
					KillTNC(TNC);
					RestartTNC(TNC);

					TNC->TimeSinceLast = 0;
				}
			}
		}

		if (TNC->PortRecord->ATTACHEDSESSIONS[0] && TNC->Streams[0].Attached == 0)
		{
			// New Attach

			int calllen;
			char Msg[80];

			TNC->SessionTimeLimit = TNC->DefaultSessionTimeLimit;		// Reset Limit

			TNC->Streams[0].Attached = TRUE;

			calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4USER, TNC->Streams[0].MyCall);
			TNC->Streams[0].MyCall[calllen] = 0;

			// Stop Listening, and set MYCALL to user's call

			send(TNC->TCPSock, "LISTEN FALSE\r\n", 14, 0);
			ChangeMYC(TNC, TNC->Streams[0].MyCall);

			// Stop other ports in same group

			SuspendOtherPorts(TNC);

			sprintf(TNC->WEB_TNCSTATE, "In Use by %s", TNC->Streams[0].MyCall);
			SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

			// Stop Scanning

			sprintf(Msg, "%d SCANSTOP", TNC->Port);
	
			Rig_Command( (TRANSPORTENTRY *) -1, Msg);

		}

		if (TNC->Streams[0].Attached)
			CheckForDetach(TNC, 0, &TNC->Streams[0], TidyClose, ForcedClose, CloseComplete);

		if (TNC->Streams[0].ReportDISC)
		{
			TNC->Streams[0].ReportDISC = FALSE;
			buff->PORT = 0;
			return -1;
		}

	

			if (TNC->CONNECTED == FALSE && TNC->CONNECTING == FALSE)
			{
				//	See if time to reconnect
		
				time(&ltime);
				if (ltime - TNC->lasttime > 9 )
				{
					ConnecttoWINMOR(port);
					TNC->lasttime = ltime;
				}
			}
		
			FD_ZERO(&readfs);
			
			if (TNC->CONNECTED) FD_SET(TNC->TCPDataSock,&readfs);
			
			FD_ZERO(&writefs);

			if (TNC->BPQtoWINMOR_Q) FD_SET(TNC->TCPDataSock,&writefs);	// Need notification of busy clearing

			FD_ZERO(&errorfs);
		
			if (TNC->CONNECTING || TNC->CONNECTED) FD_SET(TNC->TCPDataSock,&errorfs);

			timeout.tv_sec = 0;
			timeout.tv_usec = 0;				// poll

			if (select((int)TNC->TCPDataSock + 1, &readfs, &writefs, &errorfs, &timeout) > 0)
			{
				//	See what happened

				if (FD_ISSET(TNC->TCPDataSock, &readfs))
					ProcessDataSocketData(port);			
				
				if (FD_ISSET(TNC->TCPDataSock, &writefs))
				{
					// Write block has cleared. Send rest of packet

					buffptr=Q_REM(&TNC->BPQtoWINMOR_Q);
					txlen = buffptr->Len;
					memcpy(txbuff,buffptr->Data,txlen);
					bytes=send(TNC->TCPSock, (const char FAR *)&txbuff, (int)txlen, 0);
					ReleaseBuffer(buffptr);
				}
					
				if (FD_ISSET(TNC->TCPDataSock, &errorfs))
				{
					i=sprintf(ErrMsg, "WINMOR Data Connection lost for BPQ Port %d\r\n", port);
					WritetoConsole(ErrMsg);
					TNC->CONNECTING = FALSE;
					TNC->CONNECTED = FALSE;
					TNC->Streams[0].ReportDISC = TRUE;
				}
			}
		
		// See if any frames for this port

		if (TNC->WINMORtoBPQ_Q != 0)
		{
			buffptr=Q_REM(&TNC->WINMORtoBPQ_Q);

			datalen = buffptr->Len;

			buff->PORT = 0;						// Compatibility with Kam Driver
			buff->PID = 0xf0;
			memcpy(&buff->L2DATA[0], buffptr->Data, datalen);	// Data goes to +7, but we have an extra byte
			datalen = buffptr->Len;

			datalen += sizeof(void *) + 4;
			PutLengthinBuffer(buff, (int)datalen);

			ReleaseBuffer(buffptr);

			return (1);
		}

		return (0);

	case 2:				// send

		if (!TNC->CONNECTED)
		{
			// Send Error Response

			PMSGWITHLEN buffptr = GetBuff();

			if (buffptr == 0) return (0);			// No buffers, so ignore

			buffptr->Len = 36;
			memcpy(buffptr->Data, "No Connection to WINMOR Virtual TNC\r", 36);

			C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
			
			return 0;		// Don't try if not connected
		}

		if (TNC->Streams[0].BPQtoPACTOR_Q)		//Used for CTEXT
		{
			PMSGWITHLEN buffptr = Q_REM(&TNC->Streams[0].BPQtoPACTOR_Q);
			txlen = buffptr->Len;
			memcpy(txbuff, buffptr->Data, txlen);
			bytes = send(TNC->TCPDataSock, txbuff, (int)txlen, 0);
			STREAM->bytesTXed += bytes;
			WritetoTrace(TNC, txbuff, (int)txlen);
			ReleaseBuffer(buffptr);
		}
		
		if (TNC->SwallowSignon)
		{
			TNC->SwallowSignon = FALSE;		// Discard *** connected
			return 0;
		}

		txlen = GetLengthfromBuffer(buff) - (MSGHDDRLEN + 1);		// 1 as no PID

		if (TNC->Streams[0].Connected)
		{
			STREAM->PacketsSent++;

			if (STREAM->PacketsSent == 3)
			{
				if (TNC->Robust)
					send(TNC->TCPSock, "ROBUST TRUE\r\n", 13, 0);
				else
					send(TNC->TCPSock, "ROBUST FALSE\r\n", 14, 0);
			}

			bytes = send(TNC->TCPDataSock,buff->L2DATA, (int)txlen, 0);
			STREAM->bytesTXed += bytes;
			WritetoTrace(TNC, &buff->L2DATA[0], (int)txlen);

		}
		else
		{
			if (_memicmp(buff->L2DATA, "D\r", 2) == 0)
			{
				TNC->Streams[0].ReportDISC = TRUE;		// Tell Node
				return 0;
			}
	
			if (TNC->FECMode)
			{
				char Buffer[300];
				int len;

				// Send FEC Data

				buff->L2DATA[txlen] = 0;
				len = sprintf(Buffer, "%-9s: %s", TNC->Streams[0].MyCall, &buff->L2DATA);

				send(TNC->TCPDataSock, Buffer, len, 0);

				if (TNC->BusyFlags)
				{
					TNC->FECPending = 1;
				}
				else
				{
					if (TNC->FEC1600)
						send(TNC->TCPSock,"FECSEND 1600\r\n", 14, 0);
					else
						send(TNC->TCPSock,"FECSEND 500\r\n", 13, 0);
				}
				return 0;
			}


			// See if Local command (eg RADIO)

			if (_memicmp(&buff->L2DATA[0], "RADIO ", 6) == 0)
			{
				char cmd[56];

				strcpy(cmd, &buff->L2DATA[6]);
				sprintf(&buff->L2DATA[0], "%d %s", TNC->Port, &cmd);

	
				if (Rig_Command(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK, &buff->L2DATA[0]))
				{
				}
				else
				{
					PMSGWITHLEN buffptr = GetBuff();

					if (buffptr == 0) return 1;			// No buffers, so ignore

					buffptr->Len = sprintf(buffptr->Data, "%s", &buff->L2DATA[0]);
					C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
				}
				return 1;
			}

			if (_memicmp(&buff->L2DATA[0], "OVERRIDEBUSY", 12) == 0)
			{
				PMSGWITHLEN buffptr = GetBuff();

				TNC->OverrideBusy = TRUE;

				if (buffptr)
				{
					buffptr->Len = sprintf(&buffptr->Data[0], "Winmor} OK\r");
					C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
				}

				return 0;

			}

			if (_memicmp(&buff->L2DATA[0], "MAXCONREQ", 9) == 0)
			{
				if (buff->L2DATA[9] != 13)
				{
					// Limit connects

					int tries = atoi(&buff->L2DATA[10]);
					int len;

					if (tries > 10) tries = 10;
					len = sprintf(&buff->L2DATA[0], "MAXCONREQ %d\r\nMAXCONREQ\r\n", tries);

					send(TNC->TCPSock, &buff->L2DATA[0], len, 0);
					return 0;
				}
			}

			if (_memicmp(&buff->L2DATA[0], "SessionTimeLimit", 16) == 0)
			{
				if (buff->L2DATA[16] != 13)
				{					
					PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();
	
					TNC->SessionTimeLimit = atoi(&buff->L2DATA[16]) * 60;

					if (buffptr)
					{
						buffptr->Len = sprintf((UCHAR *)&buffptr->Data[0], "Winmor} OK\r");
						C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
					}
					return 0;
				}
			}

			if ((_memicmp(buff->L2DATA, "BW 500", 6) == 0) || (_memicmp(buff->L2DATA, "BW 1600", 7) == 0))
			{
				// Generate a local response
				
				PMSGWITHLEN buffptr = GetBuff();

				if (_memicmp(buff->L2DATA, "BW 500", 6) == 0)
					TNC->WL2KMode = 21;
				else
					TNC->WL2KMode = 22;
	
				if (buffptr)
				{
					buffptr->Len = sprintf(&buffptr->Data[0], "Winmor} OK\r");
					C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
				}
				TNC->WinmorCurrentMode = 0;			// So scanner will set next value
			}

			if (_memicmp(buff->L2DATA, "CODEC TRUE", 9) == 0)
				TNC->StartSent = TRUE;

			if (_memicmp(buff->L2DATA, "ROBUST", 6) == 0)
			{
				if (_memicmp(&buff->L2DATA[7], "TRUE", 4) == 0)
					TNC->Robust = TRUE;
				else
					TNC->Robust = FALSE;
			}

			if (_memicmp(buff->L2DATA, "D\r", 2) == 0)
			{
				TNC->Streams[0].ReportDISC = TRUE;		// Tell Node
				return 0;
			}

			if (_memicmp(buff->L2DATA, "FEC\r", 4) == 0 || _memicmp(buff->L2DATA, "FEC ", 4) == 0)
			{
				TNC->FECMode = TRUE;
				TNC->FECIDTimer = 0;
				send(TNC->TCPSock,"FECRCV TRUE\r\nFECRCV\r\n", 21, 0);
		
				if (_memicmp(buff->L2DATA, "FEC 1600", 8) == 0)
					TNC->FEC1600 = TRUE;
				else
					TNC->FEC1600 = FALSE;

				return 0;
			}

			// See if a Connect Command. If so, start codec and set Connecting

			if (toupper(buff->L2DATA[0]) == 'C' && buff->L2DATA[1] == ' ' && txlen > 2)	// Connect
			{
				char Connect[80] = "CONNECT ";

				memcpy(&Connect[8], &buff->L2DATA[2], txlen);
				txlen += 6;
				Connect[txlen++] = 0x0a;
				Connect[txlen] = 0;

				_strupr(Connect);

				ChangeMYC(TNC, TNC->Streams[0].MyCall);

				// See if Busy
				
				if (InterlockedCheckBusy(TNC))
				{
					// Channel Busy. Unless override set, wait

					if (TNC->OverrideBusy == 0)
					{
						// Save Command, and wait up to 10 secs
						
						sprintf(TNC->WEB_TNCSTATE, "Waiting for clear channel");
						SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

						TNC->ConnectCmd = _strdup(Connect);
						TNC->BusyDelay = TNC->BusyWait * 10;		// BusyWait secs
						return 0;
					}
				}

				TNC->OverrideBusy = FALSE;

				bytes = send(TNC->TCPSock, Connect, (int)txlen, 0);
				TNC->Streams[0].Connecting = TRUE;

				memset(TNC->Streams[0].RemoteCall, 0, 10);
				memcpy(TNC->Streams[0].RemoteCall, &Connect[8], txlen-10);

				sprintf(TNC->WEB_TNCSTATE, "%s Connecting to %s", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall);
				SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);
			}
			else
			{
				buff->L2DATA[txlen++] = 0x0a;
				bytes = send(TNC->TCPSock, &buff->L2DATA[0], (int)txlen, 0);
			}
		}
		if (bytes != txlen)
		{

			// WINMOR doesn't seem to recover from a blocked write. For now just reset
			
			winerr = WSAGetLastError();
			sprintf(ErrMsg, "WINMOR Write Failed for port %d - error code = %d\r\n", port, winerr);
			WritetoConsole(ErrMsg);	
			closesocket(TNC->TCPSock);	
			TNC->CONNECTED = FALSE;

			return (0);
		}

		return (0);

	case 3:	
		
		// CHECK IF OK TO SEND (And check TNC Status)

		if (TNC->Streams[0].Attached == 0)
			return TNC->CONNECTED << 8 | 1;

		return (TNC->CONNECTED << 8 | TNC->Streams[0].Disconnecting << 15);		// OK
			
		break;

	case 4:				// reinit

		return (0);

	case 5:				// Close

		send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);
		Sleep(100);
		shutdown(TNC->TCPDataSock, SD_BOTH);
		shutdown(TNC->TCPSock, SD_BOTH);
		Sleep(100);

		closesocket(TNC->TCPDataSock);
		closesocket(TNC->TCPSock);

		if (TNC->PID && TNC->WeStartedTNC)
		{
			KillTNC(TNC);
		}

		return (0);

	case 6:				// Scan Stop Interface

		Param = (size_t)buff;

		if (Param == 2)		// Check  Permission (shouldn't happen)
		{
			Debugprintf("Scan Check Permission called on FLDIGI");
			return 1;		// OK to change
		}

		if (!TNC->TCPSock)
			return 0;					// No connection so no interlock
	
		if (Param == 1)		// Request Permission
		{
			if (TNC->ConnectPending)
				return TRUE;	// Not OK to Change

			if (TNC->CONNECTED)
			{
				TNC->GavePermission = TRUE;
				send(TNC->TCPSock, "LISTEN FALSE\r\n", 14, 0);
			}
			return FALSE;
		}

		if (Param == 3)		// Release  Permission
		{
			if (TNC->CONNECTED)
			{
				if (TNC->GavePermission)
				{
					TNC->GavePermission = FALSE;
					send(TNC->TCPSock, "LISTEN TRUE\r\n", 13, 0);
				}
			}
			return 0;
		}

		// Param is Address of a struct ScanEntry

		Scan = (struct ScanEntry *)buff;


		if (Scan->Bandwidth == 'W')		// Set Wide Mode
		{
			if (TNC->WinmorCurrentMode != 1600)
			{
				if (TNC->WinmorCurrentMode == 0)
					if (TNC->CONNECTED)
						send(TNC->TCPSock, "LISTEN TRUE\r\n", 13, 0);

				if (TNC->CONNECTED)
					send(TNC->TCPSock, "BW 1600\r\n", 9, 0);
				TNC->WinmorCurrentMode = 1600;
			}
			TNC->WL2KMode = 22;
			return 0;
		}


		if (Scan->Bandwidth == 'N')		// Set Narrow Mode
		{
			if (TNC->WinmorCurrentMode != 500)
			{
				if (TNC->WinmorCurrentMode == 0)
					if (TNC->CONNECTED)
						send(TNC->TCPSock, "LISTEN TRUE\r\n", 13, 0);

				TNC->WinmorCurrentMode = 500;
				if (TNC->CONNECTED)
					send(TNC->TCPSock, "BW 500\r\n", 8, 0);
			}
			TNC->WL2KMode = 21;
			return 0;
		}

		if (Scan->Bandwidth == 'X')		// Dont Allow Connects
		{
			if (TNC->WinmorCurrentMode != 0)
			{
				if (TNC->CONNECTED)
					send(TNC->TCPSock, "LISTEN FALSE\r\n", 14, 0);
				TNC->WinmorCurrentMode = 0;
			}

			TNC->WL2KMode = 0;
			return 0;
		}

		return 0;
	}
	return 0;
}

VOID ReleaseTNC(struct TNCINFO * TNC)
{
	// Set mycall back to Node or Port Call, and Start Scanner

	UCHAR TXMsg[256];
	char wtext[100];

	ChangeMYC(TNC, TNC->NodeCall);

	if (TNC->CONNECTED)
		send(TNC->TCPSock, "LISTEN TRUE\r\nMAXCONREQ 4\r\n", 26, 0);

	strcpy(TNC->WEB_TNCSTATE, "Free");
	MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

	if (TNC->hWnd)
	{
		sprintf (wtext, "WINMOR Sound Card TNC - BPQ %s", TNC->PortRecord->PORTCONTROL.PORTDESCRIPTION);
		MySetWindowText(TNC->hWnd, wtext);
	}

	//	Start Scanner
				
	sprintf(TXMsg, "%d SCANSTART 15", TNC->Port);

	Rig_Command( (TRANSPORTENTRY *) -1, TXMsg);

	ReleaseOtherPorts(TNC);

}

VOID SuspendOtherPorts(struct TNCINFO * ThisTNC)
{
	// Disable other TNCs in same Interlock Group
	
	struct TNCINFO * TNC;
	int i;
	int rxInterlock = ThisTNC->RXRadio;
	int txInterlock = ThisTNC->TXRadio;

	if (rxInterlock == 0 || txInterlock == 0)
		return;

	for (i = 1; i <= MAXBPQPORTS; i++)
	{
		TNC = TNCInfo[i];
		if (TNC == NULL)
			continue;

		if (TNC == ThisTNC)
			continue;

		if (rxInterlock == TNC->RXRadio || txInterlock == TNC->TXRadio)	// Same Group	
			if (TNC->SuspendPortProc)
				TNC->SuspendPortProc(TNC, ThisTNC);
	}
}

VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC)
{
	// Enable other TNCs in same Interlock Group
	
	struct TNCINFO * TNC;
	int i;
	int rxInterlock = ThisTNC->RXRadio;
	int txInterlock = ThisTNC->TXRadio;

	if (rxInterlock == 0 && txInterlock == 0)
		return;

	for (i=1; i <= MAXBPQPORTS; i++)
	{
		TNC = TNCInfo[i];
		if (TNC == NULL)
			continue;

		if (TNC == ThisTNC)
			continue;

		if (rxInterlock == TNC->RXRadio || txInterlock == TNC->TXRadio)	// Same Group	
			if (TNC->ReleasePortProc)
				TNC->ReleasePortProc(TNC);
	}
}

VOID WinmorSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC)
{
	if (TNC->CONNECTED)
		send(TNC->TCPSock, "CODEC FALSE\r\n", 14, 0);

	if (TNC->Busy)
	{
		TNC->Busy = FALSE;		// Can't clear detector if CODEC off.
		MySetWindowText(TNC->xIDC_CHANSTATE, "Clear");
		strcpy(TNC->WEB_CHANSTATE, "Clear");
	}
}

VOID WinmorReleasePort(struct TNCINFO * TNC)
{
	if (TNC->CONNECTED)
		send(TNC->TCPSock, "CODEC TRUE\r\n", 13, 0);
}

extern char WebProcTemplate[];
extern char sliderBit[];

static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL)
{
	int Len = sprintf(Buff, WebProcTemplate, TNC->Port, TNC->Port, "WINMOR Status", "WINMOR Status");

	if (TNC->TXFreq)
		Len += sprintf(&Buff[Len], sliderBit, TNC->TXOffset, TNC->TXOffset);


	Len += sprintf(&Buff[Len], "<table style=\"text-align: left; width: 500px; font-family: monospace; align=center \" border=1 cellpadding=2 cellspacing=2>");
	Len += sprintf(&Buff[Len], "<tr><td width=110px>Comms State</td><td>%s</td></tr>", TNC->WEB_COMMSSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>TNC State</td><td>%s</td></tr>", TNC->WEB_TNCSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Mode</td><td>%s</td></tr>", TNC->WEB_MODE);
	Len += sprintf(&Buff[Len], "<tr><td>Channel State</td><td>%s</td></tr>", TNC->WEB_CHANSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Proto State</td><td>%s</td></tr>", TNC->WEB_PROTOSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Traffic</td><td>%s</td></tr>", TNC->WEB_TRAFFIC);
//	Len += sprintf(&Buff[Len], "<tr><td>TNC Restarts</td><td></td></tr>", TNC->WEB_RESTARTS);
	Len += sprintf(&Buff[Len], "</table>");

	Len += sprintf(&Buff[Len], "<textarea rows=10 style=\"width:500px; height:250px;\" id=textarea >%s</textarea>", TNC->WebBuffer);
	Len = DoScanLine(TNC, Buff, Len);

	return Len;
}


void * WinmorExtInit(EXTPORTDATA * PortEntry)
{
	int i, port;
	char Msg[255];
	char * ptr;
	APPLCALLS * APPL;
	struct TNCINFO * TNC;
	char Aux[100] = "MYAUX ";
	char Appl[11];
	char * TempScript;

	//
	//	Will be called once for each WINMOR port 
	//
	//	The Socket to connect to is in IOBASE
	//
	
	port = PortEntry->PORTCONTROL.PORTNUMBER;

	ReadConfigFile(port, ProcessLine);

	TNC = TNCInfo[port];

	if (TNC == NULL)
	{
		// Not defined in Config file

		sprintf(Msg," ** Error - no info in BPQ32.cfg for this port\n");
		WritetoConsole(Msg);

		return ExtProc;
	}

	TNC->Port = port;
	TNC->PortRecord = PortEntry;

	if (TNC->ProgramPath)
		TNC->WeStartedTNC = RestartTNC(TNC);

	TNC->PortRecord->PORTCONTROL.HWType = TNC->Hardware = H_WINMOR;

	if (TNC->BusyWait == 0)
		TNC->BusyWait = 10;

	if (TNC->BusyHold == 0)
		TNC->BusyHold = 1;


	if (PortEntry->PORTCONTROL.PORTCALL[0] == 0)
		memcpy(TNC->NodeCall, MYNODECALL, 10);
	else
		ConvFromAX25(&PortEntry->PORTCONTROL.PORTCALL[0], TNC->NodeCall);

	if (PortEntry->PORTCONTROL.PORTINTERLOCK && TNC->RXRadio == 0 && TNC->TXRadio == 0)
		TNC->RXRadio = TNC->TXRadio = PortEntry->PORTCONTROL.PORTINTERLOCK;

	PortEntry->PORTCONTROL.PROTOCOL = 10;
	PortEntry->PORTCONTROL.PORTQUALITY = 0;
	PortEntry->MAXHOSTMODESESSIONS = 1;	
	PortEntry->SCANCAPABILITIES = SIMPLE;			// Scan Control - pending connect only

	if (PortEntry->PORTCONTROL.PORTPACLEN == 0)
		PortEntry->PORTCONTROL.PORTPACLEN = 236;

	TNC->SuspendPortProc = WinmorSuspendPort;
	TNC->ReleasePortProc = WinmorReleasePort;

	TNC->ModemCentre = 1500;				// WINMOR is always 1500 Offset

	ptr=strchr(TNC->NodeCall, ' ');
	if (ptr) *(ptr) = 0;					// Null Terminate

	// Set Essential Params and MYCALL

	// Put overridable ones on front, essential ones on end

	TempScript = malloc(1000);

	strcpy(TempScript, "DebugLog True\r\n");
	strcat(TempScript, "CWID False\r\n");
	strcat(TempScript, "BW 1600\r\n");
	strcat(TempScript, "ROBUST False\r\n");
	strcat(TempScript, "MODE AUTO\r\n");

	strcat(TempScript, TNC->InitScript);

	free(TNC->InitScript);
	TNC->InitScript = TempScript;

	TNC->WL2KMode = 22;				// in case not scanning

	// Set MYCALL

	strcat(TNC->InitScript,"FECRCV True\r\n");
	strcat(TNC->InitScript,"AUTOBREAK True\r\n");

	sprintf(Msg, "MYC %s\r\nCODEC TRUE\r\nLISTEN TRUE\r\nMYC\r\n", TNC->NodeCall);
	strcat(TNC->InitScript, Msg);
	strcat(TNC->InitScript,"PROCESSID\r\n");

	for (i = 0; i < 32; i++)
	{
		APPL=&APPLCALLTABLE[i];

		if (APPL->APPLCALL_TEXT[0] > ' ')
		{
			char * ptr;
			memcpy(Appl, APPL->APPLCALL_TEXT, 10);
			ptr=strchr(Appl, ' ');

			if (ptr)
			{
				*ptr++ = ',';
				*ptr = 0;
			}

			strcat(Aux, Appl);
		}
	}
	strcat(TNC->InitScript, Aux);
	strcat(TNC->InitScript,"\r\nMYAUX\r\n");

	strcpy(TNC->CurrentMYC, TNC->NodeCall);

	if (TNC->WL2K == NULL)
		if (PortEntry->PORTCONTROL.WL2KInfo.RMSCall[0])			// Alrerady decoded
			TNC->WL2K = &PortEntry->PORTCONTROL.WL2KInfo;

	if (TNC->destaddr.sin_family == 0)
	{
		// not defined in config file, so use localhost and port from IOBASE

		TNC->destaddr.sin_family = AF_INET;
		TNC->destaddr.sin_port = htons(PortEntry->PORTCONTROL.IOBASE);
		TNC->Datadestaddr.sin_family = AF_INET;
		TNC->Datadestaddr.sin_port = htons(PortEntry->PORTCONTROL.IOBASE+1);

		TNC->HostName=malloc(10);

		if (TNC->HostName != NULL) 
			strcpy(TNC->HostName,"127.0.0.1");

	}

	PortEntry->PORTCONTROL.TNC = TNC;

	TNC->WebWindowProc = WebProc;
	TNC->WebWinX = 520;
	TNC->WebWinY = 500;
	TNC->WebBuffer = zalloc(5000);

	TNC->WEB_COMMSSTATE = zalloc(100);
	TNC->WEB_TNCSTATE = zalloc(100);
	TNC->WEB_CHANSTATE = zalloc(100);
	TNC->WEB_BUFFERS = zalloc(100);
	TNC->WEB_PROTOSTATE = zalloc(100);
	TNC->WEB_RESTARTTIME = zalloc(100);
	TNC->WEB_RESTARTS = zalloc(100);

	TNC->WEB_MODE = zalloc(20);
	TNC->WEB_TRAFFIC = zalloc(100);


#ifndef LINBPQ

	CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 500, 450, ForcedClose);

	CreateWindowEx(0, "STATIC", "Comms State", WS_CHILD | WS_VISIBLE, 10,6,120,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_COMMSSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,6,386,20, TNC->hDlg, NULL, hInstance, NULL);
	
	CreateWindowEx(0, "STATIC", "TNC State", WS_CHILD | WS_VISIBLE, 10,28,106,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TNCSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,28,520,20, TNC->hDlg, NULL, hInstance, NULL);

	CreateWindowEx(0, "STATIC", "Mode", WS_CHILD | WS_VISIBLE, 10,50,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_MODE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,50,200,20, TNC->hDlg, NULL, hInstance, NULL);
 
	CreateWindowEx(0, "STATIC", "Channel State", WS_CHILD | WS_VISIBLE, 10,72,110,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_CHANSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,72,144,20, TNC->hDlg, NULL, hInstance, NULL);
 
 	CreateWindowEx(0, "STATIC", "Proto State", WS_CHILD | WS_VISIBLE,10,94,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_PROTOSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,94,374,20 , TNC->hDlg, NULL, hInstance, NULL);
 
	CreateWindowEx(0, "STATIC", "Traffic", WS_CHILD | WS_VISIBLE,10,116,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TRAFFIC = CreateWindowEx(0, "STATIC", "0 0 0 0", WS_CHILD | WS_VISIBLE,116,116,374,20 , TNC->hDlg, NULL, hInstance, NULL);

	CreateWindowEx(0, "STATIC", "TNC Restarts", WS_CHILD | WS_VISIBLE,10,138,100,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_RESTARTS = CreateWindowEx(0, "STATIC", "0", WS_CHILD | WS_VISIBLE,116,138,40,20 , TNC->hDlg, NULL, hInstance, NULL);
	CreateWindowEx(0, "STATIC", "Last Restart", WS_CHILD | WS_VISIBLE,140,138,100,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_RESTARTTIME = CreateWindowEx(0, "STATIC", "Never", WS_CHILD | WS_VISIBLE,250,138,200,20, TNC->hDlg, NULL, hInstance, NULL);

	TNC->hMonitor= CreateWindowEx(0, "LISTBOX", "", WS_CHILD |  WS_VISIBLE  | LBS_NOINTEGRALHEIGHT | 
            LBS_DISABLENOSCROLL | WS_HSCROLL | WS_VSCROLL,
			0,170,250,300, TNC->hDlg, NULL, hInstance, NULL);

	TNC->ClientHeight = 450;
	TNC->ClientWidth = 500;

	TNC->hMenu = CreatePopupMenu();

	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_KILL, "Kill Winmor TNC");
	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_RESTART, "Kill and Restart Winmor TNC");
	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_RESTARTAFTERFAILURE, "Restart TNC after failed Connection");
	AppendMenu(TNC->hMenu, MF_STRING, ARDOP_ABORT, "Abort Current Session");

	CheckMenuItem(TNC->hMenu, WINMOR_RESTARTAFTERFAILURE, (TNC->RestartAfterFailure) ? MF_CHECKED : MF_UNCHECKED);

	MoveWindows(TNC);
#endif
	Consoleprintf("WINMOR Host %s %d", TNC->HostName, htons(TNC->destaddr.sin_port));

	ConnecttoWINMOR(port);

	time(&TNC->lasttime);			// Get initial time value

	return ExtProc;
}

int ConnecttoWINMOR(int port)
{
	_beginthread(WINMORThread, 0, (void *)(size_t)port);

	return 0;
}

VOID WINMORThread(void * portptr)
{
	// Opens both sockets and looks for data on control socket. Data socket is polled from BG,
	// but we need fast response to control messages for PTT porcessing

	int port = (int)(size_t)portptr;

	char Msg[255];
	int err, i, ret;
	u_long param=1;
	BOOL bcopt=TRUE;
	struct hostent * HostEnt;
	struct TNCINFO * TNC = TNCInfo[port];
	fd_set readfs;
	fd_set errorfs;
	struct timeval timeout;
	if (TNC->HostName == NULL)
		return;

	TNC->CONNECTING = TRUE;

	Sleep(3000);		// Allow init to complete 

#ifdef WIN32
	if (strcmp(TNC->HostName, "127.0.0.1") == 0)
	{
		// can only check if running on local host
		
		TNC->PID = GetListeningPortsPID(TNC->destaddr.sin_port);
		if (TNC->PID == 0)
		{
			TNC->CONNECTING = FALSE;
			return;						// Not listening so no point trying to connect
		}
	}
#endif

//	// If we started the TNC make sure it is still running.

//	if (!IsProcess(TNC->PID))
//	{
//		RestartTNC(TNC);
//		Sleep(3000);
//	}


	TNC->destaddr.sin_addr.s_addr = inet_addr(TNC->HostName);
	TNC->Datadestaddr.sin_addr.s_addr = inet_addr(TNC->HostName);

	if (TNC->destaddr.sin_addr.s_addr == INADDR_NONE)
	{
		//	Resolve name to address

		HostEnt = gethostbyname (TNC->HostName);
		 
		 if (!HostEnt)
		 {
			 	TNC->CONNECTING = FALSE;
				return;			// Resolve failed
		 }
		 memcpy(&TNC->destaddr.sin_addr.s_addr,HostEnt->h_addr,4);
		 memcpy(&TNC->Datadestaddr.sin_addr.s_addr,HostEnt->h_addr,4);

	}

	if (TNC->TCPSock)
		closesocket(TNC->TCPSock);

	TNC->TCPSock = 0;
	
	if (TNC->TCPDataSock)
		closesocket(TNC->TCPDataSock);

	TNC->TCPDataSock = 0;

	TNC->TCPSock=socket(AF_INET,SOCK_STREAM,0);

	if (TNC->TCPSock == INVALID_SOCKET)
	{
		i=sprintf(Msg, "Socket Failed for WINMOR socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

	 	TNC->CONNECTING = FALSE;
  	 	return; 
	}
 
	setsockopt (TNC->TCPSock, SOL_SOCKET, SO_REUSEADDR, (const char FAR *)&bcopt, 4);

	sinx.sin_family = AF_INET;
	sinx.sin_addr.s_addr = INADDR_ANY;
	sinx.sin_port = 0;

	if (bind(TNC->TCPSock, (LPSOCKADDR) &sinx, addrlen) != 0 )
	{
		//
		//	Bind Failed
		//
	
		i=sprintf(Msg, "Bind Failed for WINMOR socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);
			
		closesocket(TNC->TCPSock);
		TNC->TCPSock = 0;
	 	TNC->CONNECTING = FALSE;

  	 	return; 
	}

	if (connect(TNC->TCPSock,(LPSOCKADDR) &TNC->destaddr,sizeof(TNC->destaddr)) == 0)
	{
		//
		//	Connected successful
		//
	}
	else
	{
		if (TNC->Alerted == FALSE)
		{
			err=WSAGetLastError();
   			i=sprintf(Msg, "Connect Failed for WINMOR socket - error code = %d\r\n", err);
			WritetoConsole(Msg);
			sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC failed");
			MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);
			TNC->Alerted = TRUE;
		}
		
		closesocket(TNC->TCPSock);
	 	TNC->CONNECTING = FALSE;
		TNC->TCPSock = 0;

		return;
	}

	Sleep(1000);

	TNC->LastFreq = 0;			//	so V4 display will be updated

	TNC->TCPDataSock=socket(AF_INET,SOCK_STREAM,0);

	setsockopt (TNC->TCPDataSock, SOL_SOCKET, SO_REUSEADDR, (const char FAR *)&bcopt, 4);

	if (TNC->TCPDataSock == INVALID_SOCKET)
	{
		i=sprintf(Msg, "Socket Failed for WINMOR socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

		closesocket(TNC->TCPSock);
		TNC->TCPSock = 0;
	 	TNC->CONNECTING = FALSE;

  	 	return; 
	}
 
	if (bind(TNC->TCPDataSock, (LPSOCKADDR) &sinx, addrlen) != 0 )
	{
		//
		//	Bind Failed
		//
	
		i=sprintf(Msg, "Bind Failed for WINMOR Data socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

		closesocket(TNC->TCPSock);
		closesocket(TNC->TCPDataSock);
		TNC->TCPSock = 0;
		TNC->TCPDataSock = 0;
		TNC->CONNECTING = FALSE;

  	 	return; 
	}

	if (connect(TNC->TCPDataSock,(LPSOCKADDR) &TNC->Datadestaddr,sizeof(TNC->Datadestaddr)) == 0)
	{
		ioctlsocket (TNC->TCPDataSock,FIONBIO,&param);		// Set nonblocking
		TNC->CONNECTED = TRUE;
	 	TNC->CONNECTING = FALSE;

		// Send INIT script

		send(TNC->TCPSock, TNC->InitScript , (int)strlen(TNC->InitScript), 0);
		TNC->Alerted = TRUE;

		if (TNC->Hardware == H_V4)
			sprintf(TNC->WEB_COMMSSTATE, "Connected to V4 TNC");
		else
			sprintf(TNC->WEB_COMMSSTATE, "Connected to WINMOR TNC");
		
		GetSemaphore(&Semaphore, 40);
		MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);
		FreeSemaphore(&Semaphore);

	}
	else
	{
		sprintf(Msg, "Connect Failed for WINMOR Data socket Port %d - error code = %d\r\n", port, WSAGetLastError());
		WritetoConsole(Msg);

		closesocket(TNC->TCPSock);
		closesocket(TNC->TCPDataSock);
		TNC->TCPSock = 0;
		TNC->TCPDataSock = 0;
	 	TNC->CONNECTING = FALSE;

		return;
	}

	TNC->HeartBeat = 0;

	while (TRUE)
	{
		FD_ZERO(&readfs);	
		FD_ZERO(&errorfs);

		FD_SET(TNC->TCPSock,&readfs);
		FD_SET(TNC->TCPSock,&errorfs);

		timeout.tv_sec = 90;
		timeout.tv_usec = 0;				// We should get messages more frequently that this

		ret = select((int)TNC->TCPSock + 1, &readfs, NULL, &errorfs, &timeout);
		
		if (ret == SOCKET_ERROR)
		{
			printf("Select failed %d ", WSAGetLastError());
			goto Lost;
		}
		if (ret > 0)
		{
			//	See what happened

			if (FD_ISSET(TNC->TCPSock, &readfs))
			{
				if (TNC->Hardware == H_V4)
					V4ProcessReceivedData(TNC);			
				else
					ProcessReceivedData(TNC);
			}
								
			if (FD_ISSET(TNC->TCPSock, &errorfs))
			{
Lost:				
				sprintf(Msg, "WINMOR Connection lost for Port %d\r\n", TNC->Port);
				WritetoConsole(Msg);
				
				sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
				GetSemaphore(&Semaphore, 40);
				MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);
				FreeSemaphore(&Semaphore);
			
				TNC->CONNECTED = FALSE;
				TNC->Alerted = FALSE;

				if (TNC->PTTMode)
					Rig_PTT(TNC, FALSE);			// Make sure PTT is down

				if (TNC->Streams[0].Attached)
					TNC->Streams[0].ReportDISC = TRUE;

				closesocket(TNC->TCPSock);
				closesocket(TNC->TCPDataSock);

				TNC->TCPSock = 0;
				TNC->TCPDataSock = 0;

				return;
			}
		}
		else
		{
			// 90 secs without data. Shouldn't happen

			sprintf(Msg, "WINMOR Connection Timeout Port %d\r\n", TNC->Port);
			WritetoConsole(Msg);

			sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
			GetSemaphore(&Semaphore, 40);
			MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);
			FreeSemaphore(&Semaphore);
	
			TNC->CONNECTED = FALSE;
			TNC->Alerted = FALSE;

			if (TNC->PTTMode)
				Rig_PTT(TNC, FALSE);			// Make sure PTT is down

			if (TNC->Streams[0].Attached)
				TNC->Streams[0].ReportDISC = TRUE;

			send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);
	
			Sleep(100);
			shutdown(TNC->TCPDataSock, SD_BOTH);
			shutdown(TNC->TCPSock, SD_BOTH);
			Sleep(100);

			closesocket(TNC->TCPDataSock);
			closesocket(TNC->TCPSock);
			TNC->TCPDataSock = 0;
			TNC->TCPSock= 0;

			if (TNC->PID && TNC->WeStartedTNC)
			{
				KillTNC(TNC);
				RestartTNC(TNC);
			}
			return;
		}
	}
}

#ifdef WIN32

BOOL CALLBACK EnumTNCWindowsProc(HWND hwnd, LPARAM  lParam)
{
	char wtext[100];
	struct TNCINFO * TNC = (struct TNCINFO *)lParam; 
	UINT ProcessId;

	GetWindowText(hwnd,wtext,99);

	if (memcmp(wtext,"WINMOR Sound Card TNC", 21) == 0)
	{
		GetWindowThreadProcessId(hwnd, &ProcessId);

		if (TNC->PID == ProcessId)
		{
			 // Our Process

			TNC->hWnd = hwnd;		// save so we can reset title when sessicn closes
			sprintf (wtext, "WINMOR Sound Card TNC - BPQ %s", TNC->PortRecord->PORTCONTROL.PORTDESCRIPTION);
			SetWindowText(hwnd, wtext);
			return FALSE;
		}
	}
	
	return (TRUE);
}
#endif

VOID ProcessResponse(struct TNCINFO * TNC, UCHAR * Buffer, int MsgLen)
{
	// Response on WINMOR control channel. Could be a reply to a command, or
	// an Async  Response

	PMSGWITHLEN buffptr;
	struct STREAMINFO * STREAM = &TNC->Streams[0];

	Buffer[MsgLen - 2] = 0;

	if (_memicmp(Buffer, "FAULT failure to Restart Sound card", 20) == 0)
	{
		Debugprintf(Buffer);
	
		// Force a restart

			send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);
			send(TNC->TCPSock, "CODEC TRUE\r\n", 12, 0);
	}
	else
	{
		TNC->TimeSinceLast = 0;
	}


	if (_memicmp(Buffer, "STATE ", 6) == 0)
	{
		Debugprintf(Buffer);
	
		if (_memicmp(&Buffer[6], "OFFLINE", 7) == 0)
		{
			// Force a restart

			send(TNC->TCPSock, "CODEC FALSE\r\n", 13, 0);
			send(TNC->TCPSock, "CODEC TRUE\r\n", 12, 0);
		}
		return;
	}
	
	Buffer[MsgLen - 2] = 0;			// Remove CRLF

	if (_memicmp(Buffer, "PTT T", 5) == 0)
	{
		TNC->Busy = TNC->BusyHold * 10;				// BusyHold  delay

		if (TNC->PTTMode)
			Rig_PTT(TNC, TRUE);
		return;
	}
	if (_memicmp(Buffer, "PTT F", 5) == 0)
	{
		if (TNC->PTTMode)
			Rig_PTT(TNC, FALSE);
		return;
	}

	if (_memicmp(Buffer, "BUSY TRUE", 9) == 0)
	{	
		TNC->BusyFlags |= CDBusy;
		TNC->Busy = TNC->BusyHold * 10;				// BusyHold  delay

		SetWindowText(TNC->xIDC_CHANSTATE, "Busy");
		strcpy(TNC->WEB_CHANSTATE, "Busy");

		TNC->WinmorRestartCodecTimer = time(NULL);
		return;
	}

	if (_memicmp(Buffer, "BUSY FALSE", 10) == 0)
	{
		TNC->BusyFlags &= ~CDBusy;
		if (TNC->BusyHold)
			strcpy(TNC->WEB_CHANSTATE, "BusyHold");
		else
			strcpy(TNC->WEB_CHANSTATE, "Clear");

		SetWindowText(TNC->xIDC_CHANSTATE, TNC->WEB_CHANSTATE);
		TNC->WinmorRestartCodecTimer = time(NULL);
		return;
	}

	if (_memicmp(Buffer, "TARGET", 6) == 0)
	{
		Debugprintf(Buffer);
		GetSemaphore(&Semaphore, 50);
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		FreeSemaphore(&Semaphore);
		memcpy(TNC->TargetCall, &Buffer[7], 10);
		return;
	}

	if (_memicmp(Buffer, "OFFSET", 6) == 0)
	{
//		WritetoTrace(TNC, Buffer, MsgLen - 2);
//		memcpy(TNC->TargetCall, &Buffer[7], 10);
		return;
	}

	if (_memicmp(Buffer, "CONNECTED", 9) == 0)
	{
		char Call[11];
		char * ptr;
		APPLCALLS * APPL;
		char * ApplPtr = APPLS;
		int App;
		char Appl[10];
		struct WL2KInfo * WL2K = TNC->WL2K;

		Debugprintf(Buffer);

		GetSemaphore(&Semaphore, 50);
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		FreeSemaphore(&Semaphore);

		STREAM->ConnectTime = time(NULL); 
		STREAM->bytesRXed = STREAM->bytesTXed = STREAM->PacketsSent = 0;

		if (TNC->StartInRobust)
			send(TNC->TCPSock, "ROBUST TRUE\r\n", 13, 0);

		memcpy(Call, &Buffer[10], 10);

		ptr = strchr(Call, ' ');	
		if (ptr) *ptr = 0;

		TNC->HadConnect = TRUE;

		if (TNC->PortRecord->ATTACHEDSESSIONS[0] == 0)
		{
			TRANSPORTENTRY * SESS;
			
			// Incomming Connect

			TNC->SessionTimeLimit = TNC->DefaultSessionTimeLimit;		// Reset Limit

			// Stop other ports in same group

			SuspendOtherPorts(TNC);

			GetSemaphore(&Semaphore, 50);

			ProcessIncommingConnectEx(TNC, Call, 0, TRUE, TRUE);
			FreeSemaphore(&Semaphore);
				
			SESS = TNC->PortRecord->ATTACHEDSESSIONS[0];
			
			SESS->Mode = TNC->WL2KMode;

			if (TNC->RIG && TNC->RIG != &TNC->DummyRig && strcmp(TNC->RIG->RigName, "PTT"))
			{
				sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound Freq %s", TNC->Streams[0].RemoteCall, TNC->TargetCall, TNC->RIG->Valchar);
				SESS->Frequency = (int)(atof(TNC->RIG->Valchar) * 1000000.0) + 1500;		// Convert to Centre Freq
				SESS->Mode = TNC->WL2KMode;
			}
			else
			{
				sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound", TNC->Streams[0].RemoteCall, TNC->TargetCall);
				if (WL2K)
				{
					SESS->Frequency = WL2K->Freq;
					SESS->Mode = WL2K->mode;
				}
			}
			
			if (WL2K)
				strcpy(SESS->RMSCall, WL2K->RMSCall);

			SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

			// Check for ExcludeList

			if (ExcludeList[0])
			{
				if (CheckExcludeList(SESS->L4USER) == FALSE)
				{
					char Status[64];

					TidyClose(TNC, 0);
					sprintf(Status, "%d SCANSTART 15", TNC->Port);
					Rig_Command( (TRANSPORTENTRY *) -1, Status);
					Debugprintf("WINMOR Call from %s rejected", Call);
					return;
				}
			}

			//	IF WE HAVE A PERMITTED CALLS LIST, SEE IF HE IS IN IT

			if (TNC->PortRecord->PORTCONTROL.PERMITTEDCALLS)
			{
				UCHAR * ptr = TNC->PortRecord->PORTCONTROL.PERMITTEDCALLS;

				while (TRUE)
				{
					if (memcmp(SESS->L4USER, ptr, 6) == 0)	// Ignore SSID
						break;

					ptr += 7;

					if ((*ptr) == 0)							// Not in list
					{
						char Status[64];

						TidyClose(TNC, 0);
						sprintf(Status, "%d SCANSTART 15", TNC->Port);
						Rig_Command( (TRANSPORTENTRY *) -1, Status);
						Debugprintf("WINMOR Call from %s not in ValidCalls - rejected", Call);
						return;
					}
				}
			}

			if (STREAM->BPQtoPACTOR_Q)		//Used for CTEXT
			{
				PMSGWITHLEN buffptr = Q_REM(&STREAM->BPQtoPACTOR_Q);

				send(TNC->TCPDataSock, buffptr->Data, (int)buffptr->Len, 0);
				STREAM->bytesTXed += (int)buffptr->Len;
				WritetoTrace(TNC, buffptr->Data, (int)buffptr->Len);
				ReleaseBuffer(buffptr);
			}
		
			// See which application the connect is for

			for (App = 0; App < 32; App++)
			{
				APPL=&APPLCALLTABLE[App];
				memcpy(Appl, APPL->APPLCALL_TEXT, 10);
				ptr=strchr(Appl, ' ');

				if (ptr)
					*ptr = 0;
	
				if (_stricmp(TNC->TargetCall, Appl) == 0)
					break;
			}

			if (App < 32)
			{
				char AppName[13];

				memcpy(AppName, &ApplPtr[App * sizeof(struct CMDX)], 12);
				AppName[12] = 0;

				// Make sure app is available

				if (CheckAppl(TNC, AppName))
				{
					MsgLen = sprintf(Buffer, "%s\r", AppName);

					GetSemaphore(&Semaphore, 50);

					buffptr = GetBuff();

					if (buffptr == 0)
					{
						FreeSemaphore(&Semaphore);
						return;			// No buffers, so ignore
					}

					buffptr->Len = MsgLen;
					memcpy(buffptr->Data, Buffer, MsgLen);

					C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
		
					FreeSemaphore(&Semaphore);

					TNC->SwallowSignon = TRUE;

					// Save Appl Call in case needed for 

				}
				else
				{
					char Msg[] = "Application not available\r\n";
					
					// Send a Message, then a disconenct
					
					send(TNC->TCPDataSock, Msg, (int)strlen(Msg), 0);
					STREAM->NeedDisc = 100;	// 10 secs
				}
			}

			return;
		}
		else
		{
			// Connect Complete

			char Reply[80];
			int ReplyLen;

			GetSemaphore(&Semaphore, 50);
			
			buffptr = GetBuff();

			if (buffptr == 0)
			{
				FreeSemaphore(&Semaphore);
				return;			// No buffers, so ignore
			}
			ReplyLen = sprintf(Reply, "*** Connected to %s\r", &Buffer[10]);

			buffptr->Len = ReplyLen;
			memcpy(buffptr->Data, Reply, ReplyLen);

			C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);

			FreeSemaphore(&Semaphore);	

			TNC->Streams[0].Connecting = FALSE;
			TNC->Streams[0].Connected = TRUE;			// Subsequent data to data channel


			if (TNC->RIG)
				sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound Freq %s",  TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall, TNC->RIG->Valchar);
			else
				sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall);
			
			SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

			UpdateMH(TNC, Call, '+', 'O');
			return;
		}
	}

	if (_memicmp(Buffer, "DISCONNECTED", 12) == 0)
	{
		Debugprintf(Buffer);

		if (TNC->FECMode)
			return;

		if (TNC->StartSent)
		{
			TNC->StartSent = FALSE;		// Disconnect reported following start codec
			return;
		}

		if (TNC->Streams[0].Connecting)
		{
			// Report Connect Failed, and drop back to command mode

			TNC->Streams[0].Connecting = FALSE;

			GetSemaphore(&Semaphore, 50);

			buffptr = GetBuff();

			if (buffptr == 0)
			{
				FreeSemaphore(&Semaphore);
				return;			// No buffers, so ignore
			}

			buffptr->Len = sprintf(buffptr->Data, "Winmor} Failure with %s\r", TNC->Streams[0].RemoteCall);

			C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);

			FreeSemaphore(&Semaphore);

			if (TNC->RestartAfterFailure)
			{
				if (TNC->PID)
				{
					KillTNC(TNC);
					RestartTNC(TNC);
				}
			}

			return;
		}


		// Release Session

		if (TNC->Streams[0].Connected)
		{
			hookL4SessionDeleted(TNC, STREAM);

			GetSemaphore(&Semaphore, 50);
			WritetoTrace(TNC, Buffer, MsgLen - 2);
			FreeSemaphore(&Semaphore);
		}


		TNC->Streams[0].Connecting = FALSE;
		TNC->Streams[0].Connected = FALSE;		// Back to Command Mode
		TNC->Streams[0].ReportDISC = TRUE;		// Tell Node

		if (TNC->Streams[0].Disconnecting)		// 
			ReleaseTNC(TNC);

		TNC->Streams[0].Disconnecting = FALSE;

		return;
	}

	if (_memicmp(Buffer, "MONCALL", 7) == 0)
	{
		Debugprintf(Buffer);

		// Add to MHEARD

		GetSemaphore(&Semaphore, 50);
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		FreeSemaphore(&Semaphore);
		UpdateMH(TNC, &Buffer[8], '!', 0);
		
		if (!TNC->FECMode)
			return;							// If in FEC mode pass ID messages to user.
	}
		
	if (_memicmp(Buffer, "CMD", 3) == 0)
	{
		return;
	}

	if (_memicmp(Buffer, "BUFFERS", 7) == 0)
	{
		int inq, inrx, Sent, BPM;

		sscanf(&Buffer[8], "%d%d%d%d%d", &inq, &inrx, &TNC->Streams[0].BytesOutstanding, &Sent, &BPM);

		if (TNC->Streams[0].BytesOutstanding == 0)
		{
			// all sent
			
			if (TNC->Streams[0].Disconnecting)						// Disconnect when all sent
			{
				if (STREAM->NeedDisc == 0)
					STREAM->NeedDisc = 60;								// 6 secs
			}
//			else
//			if (TNC->TXRXState == 'S')
//				send(TNC->TCPSock,"OVER\r\n", 6, 0);

		}
		else
		{
			// Make sure Node Keepalive doesn't kill session.

			TRANSPORTENTRY * SESS = TNC->PortRecord->ATTACHEDSESSIONS[0];

			if (SESS)
			{
				SESS->L4KILLTIMER = 0;
				SESS = SESS->L4CROSSLINK;
				if (SESS)
					SESS->L4KILLTIMER = 0;
			}
		}

		SetWindowText(TNC->xIDC_TRAFFIC, &Buffer[8]);
		strcpy(TNC->WEB_TRAFFIC, &Buffer[8]);
		return;
	}

	Debugprintf(Buffer);

	if (_memicmp(Buffer, "MODE", 4) == 0)
	{
	//	Debugprintf("WINMOR RX: %s", Buffer);

		strcpy(TNC->WEB_MODE, &Buffer[5]);
		GetSemaphore(&Semaphore, 50);
		MySetWindowText(TNC->xIDC_MODE, &Buffer[5]);
		FreeSemaphore(&Semaphore);
		return;
	}

	if (_memicmp(Buffer, "PENDING", 6) == 0)
		return;

	if (_memicmp(Buffer, "FAULT", 5) == 0)
	{
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		return;
	}

	if (_memicmp(Buffer, "NEWSTATE", 8) == 0)
	{
		TNC->WinmorRestartCodecTimer = time(NULL);

		SetWindowText(TNC->xIDC_PROTOSTATE, &Buffer[9]);
		strcpy(TNC->WEB_PROTOSTATE,  &Buffer[9]);

		if (_memicmp(&Buffer[9], "CONNECTPENDING", 14) == 0)	// Save Pending state for scan control
			TNC->ConnectPending = TRUE;
		else
			TNC->ConnectPending = FALSE;
	
		if (_memicmp(&Buffer[9], "DISCONNECTING", 13) == 0)	// So we can timout stuck discpending
		{
			TNC->DiscPending = 600;
			return;
		}
		if (_memicmp(&Buffer[9], "DISCONNECTED", 12) == 0)
		{
			TNC->DiscPending = FALSE;
			return;
		}

		if (strcmp(&Buffer[9], "ISS") == 0)	// Save Pending state for scan control
			TNC->TXRXState = 'S';
		else if (strcmp(&Buffer[9], "IRS") == 0)
			TNC->TXRXState = 'R';
	
		return;
	}


	if (_memicmp(Buffer, "PROCESSID", 9) == 0)
	{
		HANDLE hProc;
		char ExeName[256] = "";

		TNC->PID = atoi(&Buffer[10]);

#ifdef WIN32

		// Get the File Name in case we want to restart it.

		if (TNC->ProgramPath == NULL)
		{
			if (GetModuleFileNameExPtr)
			{
				hProc =  OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, TNC->PID);
	
				if (hProc)
				{
					GetModuleFileNameExPtr(hProc, 0,  ExeName, 255);
					CloseHandle(hProc);

					TNC->ProgramPath = _strdup(ExeName);
				}
			}
		}

		// Set Window Title to reflect BPQ Port Description

		EnumWindows(EnumTNCWindowsProc, (LPARAM)TNC);
#endif
	}

	if ((_memicmp(Buffer, "FAULT Not from state FEC", 24) == 0) || (_memicmp(Buffer, "FAULT Blocked by Busy Lock", 24) == 0))
	{
		if (TNC->FECMode)
		{
			Sleep(1000);
			
			if (TNC->FEC1600)
				send(TNC->TCPSock,"FECSEND 1600\r\n", 14, 0);
			else
				send(TNC->TCPSock,"FECSEND 500\r\n", 13, 0);
			return;
		}
	}

	if (_memicmp(Buffer, "PLAYBACKDEVICES", 15) == 0)
	{
		TNC->PlaybackDevices = _strdup(&Buffer[16]);
	}
	// Others should be responses to commands

	if (_memicmp(Buffer, "BLOCKED", 6) == 0)
	{
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		return;
	}

	if (_memicmp(Buffer, "OVER", 4) == 0)
	{
		WritetoTrace(TNC, Buffer, MsgLen - 2);
		return;
	}

	GetSemaphore(&Semaphore, 50);

	buffptr = GetBuff();

	if (buffptr == 0)
	{
		FreeSemaphore(&Semaphore);
		return;			// No buffers, so ignore
	}
	
	buffptr->Len = sprintf(buffptr->Data, "Winmor} %s\r", Buffer);

	C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);
	FreeSemaphore(&Semaphore);
}

static int ProcessReceivedData(struct TNCINFO * TNC)
{
	int InputLen, MsgLen;
	char * ptr, * ptr2;
	char Buffer[2000];

	// May have several messages per packet, or message split over packets

	if (TNC->InputLen > 1000)	// Shouldnt have lines longer  than this on command connection
		TNC->InputLen=0;
				
	InputLen=recv(TNC->TCPSock, &TNC->TCPBuffer[TNC->InputLen], 1000 - TNC->InputLen, 0);

	if (InputLen == 0 || InputLen == SOCKET_ERROR)
	{
		// Does this mean closed?
		
		closesocket(TNC->TCPSock);
		closesocket(TNC->TCPDataSock);

		TNC->TCPSock = 0;
		TNC->TCPDataSock = 0;

		TNC->CONNECTED = FALSE;
		TNC->Streams[0].ReportDISC = TRUE;

		return 0;					
	}

	TNC->InputLen += InputLen;

loop:
	
	ptr = memchr(TNC->TCPBuffer, '\n', TNC->InputLen);

	if (ptr)	//  CR in buffer
	{
		ptr2 = &TNC->TCPBuffer[TNC->InputLen];
		ptr++;				// Assume LF Follows CR

		if (ptr == ptr2)
		{
			// Usual Case - single meg in buffer
	
			ProcessResponse(TNC, TNC->TCPBuffer, TNC->InputLen);
			TNC->InputLen=0;
		}
		else
		{
			// buffer contains more that 1 message

			MsgLen = TNC->InputLen - (int)(ptr2-ptr);

			memcpy(Buffer, TNC->TCPBuffer, MsgLen);

			ProcessResponse(TNC, Buffer, MsgLen);
			memmove(TNC->TCPBuffer, ptr, TNC->InputLen-MsgLen);

			TNC->InputLen -= MsgLen;
			goto loop;
		}
	}
	return 0;
}


VOID ProcessDataSocketData(int port)
{
	// Info on Data Socket - just packetize and send on
	
	struct TNCINFO * TNC = TNCInfo[port];
	struct STREAMINFO * STREAM = &TNC->Streams[0];

	int InputLen, PacLen = 236;
	PMSGWITHLEN buffptr;
	char * msg;
		
	TNC->TimeSinceLast = 0;

loop:
	buffptr = GetBuff();

	if (buffptr == NULL) return;			// No buffers, so ignore
			
	InputLen = recv(TNC->TCPDataSock, buffptr->Data, PacLen, 0);

	if (InputLen == -1)
	{
		ReleaseBuffer(buffptr);
		return;
	}


	//Debugprintf("Winmor: RXD %d bytes", InputLen);

	if (InputLen == 0)
	{
		// Does this mean closed?
		
		sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
		MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);
	
		TNC->CONNECTING = FALSE;
		TNC->CONNECTED = FALSE;
		TNC->Streams[0].ReportDISC = TRUE;

		ReleaseBuffer(buffptr);
		return;					
	}

	STREAM->bytesRXed += InputLen;

	msg = &buffptr->Data[0];
	msg[InputLen] = 0;	
	
	WritetoTrace(TNC, msg, InputLen);
		
	if (TNC->FECMode)
	{	
		InputLen = (int)strlen(&buffptr->Data[0]);

		if (msg[InputLen - 1] == 3)		// End of errored block
			msg[InputLen++] = 13;		// Add CR

	}
	buffptr->Len = InputLen;
	C_Q_ADD(&TNC->WINMORtoBPQ_Q, buffptr);

	goto loop;
}

/*
INT_PTR CALLBACK ConfigDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	int Cmd = LOWORD(wParam);

	switch (message)
	{
	case WM_INITDIALOG:
	{
		struct TNCINFO * TNC = (struct TNCINFO * )lParam;
		char * ptr1, *ptr2;
		int ptr3 = 0;
		char Line[1000];
		int len;

		ptr1 = TNC->CaptureDevices;

		if (!ptr1)
			return 0;				// No Devices


		while (ptr2 = strchr(ptr1, ','))
		{
			len = ptr2 - ptr1;
			memcpy(&Line[ptr3], ptr1, len);
			ptr3 += len;
			Line[ptr3++] = '\r';
			Line[ptr3++] = '\n';

			ptr1 = ++ptr2;
		}
		Line[ptr3] = 0;
		strcat(Line, ptr1);
	
		SetDlgItemText(hDlg, IDC_CAPTURE, Line);

		ptr3 = 0;

		ptr1 = TNC->PlaybackDevices;
	
		if (!ptr1)
			return 0;				// No Devices


		while (ptr2 = strchr(ptr1, ','))
		{
			len = ptr2 - ptr1;
			memcpy(&Line[ptr3], ptr1, len);
			ptr3 += len;
			Line[ptr3++] = '\r';
			Line[ptr3++] = '\n';

			ptr1 = ++ptr2;
		}
		Line[ptr3] = 0;
		strcat(Line, ptr1);
	
		SetDlgItemText(hDlg, IDC_PLAYBACK, Line);

		SendDlgItemMessage(hDlg, IDC_PLAYBACK, EM_SETSEL, -1, 0);

//		KillTNC(TNC);

		return TRUE; 
	}

	case WM_SIZING:
	{
		return TRUE;
	}

	case WM_ACTIVATE:

//		SendDlgItemMessage(hDlg, IDC_MESSAGE, EM_SETSEL, -1, 0);

		break;


	case WM_COMMAND:


		if (Cmd == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}

		return (INT_PTR)TRUE;

		break;
	}
	return (INT_PTR)FALSE;
}
*/

#ifdef LINBPQ
#include <signal.h>
#include <sys/types.h>
#endif


int KillTNC(struct TNCINFO * TNC)
{
	if (TNC->ProgramPath && _memicmp(TNC->ProgramPath, "REMOTE:", 7) == 0)
	{
		// Try to Kill TNC on a remote host

		SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
		struct sockaddr_in destaddr;
		char Msg[256];
		int Len;

		if (sock == INVALID_SOCKET)
			return 0;

		destaddr.sin_family = AF_INET;
		destaddr.sin_addr.s_addr = inet_addr(TNC->HostName);
		destaddr.sin_port = htons(8500);

		if (destaddr.sin_addr.s_addr == INADDR_NONE)
		{
			//	Resolve name to address

			struct hostent * HostEnt = gethostbyname (TNC->HostName);

			if (!HostEnt)
				return 0;			// Resolve failed

			memcpy(&destaddr.sin_addr.s_addr,HostEnt->h_addr,4);
		}

		if (TNC->PID)
			Len = sprintf(Msg, "KILL %d", TNC->PID);
		else
			Len = sprintf(Msg, "KILLBYNAME %s", &TNC->ProgramPath[7]);

		sendto(sock, Msg, Len, 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
		Sleep(100);
		closesocket(sock);

		TNC->PID = 0;			// So we don't try again
		return 1;				// Cant tell if it worked, but assume ok
	}

	if (TNC->PID == 0)
		return 0;

#ifdef WIN32
	{
		HANDLE hProc;

		Debugprintf("KillTNC Called for Pid %d", TNC->PID);

		if (TNC->PTTMode)
			Rig_PTT(TNC, FALSE);			// Make sure PTT is down

		hProc =  OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, TNC->PID);

		if (hProc)
		{
			TerminateProcess(hProc, 0);
			CloseHandle(hProc);
		}
	}
#else

	printf("KillTNC Called for Pid %d Returned %d\n", TNC->PID, kill(TNC->PID, SIGTERM));

#endif
	TNC->PID = 0;			// So we don't try again

	return 0;
}

BOOL RestartTNC(struct TNCINFO * TNC)
{
	if (TNC->ProgramPath == NULL || TNC->DontRestart)
		return 0;

	if (_memicmp(TNC->ProgramPath, "REMOTE:", 7) == 0)
	{
		int n;
		
		// Try to start TNC on a remote host

		SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
		struct sockaddr_in destaddr;

		Debugprintf("trying to restart TNC %s", TNC->ProgramPath);

		if (sock == INVALID_SOCKET)
			return 0;

		destaddr.sin_family = AF_INET;
		destaddr.sin_addr.s_addr = inet_addr(TNC->HostName);
		destaddr.sin_port = htons(8500);

		if (destaddr.sin_addr.s_addr == INADDR_NONE)
		{
			//	Resolve name to address

			struct hostent * HostEnt = gethostbyname (TNC->HostName);
		 
			if (!HostEnt)
				return 0;			// Resolve failed

			memcpy(&destaddr.sin_addr.s_addr,HostEnt->h_addr,4);
		}

		n = sendto(sock, TNC->ProgramPath, (int)strlen(TNC->ProgramPath), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
	
		Debugprintf("Restart TNC - sendto returned %d", n);

		Sleep(100);
		closesocket(sock);

		return 1;				// Cant tell if it worked, but assume ok
	}

	// Not Remote

	// Extract any parameters from command string

#ifndef WIN32
	{
		char * arg_list[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
		pid_t child_pid;
		char * Copy, * Context;
		signal(SIGCHLD, SIG_IGN); // Silently (and portably) reap children. 

		Copy = _strdup(TNC->ProgramPath);	// Save as strtok mangles it

		arg_list[0] = strtok_s(Copy, " \n\r", &Context);
		if (arg_list[0])
			arg_list[1] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[1])
			arg_list[2] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[2])
			arg_list[3] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[3])
			arg_list[4] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[4])
			arg_list[5] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[5])
			arg_list[6] = strtok_s(NULL, " \n\r", &Context);
		if (arg_list[6])
			arg_list[7] = strtok_s(NULL, " \n\r", &Context);

		//	Fork and Exec TNC

		printf("Trying to start %s\n", TNC->ProgramPath);

		/* Duplicate this process. */ 

		child_pid = fork (); 

		if (child_pid == -1) 
 		{    				
			printf ("StartTNC fork() Failed\n"); 
			free(Copy);
			return 0;
		}

		if (child_pid == 0) 
 		{    				
			execvp (arg_list[0], arg_list); 
        
			/* The execvp  function returns only if an error occurs.  */ 

			printf ("Failed to start TNC\n"); 
			exit(0);			// Kill the new process
		}
		else
		{
			TNC->PID = child_pid;
			printf("Started TNC, Process ID = %d\n", TNC->PID);
		}
		free(Copy);
		return TRUE;
	}								 
#else
	{
		int n = 0;
		
		STARTUPINFO  SInfo;			// pointer to STARTUPINFO 
	    PROCESS_INFORMATION PInfo; 	// pointer to PROCESS_INFORMATION 
		char workingDirectory[256];
		int i = strlen(TNC->ProgramPath);

		SInfo.cb=sizeof(SInfo);
		SInfo.lpReserved=NULL; 
		SInfo.lpDesktop=NULL; 
		SInfo.lpTitle=NULL; 
		SInfo.dwFlags=0; 
		SInfo.cbReserved2=0; 
	  	SInfo.lpReserved2=NULL; 

		Debugprintf("RestartTNC Called for %s", TNC->ProgramPath);

		strcpy(workingDirectory, TNC->ProgramPath);

		while (i--)
		{
			if (workingDirectory[i] == '\\' || workingDirectory[i] == '/')
			{
				workingDirectory[i] = 0; 
				break;
			}
		}

		while (KillOldTNC(TNC->ProgramPath) && n++ < 100)
		{
			Sleep(100);
		}

		if (CreateProcess(NULL, TNC->ProgramPath, NULL, NULL, FALSE,0, NULL, workingDirectory, &SInfo, &PInfo))
		{
			Debugprintf("Restart TNC OK");
			TNC->PID = PInfo.dwProcessId;
			return TRUE;
		}
		else
		{
			Debugprintf("Restart TNC Failed %d ", GetLastError());
			return FALSE;
		}
	}
#endif
	return 0;
}

VOID TidyClose(struct TNCINFO * TNC, int Stream)
{
	// If all acked, send disc
	
	if (TNC->Streams[0].BytesOutstanding == 0)
		send(TNC->TCPSock,"DISCONNECT\r\n", 12, 0);
}

VOID ForcedClose(struct TNCINFO * TNC, int Stream)
{
	send(TNC->TCPSock,"DIRTYDISCONNECT\r\n", 17, 0);
}

VOID CloseComplete(struct TNCINFO * TNC, int Stream)
{
	ReleaseTNC(TNC);

	if (TNC->FECMode)
	{
		TNC->FECMode = FALSE;
		send(TNC->TCPSock,"SENDID 0\r\n", 10, 0);
	}
}

BOOL KillOldTNC(char * Path)
{
#ifdef WIN32
	HANDLE hProc;
	char ExeName[256] = "";
	DWORD Pid = 0;

    DWORD Processes[1024], Needed, Count;
    unsigned int i;

	if (EnumProcessesPtr == NULL)
		return FALSE;

    if (!EnumProcessesPtr(Processes, sizeof(Processes), &Needed))
        return FALSE;
    
    // Calculate how many process identifiers were returned.

    Count = Needed / sizeof(DWORD);

    for (i = 0; i < Count; i++)
    {
        if (Processes[i] != 0)
        {
			hProc =  OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, Processes[i]);
	
			if (hProc)
			{
				GetModuleFileNameExPtr(hProc, 0,  ExeName, 255);

				// Path could have parameters, so use memcmp

				if (_memicmp(ExeName, Path, strlen(ExeName)) == 0)
				{
					Debugprintf("Killing Pid %d %s", Processes[i], ExeName);
					TerminateProcess(hProc, 0);
					CloseHandle(hProc);
					return TRUE;
				}
				CloseHandle(hProc);
			}
		}
	}
#endif
	return FALSE;
}
