/*
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 AGWPE as a Port Driver 
//	32bit environment,
//
//	Uses BPQ EXTERNAL interface
//


//  Version 1.0 January 2005 - Initial Version
//

//  Version 1.1	August 2005
//
//		Treat NULL string in Registry as use current directory

//	Version 1.2 January 2006

//		Support multiple commections (not quire yet!)
//		Fix memory leak when AGEPE not running


//	Version 1.3 March 2006

//		Support multiple connections

//	Version 1.4 October 1006

//		Write diagmnostics to BPQ console window instead of STDOUT

//	Version 1.5 February 2008

//		Changes for dynamic unload of bpq32.dll

//	Version 1.5.1 September 2010

//		Add option to get config from BPQ32.cfg

#define _CRT_SECURE_NO_DEPRECATE

#include "cheaders.h"
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "bpq32.h"

#ifndef LINBPQ
#include "kernelresource.h"
#include <process.h>
#endif

#include <time.h>


#define VERSION_MAJOR         2
#define VERSION_MINOR         0

//uintptr_t _beginthread(void( *start_address )( int ), unsigned stack_size, int arglist);

//int ResetExtDriver(int num);

void ConnecttoAGWThread(void * portptr);

VOID __cdecl Consoleprintf(const char * format, ...);

void CreateMHWindow();
int Update_MH_List(struct in_addr ipad, char * call, char proto);

static BOOL ReadConfigFile(int Port);
int ConnecttoAGW(int port);
int ProcessReceivedData(int bpqport);
static int ProcessLine(char * buf, int Port, BOOL CheckPort);


extern UCHAR BPQDirectory[];

#pragma pack(1)

static struct AGWHEADER
{
	byte Port;
	byte filler1[3];
	char DataKind;
	byte filler2;
	unsigned char PID;
	byte filler3;
	unsigned char callfrom[10];
	unsigned char callto[10];
	int DataLength;
	int reserved;

} AGWHeader;

static struct AGWHEADER RXHeader;


#pragma pack()

#define MAXAGWPORTS 16

//LOGFONT LFTTYFONT ;

//HFONT hFont ;

static int AGWChannel[MAXBPQPORTS+1];			// BPQ Port to AGW Port
static int BPQPort[MAXAGWPORTS][MAXBPQPORTS+1];	// AGW Port and Connection to BPQ Port
static void * AGWtoBPQ_Q[MAXBPQPORTS+1];			// Frames for BPQ, indexed by BPQ Port
static void * BPQtoAGW_Q[MAXBPQPORTS+1];			// Frames for AGW. indexed by AGW port. Only used it TCP session is blocked

//	Each port may be on a different machine. We only open one connection to each AGW instance

static SOCKET AGWSock[MAXBPQPORTS+1];			// Socket, indexed by BPQ Port

BOOL Alerted[MAXBPQPORTS+1];					// Error msg sent

static int MasterPort[MAXBPQPORTS+1];			// Pointer to first BPQ port for a specific AGW host

static char * AGWSignon[MAXBPQPORTS+1];			// Pointer to message for secure signin

static char * AGWHostName[MAXBPQPORTS+1];		// AGW Host - may be dotted decimal or DNS Name


static unsigned int AGWInst = 0;
static int AttachedProcesses=0;

static HWND hResWnd,hMHWnd;
static BOOL GotMsg;

static HANDLE STDOUT=0;

//SOCKET sock;

static SOCKADDR_IN sinx; 
static SOCKADDR_IN rxaddr;
static SOCKADDR_IN destaddr[MAXBPQPORTS+1];

static int addrlen=sizeof(sinx);

//static short AGWPort=0;

static time_t ltime,lasttime[MAXBPQPORTS+1];

static BOOL CONNECTED[MAXBPQPORTS+1];
static BOOL CONNECTING[MAXBPQPORTS+1];

//HANDLE hInstance;


static fd_set readfs;
static fd_set writefs;
static fd_set errorfs;
static struct timeval timeout;


static size_t ExtProc(int fn, int port, PMESSAGE buff)
{
	int i,winerr;
	int datalen;
	PMSGWITHLEN buffptr;
	char txbuff[500];
	unsigned int bytes,txlen=0;
	char ErrMsg[255];

	switch (fn)
	{
	case 1:				// poll

		if (MasterPort[port] == port)
		{
			SOCKET sock = AGWSock[port];

			// Only on first port using a host

			if (CONNECTED[port] == FALSE && CONNECTING[port] == FALSE)
			{
				//	See if time to reconnect
		
				time( &ltime );
				if (ltime-lasttime[port] > 9 )
				{
					ConnecttoAGW(port);
					lasttime[port]=ltime;
				}
			}
		
			FD_ZERO(&readfs);
			
			if (CONNECTED[port]) FD_SET(sock,&readfs);
	
			FD_ZERO(&writefs);

			if (BPQtoAGW_Q[port]) FD_SET(sock,&writefs);	// Need notification of busy clearing

			FD_ZERO(&errorfs);
		
			if (CONNECTED[port]) FD_SET(sock,&errorfs);

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

				if (FD_ISSET(sock, &readfs))
				{
			
					// data available
			
					ProcessReceivedData(port);
				
				}

				if (FD_ISSET(sock, &writefs))
				{
					if (BPQtoAGW_Q[port] == 0)
					{
					}
					else
					{
						// Write block has cleared. Send rest of packet

						buffptr = Q_REM(&BPQtoAGW_Q[port]);

						txlen = buffptr->Len;

						memcpy(txbuff, buffptr->Data, txlen);

						bytes = send(AGWSock[port], (const char FAR *)&txbuff, txlen, 0);

						ReleaseBuffer(buffptr);
					}
				}
					
				if (FD_ISSET(sock, &errorfs))
				{
					sprintf(ErrMsg, "AGW Connection lost for BPQ Port %d\n", port);
					Alerted[port] = FALSE;
					WritetoConsole(ErrMsg);

					CONNECTED[port]=FALSE;
				}
			}
		}

		// See if any frames for this port

		if (AGWtoBPQ_Q[port] !=0)
		{
			buffptr=Q_REM(&AGWtoBPQ_Q[port]);

			datalen = buffptr->Len - 1;

			memcpy(buff->DEST, &buffptr->Data[1] , datalen);		// Data goes to +7, but we have an extra byte
			datalen += MSGHDDRLEN;
			buff->LENGTH = datalen;

			ReleaseBuffer(buffptr);

			return (1);

		}

		return (0);



	case 2:				// send

		
		if (!CONNECTED[MasterPort[port]]) return 0;		// Don't try if not connected

		if (BPQtoAGW_Q[MasterPort[port]]) return 0;		// Socket is blocked - just drop packets
														// till it clears

		// AGW has a control byte on front, so only subtract 6 from BPQ length

		txlen = GetLengthfromBuffer((PDATAMESSAGE)buff);
		txlen -= (MSGHDDRLEN - 1);
		
		AGWHeader.Port=AGWChannel[port];
		AGWHeader.DataKind='K';				// raw send

#ifdef __BIG_ENDIAN__
		AGWHeader.DataLength = reverse(txlen);
#else
		AGWHeader.DataLength = txlen;
#endif
		memcpy(&txbuff,&AGWHeader,sizeof(AGWHeader));
		memcpy(&txbuff[sizeof(AGWHeader) + 1], &buff->DEST, txlen);
		txbuff[sizeof(AGWHeader)]=0;
		
		txlen+=sizeof(AGWHeader);

		bytes=send(AGWSock[MasterPort[port]],(const char FAR *)&txbuff, txlen, 0);
		
		if (bytes != txlen)
		{

			// AGW doesn't seem to recover from a blocked write. For now just reset
			
			winerr = WSAGetLastError();
				
			i = sprintf(ErrMsg, "AGW Write Failed for port %d - error code = %d\n", port, winerr);
			WritetoConsole(ErrMsg);
					
			closesocket(AGWSock[MasterPort[port]]);		
			CONNECTED[MasterPort[port]] = FALSE;
			return (0);
		}
		return (0);

	
	case 3:				// CHECK IF OK TO SEND

		return (0);		// OK
			
		break;

	case 4:				// reinit

//		return(ReadConfigFile("BPQAXIP.CFG"));

		return (0);
	}
	return 0;
}

void * AGWExtInit(struct PORTCONTROL *  PortEntry)

{
	int i, port;
	char Msg[255];
	
	//
	//	Will be called once for each AGW port to be mapped to a BPQ Port
	//	The AGW port number is in CHANNEL - A=0, B=1 etc
	//
	//	The Socket to connect to is in IOBASE
	//

	port=PortEntry->PORTNUMBER;

	ReadConfigFile(port);

	AGWChannel[port]=PortEntry->CHANNELNUM-65;

	if (destaddr[port].sin_family == 0)
	{
		// not defined in config file

		destaddr[port].sin_family = AF_INET;
		destaddr[port].sin_port = htons(PortEntry->IOBASE);

		AGWHostName[port]=malloc(10);

		if (AGWHostName[port] != NULL) 
			strcpy(AGWHostName[port],"127.0.0.1");

	}

	i=sprintf(Msg,"AGW Port %d Host %s %d\n",AGWChannel[port]+1,AGWHostName[port],htons(destaddr[port].sin_port));
	WritetoConsole(Msg);

	// See if we already have a port for this host
	

	MasterPort[port]=port;

	for (i=1;i<port;i++)
	{
		if (i == port) continue;

		if (destaddr[i].sin_port == destaddr[port].sin_port &&
			 _stricmp(AGWHostName[i],AGWHostName[port]) == 0)
		{
			MasterPort[port]=i;
			break;
		}
	}

	Alerted[port] = FALSE;


	BPQPort[PortEntry->CHANNELNUM-65][MasterPort[port]]=PortEntry->PORTNUMBER;
			
	if (MasterPort[port] == port)
		ConnecttoAGW(port);

	time(&lasttime[port]);			// Get initial time value

	
	return ExtProc;

}

/*

#	Config file for BPQtoAGW
#
#	For each AGW port defined in BPQCFG.TXT, Add a line here
#	Format is BPQ Port, Host/IP Address, Port

#
#	Any unspecified Ports will use 127.0.0.1 and port for BPQCFG.TXT IOADDR field
#

1 127.0.0.1 8000
2 127.0.0.1 8001

*/


BOOL ReadConfigFile(int Port)
{
	char buf[256],errbuf[256];
	char * Config;

	Config = PortConfig[Port];

	if (Config)
	{
		// Using config from bpq32.cfg

		char * ptr1 = Config, * ptr2;

		ptr2 = strchr(ptr1, 13);
		while(ptr2)
		{
			memcpy(buf, ptr1, ptr2 - ptr1);
			buf[ptr2 - ptr1] = 0;
			ptr1 = ptr2 + 2;
			ptr2 = strchr(ptr1, 13);

			strcpy(errbuf,buf);			// save in case of error
	
			if (!ProcessLine(buf, Port, FALSE))
			{
				WritetoConsole("BPQtoAGW - Bad config record ");
				Consoleprintf(errbuf);
			}
		}
		return (TRUE);
	}

	return (TRUE);
}

static int ProcessLine(char * buf, int Port, BOOL CheckPort)
{
	char * ptr,* p_user,* p_password;
	char * p_ipad;
	char * p_udpport;
	unsigned short AGWport;
	int BPQport;
	int len=510;

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

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

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

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

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

		BPQport = atoi(ptr);

		if (Port != BPQport) return TRUE;		// Not for us
	}
	else
	{
		BPQport = Port;
		p_ipad = ptr;
	}
	if(BPQport > 0 && BPQport < MAXBPQPORTS)
	{	
		p_udpport = strtok(NULL, " \t\n\r");
			
		if (p_udpport == NULL) return (FALSE);

		AGWport = atoi(p_udpport);

		destaddr[BPQport].sin_family = AF_INET;
		destaddr[BPQport].sin_port = htons(AGWport);

		AGWHostName[BPQport]=malloc(strlen(p_ipad)+1);

		if (AGWHostName[BPQport] == NULL) return TRUE;

		strcpy(AGWHostName[BPQport],p_ipad);

		p_user = strtok(NULL, " \t\n\r");
			
		if (p_user == NULL) return (TRUE);

		p_password = strtok(NULL, " \t\n\r");
			
		if (p_password == NULL) return (TRUE);

		// Allocate buffer for signon message

		AGWSignon[BPQport]=malloc(546);

		if (AGWSignon[BPQport] == NULL) return TRUE;

		memset(AGWSignon[BPQport],0,546);

		AGWSignon[BPQport][4]='P';

		memcpy(&AGWSignon[BPQport][28],&len,4);

		strcpy(&AGWSignon[BPQport][36],p_user);

		strcpy(&AGWSignon[BPQport][291],p_password);

		return (TRUE);
	}

	//
	//	Bad line
	//
	return (FALSE);
	
}
	
int ConnecttoAGW(int port)
{
	_beginthread(ConnecttoAGWThread, 0, (void *)(size_t)port);

	return 0;
}

VOID ConnecttoAGWThread(void * portptr)
{
	int port = (int)(size_t)portptr;
	char Msg[255];
	int err,i;
	u_long param=1;
	BOOL bcopt=TRUE;
	struct hostent * HostEnt;

	//	Only called for the first BPQ port for a particular host/port combination

	destaddr[port].sin_addr.s_addr = inet_addr(AGWHostName[port]);

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

		 HostEnt = gethostbyname (AGWHostName[port]);
		 
		 if (!HostEnt) return;			// Resolve failed

		 memcpy(&destaddr[port].sin_addr.s_addr,HostEnt->h_addr,4);

	}

	AGWSock[port]=socket(AF_INET,SOCK_STREAM,0);

	if (AGWSock[port] == INVALID_SOCKET)
	{
		i=sprintf(Msg, "Socket Failed for AGW socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

  	 	return; 
	}
 
	setsockopt (AGWSock[port],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(AGWSock[port], (LPSOCKADDR) &sinx, addrlen) != 0 )
	{
		//
		//	Bind Failed
		//
	
		i=sprintf(Msg, "Bind Failed for AGW socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

		closesocket(AGWSock[port]);
  	 	return; 
	}

	CONNECTING[port] = TRUE;

	if (connect(AGWSock[port],(LPSOCKADDR) &destaddr[port],sizeof(destaddr[port])) == 0)
	{
		//
		//	Connected successful
		//

		CONNECTED[port] = TRUE;
		CONNECTING[port] = FALSE;

		ioctlsocket (AGWSock[port],FIONBIO,&param);

		// If required, send signon

		if (AGWSignon[port])
			send(AGWSock[port],AGWSignon[port],546,0);

		// Request Raw Frames

		AGWHeader.Port=0;
		AGWHeader.DataKind='k';
		AGWHeader.DataLength=0;

		send(AGWSock[port],(const char FAR *)&AGWHeader,sizeof(AGWHeader),0);

		return;
	}
	else
	{
		err=WSAGetLastError();

		//
		//	Connect failed
		//

		if (Alerted[port] == FALSE)
		{
			sprintf(Msg, "Connect Failed for AGW Port %d - error code = %d\n", port, err);
		    WritetoConsole(Msg);
			Alerted[port] = TRUE;
		}

		closesocket(AGWSock[port]);
		CONNECTING[port] = FALSE;

		return;
	}
}

int ProcessReceivedData(int port)
{
	unsigned int bytes;
	int datalen,i;
	char ErrMsg[255];
	char Message[500];
	PMSGWITHLEN buffptr;

	//	Need to extract messages from byte stream

	//	Use MSG_PEEK to ensure whole message is available

	bytes = recv(AGWSock[port],(char *)&RXHeader,sizeof(RXHeader),MSG_PEEK);

	if (bytes == SOCKET_ERROR)
	{
		i=sprintf(ErrMsg, "Read Failed for AGW socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(ErrMsg);
				
		closesocket(AGWSock[port]);
					
		CONNECTED[port]=FALSE;

		return (0);
	}

	if (bytes == 0)
	{
		//	zero bytes means connection closed

		i=sprintf(ErrMsg, "AGW Connection closed for BPQ Port %d\r\n", port);
		WritetoConsole(ErrMsg);


		CONNECTED[port]=FALSE;
		return (0);
	}

	//	Have some data
	
	if (bytes == sizeof(RXHeader))
	{
		//	Have a header - see if we have any associated data
		
		datalen=RXHeader.DataLength;

		#ifdef __BIG_ENDIAN__
		datalen = reverse(datalen);
		#endif

		if (datalen > 0)
		{
			// Need data - See if enough there
			
			bytes = recv(AGWSock[port],(char *)&Message,sizeof(RXHeader)+datalen,MSG_PEEK);
		}

		if (bytes == sizeof(RXHeader)+datalen)
		{
			bytes = recv(AGWSock[port],(char *)&RXHeader,sizeof(RXHeader),0);

			if (datalen > 0)
			{
				bytes = recv(AGWSock[port],(char *)&Message,datalen,0);
			}

			// Have header, and data if needed

			// Only use frame type K

			if (RXHeader.DataKind == 'K')				// raw data
			{
				//	Make sure it is for a port we want - we may not be using all AGW ports

				if (BPQPort[RXHeader.Port][MasterPort[port]] == 0)
					return (0);

				// Get a buffer

				buffptr = GetBuff();

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

				buffptr->Len = datalen;
				memcpy(buffptr->Data, &Message, datalen);

				C_Q_ADD(&AGWtoBPQ_Q[BPQPort[RXHeader.Port][MasterPort[port]]], buffptr);
			}

			return (0);
		}

		// Have header, but not sufficient data

		return (0);
	}

	// Dont have at least header bytes
	
	return (0);
}
