From f9898cf633d93f210e485e2a3958ce9baebb4fc7 Mon Sep 17 00:00:00 2001 From: g8bpq Date: Sun, 2 Feb 2025 09:49:48 +0000 Subject: [PATCH] 6.0.24.59 --- 6pack.c | 2 +- AEAPactor.c | 2 +- AGWAPI.c | 2 +- AGWMoncode.c | 2 +- AISCommon.c | 2 +- APRSCode.c | 2 +- ARDOP.c | 27 +- BBSHTMLConfig.c | 19 +- BBSUtilities.c | 60 +- BPQChat.vcproj.NOTTSDESKTOP.John.user | 65 + BPQINP3.c | 2 +- BPQMail.c | 7 +- BPQNRR.c | 2 +- BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + BPQtoAGW.c | 2 +- Bpq32-skigdebian.c | 6741 ++++++++++++++++ Bpq32.c | 19 +- CBPQ32.vcproj | 6 +- CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + CBPQ32.vcproj.NOTTSDESKTOP.John.user | 2 +- CBPQ32.vcproj.SKIGACER.johnw.user | 65 + CHeaders.h | 19 +- CMSAuth.c | 2 +- ChatUtilities.c | 6 +- Cmd.c | 3 +- CommonCode-skigdebian.c | 5647 +++++++++++++ CommonCode.c | 88 +- DOSAPI.c | 2 +- DRATS.c | 2 +- Events.c | 1 - FBBRoutines.c | 4 + FLDigi.c | 2 +- FreeDATA.c | 2 +- HALDriver.c | 2 +- HFCommon.c | 2 +- HSMODEM.c | 2 +- HTMLCommonCode.c | 2 +- HTTPcode-skigdebian.c | 5175 ++++++++++++ HTTPcode.c | 43 +- HanksRT.c | 10 +- Housekeeping.c | 5 + IPCode.c | 2 +- KAMPactor.c | 2 +- KISSHF.c | 2 +- L2Code.c | 56 +- L3Code.c | 2 +- L4Code.c | 4 +- LinBPQ-skigdebian.c | 2154 +++++ LinBPQ.c | 58 +- MULTIPSK.c | 2 +- MailDataDefs.c | 2 +- MailNode.vcproj.NOTTSDESKTOP.John.user | 4 +- Moncode.c | 2 +- NNTPRoutines.c | 3 + PortMapper.c | 2 +- RHP-skigdebian.c | 641 ++ RHP.c | 711 ++ RigControl.c | 20 +- SCSPactor.c | 2 +- SCSTrackeMulti.c | 2 +- SCSTracker.c | 2 +- SerialPort.c | 2 +- TNCCode.c | 2 +- TNCEmulators.c | 2 +- TelnetV6-skigdebian.c | 7157 +++++++++++++++++ TelnetV6.c | 73 +- UIARQ.c | 2 +- UIRoutines.c | 2 + UZ7HODrv.c | 2 +- V4.c | 2 +- VARA.c | 2 +- Versions-skigdebian.h | 125 + Versions.h | 22 +- WINMOR.c | 2 +- WPRoutines.c | 21 +- WebMail.c | 2 +- WinRPR.c | 2 +- ...rControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + adif.c | 2 +- asmDOSAPI.asm | 4 +- asmstrucs.h | 2 + bpqaxip.c | 16 +- bpqchat.h | 8 +- bpqether.c | 2 +- bpqhdlc.c | 2 +- bpqmail.h | 10 +- bpqvkiss.c | 2 +- cMain-skigdebian.c | 2788 +++++++ cMain.c | 13 +- cheaders.h.bak | 447 + config.c | 5 +- debug/bpq32-skigdebian.pdb | Bin 0 -> 1903616 bytes debug/bpq32.pdb | Bin 1903616 -> 1903616 bytes kiss.c | 2 +- linether.c | 2 +- lzhuf32.c | 4 +- mailapi.c | 2 +- makefile | 8 +- makefile~ | 43 + mqtt.c | 2 +- nodeapi-skigdebian.c | 858 ++ nodeapi.c | 40 +- pibits.c | 2 +- 103 files changed, 33364 insertions(+), 236 deletions(-) create mode 100644 BPQChat.vcproj.NOTTSDESKTOP.John.user create mode 100644 BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 Bpq32-skigdebian.c create mode 100644 CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 CBPQ32.vcproj.SKIGACER.johnw.user create mode 100644 CommonCode-skigdebian.c create mode 100644 HTTPcode-skigdebian.c create mode 100644 LinBPQ-skigdebian.c create mode 100644 RHP-skigdebian.c create mode 100644 RHP.c create mode 100644 TelnetV6-skigdebian.c create mode 100644 Versions-skigdebian.h create mode 100644 WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 cMain-skigdebian.c create mode 100644 cheaders.h.bak create mode 100644 debug/bpq32-skigdebian.pdb create mode 100644 makefile~ create mode 100644 nodeapi-skigdebian.c diff --git a/6pack.c b/6pack.c index 9e0eef0..8f954d8 100644 --- a/6pack.c +++ b/6pack.c @@ -72,7 +72,7 @@ Using code from 6pack Linux Kernel driver with the following licence and credits #include "compatbits.h" #include -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" diff --git a/AEAPactor.c b/AEAPactor.c index 643dae1..2d5af4b 100644 --- a/AEAPactor.c +++ b/AEAPactor.c @@ -45,7 +45,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses //#include //#include -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/AGWAPI.c b/AGWAPI.c index 462fe86..bf4c7da 100644 --- a/AGWAPI.c +++ b/AGWAPI.c @@ -26,7 +26,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses */ #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" diff --git a/AGWMoncode.c b/AGWMoncode.c index bd5a7e8..7427804 100644 --- a/AGWMoncode.c +++ b/AGWMoncode.c @@ -33,7 +33,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #pragma data_seg("_BPQDATA") -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" // MSGFLAG contains CMD/RESPONSE BITS diff --git a/AISCommon.c b/AISCommon.c index d33f25a..f0b6a6a 100644 --- a/AISCommon.c +++ b/AISCommon.c @@ -7,7 +7,7 @@ #include #include #include "time.h" -#include "CHeaders.h" +#include "cheaders.h" //#include "tncinfo.h" //#include "adif.h" //#include "telnetserver.h" diff --git a/APRSCode.c b/APRSCode.c index 497ea74..7255c7a 100644 --- a/APRSCode.c +++ b/APRSCode.c @@ -25,7 +25,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE #include -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" #include #include "kernelresource.h" diff --git a/ARDOP.c b/ARDOP.c index 704092f..4e4cc8d 100644 --- a/ARDOP.c +++ b/ARDOP.c @@ -45,7 +45,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #endif #endif -#include "CHeaders.h" +#include "cheaders.h" int (WINAPI FAR *GetModuleFileNameExPtr)(); @@ -136,6 +136,9 @@ BOOL ARDOPStopPort(struct PORTCONTROL * PORT) if (TNC->Streams[0].Attached) TNC->Streams[0].ReportDISC = TRUE; + TNC->Streams[0].Connected = 0; + TNC->Streams[0].Attached = 0; + if (TNC->TCPSock) { shutdown(TNC->TCPSock, SD_BOTH); @@ -162,6 +165,9 @@ BOOL ARDOPStopPort(struct PORTCONTROL * PORT) sprintf(PORT->TNC->WEB_COMMSSTATE, "%s", "Port Stopped"); MySetWindowText(PORT->TNC->xIDC_COMMSSTATE, PORT->TNC->WEB_COMMSSTATE); + strcpy(TNC->WEB_TNCSTATE, "Free"); + MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + return TRUE; } @@ -643,6 +649,12 @@ VOID ARDOPSendCommand(struct TNCINFO * TNC, char * Buff, BOOL Queue) if (Buff[0] == 0) // Terminal Keepalive? return; + if (memcmp(Buff, "LISTEN ", 7) == 0) + { + strcpy(TNC->WEB_MODE, &Buff[7]); + MySetWindowText(TNC->xIDC_MODE, &Buff[7]); + } + EncLen = sprintf(Encoded, "%s\r", Buff); // it is possible for binary data to be dumped into the command @@ -1918,7 +1930,7 @@ static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL) Len += sprintf(&Buff[Len], "Comms State%s", TNC->WEB_COMMSSTATE); Len += sprintf(&Buff[Len], "TNC State%s", TNC->WEB_TNCSTATE); - Len += sprintf(&Buff[Len], "Mode%s", TNC->WEB_MODE); + Len += sprintf(&Buff[Len], "Listen%s", TNC->WEB_MODE); Len += sprintf(&Buff[Len], "Channel State%s   %s", TNC->WEB_CHANSTATE, TNC->WEB_LEVELS); Len += sprintf(&Buff[Len], "Proto State%s", TNC->WEB_PROTOSTATE); Len += sprintf(&Buff[Len], "Traffic%s", TNC->WEB_TRAFFIC); @@ -2134,7 +2146,7 @@ VOID * ARDOPExtInit(EXTPORTDATA * PortEntry) 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, 120,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); + CreateWindowEx(0, "STATIC", "Listen", WS_CHILD | WS_VISIBLE, 10,50,80,20, TNC->hDlg, NULL, hInstance, NULL); TNC->xIDC_MODE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 120,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); @@ -2600,6 +2612,8 @@ VOID ARDOPThread(struct TNCINFO * TNC) TNC->Alerted = TRUE; + ARDOPSendCommand(TNC, "LISTEN TRUE", TRUE); + sprintf(TNC->WEB_COMMSSTATE, "Connected to ARDOP TNC"); MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE); @@ -2679,6 +2693,10 @@ VOID ARDOPThread(struct TNCINFO * TNC) sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost"); MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE); + strcpy(TNC->WEB_TNCSTATE, "Free"); + MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + TNC->CONNECTED = FALSE; TNC->Alerted = FALSE; @@ -3344,6 +3362,9 @@ VOID ARDOPProcessResponse(struct TNCINFO * TNC, UCHAR * Buffer, int MsgLen) RestartTNC(TNC); } + sprintf(TNC->WEB_TNCSTATE, "In Use by %s", TNC->Streams[0].MyCall); + MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + return; } diff --git a/BBSHTMLConfig.c b/BBSHTMLConfig.c index 3234c00..6207298 100644 --- a/BBSHTMLConfig.c +++ b/BBSHTMLConfig.c @@ -19,7 +19,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include "bpqmail.h" #ifdef WIN32 @@ -2219,21 +2219,21 @@ VOID ProcessUserUpdate(struct HTTPConnectionInfo * Session, char * MsgPtr, char ptr1 = GetNextParam(&ptr2); // Last Listed USER->lastmsg = atoi(ptr1); ptr1 = GetNextParam(&ptr2); // Name - strcpy(USER->Name, ptr1); + memcpy(USER->Name, ptr1, 17); ptr1 = GetNextParam(&ptr2); // Pass - strcpy(USER->pass, ptr1); + memcpy(USER->pass, ptr1, 12); ptr1 = GetNextParam(&ptr2); // CMS Pass if (memcmp("****************", ptr1, strlen(ptr1) != 0)) { - strcpy(USER->CMSPass, ptr1); + memcpy(USER->CMSPass, ptr1, 15); } ptr1 = GetNextParam(&ptr2); // QTH - strcpy(USER->Address, ptr1); + memcpy(USER->Address, ptr1, 60); ptr1 = GetNextParam(&ptr2); // ZIP - strcpy(USER->ZIP, ptr1); + memcpy(USER->ZIP, ptr1, 8); ptr1 = GetNextParam(&ptr2); // HomeBBS - strcpy(USER->HomeBBS, ptr1); + memcpy(USER->HomeBBS, ptr1, 40); _strupr(USER->HomeBBS); SaveUserDatabase(); @@ -3038,13 +3038,10 @@ static DWORD WINAPI InstanceThread(LPVOID lpvParam) const char * auth_header = "Authorization: Bearer "; char * token_begin = strstr(MsgPtr, auth_header); - int Flags = 0, n; + int Flags = 0; // Node Flags isn't currently used - char * Tok; - char * param; - if (token_begin) { // Using Auth Header diff --git a/BBSUtilities.c b/BBSUtilities.c index 50de210..8e7c10a 100644 --- a/BBSUtilities.c +++ b/BBSUtilities.c @@ -28,6 +28,10 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #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 @@ -77,7 +81,7 @@ FARPROCX pRefreshWebMailIndex; 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); +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); @@ -6574,8 +6578,6 @@ VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg) } - - VOID SendUnbuffered(int stream, char * msg, int len) { #ifndef LINBPQ @@ -7302,7 +7304,7 @@ VOID SetupForwardingStruct(struct UserInfo * user) if (ForwardingInfo->ConTimeout == 0) ForwardingInfo->ConTimeout = 120; - GetStringValue(group, "BBSHA", Temp); + GetStringValue(group, "BBSHA", Temp, 100); if (Temp[0]) ForwardingInfo->BBSHA = _strdup(Temp); @@ -10136,15 +10138,20 @@ int GetIntValueWithDefault(config_setting_t * group, char * name, int Default) } -BOOL GetStringValue(config_setting_t * group, char * name, char * value) +BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen) { - const char * str; + char * str; config_setting_t *setting; setting = config_setting_get_member (group, name); if (setting) { - str = config_setting_get_string (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; } @@ -10221,25 +10228,24 @@ BOOL GetConfig(char * ConfigName) Localtime = GetIntValue(group, "Localtime"); AliasText = GetMultiStringValue(group, "FWDAliases"); - GetStringValue(group, "BBSName", BBSName); - GetStringValue(group, "MailForText", MailForText); - GetStringValue(group, "SYSOPCall", SYSOPCall); - GetStringValue(group, "H-Route", HRoute); - GetStringValue(group, "AMPRDomain", AMPRDomain); + 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); - GetStringValue(group, "ISPSMTPName", ISPSMTPName); - GetStringValue(group, "ISPPOP3Name", ISPPOP3Name); + 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); - GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); - GetStringValue(group, "ISPAccountName", ISPAccountName); + GetStringValue(group, "ISPAccountName", ISPAccountName, 50); + GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass, 100); sprintf(SignoffMsg, "73 de %s\r", BBSName); // Default - GetStringValue(group, "SignoffMsg", SignoffMsg); + GetStringValue(group, "SignoffMsg", ISPAccountName, 50); DecryptPass(EncryptedISPAccountPass, ISPAccountPass, (int)strlen(EncryptedISPAccountPass)); @@ -10251,10 +10257,10 @@ BOOL GetConfig(char * ConfigName) #ifndef LINBPQ - GetStringValue(group, "MonitorSize", Size); + 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); + GetStringValue(group, "WindowSize", Size, sizeof(Size)); sscanf(Size,"%d,%d,%d,%d",&MainRect.left,&MainRect.right,&MainRect.top,&MainRect.bottom); Bells = GetIntValue(group, "Bells"); @@ -10267,7 +10273,7 @@ BOOL GetConfig(char * ConfigName) WrapInput = GetIntValue(group, "WrapInput"); FlashOnConnect = GetIntValue(group, "FlashOnConnect"); - GetStringValue(group, "ConsoleSize", Size); + GetStringValue(group, "ConsoleSize", Size, 80); sscanf(Size,"%d,%d,%d,%d,%d", &ConsoleRect.left, &ConsoleRect.right, &ConsoleRect.top, &ConsoleRect.bottom,&OpenConsole); @@ -10346,7 +10352,7 @@ BOOL GetConfig(char * ConfigName) // Get FBB Filters - GetStringValue(group, "FBBFilters", FBBString); + GetStringValue(group, "FBBFilters", FBBString, sizeof(FBBString)); ptr1 = FBBString; @@ -10432,8 +10438,8 @@ BOOL GetConfig(char * ConfigName) SendWP = GetIntValue(group, "SendWP"); SendWPType = GetIntValue(group, "SendWPType"); - GetStringValue(group, "SendWPTO", SendWPTO); - GetStringValue(group, "SendWPVIA", SendWPVIA); + GetStringValue(group, "SendWPTO", SendWPTO, sizeof(SendWPTO)); + GetStringValue(group, "SendWPVIA", SendWPVIA, sizeof(SendWPVIA)); SendWPAddrs = GetMultiStringValue(group, "SendWPAddrs"); @@ -10463,7 +10469,7 @@ BOOL GetConfig(char * ConfigName) SendWPVIA[0] = 0; } - GetStringValue(group, "Version", Size); + 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++) @@ -10481,7 +10487,7 @@ BOOL GetConfig(char * ConfigName) UIHDDR[i] = GetIntValueWithDefault(group, "SendHDDR", UIEnabled[i]); UINull[i] = GetIntValue(group, "SendNull"); Size[0] = 0; - GetStringValue(group, "Digis", Size); + GetStringValue(group, "Digis", Size, sizeof(Size)); if (Size[0]) UIDigi[i] = _strdup(Size); } diff --git a/BPQChat.vcproj.NOTTSDESKTOP.John.user b/BPQChat.vcproj.NOTTSDESKTOP.John.user new file mode 100644 index 0000000..b627f78 --- /dev/null +++ b/BPQChat.vcproj.NOTTSDESKTOP.John.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/BPQINP3.c b/BPQINP3.c index dac41c4..38c49b9 100644 --- a/BPQINP3.c +++ b/BPQINP3.c @@ -28,7 +28,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #pragma data_seg("_BPQDATA") -#include "CHeaders.h" +#include "cheaders.h" #include "time.h" #include "stdio.h" diff --git a/BPQMail.c b/BPQMail.c index 64eb9a3..f566d64 100644 --- a/BPQMail.c +++ b/BPQMail.c @@ -1146,6 +1146,7 @@ // Fix sending ampr.org mail when RMS is not enabled (51) // Send forwarding info to packetnodes.spots.radio database (51) // Fix bug in WP Message processing (56) +// Fix treating addresses ending in WW as Internet (57) #include "bpqmail.h" #include "winstdint.h" @@ -1537,7 +1538,11 @@ VOID WriteMiniDump() } -void GetSemaphore(struct SEM * Semaphore, int ID) + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line) { // // Wait for it to be free diff --git a/BPQNRR.c b/BPQNRR.c index 554b0e5..7a54777 100644 --- a/BPQNRR.c +++ b/BPQNRR.c @@ -36,7 +36,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses //#include "vmm.h" -#include "CHeaders.h" +#include "cheaders.h" extern int SENDNETFRAME(); diff --git a/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/BPQtoAGW.c b/BPQtoAGW.c index b91a20c..a173c66 100644 --- a/BPQtoAGW.c +++ b/BPQtoAGW.c @@ -56,7 +56,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #ifndef WIN32 #include #include diff --git a/Bpq32-skigdebian.c b/Bpq32-skigdebian.c new file mode 100644 index 0000000..a78510b --- /dev/null +++ b/Bpq32-skigdebian.c @@ -0,0 +1,6741 @@ +/* +Copyright 2001-2022 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modifyextern int HTTP +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 +*/ +// +// 409l Oct 2001 Fix l3timeout for KISS +// +// 409m Oct 2001 Fix Crossband Digi +// +// 409n May 2002 Change error handling on load ext DLL + +// 409p March 2005 Allow Multidigit COM Ports (kiss.c) + +// 409r August 2005 Treat NULL string in Registry as use current directory +// Allow shutdown to close BPQ Applications + +// 409s October 2005 Add DLL:Export entries to API for BPQTNC2 + +// 409t January 2006 +// +// Add API for Perl "GetPerlMsg" +// Add API for BPQ1632 "GETBPQAPI" - returns address of Assembler API routine +// Add Registry Entry "BPQ Directory". If present, overrides "Config File Location" +// Add New API "GetBPQDirectory" - Returns location of config file +// Add New API "ChangeSessionCallsign" - equivalent to "*** linked to" command +// Rename BPQNODES to BPQNODES.dat +// New API "GetAttachedProcesses" - returns number of processes connected. +// Warn if user trys to close Console Window. +// Add Debug entries to record Process Attach/Detach +// Fix recovery following closure of first process + +// 409t Beta 2 February 2006 +// +// Add API Entry "GetPortNumber" +// +// 409u February 2006 +// +// Fix crash if allocate/deallocate called with stream=0 +// Add API to ch +// Display config file path +// Fix saving of Locked Node flag +// Added SAVENODES SYSOP command +// +// 409u 2 March 2006 +// +// Fix SetupBPQDirectory +// Add CopyBPQDirectory (for Basic Programs) +// +// 409u 3 March 2006 +// +// Release streams on DLL unload + +// 409v October 2006 +// +// Support Minimize to Tray for all BPQ progams +// Implement L4 application callsigns + +// 410 November 2006 +// +// Modified to compile with C++ 2005 Express Edition +// Make MCOM MTX MMASK local variables +// +// 410a January 2007 +// +// Add program name to Attach-Detach messages +// Attempt to detect processes which have died +// Fix bug in NETROM and IFrame decode which would cause crash if frame was corrupt +// Add BCALL - origin call for Beacons +// Fix KISS ACKMODE ACK processing +// + +// 410b November 2007 +// +// Allow CTEXT of up to 510, and enforce PACLEN, fragmenting if necessary + +// 410c December 2007 + +// Fix problem with NT introduced in V410a +// Display location of DLL on Console + +// 410d January 2008 + +// Fix crash in DLL Init caused by long path to program +// Invoke Appl2 alias on C command (if enabled) +// Allow C command to be disabled +// Remove debug trap in GETRAWFRAME +// Validate Alias of directly connected node, mainly for KPC3 DISABL Problem +// Move Port statup code out of DLLInit (mainly for perl) +// Changes to allow Load/Unload of bpq32.dll by appl +// CloseBPQ32 API added +// Ext Driver Close routes called +// Changes to release Mutex + +// 410e May 2008 + +// Fix missing SSID on last call of UNPROTO string (CONVTOAX25 in main.asm) +// Fix VCOM Driver (RX Len was 1 byte too long) +// Fix possible crash on L4CODE if L4DACK received out of sequence +// Add basic IP decoding + +// 410f October 2008 + +// Add IP Gateway +// Add Multiport DIGI capability +// Add GetPortDescription API +// Fix potential hangs if RNR lost +// Fix problem if External driver failes to load +// Put pushad/popad round _INITIALISEPORTS (main.asm) +// Add APIs GetApplCallVB and GetPortDescription (mainly for RMS) +// Ensure Route Qual is updated if Port Qual changed +// Add Reload Option, plus menu items for DUMP and SAVENODES + +// 410g December 2008 + +// Restore API Exports BPQHOSTAPIPTR and MONDECODEPTR (accidentally deleted) +// Fix changed init of BPQDirectory (accidentally changed) +// Fix Checks for lost processes (accidentally deleted) +// Support HDLC Cards on W2K and above +// Delete Tray List entries for crashed processes +// Add Option to NODES command to sort by Callsign +// Add options to save or clear BPQNODES before Reconfig. +// Fix Reconfig in Win98 +// Monitor buffering tweaks +// Fix Init for large (>64k) tables +// Fix Nodes count in Stats + +// 410h January 2009 + +// Add Start Minimized Option +// Changes to KISS for WIn98 Virtual COM +// Open \\.\com instead of //./COM +// Extra Dignostics + +// 410i Febuary 2009 + +// Revert KISS Changes +// Save Window positions + +// 410j June 2009 + +// Fix tidying of window List when program crashed +// Add Max Nodes to Stats +// Don't update APPLnALIAS with received NODES info +// Fix MH display in other timezones +// Fix Possible crash when processing NETROM type Zero frames (eg NRR) +// Basic INP3 Stuff +// Add extra diagnostics to Lost Process detection +// Process Netrom Record Route frames. + +// 410k June 2009 + +// Fix calculation of %retries in extended ROUTES display +// Fix corruption of ROUTES table + +// 410l October 2009 + +// Add GetVersionString API call. +// Add GetPortTableEntry API call +// Keep links to neighbouring nodes open + +// Build 2 + +// Fix PE in NOROUTETODEST (missing POP EBX) + +// 410m November 2009 + +// Changes for PACTOR and WINMOR to support the ATTACH command +// Enable INP3 if configured on a route. +// Fix count of nodes in Stats Display +// Overwrite the worst quality unused route if a call is received from a node not in your +// table when the table is full + +// Build 5 + +// Rig Control Interface +// Limit KAM VHF attach and RADIO commands to authorised programs (MailChat and BPQTerminal) + +// Build 6 + +// Fix reading INP3 Flag from BPQNODES + +// Build 7 + +// Add MAXHOPS and MAXRTT config options + +// Build 8 + +// Fix INP3 deletion of Application Nodes. +// Fix GETCALLSIGN for Pactor Sessions +// Add N Call* to display all SSID's of a call +// Fix flow control on Pactor sessions. + +// Build 9 + +// HDLC Support for XP +// Add AUTH routines + +// Build 10 + +// Fix handling commands split over more that one packet. + +// Build 11 + +// Attach cmd changes for winmor disconnecting state +// Option Interlock Winmor/Pactor ports + +// Build 12 + +// Add APPLS export for winmor +// Handle commands ending CR LF + +// Build 13 + +// Incorporate Rig Control in Kernel + +// Build 14 + +// Fix config reload for Rig COntrol + +// 410n March 2010 + +// Implement C P via PACTOR/WINMOR (for Airmail) + +// Build 2 + +// Don't flip SSID bits on Downlink Connect if uplink is Pactor/WINMOR +// Fix resetting IDLE Timer on Pactor/WINMOR sessions +// Send L4 KEEPLI messages based on IDLETIME + +// 410o July 2010 + +// Read bpqcfg.txt instead of .bin +// Support 32 bit MMASK (Allowing 32 Ports) +// Support 32 bit _APPLMASK (Allowing 32 Applications) +// Allow more commands +// Allow longer command aliases +// Fix logic error in RIGControl Port Initialisation (wasn't always raising RTS and DTR +// Clear RIGControl RTS and DTR on close + +// 410o Build 2 August 2010 + +// Fix couple of errors in config (needed APPLICATIONS and BBSCALL/ALIAS/QUAL) +// Fix Kenwood Rig Control when more than one message received at once. +// Save minimzed state of Rigcontrol Window + +// 410o Build 3 August 2010 + +// Fix reporting of set errors in scan to a random session + +// 410o Build 4 August 2010 + +// Change All xxx Ports are in use to no xxxx Ports are available if there are no sessions with _APPLMASK +// Fix validation of TRANSDELAY + +// 410o Build 5 August 2010 + +// Add Repeater Shift and Set Data Mode options to Rigcontrol (for ICOM only) +// Add WINMOR and SCS Pactor mode control option to RigControl +// Extend INFOMSG to 2000 bytes +// Improve Scan freq change lock (check both SCS and WINMOR Ports) + +// 410o Build 6 September 2010 + +// Incorporate IPGateway in main code. +// Fix GetSessionInfo for Pactor/Winmor Ports +// Add Antenna Selection to RigControl +// Allow Bandwidth options on RADIO command line (as well as in Scan definitions) + +// 410o Build 7 September 2010 + +// Move rigconrtol display to driver windows +// Move rigcontrol config to driver config. +// Allow driver and IPGateway config info in bpq32.cfg +// Move IPGateway, AXIP, VKISS, AGW and WINMOR drivers into bpq32.dll +// Add option to reread IP Gateway config. +// Fix Reinit after process with timer closes (error in TellSessions). + +// 410p Build 2 October 2010 + +// Move KAM and SCS drivers to bpq32.dll + +// 410p Build 3 October 2010 + +// Support more than one axip port. + +// 410p Build 4 October 2010 + +// Dynamically load psapi.dll (for 98/ME) + +// 410p Build 5 October 2010 + +// Incorporate TelnetServer +// Fix AXIP ReRead Config +// Report AXIP accept() fails to syslog, not a popup. + +// 410p Build 6 October 2010 + +// Includes HAL support +// Changes to Pactor Drivers disconnect code +// AXIP now sends with source port = dest port, unless overridden by SOURCEPORT param +// Config now checks for duplicate port definitions +// Add Node Map reporting +// Fix WINMOR deferred disconnect. +// Report Pactor PORTCALL to WL2K instead of RMS Applcall + +// 410p Build 7 October 2010 + +// Add In/Out flag to Map reporting, and report centre, not dial +// Write Telnet log to BPQ Directory +// Add Port to AXIP resolver display +// Send Reports to update.g8bpq.net:81 +// Add support for FT100 to Rigcontrol +// Add timeout to Rigcontrol PTT +// Add Save Registry Command + +// 410p Build 8 November 2010 + +// Add NOKEEPALIVES Port Param +// Renumbered for release + +// 410p Build 9 November 2010 + +// Get Bandwith for map report from WL2K Report Command +// Fix freq display for FT100 (was KHz, not MHz) +// Don't try to change SCS mode whilst initialising +// Allow reporting of Lat/Lon as well as Locator +// Fix Telnet Log Name +// Fix starting with Minimized windows when Minimizetotray isn't set +// Extra Program Error trapping in SessionControl +// Fix reporting same freq with different bandwidths at different times. +// Code changes to support SCS Robust Packet Mode. +// Add FT2000 to Rigcontrol +// Only Send CTEXT to connects to Node (not to connects to an Application Call) + +// Released as Build 10 + +// 410p Build 11 January 2011 + +// Fix MH Update for SCS Outgoing Calls +// Add Direct CMS Access to TelnetServer +// Restructure DISCONNECT processing to run in Timer owning process + +// 410p Build 12 January 2011 + +// Add option for Hardware PTT to use a different com port from the scan port +// Add CAT PTT for Yaesu 897 (and maybe others) +// Fix RMS Packet ports busy after restart +// Fix CMS Telnet with MAXSESSIONS > 10 + +// 410p Build 13 January 2011 + +// Fix loss of buffers in TelnetServer +// Add CMS logging. +// Add non - Promiscuous mode option for BPQETHER + +// 410p Build 14 January 2011 + +// Add support for BPQTermTCP +// Allow more that one FBBPORT +// Allow Telnet FBB mode sessions to send CRLF as well as CR on user and pass msgs +// Add session length to CMS Telnet logging. +// Return Secure Session Flag from GetConnectionInfo +// Show Uptime as dd/hh/mm + +// 4.10.16.17 March 2011 + +// Add "Close all programs" command +// Add BPQ Program Directory registry key +// Use HKEY_CURRENT_USER on Vista and above (and move registry if necessary) +// Time out IP Gateway ARP entries, and only reload ax.25 ARP entries +// Add support for SCS Tracker HF Modes +// Fix WL2K Reporting +// Report Version to WL2K +// Add Driver to support Tracker with multiple sessions (but no scanning, wl2k report, etc) + + +// Above released as 5.0.0.1 + +// 5.2.0.1 + +// Add caching of CMS Server IP addresses +// Initialise TNC State on Pactor Dialogs +// Add Shortened (6 digit) AUTH mode. +// Update MH with all frames (not just I/UI) +// Add IPV6 Support for TelnetServer and AXIP +// Fix TNC OK Test for Tracker +// Fix crash in CMS mode if terminal disconnects while tcp commect in progress +// Add WL2K reporting for Robust Packet +// Add option to suppress WL2K reporting for specific frequencies +// Fix Timeband processing for Rig Control +// New Driver for SCS Tracker allowing multiple connects, so Tracker can be used for user access +// New Driver for V4 TNC + +// 5.2.1.3 October 2011 + +// Combine busy detector on Interlocked Ports (SCS PTC, WINMOR or KAM) +// Improved program error logging +// WL2K reporting changed to new format agreed with Lee Inman + +// 5.2.3.1 January 2012 + +// Connects from the console to an APPLCALL or APPLALIAS now invoke any Command Alias that has been defined. +// Fix reporting of Tracker freqs to WL2K. +// Fix Tracker monitoring setup (sending M UISC) +// Fix possible call/application routing error on RP +// Changes for P4Dragon +// Include APRS Digi/IGate +// Tracker monitoring now includes DIGIS +// Support sending UI frames using SCSTRACKER, SCTRKMULTI and UZ7HO drivers +// Include driver for UZ7HO soundcard modem. +// Accept DRIVER as well as DLLNAME, and COMPORT as well as IOADDR in bpq32.cfg. COMPORT is decimal +// No longer supports separate config files, or BPQTELNETSERVER.exe +// Improved flow control for Telnet CMS Sessions +// Fix handling Config file without a newline after last line +// Add non - Promiscuous mode option for BPQETHER +// Change Console Window to a Dialog Box. +// Fix possible corruption and loss of buffers in Tracker drivers +// Add Beacon After Session option to Tracker and UZ7HO Drivers +// Rewrite RigControl and add "Reread Config Command" +// Support User Mode VCOM Driver for VKISS ports + +// 5.2.4.1 January 2012 + +// Remove CR from Telnet User and Password Prompts +// Add Rigcontrol to UZ7HO driver +// Fix corruption of Free Buffer Count by Rigcontol +// Fix WINMOR and V4 PTT +// Add MultiPSK Driver +// Add SendBeacon export for BPQAPRS +// Add SendChatReport function +// Fix check on length of Port Config ID String with trailing spaces +// Fix interlock when Port Number <> Port Slot +// Add NETROMCALL for L3 Activity +// Add support for APRS Application +// Fix Telnet with FBBPORT and no TCPPORT +// Add Reread APRS Config +// Fix switching to Pactor after scanning in normal packet mode (PTC) + +// 5.2.5.1 February 2012 + +// Stop reading Password file. +// Add extra MPSK commands +// Fix MPSK Transparency +// Make LOCATOR command compulsory +// Add MobileBeaconInterval APRS param +// Send Course and Speed when APRS is using GPS +// Fix Robust Packet reporting in PTC driver +// Fix corruption of some MIC-E APRS packets + +// 5.2.6.1 February 2012 + +// Convert to MDI presentation of BPQ32.dll windows +// Send APRS Status packets +// Send QUIT not EXIT in PTC Init +// Implement new WL2K reporting format and include traffic reporting info in CMS signon +// New WL2KREPORT format +// Prevent loops when APPL alias refers to itself +// Add RigControl for Flex radios and ICOM IC-M710 Marine radio + +// 5.2.7.1 + +// Fix opening more thn one console window on Win98 +// Change method of configuring multiple timelots on WL2K reporting +// Add option to update WK2K Sysop Database +// Add Web server +// Add UIONLY port option + +// 5.2.7.2 + +// Fix handling TelnetServer packets over 500 bytes in normal mode + +// 5.2.7.3 + +// Fix Igate handling packets from UIView + +// 5.2.7.4 + +// Prototype Baycom driver. + +// 5.2.7.5 + +// Set WK2K group ref to MARS (3) if using a MARS service code + +// 5.2.7.7 + +// Check for programs calling CloseBPQ32 when holding semaphore +// Try/Except round Status Timer Processing + +// 5.2.7.8 + +// More Try/Except round Timer Processing + +// 5.2.7.9 + +// Enable RX in Baycom, and remove test loopback in tx + +// 5.2.7.10 + +// Try/Except round ProcessHTTPMessage + +// 5.2.7.11 + +// BAYCOM tweaks + +// 5.2.7.13 + +// Release semaphore after program error in Timer Processing +// Check fro valid dest in REFRESHROUTE + + +// Add TNC-X KISSOPTION (includes the ACKMODE bytes in the checksum( + +// Version 5.2.9.1 Sept 2012 + +// Fix using KISS ports with COMn > 16 +// Add "KISS over UDP" driver for PI as a TNC concentrator + +// Version 6.0.1.1 + +// Convert to C for linux portability +// Try to speed up kiss polling + +// Version 6.0.2.1 + +// Fix operation on Win98 +// Fix callsign error with AGWtoBPQ +// Fix PTT problem with WINMOR +// Fix Reread telnet config +// Add Secure CMS signon +// Fix error in cashing addresses of CMS servers +// Fix Port Number when using Send Raw. +// Fix PE in KISS driver if invalid subchannel received +// Fix Orignal address of beacons +// Speed up Telnet port monitoring. +// Add TNC Emulators +// Add CountFramesQueuedOnStream API +// Limit number of frames that can be queued on a session. +// Add XDIGI feature +// Add Winmor Robust Mode switching for compatibility with new Winmor TNC +// Move most APRS code from BPQAPRS to here +// Stop corruption caused by overlong KISS frames + +// Version 6.0.3.1 + +// Add starting/killing WINMOR TNC on remote host +// Fix Program Error when APRS Item or Object name is same as call of reporting station +// Dont digi a frame that we have already digi'ed +// Add ChangeSessionIdleTime API +// Add WK2KSYSOP Command +// Add IDLETIME Command +// Fix Errors in RELAYAPPL processing +// Fix PE cauaed by invalid Rigcontrol Line + +// Version 6.0.4.1 + +// Add frequency dependent autoconnect appls for SCS Pactor +// Fix DED Monitoring of I and UI with no data +// Include AGWPE Emulator (from AGWtoBPQ) +// accept DEL (Hex 7F) as backspace in Telnet +// Fix re-running resolver on re-read AXIP config +// Speed up processing, mainly for Telnet Sessions +// Fix APRS init on restart of bpq32.exe +// Change to 2 stop bits +// Fix scrolling of WINMOR trace window +// Fix Crash when ueing DED TNC Emulator +// Fix Disconnect when using BPQDED2 Driver with Telnet Sessions +// Allow HOST applications even when CMS option is disabled +// Fix processing of APRS DIGIMAP command with no targets (didn't suppress default settings) + +// Version 6.0.5.1 January 2014 + +// Add UTF8 conversion mode to Telnet (converts non-UTF-8 chars to UTF-8) +// Add "Clear" option to MH command +// Add "Connect to RMS Relay" Option +// Revert to one stop bit on serial ports, explictly set two on FT2000 rig control +// Fix routing of first call in Robust Packet +// Add Options to switch input source on rigs with build in soundcards (sor far only IC7100 and Kenwood 590) +// Add RTS>CAT PTT option for Sound Card rigs +// Add Clear Nodes Option (NODE DEL ALL) +// SCS Pactor can set differeant APPLCALLS when scanning. +// Fix possible Scan hangup after a manual requency change with SCS Pactor +// Accept Scan entry of W0 to disable WINMOR on that frequency +// Fix corruption of NETROMCALL by SIMPLE config command +// Enforce Pactor Levels +// Add Telnet outward connect +// Add Relay/Trimode Emulation +// Fix V4 Driver +// Add PTT Mux +// Add Locked ARP Entries (via bpq32.cfg) +// Fix IDLETIME node command +// Fix STAY param on connect +// Add STAY option to Attach and Application Commands +// Fix crash on copying a large AXIP MH Window +// Fix possible crash when bpq32.exe dies +// Fix DIGIPORT for UI frames + +// Version 6.0.6.1 April 2014 + +// FLDigi Interface +// Fix "All CMS Servers are inaccessible" message so Mail Forwarding ELSE works. +// Validate INP3 messages to try to prevent crash +// Fix possible crash if an overlarge KISS frame is received +// Fix error in AXR command +// Add LF to Telnet Outward Connect signin if NEEDLF added to connect line +// Add CBELL to TNC21 emulator +// Add sent objects and third party messages to APRS Dup List +// Incorporate UIUtil +// Use Memory Mapped file to pass APRS info to BPQAPRS, and process APRS HTTP in BPQ32 +// Improvements to FLDIGI interlocking +// Fix TNC State Display for Tracker +// Cache CMS Addresses on LinBPQ +// Fix count error on DED Driver when handling 256 byte packets +// Add basic SNMP interface for MRTG +// Fix memory loss from getaddrinfo +// Process "BUSY" response from Tracker +// Handle serial port writes that don't accept all the data +// Trap Error 10038 and try to reopen socket +// Fix crash if overlong command line received + +// Version 6.0.7.1 Aptil 2014 +// Fix RigContol with no frequencies for Kenwood and Yaesu +// Add busy check to FLDIGI connects + +// Version 6.0.8.1 August 2014 + +// Use HKEY_CURRENT_USER on all OS versions +// Fix crash when APRS symbol is a space. +// Fixes for FT847 CAT +// Fix display of 3rd byte of FRMR +// Add "DEFAULT ROBUST" and "FORCE ROBUST" commands to SCSPactor Driver +// Fix possible memory corruption in WINMOR driver +// Fix FT2000 Modes +// Use new WL2K reporting system (Web API Based) +// APRS Server now cycles through hosts if DNS returns more than one +// BPQ32 can now start and stop FLDIGI +// Fix loss of AXIP Resolver when running more than one AXIP port + +// Version 6.0.9.1 November 2014 + +// Fix setting NOKEEPALIVE flag on route created from incoming L3 message +// Ignore NODES from locked route with quality 0 +// Fix seting source port in AXIP +// Fix Dual Stack (IPV4/V6) on Linux. +// Fix RELAYSOCK if IPv6 is enabled. +// Add support for FT1000 +// Fix hang when APRS Messaging packet received on RF +// Attempt to normalize Node qualies when stations use widely differing Route qualities +// Add NODES VIA command to display nodes reachable via a specified neighbour +// Fix applying "DisconnectOnClose" setting on HOST API connects (Telnet Server) +// Fix buffering large messages in Telnet Host API +// Fix occasional crash in terminal part line processing +// Add "NoFallback" command to Telnet server to disable "fallback to Relay" +// Improved support for APPLCALL scanning with Pactor +// MAXBUFFS config statement is no longer needed. +// Fix USEAPPLCALLS with Tracker when connect to APPLCALL fails +// Implement LISTEN and CQ commands +// FLDIGI driver can now start FLDIGI on a remote system. +// Add IGNOREUNLOCKEDROUTES parameter +// Fix error if too many Telnet server connections + +// Version 6.0.10.1 Feb 2015 + +// Fix crash if corrupt HTML request received. +// Allow SSID's of 'R' and 'T' on non-ax.25 ports for WL2K Radio Only network. +// Make HTTP server HTTP Version 1.1 complient - use persistent conections and close after 2.5 mins +// Add INP3ONLY flag. +// Fix program error if enter UNPROTO without a destination path +// Show client IP address on HTTP sessions in Telnet Server +// Reduce frequency and number of attempts to connect to routes when Keepalives or INP3 is set +// Add FT990 RigControl support, fix FT1000MP support. +// Support ARMV5 processors +// Changes to support LinBPQ APRS Client +// Add IC7410 to supported Soundcard rigs +// Add CAT PTT to NMEA type (for ICOM Marine Radios_ +// Fix ACKMODE +// Add KISS over TCP +// Support ACKMode on VKISS +// Improved reporting of configuration file format errors +// Experimental driver to support ARQ sessions using UI frames + +// Version 6.0.11.1 September 2015 + +// Fixes for IPGateway configuration and Virtual Circuit Mode +// Separate Portmapper from IPGateway +// Add PING Command +// Add ARDOP Driver +// Add basic APPLCALL support for PTC-PRO/Dragon 7800 Packet (using MYALIAS) +// Add "VeryOldMode" for KAM Version 5.02 +// Add KISS over TCP Slave Mode. +// Support Pactor and Packet on P4Dragon on one port +// Add "Remote Staton Quality" to Web ROUTES display +// Add Virtual Host option for IPGateway NET44 Encap +// Add NAT for local hosts to IPGateway +// Fix setting filter from RADIO command for IC7410 +// Add Memory Channel Scanning for ICOM Radios +// Try to reopen Rig Control port if it fails (could be unplugged USB) +// Fix restoring position of Monitor Window +// Stop Codec on Winmor and ARDOP when an interlocked port is attached (instead of listen false) +// Support APRS beacons in RP mode on Dragon// +// Change Virtual MAC address on IPGateway to include last octet of IP Address +// Fix "NOS Fragmentation" in IP over ax.25 Virtual Circuit Mode +// Fix sending I frames before L2 session is up +// Fix Flow control on Telnet outbound sessions. +// Fix reporting of unterminatred comments in config +// Add option for RigControl to not change mode on FT100/FT990/FT1000 +// Add "Attach and Connect" for Telnet ports + +// Version 6.0.12.1 November 2015 + +// Fix logging of IP addresses for connects to FBBPORT +// Allow lower case user and passwords in Telnet "Attach and Connect" +// Fix possible hang in KISS over TCP Slave mode +// Fix duplicating LinBPQ process if running ARDOP fails +// Allow lower case command aliases and increase alias length to 48 +// Fix saving long IP frames pending ARP resolution +// Fix dropping last entry from a RIP44 message. +// Fix displaying Digis in MH list +// Add port name to Monitor config screen port list +// Fix APRS command display filter and add port filter +// Support port names in BPQTermTCP Monitor config +// Add FINDBUFFS command to dump lost buffers to Debugview/Syslog +// Buffer Web Mgmt Edit Config output +// Add WebMail Support +// Fix not closing APRS Send WX file. +// Add RUN option to APRS Config to start APRS Client +// LinBPQ run FindLostBuffers and exit if QCOUNT < 5 +// Close and reopen ARDOP connection if nothing received for 90 secs +// Add facility to bridge traffic between ports (similar to APRS Bridge but for all frame types) +// Add KISSOPTION TRACKER to set SCS Tracker into KISS Mode + +// 6.0.13.1 + +// Allow /ex to exit UNPROTO mode +// Support ARQBW commands. +// Support IC735 +// Fix sending ARDOP beacons after a busy holdoff +// Enable BPQDED driver to beacon via non-ax.25 ports. +// Fix channel number in UZ7HO monitoring +// Add SATGate mode to APRSIS Code. +// Fix crash caused by overlong user name in telnet logon +// Add option to log L4 connects +// Add AUTOADDQuiet mode to AXIP. +// Add EXCLUDE processing +// Support WinmorControl in UZ7HO driver and fix starting TNC on Linux +// Convert calls in MAP entries to upper case. +// Support Linux COM Port names for APRS GPS +// Fix using NETROM serial protocol on ASYNC Port +// Fix setting MYLEVEL by scanner after manual level change. +// Add DEBUGLOG config param to SCS Pactor Driver to log serial port traffic +// Uue #myl to set SCS Pactor MYLEVEL, and add checklevel command +// Add Multicast RX interface to FLDIGI Driver +// Fix processing application aliases to a connect command. +// Fix Buffer loss if radio connected to PTC rig port but BPQ not configured to use it +// Save backups of bpq32.cfg when editing with Web interface and report old and new length +// Add DD command to SCS Pactor, and use it for forced disconnect. +// Add ARDOP mode select to scan config +// ARDOP changes for ARDOP V 0.5+ +// Flip SSID bits on UZ7HO downlink connects + + +// Version 6.0.14.1 + +// Fix Socket leak in ARDOP and FLDIGI drivers. +// Add option to change CMS Server hostname +// ARDOP Changes for 0.8.0+ +// Discard Terminal Keepalive message (two nulls) in ARDOP command hander +// Allow parameters to be passed to ARDOP TNC when starting it +// Fix Web update of Beacon params +// Retry connects to KISS ports after failure +// Add support for ARDOP Serial Interface Native mode. +// Fix gating APRS-IS Messages to RF +// Fix Beacons when PORTNUM used +// Make sure old monitor flag is cleared for TermTCP sessions +// Add CI-V antenna control for IC746 +// Don't allow ARDOP beacons when connected +// Add support for ARDOP Serial over I2C +// Fix possble crash when using manual RADIO messages +// Save out of sequence L2 frames for possible reuse after retry +// Add KISS command to send KISS control frame to TNC +// Stop removing unused digis from packets sent to APRS-IS + +// Processing of ARDOP PING and PINGACK responses +// Handle changed encoding of WL2K update responses. +// Allow anonymous logon to telnet +// Don't use APPL= for RP Calls in Dragon Single mode. +// Add basic messaging page to APRS Web Server +// Add debug log option to SCSTracker and TrkMulti Driver +// Support REBOOT command on LinBPQ +// Allow LISTEN command on all ports that support ax.25 monitoring + +// Version 6.0.15.1 Feb 2018 + +// partial support for ax.25 V2.2 +// Add MHU and MHL commands and MH filter option +// Fix scan interlock with ARDOP +// Add Input source seiect for IC7300 +// Remove % transparency from web terminal signon message +// Fix L4 Connects In count on stats +// Fix crash caused by corrupt CMSInfo.txt +// Add Input peaks display to ARDOP status window +// Add options to show time in local and distances in KM on APRS Web pages +// Add VARA support +// Fix WINMOR Busy left set when port Suspended +// Add ARDOP-Packet Support +// Add Antenna Switching for TS 480 +// Fix possible crash in Web Terminal +// Support different Code Pages on Console sessions +// Use new Winlink API interface (api.winlink.org) +// Support USB/ACC switching on TS590SG +// Fix scanning when ARDOP or WINMOR is used without an Interlocked Pactor port. +// Set NODECALL to first Application Callsign if NODE=0 and BBSCALL not set. +// Add RIGCONTROL TUNE and POWER commands for some ICOM and Kenwwod rigs +// Fix timing out ARDOP PENDING Lock +// Support mixed case WINLINK Passwords +// Add TUNE and POWER Rigcontol Commands for some radios +// ADD LOCALTIME and DISPKM options to APRS Digi/Igate + +// 6.0.16.1 March 2018 + +// Fix Setting data mode and filter for IC7300 radios +// Add VARA to WL2KREPORT +// Add trace to SCS Tracker status window +// Fix possible hang in IPGATEWAY +// Add BeacontoIS parameter to APRSDIGI. Allows you to stop sending beacons to APRS-IS. +// Fix sending CTEXT on WINMOR sessions + +// 6.0.17.1 November 2018 + +// Change WINMOR Restart after connection to Restart after Failure and add same option to ARDOP and VARA +// Add Abort Connection to WINMOR and VARA Interfaces +// Reinstate accidentally removed CMS Access logging +// Fix MH CLEAR +// Fix corruption of NODE table if NODES received from station with null alias +// Fix loss of buffer if session closed with something in PARTCMDBUFFER +// Fix Spurious GUARD ZONE CORRUPT message in IP Code. +// Remove "reread bpq32.cfg and reconfigure" menu options +// Add support for PTT using CM108 based soundcard interfaces +// Datestamp Telnet log files and delete old Telnet and CMSAcces logs + +// 6.0.18.1 January 2019 + +// Fix validation of NODES broadcasts +// Fix HIDENODES +// Check for failure to reread config on axip reconfigure +// Fix crash if STOPPORT or STARTPORT used on KISS over TCP port +// Send Beacons from BCALL or PORTCALL if configured +// Fix possible corruption of last entry in MH display +// Ensure RTS/DTR is down when opening PTT Port +// Remove RECONFIG command +// Preparations for 64 bit version + +// 6.0.19 Sept 2019 +// Fix UZ7HO interlock +// Add commands to set Centre Frequency and Modem with UZ7HO Soundmodem (on Windows only) +// Add option to save and restore MH lists and SAVEMH command +// Add Frequency (if known) to UZ7HO MH lists +// Add Gateway option to Telnet for PAT +// Try to fix SCS Tracker recovery +// Ensure RTS/DTR is down on CAT port if using that line for PTT +// Experimental APRS Messaging in Kernel +// Add Rigcontrol on remote PC's using WinmorControl +// ADD VARAFM and VARAFM96 WL2KREPORT modes +// Fix WL2K sysop update for new Winlink API +// Fix APRS when using PORTNUM higher than the number of ports +// Add Serial Port Type +// Add option to linbpq to log APRS-IS messages. +// Send WL2K Session Reports +// Drop Tunneled Packets from 44.192 - 44.255 +// Log incoming Telnet Connects +// Add IPV4: and IPV6: overrides on AXIP Resolver. +// Add SessionTimeLimit to HF sessions (ARDOP, SCSPactor, WINMOR, VARA) +// Add RADIO FREQ command to display current frequency + +// 6.0.20 April 2020 + +// Trap and reject YAPP file transfer request. +// Fix possible overrun of TCP to Node Buffer +// Fix possible crash if APRS WX file doesn't have a terminating newline +// Change communication with BPQAPRS.exe to restore old message popup behaviour +// Preparation for 64 bit version +// Improve flow control on SCS Dragon +// Fragment messages from network links to L2 links with smaller paclen +// Change WL2K report rate to once every two hours +// Add PASS, CTEXT and CMSG commands and Stream Switch support to TNC2 Emulator +// Add SessionTimeLimit command to HF drivers (ARDOP, SCSPactor, WINMOR, VARA) +// Add links to Ports Web Manangement Page to open individual Driver windows +// Add STOPPORT/STARTPORT support to ARDOP, KAM and SCSPactor drivers +// Add CLOSE and OPEN RADIO command so Rigcontrol port can be freed fpr other use. +// Don't try to send WL2K Traffic report if Internet is down +// Move WL2K Traffic reporting to a separate thread so it doesn't block if it can't connect to server +// ADD AGWAPPL config command to set application number. AGWMASK is still supported +// Register Node Alias with UZ7HO Driver +// Register calls when UZ7HO TNC Restarts and at intervals afterwards +// Fix crash when no IOADDR or COMPORT in async port definition +// Fix Crash with Paclink-Unix when parsing ; VE7SPR-10 DE N7NIX QTC 1 +// Only apply BBSFLAG=NOBBS to APPPLICATION 1 +// Add RIGREONFIG command +// fix APRS RECONFIG on LinBPQ +// Fix Web Terminal scroll to end problem on some browsers +// Add PTT_SETS_INPUT option for IC7600 +// Add TELRECONFIG command to reread users or whole config +// Enforce PACLEN on UZ7HO ports +// Fix PACLEN on Command Output. +// Retry axip resolver if it fails at startup +// Fix AGWAPI connect via digis +// Fix Select() for Linux in MultiPSK, UZ7HO and V4 drivers +// Limit APRS OBJECT length to 80 chars +// UZ7HO disconnect incoming call if no free streams +// Improve response to REJ (no F) followed by RR (F). +// Try to prevent more than MAXFRAME frames outstanding when transmitting +// Allow more than one instance of APRS on Linux +// Stop APRS digi by originating station +// Send driver window trace to main monitor system +// Improve handling of IPOLL messages +// Fix setting end of address bit on dest call on connects to listening sessions +// Set default BBS and CHAT application number and number of streams on LinBPQ +// Support #include in bpq32.cfg processing + +// Version 6.0.21 14 December 2020 + +// Fix occasional missing newlines in some node command reponses +// More 64 bit fixes +// Add option to stop setting PDUPLEX param in SCSPACTOR +// Try to fix buffer loss +// Remove extra space from APRS position reports +// Suppress VARA IAMALIVE messages +// Add display and control of QtSoundModem modems +// Only send "No CMS connection available" message if fallbacktorelay is set. +// Add HAMLIB backend and emulator support to RIGCONTROL +// Ensure all beacons are sent even with very short beacon intervals +// Add VARA500 WL2K Reporting Mode +// Fix problem with prpcessing frame collector +// Temporarily disable L2 and L4 collectors till I can find problem +// Fix possible problem with interactive RADIO commands not giving a response, +// Incease maximum length of NODE command responses to handle maximum length INFO message, +// Allow WL2KREPORT in CONFIG section of UZ7HO port config. +// Fix program error in processing hamlib frame +// Save RestartAfterFailure option for VARA +// Check callsign has a winlink account before sending WL2KREPORT messages +// Add Bandwidth control to VARA scanning +// Renable L2 collector +// Fix TNCPORT reconnect on Linux +// Add SecureTelnet option to limit telnet outward connect to sysop mode sessions or Application Aliases +// Add option to suppress sending call to application in Telnet HOST API +// Add FT991A support to RigControl +// Use background.jpg for Edit Config page +// Send OK response to SCS Pactor commands starting with # +// Resend ICOM PTT OFF command after 30 seconds +// Add WXCall to APRS config +// Fixes for AEAPactor +// Allow PTTMUX to use real or com0com com ports +// Fix monitoring with AGW Emulator +// Derive approx position from packets on APRS ports with a valid 6 char location +// Fix corruption of APRS message lists if the station table fills up. +// Don't accept empty username or password on Relay sessions. +// Fix occasional empty Nodes broadcasts +// Add Digis to UZ7HO Port MH list +// Add PERMITTEDAPPLS port param +// Fix WK2K Session Record Reporting for Airmail and some Pactor Modes. +// Fix handling AX/IP (proto 93) frames +// Fix possible corruption sending APRS messages +// Allow Telnet connections to be made using Connect command as well as Attach then Connect +// Fix Cancel Sysop Signin +// Save axip resolver info and restore on restart +// Add Transparent mode to Telnet Server HOST API +// Fix Tracker driver if WL2KREPRRT is in main config section +// SNMP InOctets count corrected to include all frames and encoding of zero values fixed. +// Change IP Gateway to exclude handling bits of 44 Net sold to Amazon +// Fix crash in Web terminal when processing very long lines + +// Version 6.0.22.1 August 2021 + +// Fix bug in KAM TNCEMULATOR +// Add WinRPR Driver (DED over TCP) +// Fix handling of VARA config commands FM1200 and FM9600 +// Improve Web Termanal Line folding +// Add StartTNC to WinRPR driver +// Add support for VARA2750 Mode +// Add support for VARA connects via a VARA Digipeater +// Add digis to SCSTracker and WinRPR MHeard +// Separate RIGCONTROL config from PORT config and add RigControl window +// Fix crash when a Windows HID device doesn't have a product_string +// Changes to VARA TNC connection and restart process +// Trigger FALLBACKTORELAY if attempt to connect to all CMS servers fail. +// Fix saving part lines in adif log and Winlink Session reporting +// Add port specific CTEXT +// Add FRMR monitoring to UZ7HO driver +// Add audio input switching for IC7610 +// Include Rigcontrol Support for IC-F8101E +// Process any response to KISS command +// Fix NODE ADD command +// Add noUpdate flag to AXIP MAP +// Fix clearing NOFALLBACK flag in Telnet Server +// Allow connects to RMS Relay running on another host +// Allow use of Power setting in Rigcontol scan lines for Kenwood radios +// Prevent problems caused by using "CMS" as a Node Alias +// Include standard APRS Station pages in code +// Fix VALIDCALLS processing in HF drivers +// Send Netrom Link reports to Node Map +// Add REALTELNET mode to Telnet Outward Connect +// Fix using S (Stay) parameter on Telnet connects when using CMDPORT and C HOST +// Add Default frequency to rigcontrol to set a freq/mode to return to after a connection +// Fix long (> 60 seconds) scan intervals +// Improved debugging of stuck semaphores +// Fix potential securiby bug in BPQ Web server +// Send Chat Updates to chatupdate.g8bpq.net port 81 +// Add ReportRelayTraffic to Telnet config to send WL2K traffic reports for connections to RELAY +// Add experimental Mode reporting +// Add SendTandRtoRelay param to SCS Pactor, ARDOP and VARA drivers to divert calls to CMS for -T and -R to RELAY +// Add UPNP Support + +// Version 6.0.23.1 June 2022 + +// Add option to control which applcalls are enabled in VARA +// Add support for rtl_udp to Rig Control +// Fix Telnet Auto Conneect to Application when using TermTCP or Web Terminal +// Allow setting css styles for Web Terminal +// And Kill TNC and Kill and Restart TNC commands to Web Driver Windows +// More flexible RigControl for split frequency operation, eg for QO100 +// Increase stack size for ProcessHTMLMessage (.11) +// Fix HTML Content-Type on images (.12) +// Add AIS and ADSB Support (.13) +// Compress web pages (.14) +// Change minidump routine and close after program error (.15) +// Add RMS Relay SYNC Mode (.17) +// Changes for compatibility with Winlink Hybrid +// Add Rigcontrol CMD feature to Yaesu code (21) +// More diagnostic code +// Trap potential buffer overrun in ax/tcp code +// Fix possible hang in UZ7HO driver if connect takes a long time to succeed or fail +// Add FLRIG as backend for RigControl (.24) +// Fix bug in compressing some management web pages +// Fix bugs in AGW Emulator (.25) +// Add more PTT_Sets_Freq options for split frequency working (.26) +// Allow RIGCONTROL using Radio Number (Rnn) as well as Port (.26) +// Fix Telnet negotiation and backspace processing (.29) +// Fix VARA Mode change when scanning (.30) +// Add Web Mgmt Log Display (.33) +// Fix crash when connecting to RELAY when CMS=0 (.36) +// Send OK to user for manual freq changes with hamlib or flrig +// Fix Rigcontrol leaving port disabled when using an empty timeband +// Fix processing of backspace in Telnet character processing (.40) +// Increase max size of connect script +// Fix HAMLIB Slave Thread control +// Add processing of VARA mode responses and display of VARA Mode (41) +// Fix crash when VARA session aborted on LinBPQ (43) +// Fix handling port selector (2:call or p2 call) on SCS PTC packet ports (44) +// Include APRS Map web page +// Add Enable/Disable to KAMPACTOR scan control (use P0 or P1) (45) +// Add Basic DRATS interface (46) +// Fix MYCALLS on VARA (49) +// Add FreeData driver (51) +// Add additonal Rigcontrol options for QO100 (51) +// Set Content-Type: application/pdf for pdf files downloaded via web interface (51) +// Fix sending large compressed web messages (52) +// Fix freq display when using flrig or hamlib backends to rigcontrol +// Change VARA Driver to send ABORT when Session Time limit expires +// Add Chat Log to Web Logs display +// Fix possible buffer loss in RigControl +// Allow hosts on local lan to be treated as secure +// Improve validation of data sent to Winlink SessionAdd API call +// Add support for FreeDATA modem. +// Add GetLOC API Call +// Change Leaflet link in aprs map. +// Add Connect Log (64) +// Fix crash when Resolve CMS Servers returns ipv6 addresses +// Fix Reporting P4 sessions to Winlink (68) +// Add support for FreeBSD (68) +// Fix Rigcontrol PTCPORT (69) +// Set TNC Emulator sessions as secure (72) +// Fix not always detecting loss of FLRIG (73) +// Add ? and * wildcards to NODES command (74) +// Add Port RADIO config parameter (74) + +// Version 6.0.24.1 August 2023 + +// Apply NODES command wildcard to alias as well a call (2) +// Add STOPPORT/STARTPORT to VARA Driver (2) +// Add bandwidth setting to FLRIG interface. (2) +// Fix N VIA (3) +// Fix NODE ADD and NODE DEL (4) +// Improvements to FLRIG Rigcontrol backend (6, 7) +// Fix UZ7HO Window Title Update +// Reject L2 calls with a blank from call (8) +// Update WinRPR Window header with BPQ Port Description (8) +// Fix error in blank call code (9) +// Change web buttons to white on black when pressed (10) +// Fix Port CTEXT paclen on Tracker and WinRPR drivers (11) +// Add RADIO PTT command for testing PTT (11) +// Fix using APPLCALLs on SCSTracker RP call (12) +// Add Rigcntol Web Page (13) +// Fix scan bandwidth change with ARDOPOFDM (13) +// Fix setting Min Pactor Level in SCSPactor (13) +// Fix length of commands sent via CMD_TO_APPL flag (14) +// Add filter by quality option to N display (15) +// Fix VARA Mode reporting to WL2K (16) +// Add FLRIG POWER and TUNE commands (18) +// Fix crash when processing "C " without a call in UZ7HO, FLDIGI or MULTIPSK drivers (19) +// FLDIGI improvements (19) +// Fix hang at start if Telnet port Number > Number of Telnet Streams (20) +// Fix processing C command if first port driver is SCSPACTROR (20) +// Fix crash in UZ7HO driver if bad raw frame received (21) +// Fix using FLARQ chat mode with FLDIGI ddriover (22) +// Fix to KISSHF driver (23) +// Fix for application buffer loss (24) +// Add Web Sockets auto-refresh option for Webmail index page (25) +// Fix FREEDATA driver for compatibility with FreeData TNC version 0.6.4-alpha.3 (25) +// Add SmartID for bridged frames - Send ID only if packets sent recently (26) +// Add option to save and restore received APRS messages (27) +// Add mechanism to run a user program on certain events (27) +// If BeacontoIS is zero don't Gate any of our messages received locally to APRS-IS (28) +// Add Node Help command (28) +// Add APRS Igate RXOnly option (29) +// Fix RMC message handling with prefixes other than GP (29) +// Add GPSD support for APRS (30) +// Attempt to fix Tracker/WinRPR reconnect code (30) +// Changes to FreeDATA - Don't use deamon and add txlevel and send text commands (31) +// Fix interactive commands in tracker driver (33) +// Fix SESSIONTIMELIMIT processing +// Add STOPPORT/STARTPORT for UZ7HO driver +// Fix processing of extended QtSM 'g' frame (36) +// Allow setting just freq on Yaseu rigs (37) +// Enable KISSHF driver on Linux (40) +// Allow AISHOST and ADSBHOST to be a name as well as an address (41) +// Fix Interlock of incoming UZ7HO connections (41) +// Disable VARA Actions menu if not sysop (41) +// Fix Port CTEXT on UZ7HO B C or D channels (42) +// Fix repeated trigger of SessionTimeLimit (43) +// Fix posible memory corruption in UpateMH (44) +// Add PHG to APRS beacons (45) +// Dont send DM to stations in exclude list(45) +// Improvements to RMS Relay SYNC Mode (46) +// Check L4 connects against EXCLUDE list (47) +// Add vaidation of LOC in WL2K Session Reports (49) +// Change gpsd support for compatibility with Share Gps (50) +// Switch APRS Map to my Tiles (52) +// Fix using ; in UNPROTO Mode messages (52) +// Use sha1 code from https://www.packetizer.com/security/sha1/ instead of openssl (53) +// Fix TNC Emulator Monitoring (53) +// Fix attach and connect on Telnet port bug introduced in .55 (56) +// Fix stopping WinRPR TNC and Start/Stop UZ7HO TNCX on Linux (57) +// Fix stack size in beginthread for MAC (58) +// Add NETROM over VARA (60) +// Add Disconnect Script (64) +// Add node commands to set UZ7HO modem mode and freq (64) +// Trap empty NODECALL or NETROMCALL(65) +// Trap NODES messages with empty From Call (65) +// Add RigControl for SDRConsole (66) +// Fix FLRig crash (66) +// Fix VARA disconnect handling (67) +// Support 64 ports (69) +// Fix Node commands for setting UZ7HO Modem (70) +// Fix processing SABM on an existing session (71) +// Extend KISS Node command to send more than one parameter byte (72) +// Add G7TAJ's code to record activity of HF ports for stats display (72) +// Add option to send KISS command to TNC on startup (73) +// Fix Bug in DED Emulator Monitor code (74) +// Add Filters to DED Monitor code (75) +// Detect loss of DED application (76) +// Fix connects to Application Alias with UZ7HO Driver (76) +// Fix Interlock of ports on same UZ7HO modem. (76) +// Add extended Ports command (77) +// Fix crash in Linbpq when stdout is redirected to /dev/tty? and stdin ia redirected (78) +// Fix Web Terminal (80) +// Trap ENCRYPTION message from VARA (81) +// Fix processing of the Winlink API /account/exists response (82) +// Fix sending CTEXT to L4 connects to Node when FULL_CTEXT is not set + +// Version 6.0.25.? + +// Fix 64 bit compatibility problems in SCSTracker and UZ7HO drivers +// Add Chat PACLEN config (5) +// Fix NC to Application Call (6) +// Fix INP3 L3RTT messages on Linux and correct RTT calculation (9) +// Get Beacon config from config file on Windows (9) +// fix processing DED TNC Emulator M command with space between M and params (10) +// Fix sending UI frames on SCSPACTOR (11) +// Dont allow ports that can't set digi'ed bit in callsigns to digipeat. (11) +// Add SDRAngel rig control (11) +// Add option to specify config and data directories on linbpq (12) +// Allow zero resptime (send RR immediately) (13) +// Make sure CMD bit is set on UI frames +// Add setting Modem Flags in QtSM AGW mode +// If FT847 om PTC Port send a "Cat On" command (17) +// Fix some 63 port bugs in RigCOntrol (17) +// Fix 63 port bug in Bridging (18) +// Add FTDX10 Rigcontrol (19) +// Fix 64 bit bug in displaying INP3 Messages (20) +// Improve restart of WinRPR TNC on remote host (21) +// Fix some Rigcontrol issues with empty timebands (22) +// Fix 64 bit bug in processing INP3 Messages (22) +// First pass at api (24) +// Send OK in response to Rigcontrol CMD (24) +// Disable CTS check in WriteComBlock (26) +// Improvments to reporting to M0LTE Map (26) +// IPGateway fix from github user isavitsky (27) +// Fix possible crash in SCSPactor PTCPORT code (29) +// Add NodeAPI call sendLinks and remove get from other calls (32) +// Improve validation of Web Beacon Config (33) +// Support SNMP via host ip stack as well as IPGateway (34) +// Switch APRS Map to OSM tile servers (36) +// Fix potential buffer overflow in Telnet login (36) +// Allow longer serial device names (37) +// Fix ICF8101 Mode setting (37) +// Kill link if we are getting repeated RR(F) after timeout +// (Indicating other station is seeing our RR(P) but not the resent I frame) (40) +// Change default of SECURETELNET to 1 (41) +// Add optional ATTACH time limit for ARDOP (42) +// Fix buffer overflow risk in HTTP Terminal(42) +// Fix KISSHF Interlock (43) +// Support other than channel A on HFKISS (43) +// Support additional port info reporting for M0LTE Map (44) +// Allow interlocking of KISS and Session mode ports (eg ARDOP and VARA) (45) +// Add ARDOP UI Packets to MH (45) +// Add support for Qtsm Mgmt Interface (45) +// NodeAPI improvements (46) +// Add MQTT Interface (46) +// Fix buffer leak in ARDOP code(46) +// Fix possible crash if MQTT not in use (47) +// Add optional ATTACH time limit for VARA (48) +// API format fixes (48) +// AGWAPI Add protection against accidental connects from a non-agw application (50) +// Save MH and NODES every hour (51) +// Fix handling long unix device names (now max 250 bytes) (52) +// Fix error reporting in api update (53) +// Coding changes to remove some compiler warnings (53, 54) +// Add MQTT reporting of Mail Events (54) +// Fix beaconong on KISSHF ports (55) +// Fix MailAPI msgs endpoint +// Attempt to fix NC going to wrong application. (57) +// Improve ARDOP send of session code (58) + + +#define CKernel + +#include "Versions.h" + +#define _CRT_SECURE_NO_DEPRECATE + +#pragma data_seg("_BPQDATA") + +#include "time.h" +#include "stdio.h" +#include + +#include "compatbits.h" +#include "AsmStrucs.h" + +#include "SHELLAPI.H" +#include "kernelresource.h" + +#include +#include +#include "BPQTermMDI.h" + +#include "GetVersion.h" + +#define DllImport __declspec( dllimport ) + +#define CheckGuardZone() _CheckGuardZone(__FILE__, __LINE__) +void _CheckGuardZone(char * File, int Line); + +#define CHECKLOADED 0 +#define SETAPPLFLAGS 1 +#define SENDBPQFRAME 2 +#define GETBPQFRAME 3 +#define GETSTREAMSTATUS 4 +#define CLEARSTREAMSTATUS 5 +#define BPQCONDIS 6 +#define GETBUFFERSTATUS 7 +#define GETCONNECTIONINFO 8 +#define BPQRETURN 9 // GETCALLS +//#define RAWTX 10 //IE KISS TYPE DATA +#define GETRAWFRAME 11 +#define UPDATESWITCH 12 +#define BPQALLOC 13 +//#define SENDNETFRAME 14 +#define GETTIME 15 + +extern short NUMBEROFPORTS; +extern long PORTENTRYLEN; +extern long LINKTABLELEN; +extern struct PORTCONTROL * PORTTABLE; +extern void * FREE_Q; +extern UINT APPL_Q; // Queue of frames for APRS Appl + +extern TRANSPORTENTRY * L4TABLE; +extern UCHAR NEXTID; +extern DWORD MAXCIRCUITS; +extern DWORD L4DEFAULTWINDOW; +extern DWORD L4T1; +extern APPLCALLS APPLCALLTABLE[]; +extern char * APPLS; + +extern struct WL2KInfo * WL2KReports; + +extern int NUMBEROFTNCPORTS; + + +void * VCOMExtInit(struct PORTCONTROL * PortEntry); +void * AXIPExtInit(struct PORTCONTROL * PortEntry); +void * SCSExtInit(struct PORTCONTROL * PortEntry); +void * AEAExtInit(struct PORTCONTROL * PortEntry); +void * KAMExtInit(struct PORTCONTROL * PortEntry); +void * HALExtInit(struct PORTCONTROL * PortEntry); +void * ETHERExtInit(struct PORTCONTROL * PortEntry); +void * AGWExtInit(struct PORTCONTROL * PortEntry); +void * WinmorExtInit(EXTPORTDATA * PortEntry); +void * TelnetExtInit(EXTPORTDATA * PortEntry); +//void * SoundModemExtInit(EXTPORTDATA * PortEntry); +void * TrackerExtInit(EXTPORTDATA * PortEntry); +void * TrackerMExtInit(EXTPORTDATA * PortEntry); +void * V4ExtInit(EXTPORTDATA * PortEntry); +void * UZ7HOExtInit(EXTPORTDATA * PortEntry); +void * MPSKExtInit(EXTPORTDATA * PortEntry); +void * FLDigiExtInit(EXTPORTDATA * PortEntry); +void * UIARQExtInit(EXTPORTDATA * PortEntry); +void * SerialExtInit(EXTPORTDATA * PortEntry); +void * ARDOPExtInit(EXTPORTDATA * PortEntry); +void * VARAExtInit(EXTPORTDATA * PortEntry); +void * KISSHFExtInit(EXTPORTDATA * PortEntry); +void * WinRPRExtInit(EXTPORTDATA * PortEntry); +void * HSMODEMExtInit(EXTPORTDATA * PortEntry); +void * FreeDataExtInit(EXTPORTDATA * PortEntry); +void * SIXPACKExtInit(EXTPORTDATA * PortEntry); + +extern char * ConfigBuffer; // Config Area +VOID REMOVENODE(dest_list * DEST); +DllExport int ConvFromAX25(unsigned char * incall,unsigned char * outcall); +DllExport int ConvToAX25(unsigned char * incall,unsigned char * outcall); +VOID GetUIConfig(); +VOID ADIFWriteFreqList(); +void SaveAIS(); +void initAIS(); +void initADSB(); + +extern BOOL ADIFLogEnabled; + +int CloseOnError = 0; + +char UIClassName[]="UIMAINWINDOW"; // the main window class name + +HWND UIhWnd; + +extern char AUTOSAVE; +extern char AUTOSAVEMH; + +extern char MYNODECALL; // 10 chars,not null terminated + +extern QCOUNT; +extern BPQVECSTRUC BPQHOSTVECTOR[]; +#define BPQHOSTSTREAMS 64 +#define IPHOSTVECTOR BPQHOSTVECTOR[BPQHOSTSTREAMS + 3] + +extern char * CONFIGFILENAME; + +DllExport BPQVECSTRUC * BPQHOSTVECPTR; + +extern int DATABASESTART; + +extern struct ROUTE * NEIGHBOURS; +extern int ROUTE_LEN; +extern int MAXNEIGHBOURS; + +extern struct DEST_LIST * DESTS; // NODE LIST +extern int DEST_LIST_LEN; +extern int MAXDESTS; // MAX NODES IN SYSTEM + +extern struct _LINKTABLE * LINKS; +extern int LINK_TABLE_LEN; +extern int MAXLINKS; + +extern double LatFromLOC; +extern double LonFromLOC; + + +extern int BPQHOSTAPI(); +extern int INITIALISEPORTS(); +extern int TIMERINTERRUPT(); +extern int MONDECODE(); +extern int BPQMONOPTIONS(); +extern char PWTEXT[]; +extern char PWLen; + +extern int FINDFREEDESTINATION(); +extern int RAWTX(); +extern int RELBUFF(); +extern int SENDNETFRAME(); +extern char MYCALL[]; // 7 chars, ax.25 format + +extern HWND hIPResWnd; +extern BOOL IPMinimized; + +extern int NODESINPROGRESS; +extern VOID * CURRENTNODE; + + +BOOL Start(); + +VOID SaveWindowPos(int port); +VOID SaveAXIPWindowPos(int port); +VOID SetupRTFHddr(); +DllExport VOID APIENTRY CreateNewTrayIcon(); +int DoReceivedData(int Stream); +int DoStateChange(int Stream); +int DoMonData(int Stream); +struct ConsoleInfo * CreateChildWindow(int Stream, BOOL DuringInit); +CloseHostSessions(); +SaveHostSessions(); +VOID SaveBPQ32Windows(); +VOID CloseDriverWindow(int port); +VOID CheckWL2KReportTimer(); +VOID SetApplPorts(); +VOID WriteMiniDump(); +VOID FindLostBuffers(); +BOOL InitializeTNCEmulator(); +VOID TNCTimer(); +char * strlop(char * buf, char delim); + +DllExport int APIENTRY Get_APPLMASK(int Stream); +DllExport int APIENTRY GetStreamPID(int Stream); +DllExport int APIENTRY GetApplFlags(int Stream); +DllExport int APIENTRY GetApplNum(int Stream); +DllExport BOOL APIENTRY GetAllocationState(int Stream); +DllExport int APIENTRY GetMsg(int stream, char * msg, int * len, int * count ); +DllExport int APIENTRY RXCount(int Stream); +DllExport int APIENTRY TXCount(int Stream); +DllExport int APIENTRY MONCount(int Stream); +DllExport int APIENTRY GetCallsign(int stream, char * callsign); +DllExport VOID APIENTRY RelBuff(VOID * Msg); +void SaveMH(); +void DRATSPoll(); + +#define C_Q_ADD(s, b) _C_Q_ADD(s, b, __FILE__, __LINE__); +int _C_Q_ADD(VOID *PQ, VOID *PBUFF, char * File, int Line); + +VOID SetWindowTextSupport(); +int WritetoConsoleSupport(char * buff); +VOID PMClose(); +VOID MySetWindowText(HWND hWnd, char * Msg); +BOOL CreateMonitorWindow(char * MonSize); +VOID FormatTime3(char * Time, time_t cTime); + +char EXCEPTMSG[80] = ""; + +char SIGNONMSG[128] = ""; +char SESSIONHDDR[80] = ""; +int SESSHDDRLEN = 0; + +BOOL IncludesMail = FALSE; +BOOL IncludesChat = FALSE; // Set if pgram is running - used for Web Page Index + + +char WL2KCall[10]; +char WL2KLoc[7]; + +extern char LOCATOR[]; // Locator for Reporting - may be Maidenhead or LAT:LON +extern char MAPCOMMENT[]; // Locator for Reporting - may be Maidenhead or LAT:LON +extern char LOC[7]; // Maidenhead Locator for Reporting +extern char ReportDest[7]; + +extern UCHAR ConfigDirectory[260]; + +extern uint64_t timeLoadedMS; + +VOID __cdecl Debugprintf(const char * format, ...); +VOID __cdecl Consoleprintf(const char * format, ...); + +DllExport int APIENTRY CloseBPQ32(); +DllExport char * APIENTRY GetLOC(); +DllExport int APIENTRY SessionControl(int stream, int command, int param); + +int DoRefreshWebMailIndex(); + +BOOL APIENTRY Init_IP(); +BOOL APIENTRY Poll_IP(); + +BOOL APIENTRY Init_PM(); +BOOL APIENTRY Poll_PM(); + +BOOL APIENTRY Init_APRS(); +BOOL APIENTRY Poll_APRS(); +VOID HTTPTimer(); + +BOOL APIENTRY Rig_Init(); +BOOL APIENTRY Rig_Close(); +BOOL Rig_Poll(); + +VOID IPClose(); +VOID APRSClose(); +VOID CloseTNCEmulator(); + +VOID Poll_AGW(); +void RHPPoll(); +BOOL AGWAPIInit(); +int AGWAPITerminate(); + +int * Flag = (int *)&Flag; // for Dump Analysis +int MAJORVERSION=4; +int MINORVERSION=9; + +struct SEM Semaphore = {0, 0, 0, 0}; +struct SEM APISemaphore = {0, 0, 0, 0}; +int SemHeldByAPI = 0; +int LastSemGets = 0; +UINT Sem_eax = 0; +UINT Sem_ebx = 0; +UINT Sem_ecx = 0; +UINT Sem_edx = 0; +UINT Sem_esi = 0; +UINT Sem_edi = 0; + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); +void FreeSemaphore(struct SEM * Semaphore); + +DllExport void * BPQHOSTAPIPTR = &BPQHOSTAPI; +//DllExport long MONDECODEPTR = (long)&MONDECODE; + +extern UCHAR BPQDirectory[]; +extern UCHAR LogDirectory[]; +extern UCHAR BPQProgramDirectory[]; + +static char BPQWinMsg[] = "BPQWindowMessage"; + +static char ClassName[] = "BPQMAINWINDOW"; + +HKEY REGTREE = HKEY_CURRENT_USER; +char REGTREETEXT[100] = "HKEY_CURRENT_USER"; + +UINT BPQMsg=0; + +#define MAXLINELEN 120 +#define MAXSCREENLEN 50 + +#define BGCOLOUR RGB(236,233,216) + +HBRUSH bgBrush = NULL; + +//int LINELEN=120; +//int SCREENLEN=50; + +//char Screen[MAXLINELEN*MAXSCREENLEN]={0}; + +//int lineno=0; +//int col=0; + +#define REPORTINTERVAL 15 * 549; // Magic Ticks Per Minute for PC's nominal 100 ms timer +int ReportTimer = 0; + +HANDLE OpenConfigFile(char * file); + +VOID SetupBPQDirectory(); +VOID SendLocation(); + +//uintptr_t _beginthread(void(*start_address)(), unsigned stack_size, int arglist); + +#define TRAY_ICON_ID 1 // ID number for the Notify Icon +#define MY_TRAY_ICON_MESSAGE WM_APP // the message ID sent to our window + +NOTIFYICONDATA niData; + +int SetupConsoleWindow(); + +BOOL StartMinimized=FALSE; +BOOL MinimizetoTray=TRUE; + +BOOL StatusMinimized = FALSE; +BOOL ConsoleMinimized = FALSE; + +HMENU trayMenu=0; + +HWND hConsWnd = NULL, hWndCons = NULL, hWndBG = NULL, ClientWnd = NULL, FrameWnd = NULL, StatusWnd = NULL; + +BOOL FrameMaximized = FALSE; + +BOOL IGateEnabled = TRUE; +extern int ISDelayTimer; // Time before trying to reopen APRS-IS link +extern int ISPort; + +UINT * WINMORTraceQ = NULL; +UINT * SetWindowTextQ = NULL; + +static RECT Rect = {100,100,400,400}; // Console Window Position +RECT FRect = {100,100,800,600}; // Frame +static RECT StatusRect = {100,100,850,500}; // Status Window + +DllExport int APIENTRY DumpSystem(); +DllExport int APIENTRY SaveNodes (); +DllExport int APIENTRY ClearNodes (); +DllExport int APIENTRY SetupTrayIcon(); + +#define Q_REM(s) _Q_REM(s, __FILE__, __LINE__) + +VOID * _Q_REM(VOID *Q, char * File, int Line); + +UINT ReleaseBuffer(UINT *BUFF); + + +VOID CALLBACK TimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime ); + +DllExport int APIENTRY DeallocateStream(int stream); + +int VECTORLENGTH = sizeof (struct _BPQVECSTRUC); + +int FirstEntry = 1; +BOOL CloseLast = TRUE; // If the user started BPQ32.exe, don't close it when other programs close +BOOL Closing = FALSE; // Set if Close All called - prevents respawning bpq32.exe + +BOOL BPQ32_EXE; // Set if Process is running BPQ32.exe. Not initialised. + // Used to Kill surplus BPQ32.exe processes + +DWORD Our_PID; // Our Process ID - local variable + +void * InitDone = 0; +int FirstInitDone = 0; +int PerlReinit = 0; +UINT_PTR TimerHandle = 0; +UINT_PTR SessHandle = 0; + +BOOL EventsEnabled = 0; + +unsigned int TimerInst = 0xffffffff; + +HANDLE hInstance = 0; + +int AttachedProcesses = 0; +int AttachingProcess = 0; +HINSTANCE hIPModule = 0; +HINSTANCE hRigModule = 0; + +BOOL ReconfigFlag = FALSE; +BOOL RigReconfigFlag = FALSE; +BOOL APRSReconfigFlag = FALSE; +BOOL CloseAllNeeded = FALSE; +BOOL NeedWebMailRefresh = FALSE; + +int AttachedPIDList[100] = {0}; + +HWND hWndArray[100] = {0}; +int PIDArray[100] = {0}; +char PopupText[30][100] = {""}; + +// Next 3 should be uninitialised so they are local to each process + +UCHAR MCOM; +UCHAR MTX; // Top bit indicates use local time +uint64_t MMASK; +UCHAR MUIONLY; + +UCHAR AuthorisedProgram; // Local Variable. Set if Program is on secure list + +char pgm[256]; // Uninitialised so per process + +HANDLE Mutex; + +BOOL PartLine = FALSE; +int pindex = 0; +DWORD * WritetoConsoleQ; + + +LARGE_INTEGER lpFrequency = {0}; +LARGE_INTEGER lastRunTime; +LARGE_INTEGER currentTime; + +int ticksPerMillisec; +int interval; + + +VOID CALLBACK SetupTermSessions(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime); + + +TIMERPROC lpTimerFunc = (TIMERPROC) TimerProc; +TIMERPROC lpSetupTermSessions = (TIMERPROC) SetupTermSessions; + + +BOOL ProcessConfig(); +VOID FreeConfig(); + +DllExport int APIENTRY WritetoConsole(char * buff); + +BOOLEAN CheckifBPQ32isLoaded(); +BOOLEAN StartBPQ32(); +DllExport VOID APIENTRY Send_AX(VOID * Block, DWORD len, UCHAR Port); +BOOL LoadIPDriver(); +BOOL Send_IP(VOID * Block, DWORD len); +VOID CheckforLostProcesses(); +BOOL LoadRigDriver(); +VOID SaveConfig(); +VOID CreateRegBackup(); +VOID ResolveUpdateThread(); +VOID OpenReportingSockets(); +DllExport VOID APIENTRY CloseAllPrograms(); +DllExport BOOL APIENTRY SaveReg(char * KeyIn, HANDLE hFile); +int upnpClose(); + +BOOL IPActive = FALSE; +extern BOOL IPRequired; +BOOL PMActive = FALSE; +extern BOOL PMRequired; +BOOL RigRequired = TRUE; +BOOL RigActive = FALSE; +BOOL APRSActive = FALSE; +BOOL AGWActive = FALSE; +BOOL needAIS = FALSE; +int needADSB = 0; + +extern int AGWPort; + +Tell_Sessions(); + + +typedef int (WINAPI FAR *FARPROCX)(); + +FARPROCX CreateToolHelp32SnapShotPtr; +FARPROCX Process32Firstptr; +FARPROCX Process32Nextptr; + +void LoadToolHelperRoutines() +{ + HINSTANCE ExtDriver=0; + int err; + char msg[100]; + + ExtDriver=LoadLibrary("kernel32.dll"); + + if (ExtDriver == NULL) + { + err=GetLastError(); + sprintf(msg,"BPQ32 Error loading kernel32.dll - Error code %d\n", err); + OutputDebugString(msg); + return; + } + + CreateToolHelp32SnapShotPtr = (FARPROCX)GetProcAddress(ExtDriver,"CreateToolhelp32Snapshot"); + Process32Firstptr = (FARPROCX)GetProcAddress(ExtDriver,"Process32First"); + Process32Nextptr = (FARPROCX)GetProcAddress(ExtDriver,"Process32Next"); + + if (CreateToolHelp32SnapShotPtr == 0) + { + err=GetLastError(); + sprintf(msg,"BPQ32 Error getting CreateToolhelp32Snapshot entry point - Error code %d\n", err); + OutputDebugString(msg); + return; + } +} + +BOOL GetProcess(int ProcessID, char * Program) +{ + HANDLE hProcessSnap; + PROCESSENTRY32 pe32; + int p; + + if (CreateToolHelp32SnapShotPtr==0) + { + return (TRUE); // Routine not available + } + // Take a snapshot of all processes in the system. + hProcessSnap = (HANDLE)CreateToolHelp32SnapShotPtr(TH32CS_SNAPPROCESS, 0); + if( hProcessSnap == INVALID_HANDLE_VALUE ) + { + OutputDebugString( "CreateToolhelp32Snapshot (of processes) Failed\n" ); + return( FALSE ); + } + + // Set the size of the structure before using it. + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + // Retrieve information about the first process, + // and exit if unsuccessful + if( !Process32Firstptr( hProcessSnap, &pe32 ) ) + { + OutputDebugString( "Process32First Failed\n" ); // Show cause of failure + CloseHandle( hProcessSnap ); // Must clean up the snapshot object! + return( FALSE ); + } + + // Now walk the snapshot of processes, and + // display information about each process in turn + do + { + if (ProcessID==pe32.th32ProcessID) + { + // if running on 98, program contains the full path - remove it + + for (p = (int)strlen(pe32.szExeFile); p >= 0; p--) + { + if (pe32.szExeFile[p]=='\\') + { + break; + } + } + p++; + + sprintf(Program,"%s", &pe32.szExeFile[p]); + CloseHandle( hProcessSnap ); + return( TRUE ); + } + + } while( Process32Nextptr( hProcessSnap, &pe32 ) ); + + + sprintf(Program,"PID %d Not Found", ProcessID); + CloseHandle( hProcessSnap ); + return(FALSE); +} + +BOOL IsProcess(int ProcessID) +{ + // Check that Process exists + + HANDLE hProcessSnap; + PROCESSENTRY32 pe32; + + if (CreateToolHelp32SnapShotPtr==0) return (TRUE); // Routine not available + + hProcessSnap = (HANDLE)CreateToolHelp32SnapShotPtr(TH32CS_SNAPPROCESS, 0); + + if( hProcessSnap == INVALID_HANDLE_VALUE ) + { + OutputDebugString( "CreateToolhelp32Snapshot (of processes) Failed\n" ); + return(TRUE); // Don't know, so assume ok + } + + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + if( !Process32Firstptr( hProcessSnap, &pe32 ) ) + { + OutputDebugString( "Process32First Failed\n" ); // Show cause of failure + CloseHandle( hProcessSnap ); // Must clean up the snapshot object! + return(TRUE); // Don't know, so assume ok + } + + do + { + if (ProcessID==pe32.th32ProcessID) + { + CloseHandle( hProcessSnap ); + return( TRUE ); + } + + } while( Process32Nextptr( hProcessSnap, &pe32 ) ); + + CloseHandle( hProcessSnap ); + return(FALSE); +} + +#include "DbgHelp.h" + +VOID MonitorThread(int x) +{ + // Thread to detect killed processes. Runs in process owning timer. + + // Obviously can't detect loss of timer owning thread! + + do + { + if (Semaphore.Gets == LastSemGets && Semaphore.Flag) + { + // It is stuck - try to release + + Debugprintf ("Semaphore locked - Process ID = %d, Held By %d from %s Line %d", + Semaphore.SemProcessID, SemHeldByAPI, Semaphore.File, Semaphore.Line); + + // Write a minidump + + WriteMiniDump(); + + Semaphore.Flag = 0; + } + + LastSemGets = Semaphore.Gets; + + Sleep(30000); + CheckforLostProcesses(); + + } while (TRUE); +} + +VOID CheckforLostProcesses() +{ + UCHAR buff[100]; + char Log[80]; + int i, n, ProcessID; + + for (n=0; n < AttachedProcesses; n++) + { + ProcessID=AttachedPIDList[n]; + + if (!IsProcess(ProcessID)) + { + // Process has died - Treat as a detach + + sprintf(Log,"BPQ32 Process %d Died\n", ProcessID); + OutputDebugString(Log); + + // Remove Tray Icon Entry + + for( i = 0; i < 100; ++i ) + { + if (PIDArray[i] == ProcessID) + { + hWndArray[i] = 0; + sprintf(Log,"BPQ32 Removing Tray Item %s\n", PopupText[i]); + OutputDebugString(Log); + DeleteMenu(trayMenu,TRAYBASEID+i,MF_BYCOMMAND); + } + } + + // If process had the semaphore, release it + + if (Semaphore.Flag == 1 && ProcessID == Semaphore.SemProcessID) + { + OutputDebugString("BPQ32 Process was holding Semaphore - attempting recovery\r\n"); + Debugprintf("Last Sem Call %d %x %x %x %x %x %x", SemHeldByAPI, + Sem_eax, Sem_ebx, Sem_ecx, Sem_edx, Sem_esi, Sem_edi); + + Semaphore.Flag = 0; + SemHeldByAPI = 0; + } + + for (i=1;i<65;i++) + { + if (BPQHOSTVECTOR[i-1].STREAMOWNER == AttachedPIDList[n]) + { + DeallocateStream(i); + } + } + + if (TimerInst == ProcessID) + { + KillTimer(NULL,TimerHandle); + TimerHandle=0; + TimerInst=0xffffffff; +// Tell_Sessions(); + OutputDebugString("BPQ32 Process was running timer \n"); + + if (MinimizetoTray) + Shell_NotifyIcon(NIM_DELETE,&niData); + + + } + + // Remove this entry from PID List + + for (i=n; i< AttachedProcesses; i++) + { + AttachedPIDList[i]=AttachedPIDList[i+1]; + } + AttachedProcesses--; + + sprintf(buff,"BPQ32 Lost Process - %d Process(es) Attached\n", AttachedProcesses); + OutputDebugString(buff); + } + } +} +VOID MonitorTimerThread(int x) +{ + // Thread to detect killed timer process. Runs in all other BPQ32 processes. + + do { + + Sleep(60000); + + if (TimerInst != 0xffffffff && !IsProcess(TimerInst)) + { + // Timer owning Process has died - Force a new timer to be created + // New timer thread will detect lost process and tidy up + + Debugprintf("BPQ32 Process %d with Timer died", TimerInst); + + // If process was holding the semaphore, release it + + if (Semaphore.Flag == 1 && TimerInst == Semaphore.SemProcessID) + { + OutputDebugString("BPQ32 Process was holding Semaphore - attempting recovery\r\n"); + Debugprintf("Last Sem Call %d %x %x %x %x %x %x", SemHeldByAPI, + Sem_eax, Sem_ebx, Sem_ecx, Sem_edx, Sem_esi, Sem_edi); + Semaphore.Flag = 0; + SemHeldByAPI = 0; + } + +// KillTimer(NULL,TimerHandle); +// TimerHandle=0; +// TimerInst=0xffffffff; +// Tell_Sessions(); + + CheckforLostProcesses(); // Normally only done in timer thread, which is now dead + + // Timer can only run in BPQ32.exe + + TimerInst=0xffffffff; // So we dont keep doing it + TimerHandle = 0; // So new process attaches + + if (Closing == FALSE && AttachingProcess == FALSE) + { + OutputDebugString("BPQ32 Reloading BPQ32.exe\n"); + StartBPQ32(); + } + +// if (MinimizetoTray) +// Shell_NotifyIcon(NIM_DELETE,&niData); + } + + } while (TRUE); +} + +VOID WritetoTraceSupport(struct TNCINFO * TNC, char * Msg, int Len); + +VOID TimerProcX(); + +VOID CALLBACK TimerProc( + HWND hwnd, // handle of window for timer messages + UINT uMsg, // WM_TIMER message + UINT idEvent, // timer identifier + DWORD dwTime) // current system time +{ + KillTimer(NULL,TimerHandle); + TimerProcX(); + TimerHandle = SetTimer(NULL,0,100,lpTimerFunc); +} +VOID TimerProcX() +{ + struct _EXCEPTION_POINTERS exinfo; + + // + // Get semaphore before proceeeding + // + + GetSemaphore(&Semaphore, 2); + + // Get time since last run + + QueryPerformanceCounter(¤tTime); + + interval = (int)(currentTime.QuadPart - lastRunTime.QuadPart) / ticksPerMillisec; + lastRunTime.QuadPart = currentTime.QuadPart; + + //Debugprintf("%d", interval); + + // Process WINMORTraceQ + + while (WINMORTraceQ) + { + UINT * Buffer = Q_REM(&WINMORTraceQ); + struct TNCINFO * TNC = (struct TNCINFO * )Buffer[1]; + int Len = Buffer[2]; + char * Msg = (char *)&Buffer[3]; + + WritetoTraceSupport(TNC, Msg, Len); + RelBuff(Buffer); + } + + if (SetWindowTextQ) + SetWindowTextSupport(); + + while (WritetoConsoleQ) + { + UINT * Buffer = Q_REM(&WritetoConsoleQ); + WritetoConsoleSupport((char *)&Buffer[2]); + RelBuff(Buffer); + } + + strcpy(EXCEPTMSG, "Timer ReconfigProcessing"); + + __try + { + + if (trayMenu == NULL) + SetupTrayIcon(); + + // See if reconfigure requested + + if (CloseAllNeeded) + { + CloseAllNeeded = FALSE; + CloseAllPrograms(); + } + + if (ReconfigFlag) + { + // Only do it it timer owning process, or we could get in a real mess! + + if(TimerInst == GetCurrentProcessId()) + { + int i; + BPQVECSTRUC * HOSTVEC; + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + WSADATA WsaData; // receives data from WSAStartup + RECT cRect; + + ReconfigFlag = FALSE; + + SetupBPQDirectory(); + + WritetoConsole("Reconfiguring ...\n\n"); + OutputDebugString("BPQ32 Reconfiguring ...\n"); + + GetWindowRect(FrameWnd, &FRect); + + SaveWindowPos(70); // Rigcontrol + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR) + { + SaveWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + SaveAXIPWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + CloseDriverWindow(PORTVEC->PORTCONTROL.PORTNUMBER); + PORTVEC->PORT_EXT_ADDR(5,PORTVEC->PORTCONTROL.PORTNUMBER, NULL); // Close External Ports + } + } + PORTVEC->PORTCONTROL.PORTCLOSECODE(&PORTVEC->PORTCONTROL); + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + IPClose(); + PMClose(); + APRSClose(); + Rig_Close(); + CloseTNCEmulator(); + if (AGWActive) + AGWAPITerminate(); + + WSACleanup(); + + WL2KReports = NULL; + + Sleep(2000); + + WSAStartup(MAKEWORD(2, 0), &WsaData); + + Consoleprintf("G8BPQ AX25 Packet Switch System Version %s %s", TextVerstring, Datestring); + Consoleprintf(VerCopyright); + + Start(); + + INITIALISEPORTS(); // Restart Ports + + SetApplPorts(); + + FreeConfig(); + + for (i=1; i<68; i++) // Include Telnet, APRS and IP Vec + { + HOSTVEC=&BPQHOSTVECTOR[i-1]; + + HOSTVEC->HOSTTRACEQ=0; // Clear header (pool has been reinitialized + + if (HOSTVEC->HOSTSESSION !=0) + { + // Had a connection + + HOSTVEC->HOSTSESSION=0; + HOSTVEC->HOSTFLAGS |=3; // Disconnected + + PostMessage(HOSTVEC->HOSTHANDLE, BPQMsg, i, 4); + } + } + + // Free the APRS Appl Q + + APPL_Q = 0; + + OpenReportingSockets(); + + WritetoConsole("\n\nReconfiguration Complete\n"); + + if (IPRequired) IPActive = Init_IP(); + if (PMRequired) PMActive = Init_PM(); + + APRSActive = Init_APRS(); + + if (ISPort == 0) + IGateEnabled = 0; + + CheckDlgButton(hConsWnd, IDC_ENIGATE, IGateEnabled); + + GetClientRect(hConsWnd, &cRect); + MoveWindow(hWndBG, 0, 0, cRect.right, 26, TRUE); + if (APRSActive) + MoveWindow(hWndCons, 2, 26, cRect.right-4, cRect.bottom - 32, TRUE); + else + { + ShowWindow(GetDlgItem(hConsWnd, IDC_GPS), SW_HIDE); + MoveWindow(hWndCons, 2, 2, cRect.right-4, cRect.bottom - 4, TRUE); + } + InvalidateRect(hConsWnd, NULL, TRUE); + + RigActive = Rig_Init(); + + if (NUMBEROFTNCPORTS) + { + FreeSemaphore(&Semaphore); + InitializeTNCEmulator(); + GetSemaphore(&Semaphore, 0); + } + + FreeSemaphore(&Semaphore); + AGWActive = AGWAPIInit(); + GetSemaphore(&Semaphore, 0); + + OutputDebugString("BPQ32 Reconfiguration Complete\n"); + } + } + + + if (RigReconfigFlag) + { + // Only do it it timer owning process, or we could get in a real mess! + + if(TimerInst == GetCurrentProcessId()) + { + RigReconfigFlag = FALSE; + CloseDriverWindow(70); + Rig_Close(); + Sleep(6000); // Allow any CATPTT, HAMLIB and FLRIG threads to close + RigActive = Rig_Init(); + + WritetoConsole("Rigcontrol Reconfiguration Complete\n"); + } + } + + if (APRSReconfigFlag) + { + // Only do it it timer owning process, or we could get in a real mess! + + if(TimerInst == GetCurrentProcessId()) + { + APRSReconfigFlag = FALSE; + APRSClose(); + APRSActive = Init_APRS(); + + WritetoConsole("APRS Reconfiguration Complete\n"); + } + } + + } + #include "StdExcept.c" + + if (Semaphore.Flag && Semaphore.SemProcessID == GetCurrentProcessId()) + FreeSemaphore(&Semaphore); + + } + + strcpy(EXCEPTMSG, "Timer Processing"); + + __try + { + if (IPActive) Poll_IP(); + if (PMActive) Poll_PM(); + if (RigActive) Rig_Poll(); + + if (NeedWebMailRefresh) + DoRefreshWebMailIndex(); + + CheckGuardZone(); + + if (APRSActive) + { + Poll_APRS(); + CheckGuardZone(); + } + + CheckWL2KReportTimer(); + + CheckGuardZone(); + + TIMERINTERRUPT(); + + CheckGuardZone(); + + FreeSemaphore(&Semaphore); // SendLocation needs to get the semaphore + + if (NUMBEROFTNCPORTS) + TNCTimer(); + + if (AGWActive) + Poll_AGW(); + + DRATSPoll(); + RHPPoll(); + + CheckGuardZone(); + + strcpy(EXCEPTMSG, "HTTP Timer Processing"); + + HTTPTimer(); + + CheckGuardZone(); + + strcpy(EXCEPTMSG, "WL2K Report Timer Processing"); + + if (ReportTimer) + { + ReportTimer--; + + if (ReportTimer == 0) + { + ReportTimer = REPORTINTERVAL; + SendLocation(); + } + } + } + + #include "StdExcept.c" + + if (Semaphore.Flag && Semaphore.SemProcessID == GetCurrentProcessId()) + FreeSemaphore(&Semaphore); + + } + + CheckGuardZone(); + + return; +} + +HANDLE NPHandle; + +int (WINAPI FAR *GetModuleFileNameExPtr)() = NULL; +int (WINAPI FAR *EnumProcessesPtr)() = NULL; + +FirstInit() +{ + WSADATA WsaData; // receives data from WSAStartup + HINSTANCE ExtDriver=0; + RECT cRect; + + + // First Time Ports and Timer init + + // Moved from DLLINIT to sort out perl problem, and meet MS Guidelines on minimising DLLMain + + // Call wsastartup - most systems need winsock, and duplicate statups could be a problem + + WSAStartup(MAKEWORD(2, 0), &WsaData); + + // Load Psapi.dll if possible + + ExtDriver=LoadLibrary("Psapi.dll"); + + SetupTrayIcon(); + + if (ExtDriver) + { + GetModuleFileNameExPtr = (FARPROCX)GetProcAddress(ExtDriver,"GetModuleFileNameExA"); + EnumProcessesPtr = (FARPROCX)GetProcAddress(ExtDriver,"EnumProcesses"); + } + + timeLoadedMS = GetTickCount(); + + INITIALISEPORTS(); + + OpenReportingSockets(); + + WritetoConsole("\n"); + WritetoConsole("Port Initialisation Complete\n"); + + if (IPRequired) IPActive = Init_IP(); + if (PMRequired) PMActive = Init_PM(); + + APRSActive = Init_APRS(); + + if (APRSActive) + { + hWndBG = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 0,0,40,546, hConsWnd, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "Enable IGate", WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, + 8,0,90,24, hConsWnd, (HMENU)-1, hInstance, NULL); + + CreateWindowEx(0, "BUTTON", "", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP, + 95,1,18,24, hConsWnd, (HMENU)IDC_ENIGATE, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "IGate State - Disconnected", + WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, 125, 0, 195, 24, hConsWnd, (HMENU)IGATESTATE, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "IGATE Stats - Msgs 0 Local Stns 0", + WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, 320, 0, 240, 24, hConsWnd, (HMENU)IGATESTATS, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "GPS Off", + WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE, 560, 0, 80, 24, hConsWnd, (HMENU)IDC_GPS, hInstance, NULL); + } + + if (ISPort == 0) + IGateEnabled = 0; + + CheckDlgButton(hConsWnd, IDC_ENIGATE, IGateEnabled); + + GetClientRect(hConsWnd, &cRect); + MoveWindow(hWndBG, 0, 0, cRect.right, 26, TRUE); + if (APRSActive) + MoveWindow(hWndCons, 2, 26, cRect.right-4, cRect.bottom - 32, TRUE); + else + { + ShowWindow(GetDlgItem(hConsWnd, IDC_GPS), SW_HIDE); + MoveWindow(hWndCons, 2, 2, cRect.right-4, cRect.bottom - 4, TRUE); + } + InvalidateRect(hConsWnd, NULL, TRUE); + + RigActive = Rig_Init(); + + _beginthread(MonitorThread,0,0); + + TimerHandle=SetTimer(NULL,0,100,lpTimerFunc); + TimerInst=GetCurrentProcessId(); + SessHandle = SetTimer(NULL, 0, 5000, lpSetupTermSessions); + + // If ARIF reporting is enabled write a Trimode Like ini for RMS Analyser + + if (ADIFLogEnabled) + ADIFWriteFreqList(); + + OutputDebugString("BPQ32 Port Initialisation Complete\n"); + + if (needAIS) + initAIS(); + + if (needADSB) + initADSB(); + + return 0; +} + +Check_Timer() +{ + if (Closing) + return 0; + + if (Semaphore.Flag) + return 0; + + if (InitDone == (void *)-1) + { + GetSemaphore(&Semaphore, 3); + Sleep(15000); + FreeSemaphore(&Semaphore); + exit (0); + } + + if (FirstInitDone == 0) + { + GetSemaphore(&Semaphore, 3); + + if (_stricmp(pgm, "bpq32.exe") == 0) + { + FirstInit(); + FreeSemaphore(&Semaphore); + if (NUMBEROFTNCPORTS) + InitializeTNCEmulator(); + + AGWActive = AGWAPIInit(); + FirstInitDone=1; // Only init in BPQ32.exe + return 0; + } + else + { + FreeSemaphore(&Semaphore); + return 0; + } + } + + if (TimerHandle == 0 && FirstInitDone == 1) + { + WSADATA WsaData; // receives data from WSAStartup + HINSTANCE ExtDriver=0; + RECT cRect; + + // Only attach timer to bpq32.exe + + if (_stricmp(pgm, "bpq32.exe") != 0) + { + return 0; + } + + GetSemaphore(&Semaphore, 3); + OutputDebugString("BPQ32 Reinitialising External Ports and Attaching Timer\n"); + + if (!ProcessConfig()) + { + ShowWindow(hConsWnd, SW_RESTORE); + SendMessage(hConsWnd, WM_PAINT, 0, 0); + SetForegroundWindow(hConsWnd); + + InitDone = (void *)-1; + FreeSemaphore(&Semaphore); + + MessageBox(NULL,"Configuration File Error","BPQ32",MB_ICONSTOP); + + exit (0); + } + + GetVersionInfo("bpq32.dll"); + + SetupConsoleWindow(); + + Consoleprintf("G8BPQ AX25 Packet Switch System Version %s %s", TextVerstring, Datestring); + Consoleprintf(VerCopyright); + Consoleprintf("Reinitialising..."); + + SetupBPQDirectory(); + + Sleep(1000); // Allow time for sockets to close + + WSAStartup(MAKEWORD(2, 0), &WsaData); + + // Load Psapi.dll if possible + + ExtDriver = LoadLibrary("Psapi.dll"); + + SetupTrayIcon(); + + if (ExtDriver) + { + GetModuleFileNameExPtr = (FARPROCX)GetProcAddress(ExtDriver,"GetModuleFileNameExA"); + EnumProcessesPtr = (FARPROCX)GetProcAddress(ExtDriver,"EnumProcesses"); + } + + Start(); + + INITIALISEPORTS(); + + OpenReportingSockets(); + + NODESINPROGRESS = 0; + CURRENTNODE = 0; + + SetApplPorts(); + + WritetoConsole("\n\nPort Reinitialisation Complete\n"); + + BPQMsg = RegisterWindowMessage(BPQWinMsg); + + CreateMutex(NULL,TRUE,"BPQLOCKMUTEX"); + +// NPHandle=CreateNamedPipe("\\\\.\\pipe\\BPQ32pipe", +// PIPE_ACCESS_DUPLEX,0,64,4096,4096,1000,NULL); + + if (IPRequired) IPActive = Init_IP(); + if (PMRequired) PMActive = Init_PM(); + + RigActive = Rig_Init(); + APRSActive = Init_APRS(); + + if (ISPort == 0) + IGateEnabled = 0; + + CheckDlgButton(hConsWnd, IDC_ENIGATE, IGateEnabled); + + GetClientRect(hConsWnd, &cRect); + MoveWindow(hWndBG, 0, 0, cRect.right, 26, TRUE); + + if (APRSActive) + MoveWindow(hWndCons, 2, 26, cRect.right-4, cRect.bottom - 32, TRUE); + else + { + ShowWindow(GetDlgItem(hConsWnd, IDC_GPS), SW_HIDE); + MoveWindow(hWndCons, 2, 2, cRect.right-4, cRect.bottom - 4, TRUE); + } + InvalidateRect(hConsWnd, NULL, TRUE); + + FreeConfig(); + + _beginthread(MonitorThread,0,0); + + ReportTimer = 0; + + OpenReportingSockets(); + + FreeSemaphore(&Semaphore); + + if (NUMBEROFTNCPORTS) + InitializeTNCEmulator(); + + AGWActive = AGWAPIInit(); + + if (StartMinimized) + if (MinimizetoTray) + ShowWindow(FrameWnd, SW_HIDE); + else + ShowWindow(FrameWnd, SW_SHOWMINIMIZED); + else + ShowWindow(FrameWnd, SW_RESTORE); + + TimerHandle=SetTimer(NULL,0,100,lpTimerFunc); + TimerInst=GetCurrentProcessId(); + SessHandle = SetTimer(NULL, 0, 5000, lpSetupTermSessions); + + return (1); + } + + return (0); +} + +DllExport INT APIENTRY CheckTimer() +{ + return Check_Timer(); +} + +Tell_Sessions() +{ + // + // Post a message to all listening sessions, so they call the + // API, and cause a new timer to be allocated + // + HWND hWnd; + int i; + + for (i=1;i<65;i++) + { + if (BPQHOSTVECTOR[i-1].HOSTFLAGS & 0x80) + { + hWnd = BPQHOSTVECTOR[i-1].HOSTHANDLE; + PostMessage(hWnd, BPQMsg,i, 1); + PostMessage(hWnd, BPQMsg,i, 2); + } + } + return (0); +} + +BOOL APIENTRY DllMain(HANDLE hInst, DWORD ul_reason_being_called, LPVOID lpReserved) +{ + DWORD n; + char buf[350]; + + int i; + unsigned int ProcessID; + + OSVERSIONINFO osvi; + + memset(&osvi, 0, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&osvi); + + + switch( ul_reason_being_called ) + { + case DLL_PROCESS_ATTACH: + + if (sizeof(HDLCDATA) > PORTENTRYLEN + 200) // 200 bytes of Hardwaredata + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"BPQ32 Too much HDLC data - Recompile","BPQ32", MB_OK); + return 0; + } + + if (sizeof(struct KISSINFO) > PORTENTRYLEN + 200) // 200 bytes of Hardwaredata + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"BPQ32 Too much KISS data - Recompile","BPQ32", MB_OK); + return 0; + } + + if (sizeof(struct _EXTPORTDATA) > PORTENTRYLEN + 200) // 200 bytes of Hardwaredata + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"BPQ32 Too much _EXTPORTDATA data - Recompile","BPQ32", MB_OK); + return 0; + } + + if (sizeof(LINKTABLE) != LINK_TABLE_LEN) + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"L2 LINK Table .c and .asm mismatch - fix and rebuild","BPQ32", MB_OK); + return 0; + } + if (sizeof(struct ROUTE) != ROUTE_LEN) + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"ROUTE Table .c and .asm mismatch - fix and rebuild", "BPQ32", MB_OK); + return 0; + } + + if (sizeof(struct DEST_LIST) != DEST_LIST_LEN) + { + // Catastrophic - Refuse to load + + MessageBox(NULL,"NODES Table .c and .asm mismatch - fix and rebuild", "BPQ32", MB_OK); + return 0; + } + + GetSemaphore(&Semaphore, 4); + + BPQHOSTVECPTR = &BPQHOSTVECTOR[0]; + + LoadToolHelperRoutines(); + + Our_PID = GetCurrentProcessId(); + + QueryPerformanceFrequency(&lpFrequency); + + ticksPerMillisec = (int)lpFrequency.QuadPart / 1000; + + lastRunTime.QuadPart = lpFrequency.QuadPart; + + GetProcess(Our_PID, pgm); + + if (_stricmp(pgm, "regsvr32.exe") == 0 || _stricmp(pgm, "bpqcontrol.exe") == 0) + { + AttachedProcesses++; // We will get a detach + FreeSemaphore(&Semaphore); + return 1; + } + + if (_stricmp(pgm,"BPQ32.exe") == 0) + BPQ32_EXE = TRUE; + + if (_stricmp(pgm,"BPQMailChat.exe") == 0) + IncludesMail = TRUE; + + if (_stricmp(pgm,"BPQMail.exe") == 0) + IncludesMail = TRUE; + + if (_stricmp(pgm,"BPQChat.exe") == 0) + IncludesChat = TRUE; + + if (FirstEntry) // If loaded by BPQ32.exe, dont close it at end + { + FirstEntry = 0; + if (BPQ32_EXE) + CloseLast = FALSE; + } + else + { + if (BPQ32_EXE && AttachingProcess == 0) + { + AttachedProcesses++; // We will get a detach + FreeSemaphore(&Semaphore); + MessageBox(NULL,"BPQ32.exe is already running\r\n\r\nIt should only be run once", "BPQ32", MB_OK); + return 0; + } + } + + if (_stricmp(pgm,"BPQTelnetServer.exe") == 0) + { + MessageBox(NULL,"BPQTelnetServer is no longer supported\r\n\r\nUse the TelnetServer in BPQ32.dll", "BPQ32", MB_OK); + AttachedProcesses++; // We will get a detach + FreeSemaphore(&Semaphore); + return 0; + } + + if (_stricmp(pgm,"BPQUIUtil.exe") == 0) + { + MessageBox(NULL,"BPQUIUtil is now part of BPQ32.dll\r\nBPQUIUtil.exe cannot be run\r\n", "BPQ32", MB_OK); + AttachedProcesses++; // We will get a detach + FreeSemaphore(&Semaphore); + return 0; + } + + if (_stricmp(pgm,"BPQMailChat.exe") == 0) + { + MessageBox(NULL,"BPQMailChat is obsolete. Run BPQMail.exe and/or BPQChat.exe instead", "BPQ32", MB_OK); + AttachedProcesses++; // We will get a detach + FreeSemaphore(&Semaphore); + return 0; + } + AuthorisedProgram = TRUE; + + if (InitDone == 0) + { +// #pragma warning(push) +// #pragma warning(disable : 4996) + +// if (_winver < 0x0600) +// #pragma warning(pop) +// { +// // Below Vista +// +// REGTREE = HKEY_LOCAL_MACHINE; +// strcpy(REGTREETEXT, "HKEY_LOCAL_MACHINE"); +// } + + hInstance=hInst; + + Mutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"BPQLOCKMUTEX"); + + if (Mutex != NULL) + { + OutputDebugString("Another BPQ32.dll is loaded\n"); + i=MessageBox(NULL,"BPQ32 DLL already loaded from another directory\nIf you REALLY want this, hit OK, else hit Cancel","BPQ32",MB_OKCANCEL); + FreeSemaphore(&Semaphore); + + if (i != IDOK) return (0); + + CloseHandle(Mutex); + } + + if (!BPQ32_EXE) + { + if (CheckifBPQ32isLoaded() == FALSE) // Start BPQ32.exe if needed + { + // Wasn't Loaded, so we have started it, and should let it init system + + goto SkipInit; + } + } + + GetVersionInfo("bpq32.dll"); + + sprintf (SIGNONMSG, "G8BPQ AX25 Packet Switch System Version %s %s\r\n%s\r\n", + TextVerstring, Datestring, VerCopyright); + + SESSHDDRLEN = sprintf(SESSIONHDDR, "G8BPQ Network System %s for Win32 (", TextVerstring); + + SetupConsoleWindow(); + SetupBPQDirectory(); + + if (!ProcessConfig()) + { + StartMinimized = FALSE; + MinimizetoTray = FALSE; + ShowWindow(FrameWnd, SW_MAXIMIZE); + ShowWindow(hConsWnd, SW_MAXIMIZE); + ShowWindow(StatusWnd, SW_HIDE); + + SendMessage(hConsWnd, WM_PAINT, 0, 0); + SetForegroundWindow(hConsWnd); + + InitDone = (void *)-1; + FreeSemaphore(&Semaphore); + + MessageBox(NULL,"Configuration File Error\r\nProgram will close in 15 seconds","BPQ32",MB_ICONSTOP); + + return (0); + } + + Consoleprintf("G8BPQ AX25 Packet Switch System Version %s %s", TextVerstring, Datestring); + Consoleprintf(VerCopyright); + + if (Start() !=0) + { + Sleep(3000); + FreeSemaphore(&Semaphore); + return (0); + } + else + { + SetApplPorts(); + + GetUIConfig(); + + InitDone = &InitDone; + BPQMsg = RegisterWindowMessage(BPQWinMsg); +// TimerHandle=SetTimer(NULL,0,100,lpTimerFunc); +// TimerInst=GetCurrentProcessId(); + +/* Mutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"BPQLOCKMUTEX"); + + if (Mutex != NULL) + { + OutputDebugString("Another BPQ32.dll is loaded\n"); + MessageBox(NULL,"BPQ32 DLL already loaded from another directory","BPQ32",MB_ICONSTOP); + FreeSemaphore(&Semaphore); + return (0); + } + +*/ + Mutex=CreateMutex(NULL,TRUE,"BPQLOCKMUTEX"); + +// CreatePipe(&H1,&H2,NULL,1000); + +// GetLastError(); + +// NPHandle=CreateNamedPipe("\\\\.\\pipe\\BPQ32pipe", +// PIPE_ACCESS_DUPLEX,0,64,4096,4096,1000,NULL); + +// GetLastError(); + +/* + // + // Read SYSOP password + // + + if (PWTEXT[0] == 0) + { + handle = OpenConfigFile("PASSWORD.BPQ"); + + if (handle == INVALID_HANDLE_VALUE) + { + WritetoConsole("Can't open PASSWORD.BPQ\n"); + PWLen=0; + PWTEXT[0]=0; + } + else + { + ReadFile(handle,PWTEXT,78,&n,NULL); + CloseHandle(handle); + } + } +*/ + for (i=0;PWTEXT[i] > 0x20;i++); //Scan for cr or null + PWLen=i; + + } + } + else + { + if (InitDone != &InitDone) + { + MessageBox(NULL,"BPQ32 DLL already loaded at another address","BPQ32",MB_ICONSTOP); + FreeSemaphore(&Semaphore); + return (0); + } + } + + // Run timer monitor thread in all processes - it is possible for the TImer thread not to be the first thread +SkipInit: + + _beginthread(MonitorTimerThread,0,0); + + FreeSemaphore(&Semaphore); + + AttachedPIDList[AttachedProcesses++] = GetCurrentProcessId(); + + if (_stricmp(pgm,"bpq32.exe") == 0 && AttachingProcess == 1) AttachingProcess = 0; + + GetProcess(GetCurrentProcessId(),pgm); + n=sprintf(buf,"BPQ32 DLL Attach complete - Program %s - %d Process(es) Attached\n",pgm,AttachedProcesses); + OutputDebugString(buf); + + // Set up local variables + + MCOM=1; + MTX=1; + MMASK=0xffffffffffffffff; + +// if (StartMinimized) +// if (MinimizetoTray) +// ShowWindow(FrameWnd, SW_HIDE); +// else +// ShowWindow(FrameWnd, SW_SHOWMINIMIZED); +// else +// ShowWindow(FrameWnd, SW_RESTORE); + + return 1; + + case DLL_THREAD_ATTACH: + + return 1; + + case DLL_THREAD_DETACH: + + return 1; + + case DLL_PROCESS_DETACH: + + if (_stricmp(pgm,"BPQMailChat.exe") == 0) + IncludesMail = FALSE; + + if (_stricmp(pgm,"BPQChat.exe") == 0) + IncludesChat = FALSE; + + ProcessID=GetCurrentProcessId(); + + Debugprintf("BPQ32 Process %d Detaching", ProcessID); + + // Release any streams that the app has failed to release + + for (i=1;i<65;i++) + { + if (BPQHOSTVECTOR[i-1].STREAMOWNER == ProcessID) + { + // If connected, disconnect + + SessionControl(i, 2, 0); + DeallocateStream(i); + } + } + + // Remove any Tray Icon Entries + + for( i = 0; i < 100; ++i ) + { + if (PIDArray[i] == ProcessID) + { + char Log[80]; + hWndArray[i] = 0; + sprintf(Log,"BPQ32 Removing Tray Item %s\n", PopupText[i]); + OutputDebugString(Log); + DeleteMenu(trayMenu,TRAYBASEID+i,MF_BYCOMMAND); + } + } + + if (Mutex) CloseHandle(Mutex); + + // Remove our entry from PID List + + for (i=0; i< AttachedProcesses; i++) + if (AttachedPIDList[i] == ProcessID) + break; + + for (; i< AttachedProcesses; i++) + { + AttachedPIDList[i]=AttachedPIDList[i+1]; + } + + AttachedProcesses--; + + if (TimerInst == ProcessID) + { + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + + OutputDebugString("BPQ32 Process with Timer closing\n"); + + // Call Port Close Routines + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR && PORTVEC->DLLhandle == NULL) // Don't call if real .dll - it's not there! + { + SaveWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + SaveAXIPWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + PORTVEC->PORT_EXT_ADDR(5,PORTVEC->PORTCONTROL.PORTNUMBER, NULL); // Close External Ports + } + } + + PORTVEC->PORTCONTROL.PORTCLOSECODE(&PORTVEC->PORTCONTROL); + + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + + IPClose(); + PMClose(); + APRSClose(); + Rig_Close(); + CloseTNCEmulator(); + if (AGWActive) + AGWAPITerminate(); + + upnpClose(); + + WSACleanup(); + WSAGetLastError(); + + if (MinimizetoTray) + Shell_NotifyIcon(NIM_DELETE,&niData); + + if (hConsWnd) DestroyWindow(hConsWnd); + + KillTimer(NULL,TimerHandle); + TimerHandle=0; + TimerInst=0xffffffff; + + if (AttachedProcesses && Closing == FALSE && AttachingProcess == 0) // Other processes + { + OutputDebugString("BPQ32 Reloading BPQ32.exe\n"); + StartBPQ32(); + } + } + else + { + // Not Timer Process + + if (AttachedProcesses == 1 && CloseLast) // Only bpq32.exe left + { + Debugprintf("Only BPQ32.exe running - close it"); + CloseAllNeeded = TRUE; + } + } + + if (AttachedProcesses < 2) + { + if (AUTOSAVE) + SaveNodes(); + if (AUTOSAVEMH) + SaveMH(); + + if (needAIS) + SaveAIS(); + } + if (AttachedProcesses == 0) + { + Closing = TRUE; + KillTimer(NULL,TimerHandle); + + if (MinimizetoTray) + Shell_NotifyIcon(NIM_DELETE,&niData); + + // Unload External Drivers + + { + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10 && PORTVEC->DLLhandle) + FreeLibrary(PORTVEC->DLLhandle); + + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + } + } + + GetProcess(GetCurrentProcessId(),pgm); + n=sprintf(buf,"BPQ32 DLL Detach complete - Program %s - %d Process(es) Attached\n",pgm,AttachedProcesses); + OutputDebugString(buf); + + return 1; + } + return 1; +} + +DllExport int APIENTRY CloseBPQ32() +{ + // Unload External Drivers + + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + int i; + int ProcessID = GetCurrentProcessId(); + + if (Semaphore.Flag == 1 && ProcessID == Semaphore.SemProcessID) + { + OutputDebugString("BPQ32 Process holding Semaphore called CloseBPQ32 - attempting recovery\r\n"); + Debugprintf("Last Sem Call %d %x %x %x %x %x %x", SemHeldByAPI, + Sem_eax, Sem_ebx, Sem_ecx, Sem_edx, Sem_esi, Sem_edi); + + Semaphore.Flag = 0; + SemHeldByAPI = 0; + } + + if (TimerInst == ProcessID) + { + OutputDebugString("BPQ32 Process with Timer called CloseBPQ32\n"); + + if (MinimizetoTray) + Shell_NotifyIcon(NIM_DELETE,&niData); + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR) + { + PORTVEC->PORT_EXT_ADDR(5,PORTVEC->PORTCONTROL.PORTNUMBER, NULL); + } + } + PORTVEC->PORTCONTROL.PORTCLOSECODE(&PORTVEC->PORTCONTROL); + + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + KillTimer(NULL,TimerHandle); + TimerHandle=0; + TimerInst=0xffffffff; + + IPClose(); + PMClose(); + APRSClose(); + Rig_Close(); + if (AGWActive) + AGWAPITerminate(); + + upnpClose(); + + CloseTNCEmulator(); + WSACleanup(); + + if (hConsWnd) DestroyWindow(hConsWnd); + + Debugprintf("AttachedProcesses %d ", AttachedProcesses); + + if (AttachedProcesses > 1 && Closing == FALSE && AttachingProcess == 0) // Other processes + { + OutputDebugString("BPQ32 Reloading BPQ32.exe\n"); + StartBPQ32(); + } + } + + return 0; +} + +BOOL CopyReg(HKEY hKeyIn, HKEY hKeyOut); + +VOID SetupBPQDirectory() +{ + HKEY hKey = 0; + HKEY hKeyIn = 0; + HKEY hKeyOut = 0; + int disp; + int retCode,Type,Vallen=MAX_PATH,i; + char msg[512]; + char ValfromReg[MAX_PATH] = ""; + char DLLName[256]="Not Known"; + char LogDir[256]; + char Time[64]; + +/* +•NT4 was/is '4' +•Win 95 is 4.00.950 +•Win 98 is 4.10.1998 +•Win 98 SE is 4.10.2222 +•Win ME is 4.90.3000 +•2000 is NT 5.0.2195 +•XP is actually 5.1 +•Vista is 6.0 +•Win7 is 6.1 + + i = _osver; / Build + i = _winmajor; + i = _winminor; +*/ +/* +#pragma warning(push) +#pragma warning(disable : 4996) + +if (_winver < 0x0600) +#pragma warning(pop) + { + // Below Vista + + REGTREE = HKEY_LOCAL_MACHINE; + strcpy(REGTREETEXT, "HKEY_LOCAL_MACHINE"); + ValfromReg[0] = 0; + } + else +*/ + { + if (_stricmp(pgm, "regsvr32.exe") == 0) + { + Debugprintf("BPQ32 loaded by regsvr32.exe - Registry not copied"); + } + else + { + // If necessary, move reg from HKEY_LOCAL_MACHINE to HKEY_CURRENT_USER + + retCode = RegOpenKeyEx (HKEY_LOCAL_MACHINE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, + KEY_READ, + &hKeyIn); + + retCode = RegCreateKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\G8BPQ\\BPQ32", 0, 0, 0, KEY_ALL_ACCESS, NULL, &hKeyOut, &disp); + + // See if Version Key exists in HKEY_CURRENT_USER - if it does, we have already done the copy + + Vallen = MAX_PATH; + retCode = RegQueryValueEx(hKeyOut, "Version" ,0 , &Type,(UCHAR *)&msg, &Vallen); + + if (retCode != ERROR_SUCCESS) + if (hKeyIn) + CopyReg(hKeyIn, hKeyOut); + + RegCloseKey(hKeyIn); + RegCloseKey(hKeyOut); + } + } + + GetModuleFileName(hInstance,DLLName,256); + + BPQDirectory[0]=0; + + retCode = RegOpenKeyEx (REGTREE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, + KEY_QUERY_VALUE, + &hKey); + + if (retCode == ERROR_SUCCESS) + { + // Try "BPQ Directory" + + Vallen = MAX_PATH; + retCode = RegQueryValueEx(hKey,"BPQ Directory",0, + &Type,(UCHAR *)&ValfromReg,&Vallen); + + if (retCode == ERROR_SUCCESS) + { + if (strlen(ValfromReg) == 2 && ValfromReg[0] == '"' && ValfromReg[1] == '"') + ValfromReg[0]=0; + } + + if (ValfromReg[0] == 0) + { + // BPQ Directory absent or = "" - try "Config File Location" + + Vallen = MAX_PATH; + + retCode = RegQueryValueEx(hKey,"Config File Location",0, + &Type,(UCHAR *)&ValfromReg,&Vallen); + + if (retCode == ERROR_SUCCESS) + { + if (strlen(ValfromReg) == 2 && ValfromReg[0] == '"' && ValfromReg[1] == '"') + ValfromReg[0]=0; + } + } + + if (ValfromReg[0] == 0) GetCurrentDirectory(MAX_PATH, ValfromReg); + + // Get StartMinimized and MinimizetoTray flags + + Vallen = 4; + retCode = RegQueryValueEx(hKey, "Start Minimized", 0, &Type, (UCHAR *)&StartMinimized, &Vallen); + + Vallen = 4; + retCode = RegQueryValueEx(hKey, "Minimize to Tray", 0, &Type, (UCHAR *)&MinimizetoTray, &Vallen); + + ExpandEnvironmentStrings(ValfromReg, BPQDirectory, MAX_PATH); + + // Also get "BPQ Program Directory" + + ValfromReg[0] = 0; + Vallen = MAX_PATH; + + retCode = RegQueryValueEx(hKey, "BPQ Program Directory",0 , &Type, (UCHAR *)&ValfromReg, &Vallen); + + if (retCode == ERROR_SUCCESS) + ExpandEnvironmentStrings(ValfromReg, BPQProgramDirectory, MAX_PATH); + + // And Log Directory + + ValfromReg[0] = 0; + Vallen = MAX_PATH; + + retCode = RegQueryValueEx(hKey, "Log Directory",0 , &Type, (UCHAR *)&ValfromReg, &Vallen); + + if (retCode == ERROR_SUCCESS) + ExpandEnvironmentStrings(ValfromReg, LogDirectory, MAX_PATH); + + RegCloseKey(hKey); + } + + strcpy(ConfigDirectory, BPQDirectory); + + if (LogDirectory[0] == 0) + strcpy(LogDirectory, BPQDirectory); + + if (BPQProgramDirectory[0] == 0) + strcpy(BPQProgramDirectory, BPQDirectory); + + sprintf(msg,"BPQ32 Ver %s Loaded from: %s by %s\n", VersionString, DLLName, pgm); + WritetoConsole(msg); + OutputDebugString(msg); + FormatTime3(Time, time(NULL)); + sprintf(msg,"Loaded %s\n", Time); + WritetoConsole(msg); + OutputDebugString(msg); + +#pragma warning(push) +#pragma warning(disable : 4996) + +#if _MSC_VER >= 1400 + +#define _winmajor 6 +#define _winminor 0 + +#endif + + i=sprintf(msg,"Windows Ver %d.%d, Using Registry Key %s\n" ,_winmajor, _winminor, REGTREETEXT); + +#pragma warning(pop) + + WritetoConsole(msg); + OutputDebugString(msg); + + i=sprintf(msg,"BPQ32 Using config from: %s\n\n",BPQDirectory); + WritetoConsole(&msg[6]); + msg[i-1]=0; + OutputDebugString(msg); + + // Don't write the Version Key if loaded by regsvr32.exe (Installer is running with Admin rights, + // so will write the wrong tree on ) + + if (_stricmp(pgm, "regsvr32.exe") == 0) + { + Debugprintf("BPQ32 loaded by regsvr32.exe - Version String not written"); + } + else + { + retCode = RegCreateKeyEx(REGTREE, "SOFTWARE\\G8BPQ\\BPQ32", 0, 0, 0, KEY_ALL_ACCESS, NULL, &hKey, &disp); + + sprintf(msg,"%d,%d,%d,%d", Ver[0], Ver[1], Ver[2], Ver[3]); + retCode = RegSetValueEx(hKey, "Version",0, REG_SZ,(BYTE *)msg, strlen(msg) + 1); + + RegCloseKey(hKey); + } + + // Make sure Logs Directory exists + + sprintf(LogDir, "%s/Logs", LogDirectory); + + CreateDirectory(LogDir, NULL); + + return; +} + +HANDLE OpenConfigFile(char *fn) +{ + HANDLE handle; + UCHAR Value[MAX_PATH]; + FILETIME LastWriteTime; + SYSTEMTIME Time; + char Msg[256]; + + + // If no directory, use current + if (BPQDirectory[0] == 0) + { + strcpy(Value,fn); + } + else + { + strcpy(Value,BPQDirectory); + strcat(Value,"\\"); + strcat(Value,fn); + } + + handle = CreateFile(Value, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + GetFileTime(handle, NULL, NULL, &LastWriteTime); + FileTimeToSystemTime(&LastWriteTime, &Time); + + sprintf(Msg,"BPQ32 Config File %s Created %.2d:%.2d %d/%.2d/%.2d\n", Value, + Time.wHour, Time.wMinute, Time.wYear, Time.wMonth, Time.wDay); + + OutputDebugString(Msg); + + return(handle); +} + +#ifdef _WIN64 +int BPQHOSTAPI() +{ + return 0; +} +#endif + + +DllExport int APIENTRY GETBPQAPI() +{ + return (int)BPQHOSTAPI; +} + +//DllExport UINT APIENTRY GETMONDECODE() +//{ +// return (UINT)MONDECODE; +//} + + +DllExport INT APIENTRY BPQAPI(int Fn, char * params) +{ + +/* +; +; BPQ HOST MODE SUPPORT CODE +; +; 22/11/95 +; +; MOVED FROM TNCODE.ASM COS CONITIONALS WERE GETTING TOO COMPLICATED +; (OS2 VERSION HAD UPSET KANT VERISON +; +; +*/ + + +/* + + BPQHOSTPORT: +; +; SPECIAL INTERFACE, MAINLY FOR EXTERNAL HOST MODE SUPPORT PROGS +; +; COMMANDS SUPPORTED ARE +; +; AH = 0 Get node/switch version number and description. On return +; AH='B',AL='P',BH='Q',BL=' ' +; DH = major version number and DL = minor version number. +; +; +; AH = 1 Set application mask to value in DL (or even DX if 16 +; applications are ever to be supported). +; +; Set application flag(s) to value in CL (or CX). +; whether user gets connected/disconnected messages issued +; by the node etc. +; +; +; AH = 2 Send frame in ES:SI (length CX) +; +; +; AH = 3 Receive frame into buffer at ES:DI, length of frame returned +; in CX. BX returns the number of outstanding frames still to +; be received (ie. after this one) or zero if no more frames +; (ie. this is last one). +; +; +; +; AH = 4 Get stream status. Returns: +; +; CX = 0 if stream disconnected or CX = 1 if stream connected +; DX = 0 if no change of state since last read, or DX = 1 if +; the connected/disconnected state has changed since +; last read (ie. delta-stream status). +; +; +; +; AH = 6 Session control. +; +; CX = 0 Conneect - _APPLMASK in DL +; CX = 1 connect +; CX = 2 disconnect +; CX = 3 return user to node +; +; +; AH = 7 Get buffer counts for stream. Returns: +; +; AX = number of status change messages to be received +; BX = number of frames queued for receive +; CX = number of un-acked frames to be sent +; DX = number of buffers left in node +; SI = number of trace frames queued for receive +; +;AH = 8 Port control/information. Called with a stream number +; in AL returns: +; +; AL = Radio port on which channel is connected (or zero) +; AH = SESSION TYPE BITS +; BX = L2 paclen for the radio port +; CX = L2 maxframe for the radio port +; DX = L4 window size (if L4 circuit, or zero) +; ES:DI = CALLSIGN + +;AH = 9 Fetch node/application callsign & alias. AL = application +; number: +; +; 0 = node +; 1 = BBS +; 2 = HOST +; 3 = SYSOP etc. etc. +; +; Returns string with alias & callsign or application name in +; user's buffer pointed to by ES:SI length CX. For example: +; +; "WORCS:G8TIC" or "TICPMS:G8TIC-10". +; +; +; AH = 10 Unproto transmit frame. Data pointed to by ES:SI, of +; length CX, is transmitted as a HDLC frame on the radio +; port (not stream) in AL. +; +; +; AH = 11 Get Trace (RAW Data) Frame into ES:DI, +; Length to CX, Timestamp to AX +; +; +; AH = 12 Update Switch. At the moment only Beacon Text may be updated +; DX = Function +; 1=update BT. ES:SI, Len CX = Text +; 2=kick off nodes broadcast +; +; AH = 13 Allocate/deallocate stream +; If AL=0, return first free stream +; If AL>0, CL=1, Allocate stream. If aleady allocated, +; return CX nonzero, else allocate, and return CX=0 +; If AL>0, CL=2, Release stream +; +; +; AH = 14 Internal Interface for IP Router +; +; Send frame - to NETROM L3 if DL=0 +; to L2 Session if DL<>0 +; +; +; AH = 15 Get interval timer + + +*/ + + + switch(Fn) + { + + case CHECKLOADED: + + params[0]=MAJORVERSION; + params[1]=MINORVERSION; + params[2]=QCOUNT; + + return (1); + } + return 0; +} + +DllExport int APIENTRY InitSwitch() +{ + return (0); +} + +/*DllExport int APIENTRY SwitchTimer() +{ + GetSemaphore((&Semaphore); + + TIMERINTERRUPT(); + + FreeSemaphore(&Semaphore); + + return (0); +} +*/ +DllExport int APIENTRY GetFreeBuffs() +{ +// Returns number of free buffers +// (BPQHOST function 7 (part)). + return (QCOUNT); +} + +DllExport UCHAR * APIENTRY GetNodeCall() +{ + return (&MYNODECALL); +} + + +DllExport UCHAR * APIENTRY GetNodeAlias() +{ + return (&MYALIASTEXT[0]); +} + +DllExport UCHAR * APIENTRY GetBBSCall() +{ + return (UCHAR *)(&APPLCALLTABLE[0].APPLCALL_TEXT); +} + + +DllExport UCHAR * APIENTRY GetBBSAlias() +{ + return (UCHAR *)(&APPLCALLTABLE[0].APPLALIAS_TEXT); +} + +DllExport VOID APIENTRY GetApplCallVB(int Appl, char * ApplCall) +{ + if (Appl < 1 || Appl > NumberofAppls ) return; + + strncpy(ApplCall,(char *)&APPLCALLTABLE[Appl-1].APPLCALL_TEXT, 10); +} + +BOOL UpdateNodesForApp(int Appl); + +DllExport BOOL APIENTRY SetApplCall(int Appl, char * NewCall) +{ + char Call[10]=" "; + int i; + + if (Appl < 1 || Appl > NumberofAppls ) return FALSE; + + i=strlen(NewCall); + + if (i > 10) i=10; + + strncpy(Call,NewCall,i); + + strncpy((char *)&APPLCALLTABLE[Appl-1].APPLCALL_TEXT,Call,10); + + if (!ConvToAX25(Call,APPLCALLTABLE[Appl-1].APPLCALL)) return FALSE; + + UpdateNodesForApp(Appl); + + return TRUE; + +} + +DllExport BOOL APIENTRY SetApplAlias(int Appl, char * NewCall) +{ + char Call[10]=" "; + int i; + + if (Appl < 1 || Appl > NumberofAppls ) return FALSE; + + i=strlen(NewCall); + + if (i > 10) i=10; + + strncpy(Call,NewCall,i); + + strncpy((char *)&APPLCALLTABLE[Appl-1].APPLALIAS_TEXT,Call,10); + + if (!ConvToAX25(Call,APPLCALLTABLE[Appl-1].APPLALIAS)) return FALSE; + + UpdateNodesForApp(Appl); + + return TRUE; + +} + + + +DllExport BOOL APIENTRY SetApplQual(int Appl, int NewQual) +{ + if (Appl < 1 || Appl > NumberofAppls ) return FALSE; + + APPLCALLTABLE[Appl-1].APPLQUAL=NewQual; + + UpdateNodesForApp(Appl); + + return TRUE; + +} + + +BOOL UpdateNodesForApp(int Appl) +{ + int App=Appl-1; + int DestLen = sizeof (struct DEST_LIST); + int n = MAXDESTS; + + struct DEST_LIST * DEST = APPLCALLTABLE[App].NODEPOINTER; + APPLCALLS * APPL=&APPLCALLTABLE[App]; + + if (DEST == NULL) + { + // No dest at the moment. If we have valid call and Qual, create an entry + + if (APPLCALLTABLE[App].APPLQUAL == 0) return FALSE; + + if (APPLCALLTABLE[App].APPLCALL[0] < 41) return FALSE; + + + GetSemaphore(&Semaphore, 5); + + DEST = DESTS; + + while (n--) + { + if (DEST->DEST_CALL[0] == 0) // Spare + break; + } + + if (n == 0) + { + // no dests + + FreeSemaphore(&Semaphore); + return FALSE; + } + + NUMBEROFNODES++; + APPL->NODEPOINTER = DEST; + + memmove (DEST->DEST_CALL,APPL->APPLCALL,13); + + DEST->DEST_STATE=0x80; // SPECIAL ENTRY + + DEST->NRROUTE[0].ROUT_QUALITY = (BYTE)APPL->APPLQUAL; + DEST->NRROUTE[0].ROUT_OBSCOUNT = 255; + + FreeSemaphore(&Semaphore); + + return TRUE; + } + + // We have a destination. If Quality is zero, remove it, else update it + + if (APPLCALLTABLE[App].APPLQUAL == 0) + { + GetSemaphore(&Semaphore, 6); + + REMOVENODE(DEST); // Clear buffers, Remove from Sorted Nodes chain, and zap entry + + APPL->NODEPOINTER=NULL; + + FreeSemaphore(&Semaphore); + return FALSE; + + } + + if (APPLCALLTABLE[App].APPLCALL[0] < 41) return FALSE; + + GetSemaphore(&Semaphore, 7); + + memmove (DEST->DEST_CALL,APPL->APPLCALL,13); + + DEST->DEST_STATE=0x80; // SPECIAL ENTRY + + DEST->NRROUTE[0].ROUT_QUALITY = (BYTE)APPL->APPLQUAL; + DEST->NRROUTE[0].ROUT_OBSCOUNT = 255; + + FreeSemaphore(&Semaphore); + return TRUE; + +} + + +DllExport UCHAR * APIENTRY GetSignOnMsg() +{ + return (&SIGNONMSG[0]); +} + + +DllExport HKEY APIENTRY GetRegistryKey() +{ + return REGTREE; +} + +DllExport char * APIENTRY GetRegistryKeyText() +{ + return REGTREETEXT;; +} + +DllExport UCHAR * APIENTRY GetBPQDirectory() +{ + while (BPQDirectory[0] == 0) + { + Debugprintf("BPQ Directory not set up - waiting"); + Sleep(1000); + } + return (&BPQDirectory[0]); +} + +DllExport UCHAR * APIENTRY GetProgramDirectory() +{ + return (&BPQProgramDirectory[0]); +} + +DllExport UCHAR * APIENTRY GetLogDirectory() +{ + return (&LogDirectory[0]); +} + +// Version for Visual Basic + +DllExport char * APIENTRY CopyBPQDirectory(char * dir) +{ + return (strcpy(dir,BPQDirectory)); +} + +DllExport int APIENTRY GetMsgPerl(int stream, char * msg) +{ + int len,count; + + GetMsg(stream, msg, &len, &count ); + + return len; +} + +int Rig_Command(int Session, char * Command); + +BOOL Rig_CommandInt(int Session, char * Command) +{ + return Rig_Command(Session, Command); +} + +DllExport int APIENTRY BPQSetHandle(int Stream, HWND hWnd) +{ + BPQHOSTVECTOR[Stream-1].HOSTHANDLE=hWnd; + return (0); +} + +#define L4USER 0 + +BPQVECSTRUC * PORTVEC ; + +VOID * InitializeExtDriver(PEXTPORTDATA PORTVEC) +{ + HINSTANCE ExtDriver=0; + char msg[128]; + int err=0; + HKEY hKey=0; + UCHAR Value[MAX_PATH]; + + // If no directory, use current + + if (BPQDirectory[0] == 0) + { + strcpy(Value,PORTVEC->PORT_DLL_NAME); + } + else + { + strcpy(Value,BPQDirectory); + strcat(Value,"\\"); + strcat(Value,PORTVEC->PORT_DLL_NAME); + } + + // Several Drivers are now built into bpq32.dll + + _strupr(Value); + + if (strstr(Value, "BPQVKISS")) + return VCOMExtInit; + + if (strstr(Value, "BPQAXIP")) + return AXIPExtInit; + + if (strstr(Value, "BPQETHER")) + return ETHERExtInit; + + if (strstr(Value, "BPQTOAGW")) + return AGWExtInit; + + if (strstr(Value, "AEAPACTOR")) + return AEAExtInit; + + if (strstr(Value, "HALDRIVER")) + return HALExtInit; + + if (strstr(Value, "KAMPACTOR")) + return KAMExtInit; + + if (strstr(Value, "SCSPACTOR")) + return SCSExtInit; + + if (strstr(Value, "WINMOR")) + return WinmorExtInit; + + if (strstr(Value, "V4")) + return V4ExtInit; + + if (strstr(Value, "TELNET")) + return TelnetExtInit; + +// if (strstr(Value, "SOUNDMODEM")) +// return SoundModemExtInit; + + if (strstr(Value, "SCSTRACKER")) + return TrackerExtInit; + + if (strstr(Value, "TRKMULTI")) + return TrackerMExtInit; + + if (strstr(Value, "UZ7HO")) + return UZ7HOExtInit; + + if (strstr(Value, "MULTIPSK")) + return MPSKExtInit; + + if (strstr(Value, "FLDIGI")) + return FLDigiExtInit; + + if (strstr(Value, "UIARQ")) + return UIARQExtInit; + +// if (strstr(Value, "BAYCOM")) +// return (UINT) BaycomExtInit; + + if (strstr(Value, "VARA")) + return VARAExtInit; + + if (strstr(Value, "ARDOP")) + return ARDOPExtInit; + + if (strstr(Value, "SERIAL")) + return SerialExtInit; + + if (strstr(Value, "KISSHF")) + return KISSHFExtInit; + + if (strstr(Value, "WINRPR")) + return WinRPRExtInit; + + if (strstr(Value, "HSMODEM")) + return HSMODEMExtInit; + + if (strstr(Value, "FREEDATA")) + return FreeDataExtInit; + + if (strstr(Value, "6PACK")) + return SIXPACKExtInit; + + ExtDriver = LoadLibrary(Value); + + if (ExtDriver == NULL) + { + err=GetLastError(); + + sprintf(msg,"Error loading Driver %s - Error code %d", + PORTVEC->PORT_DLL_NAME,err); + + MessageBox(NULL,msg,"BPQ32",MB_ICONSTOP); + + return(0); + } + + PORTVEC->DLLhandle=ExtDriver; + + return (GetProcAddress(ExtDriver,"_ExtInit@4")); + +} + +/* +_DATABASE LABEL BYTE + +FILLER DB 14 DUP (0) ; PROTECTION AGENST BUFFER PROBLEMS! + DB MAJORVERSION,MINORVERSION +_NEIGHBOURS DD 0 + DW TYPE ROUTE +_MAXNEIGHBOURS DW 20 ; MAX ADJACENT NODES + +_DESTS DD 0 ; NODE LIST + DW TYPE DEST_LIST +MAXDESTS DW 100 ; MAX NODES IN SYSTEM +*/ + + +DllExport int APIENTRY GetAttachedProcesses() +{ + return (AttachedProcesses); +} + +DllExport int * APIENTRY GetAttachedProcessList() +{ + return (&AttachedPIDList[0]); +} + +DllExport int * APIENTRY SaveNodesSupport() +{ + return (&DATABASESTART); +} + +// +// Internal BPQNODES support +// + +#define UCHAR unsigned char + +/* +ROUTE ADD G1HTL-1 2 200 0 0 0 +ROUTE ADD G4IRX-3 2 200 0 0 0 +NODE ADD MAPPLY:G1HTL-1 G1HTL-1 2 200 G4IRX-3 2 98 +NODE ADD NOT:GB7NOT G1HTL-1 2 199 G4IRX-3 2 98 + +*/ + +struct DEST_LIST * Dests; +struct ROUTE * Routes; + +int MaxNodes; +int MaxRoutes; +int NodeLen; +int RouteLen; + +int count; +int cursor; + +int len,i; + +ULONG cnt; +char Normcall[10]; +char Portcall[10]; +char Alias[7]; + +char line[100]; + +HANDLE handle; + +int APIENTRY Restart() +{ + int i, Count = AttachedProcesses; + HANDLE hProc; + DWORD PID; + + for (i = 0; i < Count; i++) + { + PID = AttachedPIDList[i]; + + // Kill Timer Owner last + + if (TimerInst != PID) + { + hProc = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID); + + if (hProc) + { + TerminateProcess(hProc, 0); + CloseHandle(hProc); + } + } + } + + hProc = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, TimerInst); + + if (hProc) + { + TerminateProcess(hProc, 0); + CloseHandle(hProc); + } + + + return 0; +} + +int APIENTRY Reboot() +{ + // Run shutdown -r -f + + STARTUPINFO SInfo; + PROCESS_INFORMATION PInfo; + char Cmd[] = "shutdown -r -f"; + + SInfo.cb=sizeof(SInfo); + SInfo.lpReserved=NULL; + SInfo.lpDesktop=NULL; + SInfo.lpTitle=NULL; + SInfo.dwFlags=0; + SInfo.cbReserved2=0; + SInfo.lpReserved2=NULL; + + return CreateProcess(NULL, Cmd, NULL, NULL, FALSE,0 ,NULL ,NULL, &SInfo, &PInfo); +} +/* +int APIENTRY Reconfig() +{ + if (!ProcessConfig()) + { + return (0); + } + SaveNodes(); + WritetoConsole("Nodes Saved\n"); + ReconfigFlag=TRUE; + WritetoConsole("Reconfig requested ... Waiting for Timer Poll\n"); + return 1; +} +*/ +// Code to support minimizing all BPQ Apps to a single Tray ICON + +// As we can't minimize the console window to the tray, I'll use an ordinary +// window instead. This also gives me somewhere to post the messages to + + +char AppName[] = "BPQ32"; +char Title[80] = "BPQ32.dll Console"; + +int NewLine(); + +char FrameClassName[] = TEXT("MdiFrame"); + +HWND ClientWnd; //This stores the MDI client area window handle + +LOGFONT LFTTYFONT ; + +HFONT hFont ; + +HMENU hPopMenu, hWndMenu; +HMENU hMainFrameMenu = NULL; +HMENU hBaseMenu = NULL; +HMENU hConsMenu = NULL; +HMENU hTermMenu = NULL; +HMENU hMonMenu = NULL; +HMENU hTermActMenu, hTermCfgMenu, hTermEdtMenu, hTermHlpMenu; +HMENU hMonActMenu, hMonCfgMenu, hMonEdtMenu, hMonHlpMenu; + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +DllExport int APIENTRY DeleteTrayMenuItem(HWND hWnd); + +#define BPQMonitorAvail 1 +#define BPQDataAvail 2 +#define BPQStateChange 4 + +VOID GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value); +SOCKET OpenWL2KHTTPSock(); +SendHTTPRequest(SOCKET sock, char * Request, char * Params, int Len, char * Return); + +BOOL GetWL2KSYSOPInfo(char * Call, char * _REPLYBUFFER); +BOOL UpdateWL2KSYSOPInfo(char * Call, char * SQL); + + +static INT_PTR CALLBACK ConfigWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + { + char _REPLYBUFFER[1000] = ""; + char Value[1000]; + + if (GetWL2KSYSOPInfo(WL2KCall, _REPLYBUFFER)) + { +// if (strstr(_REPLYBUFFER, "\"ErrorMessage\":") == 0) + + GetJSONValue(_REPLYBUFFER, "\"SysopName\":", Value); + SetDlgItemText(hDlg, NAME, Value); + + GetJSONValue(_REPLYBUFFER, "\"GridSquare\":", Value); + SetDlgItemText(hDlg, IDC_Locator, Value); + + GetJSONValue(_REPLYBUFFER, "\"StreetAddress1\":", Value); + SetDlgItemText(hDlg, ADDR1, Value); + + GetJSONValue(_REPLYBUFFER, "\"StreetAddress2\":", Value); + SetDlgItemText(hDlg, ADDR2, Value); + + GetJSONValue(_REPLYBUFFER, "\"City\":", Value); + SetDlgItemText(hDlg, CITY, Value); + + GetJSONValue(_REPLYBUFFER, "\"State\":", Value); + SetDlgItemText(hDlg, STATE, Value); + + GetJSONValue(_REPLYBUFFER, "\"Country\":", Value); + SetDlgItemText(hDlg, COUNTRY, Value); + + GetJSONValue(_REPLYBUFFER, "\"PostalCode\":", Value); + SetDlgItemText(hDlg, POSTCODE, Value); + + GetJSONValue(_REPLYBUFFER, "\"Email\":", Value); + SetDlgItemText(hDlg, EMAIL, Value); + + GetJSONValue(_REPLYBUFFER, "\"Website\":", Value); + SetDlgItemText(hDlg, WEBSITE, Value); + + GetJSONValue(_REPLYBUFFER, "\"Phones\":", Value); + SetDlgItemText(hDlg, PHONE, Value); + + GetJSONValue(_REPLYBUFFER, "\"Comments\":", Value); + SetDlgItemText(hDlg, ADDITIONALDATA, Value); + + } + + return (INT_PTR)TRUE; + } + case WM_COMMAND: + + switch(LOWORD(wParam)) + { + + case ID_SAVE: + { + char Name[100]; + char PasswordText[100]; + char LocatorText[100]; + char Addr1[100]; + char Addr2[100]; + char City[100]; + char State[100]; + char Country[100]; + char PostCode[100]; + char Email[100]; + char Website[100]; + char Phone[100]; + char Data[100]; + + SOCKET sock; + + int Len; + char Message[2048]; + char Reply[2048] = ""; + + + GetDlgItemText(hDlg, NAME, Name, 99); + GetDlgItemText(hDlg, IDC_Password, PasswordText, 99); + GetDlgItemText(hDlg, IDC_Locator, LocatorText, 99); + GetDlgItemText(hDlg, ADDR1, Addr1, 99); + GetDlgItemText(hDlg, ADDR2, Addr2, 99); + GetDlgItemText(hDlg, CITY, City, 99); + GetDlgItemText(hDlg, STATE, State, 99); + GetDlgItemText(hDlg, COUNTRY, Country, 99); + GetDlgItemText(hDlg, POSTCODE, PostCode, 99); + GetDlgItemText(hDlg, EMAIL, Email, 99); + GetDlgItemText(hDlg, WEBSITE, Website, 99); + GetDlgItemText(hDlg, PHONE, Phone, 99); + GetDlgItemText(hDlg, ADDITIONALDATA, Data, 99); + + +//{"Callsign":"String","GridSquare":"String","SysopName":"String", +//"StreetAddress1":"String","StreetAddress2":"String","City":"String", +//"State":"String","Country":"String","PostalCode":"String","Email":"String", +//"Phones":"String","Website":"String","Comments":"String"} + + Len = sprintf(Message, + "\"Callsign\":\"%s\"," + "\"Password\":\"%s\"," + "\"GridSquare\":\"%s\"," + "\"SysopName\":\"%s\"," + "\"StreetAddress1\":\"%s\"," + "\"StreetAddress2\":\"%s\"," + "\"City\":\"%s\"," + "\"State\":\"%s\"," + "\"Country\":\"%s\"," + "\"PostalCode\":\"%s\"," + "\"Email\":\"%s\"," + "\"Phones\":\"%s\"," + "\"Website\":\"%s\"," + "\"Comments\":\"%s\"", + + WL2KCall, PasswordText, LocatorText, Name, Addr1, Addr2, City, State, Country, PostCode, Email, Phone, Website, Data); + + Debugprintf("Sending %s", Message); + + sock = OpenWL2KHTTPSock(); + + if (sock) + { + char * ptr; + + SendHTTPRequest(sock, + "/sysop/add", Message, Len, Reply); + + ptr = strstr(Reply, "\"ErrorCode\":"); + + if (ptr) + { + ptr = strstr(ptr, "Message"); + if (ptr) + { + ptr += 10; + strlop(ptr, '"'); + MessageBox(NULL ,ptr, "Error", MB_OK); + } + } + else + MessageBox(NULL, "Sysop Record Updated", "BPQ32", MB_OK); + + } + closesocket(sock); + } + + case ID_CANCEL: + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + } + return (INT_PTR)FALSE; +} + + + +LRESULT CALLBACK UIWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +VOID WINAPI OnTabbedDialogInit(HWND hDlg); + +LRESULT CALLBACK FrameWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + POINT pos; + BOOL ret; + + CLIENTCREATESTRUCT MDIClientCreateStruct; // Structure to be used for MDI client area + //HWND m_hwndSystemInformation = 0; + + if (message == BPQMsg) + { + if (lParam & BPQDataAvail) + DoReceivedData(wParam); + + if (lParam & BPQMonitorAvail) + DoMonData(wParam); + + if (lParam & BPQStateChange) + DoStateChange(wParam); + + return (0); + } + + switch (message) + { + case MY_TRAY_ICON_MESSAGE: + + switch(lParam) + { + case WM_RBUTTONUP: + case WM_LBUTTONUP: + + GetCursorPos(&pos); + + // SetForegroundWindow(FrameWnd); + + TrackPopupMenu(trayMenu, 0, pos.x, pos.y, 0, FrameWnd, 0); + return 0; + } + + break; + + case WM_CTLCOLORDLG: + return (LONG)bgBrush; + + case WM_SIZING: + case WM_SIZE: + + SendMessage(ClientWnd, WM_MDIICONARRANGE, 0 ,0); + break; + + case WM_NCCREATE: + + ret = DefFrameProc(hWnd, ClientWnd, message, wParam, lParam); + return TRUE; + + case WM_CREATE: + + // On creation of main frame, create the MDI client area + + MDIClientCreateStruct.hWindowMenu = NULL; + MDIClientCreateStruct.idFirstChild = IDM_FIRSTCHILD; + + ClientWnd = CreateWindow(TEXT("MDICLIENT"), // predefined value for MDI client area + NULL, // no caption required + WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, + 0, // No need to give any x/y or height/width since this client + // will just be used to get client windows created, effectively + // in the main window we will be seeing the mainframe window client area itself. + 0, + 0, + 0, + hWnd, + NULL, + hInstance, + (void *) &MDIClientCreateStruct); + + + return 0; + + case WM_COMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + if (wmId >= TRAYBASEID && wmId < (TRAYBASEID + 100)) + { + handle=hWndArray[wmId-TRAYBASEID]; + + if (handle == FrameWnd) + ShowWindow(handle, SW_NORMAL); + + if (handle == FrameWnd && FrameMaximized == TRUE) + PostMessage(handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); + else + PostMessage(handle, WM_SYSCOMMAND, SC_RESTORE, 0); + + SetForegroundWindow(handle); + return 0; + } + + switch(wmId) + { + struct ConsoleInfo * Cinfo = NULL; + + case ID_NEWWINDOW: + Cinfo = CreateChildWindow(0, FALSE); + if (Cinfo) + SendMessage(ClientWnd, WM_MDIACTIVATE, (WPARAM)Cinfo->hConsole, 0); + break; + + case ID_WINDOWS_CASCADE: + SendMessage(ClientWnd, WM_MDICASCADE, 0, 0); + return 0; + + case ID_WINDOWS_TILE: + SendMessage(ClientWnd, WM_MDITILE , MDITILE_HORIZONTAL, 0); + return 0; + + case BPQCLOSEALL: + CloseAllPrograms(); + // SendMessage(ClientWnd, WM_MDIICONARRANGE, 0 ,0); + + return 0; + + case BPQUICONFIG: + { + int err, i=0; + char Title[80]; + WNDCLASS wc; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = UIWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(BPQICON) ); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = bgBrush; + + wc.lpszMenuName = NULL; + wc.lpszClassName = UIClassName; + + RegisterClass(&wc); + + UIhWnd = CreateDialog(hInstance, UIClassName, 0, NULL); + + if (!UIhWnd) + { + err=GetLastError(); + return FALSE; + } + + wsprintf(Title,"BPQ32 Beacon Configuration"); + MySetWindowText(UIhWnd, Title); + ShowWindow(UIhWnd, SW_NORMAL); + + OnTabbedDialogInit(UIhWnd); // Set up pages + + // UpdateWindow(UIhWnd); + return 0; + } + + + case IDD_WL2KSYSOP: + + if (WL2KCall[0] == 0) + { + MessageBox(NULL,"WL2K Reporting is not configured","BPQ32", MB_OK); + break; + } + + DialogBox(hInstance, MAKEINTRESOURCE(IDD_WL2KSYSOP), hWnd, ConfigWndProc); + break; + + + // Handle MDI Window commands + + default: + { + if(wmId >= IDM_FIRSTCHILD) + { + DefFrameProc(hWnd, ClientWnd, message, wParam, lParam); + } + else + { + HWND hChild = (HWND)SendMessage(ClientWnd, WM_MDIGETACTIVE,0,0); + + if(hChild) + SendMessage(hChild, WM_COMMAND, wParam, lParam); + } + } + } + + break; + + case WM_INITMENUPOPUP: + { + HWND hChild = (HWND)SendMessage(ClientWnd, WM_MDIGETACTIVE,0,0); + + if(hChild) + SendMessage(hChild, WM_INITMENUPOPUP, wParam, lParam); + } + + case WM_SYSCOMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + switch (wmId) + { + case SC_MAXIMIZE: + + FrameMaximized = TRUE; + break; + + case SC_RESTORE: + + FrameMaximized = FALSE; + break; + + case SC_MINIMIZE: + + if (MinimizetoTray) + { + ShowWindow(hWnd, SW_HIDE); + return TRUE; + } + } + + return (DefFrameProc(hWnd, ClientWnd, message, wParam, lParam)); + + case WM_CLOSE: + + PostQuitMessage(0); + + if (MinimizetoTray) + DeleteTrayMenuItem(hWnd); + + break; + + default: + return (DefFrameProc(hWnd, ClientWnd, message, wParam, lParam)); + + } + return (DefFrameProc(hWnd, ClientWnd, message, wParam, lParam)); +} + +int OffsetH, OffsetW; + +int SetupConsoleWindow() +{ + WNDCLASS wc; + int i; + int retCode, Type, Vallen; + HKEY hKey=0; + char Size[80]; + WNDCLASSEX wndclassMainFrame; + RECT CRect; + + retCode = RegOpenKeyEx (REGTREE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, + KEY_QUERY_VALUE, + &hKey); + + if (retCode == ERROR_SUCCESS) + { + Vallen=80; + + retCode = RegQueryValueEx(hKey,"FrameWindowSize",0, + (ULONG *)&Type,(UCHAR *)&Size,(ULONG *)&Vallen); + + if (retCode == ERROR_SUCCESS) + sscanf(Size,"%d,%d,%d,%d",&FRect.left,&FRect.right,&FRect.top,&FRect.bottom); + + if (FRect.top < - 500 || FRect.left < - 500) + { + FRect.left = 0; + FRect.top = 0; + FRect.right = 600; + FRect.bottom = 400; + } + + + Vallen=80; + retCode = RegQueryValueEx(hKey,"WindowSize",0, + (ULONG *)&Type,(UCHAR *)&Size,(ULONG *)&Vallen); + + if (retCode == ERROR_SUCCESS) + sscanf(Size,"%d,%d,%d,%d,%d",&Rect.left,&Rect.right,&Rect.top,&Rect.bottom, &ConsoleMinimized); + + if (Rect.top < - 500 || Rect.left < - 500) + { + Rect.left = 0; + Rect.top = 0; + Rect.right = 600; + Rect.bottom = 400; + } + + Vallen=80; + + retCode = RegQueryValueEx(hKey,"StatusWindowSize",0, + (ULONG *)&Type,(UCHAR *)&Size,(ULONG *)&Vallen); + + if (retCode == ERROR_SUCCESS) + sscanf(Size, "%d,%d,%d,%d,%d", &StatusRect.left, &StatusRect.right, + &StatusRect.top, &StatusRect.bottom, &StatusMinimized); + + if (StatusRect.top < - 500 || StatusRect.left < - 500) + { + StatusRect.left = 0; + StatusRect.top = 0; + StatusRect.right = 850; + StatusRect.bottom = 500; + } + + + // Get StartMinimized and MinimizetoTray flags + + Vallen = 4; + retCode = RegQueryValueEx(hKey, "Start Minimized", 0, &Type, (UCHAR *)&StartMinimized, &Vallen); + + Vallen = 4; + retCode = RegQueryValueEx(hKey, "Minimize to Tray", 0, &Type, (UCHAR *)&MinimizetoTray, &Vallen); + } + + wndclassMainFrame.cbSize = sizeof(WNDCLASSEX); + wndclassMainFrame.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE; + wndclassMainFrame.lpfnWndProc = FrameWndProc; + wndclassMainFrame.cbClsExtra = 0; + wndclassMainFrame.cbWndExtra = 0; + wndclassMainFrame.hInstance = hInstance; + wndclassMainFrame.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(BPQICON)); + wndclassMainFrame.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclassMainFrame.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH); + wndclassMainFrame.lpszMenuName = NULL; + wndclassMainFrame.lpszClassName = FrameClassName; + wndclassMainFrame.hIconSm = NULL; + + if(!RegisterClassEx(&wndclassMainFrame)) + { + return 0; + } + + pindex = 0; + PartLine = FALSE; + + bgBrush = CreateSolidBrush(BGCOLOUR); + +// hMainFrameMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINFRAME_MENU)); + + hBaseMenu = LoadMenu(hInstance, MAKEINTRESOURCE(CONS_MENU)); + hConsMenu = GetSubMenu(hBaseMenu, 1); + hWndMenu = GetSubMenu(hBaseMenu, 0); + + hTermMenu = LoadMenu(hInstance, MAKEINTRESOURCE(TERM_MENU)); + hTermActMenu = GetSubMenu(hTermMenu, 1); + hTermCfgMenu = GetSubMenu(hTermMenu, 2); + hTermEdtMenu = GetSubMenu(hTermMenu, 3); + hTermHlpMenu = GetSubMenu(hTermMenu, 4); + + hMonMenu = LoadMenu(hInstance, MAKEINTRESOURCE(MON_MENU)); + hMonCfgMenu = GetSubMenu(hMonMenu, 1); + hMonEdtMenu = GetSubMenu(hMonMenu, 2); + hMonHlpMenu = GetSubMenu(hMonMenu, 3); + + hMainFrameMenu = CreateMenu(); + AppendMenu(hMainFrameMenu, MF_STRING + MF_POPUP, (UINT)hWndMenu, "Window"); + + //Create the main MDI frame window + + ClientWnd = NULL; + + FrameWnd = CreateWindow(FrameClassName, + "BPQ32 Console", + WS_OVERLAPPEDWINDOW |WS_CLIPCHILDREN, + FRect.left, + FRect.top, + FRect.right - FRect.left, + FRect.bottom - FRect.top, + NULL, // handle to parent window + hMainFrameMenu, // handle to menu + hInstance, // handle to the instance of module + NULL); // Long pointer to a value to be passed to the window through the + // CREATESTRUCT structure passed in the lParam parameter the WM_CREATE message + + + // Get Client Params + + if (FrameWnd == 0) + { + Debugprintf("SetupConsoleWindow Create Frame failed %d", GetLastError()); + return 0; + } + + ShowWindow(FrameWnd, SW_RESTORE); + + + GetWindowRect(FrameWnd, &FRect); + OffsetH = FRect.bottom - FRect.top; + OffsetW = FRect.right - FRect.left; + GetClientRect(FrameWnd, &CRect); + OffsetH -= CRect.bottom; + OffsetW -= CRect.right; + OffsetH -= 4; + + // Create Console Window + + wc.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE; + wc.lpfnWndProc = (WNDPROC)WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(BPQICON)); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wc.lpszMenuName = 0; + wc.lpszClassName = ClassName; + + i=RegisterClass(&wc); + + sprintf (Title, "BPQ32.dll Console Version %s", VersionString); + + hConsWnd = CreateMDIWindow(ClassName, "Console", 0, + 0,0,0,0, ClientWnd, hInstance, 1234); + + i = GetLastError(); + + if (!hConsWnd) { + return (FALSE); + } + + wc.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE; + wc.lpfnWndProc = (WNDPROC)StatusWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(BPQICON)); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wc.lpszMenuName = 0; + wc.lpszClassName = "Status"; + + i=RegisterClass(&wc); + + if (StatusRect.top < OffsetH) // Make sure not off top of MDI frame + { + int Error = OffsetH - StatusRect.top; + StatusRect.top += Error; + StatusRect.bottom += Error; + } + + StatusWnd = CreateMDIWindow("Status", "Stream Status", 0, + StatusRect.left, StatusRect.top, StatusRect.right - StatusRect.left, + StatusRect.bottom - StatusRect.top, ClientWnd, hInstance, 1234); + + SetTimer(StatusWnd, 1, 1000, NULL); + + hPopMenu = GetSubMenu(hBaseMenu, 1) ; + + if (MinimizetoTray) + CheckMenuItem(hPopMenu, BPQMINTOTRAY, MF_CHECKED); + else + CheckMenuItem(hPopMenu, BPQMINTOTRAY, MF_UNCHECKED); + + if (StartMinimized) + CheckMenuItem(hPopMenu, BPQSTARTMIN, MF_CHECKED); + else + CheckMenuItem(hPopMenu, BPQSTARTMIN, MF_UNCHECKED); + + DrawMenuBar(hConsWnd); + + // setup default font information + + LFTTYFONT.lfHeight = 12; + LFTTYFONT.lfWidth = 8 ; + LFTTYFONT.lfEscapement = 0 ; + LFTTYFONT.lfOrientation = 0 ; + LFTTYFONT.lfWeight = 0 ; + LFTTYFONT.lfItalic = 0 ; + LFTTYFONT.lfUnderline = 0 ; + LFTTYFONT.lfStrikeOut = 0 ; + LFTTYFONT.lfCharSet = 0; + LFTTYFONT.lfOutPrecision = OUT_DEFAULT_PRECIS ; + LFTTYFONT.lfClipPrecision = CLIP_DEFAULT_PRECIS ; + LFTTYFONT.lfQuality = DEFAULT_QUALITY ; + LFTTYFONT.lfPitchAndFamily = FIXED_PITCH; + lstrcpy(LFTTYFONT.lfFaceName, "FIXEDSYS" ) ; + + hFont = CreateFontIndirect(&LFTTYFONT) ; + + SetWindowText(hConsWnd,Title); + + if (Rect.right < 100 || Rect.bottom < 100) + { + GetWindowRect(hConsWnd, &Rect); + } + + if (Rect.top < OffsetH) // Make sure not off top of MDI frame + { + int Error = OffsetH - Rect.top; + Rect.top += Error; + Rect.bottom += Error; + } + + + MoveWindow(hConsWnd, Rect.left - (OffsetW /2), Rect.top - OffsetH, Rect.right-Rect.left, Rect.bottom-Rect.top, TRUE); + + MoveWindow(StatusWnd, StatusRect.left - (OffsetW /2), StatusRect.top - OffsetH, + StatusRect.right-StatusRect.left, StatusRect.bottom-StatusRect.top, TRUE); + + hWndCons = CreateWindowEx(WS_EX_CLIENTEDGE, "LISTBOX", "", + WS_CHILD | WS_VISIBLE | LBS_NOINTEGRALHEIGHT | + LBS_DISABLENOSCROLL | LBS_NOSEL | WS_VSCROLL | WS_HSCROLL, + Rect.left, Rect.top, Rect.right - Rect.left, Rect.bottom - Rect.top, + hConsWnd, NULL, hInstance, NULL); + +// SendMessage(hWndCons, WM_SETFONT, hFont, 0); + + SendMessage(hWndCons, LB_SETHORIZONTALEXTENT , 1000, 0); + + if (ConsoleMinimized) + ShowWindow(hConsWnd, SW_SHOWMINIMIZED); + else + ShowWindow(hConsWnd, SW_RESTORE); + + if (StatusMinimized) + ShowWindow(StatusWnd, SW_SHOWMINIMIZED); + else + ShowWindow(StatusWnd, SW_RESTORE); + + ShowWindow(FrameWnd, SW_RESTORE); + + + LoadLibrary("riched20.dll"); + + if (StartMinimized) + if (MinimizetoTray) + ShowWindow(FrameWnd, SW_HIDE); + else + ShowWindow(FrameWnd, SW_SHOWMINIMIZED); + else + ShowWindow(FrameWnd, SW_RESTORE); + + CreateMonitorWindow(Size); + + return 0; +} + +DllExport int APIENTRY SetupTrayIcon() +{ + if (MinimizetoTray == 0) + return 0; + + trayMenu = CreatePopupMenu(); + + for( i = 0; i < 100; ++i ) + { + if (strcmp(PopupText[i],"BPQ32 Console") == 0) + { + hWndArray[i] = FrameWnd; + goto doneit; + } + } + + for( i = 0; i < 100; ++i ) + { + if (hWndArray[i] == 0) + { + hWndArray[i] = FrameWnd; + strcpy(PopupText[i],"BPQ32 Console"); + break; + } + } +doneit: + + for( i = 0; i < 100; ++i ) + { + if (hWndArray[i] != 0) + AppendMenu(trayMenu,MF_STRING,TRAYBASEID+i,PopupText[i]); + } + + // Set up Tray ICON + + ZeroMemory(&niData,sizeof(NOTIFYICONDATA)); + + niData.cbSize = sizeof(NOTIFYICONDATA); + + // the ID number can be any UINT you choose and will + // be used to identify your icon in later calls to + // Shell_NotifyIcon + + niData.uID = TRAY_ICON_ID; + + // state which structure members are valid + // here you can also choose the style of tooltip + // window if any - specifying a balloon window: + // NIF_INFO is a little more complicated + + strcpy(niData.szTip,"BPQ32 Windows"); + + niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP; + + // load the icon note: you should destroy the icon + // after the call to Shell_NotifyIcon + + niData.hIcon = + + //LoadIcon(NULL, IDI_APPLICATION); + + (HICON)LoadImage( hInstance, + MAKEINTRESOURCE(BPQICON), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR); + + + // set the window you want to receive event messages + + niData.hWnd = FrameWnd; + + // set the message to send + // note: the message value should be in the + // range of WM_APP through 0xBFFF + + niData.uCallbackMessage = MY_TRAY_ICON_MESSAGE; + + // Call Shell_NotifyIcon. NIM_ADD adds a new tray icon + + if (Shell_NotifyIcon(NIM_ADD,&niData)) + Debugprintf("BPQ32 Create Tray Icon Ok"); +// else +// Debugprintf("BPQ32 Create Tray Icon failed %d", GetLastError()); + + return 0; +} + +VOID SaveConfig() +{ + HKEY hKey=0; + int retCode, disp; + + retCode = RegCreateKeyEx(REGTREE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, // Reserved + 0, // Class + 0, // Options + KEY_ALL_ACCESS, + NULL, // Security Attrs + &hKey, + &disp); + + if (retCode == ERROR_SUCCESS) + { + retCode = RegSetValueEx(hKey, "Start Minimized", 0, REG_DWORD, (UCHAR *)&StartMinimized, 4); + retCode = RegSetValueEx(hKey, "Minimize to Tray", 0, REG_DWORD, (UCHAR *)&MinimizetoTray, 4); + } +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + POINT pos; + HWND handle; + RECT cRect; + + switch (message) + { + case WM_MDIACTIVATE: + + // Set the system info menu when getting activated + + if (lParam == (LPARAM) hWnd) + { + // Activate + + // GetSubMenu function should retrieve a handle to the drop-down menu or submenu. + + RemoveMenu(hBaseMenu, 1, MF_BYPOSITION); + AppendMenu(hBaseMenu, MF_STRING + MF_POPUP, (UINT)hConsMenu, "Actions"); + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hBaseMenu, (LPARAM) hWndMenu); + } + else + { + // Deactivate + + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hMainFrameMenu, (LPARAM) NULL); + } + + DrawMenuBar(FrameWnd); + + return TRUE; //DefMDIChildProc(hWnd, message, wParam, lParam); + + case MY_TRAY_ICON_MESSAGE: + + switch(lParam) + { + case WM_RBUTTONUP: + case WM_LBUTTONUP: + + GetCursorPos(&pos); + + SetForegroundWindow(hWnd); + + TrackPopupMenu(trayMenu, 0, pos.x, pos.y, 0, hWnd, 0); + return 0; + } + + break; + + case WM_CTLCOLORDLG: + return (LONG)bgBrush; + + case WM_COMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + if (wmId == IDC_ENIGATE) + { + int retCode, disp; + HKEY hKey=0; + + IGateEnabled = IsDlgButtonChecked(hWnd, IDC_ENIGATE); + + if (IGateEnabled) + ISDelayTimer = 60; + + retCode = RegCreateKeyEx(REGTREE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, // Reserved + 0, // Class + 0, // Options + KEY_ALL_ACCESS, + NULL, // Security Attrs + &hKey, + &disp); + + if (retCode == ERROR_SUCCESS) + { + retCode = RegSetValueEx(hKey,"IGateEnabled", 0 , REG_DWORD,(BYTE *)&IGateEnabled, 4); + RegCloseKey(hKey); + } + + return 0; + } + + if (wmId == BPQSAVENODES) + { + SaveNodes(); + WritetoConsole("Nodes Saved\n"); + return 0; + } + if (wmId == BPQCLEARRECONFIG) + { + if (!ProcessConfig()) + { + MessageBox(NULL,"Configuration File check falled - will continue with old config","BPQ32",MB_OK); + return (0); + } + + ClearNodes(); + WritetoConsole("Nodes file Cleared\n"); + ReconfigFlag=TRUE; + WritetoConsole("Reconfig requested ... Waiting for Timer Poll\n"); + return 0; + } + if (wmId == BPQRECONFIG) + { + if (!ProcessConfig()) + { + MessageBox(NULL,"Configuration File check falled - will continue with old config","BPQ32",MB_OK); + return (0); + } + SaveNodes(); + WritetoConsole("Nodes Saved\n"); + ReconfigFlag=TRUE; + WritetoConsole("Reconfig requested ... Waiting for Timer Poll\n"); + return 0; + } + + if (wmId == SCANRECONFIG) + { + if (!ProcessConfig()) + { + MessageBox(NULL,"Configuration File check falled - will continue with old config","BPQ32",MB_OK); + return (0); + } + + RigReconfigFlag = TRUE; + WritetoConsole("Rigcontrol Reconfig requested ... Waiting for Timer Poll\n"); + return 0; + } + + if (wmId == APRSRECONFIG) + { + if (!ProcessConfig()) + { + MessageBox(NULL,"Configuration File check falled - will continue with old config","BPQ32",MB_OK); + return (0); + } + + APRSReconfigFlag=TRUE; + WritetoConsole("APRS Reconfig requested ... Waiting for Timer Poll\n"); + return 0; + } + if (wmId == BPQDUMP) + { + DumpSystem(); + return 0; + } + + if (wmId == BPQCLOSEALL) + { + CloseAllPrograms(); + return 0; + } + + if (wmId == BPQUICONFIG) + { + int err, i=0; + char Title[80]; + WNDCLASS wc; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = UIWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(BPQICON) ); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = bgBrush; + + wc.lpszMenuName = NULL; + wc.lpszClassName = UIClassName; + + RegisterClass(&wc); + + UIhWnd = CreateDialog(hInstance, UIClassName,0,NULL); + + if (!UIhWnd) + { + err=GetLastError(); + return FALSE; + } + + wsprintf(Title,"BPQ32 Beacon Utility Version"); + MySetWindowText(UIhWnd, Title); + return 0; + } + + if (wmId == BPQSAVEREG) + { + CreateRegBackup(); + return 0; + } + + if (wmId == BPQMINTOTRAY) + { + MinimizetoTray = !MinimizetoTray; + + if (MinimizetoTray) + CheckMenuItem(hPopMenu, BPQMINTOTRAY, MF_CHECKED); + else + CheckMenuItem(hPopMenu, BPQMINTOTRAY, MF_UNCHECKED); + + SaveConfig(); + return 0; + } + + if (wmId == BPQSTARTMIN) + { + StartMinimized = !StartMinimized; + + if (StartMinimized) + CheckMenuItem(hPopMenu, BPQSTARTMIN, MF_CHECKED); + else + CheckMenuItem(hPopMenu, BPQSTARTMIN, MF_UNCHECKED); + + SaveConfig(); + return 0; + } + + if (wmId >= TRAYBASEID && wmId < (TRAYBASEID + 100)) + { + handle=hWndArray[wmId-TRAYBASEID]; + + if (handle == FrameWnd && FrameMaximized == TRUE) + PostMessage(handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); + else + PostMessage(handle, WM_SYSCOMMAND, SC_RESTORE, 0); + + SetForegroundWindow(handle); + return 0; + } + + case WM_SYSCOMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + switch (wmId) + { + case SC_MINIMIZE: + + ConsoleMinimized = TRUE; + break; + + case SC_RESTORE: + + ConsoleMinimized = FALSE; + SendMessage(ClientWnd, WM_MDIRESTORE, (WPARAM)hWnd, 0); + + break; + } + + return DefMDIChildProc(hWnd, message, wParam, lParam); + + + case WM_SIZE: + + GetClientRect(hWnd, &cRect); + + MoveWindow(hWndBG, 0, 0, cRect.right, 26, TRUE); + + if (APRSActive) + MoveWindow(hWndCons, 2, 26, cRect.right-4, cRect.bottom - 32, TRUE); + else + MoveWindow(hWndCons, 2, 2, cRect.right-4, cRect.bottom - 4, TRUE); + +// InvalidateRect(hWnd, NULL, TRUE); + break; + +/* + case WM_PAINT: + + hdc = BeginPaint (hWnd, &ps); + + hOldFont = SelectObject( hdc, hFont) ; + + for (i=0; i 300) + len = 300; + + memcpy(&buffptr[2], buff, len + 1); + + C_Q_ADD(&WritetoConsoleQ, buffptr); + + return 0; +} + +int WritetoConsoleSupport(char * buff) +{ + + int len=strlen(buff); + char Temp[2000]= ""; + char * ptr; + + if (PartLine) + { + SendMessage(hWndCons, LB_GETTEXT, pindex, (LPARAM)(LPCTSTR) Temp); + SendMessage(hWndCons, LB_DELETESTRING, pindex, 0); + PartLine = FALSE; + } + + if ((strlen(Temp) + strlen(buff)) > 1990) + Temp[0] = 0; // Should never have anything this long + + strcat(Temp, buff); + + ptr = strchr(Temp, '\n'); + + if (ptr) + *ptr = 0; + else + PartLine = TRUE; + + pindex=SendMessage(hWndCons, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) Temp); + return 0; + } + +DllExport VOID APIENTRY BPQOutputDebugString(char * String) +{ + OutputDebugString(String); + return; + } + +HANDLE handle; +char fn[]="BPQDUMP"; +ULONG cnt; +char * stack; +//char screen[1920]; +//COORD ReadCoord; + +#define DATABYTES 400000 + +extern UCHAR DATAAREA[]; + +DllExport int APIENTRY DumpSystem() +{ + char fn[200]; + char Msg[250]; + + sprintf(fn,"%s\\BPQDUMP",BPQDirectory); + + handle = CreateFile(fn, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + +#ifndef _WIN64 + + _asm { + + mov stack,esp + } + + WriteFile(handle,stack,128,&cnt,NULL); +#endif + +// WriteFile(handle,Screen,MAXLINELEN*MAXSCREENLEN,&cnt,NULL); + + WriteFile(handle,DATAAREA, DATABYTES,&cnt,NULL); + + CloseHandle(handle); + + sprintf(Msg, "Dump to %s Completed\n", fn); + WritetoConsole(Msg); + + FindLostBuffers(); + + return (0); +} + +BOOLEAN CheckifBPQ32isLoaded() +{ + HANDLE Mutex; + + // See if BPQ32 is running - if we create it in the NTVDM address space by + // loading bpq32.dll it will not work. + + Mutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"BPQLOCKMUTEX"); + + if (Mutex == NULL) + { + if (AttachingProcess == 0) // Already starting BPQ32 + { + OutputDebugString("BPQ32 No other bpq32 programs running - Loading BPQ32.exe\n"); + StartBPQ32(); + } + return FALSE; + } + + CloseHandle(Mutex); + + return TRUE; +} + +BOOLEAN StartBPQ32() +{ + UCHAR Value[100]; + + char bpq[]="BPQ32.exe"; + char *fn=(char *)&bpq; + HKEY hKey=0; + int ret,Type,Vallen=99; + + char Errbuff[100]; + char buff[20]; + + STARTUPINFO StartupInfo; // pointer to STARTUPINFO + PROCESS_INFORMATION ProcessInformation; // pointer to PROCESS_INFORMATION + + AttachingProcess = 1; + +// Get address of BPQ Directory + + Value[0]=0; + + ret = RegOpenKeyEx (REGTREE, + "SOFTWARE\\G8BPQ\\BPQ32", + 0, + KEY_QUERY_VALUE, + &hKey); + + if (ret == ERROR_SUCCESS) + { + ret = RegQueryValueEx(hKey, "BPQ Program Directory", 0, &Type,(UCHAR *)&Value, &Vallen); + + if (ret == ERROR_SUCCESS) + { + if (strlen(Value) == 2 && Value[0] == '"' && Value[1] == '"') + Value[0]=0; + } + + + if (Value[0] == 0) + { + + // BPQ Directory absent or = "" - "try Config File Location" + + ret = RegQueryValueEx(hKey,"BPQ Directory",0, + &Type,(UCHAR *)&Value,&Vallen); + + if (ret == ERROR_SUCCESS) + { + if (strlen(Value) == 2 && Value[0] == '"' && Value[1] == '"') + Value[0]=0; + } + + } + RegCloseKey(hKey); + } + + if (Value[0] == 0) + { + strcpy(Value,fn); + } + else + { + strcat(Value,"\\"); + strcat(Value,fn); + } + + StartupInfo.cb=sizeof(StartupInfo); + StartupInfo.lpReserved=NULL; + StartupInfo.lpDesktop=NULL; + StartupInfo.lpTitle=NULL; + StartupInfo.dwFlags=0; + StartupInfo.cbReserved2=0; + StartupInfo.lpReserved2=NULL; + + if (!CreateProcess(Value,NULL,NULL,NULL,FALSE, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL,NULL,&StartupInfo,&ProcessInformation)) + { + ret=GetLastError(); + + _itoa(ret,buff,10); + + strcpy(Errbuff, "BPQ32 Load "); + strcat(Errbuff,Value); + strcat(Errbuff," failed "); + strcat(Errbuff,buff); + OutputDebugString(Errbuff); + AttachingProcess = 0; + return FALSE; + } + + return TRUE; +} + + +DllExport BPQVECSTRUC * APIENTRY GetIPVectorAddr() +{ + return &IPHOSTVECTOR; +} + +DllExport UINT APIENTRY GETSENDNETFRAMEADDR() +{ + return (UINT)&SENDNETFRAME; +} + +DllExport VOID APIENTRY RelBuff(VOID * Msg) +{ + UINT * pointer, * BUFF = Msg; + + if (Semaphore.Flag == 0) + Debugprintf("ReleaseBuffer called without semaphore"); + + pointer = FREE_Q; + + *BUFF =(UINT)pointer; + + FREE_Q = BUFF; + + QCOUNT++; + + return; +} + +extern int MINBUFFCOUNT; + +DllExport VOID * APIENTRY GetBuff() +{ + UINT * Temp = Q_REM(&FREE_Q); + + if (Semaphore.Flag == 0) + Debugprintf("GetBuff called without semaphore"); + + if (Temp) + { + QCOUNT--; + + if (QCOUNT < MINBUFFCOUNT) + MINBUFFCOUNT = QCOUNT; + } + + return Temp; +} + + +VOID __cdecl Debugprintf(const char * format, ...) +{ + char Mess[10000]; + va_list(arglist); + + va_start(arglist, format); + vsprintf(Mess, format, arglist); + strcat(Mess, "\r\n"); + OutputDebugString(Mess); + + return; +} + +unsigned short int compute_crc(unsigned char *buf, int txlen); + +extern SOCKADDR_IN reportdest; + +extern SOCKET ReportSocket; + +extern SOCKADDR_IN Chatreportdest; + +DllExport VOID APIENTRY SendChatReport(SOCKET ChatReportSocket, char * buff, int txlen) +{ + unsigned short int crc = compute_crc(buff, txlen); + + crc ^= 0xffff; + + buff[txlen++] = (crc&0xff); + buff[txlen++] = (crc>>8); + + sendto(ChatReportSocket, buff, txlen, 0, (LPSOCKADDR)&Chatreportdest, sizeof(Chatreportdest)); +} + +VOID CreateRegBackup() +{ + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + char RegFileName[MAX_PATH]; + char Msg[80]; + HANDLE handle; + int len, written; + char RegLine[300]; + +// SHELLEXECUTEINFO sei; +// STARTUPINFO SInfo; +// PROCESS_INFORMATION PInfo; + + sprintf(RegFileName, "%s\\BPQ32.reg", BPQDirectory); + + // Keep 4 Generations + + strcpy(Backup2, RegFileName); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, RegFileName); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, RegFileName); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, RegFileName); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); //Move .bak to .bak.1 + + strcpy(Backup2, RegFileName); + strcat(Backup2, ".bak"); + + CopyFile(RegFileName, Backup2, FALSE); // Copy to .bak + + handle = CreateFile(RegFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + sprintf(Msg, "Failed to open Registry Save File\n"); + WritetoConsole(Msg); + return; + } + + len = sprintf(RegLine, "Windows Registry Editor Version 5.00\r\n\r\n"); + WriteFile(handle, RegLine, len, &written, NULL); + + if (SaveReg("Software\\G8BPQ\\BPQ32", handle)) + WritetoConsole("Registry Save complete\n"); + else + WritetoConsole("Registry Save failed\n"); + + CloseHandle(handle); + return ; +/* + + if (REGTREE == HKEY_LOCAL_MACHINE) // < Vista + { + sprintf(cmd, + "regedit /E \"%s\\BPQ32.reg\" %s\\Software\\G8BPQ\\BPQ32", BPQDirectory, REGTREETEXT); + + ZeroMemory(&SInfo, sizeof(SInfo)); + + SInfo.cb=sizeof(SInfo); + SInfo.lpReserved=NULL; + SInfo.lpDesktop=NULL; + SInfo.lpTitle=NULL; + SInfo.dwFlags=0; + SInfo.cbReserved2=0; + SInfo.lpReserved2=NULL; + + if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0 ,NULL, NULL, &SInfo, &PInfo) == 0) + { + sprintf(Msg, "Error: CreateProcess for regedit failed 0%d\n", GetLastError() ); + WritetoConsole(Msg); + return; + } + } + else + { + + sprintf(cmd, + "/E \"%s\\BPQ32.reg\" %s\\Software\\G8BPQ\\BPQ32", BPQDirectory, REGTREETEXT); + + ZeroMemory(&sei, sizeof(sei)); + + sei.cbSize = sizeof(SHELLEXECUTEINFOW); + sei.hwnd = hWnd; + sei.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI; + sei.lpVerb = "runas"; + sei.lpFile = "regedit.exe"; + sei.lpParameters = cmd; + sei.nShow = SW_SHOWNORMAL; + + if (!ShellExecuteEx(&sei)) + { + sprintf(Msg, "Error: ShellExecuteEx for regedit failed %d\n", GetLastError() ); + WritetoConsole(Msg); + return; + } + } + + sprintf(Msg, "Registry Save Initiated\n", fn); + WritetoConsole(Msg); + + return ; +*/ +} + +BOOL CALLBACK EnumForCloseProc(HWND hwnd, LPARAM lParam) +{ + struct TNCINFO * TNC = (struct TNCINFO *)lParam; + UINT ProcessId; + + GetWindowThreadProcessId(hwnd, &ProcessId); + + for (i=0; i< AttachedProcesses; i++) + { + if (AttachedPIDList[i] == ProcessId) + { + Debugprintf("BPQ32 Close All Closing PID %d", ProcessId); + PostMessage(hwnd, WM_CLOSE, 1, 1); + // AttachedPIDList[i] = 0; // So we don't do it again + break; + } + } + + return (TRUE); +} +DllExport BOOL APIENTRY RestoreFrameWindow() +{ + return ShowWindow(FrameWnd, SW_RESTORE); +} + +DllExport VOID APIENTRY CreateNewTrayIcon() +{ + Shell_NotifyIcon(NIM_DELETE,&niData); + trayMenu = NULL; +} + +DllExport VOID APIENTRY CloseAllPrograms() +{ +// HANDLE hProc; + + // Close all attached BPQ32 programs + + Closing = TRUE; + + ShowWindow(FrameWnd, SW_RESTORE); + + GetWindowRect(FrameWnd, &FRect); + + SaveBPQ32Windows(); + CloseHostSessions(); + + if (AttachedProcesses == 1) + CloseBPQ32(); + + Debugprintf("BPQ32 Close All Processes %d PIDS %d %d %d %d", AttachedProcesses, AttachedPIDList[0], + AttachedPIDList[1], AttachedPIDList[2], AttachedPIDList[3]); + + if (MinimizetoTray) + Shell_NotifyIcon(NIM_DELETE,&niData); + + EnumWindows(EnumForCloseProc, (LPARAM)NULL); +} + +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_NAME 16383 +#define MAX_VALUE_DATA 65536 + +BOOL CopyReg(HKEY hKeyIn, HKEY hKeyOut) +{ + TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys=0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cValues; // number of values for key + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + + DWORD i, retCode; + + TCHAR achValue[MAX_VALUE_NAME]; + DWORD cchValue = MAX_VALUE_NAME; + + // Get the class name and the value count. + retCode = RegQueryInfoKey( + hKeyIn, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + // Enumerate the subkeys, until RegEnumKeyEx fails. + + if (cSubKeys) + { + Debugprintf( "\nNumber of subkeys: %d\n", cSubKeys); + + for (i=0; i 76) + { + len = sprintf(RegLine, "%s\\\r\n", RegLine); + WriteFile(hFile, RegLine, len, &written, NULL); + strcpy(RegLine, " "); + len = 2; + } + + len = sprintf(RegLine, "%s%02x,", RegLine, Value[k]); + } + RegLine[--len] = 0x0d; + RegLine[++len] = 0x0a; + len++; + + break; + + case REG_DWORD: //( 4 ) // 32-bit number +// case REG_DWORD_LITTLE_ENDIAN: //( 4 ) // 32-bit number (same as REG_DWORD) + + memcpy(&Intval, Value, 4); + len = sprintf(RegLine, "\"%s\"=dword:%08x\r\n", achValue, Intval); + break; + + case REG_DWORD_BIG_ENDIAN: //( 5 ) // 32-bit number + break; + case REG_LINK: //( 6 ) // Symbolic Link (unicode) + break; + case REG_MULTI_SZ: //( 7 ) // Multiple Unicode strings + + len = sprintf(RegLine, "\"%s\"=hex(7):%02x,00,", achValue, Value[0]); + for (k = 1; k < ValLen; k++) + { + if (len > 76) + { + len = sprintf(RegLine, "%s\\\r\n", RegLine); + WriteFile(hFile, RegLine, len, &written, NULL); + strcpy(RegLine, " "); + len = 2; + } + len = sprintf(RegLine, "%s%02x,", RegLine, Value[k]); + if (len > 76) + { + len = sprintf(RegLine, "%s\\\r\n", RegLine); + WriteFile(hFile, RegLine, len, &written, NULL); + strcpy(RegLine, " "); + } + len = sprintf(RegLine, "%s00,", RegLine); + } + + RegLine[--len] = 0x0d; + RegLine[++len] = 0x0a; + len++; + break; + + case REG_RESOURCE_LIST: //( 8 ) // Resource list in the resource map + break; + case REG_FULL_RESOURCE_DESCRIPTOR: //( 9 ) // Resource list in the hardware description + break; + case REG_RESOURCE_REQUIREMENTS_LIST://( 10 ) + break; + case REG_QWORD: //( 11 ) // 64-bit number +// case REG_QWORD_LITTLE_ENDIAN: //( 11 ) // 64-bit number (same as REG_QWORD) + break; + + } + + WriteFile(hFile, RegLine, len, &written, NULL); + } + } + } + + WriteFile(hFile, "\r\n", 2, &written, NULL); + + // Enumerate the subkeys, until RegEnumKeyEx fails. + + if (cSubKeys) + { + for (i=0; i> 1; + } + + Flags=GetApplFlags(i); + + if (OneBits > 1) + sprintf(&NewScreen[(i+1)*54],"%2d%s%3d %3d %3d %03x %3x %10s%-20s", + i, flag, RXCount(i), TXCount(i), MONCount(i), Mask, Flags, callsign, + BPQHOSTVECTOR[i-1].PgmName); + else + sprintf(&NewScreen[(i+1)*54],"%2d%s%3d %3d %3d %3d %3x %10s%-20s", + i, flag, RXCount(i), TXCount(i), MONCount(i), AppNumber, Flags, callsign, + BPQHOSTVECTOR[i-1].PgmName); + + } + } + + #include "StdExcept.c" + + if (Semaphore.Flag && Semaphore.SemProcessID == GetCurrentProcessId()) + FreeSemaphore(&Semaphore); + + } + + if (memcmp(Screen, NewScreen, 33 * 108) == 0) // No Change + return 0; + + memcpy(Screen, NewScreen, 33 * 108); + InvalidateRect(StatusWnd,NULL,FALSE); + + return(0); +} + +LRESULT CALLBACK StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + PAINTSTRUCT ps; + HDC hdc; + HFONT hOldFont ; + HGLOBAL hMem; + MINMAXINFO * mmi; + int i; + + switch (message) + { + case WM_TIMER: + + if (Semaphore.Flag == 0) + DoStatus(); + break; + + case WM_MDIACTIVATE: + + // Set the system info menu when getting activated + + if (lParam == (LPARAM) hWnd) + { + // Activate + + RemoveMenu(hBaseMenu, 1, MF_BYPOSITION); + AppendMenu(hBaseMenu, MF_STRING + MF_POPUP, (UINT)hConsMenu, "Actions"); + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hBaseMenu, (LPARAM) hWndMenu); + } + else + { + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hMainFrameMenu, (LPARAM) NULL); + } + + DrawMenuBar(FrameWnd); + + return TRUE; //DefMDIChildProc(hWnd, message, wParam, lParam); + + case WM_GETMINMAXINFO: + + mmi = (MINMAXINFO *)lParam; + mmi->ptMaxSize.x = 850; + mmi->ptMaxSize.y = 500; + mmi->ptMaxTrackSize.x = 850; + mmi->ptMaxTrackSize.y = 500; + + + case WM_COMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + //Parse the menu selections: + + switch (wmId) + { + +/* + case BPQSTREAMS: + + CheckMenuItem(hMenu,BPQSTREAMS,MF_CHECKED); + CheckMenuItem(hMenu,BPQIPSTATUS,MF_UNCHECKED); + + StreamDisplay = TRUE; + + break; + + case BPQIPSTATUS: + + CheckMenuItem(hMenu,BPQSTREAMS,MF_UNCHECKED); + CheckMenuItem(hMenu,BPQIPSTATUS,MF_CHECKED); + + StreamDisplay = FALSE; + memset(Screen, ' ', 4000); + + + break; + +*/ + + case BPQCOPY: + + // + // Copy buffer to clipboard + // + hMem=GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, 33*110); + + if (hMem != 0) + { + if (OpenClipboard(hWnd)) + { +// CopyScreentoBuffer(GlobalLock(hMem)); + GlobalUnlock(hMem); + EmptyClipboard(); + SetClipboardData(CF_TEXT,hMem); + CloseClipboard(); + } + else + { + GlobalFree(hMem); + } + + } + + break; + + } + + return DefMDIChildProc(hWnd, message, wParam, lParam); + + + case WM_SYSCOMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + switch (wmId) + { + case SC_MAXIMIZE: + + break; + + case SC_MINIMIZE: + + StatusMinimized = TRUE; + break; + + case SC_RESTORE: + + StatusMinimized = FALSE; + SendMessage(ClientWnd, WM_MDIRESTORE, (WPARAM)hWnd, 0); + break; + } + + return DefMDIChildProc(hWnd, message, wParam, lParam); + + case WM_PAINT: + + hdc = BeginPaint (hWnd, &ps); + + hOldFont = SelectObject( hdc, hFont) ; + + for (i=0; i<33; i++) + { + TextOut(hdc,0,i*14,&Screen[i*108],108); + } + + SelectObject( hdc, hOldFont ) ; + EndPaint (hWnd, &ps); + + break; + + case WM_DESTROY: + +// PostQuitMessage(0); + + break; + + + default: + + return DefMDIChildProc(hWnd, message, wParam, lParam); + + } + return (0); +} + +VOID SaveMDIWindowPos(HWND hWnd, char * RegKey, char * Value, BOOL Minimized) +{ + HKEY hKey=0; + char Size[80]; + char Key[80]; + int retCode, disp; + RECT Rect; + + if (IsWindow(hWnd) == FALSE) + return; + + ShowWindow(hWnd, SW_RESTORE); + + if (GetWindowRect(hWnd, &Rect) == FALSE) + return; + + // Make relative to Frame + + Rect.top -= FRect.top ; + Rect.left -= FRect.left; + Rect.bottom -= FRect.top; + Rect.right -= FRect.left; + + sprintf(Key, "SOFTWARE\\G8BPQ\\BPQ32\\%s", RegKey); + + retCode = RegCreateKeyEx(REGTREE, Key, 0, 0, 0, + KEY_ALL_ACCESS, NULL, &hKey, &disp); + + if (retCode == ERROR_SUCCESS) + { + sprintf(Size,"%d,%d,%d,%d,%d", Rect.left, Rect.right, Rect.top ,Rect.bottom, Minimized); + retCode = RegSetValueEx(hKey, Value, 0, REG_SZ,(BYTE *)&Size, strlen(Size)); + RegCloseKey(hKey); + } +} + +extern int GPSPort; +extern char LAT[]; // in standard APRS Format +extern char LON[]; // in standard APRS Format + +VOID SaveBPQ32Windows() +{ + HKEY hKey=0; + char Size[80]; + int retCode, disp; + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + int i; + + retCode = RegCreateKeyEx(REGTREE, "SOFTWARE\\G8BPQ\\BPQ32", 0, 0, 0, KEY_ALL_ACCESS, NULL, &hKey, &disp); + + if (retCode == ERROR_SUCCESS) + { + sprintf(Size,"%d,%d,%d,%d", FRect.left, FRect.right, FRect.top, FRect.bottom); + retCode = RegSetValueEx(hKey, "FrameWindowSize", 0, REG_SZ, (BYTE *)&Size, strlen(Size)); + + // Save GPS Position + + if (GPSPort) + { + sprintf(Size, "%s, %s", LAT, LON); + retCode = RegSetValueEx(hKey, "GPS", 0, REG_SZ,(BYTE *)&Size, strlen(Size)); + } + + RegCloseKey(hKey); + } + + SaveMDIWindowPos(StatusWnd, "", "StatusWindowSize", StatusMinimized); + SaveMDIWindowPos(hConsWnd, "", "WindowSize", ConsoleMinimized); + + for (i=0; iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR) + { + SaveWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + SaveAXIPWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); + } + } + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + SaveWindowPos(70); // Rigcontrol + + + if (hIPResWnd) + SaveMDIWindowPos(hIPResWnd, "", "IPResSize", IPMinimized); + + SaveHostSessions(); +} + +DllExport BOOL APIENTRY CheckIfOwner() +{ + // + // Returns TRUE if current process is root process + // that loaded the DLL + // + + if (TimerInst == GetCurrentProcessId()) + + return (TRUE); + else + return (FALSE); +} + +VOID GetParam(char * input, char * key, char * value) +{ + char * ptr = strstr(input, key); + char Param[2048]; + char * ptr1, * ptr2; + char c; + + + if (ptr) + { + ptr2 = strchr(ptr, '&'); + if (ptr2) *ptr2 = 0; + strcpy(Param, ptr + strlen(key)); + if (ptr2) *ptr2 = '&'; // Restore string + + // Undo any % transparency + + ptr1 = Param; + ptr2 = Param; + + c = *(ptr1++); + + while (c) + { + if (c == '%') + { + int n; + int m = *(ptr1++) - '0'; + if (m > 9) m = m - 7; + n = *(ptr1++) - '0'; + if (n > 9) n = n - 7; + + *(ptr2++) = m * 16 + n; + } + else if (c == '+') + *(ptr2++) = ' '; + else + *(ptr2++) = c; + + c = *(ptr1++); + } + + *(ptr2++) = 0; + + strcpy(value, Param); + } +} + +int GetListeningPortsPID(int Port) +{ + MIB_TCPTABLE_OWNER_PID * TcpTable = NULL; + PMIB_TCPROW_OWNER_PID Row; + int dwSize = 0; + DWORD n; + + // Get PID of process for this TCP Port + + // Get Length of table + + GetExtendedTcpTable(TcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0); + + TcpTable = malloc(dwSize); + + if (TcpTable == NULL) + return 0; + + GetExtendedTcpTable(TcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0); + + for (n = 0; n < TcpTable->dwNumEntries; n++) + { + Row = &TcpTable->table[n]; + + if (Row->dwLocalPort == Port && Row->dwState == MIB_TCP_STATE_LISTEN) + { + return Row->dwOwningPid; + break; + } + } + return 0; // Not found +} + +DllExport char * APIENTRY GetLOC() +{ + return LOC; +} + +DllExport void APIENTRY GetLatLon(double * lat, double * lon) +{ + *lat = LatFromLOC; + *lon = LonFromLOC; + return; +} + + +// UZ7HO Dll PTT interface + +// 1 ext_PTT_info +// 2 ext_PTT_settings +// 3 ext_PTT_OFF +// 4 ext_PTT_ON +// 5 ext_PTT_close +// 6 ext_PTT_open + +extern struct RIGINFO * DLLRIG; // Rig record for dll PTT interface (currently only for UZ7HO); + +VOID Rig_PTT(struct TNCINFO * TNC, BOOL PTTState); +VOID Rig_PTTEx(struct RIGINFO * RIG, BOOL PTTState, struct TNCINFO * TNC); + +int WINAPI ext_PTT_info() +{ + return 0; +} + +int WINAPI ext_PTT_settings() +{ + return 0; +} + +int WINAPI ext_PTT_OFF(int Port) +{ + if (DLLRIG) + Rig_PTTEx(DLLRIG, 0, 0); + + return 0; +} + +int WINAPI ext_PTT_ON(int Port) +{ + if (DLLRIG) + Rig_PTTEx(DLLRIG, 1, 0); + + return 0; +} +int WINAPI ext_PTT_close() +{ + if (DLLRIG) + Rig_PTTEx(DLLRIG, 0, 0); + + return 0; +} + +DllExport INT WINAPI ext_PTT_open() +{ + return 1; +} + +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; +} + diff --git a/Bpq32.c b/Bpq32.c index 1a5644f..b6acf29 100644 --- a/Bpq32.c +++ b/Bpq32.c @@ -3,7 +3,7 @@ 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 +LinBPQ/BPQ32 is free software: you can redistribute it and/or modifyextern int HTTP 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. @@ -1241,6 +1241,13 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // Add MQTT reporting of Mail Events (54) // Fix beaconong on KISSHF ports (55) // Fix MailAPI msgs endpoint +// Attempt to fix NC going to wrong application. (57) +// Improve ARDOP end of session code (58) +// Run M0LTE Map repoorting in a separate thread (59) +// Add support fro WhatsPac (59) +// Add timestamps to LIS monitor + + #define CKernel @@ -1503,6 +1510,7 @@ VOID APRSClose(); VOID CloseTNCEmulator(); VOID Poll_AGW(); +void RHPPoll(); BOOL AGWAPIInit(); int AGWAPITerminate(); @@ -1521,7 +1529,9 @@ UINT Sem_edx = 0; UINT Sem_esi = 0; UINT Sem_edi = 0; -void GetSemaphore(struct SEM * Semaphore, int ID); + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); void FreeSemaphore(struct SEM * Semaphore); DllExport void * BPQHOSTAPIPTR = &BPQHOSTAPI; @@ -1869,8 +1879,8 @@ VOID MonitorThread(int x) { // It is stuck - try to release - Debugprintf ("Semaphore locked - Process ID = %d, Held By %d", - Semaphore.SemProcessID, SemHeldByAPI); + Debugprintf ("Semaphore locked - Process ID = %d, Held By %d from %s Line %d", + Semaphore.SemProcessID, SemHeldByAPI, Semaphore.File, Semaphore.Line); // Write a minidump @@ -2291,6 +2301,7 @@ VOID TimerProcX() Poll_AGW(); DRATSPoll(); + RHPPoll(); CheckGuardZone(); diff --git a/CBPQ32.vcproj b/CBPQ32.vcproj index b66278e..dc7f72e 100644 --- a/CBPQ32.vcproj +++ b/CBPQ32.vcproj @@ -522,6 +522,10 @@ RelativePath="..\CommonSource\PortMapper.c" > + + @@ -601,7 +605,7 @@ > + + + + + + + + + + diff --git a/CBPQ32.vcproj.NOTTSDESKTOP.John.user b/CBPQ32.vcproj.NOTTSDESKTOP.John.user index f4ba73a..270b67b 100644 --- a/CBPQ32.vcproj.NOTTSDESKTOP.John.user +++ b/CBPQ32.vcproj.NOTTSDESKTOP.John.user @@ -37,7 +37,7 @@ Name="Release|Win32" > + + + + + + + + + + diff --git a/CHeaders.h b/CHeaders.h index beb6380..4b765a2 100644 --- a/CHeaders.h +++ b/CHeaders.h @@ -30,6 +30,9 @@ int IntDecodeFrame(MESSAGE * msg, char * buffer, time_t Stamp, uint64_t Mask, BO int IntSetTraceOptionsEx(uint64_t mask, int mtxparam, int mcomparam, int monUIOnly); int CountBits64(uint64_t in); + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + #define GetBuff() _GetBuff(__FILE__, __LINE__) #define ReleaseBuffer(s) _ReleaseBuffer(s, __FILE__, __LINE__) #define CheckGuardZone() _CheckGuardZone(__FILE__, __LINE__) @@ -64,13 +67,13 @@ DllExport int APIENTRY GetConnectionInfo(int stream, char * callsign, int * port, int * sesstype, int * paclen, int * maxframe, int * l4window); +#define LIBCONFIG_STATIC +#include "libconfig.h" -struct config_setting_t; - -int GetIntValue(struct config_setting_t * group, char * name); -BOOL GetStringValue(struct config_setting_t * group, char * name, char * value); -VOID SaveIntValue(struct config_setting_t * group, char * name, int value); -VOID SaveStringValue(struct config_setting_t * group, char * name, char * value); +int GetIntValue(config_setting_t * group, char * name); +BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen); +VOID SaveIntValue(config_setting_t * group, char * name, int value); +VOID SaveStringValue(config_setting_t * group, char * name, char * value); int EncryptPass(char * Pass, char * Encrypt); VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); @@ -92,7 +95,7 @@ VOID InnerCommandHandler(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer); VOID DoTheCommand(TRANSPORTENTRY * Session); char * MOVEANDCHECK(TRANSPORTENTRY * Session, char * Bufferptr, char * Source, int Len); VOID DISPLAYCIRCUIT(TRANSPORTENTRY * L4, char * Buffer); -char * strlop(const char * buf, char delim); +char * strlop(char * buf, char delim); BOOL CompareCalls(UCHAR * c1, UCHAR * c2); VOID PostDataAvailable(TRANSPORTENTRY * Session); @@ -155,7 +158,7 @@ Dll int APIENTRY SaveNodes (); struct SEM; -void GetSemaphore(struct SEM * Semaphore, int ID); +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); void FreeSemaphore(struct SEM * Semaphore); void MySetWindowText(HWND hWnd, char * Msg); diff --git a/CMSAuth.c b/CMSAuth.c index e2fbd06..ae63e4d 100644 --- a/CMSAuth.c +++ b/CMSAuth.c @@ -23,7 +23,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #ifdef LINBPQ #include "compatbits.h" -char * strlop(const char * buf, char delim); +char * strlop(char * buf, char delim); #define APIENTRY #define VOID void diff --git a/ChatUtilities.c b/ChatUtilities.c index 9a32436..0e5ef79 100644 --- a/ChatUtilities.c +++ b/ChatUtilities.c @@ -39,7 +39,8 @@ VOID __cdecl Logprintf(int LogMode, ChatCIRCUIT * conn, int InOut, const char * return; } -void GetSemaphore(struct SEM * Semaphore, int ID) + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line) { // // Wait for it to be free @@ -74,6 +75,9 @@ loop1: ; } + Semaphore->Line = Line; + strcpy(Semaphore->File, File); + return; } void FreeSemaphore(struct SEM * Semaphore) diff --git a/Cmd.c b/Cmd.c index cda5b7d..c5f43dd 100644 --- a/Cmd.c +++ b/Cmd.c @@ -35,7 +35,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses //#include "vmm.h" //#include "SHELLAPI.H" -#include "CHeaders.h" +#include "cheaders.h" #include "bpqaprs.h" #include "kiss.h" @@ -988,7 +988,6 @@ VOID CMDSTATS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, struct { char * ptr, *Context; int Port = 0, cols = NUMBEROFPORTS, i; - char * uptime; struct PORTCONTROL * PORT = PORTTABLE; struct PORTCONTROL * STARTPORT; diff --git a/CommonCode-skigdebian.c b/CommonCode-skigdebian.c new file mode 100644 index 0000000..90e6a08 --- /dev/null +++ b/CommonCode-skigdebian.c @@ -0,0 +1,5647 @@ +/* +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 +*/ + + + +// General C Routines common to bpq32 and linbpq. Mainly moved from BPQ32.c + +#pragma data_seg("_BPQDATA") + +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "mqtt.h" + +#pragma data_seg("_BPQDATA") + +#include "cheaders.h" +#include "tncinfo.h" +#include "configstructs.h" + +extern struct CONFIGTABLE xxcfg; + +#define LIBCONFIG_STATIC +#include "libconfig.h" + +#ifndef LINBPQ + +//#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. + +#include "commctrl.h" +#include "Commdlg.h" + +#endif + +struct TNCINFO * TNCInfo[71]; // Records are Malloc'd + +extern int ReportTimer; + +Dll VOID APIENTRY Send_AX(UCHAR * Block, DWORD Len, UCHAR Port); +TRANSPORTENTRY * SetupSessionFromHost(PBPQVECSTRUC HOST, UINT ApplMask); +int Check_Timer(); +VOID SENDUIMESSAGE(struct DATAMESSAGE * Msg); +DllExport struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot); +VOID APIENTRY md5 (char *arg, unsigned char * checksum); +VOID COMSetDTR(HANDLE fd); +VOID COMClearDTR(HANDLE fd); +VOID COMSetRTS(HANDLE fd); +VOID COMClearRTS(HANDLE fd); + +VOID WriteMiniDump(); +void printStack(void); +char * FormatMH(PMHSTRUC MH, char Format); +void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode); +void SendDataToPktMap(); + +extern BOOL LogAllConnects; +extern BOOL M0LTEMap; + +char * stristr (char *ch1, char *ch2); + +extern VOID * ENDBUFFERPOOL; + + +// Read/Write length field in a buffer header + +// Needed for Big/LittleEndian and ARM5 (unaligned operation problem) portability + + +VOID PutLengthinBuffer(PDATAMESSAGE buff, USHORT datalen) +{ + if (datalen <= sizeof(void *) + 4) + datalen = sizeof(void *) + 4; // Protect + + memcpy(&buff->LENGTH, &datalen, 2); +} + +int GetLengthfromBuffer(PDATAMESSAGE buff) +{ + USHORT Length; + + memcpy(&Length, &buff->LENGTH, 2); + return Length; +} + +BOOL CheckQHeadder(UINT * Q) +{ +#ifdef WIN32 + UINT Test; + + __try + { + Test = *Q; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + Debugprintf("Invalid Q Header %p", Q); + printStack(); + return FALSE; + } +#endif + return TRUE; +} + +// Get buffer from Queue + + +VOID * _Q_REM(VOID **PQ, char * File, int Line) +{ + void ** Q; + void ** first; + VOID * next; + PMESSAGE Test; + + // PQ may not be word aligned, so copy as bytes (for ARM5) + + Q = PQ; + + if (Semaphore.Flag == 0) + Debugprintf("Q_REM called without semaphore from %s Line %d", File, Line); + + if (CheckQHeadder((UINT *) Q) == 0) + return(0); + + first = Q[0]; + + if (first == 0) + return (0); // Empty + + next = first[0]; // Address of next buffer + + Q[0] = next; + + // Make sure guard zone is zeros + + Test = (PMESSAGE)first; + + if (Test->GuardZone != 0) + { + Debugprintf("Q_REM %p GUARD ZONE CORRUPT %x Called from %s Line %d", first, Test->GuardZone, File, Line); + printStack(); + } + + return first; +} + +// Non=pool version (for IPGateway) + +VOID * _Q_REM_NP(VOID *PQ, char * File, int Line) +{ + void ** Q; + void ** first; + void * next; + + // PQ may not be word aligned, so copy as bytes (for ARM5) + + Q = PQ; + + if (CheckQHeadder((UINT *)Q) == 0) + return(0); + + first = Q[0]; + + if (first == 0) return (0); // Empty + + next = first[0]; // Address of next buffer + + Q[0] = next; + + return first; +} + +// Return Buffer to Free Queue + +extern VOID * BUFFERPOOL; +extern void ** Bufferlist[1000]; +void printStack(void); + +void _CheckGuardZone(char * File, int Line) +{ + int n = 0, i, offset = 0; + PMESSAGE Test; + UINT CodeDump[8]; + unsigned char * ptr; + + n = NUMBEROFBUFFERS; + + while (n--) + { + Test = (PMESSAGE)Bufferlist[n]; + + if (Test && Test->GuardZone) + { + Debugprintf("CheckGuardZone %p GUARD ZONE CORRUPT %d Called from %s Line %d", Test, Test->Process, File, Line); + + offset = 0; + ptr = (unsigned char *)Test; + + while (offset < 400) + { + memcpy(CodeDump, &ptr[offset], 32); + + for (i = 0; i < 8; i++) + CodeDump[i] = htonl(CodeDump[i]); + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + &ptr[offset], CodeDump[0], CodeDump[1], CodeDump[2], CodeDump[3], CodeDump[4], CodeDump[5], CodeDump[6], CodeDump[7]); + + offset += 32; + } + WriteMiniDump(); +#ifdef MDIKERNEL + CloseAllNeeded = 1; +#endif + } + + } +} + +UINT _ReleaseBuffer(VOID *pBUFF, char * File, int Line) +{ + void ** pointer, ** BUFF = pBUFF; + int n = 0; + void ** debug; + PMESSAGE Test; + UINT CodeDump[16]; + int i; + unsigned int rev; + + if (Semaphore.Flag == 0) + Debugprintf("ReleaseBuffer called without semaphore from %s Line %d", File, Line); + + // Make sure address is within pool + + if ((uintptr_t)BUFF < (uintptr_t)BUFFERPOOL || (uintptr_t)BUFF > (uintptr_t)ENDBUFFERPOOL) + { + // Not pointing to a buffer . debug points to the buffer that this is chained from + + // Dump first chunk and source tag + + memcpy(CodeDump, BUFF, 64); + + Debugprintf("Releasebuffer Buffer not in pool from %s Line %d, ptr %p prev %d", File, Line, BUFF, 0); + + for (i = 0; i < 16; i++) + { + rev = (CodeDump[i] & 0xff) << 24; + rev |= (CodeDump[i] & 0xff00) << 8; + rev |= (CodeDump[i] & 0xff0000) >> 8; + rev |= (CodeDump[i] & 0xff000000) >> 24; + + CodeDump[i] = rev; + } + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + Bufferlist[n], CodeDump[0], CodeDump[1], CodeDump[2], CodeDump[3], CodeDump[4], CodeDump[5], CodeDump[6], CodeDump[7]); + + Debugprintf(" %08x %08x %08x %08x %08x %08x %08x %08x", + CodeDump[8], CodeDump[9], CodeDump[10], CodeDump[11], CodeDump[12], CodeDump[13], CodeDump[14], CodeDump[15]); + + + return 0; + } + + Test = (PMESSAGE)pBUFF; + + if (Test->GuardZone != 0) + { + Debugprintf("_ReleaseBuffer %p GUARD ZONE CORRUPT %x Called from %s Line %d", pBUFF, Test->GuardZone, File, Line); + } + + while (n <= NUMBEROFBUFFERS) + { + if (BUFF == Bufferlist[n++]) + goto BOK1; + } + + Debugprintf("ReleaseBuffer %X not in Pool called from %s Line %d", BUFF, File, Line); + printStack(); + + return 0; + +BOK1: + + n = 0; + + // validate free Queue + + pointer = FREE_Q; + debug = &FREE_Q; + + while (pointer) + { + // Validate pointer to make sure it is in pool - it may be a duff address if Q is corrupt + + Test = (PMESSAGE)pointer; + + if (Test->GuardZone || (uintptr_t)pointer < (uintptr_t)BUFFERPOOL || (uintptr_t)pointer > (uintptr_t)ENDBUFFERPOOL) + { + // Not pointing to a buffer . debug points to the buffer that this is chained from + + // Dump first chunk and source tag + + memcpy(CodeDump, debug, 64); + + Debugprintf("Releasebuffer Pool Corruption n = %d, ptr %p prev %p", n, pointer, debug); + + for (i = 0; i < 16; i++) + { + rev = (CodeDump[i] & 0xff) << 24; + rev |= (CodeDump[i] & 0xff00) << 8; + rev |= (CodeDump[i] & 0xff0000) >> 8; + rev |= (CodeDump[i] & 0xff000000) >> 24; + + CodeDump[i] = rev; + } + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + Bufferlist[n], CodeDump[0], CodeDump[1], CodeDump[2], CodeDump[3], CodeDump[4], CodeDump[5], CodeDump[6], CodeDump[7]); + + Debugprintf(" %08x %08x %08x %08x %08x %08x %08x %08x", + CodeDump[8], CodeDump[9], CodeDump[10], CodeDump[11], CodeDump[12], CodeDump[13], CodeDump[14], CodeDump[15]); + + if (debug[400]) + Debugprintf(" %s", &debug[400]); + + } + + // See if already on free Queue + + if (pointer == BUFF) + { + Debugprintf("Trying to free buffer %p when already on FREE_Q called from %s Line %d", BUFF, File, Line); +// WriteMiniDump(); + return 0; + } + +// if (pointer[0] && pointer == pointer[0]) +// { +// Debugprintf("Buffer chained to itself"); +// return 0; +// } + + debug = pointer; + pointer = pointer[0]; + n++; + + if (n > 1000) + { + Debugprintf("Loop searching free chain - pointer = %p %p", debug, pointer); + return 0; + } + } + + pointer = FREE_Q; + + *BUFF = pointer; + + FREE_Q = BUFF; + + QCOUNT++; + + return 0; +} + +int _C_Q_ADD(VOID *PQ, VOID *PBUFF, char * File, int Line) +{ + void ** Q; + void ** BUFF = PBUFF; + void ** next; + PMESSAGE Test; + + + int n = 0; + +// PQ may not be word aligned, so copy as bytes (for ARM5) + + Q = PQ; + + if (Semaphore.Flag == 0) + Debugprintf("C_Q_ADD called without semaphore from %s Line %d", File, Line); + + if (CheckQHeadder((UINT *)Q) == 0) // Make sure Q header is readable + return(0); + + // Make sure guard zone is zeros + + Test = (PMESSAGE)PBUFF; + + if (Test->GuardZone != 0) + { + Debugprintf("C_Q_ADD %p GUARD ZONE CORRUPT %x Called from %s Line %d", PBUFF, Test->GuardZone, File, Line); + } + + Test = (PMESSAGE)Q; + + + + // Make sure address is within pool + + while (n <= NUMBEROFBUFFERS) + { + if (BUFF == Bufferlist[n++]) + goto BOK2; + } + + Debugprintf("C_Q_ADD %X not in Pool called from %s Line %d", BUFF, File, Line); + printStack(); + + return 0; + +BOK2: + + BUFF[0] = 0; // Clear chain in new buffer + + if (Q[0] == 0) // Empty + { + Q[0]=BUFF; // New one on front + return(0); + } + + next = Q[0]; + + while (next[0] != 0) + { + next = next[0]; // Chain to end of queue + } + next[0] = BUFF; // New one on end + + return(0); +} + +// Non-pool version + +int C_Q_ADD_NP(VOID *PQ, VOID *PBUFF) +{ + void ** Q; + void ** BUFF = PBUFF; + void ** next; + int n = 0; + +// PQ may not be word aligned, so copy as bytes (for ARM5) + + Q = PQ; + + if (CheckQHeadder((UINT *)Q) == 0) // Make sure Q header is readable + return(0); + + BUFF[0]=0; // Clear chain in new buffer + + if (Q[0] == 0) // Empty + { + Q[0]=BUFF; // New one on front +// memcpy(PQ, &BUFF, 4); + return 0; + } + next = Q[0]; + + while (next[0] != 0) + next=next[0]; // Chain to end of queue + + next[0] = BUFF; // New one on end + + return(0); +} + + +int C_Q_COUNT(VOID *PQ) +{ + void ** Q; + int count = 0; + +// PQ may not be word aligned, so copy as bytes (for ARM5) + + Q = PQ; + + if (CheckQHeadder((UINT *)Q) == 0) // Make sure Q header is readable + return(0); + + // SEE HOW MANY BUFFERS ATTACHED TO Q HEADER + + while (*Q) + { + count++; + if ((count + QCOUNT) > MAXBUFFS) + { + Debugprintf("C_Q_COUNT Detected corrupt Q %p len %d", PQ, count); + return count; + } + Q = *Q; + } + + return count; +} + +VOID * _GetBuff(char * File, int Line) +{ + UINT * Temp; + MESSAGE * Msg; + char * fptr = 0; + unsigned char * byteaddr; + + Temp = Q_REM(&FREE_Q); + +// FindLostBuffers(); + + if (Semaphore.Flag == 0) + Debugprintf("GetBuff called without semaphore from %s Line %d", File, Line); + + if (Temp) + { + QCOUNT--; + + if (QCOUNT < MINBUFFCOUNT) + MINBUFFCOUNT = QCOUNT; + + Msg = (MESSAGE *)Temp; + fptr = File + (int)strlen(File); + while (*fptr != '\\' && *fptr != '/') + fptr--; + fptr++; + + // Buffer Length is BUFFLEN, but buffers are allocated 512 + // So add file info in gap between + + byteaddr = (unsigned char *)Msg; + + + memset(&byteaddr[0], 0, 64); // simplify debugging lost buffers + memset(&byteaddr[400], 0, 64); // simplify debugging lost buffers + sprintf(&byteaddr[400], "%s %d", fptr, Line); + + Msg->Process = (short)GetCurrentProcessId(); + Msg->Linkptr = NULL; + Msg->Padding[0] = 0; // Used for modem status info + } + else + Debugprintf("Warning - Getbuff returned NULL"); + + return Temp; +} + +void * zalloc(int len) +{ + // malloc and clear + + void * ptr; + + ptr=malloc(len); + + if (ptr) + memset(ptr, 0, len); + + return ptr; +} + +char * strlop(char * buf, char delim) +{ + // Terminate buf at delim, and return rest of string + + char * ptr; + + if (buf == NULL) return NULL; // Protect + + ptr = strchr(buf, delim); + + if (ptr == NULL) return NULL; + + *(ptr)++=0; + + return ptr; +} + +VOID DISPLAYCIRCUIT(TRANSPORTENTRY * L4, char * Buffer) +{ + UCHAR Type = L4->L4CIRCUITTYPE; + struct PORTCONTROL * PORT; + struct _LINKTABLE * LINK; + BPQVECSTRUC * VEC; + struct DEST_LIST * DEST; + + char Normcall[20] = ""; // Could be alias:call + char Normcall2[11] = ""; + char Alias[11] = ""; + + Buffer[0] = 0; + + switch (Type) + { + case PACTOR+UPLINK: + + PORT = L4->L4TARGET.PORT; + + ConvFromAX25(L4->L4USER, Normcall); + strlop(Normcall, ' '); + + if (PORT) + sprintf(Buffer, "%s %d/%d(%s)", "TNC Uplink Port", PORT->PORTNUMBER, L4->KAMSESSION, Normcall); + + return; + + + case PACTOR+DOWNLINK: + + PORT = L4->L4TARGET.PORT; + + if (PORT) + sprintf(Buffer, "%s %d/%d", "Attached to Port", PORT->PORTNUMBER, L4->KAMSESSION); + return; + + + case L2LINK+UPLINK: + + LINK = L4->L4TARGET.LINK; + + ConvFromAX25(L4->L4USER, Normcall); + strlop(Normcall, ' '); + + if (LINK &&LINK->LINKPORT) + sprintf(Buffer, "%s %d(%s)", "Uplink", LINK->LINKPORT->PORTNUMBER, Normcall); + + return; + + case L2LINK+DOWNLINK: + + LINK = L4->L4TARGET.LINK; + + if (LINK == NULL) + return; + + ConvFromAX25(LINK->OURCALL, Normcall); + strlop(Normcall, ' '); + + ConvFromAX25(LINK->LINKCALL, Normcall2); + strlop(Normcall2, ' '); + + sprintf(Buffer, "%s %d(%s %s)", "Downlink", LINK->LINKPORT->PORTNUMBER, Normcall, Normcall2); + return; + + case BPQHOST + UPLINK: + case BPQHOST + DOWNLINK: + + // if the call has a Level 4 address display ALIAS:CALL, else just Call + + if (FindDestination(L4->L4USER, &DEST)) + Normcall[DecodeNodeName(DEST->DEST_CALL, Normcall)] = 0; // null terminate + else + Normcall[ConvFromAX25(L4->L4USER, Normcall)] = 0; + + VEC = L4->L4TARGET.HOST; + sprintf(Buffer, "%s%02d(%s)", "Host", (int)(VEC - BPQHOSTVECTOR) + 1, Normcall); + return; + + case SESSION + DOWNLINK: + case SESSION + UPLINK: + + ConvFromAX25(L4->L4USER, Normcall); + strlop(Normcall, ' '); + + DEST = L4->L4TARGET.DEST; + + if (DEST == NULL) + return; + + ConvFromAX25(DEST->DEST_CALL, Normcall2); + strlop(Normcall2, ' '); + + memcpy(Alias, DEST->DEST_ALIAS, 6); + strlop(Alias, ' '); + + sprintf(Buffer, "Circuit(%s:%s %s)", Alias, Normcall2, Normcall); + + return; + } +} + +VOID CheckForDetach(struct TNCINFO * TNC, int Stream, struct STREAMINFO * STREAM, + VOID TidyCloseProc(struct TNCINFO * TNC, int Stream), VOID ForcedCloseProc(struct TNCINFO * TNC, int Stream), VOID CloseComplete(struct TNCINFO * TNC, int Stream)) +{ + void ** buffptr; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0) + { + // Node has disconnected - clear any connection + + if (STREAM->Disconnecting) + { + // Already detected the detach, and have started to close + + STREAM->DisconnectingTimeout--; + + if (STREAM->DisconnectingTimeout) + return; // Give it a bit longer + + // Close has timed out - force a disc, and clear + + ForcedCloseProc(TNC, Stream); // Send Tidy Disconnect + + goto NotConnected; + } + + // New Disconnect + + Debugprintf("New Disconnect Port %d Q %x", TNC->Port, STREAM->BPQtoPACTOR_Q); + + if (STREAM->Connected || STREAM->Connecting) + { + + // Need to do a tidy close + + STREAM->Connecting = FALSE; + STREAM->Disconnecting = TRUE; + STREAM->DisconnectingTimeout = 300; // 30 Secs + + if (Stream == 0) + SetWindowText(TNC->xIDC_TNCSTATE, "Disconnecting"); + + // Create a traffic record + + hookL4SessionDeleted(TNC, STREAM); + + if (STREAM->BPQtoPACTOR_Q) // Still data to send? + return; // Will close when all acked + +// if (STREAM->FramesOutstanding && TNC->Hardware == H_UZ7HO) +// return; // Will close when all acked + + TidyCloseProc(TNC, Stream); // Send Tidy Disconnect + + return; + } + + // Not connected +NotConnected: + + STREAM->Disconnecting = FALSE; + STREAM->Attached = FALSE; + STREAM->Connecting = FALSE; + STREAM->Connected = FALSE; + + if (Stream == 0) + SetWindowText(TNC->xIDC_TNCSTATE, "Free"); + + STREAM->FramesQueued = 0; + STREAM->FramesOutstanding = 0; + + CloseComplete(TNC, Stream); + + if (TNC->DefaultRXFreq && TNC->RXRadio) + { + char Msg[128]; + + sprintf(Msg, "R%d %f", TNC->RXRadio, TNC->DefaultRXFreq); + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + } + + if (TNC->DefaultTXFreq && TNC->TXRadio && TNC->TXRadio != TNC->RXRadio) + { + char Msg[128]; + + sprintf(Msg, "R%d %f", TNC->TXRadio, TNC->DefaultTXFreq); + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + } + + while(STREAM->BPQtoPACTOR_Q) + { + buffptr=Q_REM(&STREAM->BPQtoPACTOR_Q); + ReleaseBuffer(buffptr); + } + + while(STREAM->PACTORtoBPQ_Q) + { + buffptr=Q_REM(&STREAM->PACTORtoBPQ_Q); + ReleaseBuffer(buffptr); + } + } +} + +char * CheckAppl(struct TNCINFO * TNC, char * Appl) +{ + APPLCALLS * APPL; + BPQVECSTRUC * PORTVEC; + int Allocated = 0, Available = 0; + int App, Stream; + struct TNCINFO * APPLTNC; + +// Debugprintf("Checking if %s is running", Appl); + + for (App = 0; App < 32; App++) + { + APPL=&APPLCALLTABLE[App]; + + if (_memicmp(APPL->APPLCMD, Appl, 12) == 0) + { + int _APPLMASK = 1 << App; + + // If App has an alias, assume it is running , unless a CMS alias - then check CMS + + if (APPL->APPLHASALIAS) + { + if (_memicmp(APPL->APPLCMD, "RELAY ", 6) == 0) + return APPL->APPLCALL_TEXT; // Assume people using RELAY know what they are doing + + if (APPL->APPLPORT && (_memicmp(APPL->APPLCMD, "RMS ", 4) == 0)) + { + APPLTNC = TNCInfo[APPL->APPLPORT]; + { + if (APPLTNC) + { + if (APPLTNC->TCPInfo && !APPLTNC->TCPInfo->CMSOK && !APPLTNC->TCPInfo->FallbacktoRelay) + return NULL; + } + } + } + return APPL->APPLCALL_TEXT; + } + + // See if App is running + + PORTVEC = &BPQHOSTVECTOR[0]; + + for (Stream = 0; Stream < 64; Stream++) + { + if (PORTVEC->HOSTAPPLMASK & _APPLMASK) + { + Allocated++; + + if (PORTVEC->HOSTSESSION == 0 && (PORTVEC->HOSTFLAGS & 3) == 0) + { + // Free and no outstanding report + + return APPL->APPLCALL_TEXT; // Running + } + } + PORTVEC++; + } + } + } + + return NULL; // Not Running +} + +VOID SetApplPorts() +{ + // If any appl has an alias, get port number + + struct APPLCONFIG * App; + APPLCALLS * APPL; + + char C[80]; + char Port[80]; + char Call[80]; + + int i, n; + + App = &xxcfg.C_APPL[0]; + + for (i=0; i < NumberofAppls; i++) + { + APPL=&APPLCALLTABLE[i]; + + if (APPL->APPLHASALIAS) + { + n = sscanf(App->CommandAlias, "%s %s %s", &C[0], &Port[0], &Call[0]); + if (n == 3) + APPL->APPLPORT = atoi(Port); + } + App++; + } +} + + +char Modenames[19][10] = {"WINMOR", "SCS", "KAM", "AEA", "HAL", "TELNET", "TRK", + "V4", "UZ7HO", "MPSK", "FLDIGI", "UIARQ", "ARDOP", "VARA", + "SERIAL", "KISSHF", "WINRPR", "HSMODEM", "FREEDATA"}; + +BOOL ProcessIncommingConnect(struct TNCINFO * TNC, char * Call, int Stream, BOOL SENDCTEXT) +{ + return ProcessIncommingConnectEx(TNC, Call, Stream, SENDCTEXT, FALSE); +} + +BOOL ProcessIncommingConnectEx(struct TNCINFO * TNC, char * Call, int Stream, BOOL SENDCTEXT, BOOL AllowTR) +{ + TRANSPORTENTRY * Session; + int Index = 0; + PMSGWITHLEN buffptr; + int Totallen = 0; + UCHAR * ptr; + struct PORTCONTROL * PORT = (struct PORTCONTROL *)TNC->PortRecord; + struct STREAMINFO * STREAM = &TNC->Streams[Stream]; + + // Stop Scanner + + if (Stream == 0 || TNC->Hardware == H_UZ7HO) + { + char Msg[80]; + + sprintf(Msg, "%d SCANSTOP", TNC->Port); + + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + + UpdateMH(TNC, Call, '+', 'I'); + } + + Session = L4TABLE; + + // Find a free Circuit Entry + + while (Index < MAXCIRCUITS) + { + if (Session->L4USER[0] == 0) + break; + + Session++; + Index++; + } + + if (Index == MAXCIRCUITS) + return FALSE; // Tables Full + + memset(Session, 0, sizeof(TRANSPORTENTRY)); + + memcpy(STREAM->RemoteCall, Call, 9); // Save Text Callsign + + // May be subsequently rejected but a good place to capture calls + + hookL4SessionAccepted(STREAM, Call, TNC->TargetCall); + + if (AllowTR) + ConvToAX25Ex(Call, Session->L4USER); // Allow -T and -R SSID's for MPS + else + ConvToAX25(Call, Session->L4USER); + ConvToAX25(MYNODECALL, Session->L4MYCALL); + Session->CIRCUITINDEX = Index; + Session->CIRCUITID = NEXTID; + NEXTID++; + if (NEXTID == 0) NEXTID++; // Keep non-zero + + TNC->PortRecord->ATTACHEDSESSIONS[Stream] = Session; + STREAM->Attached = TRUE; + + Session->L4TARGET.EXTPORT = TNC->PortRecord; + + Session->L4CIRCUITTYPE = UPLINK+PACTOR; + Session->L4WINDOW = L4DEFAULTWINDOW; + Session->L4STATE = 5; + Session->SESSIONT1 = L4T1; + Session->SESSPACLEN = TNC->PortRecord->PORTCONTROL.PORTPACLEN; + Session->KAMSESSION = Stream; + + STREAM->Connected = TRUE; // Subsequent data to data channel + + if (LogAllConnects) + { + if (TNC->TargetCall[0]) + WriteConnectLog(Call, TNC->TargetCall, Modenames[TNC->Hardware - 1]); + else + WriteConnectLog(Call, MYNODECALL, Modenames[TNC->Hardware - 1]); + } + + if (SENDCTEXT == 0) + return TRUE; + + // if Port CTEXT defined, use it + + if (PORT->CTEXT) + { + Totallen = strlen(PORT->CTEXT); + ptr = PORT->CTEXT; + } + else if (HFCTEXTLEN > 0) + { + Totallen = HFCTEXTLEN; + ptr = HFCTEXT; + } + else + return TRUE; + + while (Totallen > 0) + { + int sendLen = TNC->PortRecord->ATTACHEDSESSIONS[Stream]->SESSPACLEN; + + if (sendLen == 0) + sendLen = 80; + + if (Totallen < sendLen) + sendLen = Totallen; + + buffptr = (PMSGWITHLEN)GetBuff(); + if (buffptr == 0) return TRUE; // No buffers + + buffptr->Len = sendLen; + memcpy(&buffptr->Data[0], ptr, sendLen); + C_Q_ADD(&TNC->Streams[Stream].BPQtoPACTOR_Q, buffptr); + Totallen -= sendLen; + ptr += sendLen; + } + return TRUE; +} + +char * Config; +static char * ptr1, * ptr2; + +BOOL ReadConfigFile(int Port, int ProcLine(char * buf, int Port)) +{ + char buf[256],errbuf[256]; + + if (TNCInfo[Port]) // If restarting, free old config + free(TNCInfo[Port]); + + TNCInfo[Port] = NULL; + + Config = PortConfig[Port]; + + if (Config) + { + // Using config from bpq32.cfg + + if (strlen(Config) == 0) + { + // Empty Config File - OK for most types + + struct TNCINFO * TNC = TNCInfo[Port] = zalloc(sizeof(struct TNCINFO)); + + TNC->InitScript = malloc(2); + TNC->InitScript[0] = 0; + + return TRUE; + } + + ptr1 = Config; + + ptr2 = strchr(ptr1, 13); + while(ptr2) + { + memcpy(buf, ptr1, ptr2 - ptr1 + 1); + buf[ptr2 - ptr1 + 1] = 0; + ptr1 = ptr2 + 2; + ptr2 = strchr(ptr1, 13); + + strcpy(errbuf,buf); // save in case of error + + if (!ProcLine(buf, Port)) + { + WritetoConsoleLocal("\n"); + WritetoConsoleLocal("Bad config record "); + WritetoConsoleLocal(errbuf); + } + } + } + else + { + sprintf(buf," ** Error - No Configuration info in bpq32.cfg"); + WritetoConsoleLocal(buf); + } + + return (TRUE); +} +int GetLine(char * buf) +{ +loop: + + if (ptr2 == NULL) + return 0; + + memcpy(buf, ptr1, ptr2 - ptr1 + 2); + buf[ptr2 - ptr1 + 2] = 0; + ptr1 = ptr2 + 2; + ptr2 = strchr(ptr1, 13); + + if (buf[0] < 0x20) goto loop; + if (buf[0] == '#') goto loop; + if (buf[0] == ';') goto loop; + + if (buf[strlen(buf)-1] < 0x20) buf[strlen(buf)-1] = 0; + if (buf[strlen(buf)-1] < 0x20) buf[strlen(buf)-1] = 0; + buf[strlen(buf)] = 13; + + return 1; +} +VOID DigiToMultiplePorts(struct PORTCONTROL * PORTVEC, PMESSAGE Msg) +{ + USHORT Mask=PORTVEC->DIGIMASK; + int i; + + for (i=1; i<=NUMBEROFPORTS; i++) + { + if (Mask & 1) + { + // Block includes the Msg Header (7/11 bytes), Len Does not! + + Msg->PORT = i; + Send_AX((UCHAR *)&Msg, Msg->LENGTH - MSGHDDRLEN, i); + Mask>>=1; + } + } +} + +int CompareAlias(struct DEST_LIST ** a, struct DEST_LIST ** b) +{ + return memcmp(a[0]->DEST_ALIAS, b[0]->DEST_ALIAS, 6); + /* strcmp functions works exactly as expected from comparison function */ +} + + +int CompareNode(struct DEST_LIST ** a, struct DEST_LIST ** b) +{ + return memcmp(a[0]->DEST_CALL, b[0]->DEST_CALL, 7); +} + +DllExport int APIENTRY CountFramesQueuedOnStream(int Stream) +{ + BPQVECSTRUC * PORTVEC = &BPQHOSTVECTOR[Stream-1]; // API counts from 1 + TRANSPORTENTRY * L4 = PORTVEC->HOSTSESSION; + + int Count = 0; + + if (L4) + { + if (L4->L4CROSSLINK) // CONNECTED? + Count = CountFramesQueuedOnSession(L4->L4CROSSLINK); + else + Count = CountFramesQueuedOnSession(L4); + } + return Count; +} + +DllExport int APIENTRY ChangeSessionCallsign(int Stream, unsigned char * AXCall) +{ + // Equivalent to "*** linked to" command + + memcpy(BPQHOSTVECTOR[Stream-1].HOSTSESSION->L4USER, AXCall, 7); + return (0); +} + +DllExport int APIENTRY ChangeSessionPaclen(int Stream, int Paclen) +{ + BPQHOSTVECTOR[Stream-1].HOSTSESSION->SESSPACLEN = Paclen; + return (0); +} + +DllExport int APIENTRY ChangeSessionIdletime(int Stream, int idletime) +{ + if (BPQHOSTVECTOR[Stream-1].HOSTSESSION) + BPQHOSTVECTOR[Stream-1].HOSTSESSION->L4LIMIT = idletime; + return (0); +} + +DllExport int APIENTRY Get_APPLMASK(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].HOSTAPPLMASK; +} +DllExport int APIENTRY GetStreamPID(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].STREAMOWNER; +} + +DllExport int APIENTRY GetApplFlags(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].HOSTAPPLFLAGS; +} + +DllExport int APIENTRY GetApplNum(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].HOSTAPPLNUM; +} + +DllExport int APIENTRY GetApplMask(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].HOSTAPPLMASK; +} + +DllExport BOOL APIENTRY GetAllocationState(int Stream) +{ + return BPQHOSTVECTOR[Stream-1].HOSTFLAGS & 0x80; +} + +VOID Send_AX_Datagram(PDIGIMESSAGE Block, DWORD Len, UCHAR Port); + +extern int InitDone; +extern int SemHeldByAPI; +extern char pgm[256]; // Uninitialised so per process +extern int BPQHOSTAPI(); + + +VOID POSTSTATECHANGE(BPQVECSTRUC * SESS) +{ + // Post a message if requested +#ifndef LINBPQ + if (SESS->HOSTHANDLE) + PostMessage(SESS->HOSTHANDLE, BPQMsg, SESS->HOSTSTREAM, 4); +#endif + return; +} + + +DllExport int APIENTRY SessionControl(int stream, int command, int Mask) +{ + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return (0); + + SESS = &BPQHOSTVECTOR[stream]; + + // Send Session Control command (BPQHOST function 6) + //; CL=0 CONNECT USING APPL MASK IN DL + //; CL=1, CONNECT. CL=2 - DISCONNECT. CL=3 RETURN TO NODE + + if (command > 1) + { + // Disconnect + + if (SESS->HOSTSESSION == 0) + { + SESS->HOSTFLAGS |= 1; // State Change + POSTSTATECHANGE(SESS); + return 0; // NOT CONNECTED + } + + if (command == 3) + SESS->HOSTFLAGS |= 0x20; // Set Stay + + SESS->HOSTFLAGS |= 0x40; // SET 'DISC REQ' FLAG + + return 0; + } + + // 0 or 1 - connect + + if (SESS->HOSTSESSION) // ALREADY CONNECTED + { + SESS->HOSTFLAGS |= 1; // State Change + POSTSTATECHANGE(SESS); + return 0; + } + + // SET UP A SESSION FOR THE CONSOLE + + SESS->HOSTFLAGS |= 0x80; // SET ALLOCATED BIT + + if (command == 1) // Zero is mask supplied by caller + Mask = SESS->HOSTAPPLMASK; // SO WE GET CORRECT CALLSIGN + + L4 = SetupSessionFromHost(SESS, Mask); + + if (L4 == 0) // tables Full + { + SESS->HOSTFLAGS |= 3; // State Change + POSTSTATECHANGE(SESS); + return 0; + } + + SESS->HOSTSESSION = L4; + L4->L4CIRCUITTYPE = BPQHOST | UPLINK; + L4->Secure_Session = AuthorisedProgram; // Secure Host Session + + SESS->HOSTFLAGS |= 1; // State Change + POSTSTATECHANGE(SESS); + return 0; // ALREADY CONNECTED +} + +int FindFreeStreamEx(int GetSem); + +int FindFreeStreamNoSem() +{ + return FindFreeStreamEx(0); +} + +DllExport int APIENTRY FindFreeStream() +{ + return FindFreeStreamEx(1); +} + +int FindFreeStreamEx(int GetSem) +{ + int stream, n; + BPQVECSTRUC * PORTVEC; + +// Returns number of first unused BPQHOST stream. If none available, +// returns 255. See API function 13. + + // if init has not yet been run, wait. + + while (InitDone == 0) + { + Debugprintf("Waiting for init to complete"); + Sleep(1000); + } + + if (InitDone == -1) // Init failed + exit(0); + + if (GetSem) + GetSemaphore(&Semaphore, 9); + + stream = 0; + n = 64; + + while (n--) + { + PORTVEC = &BPQHOSTVECTOR[stream++]; + if ((PORTVEC->HOSTFLAGS & 0x80) == 0) + { + PORTVEC->STREAMOWNER=GetCurrentProcessId(); + PORTVEC->HOSTFLAGS = 128; // SET ALLOCATED BIT, clear others + memcpy(&PORTVEC->PgmName[0], pgm, 31); + if (GetSem) + FreeSemaphore(&Semaphore); + return stream; + } + } + + if (GetSem) + FreeSemaphore(&Semaphore); + + return 255; +} + +DllExport int APIENTRY AllocateStream(int stream) +{ +// Allocate stream. If stream is already allocated, return nonzero. +// Otherwise allocate stream, and return zero. + + BPQVECSTRUC * PORTVEC = &BPQHOSTVECTOR[stream -1]; // API counts from 1 + + if ((PORTVEC->HOSTFLAGS & 0x80) == 0) + { + PORTVEC->STREAMOWNER=GetCurrentProcessId(); + PORTVEC->HOSTFLAGS = 128; // SET ALLOCATED BIT, clear others + memcpy(&PORTVEC->PgmName[0], pgm, 31); + FreeSemaphore(&Semaphore); + return 0; + } + + return 1; // Already allocated +} + + +DllExport int APIENTRY DeallocateStream(int stream) +{ + BPQVECSTRUC * PORTVEC; + UINT * monbuff; + BOOL GotSem = Semaphore.Flag; + +// Release stream. + + stream--; + + if (stream < 0 || stream > 63) + return (0); + + PORTVEC=&BPQHOSTVECTOR[stream]; + + PORTVEC->STREAMOWNER=0; + PORTVEC->PgmName[0] = 0; + PORTVEC->HOSTAPPLFLAGS=0; + PORTVEC->HOSTAPPLMASK=0; + PORTVEC->HOSTHANDLE=0; + + // Clear Trace Queue + + if (PORTVEC->HOSTSESSION) + SessionControl(stream + 1, 2, 0); + + if (GotSem == 0) + GetSemaphore(&Semaphore, 0); + + while (PORTVEC->HOSTTRACEQ) + { + monbuff = Q_REM((void *)&PORTVEC->HOSTTRACEQ); + ReleaseBuffer(monbuff); + } + + if (GotSem == 0) + FreeSemaphore(&Semaphore); + + PORTVEC->HOSTFLAGS &= 0x60; // Clear Allocated. Must leave any DISC Pending bits + + return(0); +} +DllExport int APIENTRY SessionState(int stream, int * state, int * change) +{ + // Get current Session State. Any state changed is ACK'ed + // automatically. See BPQHOST functions 4 and 5. + + BPQVECSTRUC * HOST = &BPQHOSTVECTOR[stream -1]; // API counts from 1 + + Check_Timer(); // In case Appl doesnt call it often ehough + + GetSemaphore(&Semaphore, 20); + + // CX = 0 if stream disconnected or CX = 1 if stream connected + // DX = 0 if no change of state since last read, or DX = 1 if + // the connected/disconnected state has changed since + // last read (ie. delta-stream status). + + // HOSTFLAGS = Bit 80 = Allocated + // Bit 40 = Disc Request + // Bit 20 = Stay Flag + // Bit 02 and 01 State Change Bits + + if ((HOST->HOSTFLAGS & 3) == 0) + // No Chaange + *change = 0; + else + *change = 1; + + if (HOST->HOSTSESSION) // LOCAL SESSION + // Connected + *state = 1; + else + *state = 0; + + HOST->HOSTFLAGS &= 0xFC; // Clear Change Bitd + + FreeSemaphore(&Semaphore); + return 0; +} + +DllExport int APIENTRY SessionStateNoAck(int stream, int * state) +{ + // Get current Session State. Dont ACK any change + // See BPQHOST function 4 + + BPQVECSTRUC * HOST = &BPQHOSTVECTOR[stream -1]; // API counts from 1 + + Check_Timer(); // In case Appl doesnt call it often ehough + + if (HOST->HOSTSESSION) // LOCAL SESSION + // Connected + *state = 1; + else + *state = 0; + + return 0; +} + + +int SendMsgEx(int stream, char * msg, int len, int GetSem); + +int SendMsgNoSem(int stream, char * msg, int len) +{ + return SendMsgEx(stream, msg, len, 0); +} + +DllExport int APIENTRY SendMsg(int stream, char * msg, int len) +{ + return SendMsgEx(stream, msg, len, 1); +} + + +int SendMsgEx(int stream, char * msg, int len, int GetSem) +{ + // Send message to stream (BPQHOST Function 2) + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + TRANSPORTENTRY * Partner; + PDATAMESSAGE MSG; + + Check_Timer(); + + if (len > 256) + return 0; // IGNORE + + if (stream == 0) + { + // Send UNPROTO - SEND FRAME TO ALL RADIO PORTS + + // COPY DATA TO A BUFFER IN OUR SEGMENTS - SIMPLFIES THINGS LATER + + if (QCOUNT < 50) + return 0; // Dont want to run out + + if (GetSem) + GetSemaphore(&Semaphore, 10); + + if ((MSG = GetBuff()) == 0) + { + if (GetSem) + FreeSemaphore(&Semaphore); + return 0; + } + + MSG->PID = 0xF0; // Normal Data PID + + memcpy(&MSG->L2DATA[0], msg, len); + MSG->LENGTH = (len + MSGHDDRLEN + 1); + + SENDUIMESSAGE(MSG); + ReleaseBuffer(MSG); + if (GetSem) + FreeSemaphore(&Semaphore); + return 0; + } + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + if (L4 == 0) + return 0; + + if (GetSem) + GetSemaphore(&Semaphore, 22); + + SESS->HOSTFLAGS |= 0x80; // SET ALLOCATED BIT + + if (QCOUNT < 40) // PLENTY FREE? + { + if (GetSem) + FreeSemaphore(&Semaphore); + return 1; + } + + // Dont allow massive queues to form + + if (QCOUNT < 100) + { + int n = CountFramesQueuedOnStream(stream + 1); + + if (n > 100) + { + Debugprintf("Stream %d QCOUNT %d Q Len %d - discarding", stream, QCOUNT, n); + if (GetSem) + FreeSemaphore(&Semaphore); + return 1; + } + } + + if ((MSG = GetBuff()) == 0) + { + if (GetSem) + FreeSemaphore(&Semaphore); + return 1; + } + + MSG->PID = 0xF0; // Normal Data PID + + memcpy(&MSG->L2DATA[0], msg, len); + MSG->LENGTH = len + MSGHDDRLEN + 1; + + // IF CONNECTED, PASS MESSAGE TO TARGET CIRCUIT - FLOW CONTROL AND + // DELAYED DISC ONLY WORK ON ONE SIDE + + Partner = L4->L4CROSSLINK; + + L4->L4KILLTIMER = 0; // RESET SESSION TIMEOUT + + if (Partner && Partner->L4STATE > 4) // Partner and link up + { + // Connected + + Partner->L4KILLTIMER = 0; // RESET SESSION TIMEOUT + C_Q_ADD(&Partner->L4TX_Q, MSG); + PostDataAvailable(Partner); + } + else + C_Q_ADD(&L4->L4RX_Q, MSG); + + if (GetSem) + FreeSemaphore(&Semaphore); + return 0; +} +DllExport int APIENTRY SendRaw(int port, char * msg, int len) +{ + struct PORTCONTROL * PORT; + MESSAGE * MSG; + + Check_Timer(); + + // Send Raw (KISS mode) frame to port (BPQHOST function 10) + + if (len > (MAXDATA - (MSGHDDRLEN + 8))) + return 0; + + if (QCOUNT < 50) + return 1; + + // GET A BUFFER + + PORT = GetPortTableEntryFromSlot(port); + + if (PORT == 0) + return 0; + + GetSemaphore(&Semaphore, 24); + + MSG = GetBuff(); + + if (MSG == 0) + { + FreeSemaphore(&Semaphore); + return 1; + } + + memcpy(MSG->DEST, msg, len); + + MSG->LENGTH = len + MSGHDDRLEN; + + if (PORT->PROTOCOL == 10 && PORT->TNC && PORT->TNC->Hardware != H_KISSHF) // PACTOR/WINMOR Style + { + // Pactor Style. Probably will only be used for Tracker unless we do APRS over V4 or WINMOR + + EXTPORTDATA * EXTPORT = (EXTPORTDATA *) PORT; + + C_Q_ADD(&EXTPORT->UI_Q, MSG); + + FreeSemaphore(&Semaphore); + return 0; + } + + MSG->PORT = PORT->PORTNUMBER; + + PUT_ON_PORT_Q(PORT, MSG); + + FreeSemaphore(&Semaphore); + return 0; +} + +DllExport time_t APIENTRY GetRaw(int stream, char * msg, int * len, int * count) +{ + time_t Stamp; + BPQVECSTRUC * SESS; + PMESSAGE MSG; + int Msglen; + + Check_Timer(); + + *len = 0; + *count = 0; + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + + GetSemaphore(&Semaphore, 26); + + if (SESS->HOSTTRACEQ == 0) + { + FreeSemaphore(&Semaphore); + return 0; + } + + MSG = Q_REM((void *)&SESS->HOSTTRACEQ); + + Msglen = MSG->LENGTH; + + if (Msglen < 0 || Msglen > 350) + { + FreeSemaphore(&Semaphore); + return 0; + } + + Stamp = MSG->Timestamp; + + memcpy(msg, MSG, BUFFLEN - sizeof(void *)); // To c + + *len = Msglen; + + ReleaseBuffer(MSG); + + *count = C_Q_COUNT(&SESS->HOSTTRACEQ); + FreeSemaphore(&Semaphore); + + return Stamp; +} + +DllExport int APIENTRY GetMsg(int stream, char * msg, int * len, int * count ) +{ +// Get message from stream. Returns length, and count of frames +// still waiting to be collected. (BPQHOST function 3) +// AH = 3 Receive frame into buffer at ES:DI, length of frame returned +// in CX. BX returns the number of outstanding frames still to +// be received (ie. after this one) or zero if no more frames +// (ie. this is last one). +// + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + PDATAMESSAGE MSG; + int Msglen; + + Check_Timer(); + + *len = 0; + *count = 0; + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + GetSemaphore(&Semaphore, 25); + + if (L4 == 0 || L4->L4TX_Q == 0) + { + FreeSemaphore(&Semaphore); + return 0; + } + + L4->L4KILLTIMER = 0; // RESET SESSION TIMEOUT + + if(L4->L4CROSSLINK) + L4->L4CROSSLINK->L4KILLTIMER = 0; + + MSG = Q_REM((void *)&L4->L4TX_Q); + + Msglen = MSG->LENGTH - (MSGHDDRLEN + 1); // Dont want PID + + if (Msglen < 0) + { + FreeSemaphore(&Semaphore); + return 0; + } + + if (Msglen > 256) + Msglen = 256; + + memcpy(msg, &MSG->L2DATA[0], Msglen); + + *len = Msglen; + + ReleaseBuffer(MSG); + + *count = C_Q_COUNT(&L4->L4TX_Q); + FreeSemaphore(&Semaphore); + + return 0; +} + + +DllExport int APIENTRY RXCount(int stream) +{ +// Returns count of packets waiting on stream +// (BPQHOST function 7 (part)). + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + + Check_Timer(); + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + if (L4 == 0) + return 0; // NOT CONNECTED + + return C_Q_COUNT(&L4->L4TX_Q); +} + +DllExport int APIENTRY TXCount(int stream) +{ +// Returns number of packets on TX queue for stream +// (BPQHOST function 7 (part)). + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + + Check_Timer(); + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + if (L4 == 0) + return 0; // NOT CONNECTED + + L4 = L4->L4CROSSLINK; + + if (L4 == 0) + return 0; // NOTHING ro Q on + + return (CountFramesQueuedOnSession(L4)); +} + +DllExport int APIENTRY MONCount(int stream) +{ +// Returns number of monitor frames available +// (BPQHOST function 7 (part)). + + BPQVECSTRUC * SESS; + + Check_Timer(); + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + + return C_Q_COUNT(&SESS->HOSTTRACEQ); +} + + +DllExport int APIENTRY GetCallsign(int stream, char * callsign) +{ + // Returns call connected on stream (BPQHOST function 8 (part)). + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + TRANSPORTENTRY * Partner; + UCHAR Call[11] = "SWITCH "; + UCHAR * AXCall = NULL; + Check_Timer(); + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + GetSemaphore(&Semaphore, 26); + + if (L4 == 0) + { + FreeSemaphore(&Semaphore); + return 0; + } + + Partner = L4->L4CROSSLINK; + + if (Partner) + { + // CONNECTED OUT - GET TARGET SESSION + + if (Partner->L4CIRCUITTYPE & BPQHOST) + { + AXCall = &Partner->L4USER[0]; + } + else if (Partner->L4CIRCUITTYPE & L2LINK) + { + struct _LINKTABLE * LINK = Partner->L4TARGET.LINK; + + if (LINK) + AXCall = LINK->LINKCALL; + + if (Partner->L4CIRCUITTYPE & UPLINK) + { + // IF UPLINK, SHOULD USE SESSION CALL, IN CASE *** LINKED HAS BEEN USED + + AXCall = &Partner->L4USER[0]; + } + } + else if (Partner->L4CIRCUITTYPE & PACTOR) + { + // PACTOR Type - Frames are queued on the Port Entry + + EXTPORTDATA * EXTPORT = Partner->L4TARGET.EXTPORT; + + if (EXTPORT) + AXCall = &EXTPORT->ATTACHEDSESSIONS[Partner->KAMSESSION]->L4USER[0]; + + } + else + { + // MUST BE NODE SESSION + + // ANOTHER NODE + + // IF THE HOST IS THE UPLINKING STATION, WE NEED THE TARGET CALL + + if (L4->L4CIRCUITTYPE & UPLINK) + { + struct DEST_LIST *DEST = Partner->L4TARGET.DEST; + + if (DEST) + AXCall = &DEST->DEST_CALL[0]; + } + else + AXCall = Partner->L4USER; + } + if (AXCall) + ConvFromAX25(AXCall, Call); + } + + memcpy(callsign, Call, 10); + + FreeSemaphore(&Semaphore); + return 0; +} + +DllExport int APIENTRY GetConnectionInfo(int stream, char * callsign, + int * port, int * sesstype, int * paclen, + int * maxframe, int * l4window) +{ + // Return the Secure Session Flag rather than not connected + + BPQVECSTRUC * SESS; + TRANSPORTENTRY * L4; + TRANSPORTENTRY * Partner; + UCHAR Call[11] = "SWITCH "; + UCHAR * AXCall; + Check_Timer(); + + stream--; // API uses 1 - 64 + + if (stream < 0 || stream > 63) + return 0; + + SESS = &BPQHOSTVECTOR[stream]; + L4 = SESS->HOSTSESSION; + + GetSemaphore(&Semaphore, 27); + + if (L4 == 0) + { + FreeSemaphore(&Semaphore); + return 0; + } + + Partner = L4->L4CROSSLINK; + + // Return the Secure Session Flag rather than not connected + + // AL = Radio port on which channel is connected (or zero) + // AH = SESSION TYPE BITS + // EBX = L2 paclen for the radio port + // ECX = L2 maxframe for the radio port + // EDX = L4 window size (if L4 circuit, or zero) or -1 if not connected + // ES:DI = CALLSIGN + + *port = 0; + *sesstype = 0; + *paclen = 0; + *maxframe = 0; + *l4window = 0; + if (L4->SESSPACLEN) + *paclen = L4->SESSPACLEN; + else + *paclen = 256; + + if (Partner) + { + // CONNECTED OUT - GET TARGET SESSION + + *l4window = Partner->L4WINDOW; + *sesstype = Partner->L4CIRCUITTYPE; + + if (Partner->L4CIRCUITTYPE & BPQHOST) + { + AXCall = &Partner->L4USER[0]; + } + else if (Partner->L4CIRCUITTYPE & L2LINK) + { + struct _LINKTABLE * LINK = Partner->L4TARGET.LINK; + + // EXTRACT PORT AND MAXFRAME + + *port = LINK->LINKPORT->PORTNUMBER; + *maxframe = LINK->LINKWINDOW; + *l4window = 0; + + AXCall = LINK->LINKCALL; + + if (Partner->L4CIRCUITTYPE & UPLINK) + { + // IF UPLINK, SHOULD USE SESSION CALL, IN CASE *** LINKED HAS BEEN USED + + AXCall = &Partner->L4USER[0]; + } + } + else if (Partner->L4CIRCUITTYPE & PACTOR) + { + // PACTOR Type - Frames are queued on the Port Entry + + EXTPORTDATA * EXTPORT = Partner->L4TARGET.EXTPORT; + + *port = EXTPORT->PORTCONTROL.PORTNUMBER; + AXCall = &EXTPORT->ATTACHEDSESSIONS[Partner->KAMSESSION]->L4USER[0]; + + } + else + { + // MUST BE NODE SESSION + + // ANOTHER NODE + + // IF THE HOST IS THE UPLINKING STATION, WE NEED THE TARGET CALL + + if (L4->L4CIRCUITTYPE & UPLINK) + { + struct DEST_LIST *DEST = Partner->L4TARGET.DEST; + + AXCall = &DEST->DEST_CALL[0]; + } + else + AXCall = Partner->L4USER; + } + ConvFromAX25(AXCall, Call); + } + + memcpy(callsign, Call, 10); + + FreeSemaphore(&Semaphore); + + if (Partner) + return Partner->Secure_Session; + + return 0; +} + + +DllExport int APIENTRY SetAppl(int stream, int flags, int mask) +{ +// Sets Application Flags and Mask for stream. (BPQHOST function 1) +// AH = 1 Set application mask to value in EDX (or even DX if 16 +// applications are ever to be supported). +// +// Set application flag(s) to value in CL (or CX). +// whether user gets connected/disconnected messages issued +// by the node etc. + + + BPQVECSTRUC * PORTVEC; + stream--; + + if (stream < 0 || stream > 63) + return (0); + + PORTVEC=&BPQHOSTVECTOR[stream]; + + PORTVEC->HOSTAPPLFLAGS = flags; + PORTVEC->HOSTAPPLMASK = mask; + + // If either is non-zero, set allocated and Process. This gets round problem with + // stations that don't call allocate stream + + if (flags || mask) + { + if ((PORTVEC->HOSTFLAGS & 128) == 0) // Not allocated + { + PORTVEC->STREAMOWNER=GetCurrentProcessId(); + memcpy(&PORTVEC->PgmName[0], pgm, 31); + PORTVEC->HOSTFLAGS = 128; // SET ALLOCATED BIT, clear others + } + } + + return (0); +} + +DllExport struct PORTCONTROL * APIENTRY GetPortTableEntry(int portslot) // Kept for Legacy apps +{ + struct PORTCONTROL * PORTVEC=PORTTABLE; + + if (portslot>NUMBEROFPORTS) + portslot=NUMBEROFPORTS; + + while (--portslot > 0) + PORTVEC=PORTVEC->PORTPOINTER; + + return PORTVEC; +} + +// Proc below renamed to avoid confusion with GetPortTableEntryFromPortNum + +DllExport struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot) +{ + struct PORTCONTROL * PORTVEC=PORTTABLE; + + if (portslot>NUMBEROFPORTS) + portslot=NUMBEROFPORTS; + + while (--portslot > 0) + PORTVEC=PORTVEC->PORTPOINTER; + + return PORTVEC; +} + +int CanPortDigi(int Port) +{ + struct PORTCONTROL * PORTVEC = GetPortTableEntryFromPortNum(Port); + struct TNCINFO * TNC; + + if (PORTVEC == NULL) + return FALSE; + + TNC = PORTVEC->TNC; + + if (TNC == NULL) + return TRUE; + + if (TNC->Hardware == H_SCS || TNC->Hardware == H_TRK || TNC->Hardware == H_TRKM || TNC->Hardware == H_WINRPR) + return FALSE; + + return TRUE; +} + +struct PORTCONTROL * APIENTRY GetPortTableEntryFromPortNum(int portnum) +{ + struct PORTCONTROL * PORTVEC = PORTTABLE; + + do + { + if (PORTVEC->PORTNUMBER == portnum) + return PORTVEC; + + PORTVEC=PORTVEC->PORTPOINTER; + } + while (PORTVEC); + + return NULL; +} + +DllExport UCHAR * APIENTRY GetPortDescription(int portslot, char * Desc) +{ + struct PORTCONTROL * PORTVEC=PORTTABLE; + + if (portslot>NUMBEROFPORTS) + portslot=NUMBEROFPORTS; + + while (--portslot > 0) + PORTVEC=PORTVEC->PORTPOINTER; + + memcpy(Desc, PORTVEC->PORTDESCRIPTION, 30); + Desc[30]=0; + + return 0; +} + +// Standard serial port handling routines, used by lots of modules. + +int OpenCOMMPort(struct TNCINFO * conn, char * Port, int Speed, BOOL Quiet) +{ + if (conn->WEB_COMMSSTATE == NULL) + conn->WEB_COMMSSTATE = zalloc(100); + + if (Port == NULL) + return (FALSE); + + conn->hDevice = OpenCOMPort(Port, Speed, TRUE, TRUE, Quiet, 0); + + if (conn->hDevice == 0) + { + sprintf(conn->WEB_COMMSSTATE,"%s Open failed - Error %d", Port, GetLastError()); + if (conn->xIDC_COMMSSTATE) + SetWindowText(conn->xIDC_COMMSSTATE, conn->WEB_COMMSSTATE); + + return (FALSE); + } + + sprintf(conn->WEB_COMMSSTATE,"%s Open", Port); + + if (conn->xIDC_COMMSSTATE) + SetWindowText(conn->xIDC_COMMSSTATE, conn->WEB_COMMSSTATE); + + return TRUE; +} + + + +#ifdef WIN32 + +HANDLE OpenCOMPort(char * pPort, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits) +{ + char szPort[256]; + BOOL fRetVal ; + COMMTIMEOUTS CommTimeOuts ; + int Err; + char buf[100]; + HANDLE fd; + DCB dcb; + + // if Port Name starts COM, convert to \\.\COM or ports above 10 wont work + + if (_memicmp(pPort, "COM", 3) == 0) + { + char * pp = (char *)pPort; + int p = atoi(&pp[3]); + sprintf( szPort, "\\\\.\\COM%d", p); + } + else + strcpy(szPort, pPort); + + // open COMM device + + fd = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, + 0, // exclusive access + NULL, // no security attrs + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if (fd == (HANDLE) -1) + { + if (Quiet == 0) + { + Debugprintf("%s could not be opened %d", pPort, GetLastError()); + } + return (FALSE); + } + + Err = GetFileType(fd); + + // setup device buffers + + SetupComm(fd, 4096, 4096 ) ; + + // purge any information in the buffer + + PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT | + PURGE_TXCLEAR | PURGE_RXCLEAR ) ; + + // set up for overlapped I/O + + CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ; + CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ; + CommTimeOuts.ReadTotalTimeoutConstant = 0 ; + CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ; +// CommTimeOuts.WriteTotalTimeoutConstant = 0 ; + CommTimeOuts.WriteTotalTimeoutConstant = 500 ; + SetCommTimeouts(fd, &CommTimeOuts ) ; + + dcb.DCBlength = sizeof( DCB ) ; + + GetCommState(fd, &dcb ) ; + + dcb.BaudRate = speed; + dcb.ByteSize = 8; + dcb.Parity = 0; + dcb.StopBits = TWOSTOPBITS; + dcb.StopBits = Stopbits; + + // setup hardware flow control + + dcb.fOutxDsrFlow = 0; + dcb.fDtrControl = DTR_CONTROL_DISABLE ; + + dcb.fOutxCtsFlow = 0; + dcb.fRtsControl = RTS_CONTROL_DISABLE ; + + // setup software flow control + + dcb.fInX = dcb.fOutX = 0; + dcb.XonChar = 0; + dcb.XoffChar = 0; + dcb.XonLim = 100 ; + dcb.XoffLim = 100 ; + + // other various settings + + dcb.fBinary = TRUE ; + dcb.fParity = FALSE; + + fRetVal = SetCommState(fd, &dcb); + + if (fRetVal) + { + if (SetDTR) + EscapeCommFunction(fd, SETDTR); + else + EscapeCommFunction(fd, CLRDTR); + + if (SetRTS) + EscapeCommFunction(fd, SETRTS); + else + EscapeCommFunction(fd, CLRRTS); + } + else + { + sprintf(buf,"%s Setup Failed %d ", pPort, GetLastError()); + + WritetoConsoleLocal(buf); + OutputDebugString(buf); + CloseHandle(fd); + return 0; + } + + return fd; + +} + +int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error); + +int ReadCOMBlock(HANDLE fd, char * Block, int MaxLength) +{ + BOOL Error; + return ReadCOMBlockEx(fd, Block, MaxLength, &Error); +} + +// version to pass read error back to caller + +int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error) +{ + BOOL fReadStat ; + COMSTAT ComStat ; + DWORD dwErrorFlags; + DWORD dwLength; + BOOL ret; + + if (fd == NULL) + return 0; + + // only try to read number of bytes in queue + + ret = ClearCommError(fd, &dwErrorFlags, &ComStat); + + if (ret == 0) + { + int Err = GetLastError(); + *Error = TRUE; + return 0; + } + + + dwLength = min((DWORD) MaxLength, ComStat.cbInQue); + + if (dwLength > 0) + { + fReadStat = ReadFile(fd, Block, dwLength, &dwLength, NULL) ; + + if (!fReadStat) + { + dwLength = 0 ; + ClearCommError(fd, &dwErrorFlags, &ComStat ) ; + } + } + + *Error = FALSE; + + return dwLength; +} + + +BOOL WriteCOMBlock(HANDLE fd, char * Block, int BytesToWrite) +{ + BOOL fWriteStat; + DWORD BytesWritten; + DWORD ErrorFlags; + COMSTAT ComStat; + DWORD Mask = 0; + int Err; + + Err = GetCommModemStatus(fd, &Mask); + +// if ((Mask & MS_CTS_ON) == 0) // trap com0com other end not open +// return TRUE; + + fWriteStat = WriteFile(fd, Block, BytesToWrite, + &BytesWritten, NULL ); + + if ((!fWriteStat) || (BytesToWrite != BytesWritten)) + { + int Err = GetLastError(); + ClearCommError(fd, &ErrorFlags, &ComStat); + return FALSE; + } + return TRUE; +} + +VOID CloseCOMPort(HANDLE fd) +{ + if (fd == NULL) + return; + + SetCommMask(fd, 0); + + // drop DTR + + COMClearDTR(fd); + + // purge any outstanding reads/writes and close device handle + + PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ; + + CloseHandle(fd); + fd = NULL; +} + + +VOID COMSetDTR(HANDLE fd) +{ + EscapeCommFunction(fd, SETDTR); +} + +VOID COMClearDTR(HANDLE fd) +{ + EscapeCommFunction(fd, CLRDTR); +} + +VOID COMSetRTS(HANDLE fd) +{ + EscapeCommFunction(fd, SETRTS); +} + +VOID COMClearRTS(HANDLE fd) +{ + EscapeCommFunction(fd, CLRRTS); +} + + +#else + +static struct speed_struct +{ + int user_speed; + speed_t termios_speed; +} speed_table[] = { + {300, B300}, + {600, B600}, + {1200, B1200}, + {2400, B2400}, + {4800, B4800}, + {9600, B9600}, + {19200, B19200}, + {38400, B38400}, + {57600, B57600}, + {115200, B115200}, + {-1, B0} +}; + + +HANDLE OpenCOMPort(VOID * pPort, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits) +{ + char Port[256]; + char buf[512]; + + // Linux Version. + + int fd; + int hwflag = 0; + u_long param=1; + struct termios term; + struct speed_struct *s; + + if ((uintptr_t)pPort < 256) + sprintf(Port, "%s/com%d", BPQDirectory, (int)(uintptr_t)pPort); + else + strcpy(Port, pPort); + + if ((fd = open(Port, O_RDWR | O_NDELAY)) == -1) + { + if (Quiet == 0) + { + perror("Com Open Failed"); + sprintf(buf," %s could not be opened \n", Port); + WritetoConsoleLocal(buf); + Debugprintf(buf); + } + return 0; + } + + // Validate Speed Param + + for (s = speed_table; s->user_speed != -1; s++) + if (s->user_speed == speed) + break; + + if (s->user_speed == -1) + { + fprintf(stderr, "tty_speed: invalid speed %d\n", speed); + return FALSE; + } + + if (tcgetattr(fd, &term) == -1) + { + perror("tty_speed: tcgetattr"); + return FALSE; + } + + cfmakeraw(&term); + cfsetispeed(&term, s->termios_speed); + cfsetospeed(&term, s->termios_speed); + + if (tcsetattr(fd, TCSANOW, &term) == -1) + { + perror("tty_speed: tcsetattr"); + return FALSE; + } + + ioctl(fd, FIONBIO, ¶m); + + Debugprintf("LinBPQ Port %s fd %d", Port, fd); + + if (SetDTR) + { + COMSetDTR(fd); + } + else + { + COMClearDTR(fd); + } + + if (SetRTS) + { + COMSetRTS(fd); + } + else + { + COMClearRTS(fd); + } + return fd; +} + +int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error); + +int ReadCOMBlock(HANDLE fd, char * Block, int MaxLength) +{ + BOOL Error; + return ReadCOMBlockEx(fd, Block, MaxLength, &Error); +} + +// version to pass read error back to caller + +int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error) +{ + int Length; + + if (fd == 0) + { + *Error = 1; + return 0; + } + + errno = 22222; // to catch zero read (?? file closed ??) + + Length = read(fd, Block, MaxLength); + + *Error = 0; + + if (Length == 0 && errno == 22222) // seems to be result of unpluging USB + { +// printf("KISS read returned zero len and no errno\n"); + *Error = 1; + return 0; + } + + if (Length < 0) + { + if (errno != 11 && errno != 35) // Would Block + { + perror("read"); + printf("Handle %d Errno %d Len %d\n", fd, errno, Length); + *Error = errno; + } + return 0; + } + + return Length; +} + +BOOL WriteCOMBlock(HANDLE fd, char * Block, int BytesToWrite) +{ + // Some systems seem to have a very small max write size + + int ToSend = BytesToWrite; + int Sent = 0, ret; + int loops = 100; + + while (ToSend && loops-- > 0) + { + ret = write(fd, &Block[Sent], ToSend); + + if (ret >= ToSend) + return TRUE; + + if (ret == -1) + { + if (errno != 11 && errno != 35) // Would Block + return FALSE; + + usleep(10000); + ret = 0; + } + + Sent += ret; + ToSend -= ret; + } + +// if (ToSend) +// { +// // Send timed out. Close and reopen device +// +// } + return TRUE; +} + +VOID CloseCOMPort(HANDLE fd) +{ + if (fd == 0) + return; + + close(fd); + fd = 0; +} + +VOID COMSetDTR(HANDLE fd) +{ + int status; + + ioctl(fd, TIOCMGET, &status); + status |= TIOCM_DTR; + ioctl(fd, TIOCMSET, &status); +} + +VOID COMClearDTR(HANDLE fd) +{ + int status; + + ioctl(fd, TIOCMGET, &status); + status &= ~TIOCM_DTR; + ioctl(fd, TIOCMSET, &status); +} + +VOID COMSetRTS(HANDLE fd) +{ + int status; + + ioctl(fd, TIOCMGET, &status); + status |= TIOCM_RTS; + ioctl(fd, TIOCMSET, &status); +} + +VOID COMClearRTS(HANDLE fd) +{ + int status; + + ioctl(fd, TIOCMGET, &status); + status &= ~TIOCM_RTS; + ioctl(fd, TIOCMSET, &status); +} + +#endif + + +int MaxNodes; +int MaxRoutes; +int NodeLen; +int RouteLen; +struct DEST_LIST * Dests; +struct ROUTE * Routes; + +FILE *file; + +int DoRoutes() +{ + char digis[30] = ""; + int count, len; + char Normcall[10], Portcall[10]; + char line[80]; + + for (count=0; countNEIGHBOUR_CALL[0] != 0) + { + len=ConvFromAX25(Routes->NEIGHBOUR_CALL,Normcall); + Normcall[len]=0; + + if (Routes->NEIGHBOUR_DIGI1[0] != 0) + { + memcpy(digis," VIA ",5); + + len=ConvFromAX25(Routes->NEIGHBOUR_DIGI1,Portcall); + Portcall[len]=0; + strcpy(&digis[5],Portcall); + + if (Routes->NEIGHBOUR_DIGI2[0] != 0) + { + len=ConvFromAX25(Routes->NEIGHBOUR_DIGI2,Portcall); + Portcall[len]=0; + strcat(digis," "); + strcat(digis,Portcall); + } + } + else + digis[0] = 0; + + len=sprintf(line, + "ROUTE ADD %s %d %d %s %d %d %d %d %d\n", + Normcall, + Routes->NEIGHBOUR_PORT, + Routes->NEIGHBOUR_QUAL, digis, + Routes->NBOUR_MAXFRAME, + Routes->NBOUR_FRACK, + Routes->NBOUR_PACLEN, + Routes->INP3Node | (Routes->NoKeepAlive << 2), + Routes->OtherendsRouteQual); + + fputs(line, file); + } + + Routes+=1; + } + + return (0); +} + +int DoNodes() +{ + int count, len, cursor, i; + char Normcall[10], Portcall[10]; + char line[80]; + char Alias[7]; + + Dests-=1; + + for (count=0; countNRROUTE[0].ROUT_NEIGHBOUR == 0) + continue; + + { + len=ConvFromAX25(Dests->DEST_CALL,Normcall); + Normcall[len]=0; + + memcpy(Alias,Dests->DEST_ALIAS,6); + + Alias[6]=0; + + for (i=0;i<6;i++) + { + if (Alias[i] == ' ') + Alias[i] = 0; + } + + cursor=sprintf(line,"NODE ADD %s:%s ", Alias,Normcall); + + if (Dests->NRROUTE[0].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[0].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25( + Dests->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len]=0; + + len=sprintf(&line[cursor],"%s %d %d ", + Portcall, + Dests->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, + Dests->NRROUTE[0].ROUT_QUALITY); + + cursor+=len; + + if (Dests->NRROUTE[0].ROUT_OBSCOUNT > 127) + { + len=sprintf(&line[cursor],"! "); + cursor+=len; + } + } + + if (Dests->NRROUTE[1].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[1].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25( + Dests->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len]=0; + + len=sprintf(&line[cursor],"%s %d %d ", + Portcall, + Dests->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, + Dests->NRROUTE[1].ROUT_QUALITY); + + cursor+=len; + + if (Dests->NRROUTE[1].ROUT_OBSCOUNT > 127) + { + len=sprintf(&line[cursor],"! "); + cursor+=len; + } + } + + if (Dests->NRROUTE[2].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[2].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25( + Dests->NRROUTE[2].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len]=0; + + len=sprintf(&line[cursor],"%s %d %d ", + Portcall, + Dests->NRROUTE[2].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, + Dests->NRROUTE[2].ROUT_QUALITY); + + cursor+=len; + + if (Dests->NRROUTE[2].ROUT_OBSCOUNT > 127) + { + len=sprintf(&line[cursor],"! "); + cursor+=len; + } + } + + if (cursor > 30) + { + line[cursor++]='\n'; + line[cursor++]=0; + fputs(line, file); + } + } + } + return (0); +} + +void SaveMH() +{ + char FN[250]; + struct PORTCONTROL * PORT = PORTTABLE; + FILE *file; + + if (BPQDirectory[0] == 0) + { + strcpy(FN, "MHSave.txt"); + } + else + { + strcpy(FN,BPQDirectory); + strcat(FN,"/"); + strcat(FN,"MHSave.txt"); + } + + if ((file = fopen(FN, "w")) == NULL) + return; + + while (PORT) + { + int Port = 0; + char * ptr; + + MHSTRUC * MH = PORT->PORTMHEARD; + + int count = MHENTRIES; + int n; + char Normcall[20]; + char From[10]; + char DigiList[100]; + char * Output; + int len; + char Digi = 0; + + + // Note that the MHDIGIS field may contain rubbish. You have to check End of Address bit to find + // how many digis there are + + if (MH == NULL) + continue; + + fprintf(file, "Port:%d\n", PORT->PORTNUMBER); + + while (count--) + { + if (MH->MHCALL[0] == 0) + break; + + Digi = 0; + + len = ConvFromAX25(MH->MHCALL, Normcall); + Normcall[len] = 0; + + n = 8; // Max number of digi-peaters + + ptr = &MH->MHCALL[6]; // End of Address bit + + Output = &DigiList[0]; + + if ((*ptr & 1) == 0) + { + // at least one digi + + strcpy(Output, "via "); + Output += 4; + + while ((*ptr & 1) == 0) + { + // MORE TO COME + + From[ConvFromAX25(ptr + 1, From)] = 0; + Output += sprintf((char *)Output, "%s", From); + + ptr += 7; + n--; + + if (n == 0) + break; + + // See if digi actioned - put a * on last actioned + + if (*ptr & 0x80) + { + if (*ptr & 1) // if last address, must need * + { + *(Output++) = '*'; + Digi = '*'; + } + + else + if ((ptr[7] & 0x80) == 0) // Repeased by next? + { + *(Output++) = '*'; // No, so need * + Digi = '*'; + } + + + } + *(Output++) = ','; + } + *(--Output) = 0; // remove last comma + } + else + *(Output) = 0; + + // if we used a digi set * on call and display via string + + + if (Digi) + Normcall[len++] = Digi; + else + DigiList[0] = 0; // Dont show list if not used + + Normcall[len++] = 0; + + ptr = FormatMH(MH, 'U'); + + ptr[15] = 0; + + if (MH->MHDIGI) + fprintf(file, "%d %6d %-10s%c %s %s|%s|%s\n", (int)MH->MHTIME, MH->MHCOUNT, Normcall, MH->MHDIGI, ptr, DigiList, MH->MHLocator, MH->MHFreq); + else + fprintf(file, "%d %6d %-10s%c %s %s|%s|%s\n", (int)MH->MHTIME, MH->MHCOUNT, Normcall, ' ', ptr, DigiList, MH->MHLocator, MH->MHFreq); + + MH++; + } + PORT = PORT->PORTPOINTER; + } + + fclose(file); + + return; +} + + +int APIENTRY SaveNodes () +{ + char FN[250]; + + Routes = NEIGHBOURS; + RouteLen = ROUTE_LEN; + MaxRoutes = MAXNEIGHBOURS; + + Dests = DESTS; + NodeLen = DEST_LIST_LEN; + MaxNodes = MAXDESTS; + + // Set up pointer to BPQNODES file + + if (BPQDirectory[0] == 0) + { + strcpy(FN,"BPQNODES.dat"); + } + else + { + strcpy(FN,BPQDirectory); + strcat(FN,"/"); + strcat(FN,"BPQNODES.dat"); + } + + if ((file = fopen(FN, "w")) == NULL) + return FALSE; + + DoRoutes(); + DoNodes(); + + fclose(file); + + return (0); +} + +DllExport int APIENTRY ClearNodes () +{ + char FN[250]; + + // Set up pointer to BPQNODES file + + if (BPQDirectory[0] == 0) + { + strcpy(FN,"BPQNODES.dat"); + } + else + { + strcpy(FN,BPQDirectory); + strcat(FN,"/"); + strcat(FN,"BPQNODES.dat"); + } + + if ((file = fopen(FN, "w")) == NULL) + return FALSE; + + fclose(file); + + return (0); +} + + +static char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + +char * FormatMH(PMHSTRUC MH, char Format) +{ + struct tm * TM; + static char MHTime[50]; + time_t szClock; + char LOC[7]; + + memcpy(LOC, MH->MHLocator, 6); + LOC[6] = 0; + + if (Format == 'U' || Format =='L') + szClock = MH->MHTIME; + else + szClock = time(NULL) - MH->MHTIME; + + if (Format == 'L') + TM = localtime(&szClock); + else + TM = gmtime(&szClock); + + if (Format == 'U' || Format =='L') + sprintf(MHTime, "%s %02d %.2d:%.2d:%.2d %s %s", + month[TM->tm_mon], TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec, MH->MHFreq, LOC); + else + sprintf(MHTime, "%.2d:%.2d:%.2d:%.2d %s %s", + TM->tm_yday, TM->tm_hour, TM->tm_min, TM->tm_sec, MH->MHFreq, LOC); + + return MHTime; + +} + + +Dll VOID APIENTRY CreateOneTimePassword(char * Password, char * KeyPhrase, int TimeOffset) +{ + // Create a time dependent One Time Password from the KeyPhrase + // TimeOffset is used when checking to allow for slight variation in clocks + + time_t NOW = time(NULL); + UCHAR Hash[16]; + char Key[1000]; + int i, chr; + + NOW = NOW/30 + TimeOffset; // Only Change every 30 secs + + sprintf(Key, "%s%x", KeyPhrase, (int)NOW); + + md5(Key, Hash); + + for (i=0; i<16; i++) + { + chr = (Hash[i] & 31); + if (chr > 9) chr += 7; + + Password[i] = chr + 48; + } + + Password[16] = 0; + return; +} + +Dll BOOL APIENTRY CheckOneTimePassword(char * Password, char * KeyPhrase) +{ + char CheckPassword[17]; + int Offsets[10] = {0, -1, 1, -2, 2, -3, 3, -4, 4}; + int i, Pass; + + if (strlen(Password) < 16) + Pass = atoi(Password); + + for (i = 0; i < 9; i++) + { + CreateOneTimePassword(CheckPassword, KeyPhrase, Offsets[i]); + + if (strlen(Password) < 16) + { + // Using a numeric extract + + long long Val; + + memcpy(&Val, CheckPassword, 8); + Val = Val %= 1000000; + + if (Pass == Val) + return TRUE; + } + else + if (memcmp(Password, CheckPassword, 16) == 0) + return TRUE; + } + + return FALSE; +} + + +DllExport BOOL ConvToAX25Ex(unsigned char * callsign, unsigned char * ax25call) +{ + // Allows SSID's of 'T and 'R' + + int i; + + memset(ax25call,0x40,6); // in case short + ax25call[6]=0x60; // default SSID + + for (i=0;i<7;i++) + { + if (callsign[i] == '-') + { + // + // process ssid and return + // + + if (callsign[i+1] == 'T') + { + ax25call[6]=0x42; + return TRUE; + } + + if (callsign[i+1] == 'R') + { + ax25call[6]=0x44; + return TRUE; + } + i = atoi(&callsign[i+1]); + + if (i < 16) + { + ax25call[6] |= i<<1; + return (TRUE); + } + return (FALSE); + } + + if (callsign[i] == 0 || callsign[i] == 13 || callsign[i] == ' ' || callsign[i] == ',') + { + // + // End of call - no ssid + // + return (TRUE); + } + + ax25call[i] = callsign[i] << 1; + } + + // + // Too many chars + // + + return (FALSE); +} + + +DllExport BOOL ConvToAX25(unsigned char * callsign, unsigned char * ax25call) +{ + int i; + + memset(ax25call,0x40,6); // in case short + ax25call[6]=0x60; // default SSID + + for (i=0;i<7;i++) + { + if (callsign[i] == '-') + { + // + // process ssid and return + // + i = atoi(&callsign[i+1]); + + if (i < 16) + { + ax25call[6] |= i<<1; + return (TRUE); + } + return (FALSE); + } + + if (callsign[i] == 0 || callsign[i] == 13 || callsign[i] == ' ' || callsign[i] == ',') + { + // + // End of call - no ssid + // + return (TRUE); + } + + ax25call[i] = callsign[i] << 1; + } + + // + // Too many chars + // + + return (FALSE); +} + + +DllExport int ConvFromAX25(unsigned char * incall,unsigned char * outcall) +{ + int in,out=0; + unsigned char chr; + + memset(outcall,0x20,10); + + for (in=0;in<6;in++) + { + chr=incall[in]; + if (chr == 0x40) + break; + chr >>= 1; + outcall[out++]=chr; + } + + chr=incall[6]; // ssid + + if (chr == 0x42) + { + outcall[out++]='-'; + outcall[out++]='T'; + return out; + } + + if (chr == 0x44) + { + outcall[out++]='-'; + outcall[out++]='R'; + return out; + } + + chr >>= 1; + chr &= 15; + + if (chr > 0) + { + outcall[out++]='-'; + if (chr > 9) + { + chr-=10; + outcall[out++]='1'; + } + chr+=48; + outcall[out++]=chr; + } + return (out); +} + +unsigned short int compute_crc(unsigned char *buf, int txlen); + +SOCKADDR_IN reportdest = {0}; + +SOCKET ReportSocket = 0; + +SOCKADDR_IN Chatreportdest = {0}; + +extern char LOCATOR[]; // Locator for Reporting - may be Maidenhead or LAT:LON +extern char MAPCOMMENT[]; // Locator for Reporting - may be Maidenhead or LAT:LON +extern char LOC[7]; // Maidenhead Locator for Reporting +extern char ReportDest[7]; + + +VOID SendReportMsg(char * buff, int txlen) +{ + unsigned short int crc = compute_crc(buff, txlen); + + crc ^= 0xffff; + + buff[txlen++] = (crc&0xff); + buff[txlen++] = (crc>>8); + + sendto(ReportSocket, buff, txlen, 0, (struct sockaddr *)&reportdest, sizeof(reportdest)); + +} +VOID SendLocation() +{ + MESSAGE AXMSG = {0}; + PMESSAGE AXPTR = &AXMSG; + char Msg[512]; + int Len; + + Len = sprintf(Msg, "%s %s
%s", LOCATOR, VersionString, MAPCOMMENT); + +#ifdef LINBPQ + Len = sprintf(Msg, "%s L%s
%s", LOCATOR, VersionString, MAPCOMMENT); +#endif +#ifdef MACBPQ + Len = sprintf(Msg, "%s M%s
%s", LOCATOR, VersionString, MAPCOMMENT); +#endif +#ifdef FREEBSD + Len = sprintf(Msg, "%s F%s
%s", LOCATOR, VersionString, MAPCOMMENT); +#endif + + if (Len > 256) + Len = 256; + + // Block includes the Msg Header (7 bytes), Len Does not! + + memcpy(AXPTR->DEST, ReportDest, 7); + memcpy(AXPTR->ORIGIN, MYCALL, 7); + AXPTR->DEST[6] &= 0x7e; // Clear End of Call + AXPTR->DEST[6] |= 0x80; // set Command Bit + + AXPTR->ORIGIN[6] |= 1; // Set End of Call + AXPTR->CTL = 3; //UI + AXPTR->PID = 0xf0; + memcpy(AXPTR->L2DATA, Msg, Len); + + SendReportMsg((char *)&AXMSG.DEST, Len + 16); + + if (M0LTEMap) + SendDataToPktMap(); + + return; + +} + + + +VOID SendMH(struct TNCINFO * TNC, char * call, char * freq, char * LOC, char * Mode) +{ + MESSAGE AXMSG; + PMESSAGE AXPTR = &AXMSG; + char Msg[100]; + int Len; + + if (ReportSocket == 0 || LOCATOR[0] == 0) + return; + + Len = sprintf(Msg, "MH %s,%s,%s,%s", call, freq, LOC, Mode); + + // Block includes the Msg Header (7 bytes), Len Does not! + + memcpy(AXPTR->DEST, ReportDest, 7); + + if (TNC && TNC->PortRecord->PORTCONTROL.PORTCALL[0]) + memcpy(AXPTR->ORIGIN, TNC->PortRecord->PORTCONTROL.PORTCALL, 7); + else + memcpy(AXPTR->ORIGIN, MYCALL, 7); + AXPTR->DEST[6] &= 0x7e; // Clear End of Call + AXPTR->DEST[6] |= 0x80; // set Command Bit + + AXPTR->ORIGIN[6] |= 1; // Set End of Call + AXPTR->CTL = 3; //UI + AXPTR->PID = 0xf0; + memcpy(AXPTR->L2DATA, Msg, Len); + + SendReportMsg((char *)&AXMSG.DEST, Len + 16) ; + + return; + +} + +time_t TimeLastNRRouteSent = 0; + +char NRRouteMessage[256]; +int NRRouteLen = 0; + + +VOID SendNETROMRoute(struct PORTCONTROL * PORT, unsigned char * axcall) +{ + // Called to update Link Map when a NODES Broadcast is received + // Batch to reduce Load + + MESSAGE AXMSG; + PMESSAGE AXPTR = &AXMSG; + char Msg[300]; + int Len; + char Call[10]; + char Report[16]; + time_t Now = time(NULL); + int NeedSend = FALSE; + + + if (ReportSocket == 0 || LOCATOR[0] == 0) + return; + + Call[ConvFromAX25(axcall, Call)] = 0; + + sprintf(Report, "%s,%d,", Call, PORT->PORTTYPE); + + if (Now - TimeLastNRRouteSent > 60) + NeedSend = TRUE; + + if (strstr(NRRouteMessage, Report) == 0) // reported recently + strcat(NRRouteMessage, Report); + + if (strlen(NRRouteMessage) > 230 || NeedSend) + { + Len = sprintf(Msg, "LINK %s", NRRouteMessage); + + // Block includes the Msg Header (7 bytes), Len Does not! + + memcpy(AXPTR->DEST, ReportDest, 7); + memcpy(AXPTR->ORIGIN, MYCALL, 7); + AXPTR->DEST[6] &= 0x7e; // Clear End of Call + AXPTR->DEST[6] |= 0x80; // set Command Bit + + AXPTR->ORIGIN[6] |= 1; // Set End of Call + AXPTR->CTL = 3; //UI + AXPTR->PID = 0xf0; + memcpy(AXPTR->L2DATA, Msg, Len); + + SendReportMsg((char *)&AXMSG.DEST, Len + 16) ; + + TimeLastNRRouteSent = Now; + NRRouteMessage[0] = 0; + } + + return; + +} + +DllExport char * APIENTRY GetApplCall(int Appl) +{ + if (Appl < 1 || Appl > NumberofAppls ) return NULL; + + return (UCHAR *)(&APPLCALLTABLE[Appl-1].APPLCALL_TEXT); +} +DllExport char * APIENTRY GetApplAlias(int Appl) +{ + if (Appl < 1 || Appl > NumberofAppls ) return NULL; + + return (UCHAR *)(&APPLCALLTABLE[Appl-1].APPLALIAS_TEXT); +} + +DllExport int32_t APIENTRY GetApplQual(int Appl) +{ + if (Appl < 1 || Appl > NumberofAppls ) return 0; + + return (APPLCALLTABLE[Appl-1].APPLQUAL); +} + +char * GetApplCallFromName(char * App) +{ + int i; + char PaddedAppl[13] = " "; + + memcpy(PaddedAppl, App, (int)strlen(App)); + + for (i = 0; i < NumberofAppls; i++) + { + if (memcmp(&APPLCALLTABLE[i].APPLCMD, PaddedAppl, 12) == 0) + return &APPLCALLTABLE[i].APPLCALL_TEXT[0]; + } + return NULL; +} + + +DllExport char * APIENTRY GetApplName(int Appl) +{ + if (Appl < 1 || Appl > NumberofAppls ) return NULL; + + return (UCHAR *)(&APPLCALLTABLE[Appl-1].APPLCMD); +} + +DllExport int APIENTRY GetNumberofPorts() +{ + return (NUMBEROFPORTS); +} + +DllExport int APIENTRY GetPortNumber(int portslot) +{ + struct PORTCONTROL * PORTVEC=PORTTABLE; + + if (portslot>NUMBEROFPORTS) + portslot=NUMBEROFPORTS; + + while (--portslot > 0) + PORTVEC=PORTVEC->PORTPOINTER; + + return PORTVEC->PORTNUMBER; + +} + +DllExport char * APIENTRY GetVersionString() +{ +// return ((char *)&VersionStringWithBuild); + return ((char *)&VersionString); +} + +#ifdef MACBPQ + +//Fiddle till I find a better solution + +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1060 +int __sync_lock_test_and_set(int * ptr, int val) +{ + *ptr = val; + return 0; +} +#endif // __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif // MACBPQ + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line) +{ + // + // Wait for it to be free + // + + if (Semaphore->Flag != 0) + { + Semaphore->Clashes++; + } + +loop1: + + while (Semaphore->Flag != 0) + { + Sleep(10); + } + + // + // try to get semaphore + // + +#ifdef WIN32 + + { + if (InterlockedExchange(&Semaphore->Flag, 1) != 0) // Failed to get it + goto loop1; // try again;; + } + +#else + + if (__sync_lock_test_and_set(&Semaphore->Flag, 1) != 0) + + // Failed to get it + goto loop1; // try again; + +#endif + + //Ok. got it + + Semaphore->Gets++; + Semaphore->SemProcessID = GetCurrentProcessId(); + Semaphore->SemThreadID = GetCurrentThreadId(); + SemHeldByAPI = ID; + Semaphore->Line = Line; + strcpy(Semaphore->File, File); + + return; +} + +void FreeSemaphore(struct SEM * Semaphore) +{ + if (Semaphore->Flag == 0) + Debugprintf("Free Semaphore Called when Sem not held"); + + Semaphore->Rels++; + Semaphore->Flag = 0; + + return; +} + +#ifdef WIN32 + +#include "DbgHelp.h" +/* +USHORT WINAPI RtlCaptureStackBackTrace( + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __out PVOID *BackTrace, + __out_opt PULONG BackTraceHash +); +*/ +#endif + +void printStack(void) +{ +#ifdef WIN32 +#ifdef _DEBUG // So we can use on 98/2K + + unsigned int i; + void * stack[ 100 ]; + unsigned short frames; + SYMBOL_INFO * symbol; + HANDLE process; + + Debugprintf("Stack Backtrace"); + + process = GetCurrentProcess(); + + SymInitialize( process, NULL, TRUE ); + + frames = RtlCaptureStackBackTrace( 0, 60, stack, NULL ); + symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 ); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof( SYMBOL_INFO ); + + for( i = 0; i < frames; i++ ) + { + SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol ); + + Debugprintf( "%i: %s - %p", frames - i - 1, symbol->Name, symbol->Address ); + } + + free(symbol); + +#endif +#endif +} + +pthread_t ResolveUpdateThreadId = 0; + +char NodeMapServer[80] = "update.g8bpq.net"; +char ChatMapServer[80] = "chatupdate.g8bpq.net"; + +VOID ResolveUpdateThread(void * Unused) +{ + struct hostent * HostEnt1; + struct hostent * HostEnt2; + + ResolveUpdateThreadId = GetCurrentThreadId(); + + while (TRUE) + { + if (pthread_equal(ResolveUpdateThreadId, GetCurrentThreadId()) == FALSE) + { + Debugprintf("Resolve Update thread %x redundant - closing", GetCurrentThreadId()); + return; + } + + // Resolve name to address + + Debugprintf("Resolving %s", NodeMapServer); + HostEnt1 = gethostbyname (NodeMapServer); +// HostEnt1 = gethostbyname ("192.168.1.64"); + + if (HostEnt1) + memcpy(&reportdest.sin_addr.s_addr,HostEnt1->h_addr,4); + + Debugprintf("Resolving %s", ChatMapServer); + HostEnt2 = gethostbyname (ChatMapServer); +// HostEnt2 = gethostbyname ("192.168.1.64"); + + if (HostEnt2) + memcpy(&Chatreportdest.sin_addr.s_addr,HostEnt2->h_addr,4); + + if (HostEnt1 && HostEnt2) + { + Sleep(1000 * 60 * 30); + continue; + } + + Debugprintf("Resolve Failed for update.g8bpq.net or chatmap.g8bpq.net"); + Sleep(1000 * 60 * 5); + } +} + + +VOID OpenReportingSockets() +{ + u_long param=1; + BOOL bcopt=TRUE; + + if (LOCATOR[0]) + { + // Enable Node Map Reports + + ReportTimer = 1200; // 2 mins - Give Rigcontrol time to start + + ReportSocket = socket(AF_INET,SOCK_DGRAM,0); + + if (ReportSocket == INVALID_SOCKET) + { + Debugprintf("Failed to create Reporting socket"); + ReportSocket = 0; + return; + } + + ioctlsocket (ReportSocket, FIONBIO, ¶m); + setsockopt (ReportSocket, SOL_SOCKET, SO_BROADCAST, (const char FAR *)&bcopt,4); + + reportdest.sin_family = AF_INET; + reportdest.sin_port = htons(81); + ConvToAX25("DUMMY-1", ReportDest); + } + + // Set up Chat Report even if no LOCATOR reportdest.sin_family = AF_INET; + // Socket must be opened in MailChat Process + + Chatreportdest.sin_family = AF_INET; + Chatreportdest.sin_port = htons(81); + + _beginthread(ResolveUpdateThread, 0, NULL); + + printf("MQTT Enabled %d\n", MQTT); + + if (MQTT) + MQTTConnect(MQTT_HOST, MQTT_PORT, MQTT_USER, MQTT_PASS); +} + +VOID WriteMiniDumpThread(); + +time_t lastMiniDump = 0; + +void WriteMiniDump() +{ +#ifdef WIN32 + + _beginthread(WriteMiniDumpThread, 0, 0); + Sleep(3000); +} + +VOID WriteMiniDumpThread() +{ + HANDLE hFile; + BOOL ret; + char FN[256]; + struct tm * TM; + time_t Now = time(NULL); + + if (lastMiniDump == Now) // Not more than one per second + { + Debugprintf("minidump suppressed"); + return; + } + + lastMiniDump = Now; + + TM = gmtime(&Now); + + sprintf(FN, "%s/Logs/MiniDump%d%02d%02d%02d%02d%02d.dmp", BPQDirectory, + TM->tm_year + 1900, TM->tm_mon +1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec); + + hFile = CreateFile(FN, GENERIC_READ | GENERIC_WRITE, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + // Create the minidump + + ret = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + hFile, MiniDumpNormal, 0, 0, 0 ); + + if(!ret) + Debugprintf("MiniDumpWriteDump failed. Error: %u", GetLastError()); + else + Debugprintf("Minidump %s created.", FN); + CloseHandle(hFile); + } +#endif +} + +// UI Util Code + +#pragma pack(1) + +typedef struct _MESSAGEX +{ +// BASIC LINK LEVEL MESSAGE BUFFER LAYOUT + + struct _MESSAGEX * CHAIN; + + UCHAR PORT; + USHORT LENGTH; + + UCHAR DEST[7]; + UCHAR ORIGIN[7]; + +// MAY BE UP TO 56 BYTES OF DIGIS + + UCHAR CTL; + UCHAR PID; + UCHAR DATA[256]; + UCHAR PADDING[56]; // In case he have Digis + +}MESSAGEX, *PMESSAGEX; + +#pragma pack() + + +int PortNum[MaxBPQPortNo + 1] = {0}; // Tab nunber to port + +char * UIUIDigi[MaxBPQPortNo + 1]= {0}; +char * UIUIDigiAX[MaxBPQPortNo + 1] = {0}; // ax.25 version of digistring +int UIUIDigiLen[MaxBPQPortNo + 1] = {0}; // Length of AX string + +char UIUIDEST[MaxBPQPortNo + 1][11] = {0}; // Dest for Beacons + +char UIAXDEST[MaxBPQPortNo + 1][7] = {0}; + + +UCHAR FN[MaxBPQPortNo + 1][256]; // Filename +int Interval[MaxBPQPortNo + 1]; // Beacon Interval (Mins) +int MinCounter[MaxBPQPortNo + 1]; // Interval Countdown + +BOOL SendFromFile[MaxBPQPortNo + 1]; +char Message[MaxBPQPortNo + 1][1000]; // Beacon Text + +VOID SendUIBeacon(int Port); + +BOOL RunUI = TRUE; + +VOID UIThread(void * Unused) +{ + int Port, MaxPorts = GetNumberofPorts(); + + Sleep(60000); + + while (RunUI) + { + int sleepInterval = 60000; + + for (Port = 1; Port <= MaxPorts; Port++) + { + if (MinCounter[Port]) + { + MinCounter[Port]--; + + if (MinCounter[Port] == 0) + { + MinCounter[Port] = Interval[Port]; + SendUIBeacon(Port); + + // pause beteen beacons but adjust sleep interval to suit + + Sleep(10000); + sleepInterval -= 10000; + } + } + } + + while (sleepInterval <= 0) // just in case we have a crazy config + sleepInterval += 60000; + + Sleep(sleepInterval); + } +} + +int UIRemoveLF(char * Message, int len) +{ + // Remove lf chars + + char * ptr1, * ptr2; + + ptr1 = ptr2 = Message; + + while (len-- > 0) + { + *ptr2 = *ptr1; + + if (*ptr1 == '\r') + if (*(ptr1+1) == '\n') + { + ptr1++; + len--; + } + ptr1++; + ptr2++; + } + + return (int)(ptr2 - Message); +} + + + + +VOID UISend_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue) +{ + MESSAGEX AXMSG; + PMESSAGEX AXPTR = &AXMSG; + int DataLen = Len; + struct PORTCONTROL * PORT = GetPortTableEntryFromSlot(Port); + + // Block includes the Msg Header (7 or 11 bytes), Len Does not! + + memcpy(AXPTR->DEST, HWADDR, 7); + + // Get BCALL or PORTCALL if set + + if (PORT && PORT->PORTBCALL[0]) + memcpy(AXPTR->ORIGIN, PORT->PORTBCALL, 7); + else if (PORT && PORT->PORTCALL[0]) + memcpy(AXPTR->ORIGIN, PORT->PORTCALL, 7); + else + memcpy(AXPTR->ORIGIN, MYCALL, 7); + + AXPTR->DEST[6] &= 0x7e; // Clear End of Call + AXPTR->DEST[6] |= 0x80; // set Command Bit + + if (UIUIDigi[Port]) + { + // This port has a digi string + + int DigiLen = UIUIDigiLen[Port]; + UCHAR * ptr; + + memcpy(&AXPTR->CTL, UIUIDigiAX[Port], DigiLen); + + ptr = (UCHAR *)AXPTR; + ptr += DigiLen; + AXPTR = (PMESSAGEX)ptr; + + Len += DigiLen; + } + + AXPTR->ORIGIN[6] |= 1; // Set End of Call + AXPTR->CTL = 3; //UI + AXPTR->PID = 0xf0; + memcpy(AXPTR->DATA, Msg, DataLen); + +// if (Queue) +// QueueRaw(Port, &AXMSG, Len + 16); +// else + SendRaw(Port, (char *)&AXMSG.DEST, Len + 16); + + return; + +} + + + +VOID SendUIBeacon(int Port) +{ + char UIMessage[1024]; + int Len = (int)strlen(Message[Port]); + int Index = 0; + + if (SendFromFile[Port]) + { + FILE * hFile; + + hFile = fopen(FN[Port], "rb"); + + if (hFile == 0) + return; + + Len = (int)fread(UIMessage, 1, 1024, hFile); + + fclose(hFile); + + } + else + strcpy(UIMessage, Message[Port]); + + Len = UIRemoveLF(UIMessage, Len); + + while (Len > 256) + { + UISend_AX_Datagram(&UIMessage[Index], 256, Port, UIAXDEST[Port], TRUE); + Index += 256; + Len -= 256; + Sleep(2000); + } + UISend_AX_Datagram(&UIMessage[Index], Len, Port, UIAXDEST[Port], TRUE); +} + +#ifndef LINBPQ + +typedef struct tag_dlghdr +{ + HWND hwndTab; // tab control + HWND hwndDisplay; // current child dialog box + RECT rcDisplay; // display rectangle for the tab control + + DLGTEMPLATE *apRes[MaxBPQPortNo + 1]; + +} DLGHDR; + +DLGTEMPLATE * WINAPI DoLockDlgRes(LPCSTR lpszResName); + +#endif + +HWND hwndDlg; +int PageCount; +int CurrentPage=0; // Page currently on show in tabbed Dialog + + +VOID WINAPI OnSelChanged(HWND hwndDlg); +VOID WINAPI OnChildDialogInit(HWND hwndDlg); + +#define ICC_STANDARD_CLASSES 0x00004000 + +HWND hwndDisplay; + +#define ID_TEST 102 +#define IDD_DIAGLOG1 103 +#define IDC_FROMFILE 1022 +#define IDC_EDIT1 1054 +#define IDC_FILENAME 1054 +#define IDC_EDIT2 1055 +#define IDC_MESSAGE 1055 +#define IDC_EDIT3 1056 +#define IDC_INTERVAL 1056 +#define IDC_EDIT4 1057 +#define IDC_UIDEST 1057 +#define IDC_FILE 1058 +#define IDC_TAB1 1059 +#define IDC_UIDIGIS 1059 +#define IDC_PORTNAME 1060 + +extern HKEY REGTREE; +HBRUSH bgBrush; + +VOID SetupUI(int Port) +{ + char DigiString[100], * DigiLeft; + + ConvToAX25(UIUIDEST[Port], &UIAXDEST[Port][0]); + + UIUIDigiLen[Port] = 0; + + if (UIUIDigi[Port]) + { + UIUIDigiAX[Port] = zalloc(100); + strcpy(DigiString, UIUIDigi[Port]); + DigiLeft = strlop(DigiString,','); + + while(DigiString[0]) + { + ConvToAX25(DigiString, &UIUIDigiAX[Port][UIUIDigiLen[Port]]); + UIUIDigiLen[Port] += 7; + + if (DigiLeft) + { + memmove(DigiString, DigiLeft, (int)strlen(DigiLeft) + 1); + DigiLeft = strlop(DigiString,','); + } + else + DigiString[0] = 0; + } + } +} + +#ifndef LINBPQ + +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 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); + +} + +#endif + +config_t cfg; + +VOID SaveUIConfig() +{ + config_setting_t *root, *group, *UIGroup; + int Port, MaxPort = GetNumberofPorts(); + char ConfigName[256]; + + if (BPQDirectory[0] == 0) + { + strcpy(ConfigName,"UIUtil.cfg"); + } + else + { + strcpy(ConfigName,BPQDirectory); + strcat(ConfigName,"/"); + strcat(ConfigName,"UIUtil.cfg"); + } + + // Get rid of old config before saving + + config_init(&cfg); + + root = config_root_setting(&cfg); + + group = config_setting_add(root, "main", CONFIG_TYPE_GROUP); + + UIGroup = config_setting_add(group, "UIUtil", CONFIG_TYPE_GROUP); + + for (Port = 1; Port <= MaxPort; Port++) + { + char Key[20]; + + sprintf(Key, "Port%d", Port); + group = config_setting_add(UIGroup, Key, CONFIG_TYPE_GROUP); + + SaveStringValue(group, "UIDEST", &UIUIDEST[Port][0]); + SaveStringValue(group, "FileName", &FN[Port][0]); + SaveStringValue(group, "Message", &Message[Port][0]); + SaveStringValue(group, "Digis", UIUIDigi[Port]); + + SaveIntValue(group, "Interval", Interval[Port]); + SaveIntValue(group, "SendFromFile", SendFromFile[Port]); + } + + if(!config_write_file(&cfg, ConfigName)) + { + fprintf(stderr, "Error while writing file.\n"); + config_destroy(&cfg); + return; + } + + config_destroy(&cfg); +} + +int GetRegConfig(); + +VOID GetUIConfig() +{ + char Key[100]; + char CfgFN[256]; + char Digis[100]; + struct stat STAT; + + config_t cfg; + config_setting_t *group; + int Port, MaxPort = GetNumberofPorts(); + + memset((void *)&cfg, 0, sizeof(config_t)); + + config_init(&cfg); + + if (BPQDirectory[0] == 0) + { + strcpy(CfgFN,"UIUtil.cfg"); + } + else + { + strcpy(CfgFN,BPQDirectory); + strcat(CfgFN,"/"); + strcat(CfgFN,"UIUtil.cfg"); + } + + if (stat(CfgFN, &STAT) == -1) + { + // No file. If Windows try to read from registy + +#ifndef LINBPQ + GetRegConfig(); +#else + Debugprintf("UIUtil Config File not found\n"); +#endif + return; + } + + if(!config_read_file(&cfg, CfgFN)) + { + fprintf(stderr, "UI Util Config Error Line %d - %s\n", config_error_line(&cfg), config_error_text(&cfg)); + + config_destroy(&cfg); + return; + } + + group = config_lookup(&cfg, "main"); + + if (group) + { + for (Port = 1; Port <= MaxPort; Port++) + { + sprintf(Key, "main.UIUtil.Port%d", Port); + + group = config_lookup (&cfg, Key); + + if (group) + { + GetStringValue(group, "UIDEST", &UIUIDEST[Port][0], 11); + GetStringValue(group, "FileName", &FN[Port][0], 256); + GetStringValue(group, "Message", &Message[Port][0], 1000); + GetStringValue(group, "Digis", Digis, 100); + UIUIDigi[Port] = _strdup(Digis); + + Interval[Port] = GetIntValue(group, "Interval"); + MinCounter[Port] = Interval[Port]; + + SendFromFile[Port] = GetIntValue(group, "SendFromFile"); + + SetupUI(Port); + } + } + } + + + _beginthread(UIThread, 0, NULL); + +} + +#ifndef LINBPQ + +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; +} + +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; +} + +int GetRegConfig() +{ + int retCode, Vallen, Type, i; + char Key[80]; + char Size[80]; + HKEY hKey; + RECT Rect; + + wsprintf(Key, "SOFTWARE\\G8BPQ\\BPQ32\\UIUtil"); + + retCode = RegOpenKeyEx (REGTREE, Key, 0, KEY_QUERY_VALUE, &hKey); + + if (retCode == ERROR_SUCCESS) + { + Vallen=80; + + retCode = RegQueryValueEx(hKey,"Size",0, + (ULONG *)&Type,(UCHAR *)&Size,(ULONG *)&Vallen); + + if (retCode == ERROR_SUCCESS) + sscanf(Size,"%d,%d,%d,%d",&Rect.left,&Rect.right,&Rect.top,&Rect.bottom); + + RegCloseKey(hKey); + } + + for (i=1; i<=32; i++) + { + wsprintf(Key, "SOFTWARE\\G8BPQ\\BPQ32\\UIUtil\\UIPort%d", i); + + retCode = RegOpenKeyEx (REGTREE, + Key, + 0, + KEY_QUERY_VALUE, + &hKey); + + if (retCode == ERROR_SUCCESS) + { + Vallen=0; + RegQueryValueEx(hKey,"Digis",0, + (ULONG *)&Type, NULL, (ULONG *)&Vallen); + + if (Vallen) + { + UIUIDigi[i] = malloc(Vallen); + RegQueryValueEx(hKey,"Digis",0, + (ULONG *)&Type, UIUIDigi[i], (ULONG *)&Vallen); + } + + Vallen=4; + retCode = RegQueryValueEx(hKey, "Interval", 0, + (ULONG *)&Type, (UCHAR *)&Interval[i], (ULONG *)&Vallen); + + MinCounter[i] = Interval[i]; + + Vallen=4; + retCode = RegQueryValueEx(hKey, "SendFromFile", 0, + (ULONG *)&Type, (UCHAR *)&SendFromFile[i], (ULONG *)&Vallen); + + + Vallen=10; + retCode = RegQueryValueEx(hKey, "UIDEST", 0, &Type, &UIUIDEST[i][0], &Vallen); + + Vallen=255; + retCode = RegQueryValueEx(hKey, "FileName", 0, &Type, &FN[i][0], &Vallen); + + Vallen=999; + retCode = RegQueryValueEx(hKey, "Message", 0, &Type, &Message[i][0], &Vallen); + + SetupUI(i); + + RegCloseKey(hKey); + } + } + + SaveUIConfig(); + + return TRUE; +} + +INT_PTR CALLBACK ChildDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ +// This processes messages from controls on the tab subpages + int Command; + + int retCode, disp; + char Key[80]; + HKEY hKey; + BOOL OK; + OPENFILENAME ofn; + char Digis[100]; + + int Port = PortNum[CurrentPage]; + + + switch (message) + { + case WM_NOTIFY: + + switch (((LPNMHDR)lParam)->code) + { + case TCN_SELCHANGE: + OnSelChanged(hDlg); + return TRUE; + // More cases on WM_NOTIFY switch. + case NM_CHAR: + return TRUE; + } + + break; + case WM_INITDIALOG: + OnChildDialogInit( hDlg); + return (INT_PTR)TRUE; + + case WM_CTLCOLORDLG: + + return (LONG)bgBrush; + + case WM_CTLCOLORSTATIC: + { + HDC hdcStatic = (HDC)wParam; + SetTextColor(hdcStatic, RGB(0, 0, 0)); + SetBkMode(hdcStatic, TRANSPARENT); + return (LONG)bgBrush; + } + + + case WM_COMMAND: + + Command = LOWORD(wParam); + + if (Command == 2002) + return TRUE; + + switch (Command) + { + case IDC_FILE: + + memset(&ofn, 0, sizeof (OPENFILENAME)); + ofn.lStructSize = sizeof (OPENFILENAME); + ofn.hwndOwner = hDlg; + ofn.lpstrFile = &FN[Port][0]; + ofn.nMaxFile = 250; + ofn.lpstrTitle = "File to send as beacon"; + ofn.lpstrInitialDir = BPQDirectory; + + if (GetOpenFileName(&ofn)) + SetDlgItemText(hDlg, IDC_FILENAME, &FN[Port][0]); + + break; + + + case IDOK: + + GetDlgItemText(hDlg, IDC_UIDEST, &UIUIDEST[Port][0], 10); + + if (UIUIDigi[Port]) + { + free(UIUIDigi[Port]); + UIUIDigi[Port] = NULL; + } + + if (UIUIDigiAX[Port]) + { + free(UIUIDigiAX[Port]); + UIUIDigiAX[Port] = NULL; + } + + GetDlgItemText(hDlg, IDC_UIDIGIS, Digis, 99); + + UIUIDigi[Port] = _strdup(Digis); + + GetDlgItemText(hDlg, IDC_FILENAME, &FN[Port][0], 255); + GetDlgItemText(hDlg, IDC_MESSAGE, &Message[Port][0], 1000); + + Interval[Port] = GetDlgItemInt(hDlg, IDC_INTERVAL, &OK, FALSE); + + MinCounter[Port] = Interval[Port]; + + SendFromFile[Port] = IsDlgButtonChecked(hDlg, IDC_FROMFILE); + + wsprintf(Key, "SOFTWARE\\G8BPQ\\BPQ32\\UIUtil\\UIPort%d", PortNum[CurrentPage]); + + retCode = RegCreateKeyEx(REGTREE, + Key, 0, 0, 0, KEY_ALL_ACCESS, NULL, &hKey, &disp); + + if (retCode == ERROR_SUCCESS) + { + retCode = RegSetValueEx(hKey, "UIDEST", 0, REG_SZ,(BYTE *)&UIUIDEST[Port][0], (int)strlen(&UIUIDEST[Port][0])); + retCode = RegSetValueEx(hKey, "FileName", 0, REG_SZ,(BYTE *)&FN[Port][0], (int)strlen(&FN[Port][0])); + retCode = RegSetValueEx(hKey, "Message", 0, REG_SZ,(BYTE *)&Message[Port][0], (int)strlen(&Message[Port][0])); + retCode = RegSetValueEx(hKey, "Interval", 0, REG_DWORD,(BYTE *)&Interval[Port], 4); + retCode = RegSetValueEx(hKey, "SendFromFile", 0, REG_DWORD,(BYTE *)&SendFromFile[Port], 4); + retCode = RegSetValueEx(hKey, "Digis",0, REG_SZ, Digis, (int)strlen(Digis)); + + RegCloseKey(hKey); + } + + SetupUI(Port); + + SaveUIConfig(); + + return (INT_PTR)TRUE; + + + case IDCANCEL: + + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + + case ID_TEST: + + SendUIBeacon(Port); + return TRUE; + + } + break; + + } + return (INT_PTR)FALSE; +} + + + +VOID WINAPI OnTabbedDialogInit(HWND hDlg) +{ + DLGHDR *pHdr = (DLGHDR *) LocalAlloc(LPTR, sizeof(DLGHDR)); + DWORD dwDlgBase = GetDialogBaseUnits(); + int cxMargin = LOWORD(dwDlgBase) / 4; + int cyMargin = HIWORD(dwDlgBase) / 8; + + TC_ITEM tie; + RECT rcTab; + + int i, pos, tab = 0; + INITCOMMONCONTROLSEX init; + + char PortNo[60]; + struct _EXTPORTDATA * PORTVEC; + + hwndDlg = hDlg; // Save Window Handle + + // Save a pointer to the DLGHDR structure. + +#define GWL_USERDATA (-21) + + SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) pHdr); + + // Create the tab control. + + + init.dwICC = ICC_STANDARD_CLASSES; + init.dwSize=sizeof(init); + i=InitCommonControlsEx(&init); + + pHdr->hwndTab = CreateWindow(WC_TABCONTROL, "", WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, + 0, 0, 100, 100, hwndDlg, NULL, hInstance, NULL); + + if (pHdr->hwndTab == NULL) { + + // handle error + + } + + // Add a tab for each of the child dialog boxes. + + tie.mask = TCIF_TEXT | TCIF_IMAGE; + + tie.iImage = -1; + + for (i = 1; i <= NUMBEROFPORTS; i++) + { + // Only allow UI on ax.25 ports + + PORTVEC = (struct _EXTPORTDATA * )GetPortTableEntryFromSlot(i); + + if (PORTVEC->PORTCONTROL.PORTTYPE == 16) // EXTERNAL + if (PORTVEC->PORTCONTROL.PROTOCOL == 10) // Pactor/WINMOR + if (PORTVEC->PORTCONTROL.UICAPABLE == 0) + continue; + + wsprintf(PortNo, "Port %2d", GetPortNumber(i)); + PortNum[tab] = i; + + tie.pszText = PortNo; + TabCtrl_InsertItem(pHdr->hwndTab, tab, &tie); + + pHdr->apRes[tab++] = DoLockDlgRes("PORTPAGE"); + } + + PageCount = tab; + + // Determine the bounding rectangle for all child dialog boxes. + + SetRectEmpty(&rcTab); + + for (i = 0; i < PageCount; i++) + { + if (pHdr->apRes[i]->cx > rcTab.right) + rcTab.right = pHdr->apRes[i]->cx; + + if (pHdr->apRes[i]->cy > rcTab.bottom) + rcTab.bottom = pHdr->apRes[i]->cy; + + } + + MapDialogRect(hwndDlg, &rcTab); + +// rcTab.right = rcTab.right * LOWORD(dwDlgBase) / 4; + +// rcTab.bottom = rcTab.bottom * HIWORD(dwDlgBase) / 8; + + // Calculate how large to make the tab control, so + + // the display area can accomodate all the child dialog boxes. + + TabCtrl_AdjustRect(pHdr->hwndTab, TRUE, &rcTab); + + OffsetRect(&rcTab, cxMargin - rcTab.left, cyMargin - rcTab.top); + + // Calculate the display rectangle. + + CopyRect(&pHdr->rcDisplay, &rcTab); + + TabCtrl_AdjustRect(pHdr->hwndTab, FALSE, &pHdr->rcDisplay); + + // Set the size and position of the tab control, buttons, + + // and dialog box. + + SetWindowPos(pHdr->hwndTab, NULL, rcTab.left, rcTab.top, rcTab.right - rcTab.left, rcTab.bottom - rcTab.top, SWP_NOZORDER); + + // Move the Buttons to bottom of page + + pos=rcTab.left+cxMargin; + + + // Size the dialog box. + + SetWindowPos(hwndDlg, NULL, 0, 0, rcTab.right + cyMargin + 2 * GetSystemMetrics(SM_CXDLGFRAME), + rcTab.bottom + 2 * cyMargin + 2 * GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYCAPTION), + SWP_NOMOVE | SWP_NOZORDER); + + // Simulate selection of the first item. + + OnSelChanged(hwndDlg); + +} + +// DoLockDlgRes - loads and locks a dialog template resource. + +// Returns a pointer to the locked resource. + +// lpszResName - name of the resource + +DLGTEMPLATE * WINAPI DoLockDlgRes(LPCSTR lpszResName) +{ + HRSRC hrsrc = FindResource(hInstance, lpszResName, RT_DIALOG); + HGLOBAL hglb = LoadResource(hInstance, hrsrc); + + return (DLGTEMPLATE *) LockResource(hglb); +} + +//The following function processes the TCN_SELCHANGE notification message for the main dialog box. The function destroys the dialog box for the outgoing page, if any. Then it uses the CreateDialogIndirect function to create a modeless dialog box for the incoming page. + +// OnSelChanged - processes the TCN_SELCHANGE notification. + +// hwndDlg - handle of the parent dialog box + +VOID WINAPI OnSelChanged(HWND hwndDlg) +{ + char PortDesc[40]; + int Port; + + DLGHDR *pHdr = (DLGHDR *) GetWindowLong(hwndDlg, GWL_USERDATA); + + CurrentPage = TabCtrl_GetCurSel(pHdr->hwndTab); + + // Destroy the current child dialog box, if any. + + if (pHdr->hwndDisplay != NULL) + + DestroyWindow(pHdr->hwndDisplay); + + // Create the new child dialog box. + + pHdr->hwndDisplay = CreateDialogIndirect(hInstance, pHdr->apRes[CurrentPage], hwndDlg, ChildDialogProc); + + hwndDisplay = pHdr->hwndDisplay; // Save + + Port = PortNum[CurrentPage]; + // Fill in the controls + + GetPortDescription(PortNum[CurrentPage], PortDesc); + + SetDlgItemText(hwndDisplay, IDC_PORTNAME, PortDesc); + + CheckDlgButton(hwndDisplay, IDC_FROMFILE, SendFromFile[Port]); + + SetDlgItemInt(hwndDisplay, IDC_INTERVAL, Interval[Port], FALSE); + + SetDlgItemText(hwndDisplay, IDC_UIDEST, &UIUIDEST[Port][0]); + SetDlgItemText(hwndDisplay, IDC_UIDIGIS, UIUIDigi[Port]); + + + + SetDlgItemText(hwndDisplay, IDC_FILENAME, &FN[Port][0]); + SetDlgItemText(hwndDisplay, IDC_MESSAGE, &Message[Port][0]); + + ShowWindow(pHdr->hwndDisplay, SW_SHOWNORMAL); + +} + + +//The following function processes the WM_INITDIALOG message for each of the child dialog boxes. You cannot specify the position of a dialog box created using the CreateDialogIndirect function. This function uses the SetWindowPos function to position the child dialog within the tab control's display area. + +// OnChildDialogInit - Positions the child dialog box to fall + +// within the display area of the tab control. + +VOID WINAPI OnChildDialogInit(HWND hwndDlg) +{ + HWND hwndParent = GetParent(hwndDlg); + DLGHDR *pHdr = (DLGHDR *) GetWindowLong(hwndParent, GWL_USERDATA); + + SetWindowPos(hwndDlg, HWND_TOP, pHdr->rcDisplay.left, pHdr->rcDisplay.top, 0, 0, SWP_NOSIZE); +} + + + +LRESULT CALLBACK UIWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + HKEY hKey=0; + + switch (message) { + + case WM_INITDIALOG: + OnTabbedDialogInit(hWnd); + return (INT_PTR)TRUE; + + case WM_NOTIFY: + + switch (((LPNMHDR)lParam)->code) + { + case TCN_SELCHANGE: + OnSelChanged(hWnd); + return TRUE; + // More cases on WM_NOTIFY switch. + case NM_CHAR: + return TRUE; + } + + break; + + + case WM_CTLCOLORDLG: + return (LONG)bgBrush; + + case WM_CTLCOLORSTATIC: + { + HDC hdcStatic = (HDC)wParam; + SetTextColor(hdcStatic, RGB(0, 0, 0)); + SetBkMode(hdcStatic, TRANSPARENT); + + return (LONG)bgBrush; + } + + case WM_COMMAND: + + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + switch (wmId) { + + case IDOK: + + return TRUE; + + default: + + return 0; + } + + + case WM_SYSCOMMAND: + + wmId = LOWORD(wParam); // Remember, these are... + wmEvent = HIWORD(wParam); // ...different for Win32! + + switch (wmId) + { + case SC_RESTORE: + + return (DefWindowProc(hWnd, message, wParam, lParam)); + + case SC_MINIMIZE: + + if (MinimizetoTray) + return ShowWindow(hWnd, SW_HIDE); + else + return (DefWindowProc(hWnd, message, wParam, lParam)); + + break; + + default: + return (DefWindowProc(hWnd, message, wParam, lParam)); + } + + case WM_CLOSE: + return(DestroyWindow(hWnd)); + + default: + return (DefWindowProc(hWnd, message, wParam, lParam)); + + } + + return (0); +} + +#endif + +extern struct DATAMESSAGE * REPLYBUFFER; +char * __cdecl Cmdprintf(TRANSPORTENTRY * Session, char * Bufferptr, const char * format, ...); + +void GetPortCTEXT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, struct CMDX * CMD) +{ + char FN[250]; + FILE *hFile; + struct stat STAT; + struct PORTCONTROL * PORT = PORTTABLE; + char PortList[256] = ""; + + while (PORT) + { + if (PORT->CTEXT) + { + free(PORT->CTEXT); + PORT->CTEXT = 0; + } + + if (BPQDirectory[0] == 0) + sprintf(FN, "Port%dCTEXT.txt", PORT->PORTNUMBER); + else + sprintf(FN, "%s/Port%dCTEXT.txt", BPQDirectory, PORT->PORTNUMBER); + + if (stat(FN, &STAT) == -1) + { + PORT = PORT->PORTPOINTER; + continue; + } + + hFile = fopen(FN, "rb"); + + if (hFile) + { + char * ptr; + + PORT->CTEXT = zalloc(STAT.st_size + 1); + fread(PORT->CTEXT , 1, STAT.st_size, hFile); + fclose(hFile); + + // convert CRLF or LF to CR + + while (ptr = strstr(PORT->CTEXT, "\r\n")) + memmove(ptr, ptr + 1, strlen(ptr)); + + // Now has LF + + while (ptr = strchr(PORT->CTEXT, '\n')) + *ptr = '\r'; + + + sprintf(PortList, "%s,%d", PortList, PORT->PORTNUMBER); + } + + PORT = PORT->PORTPOINTER; + } + + if (Session) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "CTEXT Read for ports %s\r", &PortList[1]); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + } + else + Debugprintf("CTEXT Read for ports %s\r", &PortList[1]); +} + +// Get the current frequency for a port. This can get a bit complicated, especially if looking for centre freq +// rather than dial freq (as this depends on mode). +// +// Used for various reporting functions - MH, Maps, BBS New User message, + +// I think I'll try PORT "PortFreq" setting first then if that isn't available via rigcontrol. +// +// For now at least will report dial freq if using RIGCONTROL + +DllExport uint64_t APIENTRY GetPortFrequency(int PortNo, char * FreqString) +{ + struct PORTCONTROL * PORT = GetPortTableEntryFromPortNum(PortNo); + double freq = 0.0; + uint64_t freqint = 0; + + char * ptr; + int n = 3; + + FreqString[0] = 0; + + if (PORT == 0) + return 0; + + if (PORT->PortFreq) + { + freqint = PORT->PortFreq; + freq = freqint / 1000000.0; + } + else + { + // Try rigcontrol + + + struct TNCINFO * TNC; + struct RIGINFO * RIG = 0; + + if (PORT->RIGPort) + TNC = TNCInfo[PORT->RIGPort]; + else + TNC = TNCInfo[PortNo]; + + if (TNC) + RIG = TNC->RIG; + + if (RIG == 0) + return 0; + + // Frequency should be in valchar + + if (RIG->Valchar[0] == 0) + return 0; + + freq = atof(TNC->RIG->Valchar); + freqint = (int64_t)(freq * 1000000.0); + } + + sprintf(FreqString, "%.6f", freq); + + // Return 3 digits after . (KHz) unless more are significant + + ptr = &FreqString[strlen(FreqString) - 1]; + + while (n-- && *(ptr) == '0') + *ptr-- = 0; + + return freqint; +} + +SOCKET OpenHTTPSock(char * Host) +{ + SOCKET sock = 0; + struct sockaddr_in destaddr; + struct sockaddr_in sinx; + int addrlen=sizeof(sinx); + struct hostent * HostEnt; + int err; + u_long param=1; + BOOL bcopt=TRUE; + + destaddr.sin_family = AF_INET; + destaddr.sin_port = htons(80); + + // Resolve name to address + + HostEnt = gethostbyname (Host); + + if (!HostEnt) + { + err = WSAGetLastError(); + + Debugprintf("Resolve Failed for %s %d %x", Host, err, err); + return 0 ; // Resolve failed + } + + memcpy(&destaddr.sin_addr.s_addr,HostEnt->h_addr,4); + + // Allocate a Socket entry + + sock = socket(AF_INET,SOCK_STREAM,0); + + if (sock == INVALID_SOCKET) + return 0; + + setsockopt (sock, 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(sock, (struct sockaddr *) &sinx, addrlen) != 0 ) + return FALSE; + + if (connect(sock,(struct sockaddr *) &destaddr, sizeof(destaddr)) != 0) + { + err=WSAGetLastError(); + closesocket(sock); + return 0; + } + + return sock; +} + +static char HeaderTemplate[] = "POST %s HTTP/1.1\r\n" + "Accept: app N B lication/json\r\n" +// "Accept-Encoding: gzip,deflate,gzip, deflate\r\n" + "Content-Type: application/json\r\n" + "Host: %s:%d\r\n" + "Content-Length: %d\r\n" + "User-Agent: %s%s\r\n" +// "Expect: 100-continue\r\n" + "\r\n"; + + +DllExport VOID WINAPI SendWebRequest(char * Host, char * Request, char * Params, char * Return) +{ + SOCKET sock; + int InputLen = 0; + int inptr = 0; + char Buffer[4096]; + char Header[256]; + char * ptr, * ptr1; + int Sent; + int Len = strlen(Params); + + if (M0LTEMap == 0) + return; + + sock = OpenHTTPSock(Host); + + if (sock == 0) + return; + +#ifdef LINBPQ + sprintf(Header, HeaderTemplate, Request, Host, 80, Len, "linbpq/", VersionString, Params); +#else + sprintf(Header, HeaderTemplate, Request, Host, 80, Len, "bpq32/", VersionString, Params); +#endif + Sent = send(sock, Header, (int)strlen(Header), 0); + Sent = send(sock, Params, (int)strlen(Params), 0); + + if (Sent == -1) + { + int Err = WSAGetLastError(); + Debugprintf("Error %d from Web Update send()", Err); + closesocket(sock); + return; + } + + while (InputLen != -1) + { + InputLen = recv(sock, &Buffer[inptr], 4095 - inptr, 0); + + if (InputLen == -1 || InputLen == 0) + { + int Err = WSAGetLastError(); + Debugprintf("Error %d from Web Update recv()", Err); + closesocket(sock); + return; + } + + inptr += InputLen; + + Buffer[inptr] = 0; + + ptr = strstr(Buffer, "\r\n\r\n"); + + if (ptr) + { + // got header + + int Hddrlen = (int)(ptr - Buffer); + + ptr1 = strstr(Buffer, "Content-Length:"); + + if (ptr1) + { + // Have content length + + int ContentLen = atoi(ptr1 + 16); + + if (ContentLen + Hddrlen + 4 == inptr) + { + // got whole response + + if (strstr(Buffer, " 200 OK")) + { + if (Return) + { + memcpy(Return, ptr + 4, ContentLen); + Return[ContentLen] = 0; + } + else + Debugprintf("Map Database update ok"); + + } + else + { + strlop(Buffer, 13); + Debugprintf("Map Update failed - %s", Buffer); + } + closesocket(sock); + return; + } + } + else + { + ptr1 = strstr(_strlwr(Buffer), "transfer-encoding:"); + + if (ptr1) + { + // Just accept anything until I've sorted things with Lee + + closesocket(sock); + Debugprintf("Web Database update ok"); + return; + } + } + } + } +} + +// https://packetnodes.spots.radio/api/NodeData/{callsign} + +//SendHTTPRequest(sock, "/account/exists", Message, Len, Response); + +#include "kiss.h" + +extern char MYALIASLOPPED[10]; +extern int MasterPort[MAXBPQPORTS+1]; + + +// G7TAJ // +/* + {"mheard": [ + { + "Callsign": "GB7CIP-7", + "Port": "VHF", + "Packets": 70369, + "LastHeard": "2024-12-29 20:26:32" + }, +*/ + +void BuildPortMH(char * MHJSON, struct PORTCONTROL * PORT) +{ + struct tm * TM; + static char MHTIME[50]; + time_t szClock; + MHSTRUC * MH = PORT->PORTMHEARD; + int count = MHENTRIES; + char Normcall[20]; + int len; + char * ptr; + char mhstr[400]; + int i; + char c; + + if (MH == NULL) + return; + + while (count--) + { + if (MH->MHCALL[0] == 0) + break; + + len = ConvFromAX25(MH->MHCALL, Normcall); + Normcall[len] = 0; + + ptr = &MH->MHCALL[6]; // End of Address bit + + if ((*ptr & 1) == 0) + { + // at least one digi - which we are not going to include + MH++; + continue; + } + + // validate call to prevent corruption of json + + for (i=0; i < len; i++) + { + c = Normcall[i]; + + if (!isalnum(c) && !(c == '#') && !(c == ' ') && !(c == '-')) + goto skipit; + } + + + //format TIME + + szClock = MH->MHTIME; + TM = gmtime(&szClock); + sprintf(MHTIME, "%d-%d-%d %02d:%02d:%02d", + TM->tm_year+1900, TM->tm_mon + 1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec); + + sprintf(mhstr, "{\"callSign\": \"%s\", \"port\": \"%d\", \"packets\": %d, \"lastHeard\": \"%s\" },\r\n" , + Normcall, PORT->PORTNUMBER, MH->MHCOUNT, MHTIME); + + strcat( MHJSON, mhstr ); +skipit: + MH++; + } +} + +void SendDataToPktMapThread(); + +void SendDataToPktMap() +{ + _beginthread(SendDataToPktMapThread,2048000,0); +} + +void SendDataToPktMapThread() +{ + char Return[256] = ""; + char Request[64]; + char Params[50000]; + + struct PORTCONTROL * PORT = PORTTABLE; + struct PORTCONTROL * SAVEPORT; + struct ROUTE * Routes = NEIGHBOURS; + int MaxRoutes = MAXNEIGHBOURS; + + int PortNo; + int Active; + uint64_t Freq; + int Baud; + int Bitrate; + char * Mode; + char * Use; + char * Type; + char * Modulation; + char * Usage; + + char locked[] = " ! "; + int Percent = 0; + int Port = 0; + char Normcall[10]; + char Copy[20]; + char ID[33]; + + char * ptr = Params; + +// G7TAJ // + char MHJSON[50000]; + char * mhptr; + char * b4Routesptr; + + MHJSON[0]=0; +// G7TAJ // + +// printf("Sending to new map\n"); + + sprintf(Request, "/api/NodeData/%s", MYNODECALL); + +// https://packetnodes.spots.radio/swagger/index.html + + // This builds the request and sends it + + // Minimum header seems to be + + // "nodeAlias": "BPQ", + // "location": {"locator": "IO68VL"}, + // "software": {"name": "BPQ32","version": "6.0.24.3"}, + + ptr += sprintf(ptr, "{\"nodeAlias\": \"%s\",\r\n", MYALIASLOPPED); + + if (strlen(LOCATOR) == 6) + ptr += sprintf(ptr, "\"location\": {\"locator\": \"%s\"},\r\n", LOCATOR); + else + { + // Lat Lon + + double myLat, myLon; + char LocCopy[80]; + char * context; + + strcpy(LocCopy, LOCATOR); + + myLat = atof(strtok_s(LocCopy, ",:; ", &context)); + myLon = atof(context); + + ptr += sprintf(ptr, "\"location\": {\"coords\": {\"lat\": %f, \"lon\": %f}},\r\n", + myLat, myLon); + + } + +#ifdef LINBPQ + ptr += sprintf(ptr, "\"software\": {\"name\": \"LINBPQ\",\"version\": \"%s\"},\r\n", VersionString); +#else + ptr += sprintf(ptr, "\"software\": {\"name\": \"BPQ32\",\"version\": \"%s\"},\r\n", VersionString); +#endif + ptr += sprintf(ptr, "\"source\": \"ReportedByNode\",\r\n"); + +// G7TAJ // + sprintf(MHJSON, ",\"mheard\": ["); +// G7TAJ // + + + //Ports + + ptr += sprintf(ptr, "\"ports\": ["); + + // Get active ports + + while (PORT) + { + PortNo = PORT->PORTNUMBER; + + if (PORT->Hide) + { + PORT = PORT->PORTPOINTER; + continue; + } + + if (PORT->SendtoM0LTEMap == 0) + { + PORT = PORT->PORTPOINTER; + continue; + } + + // Try to get port status - may not be possible with some + + if (PORT->PortStopped) + { + PORT = PORT->PORTPOINTER; + continue; + } + + Active = 0; + Freq = 0; + Baud = 0; + Mode = "ax.25"; + Use = ""; + Type = "RF"; + Bitrate = 0; + Modulation = "FSK"; + Usage = "Access"; + + if (PORT->PortFreq) + Freq = PORT->PortFreq; + + if (PORT->PORTTYPE == 0) + { + struct KISSINFO * KISS = (struct KISSINFO *)PORT; + NPASYINFO Port; + + SAVEPORT = PORT; + + if (KISS->FIRSTPORT && KISS->FIRSTPORT != KISS) + { + // Not first port on device + + PORT = (struct PORTCONTROL *)KISS->FIRSTPORT; + Port = KISSInfo[PortNo]; + } + + Port = KISSInfo[PORT->PORTNUMBER]; + + if (Port) + { + // KISS like - see if connected + + if (PORT->PORTIPADDR.s_addr || PORT->KISSSLAVE) + { + // KISS over UDP or TCP + + if (PORT->KISSTCP) + { + if (Port->Connected) + Active = 1; + } + else + Active = 1; // UDP - Cant tell + } + else + if (Port->idComDev) // Serial port Open + Active = 1; + + PORT = SAVEPORT; + } + } + else if (PORT->PORTTYPE == 14) // Loopback + Active = 0; + + else if (PORT->PORTTYPE == 16) // External + { + if (PORT->PROTOCOL == 10) // 'HF' Port + { + struct TNCINFO * TNC = TNCInfo[PortNo]; + struct AGWINFO * AGW; + + if (TNC == NULL) + { + PORT = PORT->PORTPOINTER; + continue; + } + + if (Freq == 0 && TNC->RIG) + Freq = TNC->RIG->RigFreq * 1000000; + + switch (TNC->Hardware) // Hardware Type + { + case H_KAM: + case H_AEA: + case H_HAL: + case H_SERIAL: + + // Serial + + if (TNC->hDevice) + Active = 1; + + break; + + case H_SCS: + case H_TRK: + case H_WINRPR: + + if (TNC->HostMode) + Active = 1; + + break; + + + case H_UZ7HO: + + if (TNCInfo[MasterPort[PortNo]]->CONNECTED) + Active = 1; + + // Try to get mode and frequency + + AGW = TNC->AGWInfo; + + if (AGW && AGW->isQTSM) + { + if (AGW->ModemName[0]) + { + char * ptr1, * ptr2, *Context; + + strcpy(Copy, AGW->ModemName); + ptr1 = strtok_s(Copy, " ", & Context); + ptr2 = strtok_s(NULL, " ", & Context); + + if (Context) + { + Modulation = Copy; + + if (strstr(ptr1, "BPSK") || strstr(ptr1, "AFSK")) + { + Baud = Bitrate = atoi(Context); + } + else if (strstr(ptr1, "QPSK")) + { + Modulation = "QPSK"; + Bitrate = atoi(Context); + Baud = Bitrate /2; + } + } + } + } + + break; + + case H_KISSHF: + + // Try to get mode from ID then drop through + + if (stristr(PORT->PORTDESCRIPTION, "BPSK")) + { + Modulation = "BPSK"; + } + + case H_WINMOR: + case H_V4: + + case H_MPSK: + case H_FLDIGI: + case H_UIARQ: + case H_ARDOP: + case H_VARA: + + case H_FREEDATA: + + // TCP + + Mode = Modenames[TNC->Hardware - 1]; + + if (TNC->CONNECTED) + Active = 1; + + break; + + case H_TELNET: + + Active = 1; + Type = "Internet"; + Mode = ""; + } + } + else + { + // External but not HF - AXIP, BPQETHER VKISS, ?? + + struct _EXTPORTDATA * EXTPORT = (struct _EXTPORTDATA *)PORT; + Type = "Internet"; + Active = 1; + } + } + + if (Active) + { + char * ptr2 = &ID[29]; + strcpy(ID, PORT->PORTDESCRIPTION); + while (*(ptr2) == ' ' && ptr2 != ID) + *(ptr2--) = 0; + + if (PORT->M0LTEMapInfo) + { + // Override with user configured values - RF,7.045,BPSK,300,300,Access + + char param[256]; + char *p1, *p2, *p3, *p4, *p5; + + strcpy(param, PORT->M0LTEMapInfo); + + p1 = strlop(param, ','); + p2 = strlop(p1, ','); + p3 = strlop(p2, ','); + p4 = strlop(p3, ','); + p5 = strlop(p4, ','); + + // int n = sscanf(PORT->M0LTEMapInfo, "%s,%s,%s,%s,%s,%s", &p1, &p2, &p3, &p4, &p5, &p6); + + if (p5) + { + if (param[0]) Type = param; + + if (p1[0]) + { + // if set to DIAL+=n and frequency set from config or rigcontrol modify it + + uint64_t offset = 0; + + if (_memicmp(p1, "DIAL+", 5) == 0) + offset = atoi(&p1[5]); + else if (_memicmp(p1, "DIAL-", 5) == 0) + offset = -atoi(&p1[5]); + else + Freq = atof(p1) * 1000000; + + if (Freq != 0) + Freq += offset; + + } + + if (p2[0]) Modulation = p2; + if (p3[0]) Baud = atoi(p3); + if (p4[0]) Bitrate = atoi(p4); + if (p5[0]) Usage = p5; + } + } + + ptr += sprintf(ptr, "{\"id\": \"%d\",\"linkType\": \"%s\"," + "\"freq\": \"%lld\",\"mode\": \"%s\",\"modulation\": \"%s\"," + "\"baud\": \"%d\",\"bitrate\": \"%d\",\"usage\": \"%s\",\"comment\": \"%s\"},\r\n", + PortNo, Type, + Freq, Mode, Modulation, + Baud, Bitrate, Usage, ID); + +// G7TAJ // + // make MH list to be added later + BuildPortMH(MHJSON, PORT); + +// G7TAJ // + + + } + + PORT = PORT->PORTPOINTER; + } + + ptr -= 3; + ptr += sprintf(ptr, "],\r\n"); + + // Neighbours + +// G7TAJ // + b4Routesptr = ptr-3; +// G7TAJ // + + ptr += sprintf(ptr, "\"neighbours\": [\r\n"); + + while (MaxRoutes--) + { + if (Routes->NEIGHBOUR_CALL[0] != 0) + if (Routes->NEIGHBOUR_LINK && Routes->NEIGHBOUR_LINK->L2STATE >= 5) + { + ConvFromAX25(Routes->NEIGHBOUR_CALL, Normcall); + strlop(Normcall, ' '); + + ptr += sprintf(ptr, + "{\"node\": \"%s\", \"port\": \"%d\", \"quality\": \"%d\"},\r\n", + Normcall, Routes->NEIGHBOUR_PORT, Routes->NEIGHBOUR_QUAL); + } + + Routes++; + } + +// G7TAJ // + + // if !strstr quality, then there are none, so remove neighbours portion + if ( strstr(Params, "quality") == NULL ) { + ptr = b4Routesptr; + } else { + ptr -= 3; + ptr += sprintf(ptr, "]"); + } + + if ( strlen(MHJSON) > 15 ) { + mhptr = MHJSON + strlen(MHJSON); + mhptr -= 3; + sprintf(mhptr, "]\r\n"); + ptr += sprintf(ptr, "\r\n%s", MHJSON); + + } + + ptr += sprintf(ptr, "}"); + + + +// G7TAJ // + + +/* +{ + "nodeAlias": "BPQ", + "location": {"locator": "IO92KX"}, + "software": {"name": "BPQ32","version": "6.0.24.11 Debug Build "}, + "contact": "G8BPQ", + "sysopComment": "Testing", + "source": "ReportedByNode" +} + + "ports": [ + { + "id": "string", + "linkType": "RF", + "freq": 0, + "mode": "string", + "modulation": "string", + "baud": 0, + "bitrate": 0, + "usage": "Access", + "comment": "string" + } + ], + +*/ + // "contact": "string", + // "neighbours": [{"node": "G7TAJ","port": "30"}] + + SendWebRequest("packetnodes.spots.radio", Request, Params, 0); +} + +// ="{\"neighbours\": [{\"node\": \"G7TAJ\",\"port\": \"30\"}]}"; + +//'POST' \ +// 'https://packetnodes.spots.radio/api/NodeData/GM8BPQ' \ +// -H 'accept: */*' \ +// -H 'Content-Type: application/json' \ +// -d '{ +// "nodeAlias": "BPQ", +// "location": {"locator": "IO68VL"}, +// "software": {"name": "BPQ32","version": "6.0.24.3"}, +// "contact": "string", +// "neighbours": [{"node": "G7TAJ","port": "30"}] +//}' + + + + + + + + + diff --git a/CommonCode.c b/CommonCode.c index b02b0fa..18d412c 100644 --- a/CommonCode.c +++ b/CommonCode.c @@ -32,7 +32,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #pragma data_seg("_BPQDATA") -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "configstructs.h" @@ -69,7 +69,7 @@ VOID WriteMiniDump(); void printStack(void); char * FormatMH(PMHSTRUC MH, char Format); void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode); -void SendDataToPktMap(char *Msg); +void SendDataToPktMap(); extern BOOL LogAllConnects; extern BOOL M0LTEMap; @@ -571,7 +571,7 @@ void * zalloc(int len) return ptr; } -char * strlop(const char * buf, char delim) +char * strlop(char * buf, char delim) { // Terminate buf at delim, and return rest of string @@ -1456,7 +1456,21 @@ DllExport int APIENTRY SessionStateNoAck(int stream, int * state) return 0; } + +int SendMsgEx(int stream, char * msg, int len, int GetSem); + +int SendMsgNoSem(int stream, char * msg, int len) +{ + return SendMsgEx(stream, msg, len, 0); +} + DllExport int APIENTRY SendMsg(int stream, char * msg, int len) +{ + return SendMsgEx(stream, msg, len, 1); +} + + +int SendMsgEx(int stream, char * msg, int len, int GetSem) { // Send message to stream (BPQHOST Function 2) @@ -1479,11 +1493,13 @@ DllExport int APIENTRY SendMsg(int stream, char * msg, int len) if (QCOUNT < 50) return 0; // Dont want to run out - GetSemaphore(&Semaphore, 10); + if (GetSem) + GetSemaphore(&Semaphore, 10); if ((MSG = GetBuff()) == 0) { - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 0; } @@ -1494,7 +1510,8 @@ DllExport int APIENTRY SendMsg(int stream, char * msg, int len) SENDUIMESSAGE(MSG); ReleaseBuffer(MSG); - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 0; } @@ -1509,13 +1526,15 @@ DllExport int APIENTRY SendMsg(int stream, char * msg, int len) if (L4 == 0) return 0; - GetSemaphore(&Semaphore, 22); + if (GetSem) + GetSemaphore(&Semaphore, 22); SESS->HOSTFLAGS |= 0x80; // SET ALLOCATED BIT if (QCOUNT < 40) // PLENTY FREE? { - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 1; } @@ -1528,14 +1547,16 @@ DllExport int APIENTRY SendMsg(int stream, char * msg, int len) if (n > 100) { Debugprintf("Stream %d QCOUNT %d Q Len %d - discarding", stream, QCOUNT, n); - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 1; } } if ((MSG = GetBuff()) == 0) { - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 1; } @@ -1562,7 +1583,8 @@ DllExport int APIENTRY SendMsg(int stream, char * msg, int len) else C_Q_ADD(&L4->L4RX_Q, MSG); - FreeSemaphore(&Semaphore); + if (GetSem) + FreeSemaphore(&Semaphore); return 0; } DllExport int APIENTRY SendRaw(int port, char * msg, int len) @@ -3318,7 +3340,7 @@ VOID SendLocation() SendReportMsg((char *)&AXMSG.DEST, Len + 16); if (M0LTEMap) - SendDataToPktMap(""); + SendDataToPktMap(); return; @@ -3326,7 +3348,6 @@ VOID SendLocation() - VOID SendMH(struct TNCINFO * TNC, char * call, char * freq, char * LOC, char * Mode) { MESSAGE AXMSG; @@ -3342,7 +3363,8 @@ VOID SendMH(struct TNCINFO * TNC, char * call, char * freq, char * LOC, char * M // Block includes the Msg Header (7 bytes), Len Does not! memcpy(AXPTR->DEST, ReportDest, 7); - if (TNC->PortRecord->PORTCONTROL.PORTCALL[0]) + + if (TNC && TNC->PortRecord->PORTCONTROL.PORTCALL[0]) memcpy(AXPTR->ORIGIN, TNC->PortRecord->PORTCONTROL.PORTCALL, 7); else memcpy(AXPTR->ORIGIN, MYCALL, 7); @@ -3502,8 +3524,10 @@ int __sync_lock_test_and_set(int * ptr, int val) #endif // MACBPQ +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + -void GetSemaphore(struct SEM * Semaphore, int ID) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line) { // // Wait for it to be free @@ -3547,6 +3571,8 @@ loop1: Semaphore->SemProcessID = GetCurrentProcessId(); Semaphore->SemThreadID = GetCurrentThreadId(); SemHeldByAPI = ID; + Semaphore->Line = Line; + strcpy(Semaphore->File, File); return; } @@ -4171,10 +4197,10 @@ VOID GetUIConfig() if (group) { - GetStringValue(group, "UIDEST", &UIUIDEST[Port][0]); - GetStringValue(group, "FileName", &FN[Port][0]); - GetStringValue(group, "Message", &Message[Port][0]); - GetStringValue(group, "Digis", Digis); + GetStringValue(group, "UIDEST", &UIUIDEST[Port][0], 11); + GetStringValue(group, "FileName", &FN[Port][0], 256); + GetStringValue(group, "Message", &Message[Port][0], 1000); + GetStringValue(group, "Digis", Digis, 100); UIUIDigi[Port] = _strdup(Digis); Interval[Port] = GetIntValue(group, "Interval"); @@ -4205,15 +4231,21 @@ int GetIntValue(config_setting_t * group, char * name) return 0; } -BOOL GetStringValue(config_setting_t * group, char * name, char * value) +BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen) { - const char * str; + char * str; config_setting_t *setting; setting = config_setting_get_member (group, name); if (setting) { - str = config_setting_get_string (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; } @@ -5131,10 +5163,16 @@ skipit: } } +void SendDataToPktMapThread(); + +void SendDataToPktMap() +{ + _beginthread(SendDataToPktMapThread,2048000,0); +} -void SendDataToPktMap(char *Msg) +void SendDataToPktMapThread() { - char Return[256]; + char Return[256] = ""; char Request[64]; char Params[50000]; @@ -5582,7 +5620,7 @@ void SendDataToPktMap(char *Msg) // "contact": "string", // "neighbours": [{"node": "G7TAJ","port": "30"}] - SendWebRequest("packetnodes.spots.radio", Request, Params, Return); + SendWebRequest("packetnodes.spots.radio", Request, Params, 0); } // ="{\"neighbours\": [{\"node\": \"G7TAJ\",\"port\": \"30\"}]}"; diff --git a/DOSAPI.c b/DOSAPI.c index 49db0f1..5780608 100644 --- a/DOSAPI.c +++ b/DOSAPI.c @@ -32,7 +32,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "compatbits.h" -#include "CHeaders.h" +#include "cheaders.h" extern QCOUNT; extern BPQVECSTRUC BPQHOSTVECTOR[]; diff --git a/DRATS.c b/DRATS.c index 8c7e9e9..c7b6b83 100644 --- a/DRATS.c +++ b/DRATS.c @@ -21,7 +21,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" #include "telnetserver.h" diff --git a/Events.c b/Events.c index e47e012..6d5f618 100644 --- a/Events.c +++ b/Events.c @@ -125,7 +125,6 @@ void hookL2SessionAccepted(int Port, char * remotecall, char * ourcall, struct _ strcpy(LINK->callingCall, remotecall); strcpy(LINK->receivingCall, ourcall); strcpy(LINK->Direction, "In"); - } void hookL2SessionDeleted(struct _LINKTABLE * LINK) diff --git a/FBBRoutines.c b/FBBRoutines.c index 67ca4ee..1810bf9 100644 --- a/FBBRoutines.c +++ b/FBBRoutines.c @@ -23,6 +23,10 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "bpqmail.h" +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); + + int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress); void MQTTMessageEvent(void* message); diff --git a/FLDigi.c b/FLDigi.c index 89d1597..480a443 100644 --- a/FLDigi.c +++ b/FLDigi.c @@ -23,7 +23,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); diff --git a/FreeDATA.c b/FreeDATA.c index 01fb151..66ff40a 100644 --- a/FreeDATA.c +++ b/FreeDATA.c @@ -32,7 +32,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #endif #endif -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" #include "tncinfo.h" diff --git a/HALDriver.c b/HALDriver.c index b15d4fe..a914a38 100644 --- a/HALDriver.c +++ b/HALDriver.c @@ -28,7 +28,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "time.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/HFCommon.c b/HFCommon.c index ba936dd..c0f82d7 100644 --- a/HFCommon.c +++ b/HFCommon.c @@ -30,7 +30,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "kernelresource.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #ifndef LINBPQ #include diff --git a/HSMODEM.c b/HSMODEM.c index a9af669..4efdb80 100644 --- a/HSMODEM.c +++ b/HSMODEM.c @@ -33,7 +33,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #endif -#include "CHeaders.h" +#include "cheaders.h" #pragma pack(1) diff --git a/HTMLCommonCode.c b/HTMLCommonCode.c index 9e2a3eb..b86a7eb 100644 --- a/HTMLCommonCode.c +++ b/HTMLCommonCode.c @@ -27,7 +27,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #pragma data_seg("_BPQDATA") -#include "CHeaders.h" +#include "cheaders.h" #include "templatedefs.c" // Inline definitions from HTLMPages diff --git a/HTTPcode-skigdebian.c b/HTTPcode-skigdebian.c new file mode 100644 index 0000000..ac0a3ff --- /dev/null +++ b/HTTPcode-skigdebian.c @@ -0,0 +1,5175 @@ +/* +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 +*/ + + +//#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#define _CRT_SECURE_NO_DEPRECATE + +#define DllImport + +#include "cheaders.h" +#include + +#include "tncinfo.h" +#include "time.h" +#include "bpq32.h" +#include "telnetserver.h" + +// This is needed to link with a lib built from source + +#ifdef WIN32 +#define ZEXPORT __stdcall +#endif + +#include + +#define CKernel +#include "httpconnectioninfo.h" + +extern int MAXBUFFS, QCOUNT, MINBUFFCOUNT, NOBUFFCOUNT, BUFFERWAITS, L3FRAMES; +extern int NUMBEROFNODES, MAXDESTS, L4CONNECTSOUT, L4CONNECTSIN, L4FRAMESTX, L4FRAMESRX, L4FRAMESRETRIED, OLDFRAMES; +extern int STATSTIME; +extern TRANSPORTENTRY * L4TABLE; +extern BPQVECSTRUC BPQHOSTVECTOR[]; +extern BOOL APRSApplConnected; +extern char VersionString[]; +VOID FormatTime3(char * Time, time_t cTime); +DllExport int APIENTRY Get_APPLMASK(int Stream); +VOID SaveUIConfig(); +int ProcessNodeSignon(SOCKET sock, struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, int LOCAL); +VOID SetupUI(int Port); +VOID SendUIBeacon(int Port); +VOID GetParam(char * input, char * key, char * value); +VOID ARDOPAbort(struct TNCINFO * TNC); +VOID WriteMiniDump(); +BOOL KillTNC(struct TNCINFO * TNC); +BOOL RestartTNC(struct TNCINFO * TNC); +int GetAISPageInfo(char * Buffer, int ais, int adsb); +int GetAPRSPageInfo(char * Buffer, double N, double S, double W, double E, int aprs, int ais, int adsb); +unsigned char * Compressit(unsigned char * In, int Len, int * OutLen); +char * stristr (char *ch1, char *ch2); +int GetAPRSIcon(unsigned char * _REPLYBUFFER, char * NodeURL); +char * GetStandardPage(char * FN, int * Len); +BOOL SHA1PasswordHash(char * String, char * Hash); +char * byte_base64_encode(char *str, int len); +int APIProcessHTTPMessage(char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); +int RHPProcessHTTPMessage(struct ConnectionInfo * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); + +extern struct ROUTE * NEIGHBOURS; +extern int ROUTE_LEN; +extern int MAXNEIGHBOURS; + +extern struct DEST_LIST * DESTS; // NODE LIST +extern int DEST_LIST_LEN; +extern int MAXDESTS; // MAX NODES IN SYSTEM + +extern struct _LINKTABLE * LINKS; +extern int LINK_TABLE_LEN; +extern int MAXLINKS; +extern char * RigWebPage; +extern COLORREF Colours[256]; + +extern BOOL IncludesMail; +extern BOOL IncludesChat; + +extern BOOL APRSWeb; +extern BOOL RigActive; + +extern HKEY REGTREE; + +extern BOOL APRSActive; + +extern UCHAR LogDirectory[]; + +extern struct RIGPORTINFO * PORTInfo[34]; +extern int NumberofPorts; + +extern UCHAR ConfigDirectory[260]; + +VOID sendandcheck(SOCKET sock, const char * Buffer, int Len); +int CompareNode(const void *a, const void *b); +int CompareAlias(const void *a, const void *b); +void ProcessMailHTTPMessage(struct HTTPConnectionInfo * Session, char * Method, char * URL, char * input, char * Reply, int * RLen, int InputLen, char * Token); +void ProcessChatHTTPMessage(struct HTTPConnectionInfo * Session, char * Method, char * URL, char * input, char * Reply, int * RLen); +struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot); +int SetupNodeMenu(char * Buff, int SYSOP); +int StatusProc(char * Buff); +int ProcessMailSignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, BOOL WebMail, int LOCAL); +int ProcessMailAPISignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, BOOL WebMail, int LOCAL); +int ProcessChatSignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, int LOCAL); +VOID APRSProcessHTTPMessage(SOCKET sock, char * MsgPtr, BOOL LOCAL, BOOL COOKIE); + + +static struct HTTPConnectionInfo * SessionList; // active term mode sessions + +char Mycall[10]; + +char MAILPipeFileName[] = "\\\\.\\pipe\\BPQMAILWebPipe"; +char CHATPipeFileName[] = "\\\\.\\pipe\\BPQCHATWebPipe"; + +char Index[] = "%s's BPQ32 Web Server

" +"" +"" +"
Node PagesAPRS Pages
"; + +char IndexNoAPRS[] = "" +""; + +//char APRSBit[] = "APRS Pages"; + +//char MailBit[] = "Mail Mgmt" +// "WebMail"; +//char ChatBit[] = "Chat Mgmt"; + +char Tail[] = ""; + +char RouteHddr[] = "

Routes

" +""; + +char RouteLine[] = ""; +char xNodeHddr[] = "
" +"
PortCallQualityNode CountFrame CountRetriesPercentMaxframeFrackLast HeardQueuedRem Qual
%s%d%s%c%d%d%d%d%d%%d%d%02d:%02d%d%d
" +"
" +"" +"
" +"

Nodes %s

"; + +char NodeHddr[] = "
" +"" +"" +"
" +"

Nodes %s

"; + +char NodeLine[] = ""; + + +char StatsHddr[] = "

Node Stats

%s:%s
" +""; + +char PortStatsHddr[] = "

Stats for Port %d

"; + +char PortStatsLine[] = ""; + + +char Beacons[] = "

Beacon Configuration for Port %d

You need to be signed in to save changes

%s %d
" +"" +"
" +"" +"" +"" +"" +"" +"
Send Interval (Minutes)
To
Path
Send From File
Text
" +"" + +"

" +""; + + +char LinkHddr[] = "

Links

" +""; + +char LinkLine[] = ""; + +char UserHddr[] = "

Sessions

Far CallOur CallPortax.25 stateLink Typeax.25 Version
%s%s%d%s%s%d
"; + +char UserLine[] = ""; + +char TermSignon[] = "BPQ32 Node %s Terminal Access" +"

BPQ32 Node %s Terminal Access

" +"

Please enter username and password to access the node

" +"" +"
%s%s%s
" +"" +"
User
Password
" +"

" +""; + + +char PassError[] = "

Sorry, User or Password is invalid - please try again

"; + +char BusyError[] = "

Sorry, No sessions available - please try later

"; + +char LostSession[] = "Sorry, Session had been lost - refresh page to sign in again"; +char NoSessions[] = "Sorry, No Sessions available - refresh page to try again"; + +char TermPage[] = "" +"BPQ32 Node %s" +"" +"" +"

BPQ32 Node %s

" +"
" +"

" +"" +"" +""; + +char TermOutput[] = "" +"" +"" +"" +"" +"" +"" +"
\""; + + +// font-family:monospace;background-color:black;color:lawngreen;font-size:12px + +char TermOutputTail[] = "
"; + +/* +char InputLine[] = "" +"
" +"" +"
"; +*/ +char InputLine[] = "" +"
" +"\" id=inp type=text text width=100%% name=input />" +"
"; + +static char NodeSignon[] = "BPQ32 Node SYSOP Access" +"

BPQ32 Node %s SYSOP Access

" +"

This page sets Cookies. Don't continue if you object to this

" +"

Please enter Callsign and Password to access the Node

" +"
" +"" +"" +"
User
Password
" +"

"; + + +static char MailSignon[] = "BPQ32 Mail Server Access" +"

BPQ32 Mail Server %s Access

" +"

Please enter Callsign and Password to access the BBS

" +"
" +"" +"" +"
User
Password
" +"

"; + +static char ChatSignon[] = "BPQ32 Chat Server Access" +"

BPQ32 Chat Server %s Access

" +"

Please enter Callsign and Password to access the Chat Server

" +"
" +"" +"" +"
User
Password
" +"

"; + + +static char MailLostSession[] = "" +"
" +"Sorry, Session had been lost

    " +"
"; + + +static char ConfigEditPage[] = "" +"Edit Config" +"
" +"

" +"
"; + +static char EXCEPTMSG[80] = ""; + + +void UndoTransparency(char * input) +{ + char * ptr1, * ptr2; + char c; + int hex; + + if (input == NULL) + return; + + ptr1 = ptr2 = input; + + // Convert any %xx constructs + + while (1) + { + c = *(ptr1++); + + if (c == 0) + break; + + if (c == '%') + { + c = *(ptr1++); + if(isdigit(c)) + hex = (c - '0') << 4; + else + hex = (tolower(c) - 'a' + 10) << 4; + + c = *(ptr1++); + if(isdigit(c)) + hex += (c - '0'); + else + hex += (tolower(c) - 'a' + 10); + + *(ptr2++) = hex; + } + else if (c == '+') + *(ptr2++) = 32; + else + *(ptr2++) = c; + } + *ptr2 = 0; +} + + + + +VOID PollSession(struct HTTPConnectionInfo * Session) +{ + int state, change; + int count, len; + char Msg[400] = ""; + char Formatted[8192]; + char * ptr1, * ptr2; + char c; + int Line; + + // Poll Node + + SessionState(Session->Stream, &state, &change); + + if (change == 1) + { + int Line = Session->LastLine++; + free(Session->ScreenLines[Line]); + + if (state == 1)// Connected + Session->ScreenLines[Line] = _strdup("*** Connected
\r\n"); + else + Session->ScreenLines[Line] = _strdup("*** Disconnected
\r\n"); + + if (Line == 99) + Session->LastLine = 0; + + Session->Changed = TRUE; + } + + if (RXCount(Session->Stream) > 0) + { + int realLen = 0; + + do + { + GetMsg(Session->Stream, &Msg[0], &len, &count); + + // replace cr with
and space with   + + + ptr1 = Msg; + ptr2 = &Formatted[0]; + + if (Session->PartLine) + { + // Last line was incomplete - append to it + + realLen = Session->PartLine; + + Line = Session->LastLine - 1; + + if (Line < 0) + Line = 99; + + strcpy(Formatted, Session->ScreenLines[Line]); + ptr2 += strlen(Formatted); + + Session->LastLine = Line; + Session->PartLine = FALSE; + } + + while (len--) + { + c = *(ptr1++); + realLen++; + + if (c == 13) + { + int LineLen; + + strcpy(ptr2, "
\r\n"); + + // Write to screen + + Line = Session->LastLine++; + free(Session->ScreenLines[Line]); + + LineLen = (int)strlen(Formatted); + + // if line starts with a colour code, process it + + if (Formatted[0] == 0x1b && LineLen > 1) + { + int ColourCode = Formatted[1] - 10; + COLORREF Colour = Colours[ColourCode]; + char ColString[30]; + + memmove(&Formatted[20], &Formatted[2], LineLen); + sprintf(ColString, "", GetRValue(Colour), GetGValue(Colour), GetBValue(Colour)); + memcpy(Formatted, ColString, 20); + strcat(Formatted, ""); + LineLen =+ 28; + } + + Session->ScreenLineLen[Line] = LineLen; + Session->ScreenLines[Line] = _strdup(Formatted); + + if (Line == 99) + Session->LastLine = 0; + + ptr2 = &Formatted[0]; + realLen = 0; + + } + else if (c == 32) + { + memcpy(ptr2, " ", 6); + ptr2 += 6; + + // Make sure line isn't too long + // but beware of spaces expanded to   - count chars in line + + if ((realLen) > 100) + { + strcpy(ptr2, "
\r\n"); + + Line = Session->LastLine++; + free(Session->ScreenLines[Line]); + + Session->ScreenLines[Line] = _strdup(Formatted); + + if (Line == 99) + Session->LastLine = 0; + + ptr2 = &Formatted[0]; + realLen = 0; + } + } + else if (c == '>') + { + memcpy(ptr2, ">", 4); + ptr2 += 4; + } + else if (c == '<') + { + memcpy(ptr2, "<", 4); + ptr2 += 4; + } + else + *(ptr2++) = c; + + } + + *ptr2 = 0; + + if (ptr2 != &Formatted[0]) + { + // Incomplete line + + // Save to screen + + Line = Session->LastLine++; + free(Session->ScreenLines[Line]); + + Session->ScreenLines[Line] = _strdup(Formatted); + + if (Line == 99) + Session->LastLine = 0; + + Session->PartLine = realLen; + } + + // strcat(Session->ScreenBuffer, Formatted); + Session->Changed = TRUE; + + } while (count > 0); + } +} + + +VOID HTTPTimer() +{ + // Run every tick. Check for status change and data available + + struct HTTPConnectionInfo * Session = SessionList; // active term mode sessions + struct HTTPConnectionInfo * PreviousSession = NULL; + +// inf(); + + while (Session) + { + Session->KillTimer++; + + if (Session->Key[0] != 'T') + { + PreviousSession = Session; + Session = Session->Next; + continue; + } + + if (Session->KillTimer > 3000) // Around 5 mins + { + int i; + int Stream = Session->Stream; + + for (i = 0; i < 100; i++) + { + free(Session->ScreenLines[i]); + } + + SessionControl(Stream, 2, 0); + SessionState(Stream, &i, &i); + DeallocateStream(Stream); + + if (PreviousSession) + PreviousSession->Next = Session->Next; // Remove from chain + else + SessionList = Session->Next; + + free(Session); + + break; + } + + PollSession(Session); + + // if (Session->ResponseTimer == 0 && Session->Changed) + // Debugprintf("Data to send but no outstanding GET"); + + if (Session->ResponseTimer) + { + Session->ResponseTimer--; + + if (Session->ResponseTimer == 0 || Session->Changed) + { + SOCKET sock = Session->sock; + char _REPLYBUFFER[100000]; + int ReplyLen; + char Header[256]; + int HeaderLen; + int Last = Session->LastLine; + int n; + struct TNCINFO * TNC = Session->TNC; + struct TCPINFO * TCP = 0; + + if (TNC) + TCP = TNC->TCPInfo; + + if (TCP && TCP->WebTermCSS) + sprintf(_REPLYBUFFER, TermOutput, TCP->WebTermCSS); + else + sprintf(_REPLYBUFFER, TermOutput, ""); + + for (n = Last;;) + { + if ((strlen(Session->ScreenLines[n]) + strlen(_REPLYBUFFER)) < 99999) + strcat(_REPLYBUFFER, Session->ScreenLines[n]); + + if (n == 99) + n = -1; + + if (++n == Last) + break; + } + + ReplyLen = (int)strlen(_REPLYBUFFER); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", TermOutputTail); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + + Session->ResponseTimer = Session->Changed = 0; + } + } + PreviousSession = Session; + Session = Session->Next; + } +} + +struct HTTPConnectionInfo * AllocateSession(SOCKET sock, char Mode) +{ + time_t KeyVal; + struct HTTPConnectionInfo * Session = zalloc(sizeof(struct HTTPConnectionInfo)); + int i; + + if (Session == NULL) + return NULL; + + if (Mode == 'T') + { + // Terminal + + for (i = 0; i < 20; i++) + Session->ScreenLines[i] = _strdup("Scroll to end
"); + + for (i = 20; i < 100; i++) + Session->ScreenLines[i] = _strdup("
\r\n"); + + Session->Stream = FindFreeStream(); + + if (Session->Stream == 0) + return NULL; + + SessionControl(Session->Stream, 1, 0); + } + + KeyVal = (int)sock * time(NULL); + + sprintf(Session->Key, "%c%012X", Mode, (int)KeyVal); + + if (SessionList) + Session->Next = SessionList; + + SessionList = Session; + + return Session; +} + +struct HTTPConnectionInfo * FindSession(char * Key) +{ + struct HTTPConnectionInfo * Session = SessionList; + + while (Session) + { + if (strcmp(Session->Key, Key) == 0) + return Session; + + Session = Session->Next; + } + + return NULL; +} + +void ProcessTermInput(SOCKET sock, char * MsgPtr, int MsgLen, char * Key) +{ + char _REPLYBUFFER[2048]; + int ReplyLen; + char Header[256]; + int HeaderLen; + int State; + struct HTTPConnectionInfo * Session = FindSession(Key); + int Stream; + int maxlen = 1000; + + + if (Session == NULL) + { + ReplyLen = sprintf(_REPLYBUFFER, "%s", LostSession); + } + else + { + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * end = &MsgPtr[MsgLen]; + int Line = Session->LastLine++; + char * ptr1, * ptr2; + char c; + UCHAR hex; + + int msglen = end - input; + + struct TNCINFO * TNC = Session->TNC; + struct TCPINFO * TCP = 0; + + if (TNC) + TCP = TNC->TCPInfo; + + if (TCP && TCP->WebTermCSS) + maxlen -= strlen(TCP->WebTermCSS); + + if (MsgLen > maxlen) + { + Session->KillTimer = 99999; // close session + return; + } + + + if (TCP && TCP->WebTermCSS) + ReplyLen = sprintf(_REPLYBUFFER, InputLine, Key, TCP->WebTermCSS); + else + ReplyLen = sprintf(_REPLYBUFFER, InputLine, Key, ""); + + + Stream = Session->Stream; + + input += 10; + ptr1 = ptr2 = input; + + // Convert any %xx constructs + + while (ptr1 != end) + { + c = *(ptr1++); + if (c == '%') + { + c = *(ptr1++); + if(isdigit(c)) + hex = (c - '0') << 4; + else + hex = (tolower(c) - 'a' + 10) << 4; + + c = *(ptr1++); + if(isdigit(c)) + hex += (c - '0'); + else + hex += (tolower(c) - 'a' + 10); + + *(ptr2++) = hex; + } + else if (c == '+') + *(ptr2++) = 32; + else + *(ptr2++) = c; + } + + end = ptr2; + + *ptr2 = 0; + + strcat(input, "
\r\n"); + + free(Session->ScreenLines[Line]); + + Session->ScreenLines[Line] = _strdup(input); + + if (Line == 99) + Session->LastLine = 0; + + *end++ = 13; + *end = 0; + + SessionStateNoAck(Stream, &State); + + if (State == 0) + { + char AXCall[10]; + SessionControl(Stream, 1, 0); + if (BPQHOSTVECTOR[Session->Stream -1].HOSTSESSION == NULL) + { + //No L4 sessions free + + ReplyLen = sprintf(_REPLYBUFFER, "%s", NoSessions); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + return; + } + + ConvToAX25(Session->HTTPCall, AXCall); + ChangeSessionCallsign(Stream, AXCall); + if (Session->USER) + BPQHOSTVECTOR[Session->Stream -1].HOSTSESSION->Secure_Session = Session->USER->Secure; + else + Debugprintf("HTTP Term Session->USER is NULL"); + } + + SendMsg(Stream, input, (int)(end - input)); + Session->Changed = TRUE; + Session->KillTimer = 0; + } + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); +} + + +void ProcessTermClose(SOCKET sock, char * MsgPtr, int MsgLen, char * Key, int LOCAL) +{ + char _REPLYBUFFER[8192]; + int ReplyLen = sprintf(_REPLYBUFFER, InputLine, Key, ""); + char Header[256]; + int HeaderLen; + struct HTTPConnectionInfo * Session = FindSession(Key); + + if (Session) + { + Session->KillTimer = 99999; + } + + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n" + "\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); +} + +int ProcessTermSignon(struct TNCINFO * TNC, SOCKET sock, char * MsgPtr, int MsgLen, int LOCAL) +{ + char _REPLYBUFFER[8192]; + int ReplyLen; + char Header[256]; + int HeaderLen; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * user, * password, * Context, * Appl; + char NoApp[] = ""; + struct TCPINFO * TCP = TNC->TCPInfo; + + if (input) + { + int i; + struct UserRec * USER; + + UndoTransparency(input); + + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + goto Sendit; + } + user = strtok_s(&input[9], "&", &Context); + password = strtok_s(NULL, "=", &Context); + password = strtok_s(NULL, "&", &Context); + + Appl = strtok_s(NULL, "=", &Context); + Appl = strtok_s(NULL, "&", &Context); + + if (Appl == 0) + Appl = NoApp; + + if (password == NULL) + { + ReplyLen = sprintf(_REPLYBUFFER, TermSignon, Mycall, Mycall, Appl); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", PassError); + goto Sendit; + } + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if ((strcmp(password, USER->Password) == 0) && + ((_stricmp(user, USER->UserName) == 0 ) || (_stricmp(USER->UserName, "ANON") == 0))) + { + // ok + + struct HTTPConnectionInfo * Session = AllocateSession(sock, 'T'); + + if (Session) + { + char AXCall[10]; + ReplyLen = sprintf(_REPLYBUFFER, TermPage, Mycall, Mycall, Session->Key, Session->Key, Session->Key); + if (_stricmp(USER->UserName, "ANON") == 0) + strcpy(Session->HTTPCall, _strupr(user)); + else + strcpy(Session->HTTPCall, USER->Callsign); + ConvToAX25(Session->HTTPCall, AXCall); + ChangeSessionCallsign(Session->Stream, AXCall); + BPQHOSTVECTOR[Session->Stream -1].HOSTSESSION->Secure_Session = USER->Secure; + Session->USER = USER; + + if (USER->Appl[0]) + SendMsg(Session->Stream, USER->Appl, (int)strlen(USER->Appl)); + } + else + { + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", BusyError); + } + break; + } + } + + if (i == TCP->NumberofUsers) + { + // Not found + + ReplyLen = sprintf(_REPLYBUFFER, TermSignon, Mycall, Mycall, Appl); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", PassError); + } + + } + +Sendit: + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 1; +} + +char * LookupKey(char * Key) +{ + if (strcmp(Key, "##MY_CALLSIGN##") == 0) + { + char Mycall[10]; + memcpy(Mycall, &MYNODECALL, 10); + strlop(Mycall, ' '); + + return _strdup(Mycall); + } + return NULL; +} + + +int ProcessSpecialPage(char * Buffer, int FileSize) +{ + // replaces ##xxx### constructs with the requested data + + char * NewMessage = malloc(100000); + char * ptr1 = Buffer, * ptr2, * ptr3, * ptr4, * NewPtr = NewMessage; + int PrevLen; + int BytesLeft = FileSize; + int NewFileSize = FileSize; + char * StripPtr = ptr1; + + // strip comments blocks + + while (ptr4 = strstr(ptr1, ""); + if (ptr2) + { + PrevLen = (int)(ptr4 - ptr1); + memcpy(StripPtr, ptr1, PrevLen); + StripPtr += PrevLen; + ptr1 = ptr2 + 3; + BytesLeft = (int)(FileSize - (ptr1 - Buffer)); + } + } + + memcpy(StripPtr, ptr1, BytesLeft); + StripPtr += BytesLeft; + + BytesLeft = (int)(StripPtr - Buffer); + + FileSize = BytesLeft; + NewFileSize = FileSize; + ptr1 = Buffer; + ptr1[FileSize] = 0; + +loop: + ptr2 = strstr(ptr1, "##"); + + if (ptr2) + { + PrevLen = (int)(ptr2 - ptr1); // Bytes before special text + + ptr3 = strstr(ptr2+2, "##"); + + if (ptr3) + { + char Key[80] = ""; + int KeyLen; + char * NewText; + int NewTextLen; + + ptr3 += 2; + KeyLen = (int)(ptr3 - ptr2); + + if (KeyLen < 80) + memcpy(Key, ptr2, KeyLen); + + NewText = LookupKey(Key); + + if (NewText) + { + NewTextLen = (int)strlen(NewText); + NewFileSize = NewFileSize + NewTextLen - KeyLen; + // NewMessage = realloc(NewMessage, NewFileSize); + + memcpy(NewPtr, ptr1, PrevLen); + NewPtr += PrevLen; + memcpy(NewPtr, NewText, NewTextLen); + NewPtr += NewTextLen; + + free(NewText); + NewText = NULL; + } + else + { + // Key not found, so just leave + + memcpy(NewPtr, ptr1, PrevLen + KeyLen); + NewPtr += (PrevLen + KeyLen); + } + + ptr1 = ptr3; // Continue scan from here + BytesLeft = (int)(Buffer + FileSize - ptr3); + } + else // Unmatched ## + { + memcpy(NewPtr, ptr1, PrevLen + 2); + NewPtr += (PrevLen + 2); + ptr1 = ptr2 + 2; + } + goto loop; + } + + // Copy Rest + + memcpy(NewPtr, ptr1, BytesLeft); + NewMessage[NewFileSize] = 0; + + strcpy(Buffer, NewMessage); + free(NewMessage); + + return NewFileSize; +} + +int SendMessageFile(SOCKET sock, char * FN, BOOL OnlyifExists, int allowDeflate) +{ + int FileSize = 0, Sent, Loops = 0; + char * MsgBytes; + char MsgFile[512]; + FILE * hFile; + int ReadLen; + BOOL Special = FALSE; + int Len; + int HeaderLen; + char Header[256]; + char TimeString[64]; + char FileTimeString[64]; + struct stat STAT; + char * ptr; + char * Compressed = 0; + char Encoding[] = "Content-Encoding: deflate\r\n"; + char Type[64] = "Content-Type: text/html\r\n"; + +#ifdef WIN32 + + struct _EXCEPTION_POINTERS exinfo; + strcpy(EXCEPTMSG, "SendMessageFile"); + + __try { +#endif + + UndoTransparency(FN); + + if (strstr(FN, "..")) + { + FN[0] = '/'; + FN[1] = 0; + } + + if (strlen(FN) > 256) + { + FN[256] = 0; + Debugprintf("HTTP File Name too long %s", FN); + } + + if (strcmp(FN, "/") == 0) + if (APRSActive) + sprintf(MsgFile, "%s/HTML/index.html", BPQDirectory); + else + sprintf(MsgFile, "%s/HTML/indexnoaprs.html", BPQDirectory); + else + sprintf(MsgFile, "%s/HTML%s", BPQDirectory, FN); + + + // First see if file exists so we can override standard ones in code + + if (stat(MsgFile, &STAT) == 0 && (hFile = fopen(MsgFile, "rb"))) + { + FileSize = STAT.st_size; + + MsgBytes = zalloc(FileSize + 1); + + ReadLen = (int)fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + // ft.QuadPart -= 116444736000000000; + // ft.QuadPart /= 10000000; + + // ctime = ft.LowPart; + + FormatTime3(FileTimeString, STAT.st_ctime); + } + else + { + // See if it is a hard coded file + + MsgBytes = GetStandardPage(&FN[1], &FileSize); + + if (MsgBytes) + { + if (FileSize == 0) + FileSize = strlen(MsgBytes); + + FormatTime3(FileTimeString, 0); + } + else + { + if (OnlyifExists) // Set if we dont want an error response if missing + return -1; + + Len = sprintf(Header, "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nPage not found\r\n"); + send(sock, Header, Len, 0); + return 0; + } + } + + // if HTML file, look for ##...## substitutions + + if ((strcmp(FN, "/") == 0 || strstr(FN, "htm" ) || strstr(FN, "HTM")) && strstr(MsgBytes, "##" )) + { + FileSize = ProcessSpecialPage(MsgBytes, FileSize); + FormatTime3(FileTimeString, time(NULL)); + + } + + FormatTime3(TimeString, time(NULL)); + + ptr = FN; + + while (strchr(ptr, '.')) + { + ptr = strchr(ptr, '.'); + ++ptr; + } + + if (_stricmp(ptr, "js") == 0) + strcpy(Type, "Content-Type: text/javascript\r\n"); + + if (_stricmp(ptr, "css") == 0) + strcpy(Type, "Content-Type: text/css\r\n"); + + if (_stricmp(ptr, "pdf") == 0) + strcpy(Type, "Content-Type: application/pdf\r\n"); + + if (allowDeflate) + { + Compressed = Compressit(MsgBytes, FileSize, &FileSize); + } + else + { + Encoding[0] = 0; + Compressed = MsgBytes; + } + + if (_stricmp(ptr, "jpg") == 0 || _stricmp(ptr, "jpeg") == 0 || _stricmp(ptr, "png") == 0 || + _stricmp(ptr, "gif") == 0 || _stricmp(ptr, "bmp") == 0 || _stricmp(ptr, "ico") == 0) + strcpy(Type, "Content-Type: image\r\n"); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "%s%s" + "\r\n", FileSize, TimeString, FileTimeString, Type, Encoding); + + send(sock, Header, HeaderLen, 0); + + Sent = send(sock, Compressed, FileSize, 0); + + while (Sent != FileSize && Loops++ < 3000) // 100 secs max + { + if (Sent > 0) // something sent + { +// Debugprintf("%d out of %d sent", Sent, FileSize); + FileSize -= Sent; + memmove(Compressed, &Compressed[Sent], FileSize); + } + + Sleep(30); + Sent = send(sock, Compressed, FileSize, 0); + } + +// Debugprintf("%d out of %d sent %d loops", Sent, FileSize, Loops); + + + free (MsgBytes); + if (allowDeflate) + free (Compressed); + +#ifdef WIN32 + } +#include "StdExcept.c" + Debugprintf("Sending FIle %s", FN); +} +#endif + +return 0; +} + +VOID sendandcheck(SOCKET sock, const char * Buffer, int Len) +{ + int Loops = 0; + int Sent = send(sock, Buffer, Len, 0); + char * Copy = NULL; + + while (Sent != Len && Loops++ < 300) // 10 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, Len, Loops); + + if (Copy == NULL) + { + Copy = malloc(Len); + memcpy(Copy, Buffer, Len); + } + + if (Sent > 0) // something sent + { + Len -= Sent; + memmove(Copy, &Copy[Sent], Len); + } + + Sleep(30); + Sent = send(sock, Copy, Len, 0); + } + + if (Copy) + free(Copy); + + return; +} + +int RefreshTermWindow(struct TCPINFO * TCP, struct HTTPConnectionInfo * Session, char * _REPLYBUFFER) +{ + char Msg[400] = ""; + int HeaderLen, ReplyLen; + char Header[256]; + + PollSession(Session); // See if anything received + + if (Session->Changed) + { + int Last = Session->LastLine; + int n; + + if (TCP && TCP->WebTermCSS) + sprintf(_REPLYBUFFER, TermOutput, TCP->WebTermCSS); + else + sprintf(_REPLYBUFFER, TermOutput, ""); + + for (n = Last;;) + { + strcat(_REPLYBUFFER, Session->ScreenLines[n]); + + if (n == 99) + n = -1; + + if (++n == Last) + break; + } + + Session->Changed = 0; + + ReplyLen = (int)strlen(_REPLYBUFFER); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", TermOutputTail); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen); + sendandcheck(Session->sock, Header, HeaderLen); + sendandcheck(Session->sock, _REPLYBUFFER, ReplyLen); + + return 1; + } + else + return 0; +} + +int SetupNodeMenu(char * Buff, int LOCAL) +{ + int Len = 0, i; + struct TNCINFO * TNC; + int top = 0, left = 0; + + char NodeMenuHeader[] = "%s's BPQ32 Web Server" + "" + + "" + "

BPQ32 Node %s

" + "

" + "" + "" + "" + "" + "" + "" + "%s%s%s%s%s%s"; + + char DriverBit[] = "" + ""; + + char APRSBit[] = ""; + + char MailBit[] = "" + ""; + + char ChatBit[] = ""; + char SigninBit[] = ""; + + char NodeTail[] = + "" + "
RoutesNodesPortsLinksUsersStatsTerminalDriver WindowsStream StatusAPRS PagesMail MgmtWebMailChat MgmtSYSOP SigninEdit Config" + "
"; + + + Len = sprintf(Buff, NodeMenuHeader, Mycall); + + for (i=1; i <= MAXBPQPORTS; i++) + { + TNC = TNCInfo[i]; + if (TNC == NULL) + continue; + + if (TNC->WebWindowProc) + { + Len += sprintf(&Buff[Len], NodeMenuLine, i, TNC->WebWinX, TNC->WebWinY, top, left); + top += 22; + left += 22; + } + } + + Len += sprintf(&Buff[Len], NodeMenuRest, Mycall, + DriverBit, + (APRSWeb)?APRSBit:"", + (IncludesMail)?MailBit:"", (IncludesChat)?ChatBit:"", (LOCAL)?"":SigninBit, NodeTail); + + return Len; +} + +VOID SaveConfigFile(SOCKET sock , char * MsgPtr, char * Rest, int LOCAL) +{ + int ReplyLen = 0; + char * ptr, * ptr1, * ptr2, *input; + char c; + int MsgLen, WriteLen = 0; + char inputname[250]="bpq32.cfg"; + FILE *fp1; + char Header[256]; + int HeaderLen; + char Reply[4096]; + char Mess[256]; + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + struct stat STAT; + + input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + + if (input) + { + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + // ReplyLen = sprintf(Reply, "%s", ""); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + return; + } + + ptr = strstr(input, "&Save="); + + if (ptr) + { + *ptr = 0; + + // Undo any % transparency + + ptr1 = ptr2 = input + 8; + + c = *(ptr1++); + + while (c) + { + if (c == '%') + { + int n; + int m = *(ptr1++) - '0'; + if (m > 9) m = m - 7; + n = *(ptr1++) - '0'; + if (n > 9) n = n - 7; + + c = m * 16 + n; + } + else if (c == '+') + c = ' '; + +#ifndef WIN32 + if (c != 13) // Strip CR if Linux +#endif + *(ptr2++) = c; + + c = *(ptr1++); + + } + + *(ptr2++) = 0; + + MsgLen = (int)strlen(input + 8); + + if (ConfigDirectory[0] == 0) + { + strcpy(inputname, "bpq32.cfg"); + } + else + { + strcpy(inputname,ConfigDirectory); + strcat(inputname,"/"); + strcat(inputname, "bpq32.cfg"); + } + + // Make a backup of the config + + // Keep 4 Generations + + strcpy(Backup2, inputname); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, inputname); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, inputname); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, inputname); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); // Move .bak to .bak.1 + + CopyFile(inputname, Backup1, FALSE); // Copy to .bak + + // Get length to compare with new length + + stat(inputname, &STAT); + + fp1 = fopen(inputname, "wb"); + + if (fp1) + { + WriteLen = (int)fwrite(input + 8, 1, MsgLen, fp1); + fclose(fp1); + } + + if (WriteLen != MsgLen) + sprintf_s(Mess, sizeof(Mess), "Failed to write Config File"); + else + sprintf_s(Mess, sizeof(Mess), "Configuration Saved, Orig Length %d New Length %d", + STAT.st_size, MsgLen); + } + + ReplyLen = sprintf(Reply, "", Mess); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + } + return; +} + +// Compress using deflate. Caller must free output buffer after use + +unsigned char * Compressit(unsigned char * In, int Len, int * OutLen) +{ + z_stream defstream; + int maxSize; + unsigned char * Out; + + defstream.zalloc = Z_NULL; + defstream.zfree = Z_NULL; + defstream.opaque = Z_NULL; + + defstream.avail_in = Len; // size of input + defstream.next_in = (Bytef *)In; // input char array + + deflateInit(&defstream, Z_BEST_COMPRESSION); + maxSize = deflateBound(&defstream, Len); + + Out = malloc(maxSize); + + defstream.avail_out = maxSize; // size of output + defstream.next_out = (Bytef *)Out; // output char array + + deflate(&defstream, Z_FINISH); + deflateEnd(&defstream); + + *OutLen = defstream.total_out; + + return Out; +} + + +int InnerProcessHTTPMessage(struct ConnectionInfo * conn) +{ + struct TCPINFO * TCP = conn->TNC->TCPInfo; + SOCKET sock = conn->socket; + char * MsgPtr = conn->InputBuffer; + int MsgLen = conn->InputLen; + int InputLen = 0; + int OutputLen = 0; + int Bufferlen; + struct HTTPConnectionInfo CI; + struct HTTPConnectionInfo * sockptr = &CI; + struct HTTPConnectionInfo * Session = NULL; + + char URL[100000]; + char * ptr; + char * encPtr = 0; + int allowDeflate = 0; + char * Compressed = 0; + char * HostPtr = 0; + + char * Context, * Method, * NodeURL = 0, * Key; + char _REPLYBUFFER[250000]; + char Reply[250000]; + + int ReplyLen = 0; + char Header[256]; + int HeaderLen; + char TimeString[64]; + BOOL LOCAL = FALSE; + BOOL COOKIE = FALSE; + int Len; + char * WebSock = 0; + + char PortsHddr[] = "

Ports

" + ""; + +// char PortLine[] = ""; + + char PortLineWithBeacon[] = "" + "\r\n"; + + char SessionPortLine[] = "" + "\r\n"; + + char PortLineWithDriver[] = "" + "\r\n"; + + + char PortLineWithBeaconAndDriver[] = "" + "" + "\r\n"; + + char RigControlLine[] = "" + "\r\n"; + + + char Encoding[] = "Content-Encoding: deflate\r\n"; + +#ifdef WIN32xx + + struct _EXCEPTION_POINTERS exinfo; + strcpy(EXCEPTMSG, "ProcessHTTPMessage"); + + __try { +#endif + + Len = (int)strlen(MsgPtr); + if (Len > 100000) + return 0; + + strcpy(URL, MsgPtr); + + HostPtr = strstr(MsgPtr, "Host: "); + + WebSock = strstr(MsgPtr, "Upgrade: websocket"); + + if (HostPtr) + { + uint32_t Host; + char Hostname[32]= ""; + struct LOCALNET * LocalNet = conn->TNC->TCPInfo->LocalNets; + + HostPtr += 6; + memcpy(Hostname, HostPtr, 31); + strlop(Hostname, ':'); + Host = inet_addr(Hostname); + + if (strcmp(Hostname, "127.0.0.1") == 0) + LOCAL = TRUE; + else + { + if (conn->sin.sin_family != AF_INET6) + { + while(LocalNet) + { + uint32_t MaskedHost = conn->sin.sin_addr.s_addr & LocalNet->Mask; + if (MaskedHost == LocalNet->Network) + { + LOCAL = 1; + break; + } + LocalNet = LocalNet->Next; + } + } + } + } + + encPtr = stristr(MsgPtr, "Accept-Encoding:"); + + if (encPtr && stristr(encPtr, "deflate")) + allowDeflate = 1; + else + Encoding[0] = 0; + + ptr = strstr(MsgPtr, "BPQSessionCookie=N"); + + if (ptr) + { + COOKIE = TRUE; + Key = ptr + 17; + ptr = strchr(Key, ','); + if (ptr) + { + *ptr = 0; + Session = FindSession(Key); + *ptr = ','; + } + else + { + ptr = strchr(Key, 13); + if (ptr) + { + *ptr = 0; + Session = FindSession(Key); + *ptr = 13; + } + } + } + + if (WebSock) + { + // Websock connection request - Reply and remember state. + + char KeyMsg[128]; + char Webx[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Fixed UID + char Hash[64] = ""; + char * Hash64; // base 64 version + char * ptr; + + //Sec-WebSocket-Key: l622yZS3n+zI+hR6SVWkPw== + + char ReplyMsg[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n" +// "Sec-WebSocket-Protocol: chat\r\n" + "\r\n"; + + ptr = strstr(MsgPtr, "Sec-WebSocket-Key:"); + + if (ptr) + { + ptr += 18; + while (*ptr == ' ') + ptr++; + + memcpy(KeyMsg, ptr, 40); + strlop(KeyMsg, 13); + strlop(KeyMsg, ' '); + strcat(KeyMsg, Webx); + + SHA1PasswordHash(KeyMsg, Hash); + Hash64 = byte_base64_encode(Hash, 20); + + conn->WebSocks = 1; + strlop(&URL[4], ' '); + strcpy(conn->WebURL, &URL[4]); + + ReplyLen = sprintf(Reply, ReplyMsg, Hash64); + + free(Hash64); + goto Returnit; + + } + } + + + ptr = strstr(URL, " HTTP"); + + if (ptr) + *ptr = 0; + + Method = strtok_s(URL, " ", &Context); + + memcpy(Mycall, &MYNODECALL, 10); + strlop(Mycall, ' '); + + + // Look for API messages + + if (_memicmp(Context, "/api/", 5) == 0 || _stricmp(Context, "/api") == 0) + { + char * Compressed; + + // if for mail api process signon here and rearrange url from + // api/v1/mail to mail/api/v1 so it goes to mail handler later + + if (_memicmp(Context, "/api/v1/mail/", 13) == 0) + { + memcpy(MsgPtr, "GET /mail/api/v1/", 17); + + if (memcmp(&Context[13], "login", 5) == 0) + { + ReplyLen = ProcessMailAPISignon(TCP, MsgPtr, "M", Reply, &Session, FALSE, LOCAL); + memcpy(MsgPtr, "GET /mail/api/v1/", 17); + + if (ReplyLen) // Error message + goto Returnit; + } + + memcpy(Context, "/mail/api/v1/", 13); + goto doHeader; + } + else + { + ReplyLen = APIProcessHTTPMessage(_REPLYBUFFER, Method, Context, MsgPtr, LOCAL, COOKIE); + + if (memcmp(_REPLYBUFFER, "HTTP", 4) == 0) + { + // Full Message - just send it + + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + + return 0; + } + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "%s\r\n", ReplyLen, Encoding); + + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + } + + if (_memicmp(Context, "/rhp/", 5) == 0 || _stricmp(Context, "/rhp") == 0) + { + { + ReplyLen = RHPProcessHTTPMessage(conn, _REPLYBUFFER, Method, Context, MsgPtr, LOCAL, COOKIE); + + if (memcmp(_REPLYBUFFER, "HTTP", 4) == 0) + { + // Full Message - just send it + + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + + return 0; + } + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "%s\r\n", ReplyLen, Encoding); + + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + } + + + // APRS process internally + + if (_memicmp(Context, "/APRS/", 6) == 0 || _stricmp(Context, "/APRS") == 0) + { + APRSProcessHTTPMessage(sock, MsgPtr, LOCAL, COOKIE); + return 0; + } + + + if (_stricmp(Context, "/Node/Signon?Node") == 0) + { + char * IContext; + + NodeURL = strtok_s(Context, "?", &IContext); + Key = strtok_s(NULL, "?", &IContext); + + ProcessNodeSignon(sock, TCP, MsgPtr, Key, Reply, &Session, LOCAL); + return 0; + + } + + // If for Mail or Chat, check for a session, and send login screen if necessary + + // Moved here to simplify operation with both internal and external clients + + if (_memicmp(Context, "/Mail/", 6) == 0) + { + int RLen = 0; + char Appl; + char * input; + char * IContext; + + NodeURL = strtok_s(Context, "?", &IContext); + Key = strtok_s(NULL, "?", &IContext); + + if (_stricmp(NodeURL, "/Mail/Signon") == 0) + { + ReplyLen = ProcessMailSignon(TCP, MsgPtr, Key, Reply, &Session, FALSE, LOCAL); + + if (ReplyLen) + { + goto Returnit; + } + +#ifdef LINBPQ + strcpy(Context, "/Mail/Header"); +#else + strcpy(MsgPtr, "POST /Mail/Header"); +#endif + goto doHeader; + } + + if (_stricmp(NodeURL, "/Mail/Lost") == 0) + { + input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + + if (input && strstr(input, "Cancel=Exit")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + RLen = ReplyLen; + goto Returnit; + } + if (Key) + Appl = Key[0]; + + Key = 0; + } + + if (Key == 0 || Key[0] == 0) + { + // No Session + + // if not local send a signon screen, else create a user session + + if (LOCAL || COOKIE) + { + Session = AllocateSession(sock, 'M'); + + if (Session) + { + strcpy(Context, "/Mail/Header"); + goto doHeader; + } + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + RLen = ReplyLen; + + goto Returnit; + + } + + ReplyLen = sprintf(Reply, MailSignon, Mycall, Mycall); + + RLen = ReplyLen; + goto Returnit; + } + + Session = FindSession(Key); + + + if (Session == NULL && _memicmp(Context, "/Mail/API/", 10) != 0) + { + ReplyLen = sprintf(Reply, MailLostSession, Key); + RLen = ReplyLen; + goto Returnit; + } + } + + if (_memicmp(Context, "/Chat/", 6) == 0) + { + int RLen = 0; + char Appl; + char * input; + char * IContext; + + + HostPtr = strstr(MsgPtr, "Host: "); + + if (HostPtr) + { + uint32_t Host; + char Hostname[32]= ""; + struct LOCALNET * LocalNet = conn->TNC->TCPInfo->LocalNets; + + HostPtr += 6; + memcpy(Hostname, HostPtr, 31); + strlop(Hostname, ':'); + Host = inet_addr(Hostname); + + if (strcmp(Hostname, "127.0.0.1") == 0) + LOCAL = TRUE; + else while(LocalNet) + { + uint32_t MaskedHost = Host & LocalNet->Mask; + if (MaskedHost == LocalNet->Network) + { + char * rest; + LOCAL = 1; + rest = strchr(HostPtr, 13); + if (rest) + { + memmove(HostPtr + 9, rest, strlen(rest) + 1); + memcpy(HostPtr, "127.0.0.1", 9); + break; + } + } + LocalNet = LocalNet->Next; + } + } + + NodeURL = strtok_s(Context, "?", &IContext); + Key = strtok_s(NULL, "?", &IContext); + + if (_stricmp(NodeURL, "/Chat/Signon") == 0) + { + ReplyLen = ProcessChatSignon(TCP, MsgPtr, Key, Reply, &Session, LOCAL); + + if (ReplyLen) + { + goto Returnit; + } + +#ifdef LINBPQ + strcpy(Context, "/Chat/Header"); +#else + strcpy(MsgPtr, "POST /Chat/Header"); +#endif + goto doHeader; + } + + if (_stricmp(NodeURL, "/Chat/Lost") == 0) + { + input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + + if (input && strstr(input, "Cancel=Exit")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + RLen = ReplyLen; + goto Returnit; + } + if (Key) + Appl = Key[0]; + + Key = 0; + } + + if (Key == 0 || Key[0] == 0) + { + // No Session + + // if not local send a signon screen, else create a user session + + if (LOCAL || COOKIE) + { + Session = AllocateSession(sock, 'C'); + + if (Session) + { + strcpy(Context, "/Chat/Header"); + goto doHeader; + } + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + RLen = ReplyLen; + + goto Returnit; + + } + + ReplyLen = sprintf(Reply, ChatSignon, Mycall, Mycall); + + RLen = ReplyLen; + goto Returnit; + } + + Session = FindSession(Key); + + if (Session == NULL) + { + int Sent, Loops = 0; + ReplyLen = sprintf(Reply, MailLostSession, Key); + RLen = ReplyLen; +Returnit: + if (memcmp(Reply, "HTTP", 4) == 0) + { + // Full Header provided by appl - just send it + + // Send may block + + Sent = send(sock, Reply, ReplyLen, 0); + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(Reply, &Reply[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sock, Reply, ReplyLen, 0); + } + + return 0; + } + + if (NodeURL && _memicmp(NodeURL, "/mail/api/", 10) != 0) + { + // Add tail + + strcpy(&Reply[ReplyLen], Tail); + ReplyLen += strlen(Tail); + } + + // compress if allowed + + if (allowDeflate) + Compressed = Compressit(Reply, ReplyLen, &ReplyLen); + else + Compressed = Reply; + + if (NodeURL && _memicmp(NodeURL, "/mail/api/", 10) == 0) + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: application/json\r\nConnection: close\r\n%s\r\n", ReplyLen, Encoding); + else + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n%s\r\n", ReplyLen, Encoding); + + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + } + +doHeader: + +#ifdef LINBPQ + + if ((_memicmp(Context, "/MAIL/", 6) == 0) || (_memicmp(Context, "/WebMail", 8) == 0)) + { + char _REPLYBUFFER[250000]; + struct HTTPConnectionInfo Dummy = {0}; + int Sent, Loops = 0; + char token[16] = ""; + + // look for auth header + + const char * auth_header = "Authorization: Bearer "; + char * token_begin = strstr(MsgPtr, auth_header); + int Flags = 0, n; + + char * Tok; + char * param; + + if (token_begin) + { + // Using Auth Header + + // Extract the token from the request (assuming it's present in the request headers) + + token_begin += strlen(auth_header); // Move to the beginning of the token + strncpy(token, token_begin, 13); + token[13] = '\0'; // Null-terminate the token + } + + ReplyLen = 0; + + if (Session == 0) + Session = &Dummy; + + if (LOCAL) + Session->TNC = (void *)1; // TNC only used for Web Terminal Sessions + else + Session->TNC = (void *)0; + + ProcessMailHTTPMessage(Session, Method, Context, MsgPtr, _REPLYBUFFER, &ReplyLen, MsgLen, token); + + if (Context && _memicmp(Context, "/mail/api/", 10) == 0) + { + if (memcmp(_REPLYBUFFER, "HTTP", 4) == 0) + { + // Full Header provided by appl - just send it + + // Send may block + + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(_REPLYBUFFER, &_REPLYBUFFER[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + } + return 0; + } + + // compress if allowed + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: application/json\r\nConnection: close\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + + if (memcmp(_REPLYBUFFER, "HTTP", 4) == 0) + { + // Full Header provided by appl - just send it + + // Send may block + + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(_REPLYBUFFER, &_REPLYBUFFER[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + } + return 0; + } + + if (Context && _memicmp(Context, "/mail/api/", 10) != 0) + { + + // Add tail + + strcpy(&_REPLYBUFFER[ReplyLen], Tail); + ReplyLen += strlen(Tail); + + } + + // compress if allowed + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = Reply; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + + +/* + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + + // Send may block + + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + + if (Sent == -1) + return 0; + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(_REPLYBUFFER, &_REPLYBUFFER[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sock, _REPLYBUFFER, ReplyLen, 0); + } + + send(sock, Tail, (int)strlen(Tail), 0); + return 0; +*/ + } + + if (_memicmp(Context, "/CHAT/", 6) == 0) + { + char _REPLYBUFFER[100000]; + + ReplyLen = 0; + + ProcessChatHTTPMessage(Session, Method, Context, MsgPtr, _REPLYBUFFER, &ReplyLen); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 0; + + } + + + /* + Sent = send(sock, _REPLYBUFFER, InputLen, 0); + + while (Sent != InputLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(_REPLYBUFFER, &_REPLYBUFFER[Sent], InputLen); + } + + Sleep(30); + Sent = send(sock, _REPLYBUFFER, InputLen, 0); + } + return 0; + } + */ +#else + + // Pass to MailChat if active + + NodeURL = Context; + + if ((_memicmp(Context, "/MAIL/", 6) == 0) || (_memicmp(Context, "/WebMail", 8) == 0)) + { + // If for Mail, Pass to Mail Server via Named Pipe + + HANDLE hPipe; + + hPipe = CreateFile(MAILPipeFileName, GENERIC_READ | GENERIC_WRITE, + 0, // exclusive access + NULL, // no security attrs + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if (hPipe == (HANDLE)-1) + { + InputLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 28\r\n\r\nMail Data is not available\r\n"); + send(sock, Reply, InputLen, 0); + } + else + { + // int Sent; + int Loops = 0; + struct HTTPConnectionInfo Dummy = {0}; + + if (Session == 0) + Session = &Dummy; + + Session->TNC = LOCAL; // TNC is only used on Web Terminal Sessions so can reuse as LOCAL flag + + WriteFile(hPipe, Session, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + WriteFile(hPipe, MsgPtr, MsgLen, &InputLen, NULL); + + + ReadFile(hPipe, Session, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + ReadFile(hPipe, Reply, 250000, &ReplyLen, NULL); + if (ReplyLen <= 0) + { + InputLen = GetLastError(); + } + + CloseHandle(hPipe); + goto Returnit; + } + return 0; + } + + if (_memicmp(Context, "/CHAT/", 6) == 0) + { + HANDLE hPipe; + + hPipe = CreateFile(CHATPipeFileName, GENERIC_READ | GENERIC_WRITE, + 0, // exclusive access + NULL, // no security attrs + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if (hPipe == (HANDLE)-1) + { + InputLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 28\r\n\r\nChat Data is not available\r\n"); + send(sock, Reply, InputLen, 0); + } + else + { + // int Sent; + int Loops = 0; + + WriteFile(hPipe, Session, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + WriteFile(hPipe, MsgPtr, MsgLen, &InputLen, NULL); + + + ReadFile(hPipe, Session, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + ReadFile(hPipe, Reply, 100000, &ReplyLen, NULL); + if (ReplyLen <= 0) + { + InputLen = GetLastError(); + } + + CloseHandle(hPipe); + goto Returnit; + } + return 0; + } + +#endif + + NodeURL = strtok_s(NULL, "?", &Context); + + if (NodeURL == NULL) + return 0; + + if (strcmp(Method, "POST") == 0) + { + if (_stricmp(NodeURL, "/Node/freqOffset") == 0) + { + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + int port = atoi(Context); + + if (input == 0) + return 1; + + input += 4; + + if (port > 0 && port <= MaxBPQPortNo) + { + struct TNCINFO * TNC = TNCInfo[port]; + char value[6]; + + if (TNC == 0) + return 1; + + TNC->TXOffset = atoi(input); +#ifdef WIN32 + sprintf(value, "%d", TNC->TXOffset); + MySetWindowText(TNC->xIDC_TXTUNEVAL, value); + SendMessage(TNC->xIDC_TXTUNE, TBM_SETPOS, (WPARAM) TRUE, (LPARAM) TNC->TXOffset); // min. & max. positions + +#endif + } + return 1; + } + + if (_stricmp(NodeURL, "/Node/PortAction") == 0) + { + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + int port = atoi(Context); + + if (input == 0) + return 1; + + input += 4; + + if (port > 0 && port <= MaxBPQPortNo) + { + struct TNCINFO * TNC = TNCInfo[port]; + + if (TNC == 0) + return 1; + + if (LOCAL == FALSE && COOKIE == FALSE) + return 1; + + if (strcmp(input, "Abort") == 0) + { + if (TNC->ForcedCloseProc) + TNC->ForcedCloseProc(TNC, 0); + } + else if (strcmp(input, "Kill") == 0) + { + TNC->DontRestart = TRUE; + KillTNC(TNC); + } + else if (strcmp(input, "KillRestart") == 0) + { + TNC->DontRestart = FALSE; + KillTNC(TNC); + RestartTNC(TNC); + + } + } + return 1; + } + + if (_stricmp(NodeURL, "/TermInput") == 0) + { + ProcessTermInput(sock, MsgPtr, MsgLen, Context); + return 0; + } + + if (_stricmp(NodeURL, "/Node/TermSignon") == 0) + { + ProcessTermSignon(conn->TNC, sock, MsgPtr, MsgLen, LOCAL); + } + + if (_stricmp(NodeURL, "/Node/Signon") == 0) + { + ProcessNodeSignon(sock, TCP, MsgPtr, Key, Reply, &Session, LOCAL); + return 0; + } + + if (_stricmp(NodeURL, "/Node/TermClose") == 0) + { + ProcessTermClose(sock, MsgPtr, MsgLen, Context, LOCAL); + return 0; + } + + if (_stricmp(NodeURL, "/Node/BeaconAction") == 0) + { + char Header[256]; + int HeaderLen; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + int Port; + char Param[100]; +#ifndef LINBPQ + int retCode, disp; + char Key[80]; + HKEY hKey; +#endif + struct PORTCONTROL * PORT; + int Slot = 0; + + + if (LOCAL == FALSE && COOKIE == FALSE) + { + // Send Not Authorized + + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "
Not authorized - please sign in"); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 1; + } + + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 1; + } + + GetParam(input, "Port=", &Param[0]); + Port = atoi(&Param[0]); + PORT = GetPortTableEntryFromPortNum(Port); // Need slot not number + if (PORT) + Slot = PORT->PortSlot; + + GetParam(input, "Every=", &Param[0]); + Interval[Slot] = atoi(&Param[0]); + + GetParam(input, "Dest=", &Param[0]); + _strupr(Param); + strcpy(UIUIDEST[Slot], &Param[0]); + + GetParam(input, "Path=", &Param[0]); + _strupr(Param); + if (UIUIDigi[Slot]) + free(UIUIDigi[Slot]); + UIUIDigi[Slot] = _strdup(&Param[0]); + + GetParam(input, "File=", &Param[0]); + strcpy(FN[Slot], &Param[0]); + GetParam(input, "Text=", &Param[0]); + strcpy(Message[Slot], &Param[0]); + + MinCounter[Slot] = Interval[Slot]; + + SendFromFile[Slot] = 0; + + if (FN[Slot][0]) + SendFromFile[Slot] = 1; + + SetupUI(Slot); + +#ifdef LINBPQ + SaveUIConfig(); +#else + SaveUIConfig(); + + wsprintf(Key, "SOFTWARE\\G8BPQ\\BPQ32\\UIUtil\\UIPort%d", Port); + + retCode = RegCreateKeyEx(REGTREE, + Key, 0, 0, 0, KEY_ALL_ACCESS, NULL, &hKey, &disp); + + if (retCode == ERROR_SUCCESS) + { + retCode = RegSetValueEx(hKey, "UIDEST", 0, REG_SZ,(BYTE *)&UIUIDEST[Port][0], strlen(&UIUIDEST[Port][0])); + retCode = RegSetValueEx(hKey, "FileName", 0, REG_SZ,(BYTE *)&FN[Port][0], strlen(&FN[Port][0])); + retCode = RegSetValueEx(hKey, "Message", 0, REG_SZ,(BYTE *)&Message[Port][0], strlen(&Message[Port][0])); + retCode = RegSetValueEx(hKey, "Interval", 0, REG_DWORD,(BYTE *)&Interval[Port], 4); + retCode = RegSetValueEx(hKey, "SendFromFile", 0, REG_DWORD,(BYTE *)&SendFromFile[Port], 4); + retCode = RegSetValueEx(hKey, "Digis",0, REG_SZ, UIUIDigi[Port], strlen(UIUIDigi[Port])); + + RegCloseKey(hKey); + } +#endif + if (strstr(input, "Test=Test")) + SendUIBeacon(Slot); + + + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], Beacons, Port, + Interval[Slot], &UIUIDEST[Slot][0], &UIUIDigi[Slot][0], &FN[Slot][0], &Message[Slot][0], Port); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 1; + } + + if (_stricmp(NodeURL, "/Node/CfgSave") == 0) + { + // Save Config File + + SaveConfigFile(sock, MsgPtr, Key, LOCAL); + return 0; + } + + if (_stricmp(NodeURL, "/Node/LogAction") == 0) + { + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 1; + } + + + if (_stricmp(NodeURL, "/Node/ARDOPAbort") == 0) + { + int port = atoi(Context); + + if (port > 0 && port <= MaxBPQPortNo) + { + struct TNCINFO * TNC = TNCInfo[port]; + + if (TNC && TNC->ForcedCloseProc) + TNC->ForcedCloseProc(TNC, 0); + + + if (TNC && TNC->WebWindowProc) + ReplyLen = TNC->WebWindowProc(TNC, _REPLYBUFFER, LOCAL); + + + ReplyLen = sprintf(Reply, "", "Ok"); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + // goto SendResp; + + // HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + strlen(Tail)); + // send(sock, Header, HeaderLen, 0); + // send(sock, _REPLYBUFFER, ReplyLen, 0); + // send(sock, Tail, strlen(Tail), 0); + + return 1; + } + + } + + send(sock, _REPLYBUFFER, InputLen, 0); + return 0; + } + + if (_stricmp(NodeURL, "/") == 0 || _stricmp(NodeURL, "/Index.html") == 0) + { + // Send if present, else use default + + Bufferlen = SendMessageFile(sock, NodeURL, TRUE, allowDeflate); // return -1 if not found + + if (Bufferlen != -1) + return 0; // We've sent it + else + { + if (APRSApplConnected) + ReplyLen = sprintf(_REPLYBUFFER, Index, Mycall, Mycall); + else + ReplyLen = sprintf(_REPLYBUFFER, IndexNoAPRS, Mycall, Mycall); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 0; + } + } + + else if (_stricmp(NodeURL, "/NodeMenu.html") == 0 || _stricmp(NodeURL, "/Node/NodeMenu.html") == 0) + { + // Send if present, else use default + + char Menu[] = "/NodeMenu.html"; + + Bufferlen = SendMessageFile(sock, Menu, TRUE, allowDeflate); // return -1 if not found + + if (Bufferlen != -1) + return 0; // We've sent it + } + + else if (_memicmp(NodeURL, "/aisdata.txt", 12) == 0) + { + char * Compressed; + ReplyLen = GetAISPageInfo(_REPLYBUFFER, 1, 1); + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: Text\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + + else if (_memicmp(NodeURL, "/aprsdata.txt", 13) == 0) + { + char * Compressed; + char * ptr; + double N, S, W, E; + int aprs = 1, ais = 1, adsb = 1; + + ptr = &NodeURL[14]; + + N = atof(ptr); + ptr = strlop(ptr, '|'); + S = atof(ptr); + ptr = strlop(ptr, '|'); + W = atof(ptr); + ptr = strlop(ptr, '|'); + E = atof(ptr); + ptr = strlop(ptr, '|'); + if (ptr) + { + aprs = atoi(ptr); + ptr = strlop(ptr, '|'); + ais = atoi(ptr); + ptr = strlop(ptr, '|'); + adsb = atoi(ptr); + } + ReplyLen = GetAPRSPageInfo(_REPLYBUFFER, N, S, W, E, aprs, ais, adsb); + + if (ReplyLen < 240000) + ReplyLen += GetAISPageInfo(&_REPLYBUFFER[ReplyLen], ais, adsb); + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: %d\r\nContent-Type: Text\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + + else if (_memicmp(NodeURL, "/portstats.txt", 15) == 0) + { + char * Compressed; + char * ptr; + int port; + struct PORTCONTROL * PORT; + + ptr = &NodeURL[15]; + + port = atoi(ptr); + + PORT = GetPortTableEntryFromPortNum(port); + + ReplyLen = 0; + + if (PORT && PORT->TX) + { + // We send the last 24 hours worth of data. Buffer is cyclic so oldest byte is at StatsPointer + + int first = PORT->StatsPointer; + int firstlen = 1440 - first; + + memcpy(&_REPLYBUFFER[0], &PORT->TX[first], firstlen); + memcpy(&_REPLYBUFFER[firstlen], PORT->TX, first); + + memcpy(&_REPLYBUFFER[1440], &PORT->BUSY[first], firstlen); + memcpy(&_REPLYBUFFER[1440 + firstlen], PORT->BUSY, first); + + ReplyLen = 2880; + } + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: %d\r\nContent-Type: Text\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + + + else if (_memicmp(NodeURL, "/Icon", 5) == 0 && _memicmp(&NodeURL[10], ".png", 4) == 0) + { + // APRS internal Icon + + char * Compressed; + + ReplyLen = GetAPRSIcon(_REPLYBUFFER, NodeURL); + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: Text\r\n%s\r\n", ReplyLen, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + + } + + else if (_memicmp(NodeURL, "/NODE/", 6)) + { + // Not Node, See if a local file + + Bufferlen = SendMessageFile(sock, NodeURL, FALSE, allowDeflate); // Send error if not found + return 0; + } + + // Node URL + + { + + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + + if (_stricmp(NodeURL, "/Node/webproc.css") == 0) + { + char WebprocCSS[] = + ".dropbtn {position: relative; border: 1px solid black;padding:1px;}\r\n" + ".dropdown {position: relative; display: inline-block;}\r\n" + ".dropdown-content {display: none; position: absolute;background-color: #ccc; " + "min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,00.2); z-index: 1;}\r\n" + ".dropdown-content a {color: black; padding: 1px 1px;text-decoration:none;display:block;}" + ".dropdown-content a:hover {background-color: #dddfff;}\r\n" + ".dropdown:hover .dropdown-content {display: block;}\r\n" + ".dropdown:hover .dropbtn {background-color: #ddd;}\r\n" + "input.btn:active {background:black;color:white;}\r\n" + "submit.btn:active {background:black;color:white;}\r\n"; + ReplyLen = sprintf(_REPLYBUFFER, "%s", WebprocCSS); + } + + else if (_stricmp(NodeURL, "/Node/Killandrestart") == 0) + { + int port = atoi(Context); + + if (port > 0 && port <= MaxBPQPortNo) + { + struct TNCINFO * TNC = TNCInfo[port]; + + KillTNC(TNC); + TNC->DontRestart = FALSE; + RestartTNC(TNC); + + if (TNC && TNC->WebWindowProc) + ReplyLen = TNC->WebWindowProc(TNC, _REPLYBUFFER, LOCAL); + + } + } + + else if (_stricmp(NodeURL, "/Node/Port") == 0 || _stricmp(NodeURL, "/Node/ARDOPAbort") == 0) + { + int port = atoi(Context); + + if (port > 0 && port <= MaxBPQPortNo) + { + struct TNCINFO * TNC = TNCInfo[port]; + + if (TNC && TNC->WebWindowProc) + ReplyLen = TNC->WebWindowProc(TNC, _REPLYBUFFER, LOCAL); + } + + } + + else if (_stricmp(NodeURL, "/Node/Streams") == 0) + { + ReplyLen = StatusProc(_REPLYBUFFER); + } + + else if (_stricmp(NodeURL, "/Node/Stats.html") == 0) + { + struct tm * TM; + char UPTime[50]; + time_t szClock = STATSTIME * 60; + + TM = gmtime(&szClock); + + sprintf(UPTime, "%.2d:%.2d:%.2d", TM->tm_yday, TM->tm_hour, TM->tm_min); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", StatsHddr); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "Version", VersionString); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "Uptime (Days Hours Mins)", UPTime); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "Semaphore: Get-Rel/Clashes", Semaphore.Gets - Semaphore.Rels, Semaphore.Clashes); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "Buffers: Max/Cur/Min/Out/Wait", MAXBUFFS, QCOUNT, MINBUFFCOUNT, NOBUFFCOUNT, BUFFERWAITS); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "Known Nodes/Max Nodes", NUMBEROFNODES, MAXDESTS); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "L4 Connects Sent/Rxed ", L4CONNECTSOUT, L4CONNECTSIN); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "L4 Frames TX/RX/Resent/Reseq", L4FRAMESTX, L4FRAMESRX, L4FRAMESRETRIED, OLDFRAMES); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + "L3 Frames Relayed", L3FRAMES); + + } + + else if (_stricmp(NodeURL, "/Node/RigControl.html") == 0) + { + char Test[] = + "\r\n" + "Rigcontrol\r\n" + "\r\n" + "\r\n" + "\r\n" + "
Waiting for data...
\r\n" + "\r\n"; + + + char NoRigCtl[] = + "\r\n" + "Rigcontrol\r\n" + "\r\n" + "\r\n" + "
RigControl Not Configured...
\r\n" + "\r\n"; + + if (RigWebPage) + ReplyLen = sprintf(_REPLYBUFFER, "%s", Test); + else + ReplyLen = sprintf(_REPLYBUFFER, "%s", NoRigCtl); + } + + else if (_stricmp(NodeURL, "/Node/ShowLog.html") == 0) + { + char ShowLogPage[] = + "" + "" + "Log Display" + "" + "
" + "
" +// "" + "" + "" + "
"; + + char * _REPLYBUFFER; + int ReplyLen; + char Header[256]; + int HeaderLen; + char * CfgBytes; + int CfgLen; + char inputname[250]; + FILE *fp1; + struct stat STAT; + char DummyKey[] = "DummyKey"; + time_t T; + struct tm * tm; + char Name[64] = ""; + + T = time(NULL); + tm = gmtime(&T); + + if (LOCAL == FALSE && COOKIE == FALSE) + { + // Send Not Authorized + + char _REPLYBUFFER[4096]; + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "
Not authorized - please sign in"); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + return 1; + } + + if (COOKIE == FALSE) + Key = DummyKey; + + if (memcmp(Context, "date=", 5) == 0) + { + memset(tm, 0, sizeof(struct tm)); + tm->tm_year = atoi(&Context[5]) - 1900; + tm->tm_mon = atoi(&Context[10]) - 1; + tm->tm_mday = atoi(&Context[13]); + } + + + + if (strcmp(Context, "input=Back") == 0) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + return 1; + } + + if (LogDirectory[0] == 0) + { + strcpy(inputname, "logs/"); + } + else + { + strcpy(inputname,LogDirectory); + strcat(inputname,"/"); + strcat(inputname, "/logs/"); + } + + if (strstr(Context, "CMS")) + { + sprintf(Name, "CMSAccess_%04d%02d%02d.log", + tm->tm_year +1900, tm->tm_mon+1, tm->tm_mday); + } + else if (strstr(Context, "Debug")) + { + sprintf(Name, "log_%02d%02d%02d_DEBUG.txt", + tm->tm_year - 100, tm->tm_mon+1, tm->tm_mday); + } + else if (strstr(Context, "BBS")) + { + sprintf(Name, "log_%02d%02d%02d_BBS.txt", + tm->tm_year - 100, tm->tm_mon+1, tm->tm_mday); + } + else if (strstr(Context, "Chat")) + { + sprintf(Name, "log_%02d%02d%02d_CHAT.txt", + tm->tm_year - 100, tm->tm_mon+1, tm->tm_mday); + } + else if (strstr(Context, "Telnet")) + { + sprintf(Name, "Telnet_%02d%02d%02d.log", + tm->tm_year - 100, tm->tm_mon+1, tm->tm_mday); + } + + strcat(inputname, Name); + + if (stat(inputname, &STAT) == -1) + { + CfgBytes = malloc(256); + sprintf(CfgBytes, "Log %s not found", inputname); + CfgLen = strlen(CfgBytes); + } + else + { + fp1 = fopen(inputname, "rb"); + + if (fp1 == 0) + { + CfgBytes = malloc(256); + sprintf(CfgBytes, "Log %s not found", inputname); + CfgLen = strlen(CfgBytes); + } + else + { + CfgLen = STAT.st_size; + + CfgBytes = malloc(CfgLen + 1); + + CfgLen = (int)fread(CfgBytes, 1, CfgLen, fp1); + CfgBytes[CfgLen] = 0; + } + } + + _REPLYBUFFER = malloc(CfgLen + 1000); + + ReplyLen = sprintf(_REPLYBUFFER, ShowLogPage, CfgBytes); + free (CfgBytes); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + sendandcheck(sock, Tail, (int)strlen(Tail)); + free (_REPLYBUFFER); + + return 1; + } + + else if (_stricmp(NodeURL, "/Node/EditCfg.html") == 0) + { + char * _REPLYBUFFER; + int ReplyLen; + char Header[256]; + int HeaderLen; + char * CfgBytes; + int CfgLen; + char inputname[250]="bpq32.cfg"; + FILE *fp1; + struct stat STAT; + char DummyKey[] = "DummyKey"; + + if (LOCAL == FALSE && COOKIE == FALSE) + { + // Send Not Authorized + + char _REPLYBUFFER[4096]; + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "
Not authorized - please sign in"); + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + send(sock, Header, HeaderLen, 0); + send(sock, _REPLYBUFFER, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + return 1; + } + + if (COOKIE ==FALSE) + Key = DummyKey; + + if (ConfigDirectory[0] == 0) + { + strcpy(inputname, "bpq32.cfg"); + } + else + { + strcpy(inputname,ConfigDirectory); + strcat(inputname,"/"); + strcat(inputname, "bpq32.cfg"); + } + + + if (stat(inputname, &STAT) == -1) + { + CfgBytes = _strdup("Config File not found"); + } + else + { + fp1 = fopen(inputname, "rb"); + + if (fp1 == 0) + { + CfgBytes = _strdup("Config File not found"); + } + else + { + CfgLen = STAT.st_size; + + CfgBytes = malloc(CfgLen + 1); + + CfgLen = (int)fread(CfgBytes, 1, CfgLen, fp1); + CfgBytes[CfgLen] = 0; + } + } + + _REPLYBUFFER = malloc(CfgLen + 1000); + + ReplyLen = sprintf(_REPLYBUFFER, ConfigEditPage, Key, CfgBytes); + free (CfgBytes); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", ReplyLen + (int)strlen(Tail)); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + sendandcheck(sock, Tail, (int)strlen(Tail)); + free (_REPLYBUFFER); + + return 1; + } + + + + if (_stricmp(NodeURL, "/Node/PortBeacons") == 0) + { + char * PortChar = strtok_s(NULL, "&", &Context); + int PortNo = atoi(PortChar); + struct PORTCONTROL * PORT; + int PortSlot = 0; + + PORT = GetPortTableEntryFromPortNum(PortNo); // Need slot not number + if (PORT) + PortSlot = PORT->PortSlot; + + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], Beacons, PortNo, + Interval[PortSlot], &UIUIDEST[PortSlot][0], &UIUIDigi[PortSlot][0], &FN[PortSlot][0], &Message[PortSlot][0], PortNo); + } + + + + if (_stricmp(NodeURL, "/Node/PortStats") == 0) + { + struct _EXTPORTDATA * Port; + + char * PortChar = strtok_s(NULL, "&", &Context); + int PortNo = atoi(PortChar); + int Protocol; + int PortType; + + // char PORTTYPE; // H/W TYPE + // 0 = ASYNC, 2 = PC120, 4 = DRSI + // 6 = TOSH, 8 = QUAD, 10 = RLC100 + // 12 = RLC400 14 = INTERNAL 16 = EXTERNAL + +#define KISS 0 +#define NETROM 2 +#define HDLC 6 +#define L2 8 +#define WINMOR 10 + + + // char PROTOCOL; // PORT PROTOCOL + // 0 = KISS, 2 = NETROM, 4 = BPQKISS + //; 6 = HDLC, 8 = L2 + + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsHddr, PortNo); + + Port = (struct _EXTPORTDATA *)GetPortTableEntryFromPortNum(PortNo); + + if (Port == NULL) + { + ReplyLen = sprintf(_REPLYBUFFER, "Invalid Port"); + goto SendResp; + } + + Protocol = Port->PORTCONTROL.PROTOCOL; + PortType = Port->PORTCONTROL.PROTOCOL; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Frames Digied", Port->PORTCONTROL.L2DIGIED); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Frames Heard", Port->PORTCONTROL.L2FRAMES); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Frames Received", Port->PORTCONTROL.L2FRAMESFORUS); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Frames Sent", Port->PORTCONTROL.L2FRAMESSENT); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Timeouts", Port->PORTCONTROL.L2TIMEOUTS); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "REJ Frames Received", Port->PORTCONTROL.L2REJCOUNT); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "RX out of Seq", Port->PORTCONTROL.L2OUTOFSEQ); + // ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "L2 Resequenced", Port->PORTCONTROL.L2RESEQ); + if (Protocol == HDLC) + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "Underrun", Port->PORTCONTROL.L2URUNC); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "RX Overruns", Port->PORTCONTROL.L2ORUNC); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "RX CRC Errors", Port->PORTCONTROL.RXERRORS); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "Frames abandoned", Port->PORTCONTROL.L1DISCARD); + } + else if ((Protocol == KISS && Port->PORTCONTROL.KISSFLAGS) || Protocol == NETROM) + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "Poll Timeout", Port->PORTCONTROL.L2URUNC); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "RX CRC Errors", Port->PORTCONTROL.RXERRORS); + } + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "FRMRs Sent", Port->PORTCONTROL.L2FRMRTX); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortStatsLine, "FRMRs Received", Port->PORTCONTROL.L2FRMRRX); + + // DB 'Link Active % ' + // DD AVSENDING + + } + + if (_stricmp(NodeURL, "/Node/Ports.html") == 0) + { + struct _EXTPORTDATA * ExtPort; + struct PORTCONTROL * Port; + + int count; + char DLL[20]; + char StatsURL[64]; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", PortsHddr); + + for (count = 1; count <= NUMBEROFPORTS; count++) + { + Port = GetPortTableEntryFromSlot(count); + ExtPort = (struct _EXTPORTDATA *)Port; + + // see if has a stats page + + if (Port->AVACTIVE) + sprintf(StatsURL, " Stats Graph", Port->PORTNUMBER); + else + StatsURL[0] = 0; + + if (Port->PORTTYPE == 0x10) + { + strcpy(DLL, ExtPort->PORT_DLL_NAME); + strlop(DLL, '.'); + } + else if (Port->PORTTYPE == 0) + strcpy(DLL, "ASYNC"); + + else if (Port->PORTTYPE == 22) + strcpy(DLL, "I2C"); + + else if (Port->PORTTYPE == 14) + strcpy(DLL, "INTERNAL"); + + else if (Port->PORTTYPE > 0 && Port->PORTTYPE < 14) + strcpy(DLL, "HDLC"); + + + if (Port->TNC && Port->TNC->WebWindowProc) // Has a Window + { + if (Port->UICAPABLE) + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortLineWithBeaconAndDriver, Port->PORTNUMBER, DLL, + Port->PORTDESCRIPTION, Port->PORTNUMBER, Port->PORTNUMBER, Port->TNC->WebWinX, Port->TNC->WebWinY, 200, 200, StatsURL); + else + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortLineWithDriver, Port->PORTNUMBER, DLL, + Port->PORTDESCRIPTION, Port->PORTNUMBER, Port->TNC->WebWinX, Port->TNC->WebWinY, 200, 200, StatsURL); + + continue; + } + + if (Port->PORTTYPE == 16 && Port->PROTOCOL == 10 && Port->UICAPABLE == 0) // EXTERNAL, Pactor/WINMO + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], SessionPortLine, Port->PORTNUMBER, DLL, + Port->PORTDESCRIPTION, Port->PORTNUMBER, StatsURL); + else + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortLineWithBeacon, Port->PORTNUMBER, Port->PORTNUMBER, + DLL, DLL, Port->PORTDESCRIPTION, Port->PORTNUMBER, StatsURL); + } + + if (RigActive) + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], RigControlLine, 64, "Rig Control", "Rig Control", 600, 350, 200, 200); + + } + + if (_stricmp(NodeURL, "/Node/Nodes.html") == 0) + { + struct DEST_LIST * Dests = DESTS; + int count, i; + char Normcall[10]; + char Alias[10]; + int Width = 5; + int x = 0, n = 0; + struct DEST_LIST * List[1000]; + char Param = 0; + + if (Context) + { + _strupr(Context); + Param = Context[0]; + } + + for (count = 0; count < MAXDESTS; count++) + { + if (Dests->DEST_CALL[0] != 0) + { + if (Param != 'T' || Dests->DEST_COUNT) + List[n++] = Dests; + + if (n > 999) + break; + } + + Dests++; + } + + if (n > 1) + { + if (Param == 'C') + qsort(List, n, sizeof(void *), CompareNode); + else + qsort(List, n, sizeof(void *), CompareAlias); + } + + Alias[6] = 0; + + if (Param == 'T') + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], NodeHddr, "with traffic"); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], ""); + } + else if (Param == 'C') + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], NodeHddr, "sorted by Call"); + else + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], NodeHddr, "sorted by Alias"); + + for (i = 0; i < n; i++) + { + int len = ConvFromAX25(List[i]->DEST_CALL, Normcall); + Normcall[len]=0; + + memcpy(Alias, List[i]->DEST_ALIAS, 6); + strlop(Alias, ' '); + + if (Param == 'T') + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + Normcall, Alias, List[i]->DEST_COUNT, List[i]->DEST_RTT /16, + (List[i]->DEST_STATE & 0x40)? 'B':' ', (List[i]->DEST_STATE & 63)); + + } + else + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], NodeLine, Normcall, Alias, Normcall); + + if (++x == Width) + { + x = 0; + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], ""); + } + } + } + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], ""); + } + + if (_stricmp(NodeURL, "/Node/NodeDetail") == 0) + { + UCHAR AXCall[8]; + struct DEST_LIST * Dest = DESTS; + struct NR_DEST_ROUTE_ENTRY * NRRoute; + struct ROUTE * Neighbour; + char Normcall[10]; + int i, len, count, Active; + char Alias[7]; + + Alias[6] = 0; + + _strupr(Context); + + ConvToAX25(Context, AXCall); + + for (count = 0; count < MAXDESTS; count++) + { + if (CompareCalls(Dest->DEST_CALL, AXCall)) + { + break; + } + Dest++; + } + + if (count == MAXDESTS) + { + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "

Call %s not found

", Context); + goto SendResp; + } + + memcpy(Alias, Dest->DEST_ALIAS, 6); + strlop(Alias, ' '); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], + "

Info for Node %s:%s

", Alias, Context); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "

PortDriverIDBeaconsDriver WindowStats Graph
%d %s%s
%d %s%s Beacons %s
%d%s%s %s
%d%s%s Driver Window%s
%d%s%s BeaconsDriver Window%s
%d%s%s Rig Control
%s%s
%s%s
%s%d%d
%s%d%d%d%d%d
%s%d%d
%s%d%d
%s%d%d%d%d
%s%d
CallFramesRTTBPQ?Hops
%s:%s%d%d%c%.0d
"); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "
FramesRTTBPQ?Hops
%d%d%c%.0d
", + Dest->DEST_COUNT, Dest->DEST_RTT /16, + (Dest->DEST_STATE & 0x40)? 'B':' ', (Dest->DEST_STATE & 63)); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "

Neighbours

"); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], + "" + ""); + + NRRoute = &Dest->NRROUTE[0]; + + Active = Dest->DEST_ROUTE; + + for (i = 1; i < 4; i++) + { + Neighbour = NRRoute->ROUT_NEIGHBOUR; + + if (Neighbour) + { + len = ConvFromAX25(Neighbour->NEIGHBOUR_CALL, Normcall); + Normcall[len] = 0; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "", + (Active == i)?'>':' ',NRRoute->ROUT_QUALITY, NRRoute->ROUT_OBSCOUNT, Neighbour->NEIGHBOUR_PORT, Normcall); + } + NRRoute++; + } + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "
Qual Obs Port Call
%c %d%d%d%s
"); + + goto SendResp; + + } + /* + + MOV ESI,OFFSET32 NODEROUTEHDDR + MOV ECX,11 + REP MOVSB + + LEA ESI,DEST_CALL[EBX] + CALL DECODENODENAME ; CONVERT TO ALIAS:CALL + REP MOVSB + + CMP DEST_RTT[EBX],0 + JE SHORT @f ; TIMER NOT SET - DEST PROBABLY NOT USED + + MOVSB ; ADD SPACE + CALL DORTT + + @@: + MOV AL,CR + STOSB + + MOV ECX,3 + MOV DH,DEST_ROUTE[EBX] ; CURRENT ACTIVE ROUTE + MOV DL,1 + + push ebx + + PUBLIC CMDN110 + CMDN110: + + MOV ESI,ROUT1_NEIGHBOUR[EBX] + CMP ESI,0 + JE CMDN199 + + + MOV AX,' ' + CMP DH,DL + JNE SHORT CMDN112 ; NOT CURRENT DEST + MOV AX,' >' + + CMDN112: + + STOSW + + PUSH ECX + + MOV AL,ROUT1_QUALITY[EBX] + CALL CONV_DIGITS ; CONVERT AL TO DECIMAL DIGITS + + mov AL,' ' + stosb + + MOV AL,ROUT1_OBSCOUNT[EBX] + CALL CONV_DIGITS ; CONVERT AL TO DECIMAL DIGITS + + mov AL,' ' + stosb + + MOV AL,NEIGHBOUR_PORT[ESI] ; GET PORT + CALL CONV_DIGITS ; CONVERT AL TO DECIMAL DIGITS + + mov AL,' ' + stosb + + + PUSH EDI + CALL CONVFROMAX25 ; CONVERT TO CALL + POP EDI + + MOV ESI,OFFSET32 NORMCALL + REP MOVSB + + MOV AL,CR + STOSB + + ADD EBX,ROUTEVECLEN + INC DL ; ROUTE NUMBER + + POP ECX + DEC ECX + JNZ CMDN110 + + PUBLIC CMDN199 + CMDN199: + + POP EBX + + ; DISPLAY INP3 ROUTES + + MOV ECX,3 + MOV DL,4 + + PUBLIC CMDNINP3 + CMDNINP3: + + MOV ESI,INPROUT1_NEIGHBOUR[EBX] + CMP ESI,0 + JE CMDNINPEND + + MOV AX,' ' + CMP DH,DL + JNE SHORT @F ; NOT CURRENT DEST + MOV AX,' >' + + @@: + + STOSW + + PUSH ECX + + MOV AL, Hops1[EBX] + CALL CONV_DIGITS ; CONVERT AL TO DECIMAL DIGITS + + mov AL,' ' + stosb + + MOVZX EAX, SRTT1[EBX] + + MOV EDX,0 + MOV ECX, 100 + DIV ECX + CALL CONV_5DIGITS + MOV AL,'.' + STOSB + MOV EAX, EDX + CALL PRINTNUM + MOV AL,'s' + STOSB + MOV AL,' ' + STOSB + + MOV AL,NEIGHBOUR_PORT[ESI] ; GET PORT + CALL CONV_DIGITS ; CONVERT AL TO DECIMAL DIGITS + + mov AL,' ' + stosb + + PUSH EDI + CALL CONVFROMAX25 ; CONVERT TO CALL + POP EDI + + MOV ESI,OFFSET32 NORMCALL + REP MOVSB + + + MOV AL,CR + STOSB + + ADD EBX,INPROUTEVECLEN + INC DL ; ROUTE NUMBER + + POP ECX + LOOP CMDNINP3 + + CMDNINPEND: + + ret + + */ + + + if (_stricmp(NodeURL, "/Node/Routes.html") == 0) + { + struct ROUTE * Routes = NEIGHBOURS; + int MaxRoutes = MAXNEIGHBOURS; + int count; + char Normcall[10]; + char locked; + int NodeCount; + int Percent = 0; + int Iframes, Retries; + char Active[10]; + int Queued; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", RouteHddr); + + for (count=0; countNEIGHBOUR_CALL[0] != 0) + { + int len = ConvFromAX25(Routes->NEIGHBOUR_CALL, Normcall); + Normcall[len]=0; + + if ((Routes->NEIGHBOUR_FLAG & 1) == 1) + locked = '!'; + else + locked = ' '; + + NodeCount = COUNTNODES(Routes); + + if (Routes->NEIGHBOUR_LINK) + Queued = COUNT_AT_L2(Routes->NEIGHBOUR_LINK); + else + Queued = 0; + + Iframes = Routes->NBOUR_IFRAMES; + Retries = Routes->NBOUR_RETRIES; + + if (Routes->NEIGHBOUR_LINK && Routes->NEIGHBOUR_LINK->L2STATE >= 5) + strcpy(Active, ">"); + else + strcpy(Active, " "); + + if (Iframes) + Percent = (Retries * 100) / Iframes; + else + Percent = 0; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], RouteLine, Active, Routes->NEIGHBOUR_PORT, Normcall, locked, + Routes->NEIGHBOUR_QUAL, NodeCount, Iframes, Retries, Percent, Routes->NBOUR_MAXFRAME, Routes->NBOUR_FRACK, + Routes->NEIGHBOUR_TIME >> 8, Routes->NEIGHBOUR_TIME & 0xff, Queued, Routes->OtherendsRouteQual); + } + Routes+=1; + } + } + + if (_stricmp(NodeURL, "/Node/Links.html") == 0) + { + struct _LINKTABLE * Links = LINKS; + int MaxLinks = MAXLINKS; + int count; + char Normcall1[10]; + char Normcall2[10]; + char State[12] = "", Type[12] = "Uplink"; + int axState; + int cctType; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", LinkHddr); + + for (count=0; countLINKCALL[0] != 0) + { + int len = ConvFromAX25(Links->LINKCALL, Normcall1); + Normcall1[len] = 0; + + len = ConvFromAX25(Links->OURCALL, Normcall2); + Normcall2[len] = 0; + + axState = Links->L2STATE; + + if (axState == 2) + strcpy(State, "Connecting"); + else if (axState == 3) + strcpy(State, "FRMR"); + else if (axState == 4) + strcpy(State, "Closing"); + else if (axState == 5) + strcpy(State, "Active"); + else if (axState == 6) + strcpy(State, "REJ Sent"); + + cctType = Links->LINKTYPE; + + if (cctType == 1) + strcpy(Type, "Uplink"); + else if (cctType == 2) + strcpy(Type, "Downlink"); + else if (cctType == 3) + strcpy(Type, "Node-Node"); + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], LinkLine, Normcall1, Normcall2, Links->LINKPORT->PORTNUMBER, + State, Type, 2 - Links->VER1FLAG ); + + Links+=1; + } + } + } + + if (_stricmp(NodeURL, "/Node/Users.html") == 0) + { + TRANSPORTENTRY * L4 = L4TABLE; + TRANSPORTENTRY * Partner; + int MaxLinks = MAXLINKS; + int count; + char State[12] = "", Type[12] = "Uplink"; + char LHS[50] = "", MID[10] = "", RHS[50] = ""; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", UserHddr); + + for (count=0; count < MAXCIRCUITS; count++) + { + if (L4->L4USER[0]) + { + RHS[0] = MID[0] = 0; + + if ((L4->L4CIRCUITTYPE & UPLINK) == 0) //SHORT CMDS10A ; YES + { + // IF DOWNLINK, ONLY DISPLAY IF NO CROSSLINK + + if (L4->L4CROSSLINK == 0) //jne CMDS60 ; WILL PROCESS FROM OTHER END + { + // ITS A DOWNLINK WITH NO PARTNER - MUST BE A CLOSING SESSION + // DISPLAY TO THE RIGHT FOR NOW + + strcpy(LHS, "(Closing) "); + DISPLAYCIRCUIT(L4, RHS); + goto CMDS50; + } + else + goto CMDS60; // WILL PROCESS FROM OTHER END + } + + if (L4->L4CROSSLINK == 0) + { + // Single Entry + + DISPLAYCIRCUIT(L4, LHS); + } + else + { + DISPLAYCIRCUIT(L4, LHS); + + Partner = L4->L4CROSSLINK; + + if (Partner->L4STATE == 5) + strcpy(MID, "<-->"); + else + strcpy(MID, "<~~>"); + + DISPLAYCIRCUIT(Partner, RHS); + } +CMDS50: + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], UserLine, LHS, MID, RHS); + } +CMDS60: + L4++; + } + } + /* + PUBLIC CMDUXX_1 + CMDUXX_1: + push EBX + push ESI + PUSH ECX + push EDI + + call _FINDDESTINATION + pop EDI + + jz SHORT NODE_FOUND + + push EDI ; NET/ROM not found + call CONVFROMAX25 ; CONVERT TO CALL + pop EDI + mov ESI,OFFSET32 NORMCALL + rep movsb + + jmp SHORT END_CMDUXX + + PUBLIC NODE_FOUND + NODE_FOUND: + + lea ESI,DEST_CALL[EBX] + call DECODENODENAME + + REP MOVSB + + PUBLIC END_CMDUXX + END_CMDUXX: + + POP ECX + pop ESI + pop EBX + ret + + }}} + */ + + else if (_stricmp(NodeURL, "/Node/Terminal.html") == 0) + { + if (COOKIE && Session) + { + // Already signed in as sysop + + struct UserRec * USER = Session->USER; + + struct HTTPConnectionInfo * NewSession = AllocateSession(sock, 'T'); + + if (NewSession) + { + char AXCall[10]; + ReplyLen = sprintf(_REPLYBUFFER, TermPage, Mycall, Mycall, NewSession->Key, NewSession->Key, NewSession->Key); + strcpy(NewSession->HTTPCall, USER->Callsign); + ConvToAX25(NewSession->HTTPCall, AXCall); + ChangeSessionCallsign(NewSession->Stream, AXCall); + BPQHOSTVECTOR[NewSession->Stream -1].HOSTSESSION->Secure_Session = USER->Secure; + Session->USER = USER; + NewSession->TNC = conn->TNC; + + + // if (Appl[0]) + // { + // strcat(Appl, "\r"); + // SendMsg(Session->Stream, Appl, strlen(Appl)); + // } + + } + else + { + ReplyLen = SetupNodeMenu(_REPLYBUFFER, LOCAL); + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", BusyError); + } + } + else if (LOCAL) + { + // connected to 127.0.0.1 so sign in using node call + + struct HTTPConnectionInfo * NewSession = AllocateSession(sock, 'T'); + + if (NewSession) + { + ReplyLen = sprintf(_REPLYBUFFER, TermPage, Mycall, Mycall, NewSession->Key, NewSession->Key, NewSession->Key); + strcpy(NewSession->HTTPCall, MYNODECALL); + ChangeSessionCallsign(NewSession->Stream, MYCALL); + BPQHOSTVECTOR[NewSession->Stream -1].HOSTSESSION->Secure_Session = TRUE; + NewSession->TNC = conn->TNC; + } + } + else + ReplyLen = sprintf(_REPLYBUFFER, TermSignon, Mycall, Mycall, Context); + } + + else if (_stricmp(NodeURL, "/Node/Signon.html") == 0) + { + ReplyLen = sprintf(_REPLYBUFFER, NodeSignon, Mycall, Mycall, Context); + } + + else if (_stricmp(NodeURL, "/Node/Drivers") == 0) + { + int Bufferlen = SendMessageFile(sock, "/Drivers.htm", TRUE, allowDeflate); // return -1 if not found + + if (Bufferlen != -1) + return 0; // We've sent it + } + + else if (_stricmp(NodeURL, "/Node/OutputScreen.html") == 0) + { + struct HTTPConnectionInfo * Session = FindSession(Context); + + if (Session == NULL) + { + ReplyLen = sprintf(_REPLYBUFFER, "%s", LostSession); + } + else + { + Session->sock = sock; // socket to reply on + ReplyLen = RefreshTermWindow(TCP, Session, _REPLYBUFFER); + + if (ReplyLen == 0) // Nothing new + { + // Debugprintf("GET with no data avail - response held"); + Session->ResponseTimer = 1200; // Delay response for up to a minute + } + else + { + // Debugprintf("GET - outpur sent, timer was %d, set to zero", Session->ResponseTimer); + Session->ResponseTimer = 0; + } + + Session->KillTimer = 0; + return 0; // Refresh has sent any available output + } + } + + else if (_stricmp(NodeURL, "/Node/InputLine.html") == 0) + { + struct TNCINFO * TNC = conn->TNC; + struct TCPINFO * TCP = 0; + + if (TNC) + TCP = TNC->TCPInfo; + + if (TCP && TCP->WebTermCSS) + ReplyLen = sprintf(_REPLYBUFFER, InputLine, Context, TCP->WebTermCSS); + else + ReplyLen = sprintf(_REPLYBUFFER, InputLine, Context, ""); + + } + + else if (_stricmp(NodeURL, "/Node/PTT") == 0) + { + struct TNCINFO * TNC = conn->TNC; + int x = atoi(Context); + } + + +SendResp: + + FormatTime3(TimeString, time(NULL)); + + strcpy(&_REPLYBUFFER[ReplyLen], Tail); + ReplyLen += (int)strlen(Tail); + + + if (allowDeflate) + { + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + } + else + { + Encoding[0] = 0; + Compressed = _REPLYBUFFER; + } + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n" + "Date: %s\r\n%s\r\n", ReplyLen, TimeString, Encoding); + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + } + return 0; + +#ifdef WIN32xx + } +#include "StdExcept.c" +} +return 0; +#endif +} + +void ProcessHTTPMessage(void * conn) +{ + // conn is a malloc'ed copy to handle reused connections, so need to free it + + InnerProcessHTTPMessage((struct ConnectionInfo *)conn); + free(conn); + return; +} + +static char *month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +static char *dat[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + + +VOID FormatTime3(char * Time, time_t cTime) +{ + struct tm * TM; + TM = gmtime(&cTime); + + sprintf(Time, "%s, %02d %s %3d %02d:%02d:%02d GMT", dat[TM->tm_wday], TM->tm_mday, month[TM->tm_mon], + TM->tm_year + 1900, TM->tm_hour, TM->tm_min, TM->tm_sec); + +} + +// Sun, 06 Nov 1994 08:49:37 GMT + +int StatusProc(char * Buff) +{ + int i; + char callsign[12] = ""; + char flag[3]; + UINT Mask, MaskCopy; + int Flags; + int AppNumber; + int OneBits; + int Len = sprintf(Buff, "" + "Stream Status"); + + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + Len += sprintf(&Buff[Len], ""); + + for (i=1;i<65; i++) + { + callsign[0]=0; + + if (GetAllocationState(i)) + + strcpy(flag,"*"); + else + strcpy(flag," "); + + GetCallsign(i,callsign); + + Mask = MaskCopy = Get_APPLMASK(i); + + // if only one bit set, convert to number + + AppNumber = 0; + OneBits = 0; + + while (MaskCopy) + { + if (MaskCopy & 1) + OneBits++; + + AppNumber++; + MaskCopy = MaskCopy >> 1; + } + + Flags=GetApplFlags(i); + + if (OneBits > 1) + Len += sprintf(&Buff[Len], "" + "", + i, flag, RXCount(i), TXCount(i), MONCount(i), Mask, Flags, callsign, BPQHOSTVECTOR[i-1].PgmName); + + else + Len += sprintf(&Buff[Len], "" + "", + i, flag, RXCount(i), TXCount(i), MONCount(i), AppNumber, Flags, callsign, BPQHOSTVECTOR[i-1].PgmName); + + if ((i & 1) == 0) + Len += sprintf(&Buff[Len], ""); + + } + + Len += sprintf(&Buff[Len], "
    RX   TX   MON  App  Flg Callsign  Program    RX   TX   MON  App  Flg Callsign  Program
%d%s%d%d%d%x%x%s%s%d%s%d%d%d%d%x%s%s
"); + return Len; +} + +int ProcessNodeSignon(SOCKET sock, struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, int LOCAL) +{ + int ReplyLen = 0; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * user, * password, * Key; + char Header[256]; + int HeaderLen; + struct HTTPConnectionInfo *Sess; + + + if (input) + { + int i; + struct UserRec * USER; + + UndoTransparency(input); + + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n" + "\r\n", (int)(ReplyLen + strlen(Tail))); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + } + user = strtok_s(&input[9], "&", &Key); + password = strtok_s(NULL, "=", &Key); + password = Key; + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (user && _stricmp(user, USER->UserName) == 0) + { + if (strcmp(password, USER->Password) == 0 && USER->Secure) + { + // ok + + Sess = *Session = AllocateSession(sock, 'N'); + Sess->USER = USER; + + ReplyLen = SetupNodeMenu(Reply, LOCAL); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n" + "Set-Cookie: BPQSessionCookie=%s; Path = /\r\n\r\n", (int)(ReplyLen + strlen(Tail)), Sess->Key); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return ReplyLen; + } + } + } + } + + ReplyLen = sprintf(Reply, NodeSignon, Mycall, Mycall); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", PassError); + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n", (int)(ReplyLen + strlen(Tail))); + send(sock, Header, HeaderLen, 0); + send(sock, Reply, ReplyLen, 0); + send(sock, Tail, (int)strlen(Tail), 0); + + return 0; + + + return ReplyLen; +} + +int ProcessMailAPISignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, BOOL WebMail, int LOCAL) +{ + int ReplyLen = 0; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * user, * password, * Key; + struct HTTPConnectionInfo * NewSession; + int i; + struct UserRec * USER; + + if (strchr(MsgPtr, '?')) + { + // Check Password + + user = strlop(MsgPtr, '?'); + password = strlop(user, '&'); + strlop(password, ' '); + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (user && _stricmp(user, USER->UserName) == 0) + { + if ((strcmp(password, USER->Password) == 0) && (USER->Secure || WebMail)) + { + // ok + + NewSession = AllocateSession(Appl[0], 'M'); + + *Session = NewSession; + + if (NewSession) + { + ReplyLen = 0; + strcpy(NewSession->Callsign, USER->Callsign); + } + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + return ReplyLen; + + } + } + } + + // Pass failed attempt to BBS code so it can try a bbs user login + + // Need to put url back together + + if (user && user[0] && password && password[0]) + { + sprintf(MsgPtr, "%s?%s&%s", MsgPtr, user, password); + } + } + + NewSession = AllocateSession(Appl[0], 'M'); + + *Session = NewSession; + + if (NewSession) + ReplyLen = 0; + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + + return ReplyLen; +} + + + + +int ProcessMailSignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, BOOL WebMail, int LOCAL) +{ + int ReplyLen = 0; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * user, * password, * Key; + struct HTTPConnectionInfo * NewSession; + + if (input) + { + int i; + struct UserRec * USER; + + UndoTransparency(input); + + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + return ReplyLen; + } + user = strtok_s(&input[9], "&", &Key); + password = strtok_s(NULL, "=", &Key); + password = Key; + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (user && _stricmp(user, USER->UserName) == 0) + { + if (strcmp(password, USER->Password) == 0 && (USER->Secure || WebMail)) + { + // ok + + NewSession = AllocateSession(Appl[0], 'M'); + + *Session = NewSession; + + if (NewSession) + { + + ReplyLen = 0; + strcpy(NewSession->Callsign, USER->Callsign); + } + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + return ReplyLen; + } + } + } + } + + ReplyLen = sprintf(Reply, MailSignon, Mycall, Mycall); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", PassError); + + return ReplyLen; +} + + +int ProcessChatSignon(struct TCPINFO * TCP, char * MsgPtr, char * Appl, char * Reply, struct HTTPConnectionInfo ** Session, int LOCAL) +{ + int ReplyLen = 0; + char * input = strstr(MsgPtr, "\r\n\r\n"); // End of headers + char * user, * password, * Key; + + if (input) + { + int i; + struct UserRec * USER; + + UndoTransparency(input); + + if (strstr(input, "Cancel=Cancel")) + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + return ReplyLen; + } + + user = strtok_s(&input[9], "&", &Key); + password = strtok_s(NULL, "=", &Key); + password = Key; + + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (user && _stricmp(user, USER->UserName) == 0) + { + if (strcmp(password, USER->Password) == 0 && USER->Secure) + { + // ok + + *Session = AllocateSession(Appl[0], 'C'); + + if (Session) + { + ReplyLen = 0; + } + else + { + ReplyLen = SetupNodeMenu(Reply, LOCAL); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", BusyError); + } + return ReplyLen; + } + } + } + } + + ReplyLen = sprintf(Reply, ChatSignon, Mycall, Mycall); + ReplyLen += sprintf(&Reply[ReplyLen], "%s", PassError); + + return ReplyLen; + +} + +#define SHA1_HASH_LEN 20 + +/* + +Copyright (C) 1998, 2009 +Paul E. Jones + +Freeware Public License (FPL) + +This software is licensed as "freeware." Permission to distribute +this software in source and binary forms, including incorporation +into other products, is hereby granted without a fee. THIS SOFTWARE +IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHOR SHALL NOT BE HELD +LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER +DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA +OR DATA BEING RENDERED INACCURATE. +*/ + +/* sha1.h + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This class implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * Many of the variable names in the SHA1Context, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +/* + * This structure will hold context information for the hashing + * operation + */ +typedef struct SHA1Context +{ + unsigned Message_Digest[5]; /* Message Digest (output) */ + + unsigned Length_Low; /* Message length in bits */ + unsigned Length_High; /* Message length in bits */ + + unsigned char Message_Block[64]; /* 512-bit message blocks */ + int Message_Block_Index; /* Index into message block array */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corruped? */ +} SHA1Context; + +/* + * Function Prototypes + */ +void SHA1Reset(SHA1Context *); +int SHA1Result(SHA1Context *); +void SHA1Input( SHA1Context *, const unsigned char *, unsigned); + +#endif + +BOOL SHA1PasswordHash(char * lpszPassword, char * Hash) +{ + SHA1Context sha; + int i; + + SHA1Reset(&sha); + SHA1Input(&sha, lpszPassword, strlen(lpszPassword)); + SHA1Result(&sha); + + // swap byte order if little endian + + for (i = 0; i < 5; i++) + sha.Message_Digest[i] = htonl(sha.Message_Digest[i]); + + memcpy(Hash, &sha.Message_Digest[0], 20); + + return TRUE; +} + +int BuildRigCtlPage(char * _REPLYBUFFER) +{ + int ReplyLen; + + struct RIGPORTINFO * PORT; + struct RIGINFO * RIG; + int p, i; + + char Page[] = + "\r\n" + // "\r\n" + "Rigcontrol\r\n" + "" + "

Rigcontrol

\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + ""; + char RigLine[] = + "\r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n"; + char Tail[] = + "
RadioFreqModeSTPorts
%s%s%s/1%c%c%s
\r\n" + "\r\n"; + + ReplyLen = sprintf(_REPLYBUFFER, "%s", Page); + + for (p = 0; p < NumberofPorts; p++) + { + PORT = PORTInfo[p]; + + for (i=0; i< PORT->ConfiguredRigs; i++) + { + RIG = &PORT->Rigs[i]; + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], RigLine, RIG->WEB_Label, RIG->WEB_FREQ, RIG->WEB_MODE, RIG->WEB_SCAN, RIG->WEB_PTT, RIG->WEB_PORTS, RIG->Interlock); + } + } + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", Tail); + return ReplyLen; +} + + +void SendRigWebPage() +{ + int i, n; + struct ConnectionInfo * sockptr; + struct TNCINFO * TNC; + struct TCPINFO * TCP; + + for (i = 0; i < 33; i++) + { + TNC = TNCInfo[i]; + + if (TNC && TNC->Hardware == H_TELNET) + { + TCP = TNC->TCPInfo; + + if (TCP) + { + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->SocketActive) + { + if (sockptr->HTTPMode && sockptr->WebSocks && strcmp(sockptr->WebURL, "RIGCTL") == 0) + { + char RigMsg[8192]; + int RigMsgLen = strlen(RigWebPage); + char* ptr; + + RigMsg[0] = 0x81; // Fin, Data + RigMsg[1] = 126; // Unmasked, Extended Len + RigMsg[2] = RigMsgLen >> 8; + RigMsg[3] = RigMsgLen & 0xff; + strcpy(&RigMsg[4], RigWebPage); + + // If secure session enable PTT button + + if (sockptr->WebSecure) + { + while (ptr = strstr(RigMsg, "hidden")) + memcpy(ptr, " ", 6); + } + + send(sockptr->socket, RigMsg, RigMsgLen + 4, 0); + } + } + } + } + } + } +} + +// Webmail web socket code + +int ProcessWebmailWebSock(char * MsgPtr, char * OutBuffer); + +void ProcessWebmailWebSockThread(void * conn) +{ + // conn is a malloc'ed copy to handle reused connections, so need to free it + + struct ConnectionInfo * sockptr = (struct ConnectionInfo *)conn; + char * URL = sockptr->WebURL; + int Loops = 0; + int Sent; + struct HTTPConnectionInfo Dummy = {0}; + int ReplyLen = 0; + int InputLen = 0; + +#ifdef LINBPQ + + char _REPLYBUFFER[250000]; + + ReplyLen = ProcessWebmailWebSock(URL, _REPLYBUFFER); + + // Send may block + + Sent = send(sockptr->socket, _REPLYBUFFER, ReplyLen, 0); + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + if (Sent > 0) // something sent + { + ReplyLen -= Sent; + memmove(_REPLYBUFFER, &_REPLYBUFFER[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sockptr->socket, _REPLYBUFFER, ReplyLen, 0); + } + +#else + // Send URL to BPQMail via Pipe. Just need a dummy session, as URL contains session key + + HANDLE hPipe; + char Reply[250000]; + + + + hPipe = CreateFile(MAILPipeFileName, GENERIC_READ | GENERIC_WRITE, + 0, // exclusive access + NULL, // no security attrs + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if (hPipe == (HANDLE)-1) + { + free(conn); + return; + } + + WriteFile(hPipe, &Dummy, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + WriteFile(hPipe, URL, strlen(URL), &InputLen, NULL); + + ReadFile(hPipe, &Dummy, sizeof (struct HTTPConnectionInfo), &InputLen, NULL); + ReadFile(hPipe, Reply, 250000, &ReplyLen, NULL); + + if (ReplyLen <= 0) + { + InputLen = GetLastError(); + } + + CloseHandle(hPipe); + + // ?? do we need a thread to handle write which may block + + Sent = send(sockptr->socket, Reply, ReplyLen, 0); + + while (Sent != ReplyLen && Loops++ < 3000) // 100 secs max + { + // Debugprintf("%d out of %d sent %d Loops", Sent, InputLen, Loops); + + if (Sent > 0) // something sent + { + InputLen -= Sent; + memmove(Reply, &Reply[Sent], ReplyLen); + } + + Sleep(30); + Sent = send(sockptr->socket, Reply, ReplyLen, 0); + } +#endif + free(conn); + return; +} + +/* + * sha1.c + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This file implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * The Secure Hashing Standard, which uses the Secure Hashing + * Algorithm (SHA), produces a 160-bit message digest for a + * given data stream. In theory, it is highly improbable that + * two messages will produce the same message digest. Therefore, + * this algorithm can serve as a means of providing a "fingerprint" + * for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code was + * written with the expectation that the processor has at least + * a 32-bit machine word size. If the machine word size is larger, + * the code should still function properly. One caveat to that + * is that the input functions taking characters and character + * arrays assume that only 8 bits of information are stored in each + * character. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated for + * messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is a + * multiple of the size of an 8-bit character. + * + */ + +/* + * Define the circular shift macro + */ +#define SHA1CircularShift(bits,word) \ + ((((word) << (bits)) & 0xFFFFFFFF) | \ + ((word) >> (32-(bits)))) + +/* Function prototypes */ +void SHA1ProcessMessageBlock(SHA1Context *); +void SHA1PadMessage(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Reset(SHA1Context *context) +{ + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Message_Digest[0] = 0x67452301; + context->Message_Digest[1] = 0xEFCDAB89; + context->Message_Digest[2] = 0x98BADCFE; + context->Message_Digest[3] = 0x10325476; + context->Message_Digest[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array within the SHA1Context provided + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * + * Returns: + * 1 if successful, 0 if it failed. + * + * Comments: + * + */ +int SHA1Result(SHA1Context *context) +{ + + if (context->Corrupted) + { + return 0; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + context->Computed = 1; + } + + return 1; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * context: [in/out] + * The SHA-1 context to update + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Input( SHA1Context *context, + const unsigned char *message_array, + unsigned length) +{ + if (!length) + { + return; + } + + if (context->Computed || context->Corrupted) + { + context->Corrupted = 1; + return; + } + + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + /* Force it to 32 bits */ + context->Length_Low &= 0xFFFFFFFF; + if (context->Length_Low == 0) + { + context->Length_High++; + /* Force it to 32 bits */ + context->Length_High &= 0xFFFFFFFF; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in the SHAContext, especially the + * single character names, were used because those were the names + * used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const unsigned K[] = /* Constants defined in SHA-1 */ + { + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + unsigned temp; /* Temporary word value */ + unsigned W[80]; /* Word sequence */ + unsigned A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = ((unsigned) context->Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]); + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Message_Digest[0]; + B = context->Message_Digest[1]; + C = context->Message_Digest[2]; + D = context->Message_Digest[3]; + E = context->Message_Digest[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Message_Digest[0] = + (context->Message_Digest[0] + A) & 0xFFFFFFFF; + context->Message_Digest[1] = + (context->Message_Digest[1] + B) & 0xFFFFFFFF; + context->Message_Digest[2] = + (context->Message_Digest[2] + C) & 0xFFFFFFFF; + context->Message_Digest[3] = + (context->Message_Digest[3] + D) & 0xFFFFFFFF; + context->Message_Digest[4] = + (context->Message_Digest[4] + E) & 0xFFFFFFFF; + + context->Message_Block_Index = 0; +} + +/* + * SHA1PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call SHA1ProcessMessageBlock() + * appropriately. When it returns, it can be assumed that the + * message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (context->Length_High >> 24) & 0xFF; + context->Message_Block[57] = (context->Length_High >> 16) & 0xFF; + context->Message_Block[58] = (context->Length_High >> 8) & 0xFF; + context->Message_Block[59] = (context->Length_High) & 0xFF; + context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF; + context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF; + context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF; + context->Message_Block[63] = (context->Length_Low) & 0xFF; + + SHA1ProcessMessageBlock(context); +} + + diff --git a/HTTPcode.c b/HTTPcode.c index 2c0a8dd..54a0614 100644 --- a/HTTPcode.c +++ b/HTTPcode.c @@ -24,7 +24,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define DllImport -#include "CHeaders.h" +#include "cheaders.h" #include #include "tncinfo.h" @@ -70,6 +70,7 @@ char * GetStandardPage(char * FN, int * Len); BOOL SHA1PasswordHash(char * String, char * Hash); char * byte_base64_encode(char *str, int len); int APIProcessHTTPMessage(char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); +int RHPProcessHTTPMessage(struct ConnectionInfo * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); extern struct ROUTE * NEIGHBOURS; extern int ROUTE_LEN; @@ -1852,6 +1853,43 @@ int InnerProcessHTTPMessage(struct ConnectionInfo * conn) } } + if (_memicmp(Context, "/rhp/", 5) == 0 || _stricmp(Context, "/rhp") == 0) + { + { + ReplyLen = RHPProcessHTTPMessage(conn, _REPLYBUFFER, Method, Context, MsgPtr, LOCAL, COOKIE); + + if (memcmp(_REPLYBUFFER, "HTTP", 4) == 0) + { + // Full Message - just send it + + sendandcheck(sock, _REPLYBUFFER, ReplyLen); + + return 0; + } + + if (allowDeflate) + Compressed = Compressit(_REPLYBUFFER, ReplyLen, &ReplyLen); + else + Compressed = _REPLYBUFFER; + + HeaderLen = sprintf(Header, "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "%s\r\n", ReplyLen, Encoding); + + sendandcheck(sock, Header, HeaderLen); + sendandcheck(sock, Compressed, ReplyLen); + + if (allowDeflate) + free (Compressed); + + return 0; + } + } + + // APRS process internally if (_memicmp(Context, "/APRS/", 6) == 0 || _stricmp(Context, "/APRS") == 0) @@ -5135,6 +5173,3 @@ void SHA1PadMessage(SHA1Context *context) } - - - diff --git a/HanksRT.c b/HanksRT.c index 79e1044..e8f5ffd 100644 --- a/HanksRT.c +++ b/HanksRT.c @@ -27,7 +27,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #ifdef LINBPQ -#include "CHeaders.h" +#include "cheaders.h" #endif #include "bpqchat.h" @@ -4178,10 +4178,10 @@ BOOL GetChatConfig(char * ConfigName) MaxChatStreams = GetIntValue(group, "MaxStreams"); reportChatEvents = GetIntValue(group, "reportChatEvents"); chatPaclen = GetIntValue(group, "chatPaclen"); - GetStringValue(group, "OtherChatNodes", OtherNodesList); - GetStringValue(group, "ChatWelcomeMsg", ChatWelcomeMsg); - GetStringValue(group, "MapPosition", Position); - GetStringValue(group, "MapPopup", PopupText); + GetStringValue(group, "OtherChatNodes", OtherNodesList, 1000); + GetStringValue(group, "ChatWelcomeMsg", ChatWelcomeMsg, 1000); + GetStringValue(group, "MapPosition", Position, 81); + GetStringValue(group, "MapPopup", PopupText, 260); PopupMode = GetIntValue(group, "PopupMode"); if (chatPaclen == 0) diff --git a/Housekeeping.c b/Housekeeping.c index 48ff1e7..bf2c49f 100644 --- a/Housekeeping.c +++ b/Housekeeping.c @@ -21,6 +21,11 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // // Housekeeping Module + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + + #include "bpqmail.h" char * APIENTRY GetBPQDirectory(); diff --git a/IPCode.c b/IPCode.c index 80939d1..17dd412 100644 --- a/IPCode.c +++ b/IPCode.c @@ -81,7 +81,7 @@ TODo ?Multiple Adapters #include #include -#include "CHeaders.h" +#include "cheaders.h" #include "ipcode.h" diff --git a/KAMPactor.c b/KAMPactor.c index 890bcf8..749d561 100644 --- a/KAMPactor.c +++ b/KAMPactor.c @@ -53,7 +53,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include "time.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/KISSHF.c b/KISSHF.c index 368e04f..0c066ff 100644 --- a/KISSHF.c +++ b/KISSHF.c @@ -27,7 +27,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include -#include "CHeaders.h" +#include "cheaders.h" extern int (WINAPI FAR *GetModuleFileNameExPtr)(); diff --git a/L2Code.c b/L2Code.c index ab0b1f5..3cac44d 100644 --- a/L2Code.c +++ b/L2Code.c @@ -30,7 +30,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "time.h" #include "stdio.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #define PFBIT 0x10 // POLL/FINAL BIT IN CONTROL BYTE @@ -130,7 +130,7 @@ extern int REALTIMETICKS; UCHAR NO_CTEXT = 0; UCHAR ALIASMSG = 0; -extern UINT APPLMASK; + static UCHAR ISNETROMMSG = 0; UCHAR MSGFLAG = 0; extern char * ALIASPTR; @@ -142,6 +142,30 @@ extern BOOL LogAllConnects; APPLCALLS * APPL; + +void SendL2ToMonMap(struct PORTCONTROL * PORT, char * ReportCall, char Mode, char Direction) +{ + // if Port Freq < 30Mhz send to Node Map + + if (PORT->PortFreq && PORT->PortFreq < 30000000) + { + char ReportMode[16]; + char ReportFreq[350] = ""; + + ReportMode[0] = '@'; + ReportMode[1] = Mode; + ReportMode[2] = '?'; + ReportMode[3] = Direction; + ReportMode[4] = 0; + + // If no position see if we have an APRS posn + + _gcvt(PORT->PortFreq, 9, ReportFreq); + + SendMH(0, ReportCall, ReportFreq, 0, ReportMode); + } +} + VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer) { // LEVEL 2 PROCESSING @@ -153,6 +177,7 @@ VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer) UCHAR CTL; uintptr_t Work; UCHAR c; + unsigned int APPLMASK = 0; // Check for invalid length (< 22 7Header + 7Addr + 7Addr + CTL @@ -166,7 +191,6 @@ VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer) PORT->L2FRAMES++; ALIASMSG = 0; - APPLMASK = 0; ISNETROMMSG = 0; MSGFLAG = 0; // CMD/RESP UNDEFINED @@ -238,6 +262,7 @@ VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer) if (PORT->PORTMHEARD) MHPROC(PORT, Buffer); + /// TAJ added 07/12/2020 for 'all RX traffic as IfinOctects InOctets[PORT->PORTNUMBER] += Buffer->LENGTH - MSGHDDRLEN; @@ -466,6 +491,8 @@ FORUS: if (PORT->UIHook && CTL == 3) PORT->UIHook(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); + LINK->APPLMASK = APPLMASK; + L2FORUS(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); } @@ -935,7 +962,7 @@ VOID ProcessXIDCommand(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESS // We need to save APPLMASK and ALIASPTR so following SABM connects to application - LINK->APPLMASK = APPLMASK; + // LINK->APPLMASK now set in L2FORUS LINK->ALIASPTR = ALIASPTR; PUT_ON_PORT_Q(PORT, Buffer); @@ -1062,7 +1089,7 @@ VOID L2LINKACTIVE(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * if (LINK->L2STATE == 1) // Sent XID? { - APPLMASK = LINK->APPLMASK; + LINK->APPLMASK; ALIASPTR = LINK->ALIASPTR; L2SABM(LINK, PORT, Buffer, ADJBUFFER, MSGFLAG); // Process the SABM @@ -1147,7 +1174,7 @@ VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffe // IF CONNECT TO APPL ADDRESS, SET UP APPL SESSION - if (APPLMASK == 0) + if (LINK->APPLMASK == 0) { // Not ATTACH TO APPL @@ -1163,7 +1190,9 @@ VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffe WriteConnectLog(fromCall, toCall, "AX.25"); hookL2SessionAccepted(PORT->PORTNUMBER, fromCall, toCall, LINK); - + + SendL2ToMonMap(PORT, fromCall, '+', 'I'); + L2SENDUA(PORT, Buffer, ADJBUFFER); if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) @@ -1280,6 +1309,8 @@ VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffe hookL2SessionAccepted(PORT->PORTNUMBER, fromCall, toCall, LINK); + SendL2ToMonMap(PORT, fromCall, '+', 'I'); + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) { struct DATAMESSAGE * Msg; @@ -1353,7 +1384,7 @@ VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffe return; } - if (cATTACHTOBBS(Session, APPLMASK, PORT->PORTPACLEN, &CONERROR) == 0) + if (cATTACHTOBBS(Session, LINK->APPLMASK, PORT->PORTPACLEN, &CONERROR) == 0) { // NO BBS AVAILABLE @@ -1380,7 +1411,8 @@ VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffe hookL2SessionAccepted(PORT->PORTNUMBER, fromCall, toCall, LINK); - + SendL2ToMonMap(PORT, fromCall, '+', 'I'); + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) { struct DATAMESSAGE * Msg; @@ -1848,8 +1880,14 @@ VOID L2_PROCESS(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * B { // RESPONSE TO SABM - SET LINK UP + char fromCall[12]; + + fromCall[ConvFromAX25(Buffer->ORIGIN, fromCall)] = 0; + RESET2X(LINK); // LEAVE QUEUED STUFF + SendL2ToMonMap(PORT, fromCall, '+', 'O'); + LINK->L2STATE = 5; LINK->L2TIMER = 0; // CANCEL TIMER LINK->L2RETRIES = 0; diff --git a/L3Code.c b/L3Code.c index 65db56e..0acd2f2 100644 --- a/L3Code.c +++ b/L3Code.c @@ -49,7 +49,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "stdio.h" #include -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" VOID UPDATEDESTLIST(); diff --git a/L4Code.c b/L4Code.c index 4efd2d1..81870a8 100644 --- a/L4Code.c +++ b/L4Code.c @@ -31,7 +31,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "stdio.h" #include -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" extern BPQVECSTRUC BPQHOSTVECTOR[]; @@ -68,7 +68,7 @@ VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode); void SendVARANetromMsg(struct TNCINFO * TNC, PL3MESSAGEBUFFER MSG); -extern UINT APPLMASK; +static UINT APPLMASK; extern BOOL LogL4Connects; extern BOOL LogAllConnects; diff --git a/LinBPQ-skigdebian.c b/LinBPQ-skigdebian.c new file mode 100644 index 0000000..60f670e --- /dev/null +++ b/LinBPQ-skigdebian.c @@ -0,0 +1,2154 @@ +/* +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 +*/ + +// Control Routine for LinBPQ + +#define _CRT_SECURE_NO_DEPRECATE + +#include "cheaders.h" +#include "bpqmail.h" +#ifdef WIN32 +#include +//#include "C:\Program Files (x86)\GnuWin32\include\iconv.h" +#else +#include +#include +#ifndef MACBPQ +#ifndef FREEBSD +#include +#endif +#endif +#endif + +#include "time.h" + +#define Connect(stream) SessionControl(stream,1,0) +#define Disconnect(stream) SessionControl(stream,2,0) +#define ReturntoNode(stream) SessionControl(stream,3,0) +#define ConnectUsingAppl(stream, appl) SessionControl(stream, 0, appl) + +BOOL APIENTRY Rig_Init(); + + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); +void FreeSemaphore(struct SEM * Semaphore); +VOID CopyConfigFile(char * ConfigName); +VOID SendMailForThread(VOID * Param); +VOID GetUIConfig(); +Dll BOOL APIENTRY Init_IP(); +VOID OpenReportingSockets(); +VOID SetupNTSAliases(char * FN); +int DeleteRedundantMessages(); +BOOL InitializeTNCEmulator(); +VOID FindLostBuffers(); +VOID IPClose(); +DllExport BOOL APIENTRY Rig_Close(); +Dll BOOL APIENTRY Poll_IP(); +BOOL Rig_Poll(); +BOOL Rig_Poll(); +VOID CheckWL2KReportTimer(); +VOID TNCTimer(); +VOID SendLocation(); +int ChatPollStreams(); +void ChatTrytoSend(); +VOID BBSSlowTimer(); +int GetHTMLForms(); +char * AddUser(char * Call, char * password, BOOL BBSFlag); +VOID SaveChatConfigFile(char * ConfigName); +VOID SaveMH(); +int upnpClose(); +void SaveAIS(); +void initAIS(); +void DRATSPoll(); +void RHPPoll(); + +VOID GetPGConfig(); +void SendBBSDataToPktMap(); + +extern uint64_t timeLoadedMS; + +BOOL IncludesMail = FALSE; +BOOL IncludesChat = FALSE; + +BOOL RunMail = FALSE; +BOOL RunChat = FALSE; +BOOL needAIS= FALSE; +BOOL needADSB = FALSE; + +int CloseOnError = 0; + +VOID Poll_AGW(); +BOOL AGWAPIInit(); +int AGWAPITerminate(); + +BOOL AGWActive = FALSE; + +extern int AGWPort; + +BOOL RigActive = FALSE; + +extern ULONG ChatApplMask; +extern char Verstring[]; + +extern char SignoffMsg[]; +extern char AbortedMsg[]; +extern char InfoBoxText[]; // Text to display in Config Info Popup + +extern int LastVer[4]; // In case we need to do somthing the first time a version is run + +extern HWND MainWnd; +extern char BaseDir[]; +extern char BaseDirRaw[]; +extern char MailDir[]; +extern char WPDatabasePath[]; +extern char RlineVer[50]; + +extern BOOL LogBBS; +extern BOOL LogCHAT; +extern BOOL LogTCP; +extern BOOL ForwardToMe; + +extern int LatestMsg; +extern char BBSName[]; +extern char SYSOPCall[]; +extern char BBSSID[]; +extern char NewUserPrompt[]; + +extern int NumberofStreams; +extern int MaxStreams; +extern ULONG BBSApplMask; +extern int BBSApplNum; +extern int ChatApplNum; +extern int MaxChatStreams; + +extern int NUMBEROFTNCPORTS; + +extern int EnableUI; + +extern BOOL AUTOSAVEMH; + +extern FILE * LogHandle[4]; + +#define MaxSockets 64 + +extern ConnectionInfo Connections[MaxSockets+1]; + +time_t LastTrafficTime; +extern int MaintTime; + +#define LOG_BBS 0 +#define LOG_CHAT 1 +#define LOG_TCP 2 +#define LOG_DEBUG_X 3 + +int _MYTIMEZONE = 0; + +// flags equates + +#define F_Excluded 0x0001 +#define F_LOC 0x0002 +#define F_Expert 0x0004 +#define F_SYSOP 0x0008 +#define F_BBS 0x0010 +#define F_PAG 0x0020 +#define F_GST 0x0040 +#define F_MOD 0x0080 +#define F_PRV 0x0100 +#define F_UNP 0x0200 +#define F_NEW 0x0400 +#define F_PMS 0x0800 +#define F_EMAIL 0x1000 +#define F_HOLDMAIL 0x2000 +#define F_POLLRMS 0x4000 +#define F_SYSOP_IN_LM 0x8000 +#define F_Temp_B2_BBS 0x00010000 + +/* #define F_PWD 0x1000 */ + + +extern UCHAR BPQDirectory[260]; +extern UCHAR LogDirectory[260]; +extern UCHAR ConfigDirectory[260]; + +// overrides from params +UCHAR LogDir[260] = ""; +UCHAR ConfigDir[260] = ""; +UCHAR DataDir[260] = ""; + + +BOOL GetConfig(char * ConfigName); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); +int EncryptPass(char * Pass, char * Encrypt); +int APIENTRY FindFreeStream(); +int PollStreams(); +int APIENTRY SetAppl(int stream, int flags, int mask); +int APIENTRY SessionState(int stream, int * state, int * change); +int APIENTRY SessionControl(int stream, int command, int Mask); + +BOOL ChatInit(); +VOID CloseChat(); +VOID CloseTNCEmulator(); + +static config_t cfg; +static config_setting_t * group; + +BOOL MonBBS = TRUE; +BOOL MonCHAT = TRUE; +BOOL MonTCP = TRUE; + +BOOL LogBBS = TRUE; +BOOL LogCHAT = TRUE; +BOOL LogTCP = TRUE; + +extern BOOL LogAPRSIS; + +BOOL UIEnabled[33]; +BOOL UINull[33]; +char * UIDigi[33]; + +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 NTSAliasesPath[MAX_PATH]; +extern char NTSAliasesName[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 time_t MaintClock; // Time to run housekeeping + +#ifdef WIN32 +BOOL KEEPGOING = 30; // 5 secs to shut down +#else +BOOL KEEPGOING = 50; // 5 secs to shut down +#endif +BOOL Restarting = FALSE; +BOOL CLOSING = FALSE; + +int ProgramErrors; +int Slowtimer = 0; + +#define REPORTINTERVAL 15 * 549; // Magic Ticks Per Minute for PC's nominal 100 ms timer +int ReportTimer = 0; + +// Console Terminal Support + +struct ConTermS +{ + int BPQStream; + BOOL Active; + int Incoming; + + char kbbuf[INPUTLEN]; + int kbptr; + + char * KbdStack[MAXSTACK]; + int StackIndex; + + BOOL CONNECTED; + int SlowTimer; +}; + +struct ConTermS ConTerm = {0, 0}; + + +VOID CheckProgramErrors() +{ + if (Restarting) + exit(0); // Make sure can't loop in restarting + + ProgramErrors++; + + if (ProgramErrors > 25) + { + Restarting = TRUE; + + Logprintf(LOG_DEBUG_X, NULL, '!', "Too Many Program Errors - Closing"); + +/* + SInfo.cb=sizeof(SInfo); + SInfo.lpReserved=NULL; + SInfo.lpDesktop=NULL; + SInfo.lpTitle=NULL; + SInfo.dwFlags=0; + SInfo.cbReserved2=0; + SInfo.lpReserved2=NULL; + + GetModuleFileName(NULL, ProgName, 256); + + Debugprintf("Attempting to Restart %s", ProgName); + + CreateProcess(ProgName, "MailChat.exe WAIT", NULL, NULL, FALSE, 0, NULL, NULL, &SInfo, &PInfo); +*/ + exit(0); + } +} + +#ifdef WIN32 + +BOOL CtrlHandler(DWORD fdwCtrlType) +{ + switch( fdwCtrlType ) + { + // Handle the CTRL-C signal. + case CTRL_C_EVENT: + printf( "Ctrl-C event\n\n" ); + CLOSING = TRUE; + Beep( 750, 300 ); + return( TRUE ); + + // CTRL-CLOSE: confirm that the user wants to exit. + case CTRL_CLOSE_EVENT: + + CLOSING = TRUE; + printf( "Ctrl-Close event\n\n" ); + Sleep(20000); + Beep( 750, 300 ); + return( TRUE ); + + // Pass other signals to the next handler. + case CTRL_BREAK_EVENT: + Beep( 900, 200 ); + printf( "Ctrl-Break event\n\n" ); + CLOSING = TRUE; + Beep( 750, 300 ); + return FALSE; + + case CTRL_LOGOFF_EVENT: + Beep( 1000, 200 ); + printf( "Ctrl-Logoff event\n\n" ); + return FALSE; + + case CTRL_SHUTDOWN_EVENT: + Beep( 750, 500 ); + printf( "Ctrl-Shutdown event\n\n" ); + CLOSING = TRUE; + Beep( 750, 300 ); + return FALSE; + + default: + return FALSE; + } +} + +#else + +#include +#include + +// Linux Signal Handlers + +static void segvhandler(int sig) +{ + void *array[10]; + size_t size; + char msg[] = "SIGSEGV Received\n"; + + write(STDERR_FILENO, msg, strlen(msg)); + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + + backtrace_symbols_fd(array, size, STDERR_FILENO); + + exit(1); +} + +static void abrthandler(int sig) +{ + void *array[10]; + size_t size; + char msg[] = "SIGABRT Received\n"; + + write(STDERR_FILENO, msg, strlen(msg)); + + // get void*'s for all entries on the stack + + size = backtrace(array, 10); + backtrace_symbols_fd(array, size, STDERR_FILENO); + + exit(1); +} + +static void sigterm_handler(int sig) +{ + syslog(LOG_INFO, "terminating on SIGTERM\n"); + CLOSING = TRUE; +} + +static void sigint_handler(int sig) +{ + printf("terminating on SIGINT\n"); + CLOSING = TRUE; +} + + +static void sigusr1_handler(int sig) +{ + signal(SIGUSR1, sigusr1_handler); +} + +#endif + + +#ifndef WIN32 + +BOOL CopyFile(char * In, char * Out, BOOL Failifexists) +{ + FILE * Handle; + DWORD FileSize; + char * Buffer; + struct stat STAT; + + if (stat(In, &STAT) == -1) + return FALSE; + + FileSize = STAT.st_size; + + Handle = fopen(In, "rb"); + + if (Handle == NULL) + return FALSE; + + Buffer = malloc(FileSize+1); + + FileSize = fread(Buffer, 1, STAT.st_size, Handle); + + fclose(Handle); + + if (FileSize != STAT.st_size) + { + free(Buffer); + return FALSE; + } + + Handle = fopen(Out, "wb"); + + if (Handle == NULL) + { + free(Buffer); + return FALSE; + } + + FileSize = fwrite(Buffer, 1, STAT.st_size, Handle); + + fclose(Handle); + free(Buffer); + + return TRUE; +} +#endif + +int RefreshMainWindow() +{ + return 0; +} + +int LastSemGets = 0; + +extern int SemHeldByAPI; + +VOID MonitorThread(void * x) +{ + // Thread to detect stuck semaphore + + do + { + if ((Semaphore.Gets == LastSemGets) && Semaphore.Flag) + { + // It is stuck - try to release + + Debugprintf ("Semaphore locked - Process ID = %d, Held By %d from %s Line %d", + Semaphore.SemProcessID, SemHeldByAPI, Semaphore.File, Semaphore.Line); + + + Semaphore.Flag = 0; + } + + LastSemGets = Semaphore.Gets; + + Sleep(30000); +// Debugprintf("Monitor Thread Still going %d %d %d %x %d", LastSemGets, Semaphore.Gets, Semaphore.Flag, Semaphore.SemThreadID, SemHeldByAPI); + + } + while (TRUE); +} + + + + +VOID TIMERINTERRUPT(); + +BOOL Start(); +VOID INITIALISEPORTS(); +Dll BOOL APIENTRY Init_APRS(); +VOID APRSClose(); +Dll VOID APIENTRY Poll_APRS(); +VOID HTTPTimer(); + + +#define CKernel +#include "Versions.h" + +extern struct SEM Semaphore; + +int SemHeldByAPI = 0; +BOOL IGateEnabled = TRUE; +BOOL APRSActive = FALSE; +BOOL ReconfigFlag = FALSE; +BOOL APRSReconfigFlag = FALSE; +BOOL RigReconfigFlag = FALSE; + +BOOL IPActive = FALSE; +extern BOOL IPRequired; + +extern struct WL2KInfo * WL2KReports; + +int InitDone; +char pgm[256] = "LINBPQ"; + +char SESSIONHDDR[80] = ""; +int SESSHDDRLEN = 0; + + +// Next 3 should be uninitialised so they are local to each process + +UCHAR MCOM; +UCHAR MUIONLY; +UCHAR MTX; +uint64_t MMASK; + + +UCHAR AuthorisedProgram; // Local Variable. Set if Program is on secure list + +int SAVEPORT = 0; + +VOID SetApplPorts(); + +char VersionString[50] = Verstring; +char VersionStringWithBuild[50]=Verstring; +int Ver[4] = {Vers}; +char TextVerstring[50] = Verstring; + +extern UCHAR PWLen; +extern char PWTEXT[]; +extern int ISPort; + +extern char ChatConfigName[250]; + +BOOL EventsEnabled = 0; + +UCHAR * GetBPQDirectory() +{ + return BPQDirectory; +} +UCHAR * GetLogDirectory() +{ + return LogDirectory; +} +extern int POP3Timer; + +// Console Terminal Stuff + +#ifndef WIN32 + + + +#define _getch getchar + +/** + Linux (POSIX) implementation of _kbhit(). + Morgan McGuire, morgan@cs.brown.edu + */ + +#include +#include +#include + +int _kbhit() { + static const int STDIN = 0; + static int initialized = 0; + + if (! initialized) { + // Use termios to turn off line buffering + struct termios term; + tcgetattr(STDIN, &term); + term.c_lflag &= ~ICANON; + + tcsetattr(STDIN, TCSANOW, &term); + setbuf(stdin, NULL); + initialized = 1; + } + + int bytesWaiting; + ioctl(STDIN, FIONREAD, &bytesWaiting); + return bytesWaiting; +} + +#endif + +void ConTermInput(char * Msg) +{ + int i; + + if (ConTerm.BPQStream == 0) + { + ConTerm.BPQStream = FindFreeStream(); + + if (ConTerm.BPQStream == 255) + { + ConTerm.BPQStream = 0; + printf("No Free Streams\n"); + return; + } + } + + if (!ConTerm.CONNECTED) + SessionControl(ConTerm.BPQStream, 1, 0); + + ConTerm.StackIndex = 0; + + // Stack it + + if (ConTerm.KbdStack[19]) + free(ConTerm.KbdStack[19]); + + for (i = 18; i >= 0; i--) + { + ConTerm.KbdStack[i+1] = ConTerm.KbdStack[i]; + } + + ConTerm.KbdStack[0] = _strdup(ConTerm.kbbuf); + + ConTerm.kbbuf[ConTerm.kbptr]=13; + + SendMsg(ConTerm.BPQStream, ConTerm.kbbuf, ConTerm.kbptr+1); +} + +void ConTermPoll() +{ + int port, sesstype, paclen, maxframe, l4window, len; + int state, change, InputLen, count; + char callsign[11] = ""; + char Msg[300]; + + // Get current Session State. Any state changed is ACK'ed + // automatically. See BPQHOST functions 4 and 5. + + SessionState(ConTerm.BPQStream, &state, &change); + + if (change == 1) + { + if (state == 1) + { + // Connected + + ConTerm.CONNECTED = TRUE; + ConTerm.SlowTimer = 0; + } + else + { + ConTerm.CONNECTED = FALSE; + printf("*** Disconnected\n"); + } + } + + GetMsg(ConTerm.BPQStream, Msg, &InputLen, &count); + + if (InputLen) + { + char * ptr = Msg; + char * ptr2 = ptr; + Msg[InputLen] = 0; + + while (ptr) + { + ptr2 = strlop(ptr, 13); + + // Replace CR with CRLF + + printf("%s", ptr); + + if (ptr2) + printf("\r\n"); + + ptr = ptr2; + } + } + + if (_kbhit()) + { + unsigned char c = _getch(); + + if (c == 0xe0) + { + // Cursor control + + c = _getch(); + + if (c == 75) // cursor left + c = 8; + } + +#ifdef WIN32 + printf("%c", c); +#endif + if (c == 8) + { + if (ConTerm.kbptr) + ConTerm.kbptr--; + printf(" \b"); // Already echoed bs - clear typed char from screen + return; + } + + if (c == 13 || c == 10) + { + ConTermInput(ConTerm.kbbuf); + ConTerm.kbptr = 0; + return; + } + + ConTerm.kbbuf[ConTerm.kbptr++] = c; + fflush(NULL); + + } + + return; + +} + +#include + +static struct option long_options[] = +{ + {"logdir", required_argument, 0 , 'l'}, + {"configdir", required_argument, 0 , 'c'}, + {"datadir", required_argument, 0 , 'd'}, + {"help", no_argument, 0 , 'h'}, + { NULL , no_argument , NULL , no_argument } +}; + +char HelpScreen[] = + "Usage:\n" + "Optional Paramters\n" + "-l path or --logdir path Path for log files\n" + "-c path or --configdir path Path to Config file bpq32.cfg\n" + "-d path or --datadir path Path to Data Files\n" + "-v Show version and exit\n"; + +int Redirected = 0; + +static void segvhandler(int sig); +static void abrthandler(int sig); + + +int main(int argc, char * argv[]) +{ + int i; + struct UserInfo * user = NULL; + ConnectionInfo * conn; + struct stat STAT; + PEXTPORTDATA PORTVEC; + +#ifdef WIN32 + + WSADATA WsaData; // receives data from WSAStartup + HWND hWnd = GetForegroundWindow(); + + WSAStartup(MAKEWORD(2, 0), &WsaData); + SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE); + + // disable the [x] button. + + if (hWnd != NULL) + { + HMENU hMenu = GetSystemMenu(hWnd, 0); + if (hMenu != NULL) + { + DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + DrawMenuBar(hWnd); + } + } + +#else + + signal(SIGSEGV, segvhandler); + signal(SIGABRT, abrthandler); + + setlinebuf(stdout); + struct sigaction act; + openlog("LINBPQ", LOG_PID, LOG_DAEMON); +#ifndef MACBPQ +#ifndef FREEBSD + prctl(PR_SET_DUMPABLE, 1); // Enable Core Dumps even with setcap +#endif +#endif + + // Disable Console Terminal if stdout redirected + +// printf("STDOUT %d\n",isatty(STDOUT_FILENO)); +// printf("STDIN %d\n",isatty(STDIN_FILENO)); + + if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) + Redirected = 1; + + timeLoadedMS = GetTickCount(); + +#endif + + printf("G8BPQ AX25 Packet Switch System Version %s %s\n", TextVerstring, Datestring); + printf("%s\n", VerCopyright); + + + // look for optarg format parameters + + { + int val; + UCHAR * ptr1; + UCHAR * ptr2; + int c; + + while (1) + { + int option_index = 0; + + c = getopt_long(argc, argv, "l:c:d:hv", long_options, &option_index); + + // Check for end of operation or error + + if (c == -1) + break; + + // Handle options + switch (c) + { + case 'h': + + printf("%s", HelpScreen); + exit (0); + + case 'l': + strcpy(LogDir, optarg); + printf("cc %s\n", LogDir); + break; + + case 'c': + strcpy(ConfigDir, optarg); + break; + + case 'd': + strcpy(DataDir, optarg); + break; + + + case '?': + /* getopt_long already printed an error message. */ + break; + + case 'v': + return 0; + } + } + } + + sprintf(RlineVer, "LinBPQ%d.%d.%d", Ver[0], Ver[1], Ver[2]); + + + Debugprintf("G8BPQ AX25 Packet Switch System Version %s %s", TextVerstring, Datestring); + +#ifndef MACBPQ + _MYTIMEZONE = _timezone; +#endif + + if (_MYTIMEZONE < -86400 || _MYTIMEZONE > 86400) + _MYTIMEZONE = 0; + +#ifdef WIN32 + GetCurrentDirectory(256, BPQDirectory); +#else + getcwd(BPQDirectory, 256); +#endif + + strcpy(ConfigDirectory, BPQDirectory); + strcpy(LogDirectory, BPQDirectory); + + Consoleprintf("Current Directory is %s", BPQDirectory); + + if (LogDir[0]) + { + strcpy(LogDirectory, LogDir); + } + if (DataDir[0]) + { + strcpy(BPQDirectory, DataDir); + Consoleprintf("Working Directory is %s", BPQDirectory); + } + if (ConfigDir[0]) + { + strcpy(ConfigDirectory, ConfigDir); + Consoleprintf("Config Directory is %s", ConfigDirectory); + } + + for (i = optind; i < argc; i++) + { + if (_memicmp(argv[i], "logdir=", 7) == 0) + { + strcpy(LogDirectory, &argv[i][7]); + Consoleprintf("Log Directory is %s\n", LogDirectory); + break; + } + } + + Consoleprintf("Log Directory is %s", LogDirectory); + + // Make sure logs directory exists + + sprintf(LogDir, "%s/logs", LogDirectory); + +#ifdef WIN32 + CreateDirectory(LogDir, NULL); +#else + printf("Making Directory %s\n", LogDir); + i = mkdir(LogDir, S_IRWXU | S_IRWXG | S_IRWXO); + if (i == -1 && errno != EEXIST) + { + perror("Couldn't create log directory\n"); + return 0; + } + chmod(LogDir, S_IRWXU | S_IRWXG | S_IRWXO); +#endif + + if (!ProcessConfig()) + { + WritetoConsoleLocal("Configuration File Error\n"); + return (0); + } + + SESSHDDRLEN = sprintf(SESSIONHDDR, "G8BPQ Network System %s for Linux (", TextVerstring); + +#ifdef MACBPQ + SESSHDDRLEN = sprintf(SESSIONHDDR, "G8BPQ Network System %s for MAC (", TextVerstring); +#endif +#ifdef FREEBSD + SESSHDDRLEN = sprintf(SESSIONHDDR, "G8BPQ Network System %s for FreeBSD (", TextVerstring); +#endif + + + GetSemaphore(&Semaphore, 0); + + if (Start() != 0) + { + FreeSemaphore(&Semaphore); + return (0); + } + + for (i=0;PWTEXT[i] > 0x20;i++); //Scan for cr or null + + PWLen=i; + + SetApplPorts(); + + GetUIConfig(); + + INITIALISEPORTS(); + + if (IPRequired) IPActive = Init_IP(); + + APRSActive = Init_APRS(); + + if (ISPort == 0) + IGateEnabled = 0; + + if (needAIS) + initAIS(); + + RigActive = Rig_Init(); + + FreeSemaphore(&Semaphore); + + OpenReportingSockets(); + + initUTF8(); + + InitDone = TRUE; + + Debugprintf("Monitor Thread ID %x", _beginthread(MonitorThread, 0, 0)); + + +#ifdef WIN32 +#else + openlog("LINBPQ", LOG_PID, LOG_DAEMON); + + memset (&act, '\0', sizeof(act)); + + act.sa_handler = &sigint_handler; + if (sigaction(SIGINT, &act, NULL) < 0) + perror ("SIGINT"); + + act.sa_handler = &sigterm_handler; + if (sigaction(SIGTERM, &act, NULL) < 0) + perror ("sigaction"); + + act.sa_handler = SIG_IGN; + if (sigaction(SIGHUP, &act, NULL) < 0) + perror ("SIGHUP"); + + if (sigaction(SIGPIPE, &act, NULL) < 0) + perror ("SIGPIPE"); + +#endif + + for (i = optind; i < argc; i++) + { + if (_stricmp(argv[i], "chat") == 0) + IncludesChat = TRUE; + } + + if (IncludesChat) + { + RunChat = TRUE; + + printf("Starting Chat\n"); + + sprintf (ChatConfigName, "%s/chatconfig.cfg", BPQDirectory); + printf("Config File is %s\n", ChatConfigName); + + if (stat(ChatConfigName, &STAT) == -1) + { + printf("Chat Config File not found - creating a default config\n"); + ChatApplNum = 2; + MaxChatStreams = 10; + SaveChatConfigFile(ChatConfigName); + } + + if (GetChatConfig(ChatConfigName) == EXIT_FAILURE) + { + printf("Chat Config File seems corrupt - check before continuing\n"); + return -1; + } + + if (ChatApplNum) + { + if (ChatInit() == 0) + { + printf("Chat Init Failed\n"); + RunChat = 0; + } + else + { + printf("Chat Started\n"); + } + } + else + { + printf("Chat APPLNUM not defined\n"); + RunChat = 0; + } + CopyConfigFile(ChatConfigName); + } + + // Start Mail if requested by command line or config + + for (i = optind; i < argc; i++) + { + if (_stricmp(argv[i], "mail") == 0) + IncludesMail = TRUE; + } + + + if (IncludesMail) + { + RunMail = TRUE; + + printf("Starting Mail\n"); + + sprintf (ConfigName, "%s/linmail.cfg", BPQDirectory); + printf("Config File is %s\n", ConfigName); + + if (stat(ConfigName, &STAT) == -1) + { + printf("Config File not found - creating a default config\n"); + strcpy(BBSName, MYNODECALL); + strlop(BBSName, '-'); + strlop(BBSName, ' '); + BBSApplNum = 1; + MaxStreams = 10; + SaveConfig(ConfigName); + } + + if (GetConfig(ConfigName) == EXIT_FAILURE) + { + printf("BBS Config File seems corrupt - check before continuing\n"); + return -1; + } + + printf("Config Processed\n"); + + BBSApplMask = 1<<(BBSApplNum-1); + + // See if we need to warn of possible problem with BaseDir moved by installer + + sprintf(BaseDir, "%s", BPQDirectory); + + + // Set up file and directory names + + strcpy(UserDatabasePath, BaseDir); + strcat(UserDatabasePath, "/"); + strcat(UserDatabasePath, UserDatabaseName); + + strcpy(MsgDatabasePath, BaseDir); + strcat(MsgDatabasePath, "/"); + strcat(MsgDatabasePath, MsgDatabaseName); + + strcpy(BIDDatabasePath, BaseDir); + strcat(BIDDatabasePath, "/"); + strcat(BIDDatabasePath, BIDDatabaseName); + + strcpy(WPDatabasePath, BaseDir); + strcat(WPDatabasePath, "/"); + strcat(WPDatabasePath, WPDatabaseName); + + strcpy(BadWordsPath, BaseDir); + strcat(BadWordsPath, "/"); + strcat(BadWordsPath, BadWordsName); + + strcpy(NTSAliasesPath, BaseDir); + strcat(NTSAliasesPath, "/"); + strcat(NTSAliasesPath, NTSAliasesName); + + strcpy(MailDir, BaseDir); + strcat(MailDir, "/"); + strcat(MailDir, "Mail"); + +#ifdef WIN32 + CreateDirectory(MailDir, NULL); // Just in case +#else + mkdir(MailDir, S_IRWXU | S_IRWXG | S_IRWXO); + chmod(MailDir, S_IRWXU | S_IRWXG | S_IRWXO); +#endif + + // Make backup copies of Databases + + // CopyConfigFile(ConfigName); + + CopyBIDDatabase(); + CopyMessageDatabase(); + CopyUserDatabase(); + CopyWPDatabase(); + + SetupMyHA(); + SetupFwdAliases(); + SetupNTSAliases(NTSAliasesPath); + + GetWPDatabase(); + + GetMessageDatabase(); + GetUserDatabase(); + GetBIDDatabase(); + GetBadWordFile(); + GetHTMLForms(); + GetPGConfig(); + + // Make sure there is a user record for the BBS, with BBS bit set. + + user = LookupCall(BBSName); + + if (user == NULL) + { + user = AllocateUserRecord(BBSName); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + } + + if ((user->flags & F_BBS) == 0) + { + // Not Defined as a BBS + + if(SetupNewBBS(user)) + user->flags |= F_BBS; + } + + // if forwarding AMPR mail make sure User/BBS AMPR exists + + if (SendAMPRDirect) + { + BOOL NeedSave = FALSE; + + user = LookupCall("AMPR"); + + if (user == NULL) + { + user = AllocateUserRecord("AMPR"); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + NeedSave = TRUE; + } + + if ((user->flags & F_BBS) == 0) + { + // Not Defined as a BBS + + if (SetupNewBBS(user)) + user->flags |= F_BBS; + NeedSave = TRUE; + } + + if (NeedSave) + SaveUserDatabase(); + } + + + // Make sure SYSOPCALL is set + + if (SYSOPCall[0] == 0) + strcpy(SYSOPCall, BBSName); + + // See if just want to add user (mainly for setup scripts) + + if (argc == 5 && _stricmp(argv[1], "--adduser") == 0) + { + BOOL isBBS = FALSE; + char * response; + + if (_stricmp(argv[4], "TRUE") == 0) + isBBS = TRUE; + + printf("Adding User %s\r\n", argv[2]); + response = AddUser(argv[2], argv[3], isBBS); + printf("%s", response); + exit(0); + } + // Allocate Streams + + strcpy(pgm, "BBS"); + + for (i=0; i < MaxStreams; i++) + { + conn = &Connections[i]; + conn->BPQStream = FindFreeStream(); + + if (conn->BPQStream == 255) break; + + NumberofStreams++; + + // BPQSetHandle(conn->BPQStream, hWnd); + + SetAppl(conn->BPQStream, (i == 0 && EnableUI) ? 0x82 : 2, BBSApplMask); + Disconnect(conn->BPQStream); + } + + strcpy(pgm, "LINBPQ"); + + InitialiseTCP(); + InitialiseNNTP(); + + SetupListenSet(); // Master set of listening sockets + + if (EnableUI || MailForInterval) + SetupUIInterface(); + + if (MailForInterval) + _beginthread(SendMailForThread, 0, 0); + + + // Calulate time to run Housekeeping + { + struct tm *tm; + time_t now; + + now = time(NULL); + + tm = gmtime(&now); + + tm->tm_hour = MaintTime / 100; + tm->tm_min = MaintTime % 100; + tm->tm_sec = 0; + + MaintClock = mktime(tm) - (time_t)_MYTIMEZONE; + + while (MaintClock < now) + MaintClock += MaintInterval * 3600; + + Debugprintf("Maint Clock %lld NOW %lld Time to HouseKeeping %lld", (long long)MaintClock, (long long)now, (long long)(MaintClock - now)); + + if (LastHouseKeepingTime) + { + if ((now - LastHouseKeepingTime) > MaintInterval * 3600) + { + DoHouseKeeping(FALSE); + } + } + for (i = optind; i < argc; i++) + + { + if (_stricmp(argv[i], "tidymail") == 0) + DeleteRedundantMessages(); + + if (_stricmp(argv[i], "nohomebbs") == 0) + DontNeedHomeBBS = TRUE; + } + + printf("Mail Started\n"); + Logprintf(LOG_BBS, NULL, '!', "Mail Starting"); + + APIClock = 0; + + SendBBSDataToPktMap(); + + } + } + + if (NUMBEROFTNCPORTS) + InitializeTNCEmulator(); + + AGWActive = AGWAPIInit(); + + if (Redirected == 0) + ConTerm.BPQStream = FindFreeStream(); + + +#ifndef WIN32 + + for (i = 1; i < argc; i++) + { + if (_stricmp(argv[i], "daemon") == 0) + { + + // Convert to daemon + + pid_t pid, sid; + + /* Fork off the parent process */ + pid = fork(); + + if (pid < 0) + exit(EXIT_FAILURE); + + if (pid > 0) + exit(EXIT_SUCCESS); + + /* Change the file mode mask */ + + umask(0); + + /* Create a new SID for the child process */ + + sid = setsid(); + + if (sid < 0) + exit(EXIT_FAILURE); + + /* Change the current working directory */ + + if ((chdir("/")) < 0) + exit(EXIT_FAILURE); + + /* Close out the standard file descriptors */ + + printf("Entering daemon mode\n"); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + break; + } + } +#endif + + while (KEEPGOING) + { + Sleep(100); + GetSemaphore(&Semaphore, 2); + + if (QCOUNT < 10) + { + if (CLOSING == FALSE) + FindLostBuffers(); + CLOSING = TRUE; + } + + if (CLOSING) + { + if (RunChat) + { + CloseChat(); + RunChat = FALSE; + } + + if (RunMail) + { + int BPQStream, n; + + RunMail = FALSE; + + for (n = 0; n < NumberofStreams; n++) + { + BPQStream = Connections[n].BPQStream; + + if (BPQStream) + { + SetAppl(BPQStream, 0, 0); + Disconnect(BPQStream); + DeallocateStream(BPQStream); + } + } + +// SaveUserDatabase(); + SaveMessageDatabase(); + SaveBIDDatabase(); + SaveConfig(ConfigName); + } + + KEEPGOING--; // Give time for links to close + setbuf(stdout, NULL); + printf("Closing... %d \r", KEEPGOING); + } + + + if (RigReconfigFlag) + { + RigReconfigFlag = FALSE; + Rig_Close(); + Sleep(2000); // Allow CATPTT threads to close + RigActive = Rig_Init(); + + Consoleprintf("Rigcontrol Reconfiguration Complete"); + } + + if (APRSReconfigFlag) + { + APRSReconfigFlag = FALSE; + APRSClose(); + APRSActive = Init_APRS(); + + Consoleprintf("APRS Reconfiguration Complete"); + } + + if (ReconfigFlag) + { + int i; + BPQVECSTRUC * HOSTVEC; + PEXTPORTDATA PORTVEC=(PEXTPORTDATA)PORTTABLE; + + ReconfigFlag = FALSE; + +// SetupBPQDirectory(); + + WritetoConsoleLocal("Reconfiguring ...\n\n"); + OutputDebugString("BPQ32 Reconfiguring ...\n"); + + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR) + { +// SaveWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); +// SaveAXIPWindowPos(PORTVEC->PORTCONTROL.PORTNUMBER); +// CloseDriverWindow(PORTVEC->PORTCONTROL.PORTNUMBER); + PORTVEC->PORT_EXT_ADDR(5,PORTVEC->PORTCONTROL.PORTNUMBER, NULL); // Close External Ports + } + } + PORTVEC->PORTCONTROL.PORTCLOSECODE(&PORTVEC->PORTCONTROL); + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + IPClose(); + APRSClose(); + Rig_Close(); + CloseTNCEmulator(); + + if (AGWActive) + AGWAPITerminate(); + + WL2KReports = NULL; + +// Sleep(2000); + + Consoleprintf("G8BPQ AX25 Packet Switch System Version %s %s", TextVerstring, Datestring); + Consoleprintf(VerCopyright); + + Start(); + + INITIALISEPORTS(); + + SetApplPorts(); + + GetUIConfig(); + + FreeConfig(); + + for (i=1; i<68; i++) // Include Telnet, APRS, IP Vec + { + HOSTVEC=&BPQHOSTVECTOR[i-1]; + + HOSTVEC->HOSTTRACEQ=0; + + if (HOSTVEC->HOSTSESSION !=0) + { + // Had a connection + + HOSTVEC->HOSTSESSION=0; + HOSTVEC->HOSTFLAGS |=3; // Disconnected + +// PostMessage(HOSTVEC->HOSTHANDLE, BPQMsg, i, 4); + } + } + + OpenReportingSockets(); + + WritetoConsoleLocal("\n\nReconfiguration Complete\n"); + + if (IPRequired) IPActive = Init_IP(); + + APRSActive = Init_APRS(); + + if (ISPort == 0) + IGateEnabled = 0; + + RigActive = Rig_Init(); + + if (NUMBEROFTNCPORTS) + { + FreeSemaphore(&Semaphore); + InitializeTNCEmulator(); + GetSemaphore(&Semaphore, 2); + } + + FreeSemaphore(&Semaphore); + AGWActive = AGWAPIInit(); + GetSemaphore(&Semaphore, 2); + + OutputDebugString("BPQ32 Reconfiguration Complete\n"); + } + + if (IPActive) Poll_IP(); + if (RigActive) Rig_Poll(); + if (APRSActive) Poll_APRS(); + CheckWL2KReportTimer(); + + TIMERINTERRUPT(); + + FreeSemaphore(&Semaphore); + + if (Redirected == 0) + ConTermPoll(); + + if (NUMBEROFTNCPORTS) + TNCTimer(); + + if (AGWActive) + Poll_AGW(); + + DRATSPoll(); + RHPPoll(); + + HTTPTimer(); + + if (ReportTimer) + { + ReportTimer--; + + if (ReportTimer == 0) + { + ReportTimer = REPORTINTERVAL; + SendLocation(); + } + } + + Slowtimer++; + + if (RunChat) + { + ChatPollStreams(); + ChatTrytoSend(); + + if (Slowtimer > 100) // 10 secs + { + ChatTimer(); + } + } + + if (RunMail) + { + PollStreams(); + + if ((Slowtimer % 20) == 0) + FWDTimerProc(); + + if (Slowtimer > 100) // 10 secs + { + time_t NOW = time(NULL); + struct tm * tm; + + TCPTimer(); + BBSSlowTimer(); + + if (MaintClock < NOW) + { + while (MaintClock < NOW) // in case large time step + MaintClock += MaintInterval * 3600; + + Debugprintf("|Enter HouseKeeping"); + DoHouseKeeping(FALSE); + } + + if (APIClock < NOW) + { + SendBBSDataToPktMap(); + APIClock = NOW + 7200; // Every 2 hours + } + + + tm = gmtime(&NOW); + + if (tm->tm_wday == 0) // Sunday + { + if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) + { + CreateBBSTrafficReport(); + LastTrafficTime = NOW; + } + } + } + TCPFastTimer(); + TrytoSend(); + } + + if (Slowtimer > 100) + Slowtimer = 0; + } + + printf("Closing Ports\n"); + + CloseTNCEmulator(); + + if (AGWActive) + AGWAPITerminate(); + + if (needAIS) + SaveAIS(); + + // Close Ports + + PORTVEC=(PEXTPORTDATA)PORTTABLE; + + for (i=0;iPORTCONTROL.PORTTYPE == 0x10) // External + { + if (PORTVEC->PORT_EXT_ADDR) + { + PORTVEC->PORT_EXT_ADDR(5, PORTVEC->PORTCONTROL.PORTNUMBER, NULL); + } + } + PORTVEC=(PEXTPORTDATA)PORTVEC->PORTCONTROL.PORTPOINTER; + } + + if (AUTOSAVE) + SaveNodes(); + + if (AUTOSAVEMH) + SaveMH(); + + if (IPActive) + IPClose(); + + if (RunMail) + FreeWebMailMallocs(); + + upnpClose(); + + // Close any open logs + + for (i = 0; i < 4; i++) + { + if (LogHandle[i]) + fclose(LogHandle[i]); + } + + return 0; +} + +int APIENTRY WritetoConsole(char * buff) +{ + return WritetoConsoleLocal(buff); +} + +int WritetoConsoleLocal(char * buff) +{ + return printf("%s", buff); +} + +#ifdef WIN32 +void * VCOMExtInit(struct PORTCONTROL * PortEntry); +void * V4ExtInit(EXTPORTDATA * PortEntry); +#endif +//UINT SoundModemExtInit(EXTPORTDATA * PortEntry); +//UINT BaycomExtInit(EXTPORTDATA * PortEntry); + +void * AEAExtInit(struct PORTCONTROL * PortEntry); +void * MPSKExtInit(EXTPORTDATA * PortEntry); +void * HALExtInit(struct PORTCONTROL * PortEntry); + +void * AGWExtInit(struct PORTCONTROL * PortEntry); +void * KAMExtInit(struct PORTCONTROL * PortEntry); +void * WinmorExtInit(EXTPORTDATA * PortEntry); +void * SCSExtInit(struct PORTCONTROL * PortEntry); +void * TrackerExtInit(EXTPORTDATA * PortEntry); +void * TrackerMExtInit(EXTPORTDATA * PortEntry); + +void * TelnetExtInit(EXTPORTDATA * PortEntry); +void * UZ7HOExtInit(EXTPORTDATA * PortEntry); +void * FLDigiExtInit(EXTPORTDATA * PortEntry); +void * ETHERExtInit(struct PORTCONTROL * PortEntry); +void * AXIPExtInit(struct PORTCONTROL * PortEntry); +void * ARDOPExtInit(EXTPORTDATA * PortEntry); +void * VARAExtInit(EXTPORTDATA * PortEntry); +void * SerialExtInit(EXTPORTDATA * PortEntry); +void * WinRPRExtInit(EXTPORTDATA * PortEntry); +void * HSMODEMExtInit(EXTPORTDATA * PortEntry); +void * FreeDataExtInit(EXTPORTDATA * PortEntry); +void * KISSHFExtInit(EXTPORTDATA * PortEntry); + +void * InitializeExtDriver(PEXTPORTDATA PORTVEC) +{ + // Only works with built in drivers + + UCHAR Value[20]; + + strcpy(Value,PORTVEC->PORT_DLL_NAME); + + _strupr(Value); + +#ifndef FREEBSD +#ifndef MACBPQ + if (strstr(Value, "BPQETHER")) + return ETHERExtInit; +#endif +#endif + if (strstr(Value, "BPQAXIP")) + return AXIPExtInit; + + if (strstr(Value, "BPQTOAGW")) + return AGWExtInit; + + if (strstr(Value, "AEAPACTOR")) + return AEAExtInit; + + if (strstr(Value, "HALDRIVER")) + return HALExtInit; + +#ifdef WIN32 + + if (strstr(Value, "BPQVKISS")) + return VCOMExtInit; + + if (strstr(Value, "V4")) + return V4ExtInit; + +#endif +/* + if (strstr(Value, "SOUNDMODEM")) + return (UINT) SoundModemExtInit; + + if (strstr(Value, "BAYCOM")) + return (UINT) BaycomExtInit; +*/ + if (strstr(Value, "MULTIPSK")) + return MPSKExtInit; + + if (strstr(Value, "KAMPACTOR")) + return KAMExtInit; + + if (strstr(Value, "WINMOR")) + return WinmorExtInit; + + if (strstr(Value, "SCSPACTOR")) + return SCSExtInit; + + if (strstr(Value, "SCSTRACKER")) + return TrackerExtInit; + + if (strstr(Value, "TRKMULTI")) + return TrackerMExtInit; + + if (strstr(Value, "UZ7HO")) + return UZ7HOExtInit; + + if (strstr(Value, "FLDIGI")) + return FLDigiExtInit; + + if (strstr(Value, "TELNET")) + return TelnetExtInit; + + if (strstr(Value, "ARDOP")) + return ARDOPExtInit; + + if (strstr(Value, "VARA")) + return VARAExtInit; + + if (strstr(Value, "KISSHF")) + return KISSHFExtInit; + + if (strstr(Value, "SERIAL")) + return SerialExtInit; + + if (strstr(Value, "WINRPR")) + return WinRPRExtInit; + + if (strstr(Value, "HSMODEM")) + return HSMODEMExtInit; + + if (strstr(Value, "FREEDATA")) + return FreeDataExtInit; + + return(0); +} + +int APIENTRY Restart() +{ + CLOSING = TRUE; + return TRUE; +} + +int APIENTRY Reboot() +{ + // Run sudo shutdown -r -f +#ifdef WIN32 + STARTUPINFO SInfo; + PROCESS_INFORMATION PInfo; + char Cmd[] = "shutdown -r -f"; + + + SInfo.cb=sizeof(SInfo); + SInfo.lpReserved=NULL; + SInfo.lpDesktop=NULL; + SInfo.lpTitle=NULL; + SInfo.dwFlags=0; + SInfo.cbReserved2=0; + SInfo.lpReserved2=NULL; + + return CreateProcess(NULL, Cmd, NULL, NULL, FALSE,0 ,NULL ,NULL, &SInfo, &PInfo); + return 0; +#else + + char * arg_list[] = {NULL, NULL, NULL, NULL, NULL}; + pid_t child_pid; + char * Context; + signal(SIGCHLD, SIG_IGN); // Silently (and portably) reap children. + + arg_list[0] = "sudo"; + arg_list[1] = "shutdown"; + arg_list[2] = "now"; + arg_list[3] = "-r"; + + // Fork and Exec shutdown + + // Duplicate this process. + + child_pid = fork(); + + if (child_pid == -1) + { + printf ("Reboot fork() Failed\n"); + return 0; + } + + if (child_pid == 0) + { + execvp (arg_list[0], arg_list); + + /* The execvp function returns only if an error occurs. */ + + printf ("Failed to run shutdown\n"); + exit(0); // Kill the new process + } + return TRUE; +#endif + +} + +int APIENTRY Reconfig() +{ + if (!ProcessConfig()) + { + return (0); + } + SaveNodes(); + WritetoConsoleLocal("Nodes Saved\n"); + ReconfigFlag=TRUE; + WritetoConsoleLocal("Reconfig requested ... Waiting for Timer Poll\n"); + return 1; +} + +int APRSWriteLog(char * msg); + +VOID MonitorAPRSIS(char * Msg, size_t MsgLen, BOOL TX) +{ + char Line[300]; + char Copy[300]; + int Len; + struct tm * TM; + time_t NOW; + + if (LogAPRSIS == 0) + return; + + if (MsgLen > 250) + return; + + // Mustn't change Msg + + memcpy(Copy, Msg, MsgLen); + Copy[MsgLen] = 0; + + NOW = time(NULL); + TM = gmtime(&NOW); + + Len = sprintf_s(Line, 299, "%02d:%02d:%02d%c %s", TM->tm_hour, TM->tm_min, TM->tm_sec, (TX)? 'T': 'R', Copy); + + APRSWriteLog(Line); + +} + +struct TNCINFO * TNC; + +#ifndef WIN32 + +#include +#include + +#ifndef MACBPQ +#ifdef __MACH__ + +#include + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 + + + +int clock_gettime(int clk_id, struct timespec *t){ + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + uint64_t time; + time = mach_absolute_time(); + double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); + double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); + t->tv_sec = seconds; + t->tv_nsec = nseconds; + return 0; +} +#endif +#endif + + +uint64_t GetTickCount() +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); +} + + + +void SetWindowText(HWND hWnd, char * lpString) +{ + return; +}; + +BOOL SetDlgItemText(HWND hWnd, int item, char * lpString) +{ + return 0; +}; + +#endif + +int GetListeningPortsPID(int Port) +{ +#ifdef WIN32 + + MIB_TCPTABLE_OWNER_PID * TcpTable = NULL; + PMIB_TCPROW_OWNER_PID Row; + int dwSize = 0; + unsigned int n; + + // Get PID of process for this TCP Port + + // Get Length of table + + GetExtendedTcpTable(TcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0); + + TcpTable = malloc(dwSize); + GetExtendedTcpTable(TcpTable, &dwSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_LISTENER, 0); + + for (n = 0; n < TcpTable->dwNumEntries; n++) + { + Row = &TcpTable->table[n]; + + if (Row->dwLocalPort == Port && Row->dwState == MIB_TCP_STATE_LISTEN) + { + return Row->dwOwningPid; + break; + } + } +#endif + return 0; // Not found +} + + + +VOID Check_Timer() +{ +} + +VOID POSTDATAAVAIL(){}; + +COLORREF Colours[256] = {0, + RGB(0,0,0), RGB(0,0,128), RGB(0,0,192), RGB(0,0,255), // 1 - 4 + RGB(0,64,0), RGB(0,64,128), RGB(0,64,192), RGB(0,64,255), // 5 - 8 + RGB(0,128,0), RGB(0,128,128), RGB(0,128,192), RGB(0,128,255), // 9 - 12 + RGB(0,192,0), RGB(0,192,128), RGB(0,192,192), RGB(0,192,255), // 13 - 16 + RGB(0,255,0), RGB(0,255,128), RGB(0,255,192), RGB(0,255,255), // 17 - 20 + + RGB(6425,0,0), RGB(64,0,128), RGB(64,0,192), RGB(0,0,255), // 21 + RGB(64,64,0), RGB(64,64,128), RGB(64,64,192), RGB(64,64,255), + RGB(64,128,0), RGB(64,128,128), RGB(64,128,192), RGB(64,128,255), + RGB(64,192,0), RGB(64,192,128), RGB(64,192,192), RGB(64,192,255), + RGB(64,255,0), RGB(64,255,128), RGB(64,255,192), RGB(64,255,255), + + RGB(128,0,0), RGB(128,0,128), RGB(128,0,192), RGB(128,0,255), // 41 + RGB(128,64,0), RGB(128,64,128), RGB(128,64,192), RGB(128,64,255), + RGB(128,128,0), RGB(128,128,128), RGB(128,128,192), RGB(128,128,255), + RGB(128,192,0), RGB(128,192,128), RGB(128,192,192), RGB(128,192,255), + RGB(128,255,0), RGB(128,255,128), RGB(128,255,192), RGB(128,255,255), + + RGB(192,0,0), RGB(192,0,128), RGB(192,0,192), RGB(192,0,255), // 61 + RGB(192,64,0), RGB(192,64,128), RGB(192,64,192), RGB(192,64,255), + RGB(192,128,0), RGB(192,128,128), RGB(192,128,192), RGB(192,128,255), + RGB(192,192,0), RGB(192,192,128), RGB(192,192,192), RGB(192,192,255), + RGB(192,255,0), RGB(192,255,128), RGB(192,255,192), RGB(192,255,255), + + RGB(255,0,0), RGB(255,0,128), RGB(255,0,192), RGB(255,0,255), // 81 + RGB(255,64,0), RGB(255,64,128), RGB(255,64,192), RGB(255,64,255), + RGB(255,128,0), RGB(255,128,128), RGB(255,128,192), RGB(255,128,255), + RGB(255,192,0), RGB(255,192,128), RGB(255,192,192), RGB(255,192,255), + RGB(255,255,0), RGB(255,255,128), RGB(255,255,192), RGB(255,255,255) +}; + + +//VOID SendRPBeacon(struct TNCINFO * TNC) +//{ +//} + +int PollStreams() +{ + int state,change; + ConnectionInfo * conn; + int n; + struct UserInfo * user = NULL; + char ConnectedMsg[] = "*** CONNECTED "; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + DoReceivedData(conn->BPQStream); + DoBBSMonitorData(conn->BPQStream); + + SessionState(conn->BPQStream, &state, &change); + + if (change == 1) + { + if (state == 1) // Connected + { + GetSemaphore(&ConSemaphore, 0); + Connected(conn->BPQStream); + FreeSemaphore(&ConSemaphore); + } + else + { + GetSemaphore(&ConSemaphore, 0); + Disconnected(conn->BPQStream); + FreeSemaphore(&ConSemaphore); + } + } + } + + return 0; +} + + +VOID CloseConsole(int Stream) +{ +} + +#ifndef WIN32 + +int V4ProcessReceivedData(struct TNCINFO * TNC) +{ + return 0; +} +#endif + +#ifdef FREEBSD + +char * gcvt(double _Val, int _NumOfDigits, char * _DstBuf) +{ + sprintf(_DstBuf, "%f", _Val); + return _DstBuf; +} + +#endif + + + + + diff --git a/LinBPQ.c b/LinBPQ.c index bd380e1..295afd8 100644 --- a/LinBPQ.c +++ b/LinBPQ.c @@ -21,7 +21,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include "bpqmail.h" #ifdef WIN32 #include @@ -45,7 +45,11 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses BOOL APIENTRY Rig_Init(); -void GetSemaphore(struct SEM * Semaphore, int ID); + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); void FreeSemaphore(struct SEM * Semaphore); VOID CopyConfigFile(char * ConfigName); VOID SendMailForThread(VOID * Param); @@ -75,6 +79,8 @@ int upnpClose(); void SaveAIS(); void initAIS(); void DRATSPoll(); +void RHPPoll(); + VOID GetPGConfig(); void SendBBSDataToPktMap(); @@ -374,24 +380,41 @@ BOOL CtrlHandler(DWORD fdwCtrlType) #include #include - // Linux Signal Handlers - static void segvhandler(int sig) { - void *array[10]; - size_t size; + void *array[10]; + size_t size; + char msg[] = "SIGSEGV Received\n"; + + write(STDERR_FILENO, msg, strlen(msg)); + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr - // get void*'s for all entries on the stack - size = backtrace(array, 10); + backtrace_symbols_fd(array, size, STDERR_FILENO); - // print out all the frames to stderr - fprintf(stderr, "Error: signal %d:\n", sig); - backtrace_symbols_fd(array, size, STDERR_FILENO); exit(1); } +static void abrthandler(int sig) +{ + void *array[10]; + size_t size; + char msg[] = "SIGABRT Received\n"; + + write(STDERR_FILENO, msg, strlen(msg)); + + // get void*'s for all entries on the stack + + size = backtrace(array, 10); + backtrace_symbols_fd(array, size, STDERR_FILENO); + + exit(1); +} static void sigterm_handler(int sig) { @@ -481,9 +504,10 @@ VOID MonitorThread(void * x) { // It is stuck - try to release - Debugprintf ("Semaphore locked - Process ID = %d, Held By %d", - Semaphore.SemProcessID, SemHeldByAPI); - + Debugprintf ("Semaphore locked - Process ID = %d, Held By %d from %s Line %d", + Semaphore.SemProcessID, SemHeldByAPI, Semaphore.File, Semaphore.Line); + + Semaphore.Flag = 0; } @@ -761,6 +785,8 @@ char HelpScreen[] = int Redirected = 0; static void segvhandler(int sig); +static void abrthandler(int sig); + int main(int argc, char * argv[]) { @@ -792,7 +818,8 @@ int main(int argc, char * argv[]) #else -// signal(SIGSEGV, segvhandler); + signal(SIGSEGV, segvhandler); + signal(SIGABRT, abrthandler); setlinebuf(stdout); struct sigaction act; @@ -1560,6 +1587,7 @@ int main(int argc, char * argv[]) Poll_AGW(); DRATSPoll(); + RHPPoll(); HTTPTimer(); diff --git a/MULTIPSK.c b/MULTIPSK.c index ee14d9a..6f2a9ac 100644 --- a/MULTIPSK.c +++ b/MULTIPSK.c @@ -28,7 +28,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include #include diff --git a/MailDataDefs.c b/MailDataDefs.c index abfbac2..5c33c08 100644 --- a/MailDataDefs.c +++ b/MailDataDefs.c @@ -133,7 +133,7 @@ char HRoute[100]; char AMPRDomain[100]; BOOL SendAMPRDirect = 0; -char SignoffMsg[100]; +char SignoffMsg[120]; char AbortedMsg[100]="\rOutput aborted\r"; diff --git a/MailNode.vcproj.NOTTSDESKTOP.John.user b/MailNode.vcproj.NOTTSDESKTOP.John.user index fa82c00..c98c2c9 100644 --- a/MailNode.vcproj.NOTTSDESKTOP.John.user +++ b/MailNode.vcproj.NOTTSDESKTOP.John.user @@ -10,7 +10,7 @@ > #include -#include "CHeaders.h" +#include "cheaders.h" #include "ipcode.h" diff --git a/RHP-skigdebian.c b/RHP-skigdebian.c new file mode 100644 index 0000000..611e8c7 --- /dev/null +++ b/RHP-skigdebian.c @@ -0,0 +1,641 @@ +/* +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 +*/ + +/* + + Paula (G8PZT)'s Remote Host Protocol interface. + For now only sufficient support for WhatsPac + + +*/ +#define _CRT_SECURE_NO_DEPRECATE + +#include "cheaders.h" +#include "bpq32.h" + +int FindFreeStreamNoSem(); +DllExport int APIENTRY DeallocateStream(int stream); +int SendMsgNoSem(int stream, char * msg, int len); + +static void GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value, int Len); +static int GetJSONInt(char * _REPLYBUFFER, char * Name); + +// Generally Can have multiple RHP connections and each can have multiple RHF Sessions + +struct RHPSessionInfo +{ + SOCKET Socket; // Websocks Socket + int BPQStream; + int Handle; // RHP session ID + int Seq; + char Local[12]; + char Remote[12]; + BOOL Connecting; // Set while waiting for connection to complete + BOOL Listening; + BOOL Connected; +}; + +struct RHPConnectionInfo +{ + SOCKET socket; + struct RHPSessionInfo ** RHPSessions; + int NumberofRHPSessions; +}; + + +struct RHPConnectionInfo ** RHPSockets = NULL; +int NumberofRHPConnections = 0; + +struct RHPSessionInfo ** RHPSessions; +int NumberofRHPSessions; + +char ErrCodes[18][24] = +{"Ok", "Unspecified", "Bad or missing type", "Invalid handle", "No memory", "Bad or missing mode", + + "Invalid local address", + "Invalid remote address" , + "Bad or missing family" , + "Duplicate socket" , + "No such port" , + "Invalid protocol" , + "Bad parameter" , + "No buffers" , + "Unauthorised" , + "No Route" , + "Operation not supported"}; + + + + +extern char pgm[256]; + +SOCKET agwsock; + +extern int SemHeldByAPI; + +char szBuff[80]; + +int WhatsPacConfigured = 1; + + +int RHPPaclen = 236; + + +int processRHCPOpen(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer); +int processRHCPSend(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer); +int processRHCPClose(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer); + + + +void SendWebSockMessage(SOCKET socket, char * Msg, int Len) +{ + int Loops = 0; + int Sent; + int TxLen; + char * OutBuffer = Msg; + + // WebSock Encode. Buffer has 10 bytes on front for header but header len depends on Msg len + + + if (Len < 126) + { + // Two Byte Header + + OutBuffer[8] = 0x81; // Fin, Data + OutBuffer[9] = Len; + + TxLen = Len + 2; + OutBuffer = &Msg[8]; + } + else if (Len < 65536) + { + OutBuffer[6] = 0x81; // Fin, Data + OutBuffer[7] = 126; // Unmasked, Extended Len 16 + OutBuffer[8] = Len >> 8; + OutBuffer[9] = Len & 0xff; + TxLen = Len + 4; + OutBuffer = &Msg[6]; + } + else + { + OutBuffer[0] = 0x81; // Fin, Data + OutBuffer[1] = 127; // Unmasked, Extended Len 64 bits + // Len is 32 bits, so pad with zeros + OutBuffer[2] = 0; + OutBuffer[3] = 0; + OutBuffer[4] = 0; + OutBuffer[5] = 0; + OutBuffer[6] = (Len >> 24) & 0xff; + OutBuffer[7] = (Len >> 16) & 0xff; + OutBuffer[8] = (Len >> 8) & 0xff; + OutBuffer[9] = Len & 0xff; + + TxLen = Len + + 10; + OutBuffer = &Msg[0]; + } + + // Send may block + + Sent = send(socket, OutBuffer, TxLen, 0); + + while (Sent != TxLen && Loops++ < 3000) // 100 secs max + { + if (Sent > 0) // something sent + { + TxLen -= Sent; + memmove(OutBuffer, &OutBuffer[Sent], TxLen); + } + + Sleep(30); + Sent = send(socket, OutBuffer, TxLen, 0); + if (Sent == -1) + break; + } + + free(Msg); + return; +} + + + + +int RHPProcessHTTPMessage(struct ConnectionInfo * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE) +{ + // RHP messages can be sent over Websocks or normal http but I think WhatsPac only uses WebSocks + return 0; +} + + +void ProcessRHPWebSock(SOCKET socket, char * Msg, int MsgLen) +{ + int Loops = 0; + int InputLen = 0; + int Len; + + char Value[16]; + char * OutBuffer = malloc(250000); + + struct RHPConnectionInfo * RHPSocket = NULL; + int n; + + Msg[MsgLen] = 0; + + // Find Connection Record. If none, create one + + for (n = 0; n < NumberofRHPConnections; n++) + { + if (RHPSockets[n]->socket == socket) + { + RHPSocket = RHPSockets[n]; + break; + } + } + + if (RHPSocket == 0) + { + NumberofRHPConnections; + RHPSockets = realloc(RHPSockets, sizeof(void *) * (NumberofRHPConnections + 1)); + RHPSocket = RHPSockets[NumberofRHPConnections] = zalloc(sizeof (struct RHPConnectionInfo)); + NumberofRHPConnections++; + RHPSocket->socket = socket; + } + + +// {"type":"open","id":5,"pfam":"ax25","mode":"stream","port":"1","local":"G8BPQ","remote":"G8BPQ-2","flags":128} +// {"type": "openReply", "id": 82, "handle": 1, "errCode": 0, "errText": "Ok"} +// {"seqno": 0, "type": "status", "handle": 1, "flags": 0} +// ("seqno": 1, "type": "close", "handle": 1} +// {"id":40,"type":"close","handle":1} + +// {"seqno": 0, "type": "status", "handle": 1, "flags": 2}.~. +// {"seqno": 1, "type": "recv", "handle": 1, "data": "Welcome to G8BPQ's Test Switch in Nottingham \rType ? for list of available commands.\r"}. + + GetJSONValue(Msg, "\"type\":", Value, 15); + + if (_stricmp(Value, "open") == 0) + { + Len = processRHCPOpen(RHPSocket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + if (Len) + SendWebSockMessage(RHPSocket->socket, OutBuffer, Len); + return; + } + + if (_stricmp(Value, "send") == 0) + { + Len = processRHCPSend(RHPSocket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + SendWebSockMessage(RHPSocket->socket, OutBuffer, Len); + return; + } + + if (_stricmp(Value, "close") == 0) + { + Len = processRHCPClose(RHPSocket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + SendWebSockMessage(RHPSocket->socket, OutBuffer, Len); + return; + } + Debugprintf(Msg); +} + +void ProcessRHPWebSockClosed(SOCKET socket) +{ + // Close any connections on this scoket and delete socket entry + + struct RHPConnectionInfo * RHPSocket = NULL; + int n; + + // Find Connection Record. CLear any Sessions + + for (n = 0; n < NumberofRHPConnections; n++) + { + if (RHPSockets[n]->socket == socket) + { + RHPSocket = RHPSockets[n]; + + break; + } + } +} + + + +int processRHCPOpen(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer) +{ + //{"type":"open","id":5,"pfam":"ax25","mode":"stream","port":"1","local":"G8BPQ","remote":"G8BPQ-2","flags":128} + + struct RHPSessionInfo * RHPSession = 0; + + char * Value = malloc(strlen(Msg)); // Will always be long enough + int ID; + + char pfam[16]; + char Mode[16]; + int Port; + char Local[16]; + char Remote[16]; + int flags; + int Handle = 1; + int Stream; + unsigned char AXCall[10]; + int Len; + int n; + + // ID seems to be used for control commands like open. SeqNo for data within a session (i Think! + + ID = GetJSONInt(Msg, "\"id\":"); + GetJSONValue(Msg, "\"pfam\":", pfam, 15); + GetJSONValue(Msg, "\"mode\":", Mode, 15); + Port = GetJSONInt(Msg, "\"port\":"); + GetJSONValue(Msg, "\"local\":", Local, 15); + GetJSONValue(Msg, "\"remote\":", Remote, 15); + flags = GetJSONInt(Msg, "\"flags\":"); + + if (_stricmp(pfam, "ax25") != 0) + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); + + if (_stricmp(Mode, "stream") == 0) + { + { + // Allocate a RHP Session + + // See if there is an old one we can reuse + + for (n = 0; n < NumberofRHPSessions; n++) + { + if (RHPSessions[n]->BPQStream == 0) + { + RHPSession = RHPSessions[n]; + Handle = n + 1; + Stream = RHPSessions[n]->BPQStream; + + break; + } + } + + if (RHPSession == 0) + { + RHPSessions = realloc(RHPSessions, sizeof(void *) * (NumberofRHPSessions + 1)); + RHPSession = RHPSessions[NumberofRHPSessions] = zalloc(sizeof (struct RHPSessionInfo)); + NumberofRHPSessions++; + + Handle = NumberofRHPSessions; + } + + strcpy(pgm, "RHP"); + Stream = FindFreeStreamNoSem(); + strcpy(pgm, "bpq32.exe"); + + if (Stream == 255) + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); + + RHPSession->BPQStream = Stream; + RHPSession->Handle = Handle; + RHPSession->Connecting = TRUE; + RHPSession->Socket = RHPSocket->socket; + + strcpy(RHPSession->Local, Local); + strcpy(RHPSession->Remote, Remote); + + Connect(Stream); + + ConvToAX25(Local, AXCall); + ChangeSessionCallsign(Stream, AXCall); + + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 0, \"errText\": \"Ok\"}", ID, Handle); + } + } + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); +} + +int processRHCPSend(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer) +{ + // {"type":"send","handle":1,"data":";;;;;;\r","id":70} + + struct RHPSessionInfo * RHPSession; + + int ID; + char * Data; + char * ptr; + int c; + int Len; + + int Handle = 1; + + Data = malloc(strlen(Msg)); + + ID = GetJSONInt(Msg, "\"id\":"); + Handle = GetJSONInt(Msg, "\"handle\":"); + GetJSONValue(Msg, "\"data\":", Data, strlen(Msg) - 1); + + if (Handle < 1 || Handle > NumberofRHPSessions) + { + free(Data); + return sprintf(ReplyBuffer, "{\"type\": \"sendReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errtext\": \"Invalid handle\"}", ID, Handle); + } + + RHPSession = RHPSessions[Handle - 1]; + + // Look for \ escapes + + ptr = Data; + + while (ptr = strchr(ptr, '\\')) + { + c = ptr[1]; + + switch (c) + { + case 'r': + + *ptr = 13; + break; + + case '\\': + + *ptr = '\\'; + break; + + case '"': + + *ptr = '"'; + break; + } + memmove(ptr + 1, ptr + 2, strlen(ptr + 1)); + ptr++; + } + + Debugprintf(Data); + + Len = strlen(Data); + ptr = Data; + + + while (Len > RHPPaclen) + { + SendMsgNoSem(RHPSession->BPQStream, ptr, RHPPaclen); + Len -= RHPPaclen; + ptr += RHPPaclen; + } + + SendMsgNoSem(RHPSession->BPQStream, ptr, Len); + + free(Data); + return sprintf(ReplyBuffer, "{\"type\": \"sendReply\", \"id\": %d, \"handle\": %d, \"errCode\": 0, \"errText\": \"Ok\", \"status\": %d}", ID, Handle, 2); +} + + +int processRHCPClose(struct RHPConnectionInfo * RHPSocket, char * Msg, char * ReplyBuffer) +{ + + // {"id":70,"type":"close","handle":1} + + + struct RHPSessionInfo * RHPSession; + + int ID; + int Handle = 1; + + char * OutBuffer = malloc(256); + + ID = GetJSONInt(Msg, "\"id\":"); + Handle = GetJSONInt(Msg, "\"handle\":"); + + if (Handle < 1 || Handle > NumberofRHPSessions) + return sprintf(ReplyBuffer, "{\"id\": %d, \"type\": \"closeReply\", \"handle\": %d, \"errcode\": 12, \"errtext\": \"Invalid handle\"}", ID, Handle); + + + RHPSession = RHPSessions[Handle - 1]; + Disconnect(RHPSession->BPQStream); + RHPSession->Connected = 0; + RHPSession->Connecting = 0; + + DeallocateStream(RHPSession->BPQStream); + RHPSession->BPQStream = 0; + + return sprintf(ReplyBuffer, "{\"id\": %d, \"type\": \"closeReply\", \"handle\": %d, \"errcode\": 0, \"errtext\": \"Ok\"}", ID, Handle); +} + + + +void RHPPoll() +{ + int Stream; + int n; + int state, change; + int Len; + char * RHPMsg; + unsigned char Buffer[1024]; // Space to escape control chars + int pktlen, count; + + struct RHPSessionInfo * RHPSession; + + for (n = 0; n < NumberofRHPSessions; n++) + { + RHPSession = RHPSessions[n]; + Stream = RHPSession->BPQStream; + + // See if connected state has changed + + SessionState(Stream, &state, &change); + + if (change == 1) + { + if (state == 1) + { + // Connected + + RHPSession->Seq = 0; + RHPSession->Connecting = FALSE; + RHPSession->Connected = TRUE; + + RHPMsg = malloc(256); + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"status\", \"handle\": %d, \"flags\": 2}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + // Send RHP CTEXT + + RHPMsg = malloc(256); + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"recv\", \"handle\": %d, \"data\": \"Connected to RHP Server\\r\"}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + } + else + { + // Disconnected. Send Close to client + + RHPMsg = malloc(256); + Len = sprintf(&RHPMsg[10], "{\"type\": \"close\", \"seqno\": %d, \"handle\": %d}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + RHPSession->Connected = 0; + RHPSession->Connecting = 0; + + DeallocateStream(RHPSession->BPQStream); + RHPSession->BPQStream = 0; + } + } + do + { + GetMsg(Stream, Buffer, &pktlen, &count); + + if (pktlen > 0) + { + char * ptr = Buffer; + char c; + + Buffer[pktlen] = 0; + + // Message is JSON so Convert CR to \r, \ to \\ " to \" + + while (c = *(ptr)) + { + switch (c) + { + case 13: + + memmove(ptr + 2, ptr + 1, strlen(ptr)); + *(ptr++) = '\\'; + *(ptr++) = 'r'; + break; + + case '"': + + memmove(ptr + 2, ptr + 1, strlen(ptr)); + *(ptr++) = '\\'; + *(ptr++) = '"'; + break; + + case '\\': + + memmove(ptr + 2, ptr + 1, strlen(ptr)); + *(ptr++) = '\\'; + *(ptr++) = '\\'; + break; + } + ptr++; + } + + + RHPMsg = malloc(1024); + + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"recv\", \"handle\": %d, \"data\": \"%s\"}", RHPSession->Seq++, RHPSession->Handle, Buffer); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + } + + } + while (count > 0); + + } +} + + + +static void GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value, int Len) +{ + char * ptr1, * ptr2; + + Value[0] = 0; + + ptr1 = strstr(_REPLYBUFFER, Name); + + if (ptr1 == 0) + return; + + ptr1 += (strlen(Name) + 1); + +// "data":"{\"t\":\"c\",\"n\":\"John\",\"c\":\"G8BPQ\",\"lm\":1737912636,\"le\":1737883907,\"led\":1737758451,\"v\":0.33,\"cc\":[{\"cid\":1,\"lp\":1737917257201,\"le\":1737913735726,\"led\":1737905249785},{\"cid\":0,\"lp\":1737324074107,\"le\":1737323831510,\"led\":1737322973662},{\"cid\":5,\"lp\":1737992107419,\"le\":1737931466510,\"led\":1737770056244}]}\r","id":28} + + // There may be escaped " in data stream + + ptr2 = strchr(ptr1, '"'); + + while (*(ptr2 - 1) == '\\') + { + ptr2 = strchr(ptr2 + 2, '"'); + } + + + if (ptr2) + { + size_t ValLen = ptr2 - ptr1; + if (ValLen > Len) + ValLen = Len; + + memcpy(Value, ptr1, ValLen); + Value[ValLen] = 0; + } + + return; +} + + +static int GetJSONInt(char * _REPLYBUFFER, char * Name) +{ + char * ptr1; + + ptr1 = strstr(_REPLYBUFFER, Name); + + if (ptr1 == 0) + return 0; + + ptr1 += (strlen(Name)); + + return atoi(ptr1); +} + + diff --git a/RHP.c b/RHP.c new file mode 100644 index 0000000..b7002be --- /dev/null +++ b/RHP.c @@ -0,0 +1,711 @@ +/* +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 +*/ + +/* + + Paula (G8PZT)'s Remote Host Protocol interface. + For now only sufficient support for WhatsPac + + +*/ +#define _CRT_SECURE_NO_DEPRECATE + +#include "cheaders.h" +#include "bpq32.h" + +int FindFreeStreamNoSem(); +DllExport int APIENTRY DeallocateStream(int stream); +int SendMsgNoSem(int stream, char * msg, int len); + +static void GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value, int Len); +static int GetJSONInt(char * _REPLYBUFFER, char * Name); + +// Generally Can have multiple RHP connections and each can have multiple RHF Sessions + +struct RHPSessionInfo +{ + SOCKET Socket; // Websocks Socket + int BPQStream; + int Handle; // RHP session ID + int Seq; + char Local[12]; + char Remote[12]; + BOOL Connecting; // Set while waiting for connection to complete + BOOL Listening; + BOOL Connected; +}; + +struct RHPConnectionInfo +{ + SOCKET socket; + struct RHPSessionInfo ** RHPSessions; + int NumberofRHPSessions; +}; + +// Struct passed by beginhread + +struct RHPParamBlock +{ + unsigned char * Msg; + int Len; + SOCKET Socket; +}; + + + +//struct RHPConnectionInfo ** RHPSockets = NULL; +//int NumberofRHPConnections = 0; + +struct RHPSessionInfo ** RHPSessions; +int NumberofRHPSessions; + +char ErrCodes[18][24] = +{"Ok", "Unspecified", "Bad or missing type", "Invalid handle", "No memory", "Bad or missing mode", + + "Invalid local address", + "Invalid remote address" , + "Bad or missing family" , + "Duplicate socket" , + "No such port" , + "Invalid protocol" , + "Bad parameter" , + "No buffers" , + "Unauthorised" , + "No Route" , + "Operation not supported"}; + + + + +extern char pgm[256]; + +SOCKET agwsock; + +extern int SemHeldByAPI; + +char szBuff[80]; + +int WhatsPacConfigured = 1; + + +int RHPPaclen = 236; + + +int processRHCPOpen(SOCKET Socket, char * Msg, char * ReplyBuffer); +int processRHCPSend(SOCKET Socket, char * Msg, char * ReplyBuffer); +int processRHCPClose(SOCKET Socket, char * Msg, char * ReplyBuffer); + + + +void SendWebSockMessage(SOCKET socket, char * Msg, int Len) +{ + int Loops = 0; + int Sent; + int TxLen; + char * OutBuffer = Msg; + + // WebSock Encode. Buffer has 10 bytes on front for header but header len depends on Msg len + + + if (Len < 126) + { + // Two Byte Header + + OutBuffer[8] = 0x81; // Fin, Data + OutBuffer[9] = Len; + + TxLen = Len + 2; + OutBuffer = &Msg[8]; + } + else if (Len < 65536) + { + OutBuffer[6] = 0x81; // Fin, Data + OutBuffer[7] = 126; // Unmasked, Extended Len 16 + OutBuffer[8] = Len >> 8; + OutBuffer[9] = Len & 0xff; + TxLen = Len + 4; + OutBuffer = &Msg[6]; + } + else + { + OutBuffer[0] = 0x81; // Fin, Data + OutBuffer[1] = 127; // Unmasked, Extended Len 64 bits + // Len is 32 bits, so pad with zeros + OutBuffer[2] = 0; + OutBuffer[3] = 0; + OutBuffer[4] = 0; + OutBuffer[5] = 0; + OutBuffer[6] = (Len >> 24) & 0xff; + OutBuffer[7] = (Len >> 16) & 0xff; + OutBuffer[8] = (Len >> 8) & 0xff; + OutBuffer[9] = Len & 0xff; + + TxLen = Len + + 10; + OutBuffer = &Msg[0]; + } + + // Send may block + + Sent = send(socket, OutBuffer, TxLen, 0); + + while (Sent != TxLen && Loops++ < 3000) // 100 secs max + { + if (Sent > 0) // something sent + { + TxLen -= Sent; + memmove(OutBuffer, &OutBuffer[Sent], TxLen); + } + + Sleep(30); + Sent = send(socket, OutBuffer, TxLen, 0); + if (Sent == -1) + break; + } + + free(Msg); + return; +} + +void ProcessRHPWebSock(SOCKET Socket, char * Msg, int MsgLen); + +void RHPThread(void * Params) +{ + // Params and data buffer are malloced blocks so free when done with it + + struct RHPParamBlock * Block = (struct RHPParamBlock *)Params; + + ProcessRHPWebSock(Block->Socket, Block->Msg, Block->Len); + + free(Block->Msg); + free(Params); +} + +int RHPProcessHTTPMessage(void * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE) +{ + // RHP messages can be sent over Websocks or normal http but I think WhatsPac only uses WebSocks + return 0; +} + +void ProcessRHPWebSock(SOCKET Socket, char * Msg, int MsgLen) +{ + int Loops = 0; + int InputLen = 0; + int Len; + + char Value[16]; + char * OutBuffer = malloc(250000); + +// struct RHPConnectionInfo * RHPSocket = NULL; +// int n; + + Msg[MsgLen] = 0; + + // Find Connection Record. If none, create one + + // I dont think I need connection records + +/* + for (n = 0; n < NumberofRHPConnections; n++) + { + if (RHPSockets[n]->socket == socket) + { + RHPSocket = RHPSockets[n]; + break; + } + } + + if (RHPSocket == 0) + { + // See if there is an old one we can reuse + + for (n = 0; n < NumberofRHPConnections; n++) + { + if (RHPSockets[n]-Socket == -1) + { + RHPSocket = RHPSockets[n]; + RHP + + break; + } + } + + if (RHPSocket == 0) + + NumberofRHPConnections; + RHPSockets = realloc(RHPSockets, sizeof(void *) * (NumberofRHPConnections + 1)); + RHPSocket = RHPSockets[NumberofRHPConnections] = zalloc(sizeof (struct RHPConnectionInfo)); + NumberofRHPConnections++; + RHPSocket->socket = socket; + } +*/ + +// {"type":"open","id":5,"pfam":"ax25","mode":"stream","port":"1","local":"G8BPQ","remote":"G8BPQ-2","flags":128} +// {"type": "openReply", "id": 82, "handle": 1, "errCode": 0, "errText": "Ok"} +// {"seqno": 0, "type": "status", "handle": 1, "flags": 0} +// ("seqno": 1, "type": "close", "handle": 1} +// {"id":40,"type":"close","handle":1} + +// {"seqno": 0, "type": "status", "handle": 1, "flags": 2}.~. +// {"seqno": 1, "type": "recv", "handle": 1, "data": "Welcome to G8BPQ's Test Switch in Nottingham \rType ? for list of available commands.\r"}. + + GetJSONValue(Msg, "\"type\":", Value, 15); + + if (_stricmp(Value, "open") == 0) + { + Len = processRHCPOpen(Socket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + if (Len) + SendWebSockMessage(Socket, OutBuffer, Len); + return; + } + + if (_stricmp(Value, "send") == 0) + { + Len = processRHCPSend(Socket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + SendWebSockMessage(Socket, OutBuffer, Len); + return; + } + + if (_stricmp(Value, "close") == 0) + { + Len = processRHCPClose(Socket, Msg, &OutBuffer[10]); // Space at front for WebSock Header + SendWebSockMessage(Socket, OutBuffer, Len); + return; + } + Debugprintf(Msg); +} + +void ProcessRHPWebSockClosed(SOCKET socket) +{ + // Close any connections on this scoket and delete socket entry + + struct RHPSessionInfo * RHPSession = 0; + int n; + + // Find and close any Sessions + + for (n = 0; n < NumberofRHPSessions; n++) + { + if (RHPSessions[n]->Socket == socket) + { + RHPSession = RHPSessions[n]; + + if (RHPSession->BPQStream) + { + Disconnect(RHPSession->BPQStream); + DeallocateStream(RHPSession->BPQStream); + + RHPSession->BPQStream = 0; + + } + + RHPSession->Connecting = 0; + + // We can't send a close to RHP endpont as socket has gone + + RHPSession->Connected = 0; + } + } +} + + + + +int processRHCPOpen(SOCKET Socket, char * Msg, char * ReplyBuffer) +{ + //{"type":"open","id":5,"pfam":"ax25","mode":"stream","port":"1","local":"G8BPQ","remote":"G8BPQ-2","flags":128} + + struct RHPSessionInfo * RHPSession = 0; + + char * Value = malloc(strlen(Msg)); // Will always be long enough + int ID; + + char pfam[16]; + char Mode[16]; + int Port; + char Local[16]; + char Remote[16]; + int flags; + int Handle = 1; + int Stream; + unsigned char AXCall[10]; + int Len; + int n; + + // ID seems to be used for control commands like open. SeqNo for data within a session (i Think! + + ID = GetJSONInt(Msg, "\"id\":"); + GetJSONValue(Msg, "\"pfam\":", pfam, 15); + GetJSONValue(Msg, "\"mode\":", Mode, 15); + Port = GetJSONInt(Msg, "\"port\":"); + GetJSONValue(Msg, "\"local\":", Local, 15); + GetJSONValue(Msg, "\"remote\":", Remote, 15); + flags = GetJSONInt(Msg, "\"flags\":"); + + if (_stricmp(pfam, "ax25") != 0) + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); + + if (_stricmp(Mode, "stream") == 0) + { + { + // Allocate a RHP Session + + // See if there is an old one we can reuse + + for (n = 0; n < NumberofRHPSessions; n++) + { + if (RHPSessions[n]->BPQStream == 0) + { + RHPSession = RHPSessions[n]; + Handle = n + 1; + Stream = RHPSessions[n]->BPQStream; + + break; + } + } + + if (RHPSession == 0) + { + RHPSessions = realloc(RHPSessions, sizeof(void *) * (NumberofRHPSessions + 1)); + RHPSession = RHPSessions[NumberofRHPSessions] = zalloc(sizeof (struct RHPSessionInfo)); + NumberofRHPSessions++; + + Handle = NumberofRHPSessions; + } + + strcpy(pgm, "RHP"); + Stream = FindFreeStreamNoSem(); + strcpy(pgm, "bpq32.exe"); + + if (Stream == 255) + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); + + RHPSession->BPQStream = Stream; + RHPSession->Handle = Handle; + RHPSession->Connecting = TRUE; + RHPSession->Socket = Socket; + + strcpy(RHPSession->Local, Local); + strcpy(RHPSession->Remote, Remote); + + Connect(Stream); + + ConvToAX25(Local, AXCall); + ChangeSessionCallsign(Stream, AXCall); + + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 0, \"errText\": \"Ok\"}", ID, Handle); + } + } + return sprintf(ReplyBuffer, "{\"type\": \"openReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errText\": \"Bad parameter\"}", ID, 0); +} + +int processRHCPSend(SOCKET Socket, char * Msg, char * ReplyBuffer) +{ + // {"type":"send","handle":1,"data":";;;;;;\r","id":70} + + struct RHPSessionInfo * RHPSession; + + int ID; + char * Data; + char * ptr; + int c; + int Len; + + int Handle = 1; + + Data = malloc(strlen(Msg)); + + ID = GetJSONInt(Msg, "\"id\":"); + Handle = GetJSONInt(Msg, "\"handle\":"); + GetJSONValue(Msg, "\"data\":", Data, strlen(Msg) - 1); + + if (Handle < 1 || Handle > NumberofRHPSessions) + { + free(Data); + return sprintf(ReplyBuffer, "{\"type\": \"sendReply\", \"id\": %d, \"handle\": %d, \"errCode\": 12, \"errtext\": \"Invalid handle\"}", ID, Handle); + } + + RHPSession = RHPSessions[Handle - 1]; + + // Look for \ escapes + + ptr = Data; + + while (ptr = strchr(ptr, '\\')) + { + c = ptr[1]; + + switch (c) + { + case 'r': + + *ptr = 13; + break; + + case '\\': + + *ptr = '\\'; + break; + + case '"': + + *ptr = '"'; + break; + } + memmove(ptr + 1, ptr + 2, strlen(ptr + 1)); + ptr++; + } + + Debugprintf(Data); + + Len = strlen(Data); + ptr = Data; + + + while (Len > RHPPaclen) + { + SendMsgNoSem(RHPSession->BPQStream, ptr, RHPPaclen); + Len -= RHPPaclen; + ptr += RHPPaclen; + } + + SendMsgNoSem(RHPSession->BPQStream, ptr, Len); + + free(Data); + return sprintf(ReplyBuffer, "{\"type\": \"sendReply\", \"id\": %d, \"handle\": %d, \"errCode\": 0, \"errText\": \"Ok\", \"status\": %d}", ID, Handle, 2); +} + + +int processRHCPClose(SOCKET Socket, char * Msg, char * ReplyBuffer) +{ + + // {"id":70,"type":"close","handle":1} + + + struct RHPSessionInfo * RHPSession; + + int ID; + int Handle = 1; + + char * OutBuffer = malloc(256); + + ID = GetJSONInt(Msg, "\"id\":"); + Handle = GetJSONInt(Msg, "\"handle\":"); + + if (Handle < 1 || Handle > NumberofRHPSessions) + return sprintf(ReplyBuffer, "{\"id\": %d, \"type\": \"closeReply\", \"handle\": %d, \"errcode\": 12, \"errtext\": \"Invalid handle\"}", ID, Handle); + + + RHPSession = RHPSessions[Handle - 1]; + Disconnect(RHPSession->BPQStream); + RHPSession->Connected = 0; + RHPSession->Connecting = 0; + + DeallocateStream(RHPSession->BPQStream); + RHPSession->BPQStream = 0; + + return sprintf(ReplyBuffer, "{\"id\": %d, \"type\": \"closeReply\", \"handle\": %d, \"errcode\": 0, \"errtext\": \"Ok\"}", ID, Handle); +} + +char toHex[] = "0123456789abcdef"; + +void RHPPoll() +{ + int Stream; + int n; + int state, change; + int Len; + char * RHPMsg; + unsigned char Buffer[2048]; // Space to escape control chars + int pktlen, count; + + struct RHPSessionInfo * RHPSession; + + for (n = 0; n < NumberofRHPSessions; n++) + { + RHPSession = RHPSessions[n]; + Stream = RHPSession->BPQStream; + + // See if connected state has changed + + SessionState(Stream, &state, &change); + + if (change == 1) + { + if (state == 1) + { + // Connected + + RHPSession->Seq = 0; + RHPSession->Connecting = FALSE; + RHPSession->Connected = TRUE; + + RHPMsg = malloc(256); + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"status\", \"handle\": %d, \"flags\": 2}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + // Send RHP CTEXT + + RHPMsg = malloc(256); + Sleep(10); // otherwise WhatsPac doesn't display connected + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"recv\", \"handle\": %d, \"data\": \"Connected to RHP Server\\r\"}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + } + else + { + // Disconnected. Send Close to client + + RHPMsg = malloc(256); + Len = sprintf(&RHPMsg[10], "{\"type\": \"close\", \"seqno\": %d, \"handle\": %d}", RHPSession->Seq++, RHPSession->Handle); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + RHPSession->Connected = 0; + RHPSession->Connecting = 0; + + DeallocateStream(RHPSession->BPQStream); + RHPSession->BPQStream = 0; + } + } + do + { + GetMsg(Stream, Buffer, &pktlen, &count); + + if (pktlen > 0) + { + char * ptr = Buffer; + unsigned char c; + + Buffer[pktlen] = 0; + + // Message is JSON so Convert CR to \r, \ to \\ " to \" + + // Looks like I need to escape everything not between 0x20 and 0x7f eg \U00c3 + + + while (c = *(ptr)) + { + switch (c) + { + case 13: + + memmove(ptr + 2, ptr + 1, strlen(ptr) + 1); + *(ptr++) = '\\'; + *(ptr++) = 'r'; + break; + + case '"': + + memmove(ptr + 2, ptr + 1, strlen(ptr) + 1); + *(ptr++) = '\\'; + *(ptr++) = '"'; + break; + + case '\\': + + memmove(ptr + 2, ptr + 1, strlen(ptr) + 1); + *(ptr++) = '\\'; + *(ptr++) = '\\'; + break; + + default: + + if (c > 127) + { + memmove(ptr + 6, ptr + 1, strlen(ptr) + 1); + *(ptr++) = '\\'; + *(ptr++) = 'u'; + *(ptr++) = '0'; + *(ptr++) = '0'; + *(ptr++) = toHex[c >> 4]; + *(ptr++) = toHex[c & 15]; + break; + } + else + ptr++; + } + } + + RHPMsg = malloc(2048); + + Len = sprintf(&RHPMsg[10], "{\"seqno\": %d, \"type\": \"recv\", \"handle\": %d, \"data\": \"%s\"}", RHPSession->Seq++, RHPSession->Handle, Buffer); + SendWebSockMessage(RHPSession->Socket, RHPMsg, Len); + + } + + } + while (count > 0); + + } +} + + + +static void GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value, int Len) +{ + char * ptr1, * ptr2; + + Value[0] = 0; + + ptr1 = strstr(_REPLYBUFFER, Name); + + if (ptr1 == 0) + return; + + ptr1 += (strlen(Name) + 1); + +// "data":"{\"t\":\"c\",\"n\":\"John\",\"c\":\"G8BPQ\",\"lm\":1737912636,\"le\":1737883907,\"led\":1737758451,\"v\":0.33,\"cc\":[{\"cid\":1,\"lp\":1737917257201,\"le\":1737913735726,\"led\":1737905249785},{\"cid\":0,\"lp\":1737324074107,\"le\":1737323831510,\"led\":1737322973662},{\"cid\":5,\"lp\":1737992107419,\"le\":1737931466510,\"led\":1737770056244}]}\r","id":28} + + // There may be escaped " in data stream + + ptr2 = strchr(ptr1, '"'); + + while (*(ptr2 - 1) == '\\') + { + ptr2 = strchr(ptr2 + 2, '"'); + } + + + if (ptr2) + { + size_t ValLen = ptr2 - ptr1; + if (ValLen > Len) + ValLen = Len; + + memcpy(Value, ptr1, ValLen); + Value[ValLen] = 0; + } + + return; +} + + +static int GetJSONInt(char * _REPLYBUFFER, char * Name) +{ + char * ptr1; + + ptr1 = strstr(_REPLYBUFFER, Name); + + if (ptr1 == 0) + return 0; + + ptr1 += (strlen(Name)); + + return atoi(ptr1); +} + + diff --git a/RigControl.c b/RigControl.c index 024d17d..6bc414f 100644 --- a/RigControl.c +++ b/RigControl.c @@ -48,7 +48,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include "time.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #ifdef WIN32 #include @@ -324,7 +324,7 @@ VOID Rig_PTTEx(struct RIGINFO * RIG, BOOL PTTState, struct TNCINFO * TNC) // Convert to CAT string - sprintf(FreqString, "%012d", txfreq); + sprintf(FreqString, "%012lld", txfreq); switch (PORT->PortType) { @@ -455,7 +455,7 @@ VOID Rig_PTTEx(struct RIGINFO * RIG, BOOL PTTState, struct TNCINFO * TNC) // Convert to CAT string - sprintf(FreqString, "%012d", txfreq); + sprintf(FreqString, "%012lld", txfreq); switch (PORT->PortType) { @@ -896,7 +896,7 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR // if Port starts with 'R' then select Radio (was Interlock) number, not BPQ Port if (Command[0] == 'R') - n = sscanf(Command,"%s %s %s %s %s", &Dummy, &FreqString[0], &Mode[0], &FilterString[0], &Data[0]); + n = sscanf(Command,"%s %s %s %s %s", &Dummy[0], &FreqString[0], &Mode[0], &FilterString[0], &Data[0]); else n = sscanf(Command,"%d %s %s %s %s", &Port, &FreqString[0], &Mode[0], &FilterString[0], &Data[0]); @@ -1117,7 +1117,7 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR if (_stricmp(FreqString, "POWER") == 0) { - char PowerString[8] = ""; + char PowerString[16] = ""; int Power = atoi(Mode); int len; char cmd[80]; @@ -1291,7 +1291,7 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR // use text command - Len = sprintf(CmdPtr, "%S", ptr1); + Len = sprintf(CmdPtr, "%s", ptr1); break; case YAESU: @@ -2072,7 +2072,7 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR case HAMLIB: { - char cmd[80]; + char cmd[200]; int len = sprintf(cmd, "F %s\n+f\nM %s %d\n+m\n", FreqString, Mode, atoi(Data)); @@ -7252,7 +7252,7 @@ CheckScan: } else if (PORT->PortType == FT991A || PORT->PortType == FTDX10) { - FreqPtr[0]->Cmd1Len = sprintf(CmdPtr, "FA%s;MD0%X;FA;MD0;", &FreqString, ModeNo); + FreqPtr[0]->Cmd1Len = sprintf(CmdPtr, "FA%s;MD0%X;FA;MD0;", &FreqString[0], ModeNo); } else if (PORT->PortType == FT100 || PORT->PortType == FT990 || PORT->PortType == FT1000) @@ -8139,7 +8139,7 @@ void ProcessFLRIGFrame(struct RIGPORTINFO * PORT) void HLSetMode(SOCKET Sock, struct RIGINFO * RIG, unsigned char * Msg, char sep) { - char Resp[80]; + char Resp[120]; int Len; char mode[80] = ""; int filter = 0; @@ -10164,7 +10164,7 @@ VOID ConnecttoSDRANGEL(struct RIGPORTINFO * PORT) VOID SDRANGELThread(struct RIGPORTINFO * PORT) { // Opens sockets and looks for data - char Msg[255]; + char Msg[512]; int err, i, ret; u_long param=1; BOOL bcopt=TRUE; diff --git a/SCSPactor.c b/SCSPactor.c index ddb8277..bca138a 100644 --- a/SCSPactor.c +++ b/SCSPactor.c @@ -78,7 +78,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define MaxStreams 10 // First is used for Pactor, even though Pactor uses channel 31 -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/SCSTrackeMulti.c b/SCSTrackeMulti.c index 00174cf..590a61a 100644 --- a/SCSTrackeMulti.c +++ b/SCSTrackeMulti.c @@ -31,7 +31,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define MaxStreams 10 -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/SCSTracker.c b/SCSTracker.c index e6d356f..5cc106f 100644 --- a/SCSTracker.c +++ b/SCSTracker.c @@ -31,7 +31,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define MaxStreams 1 -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" //#include "bpq32.h" diff --git a/SerialPort.c b/SerialPort.c index 0fb09e7..3cda40e 100644 --- a/SerialPort.c +++ b/SerialPort.c @@ -36,7 +36,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #endif -#include "CHeaders.h" +#include "cheaders.h" extern int (WINAPI FAR *GetModuleFileNameExPtr)(); diff --git a/TNCCode.c b/TNCCode.c index d21deab..37cd8e2 100644 --- a/TNCCode.c +++ b/TNCCode.c @@ -30,7 +30,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "stdio.h" #include -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" int C_Q_COUNT(VOID *PQ); diff --git a/TNCEmulators.c b/TNCEmulators.c index cf32c9a..135d318 100644 --- a/TNCEmulators.c +++ b/TNCEmulators.c @@ -25,7 +25,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" typedef struct _TCMDX { diff --git a/TelnetV6-skigdebian.c b/TelnetV6-skigdebian.c new file mode 100644 index 0000000..8074a4a --- /dev/null +++ b/TelnetV6-skigdebian.c @@ -0,0 +1,7157 @@ +/* +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 +*/ + +// +// Telnet Driver for BPQ Switch +// +// Uses BPQ EXTERNAL interface +// + +//#define WIN32_LEAN_AND_MEAN + +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "time.h" + +#include "kernelresource.h" + +#define IDM_DISCONNECT 2000 +#define IDM_LOGGING 2100 + +#include "cheaders.h" +#include "tncinfo.h" + +#ifdef WIN32 +#include +#include "WS2tcpip.h" +#else +//#define TIOCOUTQ 0x5411 +#define SIOCOUTQ TIOCOUTQ /* output queue size (not sent + not acked) */ +#endif + +#include "adif.h" +#include "telnetserver.h" + +#define MAX_PENDING_CONNECTS 4 + +extern UCHAR LogDirectory[]; + + +static char ClassName[]="TELNETSERVER"; +static char WindowTitle[] = "Telnet Server"; +static int RigControlRow = 190; + +UCHAR * APIENTRY GetLogDirectory(); +static BOOL OpenSockets(struct TNCINFO * TNC); +static BOOL OpenSockets6(struct TNCINFO * TNC); +void ProcessHTTPMessage(void * conn); +static VOID SetupListenSet(struct TNCINFO * TNC); +int WritetoConsoleLocal(char * buff); +BOOL TelSendPacket(int Stream, struct STREAMINFO * STREAM, PMSGWITHLEN buffptr, struct ADIF * ADIF); +int GetCMSHash(char * Challenge, char * Password); +int IsUTF8(unsigned char *cpt, int len); +int Convert437toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1251toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1252toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +VOID initUTF8(); +int TrytoGuessCode(unsigned char * Char, int Len); +DllExport struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot); +extern BPQVECSTRUC * TELNETMONVECPTR; +BOOL SendWL2KSessionRecord(ADIF * ADIF, int BytesSent, int BytesReceived); +int DataSocket_ReadSync(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream); +VOID SendWL2KRegisterHybrid(struct TNCINFO * TNC); +int IntSetTraceOptionsEx(uint64_t mask, int mtxparam, int mcomparam, int monUIOnly); +int DataSocket_ReadDRATS(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream); +void processDRATSFrame(unsigned char * Message, int Len, struct ConnectionInfo * sockptr); +void DRATSConnectionLost(struct ConnectionInfo * sockptr); +int BuildRigCtlPage(char * _REPLYBUFFER); +void ProcessWebmailWebSockThread(void * conn); +void ProcessRHPWebSock(SOCKET Socket, char * msg, int Len); +void ProcessRHPWebSockClosed(SOCKET socket); +int ProcessSNMPPayload(UCHAR * Msg, int Len, UCHAR * Reply, int * OffPtr); +int RHPProcessHTTPMessage(struct ConnectionInfo * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); + + +#ifndef LINBPQ +extern HKEY REGTREE; +extern HMENU hMainFrameMenu; +extern HMENU hBaseMenu; +extern HMENU hWndMenu; +extern HFONT hFont; +extern HBRUSH bgBrush; + +extern HWND ClientWnd, FrameWnd; + +extern HANDLE hInstance; +static RECT Rect; + +LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +#endif + +extern int REALTIMETICKS; + +#define MaxSockets 26 + +struct UserRec RelayUser; +struct UserRec SyncUser = {"","Sync"};; +struct UserRec CMSUser; +struct UserRec HostUser = {"","Host"}; +struct UserRec TriModeUser; + +static char AttemptsMsg[] = "Too many attempts - Disconnected\r\n"; +static char disMsg[] = "Disconnected by SYSOP\r\n"; + +static char BlankCall[]=" "; + +BOOL LogEnabled = FALSE; +BOOL CMSLogEnabled = TRUE; +extern BOOL IncludesMail; + +extern int HTTPPort; + +static HMENU hMenu, hPopMenu, hPopMenu2, hPopMenu3; // handle of menu + +static int ProcessLine(char * buf, int Port); +VOID __cdecl Debugprintf(const char * format, ...); + + +int DisplaySessions(struct TNCINFO * TNC); +int DoStateChange(int Stream); +int Connected(int Stream); +int Disconnected(int Stream); +//int DeleteConnection(con); +static int Socket_Accept(struct TNCINFO * TNC, SOCKET SocketId, int Port); +static int Socket_Data(struct TNCINFO * TNC, SOCKET SocketId,int error, int eventcode); +static int DataSocket_Read(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM); +int DataSocket_ReadFBB(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream); +int DataSocket_ReadRelay(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM); +int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream); +int DataSocket_Write(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET TCPSock); +int DataSocket_Disconnect(struct TNCINFO * TNC, struct ConnectionInfo * sockptr); +BOOL ProcessTelnetCommand(struct ConnectionInfo * sockptr, byte * Msg, int * Len); +int ShowConnections(struct TNCINFO * TNC); +int Terminate(); +int SendtoSocket(SOCKET TCPSock,char * Msg); +int WriteLog(char * msg); +VOID WriteCMSLog(char * msg); +byte * EncodeCall(byte * Call); +VOID SendtoNode(struct TNCINFO * TNC, int Stream, char * Msg, int MsgLen); +DllExport int APIENTRY GetNumberofPorts(); + +BOOL CheckCMS(struct TNCINFO * TNC); +int TCPConnect(struct TNCINFO * TNC, struct TCPINFO * TCP, struct STREAMINFO * STREAM, char * Host, int Port, BOOL FBB); +int CMSConnect(struct TNCINFO * TNC, struct TCPINFO * TCP, struct STREAMINFO * STREAM, int Stream); +int Telnet_Connected(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Error); +BOOL ProcessConfig(); +VOID FreeConfig(); +VOID SaveCMSHostInfo(int port, struct TCPINFO * TCP, int CMSNo); +VOID GetCMSCachedInfo(struct TNCINFO * TNC); +BOOL CMSCheck(struct TNCINFO * TNC, struct TCPINFO * TCP); +VOID Tel_Format_Addr(struct ConnectionInfo * sockptr, char * dst); +VOID ProcessTrimodeCommand(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, char * MsgPtr); +VOID ProcessTrimodeResponse(struct TNCINFO * TNC, struct STREAMINFO * STREAM, unsigned char * MsgPtr, int Msglen); +VOID ProcessTriModeDataMessage(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM); + + +static int LogAge = 13; + + + +#ifdef WIN32 + +int DeleteLogFile(char * Log); + +void DeleteTelnetLogFiles() +{ + DeleteLogFile("/logs/Telnet*.log"); + DeleteLogFile("/logs/CMSAccess_*.log"); + DeleteLogFile("/logs/ConnectLog_*.log"); +} + +int DeleteLogFile(char * Log) +{ + + + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + char File[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + DWORD dwError=0; + LARGE_INTEGER ft; + time_t now = time(NULL); + int Age; + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, GetLogDirectory()); + strcat(szDir, Log); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + return dwError; + + // List all the files in the directory with some info about them. + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + OutputDebugString(ffd.cFileName); + } + else + { + ft.HighPart = ffd.ftCreationTime.dwHighDateTime; + ft.LowPart = ffd.ftCreationTime.dwLowDateTime; + + ft.QuadPart -= 116444736000000000; + ft.QuadPart /= 10000000; + + Age = (int)((now - ft.LowPart) / 86400); + + if (Age > LogAge) + { + sprintf(File, "%s/logs/%s%c", GetLogDirectory(), ffd.cFileName, 0); + Debugprintf("Deleting %s", File); + DeleteFile(File); + } + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + return dwError; +} + +#else + +#include + +int TelFilter(const struct dirent * dir) +{ + return (memcmp(dir->d_name, "CMSAccess", 9) == 0 + || memcmp(dir->d_name, "Telnet", 6) == 0 + || memcmp(dir->d_name, "ConnectLog", 6) == 0) + && strstr(dir->d_name, ".log"); +} + +int DeleteTelnetLogFiles() +{ + struct dirent **namelist; + int n; + struct stat STAT; + time_t now = time(NULL); + int Age = 0, res; + char FN[256]; + + n = scandir("logs", &namelist, TelFilter, alphasort); + + if (n < 0) + perror("scandir"); + else + { + while(n--) + { + sprintf(FN, "logs/%s", namelist[n]->d_name); + if (stat(FN, &STAT) == 0) + { + Age = (now - STAT.st_mtime) / 86400; + + if (Age > LogAge) + { + Debugprintf("Deleting %s\n", FN); + unlink(FN); + } + } + free(namelist[n]); + } + free(namelist); + } + return 0; +} +#endif + + + +void BuffertoNode(struct ConnectionInfo * sockptr, char * MsgPtr, int InputLen) +{ + // Queue to Node. Data may arrive it large quatities, possibly exceeding node buffer capacity + + if (sockptr->FromHostBuffPutptr + InputLen > sockptr->FromHostBufferSize) + { + if (InputLen > 10000) + sockptr->FromHostBufferSize += InputLen; + else + sockptr->FromHostBufferSize += 10000; + + sockptr->FromHostBuffer = realloc(sockptr->FromHostBuffer, sockptr->FromHostBufferSize); + } + + memcpy(&sockptr->FromHostBuffer[sockptr->FromHostBuffPutptr], MsgPtr, InputLen); + + sockptr->FromHostBuffPutptr += InputLen; + sockptr->InputLen = 0; + + return; + } + +BOOL SendAndCheck(struct ConnectionInfo * sockptr, unsigned char * MsgPtr, int len, int flags) +{ + int err; + int sent = send(sockptr->socket, MsgPtr, len, flags); + + if (sent == len) + return TRUE; // OK + + err = WSAGetLastError(); + + Debugprintf("TCP Send Failed - Sent %d should be %d err %d - requeue data", sent, len, err); + + if (err == 10035 || err == 115 || err == 36) //EWOULDBLOCK + { + // Save unsent data + + if (sent == -1) // -1 means none sent + sent = 0; + + sockptr->ResendBuffer = malloc(len - sent); + sockptr->ResendLen = len - sent; + + memmove(sockptr->ResendBuffer, MsgPtr + sent, len - sent); + } + return FALSE; +} + +VOID SendPortsForMonitor(SOCKET sock, int Secure) +{ + UCHAR PortInfo[3000] = {0xff, 0xff}; + UCHAR * ptr = &PortInfo[2]; + char ID[31] = ""; + struct PORTCONTROL * PORT; + int i, n, p; + + // Sends the ID of each Port to TermTCP + + p = GetNumberofPorts(); + + if (IncludesMail && Secure) + p++; + + ptr += sprintf(ptr, "%d|", p); + + if (IncludesMail && Secure) + { + p--; + ptr += sprintf(ptr,"0 Mail Monitor|"); + } + + for (n=1; n <= p; n++) + { + PORT = GetPortTableEntryFromSlot(n); + + memcpy(ID, PORT->PORTDESCRIPTION, 30); + + for (i = 29; i; i--) + { + if (ID[i] != ' ') + break; + + ID[i] = 0; + } + + ptr += sprintf(ptr,"%d %s|", PORT->PORTNUMBER, ID); + } + + + send(sock, PortInfo, (int)(ptr - PortInfo), 0); +} + +int ProcessLine(char * buf, int Port) +{ + UCHAR * ptr; + UCHAR * ptr1; + + char * p_ipad = 0; + char * p_port = 0; + unsigned short WINMORport = 0; + int len=510; + char errbuf[256]; + char * param; + char * value; + char *User, *Pwd, *UserCall, *Secure, * Appl; + int End = (int)strlen(buf) -1; + struct TNCINFO * TNC; + struct TCPINFO * TCP; + + strcpy(errbuf,buf); // save in case of error + + if (buf[End] == 10) + buf[End]=0; // remove newline + + if(buf[0] =='#') return (TRUE); // comment + + if(buf[0] ==';') return (TRUE); // comment + + ptr=strchr(buf,'='); + + if (!ptr) + ptr=strchr(buf,' '); + + if (!ptr) + return 0; + + if (TNCInfo[Port] == NULL) + { + TNC = TNCInfo[Port] = zalloc(sizeof(struct TNCINFO)); + TCP = TNC->TCPInfo = zalloc(sizeof (struct TCPINFO)); // Telnet Server Specific Data + + TCP->MaxSessions = 10; // Default Values + TNC->Hardware = H_TELNET; + TCP->IPV4 = TRUE; + TCP->SecureTelnet = 1; + strcpy(TCP->CMSServer, "cms.winlink.org"); + } + + TNC = TNCInfo[Port]; + TCP = TNC->TCPInfo; + + param=buf; + *(ptr)=0; + value=ptr+1; + + if (_stricmp(param, "IPV4") == 0) + TCP->IPV4 = atoi(value); + + else if (_stricmp(param, "IPV6") == 0) + TCP->IPV6 = atoi(value); + + else if (_stricmp(param, "CMS") == 0) + TCP->CMS = atoi(value); + + else if (_stricmp(param, "CMSPASS") == 0) + { + char Temp[80]; + + if (strlen(value) > 79) + value[79] = 0; + + strcpy(Temp, value); + strlop(Temp, 32); + strlop(Temp, 13); + strcpy(TCP->SecureCMSPassword, Temp); + + } + + else if (_stricmp(param, "CMSCALL") == 0) + { + if (strlen(value) > 9) + value[9] = 0; + strcpy(TCP->GatewayCall, value); + strlop(TCP->GatewayCall, 13); + _strupr(TCP->GatewayCall); + } + + else if (_stricmp(param, "CMSLOC") == 0) + { + if (strlen(value) > 9) + value[9] = 0; + strcpy(TCP->GatewayLoc, value); + strlop(TCP->GatewayLoc, 13); + _strupr(TCP->GatewayLoc); + } + + else if (_stricmp(param,"ReportHybrid") == 0) + TCP->ReportHybrid = atoi(value); + + else if (_stricmp(param, "HybridServiceCode") == 0) + { + TCP->HybridServiceCode = _strdup(value); + strlop(TCP->HybridServiceCode, 13); + strlop(TCP->HybridServiceCode, ';'); + _strupr(TCP->HybridServiceCode); + } + + else if (_stricmp(param, "HybridFrequencies") == 0) + { + TCP->HybridFrequencies = _strdup(value); + strlop(TCP->HybridFrequencies, 13); + strlop(TCP->HybridFrequencies, ' '); + _strupr(TCP->HybridFrequencies); + } + + else if (_stricmp(param, "HybridCoLocatedRMS") == 0) + { + TCP->HybridCoLocatedRMS = _strdup(value); + strlop(TCP->HybridCoLocatedRMS, 13); + strlop(TCP->HybridCoLocatedRMS, ' '); + _strupr(TCP->HybridCoLocatedRMS); + } + + else if (_stricmp(param,"LOGGING") == 0) + LogEnabled = atoi(value); + + else if (_stricmp(param,"CMSLOGGING") == 0) + CMSLogEnabled = atoi(value); + + else if (_stricmp(param,"DisconnectOnClose") == 0) + TCP->DisconnectOnClose = atoi(value); + + else if (_stricmp(param,"ReportRelayTraffic") == 0) + TCP->ReportRelayTraffic = atoi(value); + + else if (_stricmp(param,"SecureTelnet") == 0) + TCP->SecureTelnet = atoi(value); + + else if (_stricmp(param,"CloseOnDisconnect") == 0) + TCP->DisconnectOnClose = atoi(value); + + else if (_stricmp(param,"TCPPORT") == 0) + TCP->TCPPort = atoi(value); + + else if (_stricmp(param,"DRATSPORT") == 0) + TCP->DRATSPort = atoi(value); + + else if (_stricmp(param,"TRIMODEPORT") == 0) + TCP->TriModePort = atoi(value); + + else if (_stricmp(param,"HTTPPORT") == 0) + HTTPPort = TCP->HTTPPort = atoi(value); + + else if (_stricmp(param,"APIPORT") == 0) + TCP->APIPort = atoi(value); + + else if (_stricmp(param,"SYNCPORT") == 0) + TCP->SyncPort = atoi(value); + + else if (_stricmp(param,"SNMPPORT") == 0) + TCP->SNMPPort = atoi(value); + + else if ((_stricmp(param,"CMDPORT") == 0) || (_stricmp(param,"LINUXPORT") == 0)) + { + int n = 0; + char * context; + char * ptr = strtok_s(value, ", ", &context); + + while (ptr && n < 33) + { + TCP->CMDPort[n++] = atoi(ptr); + ptr = strtok_s(NULL, ", ", &context); + } + } + + else if (_stricmp(param,"CMSSERVER") == 0) + { + int n = 0; + char * context; + char * ptr = strtok_s(value, ", \r", &context); + + strcpy(TCP->CMSServer, ptr); + } + + else if (_stricmp(param,"RELAYHOST") == 0) + { + int n = 0; + char * context; + char * ptr = strtok_s(value, ", \r", &context); + + strcpy(TCP->RELAYHOST, ptr); + } + + + else if (_stricmp(param,"FALLBACKTORELAY") == 0) + { + int n = 0; + char * context; + char * ptr = strtok_s(value, ", \r", &context); + + TCP->FallbacktoRelay = atoi(value); + } + + else if (_stricmp(param,"FBBPORT") == 0) + { + int n = 0; + char * context; + char * ptr = strtok_s(value, ", ", &context); + + while (ptr && n < 99) + { + TCP->FBBPort[n++] = atoi(ptr); + ptr = strtok_s(NULL, ", ", &context); + } + } + + else if (_stricmp(param,"RELAYPORT") == 0) + TCP->RelayPort = atoi(value); + + else if (_stricmp(param,"RELAYAPPL") == 0) + { + if (TCP->RelayPort == 0) + TCP->RelayPort = 8772; + strcpy(TCP->RelayAPPL, value); + strcat(TCP->RelayAPPL, "\r"); + } + + else if (_stricmp(param,"SYNCAPPL") == 0) + { + if (TCP->SyncPort == 0) + TCP->SyncPort = 8780; + strcpy(TCP->SyncAPPL, value); + strcat(TCP->SyncAPPL, "\r"); + } + + // if (strcmp(param,"LOGINRESPONSE") == 0) cfgLOGINRESPONSE = value; + // if (strcmp(param,"PASSWORDRESPONSE") == 0) cfgPASSWORDRESPONSE = value; + + else if (_stricmp(param,"LOGINPROMPT") == 0) + { + ptr1 = strchr(value, 13); + if (ptr1) *ptr1 = 0; + strcpy(TCP->LoginMsg,value); + } + + else if (_stricmp(param,"PASSWORDPROMPT") == 0) + { + ptr1 = strchr(value, 13); + if (ptr1) *ptr1 = 0; + strcpy(TCP->PasswordMsg, value); + } + + else if (_stricmp(param,"HOSTPROMPT") == 0) + strcpy(TCP->cfgHOSTPROMPT, value); + + else if (_stricmp(param,"LOCALECHO") == 0) + strcpy(TCP->cfgLOCALECHO, value); + + else if (_stricmp(param,"MAXSESSIONS") == 0) + { + TCP->MaxSessions = atoi(value); + if (TCP->MaxSessions > MaxSockets ) TCP->MaxSessions = MaxSockets; + } + + else if (_stricmp(param,"CTEXT") == 0 ) + { + int len = (int)strlen (value); + + if (value[len -1] == ' ') + value[len -1] = 0; + + strcpy(TCP->cfgCTEXT, value); + + // Replace \n Signon string with cr lf + + ptr = &TCP->cfgCTEXT[0]; + +scanCTEXT: + + ptr = strstr(ptr, "\\n"); + + if (ptr) + { + *(ptr++)=13; // put in cr + *(ptr++)=10; // put in lf + + goto scanCTEXT; + } + } + + else if (_stricmp(param,"LOCALNET") == 0) + { + uint32_t Network, Mask; + uint32_t IPMask; + char * Netptr, * MaskPtr, *Context; + struct LOCALNET * LocalNet; + int Bits = 24; + + Netptr = strtok_s(value, " /;", &Context); + + if (Netptr) + { + Network = inet_addr(Netptr); + MaskPtr = strtok_s(NULL, " /;", &Context); + + if (MaskPtr) + { + Bits = atoi(MaskPtr); + + if (Bits > 32) + Bits = 32; + } + + if (Bits == 0) + IPMask = 0; + else + IPMask = (0xFFFFFFFF) << (32 - Bits); + + Mask = htonl(IPMask); // Needs to be Network order + + LocalNet = (struct LOCALNET *)zalloc(sizeof(struct LOCALNET)); + + LocalNet->Network = Network; + LocalNet->Mask = Mask; + LocalNet->Next = TNC->TCPInfo->LocalNets; + + TNC->TCPInfo->LocalNets = LocalNet; + + } + } + + + + + else if (_stricmp(param,"USER") == 0) + { + struct UserRec * USER; + char Param[8][256]; + char * ptr1, * ptr2; + int n = 0; + + // USER=user,password,call,appl,SYSOP + + memset(Param, 0, 2048); + strlop(value, 13); + strlop(value, ';'); + + ptr1 = value; + + while (ptr1 && *ptr1 && n < 8) + { + ptr2 = strchr(ptr1, ','); + if (ptr2) *ptr2++ = 0; + + strcpy(&Param[n][0], ptr1); + strlop(Param[n++], ' '); + ptr1 = ptr2; + while(ptr1 && *ptr1 && *ptr1 == ' ') + ptr1++; + } + + + User = &Param[0][0]; + + if (_stricmp(User, "ANON") == 0) + { + strcpy(&Param[2][0], "ANON"); + strcpy(&Param[4][0], ""); // Dont allow SYSOP if ANON + } + + Pwd = &Param[1][0]; + UserCall = &Param[2][0]; + Appl = &Param[3][0]; + Secure = &Param[4][0]; + + if (User[0] == 0 || Pwd[0] == 0 || UserCall[0] == 0) // invalid record + return 0; + + _strupr(UserCall); + + if (TCP->NumberofUsers == 0) + TCP->UserRecPtr = zalloc(sizeof(void *)); + else + TCP->UserRecPtr = realloc(TCP->UserRecPtr, (TCP->NumberofUsers+1) * sizeof(void *)); + + USER = zalloc(sizeof(struct UserRec)); + + TCP->UserRecPtr[TCP->NumberofUsers] = USER; + + USER->Callsign = _strdup(UserCall); + USER->Password = _strdup(Pwd); + USER->UserName = _strdup(User); + USER->Appl = zalloc(32); + USER->Secure = FALSE; + + if (_stricmp(Secure, "SYSOP") == 0) + USER->Secure = TRUE; + + if (Appl[0] && strcmp(Appl, "\"\"") != 0) + { + strcpy(USER->Appl, _strupr(Appl)); + strcat(USER->Appl, "\r\n"); + } + TCP->NumberofUsers += 1; + } + else if (_memicmp(errbuf, "WL2KREPORT", 10) == 0) + TNC->WL2K = DecodeWL2KReportLine(errbuf); + else if (_stricmp(param,"WebTermCSS") == 0) + { + TCP->WebTermCSS = _strdup(value); + } + else + return FALSE; + + return TRUE; +} + +static int MaxStreams = 26; + +void CheckRX(struct TNCINFO * TNC); +VOID TelnetPoll(int Port); +VOID ProcessTermModeResponse(struct TNCINFO * TNC); +VOID DoTNCReinit(struct TNCINFO * TNC); +VOID DoTermModeTimeout(struct TNCINFO * TNC); + +VOID DoMonitor(struct TNCINFO * TNC, UCHAR * Msg, int Len); + + +static VOID WritetoTrace(int Stream, char * Msg, int Len, struct ADIF * ADIF, char Dirn) +{ + int index = 0; + UCHAR * ptr1 = Msg, * ptr2; + UCHAR Line[1000]; + int LineLen, i; + char logmsg[200]; + + Msg[Len] = 0; + +lineloop: + + if (Len > 0) + { + // copy text to file a line at a time + + ptr2 = memchr(ptr1, 13 , Len); + + if (ptr2) + { + ptr2++; + LineLen = (int)(ptr2 - ptr1); + Len -= LineLen; + + if (LineLen > 900) + goto Skip; + + memcpy(Line, ptr1, LineLen); + memcpy(&Line[LineLen - 1], "", 4); + LineLen += 3; + + if ((*ptr2) == 10) + { + memcpy(&Line[LineLen], "", 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] > 127 || Line[i] < 32) + goto Skip; + } + + if (strlen(Line) < 100) + { + sprintf(logmsg,"%d %s\r\n", Stream, Line); + WriteCMSLog(logmsg); + UpdateADIFRecord(ADIF, Line, Dirn); + } + +Skip: + ptr1 = ptr2; + goto lineloop; + } + + // No CR + + for (i = 0; i < Len; i++) + { + if (ptr1[i] > 127) + break; + } + + if (i == Len) // No binary data + { + if (strlen(ptr1) < 100) + { + sprintf(logmsg,"%d %s\r\n", Stream, ptr1); + WriteCMSLog(logmsg); + UpdateADIFRecord(ADIF, ptr1, Dirn); + } + } + } +} + +static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) +{ + int txlen = 0, n; + PMSGWITHLEN buffptr; + struct TNCINFO * TNC = TNCInfo[port]; + struct TCPINFO * TCP; + + int Stream; + struct ConnectionInfo * sockptr; + struct STREAMINFO * STREAM; + + if (TNC == NULL) + return 0; // Not configured + + switch (fn) + { + case 7: + + // 100 mS Timer. Now needed, as Poll can be called more frequently in some circuymstances + + while (TNC->PortRecord->UI_Q) // Release anything accidentally put on UI_Q + { + buffptr = Q_REM(&TNC->PortRecord->UI_Q); + ReleaseBuffer(buffptr); + } + + TCP = TNC->TCPInfo; + + if (TCP->CMS) + { + TCP->CheckCMSTimer++; + + if (TCP->CMSOK) + { + if (TCP->CheckCMSTimer > 600 * 15) + CheckCMS(TNC); + } + else + { + if (TCP->CheckCMSTimer > 600 * 2) + CheckCMS(TNC); + } + } + + // We now use persistent HTTP sessions, so need to close after a reasonable time + + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->SocketActive) + { + if (sockptr->HTTPMode) + { + if (sockptr->WebSocks == 0) + { + if (sockptr->LastSendTime && (REALTIMETICKS - sockptr->LastSendTime) > 1500) // ~ 2.5 mins + { + closesocket(sockptr->socket); + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + } + } + } + else + { + // Time out Login + + if (sockptr->LoginState < 2 && (time(NULL) - sockptr->ConnectTime) > 30) + { + closesocket(sockptr->socket); + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + } + } + } + } + + + + return 0; + + case 1: // poll + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + TRANSPORTENTRY * SESS; + struct ConnectionInfo * sockptr = TNC->Streams[Stream].ConnectionInfo; + + if (sockptr && sockptr->UserPointer == &CMSUser) // Connected to CMS + { + SESS = TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]; + + if (SESS) + { + n = SESS->L4KILLTIMER; + if (n < (SESS->L4LIMIT - 120)) + { + SESS->L4KILLTIMER = SESS->L4LIMIT - 120; + SESS = SESS->L4CROSSLINK; + if (SESS) + SESS->L4KILLTIMER = SESS->L4LIMIT - 120; + } + } + } + + STREAM = &TNC->Streams[Stream]; + + if (STREAM->NeedDisc) + { + STREAM->NeedDisc--; + + if (STREAM->NeedDisc == 0) + { + // Send the DISCONNECT + + STREAM->ReportDISC = TRUE; + } + } + + if (STREAM->ReportDISC) + { + STREAM->ReportDISC = FALSE; + buff->PORT = Stream; + + return -1; + } + } + + TelnetPoll(port); + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (STREAM->PACTORtoBPQ_Q !=0) + { + int datalen; + + buffptr = Q_REM(&STREAM->PACTORtoBPQ_Q); + + datalen = (int)buffptr->Len; + + buff->PORT = Stream; + buff->PID = 0xf0; + memcpy(&buff->L2DATA, &buffptr->Data[0], datalen); // Data goes to + 7, but we have an extra byte + datalen += sizeof(void *) + 4; + + PutLengthinBuffer(buff, datalen); + + ReleaseBuffer(buffptr); + return (1); + } + } + + return 0; + + case 2: // send + + buffptr = GetBuff(); + + if (buffptr == 0) return (0); // No buffers, so ignore + + // Find TNC Record + + Stream = buff->PORT; + STREAM = &TNC->Streams[Stream]; + + txlen = GetLengthfromBuffer(buff) - (MSGHDDRLEN + 1); // 1 as no PID + + buffptr->Len = txlen; + memcpy(&buffptr->Data, &buff->L2DATA, txlen); + + C_Q_ADD(&STREAM->BPQtoPACTOR_Q, buffptr); + STREAM->FramesQueued++; + + return (0); + + + case 3: // CHECK IF OK TO SEND. Also used to check if TNC is responding + + Stream = (int)(size_t)buff; + + STREAM = &TNC->Streams[Stream]; + + if (STREAM->FramesQueued > 40) + return (257); // Busy + + return 256; // OK + +#define SD_BOTH 0x02 + + case 4: // reinit + { + struct _EXTPORTDATA * PortRecord; + +#ifndef LINBPQ + HWND SavehDlg, SaveCMS, SaveMonitor; + HMENU SaveMenu1, SaveMenu2, SaveMenu3; +#endif + int n, i; + + if (!ProcessConfig()) + { + Consoleprintf("Failed to reread config file - leaving config unchanged"); + break; + } + + FreeConfig(); + + for (n = 1; n <= TNC->TCPInfo->CurrentSockets; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + sockptr->SocketActive = FALSE; + closesocket(sockptr->socket); + } + + TCP = TNC->TCPInfo; + + shutdown(TCP->TCPSock, SD_BOTH); + shutdown(TCP->sock6, SD_BOTH); + + n = 0; + while (TCP->FBBsock[n]) + shutdown(TCP->FBBsock[n++], SD_BOTH); + + shutdown(TCP->Relaysock, SD_BOTH); + shutdown(TCP->HTTPsock, SD_BOTH); + shutdown(TCP->HTTPsock6, SD_BOTH); + + + n = 0; + while (TCP->FBBsock6[n]) + shutdown(TCP->FBBsock[n++], SD_BOTH); + + shutdown(TCP->Relaysock6, SD_BOTH); + Sleep(500); + + closesocket(TCP->TCPSock); + closesocket(TCP->sock6); + + n = 0; + while (TCP->FBBsock[n]) + closesocket(TCP->FBBsock[n++]); + + n = 0; + while (TCP->FBBsock6[n]) + closesocket(TCP->FBBsock6[n++]); + + closesocket(TCP->Relaysock); + closesocket(TCP->Relaysock6); + closesocket(TCP->HTTPsock); + closesocket(TCP->HTTPsock6); + + // Save info from old TNC record + + n = TNC->Port; + PortRecord = TNC->PortRecord; +#ifndef LINBPQ + SavehDlg = TNC->hDlg; + SaveCMS = TCP->hCMSWnd; + SaveMonitor = TNC->hMonitor; + SaveMenu1 = TCP->hActionMenu; + SaveMenu2 = TCP->hDisMenu; + SaveMenu3 = TCP->hLogMenu; +#endif + // Free old TCP Session Stucts + + for (i = 0; i <= TNC->TCPInfo->MaxSessions; i++) + { + free(TNC->Streams[i].ConnectionInfo); + } + + ReadConfigFile(TNC->Port, ProcessLine); + + TNC = TNCInfo[n]; + TNC->Port = n; + TNC->Hardware = H_TELNET; + TNC->RIG = &TNC->DummyRig; // Not using Rig control, so use Dummy + + // Get Menu Handles + + TCP = TNC->TCPInfo; +#ifndef LINBPQ + TNC->hDlg = SavehDlg; + TCP->hCMSWnd = SaveCMS; + TNC->hMonitor = SaveMonitor; + TCP->hActionMenu = SaveMenu1; + TCP->hDisMenu = SaveMenu2; + TCP->hLogMenu = SaveMenu3; + + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TNC->TCPInfo->CMS<<3); + CheckMenuItem(TCP->hLogMenu, 0, MF_BYPOSITION | LogEnabled<<3); + CheckMenuItem(TCP->hLogMenu, 1, MF_BYPOSITION | CMSLogEnabled<<3); +#endif + // Malloc TCP Session Stucts + + for (i = 0; i <= TNC->TCPInfo->MaxSessions; i++) + { + TNC->Streams[i].ConnectionInfo = zalloc(sizeof(struct ConnectionInfo)); + TCP->CurrentSockets = i; //Record max used to save searching all entries +#ifndef LINBPQ + if (i > 0) + ModifyMenu(TCP->hDisMenu,i - 1 ,MF_BYPOSITION | MF_STRING,IDM_DISCONNECT + 1, "."); +#endif + } + + TNC->PortRecord = PortRecord; + + Sleep(500); + OpenSockets(TNC); + OpenSockets6(TNC); + SetupListenSet(TNC); + CheckCMS(TNC); + ShowConnections(TNC); + } + + break; + + case 5: // Close + + TCP = TNC->TCPInfo; + + for (n = 1; n <= TCP->CurrentSockets; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + closesocket(sockptr->socket); + } + + shutdown(TCP->TCPSock, SD_BOTH); + + n = 0; + while (TCP->FBBsock[n]) + shutdown(TCP->FBBsock[n++], SD_BOTH); + + shutdown(TCP->Relaysock, SD_BOTH); + shutdown(TCP->HTTPsock, SD_BOTH); + shutdown(TCP->HTTPsock6, SD_BOTH); + + shutdown(TCP->sock6, SD_BOTH); + + n = 0; + while (TCP->FBBsock6[n]) + shutdown(TCP->FBBsock6[n++], SD_BOTH); + + shutdown(TCP->Relaysock6, SD_BOTH); + + Sleep(500); + closesocket(TCP->TCPSock); + + n = 0; + while (TCP->FBBsock[n]) + closesocket(TCP->FBBsock[n++]); + + closesocket(TCP->Relaysock); + + closesocket(TCP->sock6); + + n = 0; + while (TCP->FBBsock6[n]) + closesocket(TCP->FBBsock6[n++]); + + closesocket(TCP->Relaysock6); + closesocket(TCP->HTTPsock); + closesocket(TCP->HTTPsock6); + + return (0); + + case 6: // Scan Control + + return 0; // None Yet + + } + return 0; + +} + + + +static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL) +{ + int Len; + char msg[256]; + struct ConnectionInfo * sockptr; + int i,n; + + char CMSStatus[80] = ""; + + if (TNC->TCPInfo->CMS) + { + if (TNC->TCPInfo->CMSOK) + strcpy(CMSStatus, "CMS Ok"); + else + strcpy(CMSStatus, "No CMS"); + } + + Len = sprintf(Buff, "" + "Telnet StatusTelnet Status         %s", CMSStatus); + + Len += sprintf(&Buff[Len], ""); + + + Len += sprintf(&Buff[Len], ""); + + for (n = 1; n <= TNC->TCPInfo->CurrentSockets; n++) + { + sockptr=TNC->Streams[n].ConnectionInfo; + + if (!sockptr->SocketActive) + { + strcpy(msg,""); + } + else + { + if (sockptr->UserPointer == 0) + { + if (sockptr->HTTPMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + if (sockptr->WebSocks) + sprintf(msg,"", Addr); + else + sprintf(msg,"", Addr); + } + else if (sockptr->DRATSMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + sprintf(msg,"", Addr); + } + else + strcpy(msg,""); + } + else + { + i=sprintf(msg,"", + sockptr->UserPointer->UserName, sockptr->Callsign, + sockptr->FromHostBuffPutptr - sockptr->FromHostBuffGetptr); + } + } + Len += sprintf(&Buff[Len], "%s", msg); + } + + Len += sprintf(&Buff[Len], "
UserCallsignQueue
Idle  
Websock<%s  
HTTP<%s  
DRATS<%s  
Logging in  
%s%s%d
"); + return Len; +} + + + +void * TelnetExtInit(EXTPORTDATA * PortEntry) +{ + char msg[500]; + struct TNCINFO * TNC; + struct TCPINFO * TCP; + int port; + char * ptr; + int i; + HWND x=0; + +/* + UCHAR NC[257]; + WCHAR WC[1024]; + + int WLen, NLen; + + UINT UTF[256] = {0}; + UINT UTFL[256]; + + int n, u; + + for (n = 0; n < 256; n++) + NC[n] =n ; + + n = MultiByteToWideChar(437, 0, NC, 256, &WC[0], 1024); + + for (n = 0; n < 256; n++) + UTFL[n] = WideCharToMultiByte(CP_UTF8, 0, &WC[n], 1, &UTF[n], 1024 , NULL, NULL); + + // write UTF8 data as source + + { + HANDLE Handle; + int i, n, Len; + char Line [256]; + + + Handle = CreateFile("c:\\UTF8437Data.c", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + n = wsprintf (Line, "unsigned int CP437toUTF8Data[128] = {\r\n"); + + WriteFile(Handle, Line ,n , &n, NULL); + + if (Handle != INVALID_HANDLE_VALUE) + { + for (i = 128; i < 256; i += 4) + { + n = wsprintf (Line, " %d, %d, %d, %d, \r\n", + UTF[i], UTF[i+1], UTF[i+2], UTF[i+3]); + WriteFile(Handle, Line ,n , &n, NULL); + + } + + WriteFile(Handle, "};\r\n", 4, &n, NULL); + } + n = wsprintf (Line, "unsigned int CP437toUTF8DataLen[128] = {\r\n"); + + WriteFile(Handle, Line ,n , &n, NULL); + + if (Handle != INVALID_HANDLE_VALUE) + { + for (i = 128; i < 256;i += 4) + { + n = wsprintf (Line, " %d, %d, %d, %d, \r\n", + UTFL[i], UTFL[i+1], UTFL[i+2], UTFL[i+3]); + WriteFile(Handle, Line ,n , &n, NULL); + + } + + WriteFile(Handle, "};\r\n", 4, &n, NULL); + + SetEndOfFile(Handle); + + CloseHandle(Handle); + } + } +*/ + + DeleteTelnetLogFiles(); + + initUTF8(); + + sprintf(msg,"Telnet Server "); + WritetoConsoleLocal(msg); + + port=PortEntry->PORTCONTROL.PORTNUMBER; + + ReadConfigFile(port, ProcessLine); + + TNC = TNCInfo[port]; + + if (TNC == NULL) + { + // Not defined in Config file + + WritetoConsoleLocal("\n"); + return ExtProc; + } + + TCP = TNC->TCPInfo; + + TNC->Port = port; + + TNC->Hardware = H_TELNET; + + PortEntry->MAXHOSTMODESESSIONS = TNC->TCPInfo->MaxSessions + 1; // Default + + TNC->PortRecord = PortEntry; + + if (PortEntry->PORTCONTROL.PORTCALL[0] != 0) + ConvFromAX25(&PortEntry->PORTCONTROL.PORTCALL[0], TNC->NodeCall); + + PortEntry->PORTCONTROL.PROTOCOL = 10; // WINMOR/Pactor + PortEntry->PORTCONTROL.PORTQUALITY = 0; + PortEntry->SCANCAPABILITIES = NONE; // No Scan Control + PortEntry->PERMITGATEWAY = TRUE; + + ptr=strchr(TNC->NodeCall, ' '); + if (ptr) *(ptr) = 0; // Null Terminate + + TELNETMONVECPTR->HOSTAPPLFLAGS = 0x80; // Requext Monitoring + + if (TCP->LoginMsg[0] == 0) + strcpy(TCP->LoginMsg, "user:"); + if (TCP->PasswordMsg[0] == 0) + strcpy(TCP->PasswordMsg, "password:"); + if (TCP->cfgCTEXT[0] == 0) + { + char Call[10]; + memcpy(Call, MYNODECALL, 10); + strlop(Call, ' '); + + sprintf(TCP->cfgCTEXT, "Connected to %s's Telnet Server\r\n\r\n", Call); + } + + PortEntry->PORTCONTROL.TNC = TNC; + + TNC->WebWindowProc = WebProc; + TNC->WebWinX = 260; + TNC->WebWinY = 325; + +#ifndef LINBPQ + + CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, TelWndProc, 400, 300, NULL); + + TCP->hCMSWnd = CreateWindowEx(0, "STATIC", "CMS OK ", WS_CHILD | WS_VISIBLE, + 240,0,60,16, TNC->hDlg, NULL, hInstance, NULL); + + SendMessage(TCP->hCMSWnd, WM_SETFONT, (WPARAM)hFont, 0); + + x = CreateWindowEx(0, "STATIC", " User Callsign Queue", WS_CHILD | WS_VISIBLE, + 0,0,240,16, TNC->hDlg, NULL, hInstance, NULL); + + SendMessage(x, WM_SETFONT, (WPARAM)hFont, 0); + + + TNC->hMonitor= CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | WS_VSCROLL, + 0,20,400,2800, TNC->hDlg, NULL, hInstance, NULL); + + SendMessage(TNC->hMonitor, WM_SETFONT, (WPARAM)hFont, 0); + + hPopMenu = CreatePopupMenu(); + hPopMenu2 = CreatePopupMenu(); + hPopMenu3 = CreatePopupMenu(); + + AppendMenu(hPopMenu, MF_STRING + MF_POPUP, (UINT)hPopMenu2,"Logging Options"); + AppendMenu(hPopMenu, MF_STRING + MF_POPUP, (UINT)hPopMenu3,"Disconnect User"); + AppendMenu(hPopMenu, MF_STRING, TELNET_RECONFIG, "ReRead Config"); + AppendMenu(hPopMenu, MF_STRING, CMSENABLED, "CMS Access Enabled"); + AppendMenu(hPopMenu, MF_STRING, USECACHEDCMS, "Using Cached CMS Addresses"); + + AppendMenu(hPopMenu2, MF_STRING, IDM_LOGGING, "Log incoming connections"); + AppendMenu(hPopMenu2, MF_STRING, IDM_CMS_LOGGING, "Log CMS Connections"); + + AppendMenu(hPopMenu3, MF_STRING, IDM_DISCONNECT, "1"); + + TCP->hActionMenu = hPopMenu; + + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TCP->CMS<<3); + + TCP->hLogMenu = hPopMenu2; + TCP->hDisMenu = hPopMenu3; + + CheckMenuItem(TCP->hLogMenu, 0, MF_BYPOSITION | LogEnabled<<3); + CheckMenuItem(TCP->hLogMenu, 1, MF_BYPOSITION | CMSLogEnabled<<3); + +// ModifyMenu(hMenu, 1, MF_BYPOSITION | MF_OWNERDRAW | MF_STRING, 10000, 0); + +#endif + + // Malloc TCP Session Stucts + + for (i = 0; i <= TNC->TCPInfo->MaxSessions; i++) + { + TNC->Streams[i].ConnectionInfo = zalloc(sizeof(struct ConnectionInfo)); + TNC->Streams[i].ConnectionInfo->Number = i; + TCP->CurrentSockets = i; //Record max used to save searching all entries + + sprintf(msg,"%d", i); + +#ifndef LINBPQ + if (i > 1) + AppendMenu(TCP->hDisMenu, MF_STRING, IDM_DISCONNECT ,msg); +#endif + } + + OpenSockets(TNC); + OpenSockets6(TNC); + SetupListenSet(TNC); + TNC->RIG = &TNC->DummyRig; // Not using Rig control, so use Dummy + + if (TCP->CMS) + CheckCMS(TNC); + + WritetoConsoleLocal("\n"); + + ShowConnections(TNC); + + if (TCP->ReportHybrid) + SendWL2KRegisterHybrid(TNC); + + + return ExtProc; +} + +SOCKET OpenUDPSocket(struct TNCINFO * TNC) +{ + u_long param = 1; + struct sockaddr_in sinx; + int err, ret; + struct TCPINFO * TCP = TNC->TCPInfo; + char Msg[80]; + + TCP->SNMPsock = socket(AF_INET,SOCK_DGRAM,0); + + if (TCP->SNMPsock == INVALID_SOCKET) + { + WritetoConsoleLocal("Failed to create SNMP UDP socket"); + return 0; + } + + ioctl (TCP->SNMPsock, FIONBIO, ¶m); + + sinx.sin_family = AF_INET; + sinx.sin_addr.s_addr = INADDR_ANY; + + sinx.sin_port = htons(TCP->SNMPPort); + + ret = bind(TCP->SNMPsock, (struct sockaddr *) &sinx, sizeof(sinx)); + + if (ret != 0) + { + // Bind Failed + + err = WSAGetLastError(); + sprintf(Msg, "Bind Failed for SNMP UDP socket %d - error code = %d", TCP->SNMPPort, err); + WritetoConsoleLocal(Msg); + return 0; + } + + return TCP->SNMPsock; +} + + + +SOCKET OpenSocket4(struct TNCINFO * xTNC, int port) +{ + struct sockaddr_in local_sin; /* Local socket - internet style */ + struct sockaddr_in * psin; + SOCKET sock = 0; + u_long param=1; + + char szBuff[80]; + + psin=&local_sin; + psin->sin_family = AF_INET; + psin->sin_addr.s_addr = INADDR_ANY; + + if (port) + { + sock = socket(AF_INET, SOCK_STREAM, 0); + + if (sock == INVALID_SOCKET) + { + sprintf(szBuff, "socket() failed error %d\n", WSAGetLastError()); + WritetoConsoleLocal(szBuff); + return 0; + } + + setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char *)¶m,4); + + + psin->sin_port = htons(port); // Convert to network ordering + + if (bind( sock, (struct sockaddr FAR *) &local_sin, sizeof(local_sin)) == SOCKET_ERROR) + { + sprintf(szBuff, "bind(sock) failed port %d Error %d\n", port, WSAGetLastError()); + WritetoConsoleLocal(szBuff); + closesocket( sock ); + return FALSE; + } + + if (listen( sock, MAX_PENDING_CONNECTS ) < 0) + { + sprintf(szBuff, "listen(sock) failed port %d Error %d\n", port, WSAGetLastError()); + WritetoConsoleLocal(szBuff); + return FALSE; + } + ioctl(sock, FIONBIO, ¶m); + } + + return sock; +} + +BOOL OpenSockets(struct TNCINFO * TNC) +{ + struct sockaddr_in local_sin; /* Local socket - internet style */ + struct sockaddr_in * psin; + u_long param=1; + struct TCPINFO * TCP = TNC->TCPInfo; + int n; + + if (!TCP->IPV4) + return TRUE; + + psin=&local_sin; + psin->sin_family = AF_INET; + psin->sin_addr.s_addr = INADDR_ANY; + + if (TCP->TCPPort) + TCP->TCPSock = OpenSocket4(TNC, TCP->TCPPort); + + n = 0; + + while (TCP->FBBPort[n]) + { + TCP->FBBsock[n] = OpenSocket4(TNC, TCP->FBBPort[n]); + n++; + } + + if (TCP->RelayPort) + TCP->Relaysock = OpenSocket4(TNC, TCP->RelayPort); + + if (TCP->TriModePort) + { + TCP->TriModeSock = OpenSocket4(TNC, TCP->TriModePort); + TCP->TriModeDataSock = OpenSocket4(TNC, TCP->TriModePort + 1); + } + + if (TCP->HTTPPort) + TCP->HTTPsock = OpenSocket4(TNC, TCP->HTTPPort); + + if (TCP->APIPort) + TCP->APIsock = OpenSocket4(TNC, TCP->APIPort); + + if (TCP->SyncPort) + TCP->Syncsock = OpenSocket4(TNC, TCP->SyncPort); + + if (TCP->DRATSPort) + TCP->DRATSsock = OpenSocket4(TNC, TCP->DRATSPort); + + if (TCP->SNMPPort) + TCP->SNMPsock = OpenUDPSocket(TNC); + + CMSUser.UserName = _strdup("CMS"); + + TriModeUser.Secure = TRUE; + TriModeUser.UserName = _strdup("TRIMODE"); + TriModeUser.Callsign = zalloc(10); + + return TRUE; +} + +SOCKET OpenSocket6(struct TNCINFO * TNC, int port) +{ + + struct sockaddr_in6 local_sin; /* Local socket - internet style */ + struct sockaddr_in6 * psin; + SOCKET sock; + char szBuff[80]; + u_long param=1; + + memset(&local_sin, 0, sizeof(local_sin)); + + psin=&local_sin; + psin->sin6_family = AF_INET6; + psin->sin6_addr = in6addr_any; + psin->sin6_flowinfo = 0; + psin->sin6_scope_id = 0; + + sock = socket(AF_INET6, SOCK_STREAM, 0); + +#ifndef WIN32 + + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ¶m, sizeof(param)) < 0) + { + perror("setting option IPV6_V6ONLY"); + } + +#endif + + if (sock == INVALID_SOCKET) + { + sprintf(szBuff, "IPV6 socket() failed error %d\n", WSAGetLastError()); + WritetoConsoleLocal(szBuff); + return FALSE; + } + + psin->sin6_port = htons(port); // Convert to network ordering + + if (bind(sock, (struct sockaddr FAR *)psin, sizeof(local_sin)) == SOCKET_ERROR) + { + sprintf(szBuff, "IPV6 bind(sock) failed Port %d Error %d\n", port, WSAGetLastError()); + WritetoConsoleLocal(szBuff); + closesocket( sock ); + + return FALSE; + } + + if (listen( sock, MAX_PENDING_CONNECTS ) < 0) + { + sprintf(szBuff, "IPV6 listen(sock) failed Error %d\n", WSAGetLastError()); + WritetoConsoleLocal(szBuff); + + return FALSE; + } + + ioctl(sock, FIONBIO, ¶m); + return sock; +} + + +BOOL OpenSockets6(struct TNCINFO * TNC) +{ + struct sockaddr_in6 local_sin; /* Local socket - internet style */ + + struct sockaddr_in6 * psin; + struct TCPINFO * TCP = TNC->TCPInfo; + int n; + u_long param=1; + + if (!TCP->IPV6) + return TRUE; + + memset(&local_sin, 0, sizeof(local_sin)); + + psin=&local_sin; + psin->sin6_family = AF_INET6; + psin->sin6_addr = in6addr_any; + psin->sin6_flowinfo = 0; + psin->sin6_scope_id = 0; + + + if (TCP->TCPPort) + TCP->sock6 = OpenSocket6(TNC, TCP->TCPPort); + + n = 0; + + while (TCP->FBBPort[n]) + { + TCP->FBBsock6[n] = OpenSocket6(TNC, TCP->FBBPort[n]); + n++; + } + + if (TCP->RelayPort) + TCP->Relaysock6 = OpenSocket6(TNC, TCP->RelayPort); + + if (TCP->HTTPPort) + TCP->HTTPsock6 = OpenSocket6(TNC, TCP->HTTPPort); + + if (TCP->APIPort) + TCP->APIsock6 = OpenSocket6(TNC, TCP->APIPort); + + if (TCP->SyncPort) + TCP->Syncsock6 = OpenSocket6(TNC, TCP->SyncPort); + + if (TCP->DRATSPort) + TCP->DRATSsock6 = OpenSocket6(TNC, TCP->DRATSPort); + + + CMSUser.UserName = _strdup("CMS"); + + return TRUE; +} + +static VOID SetupListenSet(struct TNCINFO * TNC) +{ + // Set up master set of fd's for checking for incoming calls + + struct TCPINFO * TCP = TNC->TCPInfo; + SOCKET maxsock = 0; + int n; + fd_set * readfd = &TCP->ListenSet; + SOCKET sock; + + FD_ZERO(readfd); + + n = 0; + while (n < 100) + { + sock = TCP->FBBsock[n++]; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + } + + sock = TCP->TCPSock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->Relaysock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->HTTPsock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->APIsock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->Syncsock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->DRATSsock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->TriModeSock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->TriModeDataSock; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + n = 0; + while (n < 100) + { + sock = TCP->FBBsock6[n++]; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + } + + sock = TCP->sock6; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->Relaysock6; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->HTTPsock6; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->APIsock6; + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + + sock = TCP->DRATSsock6; + + if (sock) + { + FD_SET(sock, readfd); + if (sock > maxsock) + maxsock = sock; + } + TCP->maxsock = maxsock; +} + + +VOID TelnetPoll(int Port) +{ + struct TNCINFO * TNC = TNCInfo[Port]; + UCHAR * Poll = TNC->TXBuffer; + struct TCPINFO * TCP = TNC->TCPInfo; + struct STREAMINFO * STREAM; + int Msglen; + int Stream; + + // we now poll for incoming connections + + struct timeval timeout; + int retval; + int n; + struct ConnectionInfo * sockptr; + SOCKET sock; + int Active = 0; + SOCKET maxsock; + + fd_set readfd, writefd, exceptfd; + timeout.tv_sec = 0; + timeout.tv_usec = 0; // poll + + if (TCP->maxsock == 0) + goto nosocks; + + memcpy(&readfd, &TCP->ListenSet, sizeof(fd_set)); + + retval = select((int)(TCP->maxsock) + 1, &readfd, NULL, NULL, &timeout); + + if (retval == -1) + { + retval = WSAGetLastError(); + perror("listen select"); + } + + if (retval) + { + n = 0; + while (TCP->FBBsock[n]) + { + sock = TCP->FBBsock[n]; + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->FBBPort[n]); + + n++; + } + + sock = TCP->TCPSock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->TCPPort); + } + + sock = TCP->TriModeSock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->TriModePort); + } + sock = TCP->TriModeDataSock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->TriModePort + 1); + } + + sock = TCP->Relaysock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->RelayPort); + } + + sock = TCP->HTTPsock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->HTTPPort); + } + + sock = TCP->DRATSsock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->DRATSPort); + } + + sock = TCP->Syncsock; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->SyncPort); + } + + + + n = 0; + + while (TCP->FBBsock6[n]) + { + sock = TCP->FBBsock6[n]; + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->FBBPort[n]); + n++; + } + + sock = TCP->sock6; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->TCPPort); + } + + sock = TCP->Relaysock6; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->RelayPort); + } + + sock = TCP->HTTPsock6; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->HTTPPort); + } + + sock = TCP->DRATSsock6; + if (sock) + { + if (FD_ISSET(sock, &readfd)) + Socket_Accept(TNC, sock, TCP->DRATSPort); + } + } + + // look for data on any active sockets + + maxsock = 0; + + FD_ZERO(&readfd); + FD_ZERO(&writefd); + FD_ZERO(&exceptfd); + + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + // Should we use write event after a blocked write ???? + + if (sockptr->SocketActive) + { +// if (sockptr->socket == 0) +// { +// Debugprintf("Active Session but zero socket"); +// DataSocket_Disconnect(TNC, sockptr); +// return; +// } + + if (TNC->Streams[n].Connecting) + { + // look for complete or failed + + FD_SET(sockptr->socket, &writefd); + FD_SET(sockptr->socket, &exceptfd); + } + else + { + FD_SET(sockptr->socket, &readfd); + FD_SET(sockptr->socket, &exceptfd); + } + Active++; + if (sockptr->socket > maxsock) + maxsock = sockptr->socket; + + if (sockptr->TriModeDataSock) + { + FD_SET(sockptr->TriModeDataSock, &readfd); + FD_SET(sockptr->TriModeDataSock, &exceptfd); + + if (sockptr->TriModeDataSock > maxsock) + maxsock = sockptr->TriModeDataSock; + } + } + } + + if (Active) + { + retval = select((int)maxsock + 1, &readfd, &writefd, &exceptfd, &timeout); + + if (retval == -1) + { + perror("data select"); + Debugprintf("Telnet Select Error %d Active %d", WSAGetLastError(), Active); + + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + if (sockptr->SocketActive) + Debugprintf("Active Session %d socket %d", n, sockptr->socket); + } + } + else + { + if (retval) + { + // see who has data + + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->SocketActive) + { + sock = sockptr->socket; + + if (sockptr->TriModeDataSock) + { + if (FD_ISSET(sockptr->TriModeDataSock, &readfd)) + { + ProcessTriModeDataMessage(TNC, sockptr, sockptr->TriModeDataSock, &TNC->Streams[n]); + } + } + + if (FD_ISSET(sock, &readfd)) + { + if (sockptr->RelayMode) + DataSocket_ReadRelay(TNC, sockptr, sock, &TNC->Streams[n]); + else if (sockptr->HTTPMode) + DataSocket_ReadHTTP(TNC, sockptr, sock, n); + else if (sockptr->SyncMode) + DataSocket_ReadSync(TNC, sockptr, sock, n); + else if (sockptr->FBBMode) + DataSocket_ReadFBB(TNC, sockptr, sock, n); + else if (sockptr->DRATSMode) + DataSocket_ReadDRATS(TNC, sockptr, sock, n); + else + DataSocket_Read(TNC, sockptr, sock, &TNC->Streams[n]); + } + + if (FD_ISSET(sock, &exceptfd)) + { + Debugprintf("exceptfd set"); + Telnet_Connected(TNC, sockptr, sock, 1); + } + + if (FD_ISSET(sock, &writefd)) + Telnet_Connected(TNC, sockptr, sock, 0); + + } + } + } + } + } + + +nosocks: + + // Try SNMP + + if (TCP->SNMPsock) + { + struct sockaddr_in rxaddr; + char rxbuff[500]; + int addrlen = sizeof(struct sockaddr_in); + int Offset = 0; + + int len = recvfrom(TCP->SNMPsock, rxbuff, 500, 0,(struct sockaddr *)&rxaddr, &addrlen); + + if (len > 0) + { + UCHAR Reply[256]; + int SendLen; + + SendLen = ProcessSNMPPayload(rxbuff, len, Reply, &Offset); + + if (SendLen == 0) + return; + + sendto(TCP->SNMPsock, &Reply[Offset], SendLen, 0, (struct sockaddr *)&rxaddr, addrlen); + return; + } + } + while (TELNETMONVECPTR->HOSTTRACEQ) + { + int len; + time_t stamp; + BOOL MonitorNODES = FALSE; + MESSAGE * monbuff; + UCHAR * monchars; + + unsigned char buffer[1024] = "\xff\x1b\xb"; + + monbuff = Q_REM((void **)&TELNETMONVECPTR->HOSTTRACEQ); + monchars = (UCHAR *)monbuff; + + stamp = monbuff->Timestamp; + + if (monbuff->PORT & 0x80) // TX + buffer[2] = 91; + else + buffer[2] = 17; + + for (Stream = 0; Stream <= TCP->MaxSessions; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream]) + { + struct ConnectionInfo * sockptr = STREAM->ConnectionInfo; + + if (sockptr->BPQTermMode) + { + if (sizeof(void *) > 4) + monchars += 4; + + if (!sockptr->MonitorNODES && monchars[21] == 3 && monchars[22] == 0xcf && monchars[23] == 0xff) + { + len = 0; + } + else + { + unsigned long long SaveMMASK = MMASK; + BOOL SaveMTX = MTX; + BOOL SaveMCOM = MCOM; + BOOL SaveMUI = MUIONLY; + + IntSetTraceOptionsEx(sockptr->MMASK, sockptr->MTX, sockptr->MCOM, sockptr->MUIOnly); + len = IntDecodeFrame((MESSAGE *)monbuff, &buffer[3], stamp, sockptr->MMASK, FALSE, FALSE); + IntSetTraceOptionsEx(SaveMMASK, SaveMTX, SaveMCOM, SaveMUI); + + if (len) + { + len += 3; + buffer[len++] = 0xfe; + send(STREAM->ConnectionInfo->socket, buffer, len, 0); + } + } + } + } + } + + ReleaseBuffer(monbuff); + } + + for (Stream = 0; Stream <= TCP->MaxSessions; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] && STREAM->Attached == 0) + { + // New Attach + + int calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[Stream]->L4USER, TNC->Streams[Stream].MyCall); + TNC->Streams[Stream].MyCall[calllen] = 0; + + STREAM->Attached = TRUE; + STREAM->FramesQueued= 0; + STREAM->NoCMSFallback = 0; + + continue; + } + + if (STREAM->Attached) + { + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0 && STREAM->Attached) + { + // Node has disconnected - clear any connection + + struct ConnectionInfo * sockptr = TNC->Streams[Stream].ConnectionInfo; + SOCKET sock = sockptr->socket; + char Msg[80]; + PMSGWITHLEN buffptr; + + STREAM->Attached = FALSE; + STREAM->Connected = FALSE; + STREAM->NoCMSFallback = FALSE; + + sockptr->FromHostBuffPutptr = sockptr->FromHostBuffGetptr = 0; // clear any queued data + + while(TNC->Streams[Stream].BPQtoPACTOR_Q) + { + buffptr = (PMSGWITHLEN)Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q); + if (TelSendPacket(Stream, &TNC->Streams[Stream], buffptr, sockptr->ADIF) == FALSE) + { + // Send failed, and has saved packet + // free saved and discard any more on queue + + free(sockptr->ResendBuffer); + sockptr->ResendBuffer = NULL; + sockptr->ResendLen = 0; + + while(TNC->Streams[Stream].BPQtoPACTOR_Q) + { + buffptr=Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q); + ReleaseBuffer(buffptr); + } + break; + } + } + + while(TNC->Streams[Stream].PACTORtoBPQ_Q) + { + buffptr=Q_REM(&TNC->Streams[Stream].PACTORtoBPQ_Q); + ReleaseBuffer(buffptr); + } + + if (LogEnabled) + { + char logmsg[120]; + sprintf(logmsg,"%d Disconnected. Bytes Sent = %d Bytes Received %d\n", + sockptr->Number, STREAM->bytesTXed, STREAM->bytesRXed); + + WriteLog (logmsg); + } + + if (!sockptr->FBBMode) + { + sprintf(Msg,"*** Disconnected from Stream %d\r\n",Stream); + send(sock, Msg, (int)strlen(Msg),0); + } + + if (sockptr->UserPointer == &TriModeUser) + { + // Always Disconnect + + send(sockptr->socket, "DISCONNECTED\r\n", 14, 0); + return; + } + + if (sockptr->UserPointer == &CMSUser) + { + if (CMSLogEnabled) + { + char logmsg[120]; + sprintf(logmsg,"%d Disconnected. Bytes Sent = %d Bytes Received %d Time %d Seconds\r\n", + sockptr->Number, STREAM->bytesTXed, STREAM->bytesRXed, (int)(time(NULL) - sockptr->ConnectTime)); + + WriteCMSLog (logmsg); + } + + // Don't report if Internet down unless ReportRelayTraffic set) + + if (sockptr->RelaySession == FALSE || TCP->ReportRelayTraffic) + SendWL2KSessionRecord(sockptr->ADIF, STREAM->bytesTXed, STREAM->bytesRXed); + + WriteADIFRecord(sockptr->ADIF); + + if (sockptr->ADIF) + free(sockptr->ADIF); + + sockptr->ADIF = NULL; + + // Always Disconnect CMS Socket + + DataSocket_Disconnect(TNC, sockptr); + return; + } + + if (sockptr->RelayMode) + { + // Always Disconnect Relay Socket + + Sleep(100); + DataSocket_Disconnect(TNC, sockptr); + return; + } + + if (sockptr->Signon[0] || sockptr->ClientSession) // Outward Connect + { + Sleep(1000); + DataSocket_Disconnect(TNC, sockptr); + return; + } + + + if (TCP->DisconnectOnClose) + { + Sleep(1000); + DataSocket_Disconnect(TNC, sockptr); + } + else + { + char DisfromNodeMsg[] = "Disconnected from Node - Telnet Session kept\r\n"; + send(sockptr->socket, DisfromNodeMsg, (int)strlen(DisfromNodeMsg),0); + } + } + } + } + + for (Stream = 0; Stream <= TCP->MaxSessions; Stream++) + { + struct ConnectionInfo * sockptr = TNC->Streams[Stream].ConnectionInfo; + STREAM = &TNC->Streams[Stream]; + + if (sockptr->SocketActive && sockptr->Keepalive && L4LIMIT) + { +#ifdef WIN32 + if ((REALTIMETICKS - sockptr->LastSendTime) > (L4LIMIT - 60) * 9) // PC Ticks are about 10% slow +#else + if ((REALTIMETICKS - sockptr->LastSendTime) > (L4LIMIT - 60) * 10) +#endif + { + // Send Keepalive + + sockptr->LastSendTime = REALTIMETICKS; + BuffertoNode(sockptr, "Keepalive\r", 10); + } + } + + if (sockptr->ResendBuffer) + { + // Data saved after EWOULDBLOCK returned to send + + UCHAR * ptr = sockptr->ResendBuffer; + sockptr->ResendBuffer = NULL; + + SendAndCheck(sockptr, ptr, sockptr->ResendLen, 0); + free(ptr); + + continue; + } + + while (STREAM->BPQtoPACTOR_Q) + { + int datalen; + PMSGWITHLEN buffptr; + UCHAR * MsgPtr; + + // Make sure there is space. Linux TCP buffer is quite small + // Windows doesn't support SIOCOUTQ + +#ifndef WIN32 + int value = 0, error; + + error = ioctl(sockptr->socket, SIOCOUTQ, &value); + + if (value > 1500) + break; +#endif + buffptr = (PMSGWITHLEN)Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q); + STREAM->FramesQueued--; + datalen = (int)(buffptr->Len); + MsgPtr = &buffptr->Data[0]; + + if (STREAM->ConnectionInfo->TriMode) + { + ProcessTrimodeResponse(TNC, STREAM, MsgPtr, datalen); + ReleaseBuffer(buffptr); + return; + } + + + if (TNC->Streams[Stream].Connected) + { + if (sockptr->SyncMode) + { + // Suppress Conected and SID - Relay doesn't understand them + + if (strstr(buffptr->Data, "Connected to") || memcmp(buffptr->Data, "[BPQ-", 5) == 0) + { + ReleaseBuffer(buffptr); + return; + } + } + + if (TelSendPacket(Stream, STREAM, buffptr, sockptr->ADIF) == FALSE) + { + // Send failed, and has requeued packet + // Dont send any more + + break; + } + } + else // Not Connected + { + // Command. Do some sanity checking and look for things to process locally + + datalen--; // Exclude CR + MsgPtr[datalen] = 0; // Null Terminate + + if (_stricmp(MsgPtr, "NoFallback") == 0) + { + TNC->Streams[Stream].NoCMSFallback = TRUE; + buffptr->Len = sprintf(&buffptr->Data[0], "Ok\r"); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + return; + } + + if (_memicmp(MsgPtr, "D", 1) == 0) + { + TNC->Streams[Stream].ReportDISC = TRUE; // Tell Node + ReleaseBuffer(buffptr); + return; + } + + if ((_memicmp(MsgPtr, "C", 1) == 0) && MsgPtr[1] == ' ' && datalen > 2) // Connect + { + char Host[100] = ""; + char P2[100] = ""; + char P3[100] = ""; + char P4[100] = ""; + char P5[100] = ""; + char P6[100] = ""; + char P7[100] = ""; + unsigned int Port = 0; + int n; + + n = sscanf(&MsgPtr[2], "%s %s %s %s %s %s %s", + &Host[0], &P2[0], &P3[0], &P4[0], &P5[0], &P6[0], &P7[0]); + + sockptr->Signon[0] = 0; // Not outgoing; + sockptr->Keepalive = FALSE; // No Keepalives + sockptr->NoCallsign = FALSE; + sockptr->UTF8 = 0; // Not UTF8 + + if (_stricmp(Host, "HOST") == 0) + { + Port = atoi(P2); + + if (Port > MaxBPQPortNo || TCP->CMDPort[Port] == 0) + { + buffptr->Len = sprintf(&buffptr->Data[0], "Error - Invalid HOST Port\r"); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + STREAM->NeedDisc = 10; + return; + } + + STREAM->Connecting = TRUE; + sockptr->CMSSession = FALSE; + sockptr->FBBMode = FALSE; + + if (P3[0] == 'K' || P4[0] == 'K' || P5[0] == 'K' || P6[0] == 'K') + { + sockptr->Keepalive = TRUE; + sockptr->LastSendTime = REALTIMETICKS; + } + + if (P3[0] == 'S' || P4[0] == 'S' || P5[0] == 'S' || P6[0] == 'S') + { + // Set Say flag on partner session + + struct _TRANSPORTENTRY * Sess = TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]->L4CROSSLINK; + if (Sess) + Sess->STAYFLAG = 1; + } + + if (_stricmp(P3, "NOCALL") == 0 || _stricmp(P4, "NOCALL") == 0 || _stricmp(P5, "NOCALL") == 0 || _stricmp(P6, "NOCALL") == 0) + sockptr->NoCallsign = TRUE; + + if (_stricmp(P3, "TRANS") == 0 || _stricmp(P4, "TRANS") == 0 || _stricmp(P5, "TRANS") == 0 || _stricmp(P6, "TRANS") == 0) + { + sockptr->FBBMode = TRUE; + sockptr->NeedLF = TRUE; + } + + TCPConnect(TNC, TCP, STREAM, "127.0.0.1", TCP->CMDPort[Port], sockptr->FBBMode); + ReleaseBuffer(buffptr); + return; + } + + if (_stricmp(Host, "RELAY") == 0) + { + if (P2[0] == 0) + { + strcpy(P2, TCP->RELAYHOST); + strcpy(P3, "8772"); + } + + if (P2[0]) + { + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->CMSSession = TRUE; + STREAM->ConnectionInfo->RelaySession = TRUE; + TCPConnect(TNC, TCP, STREAM, P2, atoi(P3), TRUE); + ReleaseBuffer(buffptr); + return; + } + } + + if (_stricmp(Host, "SYNC") == 0) + { + if (P2[0] == 0) + { + strcpy(P2, TCP->RELAYHOST); + strcpy(P3, "8780"); + } + + if (P2[0]) + { + sockptr->CMSSession = FALSE; + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->SyncMode = TRUE; + TCPConnect(TNC, TCP, STREAM, P2, atoi(P3), TRUE); + ReleaseBuffer(buffptr); + return; + } + } + + + if (_stricmp(Host, "CMS") == 0) + { + if (TCP->CMS == 0 || !TCP->CMSOK) + { + if (TCP->RELAYHOST[0] && TCP->FallbacktoRelay && STREAM->NoCMSFallback == 0) + { + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->CMSSession = TRUE; + STREAM->ConnectionInfo->RelaySession = TRUE; + TCPConnect(TNC, TCP, STREAM, TCP->RELAYHOST, 8772, TRUE); + ReleaseBuffer(buffptr); + return; + } + + buffptr->Len = sprintf(&buffptr->Data[0], "Error - CMS Not Available\r"); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + STREAM->NeedDisc = 10; + CheckCMS(TNC); + + return; + } + + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->CMSSession = TRUE; + STREAM->ConnectionInfo->RelaySession = FALSE; + CMSConnect(TNC, TCP, STREAM, Stream); + ReleaseBuffer(buffptr); + + return; + } + + // Outward Connect. + + // Only Allow user specified host if Secure Session + + if (TCP->SecureTelnet) + { + struct _TRANSPORTENTRY * Sess = TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]; + +// if (Sess && Sess->L4CROSSLINK) +// Sess = Sess->L4CROSSLINK; + + if (Sess && !Sess->Secure_Session) + { + buffptr->Len = sprintf(&buffptr->Data[0], "Error - Telnet Outward Connect needs SYSOP Status\r"); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + STREAM->NeedDisc = 10; + return; + } + } + + Port = atoi(P2); + + if (Port) + { + int useFBBMode = TRUE; + + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->CMSSession = FALSE; + STREAM->ConnectionInfo->RelaySession = FALSE; + + if (_stricmp(P3, "TELNET") == 0) + { +// // FBB with CRLF + + STREAM->ConnectionInfo->NeedLF = TRUE; + } + else if (_stricmp(P3, "REALTELNET") == 0) + { +// // Telnet Mode with CRLF + + useFBBMode = FALSE; + STREAM->ConnectionInfo->NeedLF = TRUE; + } + else + STREAM->ConnectionInfo->NeedLF = FALSE; + + + STREAM->ConnectionInfo->FBBMode = TRUE; + + if (_stricmp(P3, "NOCALL") == 0 || _stricmp(P4, "NOCALL") == 0 || _stricmp(P5, "NOCALL") == 0 || _stricmp(P6, "NOCALL") == 0) + { + STREAM->ConnectionInfo->NoCallsign = TRUE; + } + else + { + if (_stricmp(P3, "NEEDLF") == 0 || STREAM->ConnectionInfo->NeedLF) + { + // Send LF after each param + + if (P6[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r\n%s\r\n%s\r\n", P4, P5, P6); + else + if (P5[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r\n%s\r\n", P4, P5); + else + if (P4[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r\n", P4); + } + else + { + if (P5[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r%s\r%s\r", P3, P4, P5); + else + if (P4[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r%s\r", P3, P4); + else + if (P3[0]) + sprintf(STREAM->ConnectionInfo->Signon, "%s\r", P3); + } + } + + TCPConnect(TNC, TCP, STREAM, Host, Port, useFBBMode); + ReleaseBuffer(buffptr); + return; + } + } + + buffptr->Len = sprintf(&buffptr->Data[0], "Error - Invalid Command\r"); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + STREAM->NeedDisc = 10; + return; + } + } + + Msglen = sockptr->FromHostBuffPutptr - sockptr->FromHostBuffGetptr; + + if (Msglen) + { + int Paclen = 0; + int Queued = 0; + TRANSPORTENTRY * Sess1 = TNC->PortRecord->ATTACHEDSESSIONS[Stream]; + TRANSPORTENTRY * Sess2 = NULL; + + if (Sess1) + Sess2 = Sess1->L4CROSSLINK; + + // Can't use TXCount - it is Semaphored= + + Queued = C_Q_COUNT(&TNC->Streams[Stream].PACTORtoBPQ_Q); + Queued += C_Q_COUNT((UINT *)&TNC->PortRecord->PORTCONTROL.PORTRX_Q); + + if (Sess2) + Queued += CountFramesQueuedOnSession(Sess2); + + if (Sess1) + Queued += CountFramesQueuedOnSession(Sess1); + + if (Queued > 15) + continue; + + if (Sess1) + Paclen = Sess1->SESSPACLEN; + + if (Paclen == 0) + Paclen = 256; + + ShowConnections(TNC); + + if (Msglen > Paclen) + Msglen = Paclen; + + if (Sess1) Sess1->L4KILLTIMER = 0; + if (Sess2) Sess2->L4KILLTIMER = 0; + + SendtoNode(TNC, Stream, &sockptr->FromHostBuffer[sockptr->FromHostBuffGetptr], Msglen); + sockptr->FromHostBuffGetptr += Msglen; + sockptr->LastSendTime = REALTIMETICKS; + } + } +} + +#ifndef LINBPQ + +LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + struct ConnectionInfo * sockptr; + SOCKET sock; + int i, n; + struct TNCINFO * TNC; + struct TCPINFO * TCP; + struct _EXTPORTDATA * PortRecord; + HWND SavehDlg, SaveCMS, SaveMonitor; + HMENU SaveMenu1, SaveMenu2, SaveMenu3; + MINMAXINFO * mmi; + + LPMEASUREITEMSTRUCT lpmis; // pointer to item of data + LPDRAWITEMSTRUCT lpdis; // pointer to item drawing data + +// struct ConnectionInfo * ConnectionInfo; + + for (i=1; i <= MAXBPQPORTS; i++) + { + TNC = TNCInfo[i]; + if (TNC == NULL) + continue; + + if (TNC->hDlg == hWnd) + break; + } + + if (TNC == NULL) + return DefMDIChildProc(hWnd, message, wParam, lParam); + + switch (message) + { +// case WM_SIZING: + case WM_SIZE: + { + RECT rcClient; + int ClientHeight, ClientWidth; + + GetClientRect(TNC->hDlg, &rcClient); + + ClientHeight = rcClient.bottom; + ClientWidth = rcClient.right; + + MoveWindow(TNC->hMonitor, 0,20 ,ClientWidth-4, ClientHeight-25, TRUE); + break; + } + + case WM_GETMINMAXINFO: + + mmi = (MINMAXINFO *)lParam; + mmi->ptMaxSize.x = 400; + mmi->ptMaxSize.y = 500; + mmi->ptMaxTrackSize.x = 400; + mmi->ptMaxTrackSize.y = 500; + break; + + + case WM_MDIACTIVATE: + { + // Set the system info menu when getting activated + + if (lParam == (LPARAM) hWnd) + { + // Activate + + RemoveMenu(hBaseMenu, 1, MF_BYPOSITION); + AppendMenu(hBaseMenu, MF_STRING + MF_POPUP, (UINT)hPopMenu, "Actions"); + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hBaseMenu, (LPARAM) hWndMenu); + + } + else + { + // Deactivate + + SendMessage(ClientWnd, WM_MDISETMENU, (WPARAM) hMainFrameMenu, (LPARAM) NULL); + } + + // call DrawMenuBar after the menu items are set + DrawMenuBar(FrameWnd); + + return TRUE; //DefMDIChildProc(hWnd, message, wParam, lParam); + + } + + case WM_SYSCOMMAND: + + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + switch (wmId) + { + case SC_RESTORE: + + TNC->Minimized = FALSE; + SendMessage(ClientWnd, WM_MDIRESTORE, (WPARAM)hWnd, 0); + return DefMDIChildProc(hWnd, message, wParam, lParam); + + case SC_MINIMIZE: + + TNC->Minimized = TRUE; + return DefMDIChildProc(hWnd, message, wParam, lParam); + } + + return DefMDIChildProc(hWnd, message, wParam, lParam); + + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + // Parse the menu selections: + + if (wmId > IDM_DISCONNECT && wmId < IDM_DISCONNECT+MaxSockets+1) + { + // disconnect user + + sockptr = TNC->Streams[wmId-IDM_DISCONNECT].ConnectionInfo; + + if (sockptr->SocketActive) + { + sock=sockptr->socket; + + send(sock,disMsg, (int)strlen(disMsg),0); + + Sleep (1000); + + shutdown(sock,2); + + DataSocket_Disconnect(TNC, sockptr); + + TNC->Streams[wmId-IDM_DISCONNECT].ReportDISC = TRUE; //Tell Node + + return 0; + } + } + + switch (wmId) + { + case CMSENABLED: + + // Toggle CMS Enabled Flag + + TCP = TNC->TCPInfo; + + TCP->CMS = !TCP->CMS; + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TCP->CMS<<3); + + if (TCP->CMS) + CheckCMS(TNC); + else + { + TCP->CMSOK = FALSE; + SetWindowText(TCP->hCMSWnd, "CMS Off"); + } + break; + + case IDM_LOGGING: + + // Toggle Logging Flag + + LogEnabled = !LogEnabled; + CheckMenuItem(TNC->TCPInfo->hLogMenu, 0, MF_BYPOSITION | LogEnabled<<3); + + break; + + case IDM_CMS_LOGGING: + + // Toggle Logging Flag + + LogEnabled = !LogEnabled; + CheckMenuItem(TNC->TCPInfo->hLogMenu, 1, MF_BYPOSITION | CMSLogEnabled<<3); + + break; + + case TELNET_RECONFIG: + + if (!ProcessConfig()) + { + Consoleprintf("Failed to reread config file - leaving config unchanged"); + break; + } + + FreeConfig(); + + for (n = 1; n <= TNC->TCPInfo->CurrentSockets; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + sockptr->SocketActive = FALSE; + closesocket(sockptr->socket); + } + + TCP = TNC->TCPInfo; + + shutdown(TCP->TCPSock, SD_BOTH); + shutdown(TCP->sock6, SD_BOTH); + + n = 0; + while (TCP->FBBsock[n]) + shutdown(TCP->FBBsock[n++], SD_BOTH); + + shutdown(TCP->Relaysock, SD_BOTH); + shutdown(TCP->HTTPsock, SD_BOTH); + shutdown(TCP->HTTPsock6, SD_BOTH); + + + n = 0; + while (TCP->FBBsock6[n]) + shutdown(TCP->FBBsock[n++], SD_BOTH); + + shutdown(TCP->Relaysock6, SD_BOTH); + + Sleep(500); + + closesocket(TCP->TCPSock); + closesocket(TCP->sock6); + + n = 0; + while (TCP->FBBsock[n]) + closesocket(TCP->FBBsock[n++]); + + n = 0; + while (TCP->FBBsock6[n]) + closesocket(TCP->FBBsock6[n++]); + + closesocket(TCP->Relaysock); + closesocket(TCP->Relaysock6); + closesocket(TCP->HTTPsock); + closesocket(TCP->HTTPsock6); + + // Save info from old TNC record + + n = TNC->Port; + PortRecord = TNC->PortRecord; + SavehDlg = TNC->hDlg; + SaveCMS = TCP->hCMSWnd; + SaveMonitor = TNC->hMonitor; + SaveMenu1 = TCP->hActionMenu; + SaveMenu2 = TCP->hDisMenu; + SaveMenu3 = TCP->hLogMenu; + + // Free old TCP Session Stucts + + for (i = 0; i <= TNC->TCPInfo->MaxSessions; i++) + { + free(TNC->Streams[i].ConnectionInfo); + } + + ReadConfigFile(TNC->Port, ProcessLine); + + TNC = TNCInfo[n]; + TNC->Port = n; + TNC->Hardware = H_TELNET; + TNC->hDlg = SavehDlg; + TNC->RIG = &TNC->DummyRig; // Not using Rig control, so use Dummy + + // Get Menu Handles + + TCP = TNC->TCPInfo; + + TCP->hCMSWnd = SaveCMS; + TNC->hMonitor = SaveMonitor; + TCP->hActionMenu = SaveMenu1; + TCP->hDisMenu = SaveMenu2; + TCP->hLogMenu = SaveMenu3; + + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TNC->TCPInfo->CMS<<3); + CheckMenuItem(TCP->hLogMenu, 0, MF_BYPOSITION | LogEnabled<<3); + CheckMenuItem(TCP->hLogMenu, 1, MF_BYPOSITION | CMSLogEnabled<<3); + + // Malloc TCP Session Stucts + + for (i = 0; i <= TNC->TCPInfo->MaxSessions; i++) + { + TNC->Streams[i].ConnectionInfo = zalloc(sizeof(struct ConnectionInfo)); + TNC->Streams[i].ConnectionInfo->Number = i; + + TCP->CurrentSockets = i; //Record max used to save searching all entries + + if (i > 0) + ModifyMenu(TCP->hDisMenu,i - 1 ,MF_BYPOSITION | MF_STRING,IDM_DISCONNECT + 1, "."); + } + + TNC->PortRecord = PortRecord; + + Sleep(500); + OpenSockets(TNC); + OpenSockets6(TNC); + SetupListenSet(TNC); + CheckCMS(TNC); + ShowConnections(TNC); + + break; + default: + return DefMDIChildProc(hWnd, message, wParam, lParam); + } + break; + + // case WM_SIZE: + +// if (wParam == SIZE_MINIMIZED) +// return (0); + + case WM_CTLCOLORDLG: + + return (LONG)bgBrush; + + + case WM_CTLCOLORSTATIC: + { + HDC hdcStatic = (HDC)wParam; + + if (TNC->TCPInfo->hCMSWnd == (HWND)lParam) + { + if (TNC->TCPInfo->CMSOK) + SetTextColor(hdcStatic, RGB(0, 128, 0)); + else + SetTextColor(hdcStatic, RGB(255, 0, 0)); + } + + SetBkMode(hdcStatic, TRANSPARENT); + return (LONG)bgBrush; + } + case WM_MEASUREITEM: + + // Retrieve pointers to the menu item's + // MEASUREITEMSTRUCT structure and MYITEM structure. + + lpmis = (LPMEASUREITEMSTRUCT) lParam; + lpmis->itemWidth = 300; + + return TRUE; + + case WM_DRAWITEM: + + // Get pointers to the menu item's DRAWITEMSTRUCT + // structure and MYITEM structure. + + lpdis = (LPDRAWITEMSTRUCT) lParam; + + // If the user has selected the item, use the selected + // text and background colors to display the item. + + SetTextColor(lpdis->hDC, RGB(0, 128, 0)); + + if (TNC->TCPInfo->CMS) + { + if (TNC->TCPInfo->CMSOK) + TextOut(lpdis->hDC, 340, lpdis->rcItem.top + 2, "CMS OK", 6); + else + { + SetTextColor(lpdis->hDC, RGB(255, 0, 0)); + TextOut(lpdis->hDC, 340, lpdis->rcItem.top + 2, "NO CMS", 6); + } + } + else + TextOut(lpdis->hDC, 340, lpdis->rcItem.top + 2, " ", 13); + + return TRUE; + + case WM_DESTROY: + + break; + } + + return DefMDIChildProc(hWnd, message, wParam, lParam); + +} +#endif + +int Socket_Accept(struct TNCINFO * TNC, SOCKET SocketId, int Port) +{ + int n, addrlen = sizeof(struct sockaddr_in6); + struct sockaddr_in6 sin6; + + struct ConnectionInfo * sockptr; + SOCKET sock; + char Negotiate[6]={IAC,WILL,suppressgoahead,IAC,WILL,echo}; +// char Negotiate[3]={IAC,WILL,echo}; + struct TCPINFO * TCP = TNC->TCPInfo; + HMENU hDisMenu = TCP->hDisMenu; + u_long param=1; + + // if for TriModeData Session, use the TriMode Control connection entry + + if (SocketId == TCP->TriModeDataSock) + { + sockptr = TNC->TCPInfo->TriModeControlSession; + sock = accept(SocketId, (struct sockaddr *)&sockptr->sin, &addrlen); + sockptr->TriModeDataSock = sock; + ioctl(sock, FIONBIO, ¶m); + + return 0; + } + +// Find a free Session + + for (n = 1; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->SocketActive == FALSE) + { + sock = accept(SocketId, (struct sockaddr *)&sockptr->sin, &addrlen); + + if (sock == INVALID_SOCKET) + { + Debugprintf("BPQ32 Telnet accept() failed Error %d", WSAGetLastError()); + return FALSE; + } + + // Log, including port + + if (LogEnabled) + { + char Addr[256]; + char logmsg[512]; + + Tel_Format_Addr(sockptr, Addr); + + sprintf(logmsg,"%d %s Incoming Connect on Port %d\n", sockptr->Number, Addr, Port); + WriteLog (logmsg); + } + + + +// Debugprintf("BPQ32 Telnet accept() Sock %d", sock); + + ioctl(sock, FIONBIO, ¶m); + + sockptr->socket = sock; + sockptr->SocketActive = TRUE; + sockptr->InputLen = 0; + sockptr->Number = n; + sockptr->LoginState = 0; + sockptr->UserPointer = 0; + sockptr->DoEcho = FALSE; + sockptr->BPQTermMode = FALSE; + sockptr->ConnectTime = time(NULL); + sockptr->Keepalive = FALSE; + sockptr->UTF8 = 0; + + TNC->Streams[n].bytesRXed = TNC->Streams[n].bytesTXed = 0; + TNC->Streams[n].FramesQueued = 0; + + sockptr->HTTPMode = FALSE; + sockptr->APIMode = FALSE; + sockptr->SyncMode = FALSE; + sockptr->DRATSMode = FALSE; + sockptr->FBBMode = FALSE; + sockptr->RelayMode = FALSE; + sockptr->ClientSession = FALSE; + sockptr->NeedLF = FALSE; + sockptr->TNC = TNC; + sockptr->WebSocks = 0; + + if (sockptr->ADIF == NULL) + sockptr->ADIF = malloc(sizeof(struct ADIF)); + + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + + if (SocketId == TCP->HTTPsock || SocketId == TCP->HTTPsock6) + sockptr->HTTPMode = TRUE; + + if (SocketId == TCP->APIsock || SocketId == TCP->APIsock6) + { + sockptr->HTTPMode = TRUE; // API is a type of HTTP socket + sockptr->APIMode = TRUE; + } + else if (SocketId == TCP->Syncsock || SocketId == TCP->Syncsock6) + sockptr->SyncMode = TRUE; + else if (SocketId == TCP->DRATSsock || SocketId == TCP->DRATSsock6) + sockptr->DRATSMode = TRUE; + + else if (SocketId == TCP->Relaysock || SocketId == TCP->Relaysock6) + { + sockptr->RelayMode = TRUE; + sockptr->FBBMode = TRUE; + } + else if (SocketId == TCP->TriModeSock) + { + sockptr->TriMode = TRUE; + sockptr->FBBMode = TRUE; + TNC->TCPInfo->TriModeControlSession = sockptr; + sockptr->TriModeConnected = FALSE; + sockptr->TriModeDataSock = 0; + } + else if (SocketId != TCP->TCPSock && SocketId != TCP->sock6) // We can have several listening FBB mode sockets + sockptr->FBBMode = TRUE; +#ifndef LINBPQ + ModifyMenu(hDisMenu, n - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + n, "."); + DrawMenuBar(TNC->hDlg); +#endif + ShowConnections(TNC); + + if (sockptr->HTTPMode) + return 0; + + if (sockptr->DRATSMode) + { + send(sock, "100 Authentication not required\n", 33, 0); + sockptr->LoginState = 2; + return 0; + } + + if (sockptr->SyncMode) + { + char MyCall[16] = ""; + char Hello[32]; + int Len; + memcpy(MyCall, MYNODECALL, 10); + strlop(MyCall, ' '); + strlop(MyCall, '-'); + + Len = sprintf(Hello, "POSYNCHELLO %s\r", MyCall); + + send(sock, Hello, Len, 0); + return 0; + } + else + if (sockptr->RelayMode) + { + send(sock,"\r\rCallsign :\r", 13,0); + } + else + if (sockptr->TriMode) + { + // Trimode emulator Control Connection. + + sockptr->UserPointer = &TriModeUser; + + + send(sock,"CMD\r\n", 5,0); + sockptr->LoginState = 5; + } + else + if (sockptr->FBBMode == FALSE) + { + send(sock, Negotiate, 6, 0); + send(sock, TCP->LoginMsg, (int)strlen(TCP->LoginMsg), 0); + } + + if (sockptr->FromHostBuffer == 0) + { + sockptr->FromHostBuffer = malloc(10000); + sockptr->FromHostBufferSize = 10000; + } + + sockptr->FromHostBuffPutptr = sockptr->FromHostBuffGetptr = 0; + + return 0; + } + } + + // No free sessions. Must accept() then close + + sock = accept(SocketId, (struct sockaddr *)&sin6, &addrlen); + + send(sock,"No Free Sessions\r\n", 18,0); + Debugprintf("No Free Telnet Sessions"); + + Sleep (1000); + closesocket(sock); + + return 0; +} + +/* +int Socket_Data(struct TNCINFO * TNC, int sock, int error, int eventcode) +{ + int n; + struct ConnectionInfo * sockptr; + struct TCPINFO * TCP = TNC->TCPInfo; + HMENU hDisMenu = TCP->hDisMenu; + + // Find Connection Record + + for (n = 0; n <= TCP->CurrentSockets; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->socket == sock && sockptr->SocketActive) + { +#ifndef LINBPQ + switch (eventcode) + { + case FD_READ: + + if (sockptr->RelayMode) + return DataSocket_ReadRelay(TNC, sockptr, sock, &TNC->Streams[n]); + + if (sockptr->HTTPMode) + return DataSocket_ReadHTTP(TNC, sockptr, sock, n); + + if (sockptr->FBBMode) + return DataSocket_ReadFBB(TNC, sockptr, sock, n); + else + return DataSocket_Read(TNC, sockptr, sock, &TNC->Streams[n]); + + case FD_WRITE: + + return 0; + + case FD_OOB: + + return 0; + + case FD_ACCEPT: + + return 0; + + case FD_CONNECT: + + return 0; + + case FD_CLOSE: + + TNC->Streams[n].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + return 0; +#endif + } + + } + + return 0; +} + +*/ +#define PACLEN 100 + +VOID SendtoNode(struct TNCINFO * TNC, int Stream, char * Msg, int MsgLen) +{ + PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff(); + + if (buffptr == NULL) + return; // No buffers, so ignore + + if (TNC->Streams[Stream].Connected == 0) + { + // Connection Closed - Get Another + + struct ConnectionInfo * sockptr = TNC->Streams[Stream].ConnectionInfo; + + if (ProcessIncommingConnect(TNC, sockptr->Callsign, sockptr->Number, FALSE) == FALSE) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return; + } + + if (sockptr->UserPointer) + TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]->Secure_Session = sockptr->UserPointer->Secure; + } + + buffptr->Len= MsgLen; // Length + + memcpy(&buffptr->Data, Msg, MsgLen); + + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); +} + +int InnerProcessData(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM, int len); + + +int DataSocket_Read(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM) +{ + int len=0, maxlen; + char NLMsg[3]={13,10,0}; + byte * IACptr; + BOOL wait; + + struct TCPINFO * TCP = TNC->TCPInfo; + int SendIndex = 0; + byte * TelCommand; + int beforeIAC = 0; + int rest = 0; + + ioctl(sock,FIONREAD,&len); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (len > maxlen) len=maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); + + if (len == SOCKET_ERROR || len ==0) + { + // Failed or closed - clear connection + + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + + // If message contains Telnet Commands we should process the data in the buffer first + // and not echo the commands! + + + IACptr = memchr(sockptr->InputBuffer, IAC, sockptr->InputLen + len); + + if (IACptr) + { + beforeIAC = IACptr - sockptr->InputBuffer; + rest = (sockptr->InputLen + len) - beforeIAC; + + if (beforeIAC) + { + TelCommand = malloc(rest); + memcpy(TelCommand, IACptr, rest); + InnerProcessData(TNC, sockptr, sock, STREAM, beforeIAC); + + // There may still be data in buffer, but it may be less than before + // Put IAC and following into buffer + + memcpy(&sockptr->InputBuffer[sockptr->InputLen], TelCommand, rest); + len -= sockptr->InputLen; + free(TelCommand); + } + } + +IACLoop: + + IACptr = memchr(sockptr->InputBuffer, IAC, sockptr->InputLen + len); + + if (IACptr) + { + // There still may be data in the buffer. + + wait = ProcessTelnetCommand(sockptr, IACptr, &rest); + + if (wait) + { + // Need more. + + sockptr->InputLen += len; + return 0; // wait for more chars + } + + // If ProcessTelnet Command returns FALSE, then it has removed the IAC and its + // params from the buffer. There may still be more to process. + + if (rest == 0) + return 0; // Nothing Left + + memmove(&sockptr->InputBuffer[sockptr->InputLen], IACptr + len - rest, rest); + len = rest; + + goto IACLoop; // There may be more + } + + return InnerProcessData(TNC, sockptr, sock, STREAM, len); +} + + +int InnerProcessData(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM, int len) +{ + int InputLen, MsgLen, i, n,charsAfter; + char NLMsg[3]={13,10,0}; + byte * CRPtr; + byte * LFPtr; + byte * BSptr; + byte * MsgPtr; + char logmsg[1000]; + struct UserRec * USER; + struct TCPINFO * TCP = TNC->TCPInfo; + int SendIndex = 0; + + // echo data just read + + if (sockptr->DoEcho && sockptr->LoginState != 1) // Password + send(sockptr->socket,&sockptr->InputBuffer[sockptr->InputLen], len, 0); + + sockptr->InputLen += len; + + // look for backspaces in data just read + + BSptr = memchr(&sockptr->InputBuffer[0], 8, sockptr->InputLen); + + if (BSptr == NULL) + BSptr = memchr(&sockptr->InputBuffer[0], 127, sockptr->InputLen); + + if (BSptr != 0) + { + // single char or BS as last is most likely, and worth treating as a special case + + int n; + + charsAfter = sockptr->InputLen - (int)((BSptr-&sockptr->InputBuffer[0])) - 1; + + if (charsAfter == 0) + { + sockptr->InputLen--; + if (sockptr->InputLen > 0) + sockptr->InputLen--; //Remove last char + + goto noBS; + } + // more than single char. Copy stuff after bs over char before + + memmove(BSptr-1, BSptr+1, charsAfter); + + n = sockptr->InputLen; + + sockptr->InputLen -= 2; // drop bs and char before + + // see if more bs chars +BSCheck: + BSptr = memchr(&sockptr->InputBuffer[0], 8, sockptr->InputLen); + + if (BSptr == NULL) + BSptr = memchr(&sockptr->InputBuffer[0], 127, sockptr->InputLen); + + if (BSptr == NULL) + goto noBS; + + charsAfter = sockptr->InputLen - (int)((BSptr-&sockptr->InputBuffer[0])) - 1; + + if (charsAfter == 0) + { + sockptr->InputLen--; // Remove BS + if (sockptr->InputLen > 0) + sockptr->InputLen--; //Remove last char if not at start + goto noBS; + } + + memmove(BSptr-1, BSptr+1, charsAfter); + sockptr->InputLen--; // Remove BS + if (sockptr->InputLen > 0) sockptr->InputLen--; //Remove last char if not at start + + goto BSCheck; // may be more bs chars + } + +noBS: + + // Extract lines from input stream + + MsgPtr = &sockptr->InputBuffer[0]; + InputLen = sockptr->InputLen; + +MsgLoop: + + // if in Client Mode, accept CR, CR Null, CR LF or LF, and replace with CR + // Also send immediately to client - dont wait for complete lines + + if (sockptr->ClientSession) + { + int n = InputLen; + char * ptr = MsgPtr; + char * Start = MsgPtr; + char c; + int len = 0; + char NodeLine[300]; + char * optr = NodeLine; + + while (n--) + { + c = *(ptr++); + + if (c == 0) + // Ignore Nulls + continue; + + len++; + *(optr++) = c; + + if (c == 13) + { + // See if next is lf or null + + if (n) + { + // Some Left + + if ((*ptr) == 0 || *(ptr) == 10) + { + // skip next + + n--; + ptr++; + } + } + } + else if (c == 10) + { + *(optr - 1) = 13; + } + else + { + // Normal Char + + if (len >= PACLEN) + { + BuffertoNode(sockptr, NodeLine, len); + optr = NodeLine; + len = 0; + } + } + } + + // All scanned - send anything outstanding + + if (len) + BuffertoNode(sockptr, NodeLine, len); + + sockptr->InputLen = 0; + ShowConnections(TNC);; + + return 0; + } + + + // Server Mode + + CRPtr=memchr(MsgPtr, 13, InputLen); + + if (CRPtr) + { + // Convert CR Null to CR LF + + LFPtr=memchr(MsgPtr, 0, InputLen); + + if (LFPtr && *(LFPtr - 1) == 13) // Convert CR NULL to CR LF + { + *LFPtr = 10; // Replace NULL with LF + send(sockptr->socket, LFPtr, 1, 0); // And echo it + } + } + + // could just have LF?? + + LFPtr=memchr(MsgPtr, 10, InputLen); + + if (LFPtr == 0) + if (CRPtr) + { + LFPtr = ++CRPtr; + InputLen++; + } + if (LFPtr == 0) + { + // Check Paclen + + if (InputLen > PACLEN) + { + if (sockptr->LoginState != 2) // Normal Data + { + // Long message received when waiting for user or password - just ignore + + sockptr->InputLen=0; + + return 0; + } + + // Send to Node + + // Line could be up to 500 chars if coming from a program rather than an interative user + // Limit send to node to 255 + + while (InputLen > 255) + { + SendtoNode(TNC, sockptr->Number, MsgPtr, 255); + sockptr->InputLen -= 255; + InputLen -= 255; + + memmove(MsgPtr,MsgPtr+255,InputLen); + } + + SendtoNode(TNC, sockptr->Number, MsgPtr, InputLen); + + sockptr->InputLen = 0; + + } // PACLEN + + return 0; // No CR + } + + // Got a LF + + // Process data up to the cr + + MsgLen = (int)(LFPtr-MsgPtr); // Include the CR but not LF + + switch (sockptr->LoginState) + { + + case 2: + + // Normal Data State + + STREAM->bytesRXed += MsgLen; + SendIndex = 0; + + // Line could be up to 500 chars if coming from a program rather than an interative user + // Limit send to node to 255. Should really use PACLEN instead of 255.... + + while (MsgLen > 255) + { + SendtoNode(TNC, sockptr->Number, MsgPtr + SendIndex, 255); + SendIndex += 255; + MsgLen -= 255; + } + + SendtoNode(TNC, sockptr->Number, MsgPtr + SendIndex, MsgLen); + + MsgLen += SendIndex; + + // If anything left, copy down buffer, and go back + + InputLen=InputLen-MsgLen-1; + + sockptr->InputLen=InputLen; + + if (InputLen > 0) + { + memmove(MsgPtr,LFPtr+1,InputLen); + + goto MsgLoop; + } + + return 0; + + case 0: + + // Check Username + // + + *(LFPtr-1)=0; // remove cr + + // send(sock, NLMsg, 2, 0); + + if (LogEnabled) + { + char Addr[256]; + + Tel_Format_Addr(sockptr, Addr); + + if (strlen(MsgPtr) > 64) + { + MsgPtr[64] = 0; + Debugprintf("Telnet Bad User Name %s", MsgPtr); + } + + sprintf(logmsg,"%d %s User=%s\n", sockptr->Number, Addr, MsgPtr); + WriteLog (logmsg); + } + + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (USER == NULL) + continue; + + if (_stricmp(USER->UserName, "ANON") == 0) + { + // Anon Login - Callsign is supplied as user + + sockptr->UserPointer = USER; //' Save pointer for checking password + strcpy(sockptr->Callsign, _strupr(MsgPtr)); //' for *** linked + } + else if (strcmp(MsgPtr,USER->UserName) == 0) + { + sockptr->UserPointer = USER; //' Save pointer for checking password + strcpy(sockptr->Callsign, USER->Callsign); //' for *** linked + } + else + continue; + + send(sock, TCP->PasswordMsg, (int)strlen(TCP->PasswordMsg),0); + + sockptr->Retries = 0; + + sockptr->LoginState = 1; + sockptr->InputLen = 0; + + n=sockptr->Number; +#ifndef LINBPQ + ModifyMenu(TCP->hDisMenu, n - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + n, MsgPtr); +#endif + ShowConnections(TNC);; + return 0; + } + + // Not found + + + if (sockptr->Retries++ == 4) + { + send(sock,AttemptsMsg,sizeof(AttemptsMsg),0); + Sleep (1000); + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + } + else + { + send(sock, TCP->LoginMsg, (int)strlen(TCP->LoginMsg), 0); + sockptr->InputLen=0; + } + + return 0; + + case 1: + + *(LFPtr-1)=0; // remove cr + + send(sock, NLMsg, 2, 0); // Need to echo NL, as password is not echoed + + if (LogEnabled) + { + char Addr[256]; + + Tel_Format_Addr(sockptr, Addr); + + if (strlen(MsgPtr) > 64) + { + MsgPtr[64] = 0; + Debugprintf("Telnet Bad Password %s", MsgPtr); + } + + + sprintf(logmsg,"%d %s Password=%s\n", sockptr->Number, Addr, MsgPtr); + WriteLog (logmsg); + } + + if (strcmp(MsgPtr, sockptr->UserPointer->Password) == 0) { + char * ct = TCP->cfgCTEXT; + char * Appl; + int ctlen = (int)strlen(ct); + + if (ProcessIncommingConnect(TNC, sockptr->Callsign, sockptr->Number, FALSE) == FALSE) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return 0; + } + + TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]->Secure_Session = sockptr->UserPointer->Secure; + + sockptr->LoginState = 2; + sockptr->InputLen = 0; + + if (ctlen > 0) send(sock, ct, ctlen, 0); + + STREAM->bytesTXed = ctlen; + + if (LogEnabled) + { + char Addr[100]; + + Tel_Format_Addr(sockptr, Addr); + sprintf(logmsg,"%d %s Call Accepted Callsign=%s\n", sockptr->Number, Addr, sockptr->Callsign); + WriteLog (logmsg); + } + + Appl = sockptr->UserPointer->Appl; + + if (Appl[0]) + SendtoNode(TNC, sockptr->Number, Appl, (int)strlen(Appl)); + + ShowConnections(TNC); + + return 0; + } + + // Bad Password + + if (sockptr->Retries++ == 4) + { + send(sock,AttemptsMsg, (int)strlen(AttemptsMsg),0); + Sleep (1000); + DataSocket_Disconnect (TNC, sockptr); //' Tidy up + } + else + { + send(sock, TCP->PasswordMsg, (int)strlen(TCP->PasswordMsg), 0); + sockptr->InputLen=0; + } + + return 0; + + default: + + return 0; + + } + + return 0; +} + +int DataSocket_ReadRelay(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM) +{ + int len=0, maxlen, InputLen, MsgLen, n; + char NLMsg[3]={13,10,0}; + byte * LFPtr; + byte * MsgPtr; + char logmsg[256]; + char RelayMsg[] = "No CMS connection available - using local BPQMail\r"; + struct TCPINFO * TCP = TNC->TCPInfo; + + ioctl(sock,FIONREAD,&len); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (len > maxlen) len=maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); + + + if (len == SOCKET_ERROR || len ==0) + { + // Failed or closed - clear connection + + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + + sockptr->InputLen+=len; + + // Extract lines from input stream + + MsgPtr = &sockptr->InputBuffer[0]; + InputLen = sockptr->InputLen; + + STREAM->bytesRXed += InputLen; + + if (sockptr->LoginState == 2) + { + // Data. FBB is binary + + // Send to Node + + // Queue to Node. Data may arrive it large quatities, possibly exceeding node buffer capacity + + STREAM->bytesRXed += InputLen; + + if (sockptr->FromHostBuffPutptr + InputLen > sockptr->FromHostBufferSize) + { + if (InputLen > 10000) + sockptr->FromHostBufferSize += InputLen; + else + sockptr->FromHostBufferSize += 10000; + + sockptr->FromHostBuffer = realloc(sockptr->FromHostBuffer, sockptr->FromHostBufferSize); + } + + memcpy(&sockptr->FromHostBuffer[sockptr->FromHostBuffPutptr], MsgPtr, InputLen); + + sockptr->FromHostBuffPutptr += InputLen; + sockptr->InputLen = 0; + + return 0; + } +/* + if (InputLen > 256) + { + SendtoNode(TNC, sockptr->Number, MsgPtr, 256); + sockptr->InputLen -= 256; + + InputLen -= 256; + + memmove(MsgPtr,MsgPtr+256,InputLen); + + goto MsgLoop; + } + + SendtoNode(TNC, sockptr->Number, MsgPtr, InputLen); + sockptr->InputLen = 0; + + return 0; + } +*/ + if (InputLen > 256) + { + // Long message received when waiting for user or password - just ignore + + sockptr->InputLen=0; + + return 0; + } + + LFPtr=memchr(MsgPtr, 13, InputLen); + + if (LFPtr == 0) + return 0; // Waitr for more + + // Got a CR + + // Process data up to the cr + + MsgLen = (int)(LFPtr-MsgPtr); + + switch (sockptr->LoginState) + { + + case 0: + + // Check Username + // + + *(LFPtr)=0; // remove cr + + if (*MsgPtr == '.') + MsgPtr++; + + if (strlen(MsgPtr) == 0) + { + DataSocket_Disconnect(TNC, sockptr); // Silently disconnect - should only be used for automatic systems + return 0; + } + + if (LogEnabled) + { + unsigned char work[4]; + memcpy(work, &sockptr->sin.sin_addr.s_addr, 4); + sprintf(logmsg,"%d %d.%d.%d.%d User=%s\n", + sockptr->Number, + work[0], work[1], work[2], work[3], + MsgPtr); + + WriteLog (logmsg); + } + + strcpy(sockptr->Callsign, _strupr(MsgPtr)); + + // Save callsign for *** linked + + send(sock, "Password :\r", 11,0); + + sockptr->Retries = 0; + sockptr->LoginState = 1; + sockptr->InputLen = 0; + + n=sockptr->Number; +#ifndef LINBPQ + ModifyMenu(TCP->hDisMenu, n - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + n, MsgPtr); +#endif + ShowConnections(TNC);; + + return 0; + + + case 1: + + *(LFPtr)=0; // remove cr + + if (strlen(MsgPtr) == 0) + { + DataSocket_Disconnect(TNC, sockptr); // Silently disconnect - should only be used for automatic systems + return 0; + } + + if (LogEnabled) + { + unsigned char work[4]; + memcpy(work, &sockptr->sin.sin_addr.s_addr, 4); + sprintf(logmsg,"%d %d.%d.%d.%d Password=%s\n", + sockptr->Number, + work[0], work[1], work[2], work[3], + MsgPtr); + + WriteLog (logmsg); + } + + if (strchr(MsgPtr, '$')) + { + // Special format Password for PAT Gateway Mode + + char * Port = strlop(MsgPtr, '$'); + char * Call; + int PortNo; + char ConMsg[80]; + + if (Port) + { + Call = strlop(Port, '$'); + + if (Call) + { + struct PORTCONTROL * PORT; + + PortNo = atoi(Port); + PORT = GetPortTableEntryFromPortNum(PortNo); + + if (PORT == NULL || PORT->PROTOCOL < 10) + sprintf(ConMsg, "C %s %s", Port, Call); + else + sprintf(ConMsg, "ATT %s %s", Port, Call); + + } + + if (ProcessIncommingConnect(TNC, sockptr->Callsign, sockptr->Number, FALSE) == 0) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return 0; + } + + sockptr->LoginState = 2; + + sockptr->InputLen = 0; + + if (LogEnabled) + { + unsigned char work[4]; + memcpy(work, &sockptr->sin.sin_addr.s_addr, 4); + sprintf(logmsg,"%d %d.%d.%d.%d Gateway Connect Call=%s Command=%s\n", + sockptr->Number, + work[0], work[1], work[2], work[3], + sockptr->Callsign,ConMsg); + + WriteLog (logmsg); + } + + // Send Command to Node + + strcat(ConMsg, "\r"); + SendtoNode(TNC, sockptr->Number, ConMsg, (int)strlen(ConMsg)); + } + + return 0; + } + + sockptr->UserPointer = &RelayUser; + + if (ProcessIncommingConnectEx(TNC, sockptr->Callsign, sockptr->Number, FALSE, TRUE) == 0) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return 0; + } + + if (TCP->FallbacktoRelay) + send(sock, RelayMsg, (int)strlen(RelayMsg), 0); + + sockptr->LoginState = 2; + + sockptr->InputLen = 0; + + if (LogEnabled) + { + unsigned char work[4]; + memcpy(work, &sockptr->sin.sin_addr.s_addr, 4); + sprintf(logmsg,"%d %d.%d.%d.%d Call Accepted Callsign =%s\n", + sockptr->Number, + work[0], work[1], work[2], work[3], + sockptr->Callsign); + + WriteLog (logmsg); + } + + ShowConnections(TNC); + + sockptr->InputLen = 0; + + // Connect to the BBS + + SendtoNode(TNC, sockptr->Number, TCP->RelayAPPL, (int)strlen(TCP->RelayAPPL)); + + ShowConnections(TNC);; + + return 0; + + default: + + return 0; + + } + + return 0; +} +#define ZEXPORT WINAPI + +#include "zlib.h" + +int DataSocket_ReadSync(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream) +{ + int len=0, maxlen, InputLen; + byte * MsgPtr; + struct TCPINFO * TCP = TNC->TCPInfo; + struct STREAMINFO * STREAM = &TNC->Streams[Stream]; + TRANSPORTENTRY * Sess1 = TNC->PortRecord->ATTACHEDSESSIONS[Stream]; + TRANSPORTENTRY * Sess2 = NULL; + + ioctl(sock,FIONREAD,&len); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (len > maxlen) len = maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); + + if (len == SOCKET_ERROR || len == 0) + { + // Failed or closed - clear connection + + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + + sockptr->InputLen+=len; + MsgPtr = &sockptr->InputBuffer[0]; + InputLen = sockptr->InputLen; + MsgPtr[InputLen] = 0; + + STREAM->bytesRXed += InputLen; + + if (sockptr->LoginState == 0) // Initial connection + { + // First Message should be POSYNCLOGON CALL + + // Extract the callsign + + char * call = strlop(MsgPtr, ' '); + + if (call == NULL || strcmp(MsgPtr, "POSYNCLOGON") !=0) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return 0; + } + + strcpy(sockptr->Callsign, call); + + call --; + *(call) = ' '; + + sockptr->UserPointer = &SyncUser; + + SendtoNode(TNC, sockptr->Number, TCP->SyncAPPL, (int)strlen(TCP->SyncAPPL)); + BuffertoNode(sockptr, MsgPtr, InputLen); + STREAM->RelaySyncStream = 1; + sockptr->LoginState = 2; + + ShowConnections(TNC); + return 0; + } + + // Queue to Node. Data may arrive in large quantities, possibly exceeding node buffer capacity + + BuffertoNode(sockptr, MsgPtr, InputLen); + sockptr->InputLen = 0; + + return 0; +} + + + +int DataSocket_ReadFBB(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream) +{ + int len=0, maxlen, InputLen, MsgLen, i, n; + char NLMsg[3]={13,10,0}; + byte * CRPtr; + byte * MsgPtr; + char logmsg[1000]; + struct UserRec * USER; + struct TCPINFO * TCP = TNC->TCPInfo; + struct STREAMINFO * STREAM = &TNC->Streams[Stream]; + TRANSPORTENTRY * Sess1 = TNC->PortRecord->ATTACHEDSESSIONS[Stream]; + TRANSPORTENTRY * Sess2 = NULL; + + ioctl(sock,FIONREAD,&len); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (len > maxlen) len = maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); + + if (len == SOCKET_ERROR || len == 0) + { + // Failed or closed - clear connection + + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + + sockptr->InputLen+=len; + + // Extract lines from input stream + + MsgPtr = &sockptr->InputBuffer[0]; + InputLen = sockptr->InputLen; + MsgPtr[InputLen] = 0; + + if (sockptr->LoginState == 0) + { + // Look for FLMSG Header + + if (InputLen > 10 && memcmp(MsgPtr, "... start\n", 10) == 0) + { + MsgPtr[9] = 13; // Convert to CR + sockptr->LoginState = 2; // Set Logged in + + SendtoNode(TNC, Stream, "..FLMSG\r", 8); // Dummy command to command handler + + } + } + +MsgLoop: + + if (sockptr->LoginState == 2) + { + // Data. FBB is binary + + int Paclen = 0; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream]) + Paclen = TNC->PortRecord->ATTACHEDSESSIONS[Stream]->SESSPACLEN; + +// if (Paclen == 0) + Paclen = 256; + + if (sockptr->BPQTermMode) + { + if (memcmp(MsgPtr, "\\\\\\\\", 4) == 0) + { + // Monitor Control + + int P8 = 0; + + int n = sscanf(&MsgPtr[4], "%llx %x %x %x %x %x %x %x", + &sockptr->MMASK, &sockptr->MTX, &sockptr->MCOM, &sockptr->MonitorNODES, + &sockptr->MonitorColour, &sockptr->MUIOnly, &sockptr->UTF8, &P8); + + if (n == 5) + sockptr->MUIOnly = sockptr->UTF8 = 0; + + if (n == 6) + sockptr->UTF8 = 0; + + if (P8 == 1) + SendPortsForMonitor(sock, sockptr->UserPointer->Secure); + sockptr->InputLen = 0; + return 0; + } + } + + if (sockptr->UserPointer == &CMSUser) + { + WritetoTrace(Stream, MsgPtr, InputLen, sockptr->ADIF, 'R'); + } + + if (InputLen == 8 && memcmp(MsgPtr, ";;;;;;\r\n", 8) == 0) + { + // CMS Keepalive + + sockptr->InputLen = 0; + return 0; + } + + // Queue to Node. Data may arrive it large quantities, possibly exceeding node buffer capacity + + STREAM->bytesRXed += InputLen; + BuffertoNode(sockptr, MsgPtr, InputLen); + sockptr->InputLen = 0; + + return 0; + } + + if (InputLen > 256) + { + // Long message received when waiting for user or password - just ignore + + sockptr->InputLen=0; + + return 0; + } + + if (MsgPtr[0] == 10) // LF + { + // Remove the LF + + InputLen--; + sockptr->InputLen--; + + memmove(MsgPtr, MsgPtr+1, InputLen); + } + + CRPtr = memchr(MsgPtr, 13, InputLen); + + if (CRPtr == 0) + return 0; // Waitr for more + + // Got a CR + + // Process data up to the cr + + MsgLen = (int)(CRPtr - MsgPtr); + + if (MsgLen == 0) // Just CR + { + MsgPtr++; // Skip it + InputLen--; + sockptr->InputLen--; + goto MsgLoop; + } + + + switch (sockptr->LoginState) + { + case 5: + + // Trimode Emulator Command + + *CRPtr = 0; + + ProcessTrimodeCommand(TNC, sockptr, MsgPtr); + + MsgLen++; + + InputLen -= MsgLen; + + memmove(MsgPtr, MsgPtr+MsgLen, InputLen); + sockptr->InputLen = InputLen ; + MsgPtr[InputLen] = 0; + + + goto MsgLoop; + + case 3: + + // CMS Signon + + strlop(MsgPtr, 13); + + sprintf(logmsg,"%d %s\r\n", Stream, MsgPtr); + WriteCMSLog (logmsg); + + if (strstr(MsgPtr, "Callsign :")) + { + char Msg[80]; + int Len; + + if (sockptr->LogonSent) + { + sockptr->InputLen=0; + return TRUE; + } + + sockptr->LogonSent = TRUE; + + if (TCP->SecureCMSPassword[0] && sockptr->RelaySession == 0) + Len = sprintf(Msg, "%s %s\r", TNC->Streams[sockptr->Number].MyCall, TCP->GatewayCall); + else + Len = sprintf(Msg, "%s\r", TNC->Streams[sockptr->Number].MyCall); + + if (sockptr->ADIF == NULL) + { + sockptr->ADIF = malloc(sizeof(struct ADIF)); + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + } + + strcpy(sockptr->ADIF->CMSCall, TCP->GatewayCall); + + send(sock, Msg, Len, 0); + sprintf(logmsg,"%d %s\n", Stream, Msg); + WriteCMSLog (logmsg); + + sockptr->InputLen=0; + + return TRUE; + } + if (memcmp(MsgPtr, ";SQ: ", 5) == 0) + { + // Secure CMS challenge + + char Msg[80]; + int Len; + int Response = GetCMSHash(&MsgPtr[5], TCP->SecureCMSPassword); + char RespString[12]; + long long Freq = 0; + int Mode = 0; + ADIF * ADIF = sockptr->ADIF; + + strcat(MsgPtr,""); + UpdateADIFRecord(ADIF, MsgPtr, 'R'); + + if (Sess1) + { + Sess2 = Sess1->L4CROSSLINK; + + if (Sess2) + { + // if Session has report info, use it + + if (Sess2->Mode) + { + ADIF->Freq = Freq = Sess2->Frequency; + ADIF->Mode = Mode = Sess2->Mode; + } + else + { + // See if L2 session - if so, get info from WL2K report line + + if (Sess2->L4CIRCUITTYPE & L2LINK) + { + LINKTABLE * LINK = Sess2->L4TARGET.LINK; + PORTCONTROLX * PORT = LINK->LINKPORT; + + ADIF->Freq = Freq = PORT->WL2KInfo.Freq; + ADIF->Mode = Mode = PORT->WL2KInfo.mode; + } + else + { + if (Sess2->RMSCall[0]) + { + ADIF->Freq = Freq = Sess2->Frequency; + ADIF->Mode = Mode = Sess2->Mode; + } + } + } + } + } + + sprintf(RespString, "%010d", Response); + + Len = sprintf(Msg, ";SR: %s %lld %d\r", &RespString[2], Freq, Mode); + + send(sock, Msg, Len,0); + sprintf(logmsg,"%d %s\n", Stream, Msg); + WriteCMSLog (logmsg); + + strcat(Msg,""); + UpdateADIFRecord(ADIF, Msg, 'S'); + + sockptr->InputLen=0; + sockptr->LoginState = 2; // Data + sockptr->LogonSent = FALSE; + + return TRUE; + } + + if (strstr(MsgPtr, "Password :")) + { + // Send "CMSTelnet" + gateway callsign + frequency + emission type if info is available + + TRANSPORTENTRY * Sess1 = TNC->PortRecord->ATTACHEDSESSIONS[Stream]; + TRANSPORTENTRY * Sess2 = NULL; + char Passline[80] = "CMSTELNET\r"; + int len = 10; + ADIF * ADIF = sockptr->ADIF; + + + if (Sess1) + { + Sess2 = Sess1->L4CROSSLINK; + + if (Sess2) + { + // if Session has report info, use it + + if (Sess2->Mode) + { + ADIF->Freq = Sess2->Frequency; + ADIF->Mode = Sess2->Mode; + } + else + { + // See if L2 session - if so, get info from WL2K report line + + if (Sess2->L4CIRCUITTYPE & L2LINK) + { + LINKTABLE * LINK = Sess2->L4TARGET.LINK; + PORTCONTROLX * PORT = LINK->LINKPORT; + + if (PORT->WL2KInfo.Freq) + { + len = sprintf(Passline, "CMSTELNET %s %lld %d\r", PORT->WL2KInfo.RMSCall, PORT->WL2KInfo.Freq, PORT->WL2KInfo.mode); + ADIF->Freq = PORT->WL2KInfo.Freq; + ADIF->Mode = PORT->WL2KInfo.mode; + } + } + else + { + if (Sess2->RMSCall[0]) + { + len = sprintf(Passline, "CMSTELNET %s %lld %d\r", Sess2->RMSCall, Sess2->Frequency, Sess2->Mode); + ADIF->Mode = Sess2->Frequency; + ADIF->Mode = Sess2->Mode; + } + } + } + } + } + send(sock, Passline, len, 0); + sockptr->LoginState = 2; // Data + sockptr->InputLen=0; + sockptr->LogonSent = FALSE; + + if (CMSLogEnabled) + { + char logmsg[120]; + sprintf(logmsg,"%d %s\r\n", sockptr->Number, Passline); + WriteCMSLog (logmsg); + } + + return TRUE; + } + + return TRUE; + + case 0: + + // Check Username + // + + *(CRPtr)=0; // remove cr + + if (LogEnabled) + { + char Addr[256]; + Tel_Format_Addr(sockptr, Addr); + + if (strlen(MsgPtr) > 64) + { + MsgPtr[64] = 0; + Debugprintf("Telnet Bad User Name %s", MsgPtr); + } + + sprintf(logmsg,"%d %s User=%s\n", sockptr->Number, Addr, MsgPtr); + WriteLog (logmsg); + } + for (i = 0; i < TCP->NumberofUsers; i++) + { + USER = TCP->UserRecPtr[i]; + + if (USER == NULL) + continue; + + if (_stricmp(USER->UserName, "ANON") == 0) + { + // Anon Login - Callsign is supplied as user + + sockptr->UserPointer = USER; //' Save pointer for checking password + strcpy(sockptr->Callsign, _strupr(MsgPtr)); //' for *** linked + } + else if (strcmp(MsgPtr,USER->UserName) == 0) + { + sockptr->UserPointer = USER; //' Save pointer for checking password + strcpy(sockptr->Callsign, USER->Callsign); //' for *** linked + + } + else + continue; + + sockptr->Retries = 0; + + sockptr->LoginState = 1; + sockptr->InputLen = 0; + + n=sockptr->Number; + +#ifndef LINBPQ + ModifyMenu(TCP->hDisMenu, n - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + n, MsgPtr); +#endif + + ShowConnections(TNC);; + + InputLen=InputLen-(MsgLen+1); + + sockptr->InputLen=InputLen; + + if (InputLen > 0) + { + memmove(MsgPtr, CRPtr+1, InputLen); + goto MsgLoop; + } + + return 0; + } + + // User Not found + + if (sockptr->Retries++ == 4) + { + send(sock,AttemptsMsg,sizeof(AttemptsMsg),0); + Sleep (1000); + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + } + else + { + send(sock, TCP->LoginMsg, (int)strlen(TCP->LoginMsg), 0); + sockptr->InputLen=0; + + } + + return 0; + + case 1: + + *(CRPtr)=0; // remove cr + + if (LogEnabled) + { + char Addr[256]; + Tel_Format_Addr(sockptr, Addr); + + if (strlen(MsgPtr) > 64) + { + MsgPtr[64] = 0; + Debugprintf("Telnet Bad Password %s", MsgPtr); + } + + sprintf(logmsg,"%d %s Password=%s\n", sockptr->Number, Addr, MsgPtr); + WriteLog (logmsg); + } + if (strcmp(MsgPtr, sockptr->UserPointer->Password) == 0) + { + char * Appl; + + if (ProcessIncommingConnect(TNC, sockptr->Callsign, sockptr->Number, FALSE) == FALSE) + { + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + return 0; + } + + TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]->Secure_Session = sockptr->UserPointer->Secure; + + sockptr->LoginState = 2; + + sockptr->InputLen = 0; + + if (LogEnabled) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + sprintf(logmsg,"%d %s Call Accepted. Callsign=%s\n", + sockptr->Number, Addr,sockptr->Callsign); + + WriteLog (logmsg); + } + + ShowConnections(TNC);; + InputLen=InputLen-(MsgLen+1); + + sockptr->InputLen=InputLen; + + // What is left is the Command to connect to the BBS + + if (InputLen > 1) + { + if (*(CRPtr+1) == 10) + { + CRPtr++; + InputLen--; + } + + memmove(MsgPtr, CRPtr+1, InputLen); + + if (_memicmp(MsgPtr, "BPQTermTCP", 10) == 0) + { + send(sock, "Connected to TelnetServer\r", 26, 0); + sockptr->BPQTermMode = TRUE; + sockptr->MMASK = 0; // Make sure defaults to off + sockptr->InputLen -= 11; + + if (sockptr->InputLen) + { + // Monitor control info may arrive in same packet + + int P8 = 0; + + memmove(MsgPtr, &MsgPtr[11], InputLen); + if (memcmp(MsgPtr, "\\\\\\\\", 4) == 0) + { + // Monitor Control + + int n = sscanf(&MsgPtr[4], "%llx %x %x %x %x %x %x %x", + &sockptr->MMASK, &sockptr->MTX, &sockptr->MCOM, &sockptr->MonitorNODES, + &sockptr->MonitorColour, &sockptr->MUIOnly, &sockptr->UTF8, &P8); + + if (n == 5) + sockptr->MUIOnly = sockptr->UTF8 = 0; + + if (n == 6) + sockptr->UTF8 = 0; + + if (P8 == 1) + SendPortsForMonitor(sock, sockptr->UserPointer->Secure); + + + sockptr->InputLen = 0; + } + } + } + else + { + MsgPtr[InputLen] = 13; + SendtoNode(TNC, sockptr->Number, MsgPtr, InputLen+1); + } + sockptr->InputLen = 0; + } + + Appl = sockptr->UserPointer->Appl; + + if (Appl[0]) + SendtoNode(TNC, sockptr->Number, Appl, (int)strlen(Appl)); + + return 0; + } + // Bad Password + + if (sockptr->Retries++ == 4) + { + send(sock,AttemptsMsg, (int)strlen(AttemptsMsg),0); + Sleep (1000); + DataSocket_Disconnect(TNC, sockptr); //' Tidy up + } + else + { + send(sock, TCP->PasswordMsg, (int)strlen(TCP->PasswordMsg), 0); + sockptr->InputLen=0; + } + + return 0; + + default: + + return 0; + } + return 0; +} + +extern char * RigWebPage; + +int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream) +{ + int w =1, x= 1, len=0, y = 2, maxlen, InputLen, ret; + char NLMsg[3]={13,10,0}; + UCHAR * MsgPtr; + UCHAR * CRLFCRLF; + UCHAR * LenPtr; + int BodyLen, ContentLen; + struct ConnectionInfo * sockcopy; + + ret = ioctl(sock,FIONREAD,&w); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (w > maxlen) w = maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], w, 0); + + if (len == SOCKET_ERROR || len == 0) + { + // Failed or closed - clear connection + + // if Websock connection till app + + if (sockptr->WebSocks) + { + if (memcmp(sockptr->WebURL, "rhp", 3) == 0) + { + ProcessRHPWebSockClosed(sockptr->socket); + return 0; + } + } + else + { + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + } + + MsgPtr = &sockptr->InputBuffer[0]; + sockptr->InputLen += len; + InputLen = sockptr->InputLen; + + MsgPtr[InputLen] = 0; + + if (sockptr->WebSocks) + { + // Websocks message + + int i, j; + int Fin, Opcode, Len, Mask; + char MaskingKey[4]; + char * ptr; + char * Payload; + + /* + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + Octet i of the transformed data ("transformed-octet-i") is the XOR of + octet i of the original data ("original-octet-i") with octet at index + i modulo 4 of the masking key ("masking-key-octet-j"): + + j = i MOD 4 + transformed-octet-i = original-octet-i XOR masking-key-octet-j +*/ + Fin = MsgPtr[0] >> 7; + Opcode = MsgPtr[0] & 15; + Mask = MsgPtr[1] >> 7; + Len = MsgPtr[1] & 127; + + if (Len == 126) // Two Byte Len + { + Len = (MsgPtr[2] << 8) + MsgPtr[3]; + memcpy(MaskingKey, &MsgPtr[4], 4); + ptr = &MsgPtr[8]; + } + else + { + memcpy(MaskingKey, &MsgPtr[2], 4); + ptr = &MsgPtr[6]; + } + + Payload = ptr; + + for (i = 0; i < Len; i++) + { + j = i & 3; + + *ptr = *ptr ^ MaskingKey[j]; + ptr++; + } + + if (Opcode == 8) + { + Debugprintf("WebSock Close"); + } + else if (Opcode == 1) + { + if (strcmp(sockptr->WebURL, "RIGCTL") == 0) + { + // PTT Message + + char RigCMD[64]; + + sprintf(RigCMD, "%s PTT", Payload); + Rig_Command( (TRANSPORTENTRY *) -1, RigCMD); + } + else if (memcmp(sockptr->WebURL, "WMRefresh", 9) == 0) + { + sockcopy = malloc(sizeof(struct ConnectionInfo)); + sockptr->TNC = TNC; + sockptr->LastSendTime = REALTIMETICKS; + + memcpy(sockcopy, sockptr, sizeof(struct ConnectionInfo)); + + _beginthread(ProcessWebmailWebSockThread, 2048000, (VOID *)sockcopy); // Needs big stack + return 0; + } + else if (memcmp(sockptr->WebURL, "rhp", 3) == 0) + { + sockcopy = malloc(sizeof(struct ConnectionInfo)); + sockptr->TNC = TNC; + sockptr->LastSendTime = REALTIMETICKS; + + memcpy(sockcopy, sockptr, sizeof(struct ConnectionInfo)); + + ProcessRHPWebSock(sockptr->socket, Payload, Len); + sockptr->InputLen = 0; + return 0; + } + } + else + Debugprintf("WebSock Opcode %d Msg %s", Opcode, &MsgPtr[6]); + + sockptr->InputLen = 0; + return 0; + } + + // Make sure request is complete - should end crlfcrlf, and if a post have the required input message + + + CRLFCRLF = strstr(MsgPtr, "\r\n\r\n"); + + if (CRLFCRLF == 0) + return 0; + + LenPtr = strstr(MsgPtr, "Content-Length:"); + + if (LenPtr) + { + ContentLen = atoi(LenPtr + 15); + BodyLen = InputLen - (int)((CRLFCRLF + 4 - MsgPtr)); + + if (BodyLen < ContentLen) + return 0; + } + + sockcopy = malloc(sizeof(struct ConnectionInfo)); + sockptr->TNC = TNC; + sockptr->LastSendTime = REALTIMETICKS; + + memcpy(sockcopy, sockptr, sizeof(struct ConnectionInfo)); + + if(strstr(MsgPtr, "Upgrade: websocket")) + { + int LOCAL = 0, COOKIE = 0; + char * HostPtr; + char * ptr; + + sockptr->WebSocks = 1; + ShowConnections(TNC); + + memcpy(sockptr->WebURL, &MsgPtr[5], 31); + strlop(sockptr->WebURL, ' '); + if (RigWebPage) + RigWebPage[0] = 0; + + HostPtr = strstr(MsgPtr, "Host: "); + + if (HostPtr) + { + uint32_t Host; + char Hostname[32]= ""; + struct LOCALNET * LocalNet = sockptr->TNC->TCPInfo->LocalNets; + + HostPtr += 6; + memcpy(Hostname, HostPtr, 31); + strlop(Hostname, ':'); + Host = inet_addr(Hostname); + + if (strcmp(Hostname, "127.0.0.1") == 0) + LOCAL = TRUE; + else + { + if (sockptr->sin.sin_family != AF_INET6) + { + while(LocalNet) + { + uint32_t MaskedHost = sockptr->sin.sin_addr.s_addr & LocalNet->Mask; + if (MaskedHost == LocalNet->Network) + { + LOCAL = 1; + break; + } + LocalNet = LocalNet->Next; + } + } + + ptr = strstr(MsgPtr, "BPQSessionCookie=N"); + + if (ptr) + COOKIE = TRUE; + } + sockptr->WebSecure = LOCAL | COOKIE; + } + } + + + _beginthread(ProcessHTTPMessage, 2048000, (VOID *)sockcopy); // Needs big stack + + sockptr->InputLen = 0; + return 0; +} + +int DataSocket_ReadDRATS(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream) +{ + int len=0, maxlen; + + ioctl(sock,FIONREAD,&len); + + maxlen = InputBufferLen - sockptr->InputLen; + + if (len > maxlen) len = maxlen; + + len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); + + if (len == SOCKET_ERROR || len == 0) + { + // Failed or closed - clear connection + + DRATSConnectionLost(sockptr); + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + + // Make sure request is complete - should end [EOB] + + processDRATSFrame(&sockptr->InputBuffer[sockptr->InputLen], len, sockptr); + return 0; +} + + +int DataSocket_Disconnect(struct TNCINFO * TNC, struct ConnectionInfo * sockptr) +{ + int n; + + if (sockptr->SocketActive) + { + if (sockptr->socket) + closesocket(sockptr->socket); + + n = sockptr->Number; +#ifndef LINBPQ + ModifyMenu(TNC->TCPInfo->hDisMenu, n - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + n, "."); +#endif + sockptr->SocketActive = FALSE; + ShowConnections(TNC);; + } + return 0; +} + +int ShowConnections(struct TNCINFO * TNC) +{ +#ifndef LINBPQ + char msg[80]; + struct ConnectionInfo * sockptr; + int i,n; + + SendMessage(TNC->hMonitor,LB_RESETCONTENT,0,0); + + for (n = 1; n <= TNC->TCPInfo->CurrentSockets; n++) + { + sockptr=TNC->Streams[n].ConnectionInfo; + + if (!sockptr->SocketActive) + { + strcpy(msg,"Idle"); + } + else + { + if (sockptr->UserPointer == 0) + { + if (sockptr->HTTPMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + + if (sockptr->WebSocks) + sprintf(msg, "Websock From %s", Addr); + else + sprintf(msg, "HTTP From %s", Addr); + } + else if (sockptr->DRATSMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + sprintf(msg, "DRATS From %s", Addr); + } + else + strcpy(msg,"Logging in"); + } + else + { + i=sprintf(msg,"%-10s %-10s %2d", + sockptr->UserPointer->UserName, sockptr->Callsign, sockptr->FromHostBuffPutptr - sockptr->FromHostBuffGetptr); + } + } + SendMessage(TNC->hMonitor, LB_ADDSTRING ,0, (LPARAM)msg); + } +#endif + return 0; +} +byte * EncodeCall(byte * Call) +{ + static char axcall[10]; + + ConvToAX25(Call, axcall); + return &axcall[0]; +} +BOOL ProcessTelnetCommand(struct ConnectionInfo * sockptr, byte * Msg, int * Len) +{ + int cmd, TelOption; + int used; + char WillSupGA[3]={IAC,WILL,suppressgoahead}; + char WillEcho[3]={IAC,WILL,echo}; + char Wont[3]={IAC,WONT,echo}; + char Dont[3]={IAC,DONT,echo}; + + // Note Msg points to the IAC, which may not be at the start of the receive buffer + // Len is number of bytes left in buffer including the IAC + + if (*Len < 2) return TRUE; //' Wait for more + + cmd = Msg[1]; + + if (cmd == DOx || cmd == DONT || cmd == WILL || cmd == WONT) + if (*Len < 3) return TRUE; //' wait for option + + TelOption = Msg[2]; + + switch (cmd) + { + case DOx: + + switch (TelOption) + { + case echo: + sockptr->DoEcho = TRUE; + send(sockptr->socket,WillEcho,3,0); + break; + + case suppressgoahead: + + send(sockptr->socket,WillSupGA,3,0); + break; + + default: + + Wont[2] = TelOption; + send(sockptr->socket,Wont,3,0); + } + + used=3; + + break; + + case DONT: + + // Debug.Print "DONT"; TelOption + + switch (TelOption) + { + case echo: + sockptr->DoEcho = FALSE; + break; + } + + Wont[2] = TelOption; + send(sockptr->socket,Wont,3,0); + + used=3; + + break; + + case WILL: + + // Debug.Print "WILL"; TelOption + +// if (TelOption == echo) sockptr->DoEcho = TRUE; + + Dont[2] = TelOption; + send(sockptr->socket, Dont, 3, 0); + + used=3; + + break; + + case WONT: + +// Debug.Print "WONT"; TelOption + + used=3; + + break; + + default: + + used=2; + + } + + // remove the processed command from the buffer + + *Len -= used; + + return FALSE; +} + + +int WriteLog(char * msg) +{ + FILE *file; + char timebuf[128]; + time_t ltime; + + UCHAR Value[MAX_PATH]; + time_t T; + struct tm * tm; + + T = time(NULL); + tm = gmtime(&T); + + if (LogDirectory[0] == 0) + { + strcpy(Value, "logs/Telnet_"); + } + else + { + strcpy(Value, LogDirectory); + strcat(Value, "/"); + strcat(Value, "logs/Telnet_"); + } + + sprintf(Value, "%s%02d%02d%02d.log", Value, + tm->tm_year - 100, tm->tm_mon+1, tm->tm_mday); + + if ((file = fopen(Value, "a")) == NULL) + return FALSE; + + time(<ime); + +#ifdef LINBPQ + { + struct tm * tmp = localtime(<ime); + strftime( timebuf, 128, + "%d/%m/%Y %H:%M:%S ", tmp ); + } +#else + { + struct tm * today; + + today = localtime(<ime); + strftime(timebuf, 128, "%d/%m/%Y %H:%M:%S ", today); + } +#endif + fputs(timebuf, file); + fputs(msg, file); + fclose(file); + return 0; +} + +char LastCMSLog[256]; + +VOID WriteCMSLog(char * msg) +{ + UCHAR Value[MAX_PATH]; + time_t T; + struct tm * tm; + FILE * Handle; + char LogMsg[256]; + int MsgLen; + + if (CMSLogEnabled == FALSE) + return; + + T = time(NULL); + tm = gmtime(&T); + + if (LogDirectory[0] == 0) + { + strcpy(Value, "logs/CMSAccess"); + } + else + { + strcpy(Value, LogDirectory); + strcat(Value, "/"); + strcat(Value, "logs/CMSAccess"); + } + + sprintf(Value, "%s_%04d%02d%02d.log", Value, + tm->tm_year +1900, tm->tm_mon+1, tm->tm_mday); + + Handle = fopen(Value, "ab"); + + if (Handle == NULL) + return; + + MsgLen = sprintf(LogMsg, "%02d:%02d:%02d %s", tm->tm_hour, tm->tm_min, tm->tm_sec, msg); + + fwrite(LogMsg , 1, MsgLen, Handle); + + fclose(Handle); + +#ifndef WIN32 + + if (strcmp(Value, LastCMSLog)) + { + UCHAR SYMLINK[MAX_PATH]; + + sprintf(SYMLINK,"%s/CMSAccessLatest.log", BPQDirectory); + unlink(SYMLINK); + strcpy(LastCMSLog, Value); + symlink(Value, SYMLINK); + } + +#endif + + return; +} + + + + + + +int Telnet_Connected(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Error) +{ + struct TCPINFO * TCP = TNC->TCPInfo; + PMSGWITHLEN buffptr; + int Stream = sockptr->Number; + char Signon[80]; + int errlen = 4; + + buffptr = (PMSGWITHLEN)GetBuff(); + if (buffptr == 0) return 0; // No buffers, so ignore + +#ifndef WIN32 + +// SO_ERROR codes + +//#define ETIMEDOUT 110 /* Connection timed out */ +//#define ECONNREFUSED 111 /* Connection refused */ +//#define EHOSTDOWN 112 /* Host is down */ +//#define EHOSTUNREACH 113 /* No route to host */ +//#define EALREADY 114 /* Operation already in progress */ +//#define EINPROGRESS 115 /* Operation now in progress */ + + if (Error == 0) + getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&Error, &errlen); + +// Debugprintf("Except Event Error after opts = %d", Error); +#endif + + if (Error) + { + if (sockptr->CMSSession && sockptr->RelaySession == 0) + { + // Try Next + + TCP->CMSFailed[sockptr->CMSIndex] = TRUE; + + if (CMSConnect(TNC, TNC->TCPInfo, &TNC->Streams[Stream], Stream)) + return 0; + + // Connect failure - if no more servers to check look for FALLBACKTORELAY + + return 0; + } + else + { + int err = 0; + int errlen = 4; + + getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); + + buffptr->Len = sprintf(&buffptr->Data[0], "*** Failed to Connect\r"); + + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + + closesocket(sock); + TNC->Streams[Stream].Connecting = FALSE; + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + TNC->Streams[Stream].NeedDisc = 10; + return 0; + } + } + + sockptr->LogonSent = FALSE; + + if (sockptr->CMSSession) + { + sockptr->LoginState = 3; // Password State + + sockptr->UserPointer = &CMSUser; + strcpy(sockptr->Callsign, TNC->Streams[Stream].MyCall); + + sockptr->DoEcho = FALSE; + sockptr->FBBMode = TRUE; + sockptr->RelayMode = FALSE; + sockptr->ClientSession = FALSE; + sockptr->SyncMode = FALSE; + + if (TCP->CMS) + SaveCMSHostInfo(TNC->Port, TNC->TCPInfo, sockptr->CMSIndex); + + if (CMSLogEnabled) + { + char logmsg[120]; + + if (sockptr->RelaySession) + sprintf(logmsg,"%d %s Connected to RELAY\r\n", sockptr->Number, TNC->Streams[Stream].MyCall); + else + sprintf(logmsg,"%d %s Connected to CMS\r\n", sockptr->Number, TNC->Streams[Stream].MyCall); + + WriteCMSLog (logmsg); + } + + if (sockptr->RelaySession) + buffptr->Len = sprintf(&buffptr->Data[0], "*** %s Connected to RELAY\r", TNC->Streams[Stream].MyCall); + else + buffptr->Len = sprintf(&buffptr->Data[0], "*** %s Connected to CMS\r", TNC->Streams[Stream].MyCall); + } + else + { + sockptr->LoginState = 2; // Data State + sockptr->UserPointer = &HostUser; + strcpy(sockptr->Callsign, TNC->Streams[Stream].MyCall); + sockptr->DoEcho = FALSE; + sockptr->ClientSession = TRUE; + + if (sockptr->SyncMode) + { + char Addr[256]; + Tel_Format_Addr(sockptr, Addr); + + buffptr->Len = sprintf(&buffptr->Data[0], "*** Connected to SYNC %s:%d\r", Addr, htons(sockptr->sin.sin_port)); + send(sockptr->socket, sockptr->Signon, (int)strlen(sockptr->Signon), 0); + } + else + { + if (sockptr->Signon[0]) + { + buffptr->Len = sprintf(&buffptr->Data[0], "*** Connected to Server\r"); + send(sockptr->socket, sockptr->Signon, (int)strlen(sockptr->Signon), 0); + } + else + { + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream]->L4CROSSLINK->APPL[0]) + buffptr->Len = sprintf(&buffptr->Data[0], "*** Connected to %s\r", + TNC->PortRecord->ATTACHEDSESSIONS[Stream]->L4CROSSLINK->APPL); + else + buffptr->Len = sprintf(&buffptr->Data[0], "*** Connected to APPL\r"); + + if (sockptr->NoCallsign == FALSE) + send(sockptr->socket, Signon, sprintf(Signon, "%s\r\n", TNC->Streams[Stream].MyCall), 0); + } + } + } + + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + + sockptr->SocketActive = TRUE; + sockptr->InputLen = 0; +// sockptr->Number = Stream; + sockptr->RelayMode = FALSE; + sockptr->ConnectTime = time(NULL); + TNC->Streams[Stream].Connecting = FALSE; + TNC->Streams[Stream].Connected = TRUE; + + if (sockptr->ADIF == NULL) + sockptr->ADIF = malloc(sizeof(struct ADIF)); + + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + + strcpy(sockptr->ADIF->Call, TNC->Streams[Stream].MyCall); + + ShowConnections(TNC); + + if (sockptr->FromHostBuffer == 0) + { + sockptr->FromHostBuffer = malloc(10000); + sockptr->FromHostBufferSize = 10000; + } + + sockptr->FromHostBuffPutptr = sockptr->FromHostBuffGetptr = 0; + + TNC->Streams[Stream].bytesRXed = TNC->Streams[Stream].bytesTXed = 0; + + return 0; +} + +VOID ReportError(struct STREAMINFO * STREAM, char * Msg) +{ + PMSGWITHLEN buffptr; + + buffptr = (PMSGWITHLEN)GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(&buffptr->Data[0], "Error - %s\r", Msg); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); +} + +VOID Report(struct STREAMINFO * STREAM, char * Msg) +{ + PMSGWITHLEN buffptr; + + buffptr = (PMSGWITHLEN)GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(&buffptr->Data[0], "%s\r", Msg); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); +} + +void CheckCMSThread(void * TNC); + +BOOL CheckCMS(struct TNCINFO * TNC) +{ + if (TNC->TCPInfo->CMS) + { + TNC->TCPInfo->CheckCMSTimer = 0; + _beginthread(CheckCMSThread, 0, (void *)TNC); + } + return 0; +} + +void CheckCMSThread(void * TNCPtr) +{ + // Resolve Name and check connectivity to each address + + struct TNCINFO * TNC = (struct TNCINFO *)TNCPtr; + struct TCPINFO * TCP = TNC->TCPInfo; +// struct hostent * HostEnt; + struct in_addr addr; + struct hostent *remoteHost; + char **pAlias; int i = 0; + BOOL INETOK = FALSE; + struct addrinfo hints, *res = 0, *saveres; + int n; + unsigned long cms; + + TCP->UseCachedCMSAddrs = FALSE; + + // if TCP->CMSServer is an ip address use it + + cms = inet_addr(TCP->CMSServer); + + if (cms != INADDR_NONE) + { + Debugprintf("Using %s for CMS Server", TCP->CMSServer); + TCP->CMSAddr[0].s_addr = cms; + TCP->CMSFailed[0] = FALSE; + TCP->CMSName[0] = _strdup(TCP->CMSServer); // Save Host Name + TCP->NumberofCMSAddrs = 1; + goto CheckServers; + } + + // First make sure we have a functioning DNS + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET6; // use IPv4 or IPv6, whichever + hints.ai_socktype = SOCK_DGRAM; + + n = getaddrinfo("a.root-servers.net", NULL, &hints, &res); + + if (n == 0) + goto rootok; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever + hints.ai_socktype = SOCK_DGRAM; + n = getaddrinfo("b.root-servers.net", NULL, &hints, &res); + + if (n == 0) + goto rootok; + + Debugprintf("Resolve root nameserver failed"); + + // Most likely is a local Internet Outage, but we could have Internet, but no name servers + // Either way, switch to using cached CMS addresses. CMS Validation will check connectivity + + TCP->UseCachedCMSAddrs = TRUE; + goto CheckServers; + +rootok: + + freeaddrinfo(res); + + INETOK = TRUE; // We have connectivity + + res = 0; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever + hints.ai_socktype = SOCK_DGRAM; + n = getaddrinfo(TCP->CMSServer, NULL, &hints, &res); + + if (n || !res || res->ai_next == 0) // Resolve Failed, or Returned only one Host + { + // Switch to Cached Servers + + if (res) + { + // If Host is amazonaws, allow it + + remoteHost = gethostbyaddr((char *) &res->ai_addr->sa_data[2], 4, AF_INET); + + if (remoteHost && strstr(_strlwr(remoteHost->h_name), "amazonaws")) + goto resok; + + Debugprintf("Resolve CMS returned only one host"); + freeaddrinfo(res); + } + else + Debugprintf("Resolve CMS Failed"); + + TCP->UseCachedCMSAddrs = TRUE; + + goto CheckServers; + } + +resok: + + saveres = res; + + while (res) + { + memcpy(&addr.s_addr, &res->ai_addr->sa_data[2], 4); + TCP->CMSAddr[i] = addr; + TCP->CMSFailed[i] = FALSE; + i++; + res = res->ai_next; + } + + freeaddrinfo(saveres); + + TCP->NumberofCMSAddrs = i; + + i = 0; + + while (i < TCP->NumberofCMSAddrs) + { + if (TCP->CMSName[i]) + free(TCP->CMSName[i]); + + remoteHost = gethostbyaddr((char *) &TCP->CMSAddr[i], 4, AF_INET); + + if (remoteHost == NULL) + { + int dwError = WSAGetLastError(); + + TCP->CMSName[i] = NULL; + + if (dwError != 0) + { + if (dwError == HOST_NOT_FOUND) + Debugprintf("CMS - Host not found"); + else if (dwError == NO_DATA) + Debugprintf("CMS No data record found"); + else + Debugprintf("CMS Gethost failed %d", dwError); + } + } + else + { + Debugprintf("CMS #%d %s Official name : %s",i, inet_ntoa(TCP->CMSAddr[i]), remoteHost->h_name); + + TCP->CMSName[i] = _strdup(remoteHost->h_name); // Save Host Name + + for (pAlias = remoteHost->h_aliases; *pAlias != 0; pAlias++) + { + Debugprintf("\tAlternate name #%d: %s\n", i, *pAlias); + } + } + i++; + } + + TCP->NumberofCMSAddrs = i; + +CheckServers: +#ifndef LINBPQ + CheckMenuItem(TNC->TCPInfo->hActionMenu, 4, MF_BYPOSITION | TCP->UseCachedCMSAddrs<<3); +#endif + if (TCP->UseCachedCMSAddrs) + { + // Get Cached Servers from CMSInfo.txt + + GetCMSCachedInfo(TNC); + } + + if (TCP->NumberofCMSAddrs == 0) + { + TCP->CMSOK = FALSE; +#ifndef LINBPQ + SetWindowText(TCP->hCMSWnd, "NO CMS"); +#endif + return; + } + + // if we don't know we have Internet connectivity, make sure we can connect to at least one of them + + TCP->CMSOK = INETOK | CMSCheck(TNC, TCP); // If we know we have Inet, dont check connectivity + +#ifndef LINBPQ + if (TCP->CMSOK) + MySetWindowText(TCP->hCMSWnd, "CMS OK"); + else + MySetWindowText(TCP->hCMSWnd, "NO CMS"); +#endif + return; +} + +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_NAME 255 +#define MAX_VALUE_DATA 255 + + + +VOID GetCMSCachedInfo(struct TNCINFO * TNC) +{ + struct TCPINFO * TCP = TNC->TCPInfo; + ULONG IPAD; + char inname[256]; + + FILE *in; + char Buffer[2048]; + char *buf = Buffer; + char *ptr1, *ptr2, *context; + int i = 0; + + if (LogDirectory[0] == 0) + { + strcpy(inname, "logs/CMSInfo.txt"); + } + else + { + strcpy(inname, LogDirectory); + strcat(inname, "/"); + strcat(inname, "logs/CMSInfo.txt"); + } + + TCP->NumberofCMSAddrs = 0; + + in = fopen(inname, "r"); + + if (!(in)) return; + + while(fgets(buf, 128, in)) + { + ptr1 = strtok_s(buf, ", ", &context); + ptr2 = strtok_s(NULL, ", ", &context); // Skip Time + ptr2 = strtok_s(NULL, ", ", &context); + + if (ptr1[0] < 32 || ptr1[0] > 127 || ptr2 == NULL) + continue; + + IPAD = inet_addr(ptr2); + + memcpy(&TCP->CMSAddr[i], &IPAD, 4); + + TCP->CMSFailed[i] = FALSE; + + if (TCP->CMSName[i]) + free(TCP->CMSName[i]); + + TCP->CMSName[i] = _strdup(ptr1); // Save Host Name + i++; + + if (i >= MaxCMS) + break; + } + + fclose(in); + + TCP->NumberofCMSAddrs = i; + + return; +} + +BOOL CMSCheck(struct TNCINFO * TNC, struct TCPINFO * TCP) +{ + // Make sure at least one CMS can be connected to + + u_long param=1; + BOOL bcopt=TRUE; + SOCKET sock; + struct sockaddr_in sinx; + struct sockaddr_in destaddr; + int addrlen=sizeof(sinx); + int n = 0; + + destaddr.sin_family = AF_INET; + destaddr.sin_port = htons(8772); + + sinx.sin_family = AF_INET; + sinx.sin_addr.s_addr = INADDR_ANY; + sinx.sin_port = 0; + + for (n = 0; n < TCP->NumberofCMSAddrs; n++) + { + sock = socket(AF_INET, SOCK_STREAM, 0); + + if (sock == INVALID_SOCKET) + return FALSE; + + memcpy(&destaddr.sin_addr.s_addr, &TCP->CMSAddr[n], 4); + + setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (const char FAR *)&bcopt,4); + + if (bind(sock, (struct sockaddr *) &sinx, addrlen) != 0 ) + return FALSE; + + if (connect(sock,(struct sockaddr *) &destaddr, sizeof(destaddr)) == 0) + { + closesocket(sock); + return TRUE; + } + + // Failed - try next + + if (TCP->CMSName[n]) + Debugprintf("Check CMS Failed for %s", TCP->CMSName[n]); + closesocket(sock); + } + return FALSE; +} + + + +int CMSConnect(struct TNCINFO * TNC, struct TCPINFO * TCP, struct STREAMINFO * STREAM, int Stream) +{ + int err; + u_long param=1; + BOOL bcopt=TRUE; + struct ConnectionInfo * sockptr; + SOCKET sock; + struct sockaddr_in sinx; + struct sockaddr_in destaddr; + int addrlen=sizeof(sinx); + int n; + char Msg[80]; + + sockptr = STREAM->ConnectionInfo; + + sock = sockptr->socket = socket(AF_INET, SOCK_STREAM, 0); + + if (sock == INVALID_SOCKET) + { + ReportError(STREAM, "Create Socket Failed"); + return FALSE; + } + + if (sockptr->ADIF == NULL) + sockptr->ADIF = malloc(sizeof(struct ADIF)); + + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + + sockptr->SocketActive = TRUE; + sockptr->InputLen = 0; + sockptr->LoginState = 2; + sockptr->UserPointer = 0; + sockptr->DoEcho = FALSE; + sockptr->BPQTermMode = FALSE; + + sockptr->FBBMode = TRUE; // Raw Data + sockptr->NeedLF = FALSE; + + if (sockptr->ADIF == NULL) + sockptr->ADIF = malloc(sizeof(struct ADIF)); + + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + + destaddr.sin_family = AF_INET; + destaddr.sin_port = htons(8772); + + // See if current CMS is down + + n = 0; + + while (TCP->CMSFailed[TCP->NextCMSAddr]) + { + TCP->NextCMSAddr++; + if (TCP->NextCMSAddr >= TCP->NumberofCMSAddrs) TCP->NextCMSAddr = 0; + n++; + + if (n == TCP->NumberofCMSAddrs) + { + TCP->CMSOK = FALSE; +#ifndef LINBPQ + DrawMenuBar(TNC->hDlg); +#endif + ReportError(STREAM, "All CMS Servers are inaccessible"); + closesocket(sock); + + if (TCP->RELAYHOST[0] && TCP->FallbacktoRelay && STREAM->NoCMSFallback == 0) + { + STREAM->Connecting = TRUE; + STREAM->ConnectionInfo->CMSSession = TRUE; + STREAM->ConnectionInfo->RelaySession = TRUE; + return TCPConnect(TNC, TCP, STREAM, TCP->RELAYHOST, 8772, TRUE); + } + + STREAM->NeedDisc = 10; + TNC->Streams[Stream].Connecting = FALSE; + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + return FALSE; + } + } + + sockptr->CMSIndex = TCP->NextCMSAddr; + + sprintf(Msg, "Trying %s", TCP->CMSName[TCP->NextCMSAddr]); + + memcpy(&destaddr.sin_addr.s_addr, &TCP->CMSAddr[TCP->NextCMSAddr++], 4); + + if (TCP->NextCMSAddr >= TCP->NumberofCMSAddrs) + TCP->NextCMSAddr = 0; + + ioctl(sockptr->socket, FIONBIO, ¶m); + + setsockopt (sockptr->socket, 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(sockptr->socket, (struct sockaddr *) &sinx, addrlen) != 0 ) + { + ReportError(STREAM, "Bind Failed"); + return FALSE; + } + +#ifndef LINBPQ + ModifyMenu(TCP->hDisMenu, Stream - 1, MF_BYPOSITION | MF_STRING, IDM_DISCONNECT + Stream, "CMS"); +#endif + + Report(STREAM, Msg); + + if (connect(sockptr->socket,(struct sockaddr *) &destaddr, sizeof(destaddr)) == 0) + { + // + // Connected successful + // + + ReportError(STREAM, "*** Connected"); + return TRUE; + } + else + { + err=WSAGetLastError(); + + if (err == 10035 || err == 115 || err == 36 || err == 150) //EWOULDBLOCK + { + // Connect in Progress + + sockptr->UserPointer = &CMSUser; + return TRUE; + } + else + { + // Connect failed + + closesocket(sockptr->socket); + + if (sockptr->CMSSession && sockptr->RelaySession == 0) + { + // Try Next + + TCP->CMSFailed[sockptr->CMSIndex] = TRUE; + Debugprintf("Connect Failed %d, trying next", err); + CMSConnect(TNC, TNC->TCPInfo, &TNC->Streams[Stream], Stream); + return 0; + } + + ReportError(STREAM, "Connect Failed"); + CheckCMS(TNC); + + STREAM->Connecting = FALSE; + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + STREAM->NeedDisc = 10; + + return FALSE; + } + } + return FALSE; + +} + +VOID SaveCMSHostInfo(int port, struct TCPINFO * TCP, int CMSNo) +{ + char Info[256]; + char inname[256]; + char outname[256]; + + unsigned char work[4]; + FILE *in, *out; + char Buffer[2048]; + char *buf = Buffer; + + if (TCP->CMS == 0) + return; + + if (CMSNo > 9 || CMSNo < 0 || TCP->CMSName[CMSNo] == 0) + { + Debugprintf("SaveCMSHostInfo invalid CMS Number %d", CMSNo); + return; + } + + if (LogDirectory[0] == 0) + { + strcpy(inname, "logs/CMSInfo.txt"); + } + else + { + strcpy(inname, LogDirectory); + strcat(inname, "/"); + strcat(inname, "logs/CMSInfo.txt"); + } + + if (LogDirectory[0] == 0) + { + strcpy(outname, "logs/CMSInfo.tmp"); + } + else + { + strcpy(outname, LogDirectory); + strcat(outname, "/"); + strcat(outname, "logs/CMSInfo.tmp"); + } + + memcpy(work, &TCP->CMSAddr[CMSNo].s_addr, 4); + + sprintf(Info,"%s %d %d.%d.%d.%d\n", TCP->CMSName[CMSNo], (int)time(NULL), + work[0], work[1], work[2], work[3]); + + + in = fopen(inname, "r"); + + if (!(in)) + { + in = fopen(inname, "w"); + + if (!(in)) + { + perror("Failed to create CMSInfo.txt"); + Debugprintf("Failed to create CMSInfo.txt"); + return; + } + fclose(in); + in = fopen(inname, "r"); + } + + if (!(in)) return; + + out = fopen(outname, "w"); + + if (!(out)) return; + + while(fgets(buf, 128, in)) + { + char addr[256]; + time_t t; + char ip[256]; + int n; + + if (sizeof(time_t) == 4) + n = sscanf(buf,"%s %d %s", addr, (int *)&t, ip); + else + n = sscanf(buf, "%s %lld %s", addr, &t, ip); + + if (n == 3) + { + time_t age = time(NULL) - t; + + // if not current server and not too old, copy across + + if (addr[0] > 31 && addr[0] < 127) + if ((age < 86400 * 30) && strcmp(addr, TCP->CMSName[CMSNo]) != 0) + fputs(buf, out); + } + } + + fputs(Info, out); + + fclose(in); + fclose(out); + + remove(inname); + rename(outname, inname); + + return; +} + +int TCPConnect(struct TNCINFO * TNC, struct TCPINFO * TCP, struct STREAMINFO * STREAM, char * Host, int Port, BOOL FBB) +{ + int err; + u_long param=1; + BOOL bcopt=TRUE; + struct ConnectionInfo * sockptr; + SOCKET sock; + struct sockaddr_in sinx; + struct sockaddr_in destaddr; + int addrlen=sizeof(sinx); + int i; + + sockptr = STREAM->ConnectionInfo; + + sock = sockptr->socket = socket(AF_INET, SOCK_STREAM, 0); + + if (sock == INVALID_SOCKET) + { + ReportError(STREAM, "Create Socket Failed"); + return FALSE; + } + + sockptr->SocketActive = TRUE; + sockptr->InputLen = 0; + sockptr->LoginState = 2; + sockptr->UserPointer = 0; + sockptr->DoEcho = FALSE; + + sockptr->FBBMode = FBB; // Raw Data + + if (sockptr->ADIF == NULL) + sockptr->ADIF = malloc(sizeof(struct ADIF)); + + memset(sockptr->ADIF, 0, sizeof(struct ADIF)); + + + // Resolve Name if needed + + sockptr->sin.sin_family = AF_INET; + sockptr->sin.sin_port = htons(Port); + + sockptr->sin.sin_addr.s_addr = inet_addr(Host); + + if (sockptr->sin.sin_addr.s_addr == INADDR_NONE) + { + struct hostent * HostEnt; + + // Resolve name to address + + HostEnt = gethostbyname(Host); + + if (!HostEnt) + { + ReportError(STREAM, "Resolve HostName Failed"); + return FALSE; // Resolve failed + } + i = 0; + while (HostEnt->h_addr_list[i] != 0) + { + struct in_addr addr; + addr.s_addr = *(u_long *) HostEnt->h_addr_list[i++]; + } + memcpy(&sockptr->sin.sin_addr.s_addr, HostEnt->h_addr, 4); + } + + ioctl (sockptr->socket, FIONBIO, ¶m); + + setsockopt (sockptr->socket, 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(sockptr->socket, (struct sockaddr *) &sinx, addrlen) != 0 ) + { + ReportError(STREAM, "Bind Failed"); + return FALSE; + } + + if (LogEnabled) + { + char logmsg[512]; + + sprintf(logmsg,"%d Outward Connect to %s Port %d\n", sockptr->Number, Host, Port); + WriteLog (logmsg); + } + + + if (connect(sockptr->socket,(struct sockaddr *) &sockptr->sin, sizeof(destaddr)) == 0) + { + // + // Connected successful + // + + ReportError(STREAM, "*** Connected"); + + // Get Send Buffer Size + + return TRUE; + } + else + { + err=WSAGetLastError(); + + if (err == 10035 || err == 115 || err == 36) //EWOULDBLOCK + { + // Connect in Progress + + sockptr->UserPointer = &HostUser; + return TRUE; + } + else + { + // Connect failed + + closesocket(sockptr->socket); + ReportError(STREAM, "Connect Failed"); + STREAM->Connecting = FALSE; + sockptr->SocketActive = FALSE; + ShowConnections(TNC); + STREAM->NeedDisc = 10; + + return FALSE; + } + } + + return FALSE; + +} + + +VOID Tel_Format_Addr(struct ConnectionInfo * sockptr, char * dst) +{ + unsigned char * src; + char zeros[12] = ""; + char * ptr; + struct + { + int base, len; + } best, cur; + unsigned int words[8]; + int i; + + if (sockptr->sin.sin_family != AF_INET6) + { + unsigned char work[4]; + memcpy(work, &sockptr->sin.sin_addr.s_addr, 4); + sprintf(dst,"%d.%d.%d.%d", work[0], work[1], work[2], work[3]); + return; + } + + src = (unsigned char *)&sockptr->sin6.sin6_addr; + + // See if Encapsulated IPV4 addr + + if (src[12] != 0) + { + if (memcmp(src, zeros, 12) == 0) // 12 zeros, followed by non-zero + { + sprintf(dst,"::%d.%d.%d.%d", src[12], src[13], src[14], src[15]); + return; + } + } + + // Convert 16 bytes to 8 words + + for (i = 0; i < 16; i += 2) + words[i / 2] = (src[i] << 8) | src[i + 1]; + + // Look for longest run of zeros + + best.base = -1; + cur.base = -1; + + for (i = 0; i < 8; i++) + { + if (words[i] == 0) + { + if (cur.base == -1) + cur.base = i, cur.len = 1; // New run, save start + else + cur.len++; // Continuation - increment length + } + else + { + // End of a run of zeros + + if (cur.base != -1) + { + // See if this run is longer + + if (best.base == -1 || cur.len > best.len) + best = cur; + + cur.base = -1; // Start again + } + } + } + + if (cur.base != -1) + { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + + if (best.base != -1 && best.len < 2) + best.base = -1; + + ptr = dst; + + for (i = 0; i < 8; i++) + { + /* Are we inside the best run of 0x00's? */ + + if (best.base != -1 && i >= best.base && i < (best.base + best.len)) + { + // Just output one : for whole string of zeros + + *ptr++ = ':'; + i = best.base + best.len - 1; // skip rest of zeros + continue; + } + + /* Are we following an initial run of 0x00s or any real hex? */ + + if (i != 0) + *ptr++ = ':'; + + ptr += sprintf (ptr, "%x", words[i]); + + // Was it a trailing run of 0x00's? + } + + if (best.base != -1 && (best.base + best.len) == 8) + *ptr++ = ':'; + + *ptr++ = '\0'; +} + +BOOL TelSendPacket(int Stream, struct STREAMINFO * STREAM, PMSGWITHLEN buffptr, struct ADIF * ADIF) +{ + int datalen; + UCHAR * MsgPtr; + SOCKET sock; + struct ConnectionInfo * sockptr = STREAM->ConnectionInfo; + + datalen = (int)buffptr->Len; + MsgPtr = &buffptr->Data[0]; + + STREAM->bytesTXed += datalen; + + sock = sockptr->socket; + + if (sockptr->UserPointer == &CMSUser) + { + WritetoTrace(Stream, MsgPtr, datalen, ADIF, 'S'); + } + + if (sockptr->UTF8) + { + // Convert any non-utf8 chars + + if (IsUTF8(MsgPtr, datalen) == FALSE) + { + unsigned char UTF[1024]; + int u, code; + + // Try to guess encoding + + code = TrytoGuessCode(MsgPtr, datalen); + + if (code == 437) + u = Convert437toUTF8(MsgPtr, datalen, UTF); + else if (code == 1251) + u = Convert1251toUTF8(MsgPtr, datalen, UTF); + else + u = Convert1252toUTF8(MsgPtr, datalen, UTF); + + SendAndCheck(sockptr, UTF, u, 0); + ReleaseBuffer(buffptr); + return TRUE; + } + } + + if (sockptr->FBBMode && sockptr->NeedLF == FALSE) + { +/* + // if Outward Connect to FBB, Replace ff with ffff + + if (0) // if we use this need to fix retry + { + char * ptr2, * ptr = &MsgPtr[0]; + int i; + do + { + ptr2 = memchr(ptr, 255, datalen); + + if (ptr2 == 0) + { + // no ff, so just send as is + + xxxsend(sock, ptr, datalen, 0); + i=0; + break; + } + + i=ptr2+1-ptr; + + xxsend(sock,ptr,i,0); + xxsend(sock,"\xff",1,0); + + datalen-=i; + ptr=ptr2+1; + } + while (datalen>0); + } +*/ + // Normal FBB Mode path + + BOOL ret = SendAndCheck(sockptr, MsgPtr, datalen, 0); + ReleaseBuffer(buffptr); + return ret; + } + + // Not FBB mode, or FBB and NEEDLF Replace cr with crlf + + { + unsigned char Out[1024]; + unsigned char c; + unsigned char * ptr2 = Out; + unsigned char * ptr = &MsgPtr[0]; + + while (datalen--) + { + c = (*ptr++); + + if (c == 13) + { + *(ptr2++) = 13; + *(ptr2++) = 10; + } + else + *(ptr2++) = c; + } + + ReleaseBuffer(buffptr); + return SendAndCheck(sockptr, Out, (int)(ptr2 - Out), 0); + } +} + +VOID ProcessTrimodeCommand(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, char * MsgPtr) +{ + struct STREAMINFO * STREAM = &TNC->Streams[sockptr->Number]; + int Port = 4; + + Debugprintf(MsgPtr); + + if (strcmp(MsgPtr, "CLOSE") == 0) + { + if (STREAM->Connected) + { + STREAM->ReportDISC = TRUE; + } + } + +// MYCALLSIGN XE2BNC + else + if (memcmp(MsgPtr, "MYCALLSIGN", 10) == 0) + { + char * call = &MsgPtr[11]; + + if (strlen(call) > 9) + call[9] = 0; + + memcpy(STREAM->MyCall, call, 10); + + ConvToAX25(call, &TNC->PortRecord->ATTACHEDSESSIONS[sockptr->Number]->L4USER[0]); + + strcpy(&TNCInfo[Port]->Streams[0].MyCall[0], call); + } + + +// TARGETCALLSIGN KE7XO + else + if (memcmp(MsgPtr, "TARGETCALLSIGN", 14) == 0) + { + char * call = &MsgPtr[15]; + + if (strlen(call) > 9) + call[9] = 0; + + memcpy(STREAM->RemoteCall, call, 10); + } +// INITIATECALL 50 + else + if (memcmp(MsgPtr, "INITIATECALL", 12) == 0) + { + char Cmd[80]; + int n; + + n = sprintf(Cmd,"C %s\r", STREAM->RemoteCall); + + SendtoNode(TNC, sockptr->Number, Cmd, n); + } + + +// CHANNEL 3586500,None,None + else + if (memcmp(MsgPtr, "CHANNEL", 7) == 0) + { + double Freq = atof(&MsgPtr[8]); + char Radiocmd[80]; + int n; + + strcpy(sockptr->Callsign, "G8BPQ"); + + n = sprintf(Radiocmd,"RADIO %f %s\r", Freq/1000000, "USB"); + + SendtoNode(TNC, sockptr->Number, Radiocmd, n); + } + + else + if (memcmp(MsgPtr, "PROTOCOL", 8) == 0) + { + // Attach the relevant port + + SendtoNode(TNC, sockptr->Number, "ATTACH 4\r", 9); + } + + else + if (strcmp(MsgPtr, "BUSY") == 0) + send(sockptr->socket, "BUSY False\r\n", 12,0); + + + send(sockptr->socket, "CMD\r\n", 5,0); + +// SendtoNode(TNC, sockptr->Number, NodeLine, len); +} + + +VOID ProcessTrimodeResponse(struct TNCINFO * TNC, struct STREAMINFO * STREAM, unsigned char * MsgPtr, int Msglen) +{ + MsgPtr[Msglen] = 0; + + if (STREAM->ConnectionInfo->TriModeConnected) + { + // Send over the Data Socket + + send(STREAM->ConnectionInfo->TriModeDataSock, MsgPtr, Msglen, 0); + + return; + } + + strlop(MsgPtr, 13); + Debugprintf(MsgPtr); + + if (memcmp(MsgPtr, "*** Connected to ", 17) == 0) + { + char Cmd[80]; + int n; + + n = sprintf(Cmd,"CONNECTED %s\r", &MsgPtr[17]); + + STREAM->ConnectionInfo->TriModeConnected = TRUE; + + send(STREAM->ConnectionInfo->socket, Cmd, n, 0); + } +} + +VOID ProcessTriModeDataMessage(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, struct STREAMINFO * STREAM) +{ + int len=0; + char NLMsg[3]={13,10,0}; + char RelayMsg[] = "No CMS connection available - using local BPQMail\r"; + struct TCPINFO * TCP = TNC->TCPInfo; + unsigned char Buffer[256]; + + ioctl(sock,FIONREAD,&len); + + if (len > 256) len = 256; + + len = recv(sock, Buffer, len, 0); + + if (len == SOCKET_ERROR || len ==0) + { + // Failed or closed - clear connection + + closesocket(sock); + return; + } + + SendtoNode(TNC, sockptr->Number, Buffer, len); +} + +extern struct DATAMESSAGE * REPLYBUFFER; +char * __cdecl Cmdprintf(TRANSPORTENTRY * Session, char * Bufferptr, const char * format, ...); + + +VOID RECONFIGTELNET (TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, struct CMDX * CMD) +{ + int Port = 0, index =0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + struct TNCINFO * TNC; + char * ptr1, * ptr2; + char buf[256],errbuf[256]; + char * Config; + struct TCPINFO * TCP; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + TNC = TNCInfo[Port]; + + if (TNC == NULL || TNC->Hardware != H_TELNET) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a Telnet port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + TCP = TNC->TCPInfo; + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr && _stricmp(ptr, "ALL") == 0) + { + // Use EXTRESTART Code + + PEXTPORTDATA PORTVEC = (PEXTPORTDATA) PORT; + PORTVEC->EXTRESTART = 1; + + Bufferptr = Cmdprintf(Session, Bufferptr, "Reconfig Telnet Ok\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (ptr && _stricmp(ptr, "USERS") == 0) + { + // Reconfig Users + + if (!ProcessConfig()) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Failed to reread config file - leaving config unchanged\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Config = PortConfig[Port]; + + if (Config == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "No Config Entries found\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Don't free old user records - sessions may have pointers to them + + // Free the header + + if (TCP->UserRecPtr) + { + free(TCP->UserRecPtr); + TCP->UserRecPtr = NULL; + } + + TCP->NumberofUsers = 0; + + // Look for USER lines + + ptr1 = Config; + ptr2 = strchr(ptr1, 13); + + while(ptr2) + { + memcpy(buf, ptr1, ptr2 - ptr1 + 1); + buf[ptr2 - ptr1 + 1] = 0; + ptr1 = ptr2 + 2; + ptr2 = strchr(ptr1, 13); + strcpy(errbuf,buf); // save in case of erro + + if (_memicmp(buf, "USER=", 5) == 0 || _memicmp(buf, "USER ", 5) == 0) + { + char *User, *Pwd, *UserCall, *Secure, * Appl; + int End = (int)strlen(buf) -1; + struct UserRec * USER; + char Param[8][256]; + char * ptr1, * ptr2; + int n = 0; + char * value = &buf[5]; + + // USER=user,password,call,appl,SYSOP + + memset(Param, 0, 2048); + strlop(value, 13); + strlop(value, ';'); + + ptr1 = value; + + while (ptr1 && *ptr1 && n < 8) + { + ptr2 = strchr(ptr1, ','); + if (ptr2) *ptr2++ = 0; + + strcpy(&Param[n][0], ptr1); + strlop(Param[n++], ' '); + ptr1 = ptr2; + while(ptr1 && *ptr1 && *ptr1 == ' ') + ptr1++; + } + + + User = &Param[0][0]; + + if (_stricmp(User, "ANON") == 0) + { + strcpy(&Param[2][0], "ANON"); + strcpy(&Param[4][0], ""); // Dont allow SYSOP if ANON + } + + Pwd = &Param[1][0]; + UserCall = &Param[2][0]; + Appl = &Param[3][0]; + Secure = &Param[4][0]; + + if (User[0] == 0 || Pwd[0] == 0 || UserCall[0] == 0) // invalid record + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Bad USER Record %s\r", errbuf); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + _strupr(UserCall); + + if (TCP->NumberofUsers == 0) + TCP->UserRecPtr = malloc(sizeof(void *)); + else + TCP->UserRecPtr = realloc(TCP->UserRecPtr, (TCP->NumberofUsers+1) * sizeof(void *)); + + USER = zalloc(sizeof(struct UserRec)); + + TCP->UserRecPtr[TCP->NumberofUsers] = USER; + + USER->Callsign = _strdup(UserCall); + USER->Password = _strdup(Pwd); + USER->UserName = _strdup(User); + USER->Appl = zalloc(32); + USER->Secure = FALSE; + + if (_stricmp(Secure, "SYSOP") == 0) + USER->Secure = TRUE; + + if (Appl[0] && strcmp(Appl, "\"\"") != 0) + { + strcpy(USER->Appl, _strupr(Appl)); + strcat(USER->Appl, "\r\n"); + } + TCP->NumberofUsers++; + } + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Reread Telnet Users Ok - %d USER Records\r", TCP->NumberofUsers); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid parameter - use either USERS or ALL \r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID SHOWTELNET(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, struct CMDX * CMD) +{ + // DISPLAY Telnet Server Status Mheard + + int Port = 0, index =0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + int txlen = 0, n; + struct TNCINFO * TNC; + char msg[80]; + struct ConnectionInfo * sockptr; + int i; + char CMS[] = "CMS Disabled"; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + TNC = TNCInfo[Port]; + + if (TNC == NULL || TNC->Hardware != H_TELNET) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a Telnet port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (TNC->TCPInfo->CMS) + if (TNC->TCPInfo->CMSOK) + strcpy(CMS, "CMS Ok"); + else + strcpy(CMS, "No CMS"); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Telnet Status for Port %d %s\r", Port, CMS); + + for (n = 1; n <= TNC->TCPInfo->CurrentSockets; n++) + { + sockptr=TNC->Streams[n].ConnectionInfo; + + if (!sockptr->SocketActive) + { + strcpy(msg,"Idle"); + } + else + { + if (sockptr->UserPointer == 0) + { + if (sockptr->HTTPMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + + if (sockptr->WebSocks) + sprintf(msg, "Websock From %s", Addr); + else + sprintf(msg, "HTTP From %s", Addr); + + } + else if (sockptr->DRATSMode) + { + char Addr[100]; + Tel_Format_Addr(sockptr, Addr); + sprintf(msg, "DRATS From %s", Addr); + } + else + strcpy(msg,"Logging in"); + } + else + { + i=sprintf(msg,"%-10s %-10s %2d", + sockptr->UserPointer->UserName,sockptr->Callsign,sockptr->BPQStream); + } + } + Bufferptr = Cmdprintf(Session, Bufferptr, "%s\r", msg); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + + +// Refresh any Web Socket Webmail index display +// Called whenever message database is changed + +#ifdef LINBPQ + +int DoRefreshWebMailIndex(); + +int RefreshWebMailIndex() +{ + DoRefreshWebMailIndex(); + return 0; +} + +#else + +// Have to pass request from BPQMail to DLL as socket can only be accessed in calling process +// Pass request back to WebMail via pipe + +// Code must run in bpq32 process, so set flag here and call code from Timer Routine + +extern BOOL NeedWebMailRefresh; + + +DllExport int APIENTRY RefreshWebMailIndex() +{ + NeedWebMailRefresh = 1; + return 0; +} + +#endif + +int DoRefreshWebMailIndex() +{ + // Loop through all sockets and pick out WebMail Index Connections + + int i, n; + struct ConnectionInfo * sockptr; + struct ConnectionInfo * sockcopy; + struct TNCINFO * TNC; + struct TCPINFO * TCP; + +#ifndef LINBPQ + NeedWebMailRefresh = 0; +#endif + + for (i = 0; i < 33; i++) + { + TNC = TNCInfo[i]; + + if (TNC && TNC->Hardware == H_TELNET) + { + TCP = TNC->TCPInfo; + + if (TCP) + { + for (n = 0; n <= TCP->MaxSessions; n++) + { + sockptr = TNC->Streams[n].ConnectionInfo; + + if (sockptr->SocketActive) + { + if (sockptr->HTTPMode && sockptr->WebSocks && memcmp(sockptr->WebURL, "WMRefresh", 9) == 0) + { + sockcopy = malloc(sizeof(struct ConnectionInfo)); + sockptr->TNC = TNC; + sockptr->LastSendTime = REALTIMETICKS; + + memcpy(sockcopy, sockptr, sizeof(struct ConnectionInfo)); + + _beginthread(ProcessWebmailWebSockThread, 2048000, (VOID *)sockcopy); // Needs big stack + } + } + } + } + } + } + return 0; +} + diff --git a/TelnetV6.c b/TelnetV6.c index b9ed7f1..54b8c92 100644 --- a/TelnetV6.c +++ b/TelnetV6.c @@ -37,7 +37,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define IDM_DISCONNECT 2000 #define IDM_LOGGING 2100 -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #ifdef WIN32 @@ -85,7 +85,11 @@ void processDRATSFrame(unsigned char * Message, int Len, struct ConnectionInfo * void DRATSConnectionLost(struct ConnectionInfo * sockptr); int BuildRigCtlPage(char * _REPLYBUFFER); void ProcessWebmailWebSockThread(void * conn); +void RHPThread(void * Params); +void ProcessRHPWebSockClosed(SOCKET socket); int ProcessSNMPPayload(UCHAR * Msg, int Len, UCHAR * Reply, int * OffPtr); +int RHPProcessHTTPMessage(struct ConnectionInfo * conn, char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE); + #ifndef LINBPQ extern HKEY REGTREE; @@ -123,6 +127,8 @@ BOOL LogEnabled = FALSE; BOOL CMSLogEnabled = TRUE; extern BOOL IncludesMail; +extern int HTTPPort; + static HMENU hMenu, hPopMenu, hPopMenu2, hPopMenu3; // handle of menu static int ProcessLine(char * buf, int Port); @@ -533,7 +539,7 @@ int ProcessLine(char * buf, int Port) TCP->TriModePort = atoi(value); else if (_stricmp(param,"HTTPPORT") == 0) - TCP->HTTPPort = atoi(value); + HTTPPort = TCP->HTTPPort = atoi(value); else if (_stricmp(param,"APIPORT") == 0) TCP->APIPort = atoi(value); @@ -4967,6 +4973,15 @@ MsgLoop: extern char * RigWebPage; +struct RHPParamBlock +{ + unsigned char * Msg; + int Len; + SOCKET Socket; +}; + + + int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, SOCKET sock, int Stream) { int w =1, x= 1, len=0, y = 2, maxlen, InputLen, ret; @@ -4989,9 +5004,23 @@ int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, S { // Failed or closed - clear connection - TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node - DataSocket_Disconnect(TNC, sockptr); - return 0; + // if Websock connection till app + + if (sockptr->WebSocks) + { + if (memcmp(sockptr->WebURL, "rhp", 3) == 0) + { + ProcessRHPWebSockClosed(sockptr->socket); + DataSocket_Disconnect(TNC, sockptr); + return 0; + } + } + else + { + TNC->Streams[sockptr->Number].ReportDISC = TRUE; //Tell Node + DataSocket_Disconnect(TNC, sockptr); + return 0; + } } MsgPtr = &sockptr->InputBuffer[0]; @@ -5008,6 +5037,7 @@ int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, S int Fin, Opcode, Len, Mask; char MaskingKey[4]; char * ptr; + char * Payload; /* +-+-+-+-+-------+-+-------------+-------------------------------+ @@ -5035,8 +5065,20 @@ int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, S Opcode = MsgPtr[0] & 15; Mask = MsgPtr[1] >> 7; Len = MsgPtr[1] & 127; - memcpy(MaskingKey, &MsgPtr[2], 4); - ptr = &MsgPtr[6]; + + if (Len == 126) // Two Byte Len + { + Len = (MsgPtr[2] << 8) + MsgPtr[3]; + memcpy(MaskingKey, &MsgPtr[4], 4); + ptr = &MsgPtr[8]; + } + else + { + memcpy(MaskingKey, &MsgPtr[2], 4); + ptr = &MsgPtr[6]; + } + + Payload = ptr; for (i = 0; i < Len; i++) { @@ -5058,7 +5100,7 @@ int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, S char RigCMD[64]; - sprintf(RigCMD, "%s PTT", &MsgPtr[6]); + sprintf(RigCMD, "%s PTT", Payload); Rig_Command( (TRANSPORTENTRY *) -1, RigCMD); } else if (memcmp(sockptr->WebURL, "WMRefresh", 9) == 0) @@ -5072,6 +5114,21 @@ int DataSocket_ReadHTTP(struct TNCINFO * TNC, struct ConnectionInfo * sockptr, S _beginthread(ProcessWebmailWebSockThread, 2048000, (VOID *)sockcopy); // Needs big stack return 0; } + else if (memcmp(sockptr->WebURL, "rhp", 3) == 0) + { + // Run in thread as it may block; + + struct RHPParamBlock * ParamBlock = malloc(sizeof(struct RHPParamBlock)); + + ParamBlock->Socket = sockptr->socket; + ParamBlock->Len = Len; + ParamBlock->Msg = malloc(Len + 10); + memcpy(ParamBlock->Msg, Payload, Len); + _beginthread(RHPThread, 0, (VOID *)ParamBlock); + + sockptr->InputLen = 0; + return 0; + } } else Debugprintf("WebSock Opcode %d Msg %s", Opcode, &MsgPtr[6]); diff --git a/UIARQ.c b/UIARQ.c index 3dd588f..4d4b779 100644 --- a/UIARQ.c +++ b/UIARQ.c @@ -23,7 +23,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); diff --git a/UIRoutines.c b/UIRoutines.c index 923725b..d8195cd 100644 --- a/UIRoutines.c +++ b/UIRoutines.c @@ -22,6 +22,8 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // UI Handling Routines #include "bpqmail.h" +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); char UIDEST[10] = "FBB"; diff --git a/UZ7HODrv.c b/UZ7HODrv.c index f9b433b..0a417c7 100644 --- a/UZ7HODrv.c +++ b/UZ7HODrv.c @@ -37,7 +37,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/V4.c b/V4.c index 5bd6880..08f2f23 100644 --- a/V4.c +++ b/V4.c @@ -36,7 +36,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define SD_BOTH 0x02 -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "bpq32.h" diff --git a/VARA.c b/VARA.c index e8fd061..d9fc67e 100644 --- a/VARA.c +++ b/VARA.c @@ -28,7 +28,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include -#include "CHeaders.h" +#include "cheaders.h" #ifdef WIN32 #include diff --git a/Versions-skigdebian.h b/Versions-skigdebian.h new file mode 100644 index 0000000..d5d92c3 --- /dev/null +++ b/Versions-skigdebian.h @@ -0,0 +1,125 @@ + +#ifdef Kernel + +#define Vers 5,2,9,2 +#define Verstring "5.2.9.2\0" +#define Datestring "September 2012" +#define VerComments "G8BPQ Packet Switch V5.2.9.2\0" +#define VerCopyright "Copyright © 2001-2012 John Wiseman G8BPQ\0" +#define VerDesc "BPQ32 Switch\0" + +#endif + +#define KVers 6,0,24,59 +#define KVerstring "6.0.24.59\0" + +#ifdef CKernel + +#define Vers KVers +#define Verstring KVerstring +#define Datestring "January 2025" +#define VerComments "G8BPQ Packet Switch (C Version)" KVerstring +#define VerCopyright "Copyright © 2001-2025 John Wiseman G8BPQ\0" +#define VerDesc "BPQ32 Switch\0" +#define VerProduct "BPQ32" + +#endif + +#ifdef TermTCP + +#define Vers 1,0,16,2 +#define Verstring "1.0.16.2\0" +#define VerComments "Internet Terminal for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2011-2025 John Wiseman G8BPQ\0" +#define VerDesc "Simple TCP Terminal Program for G8BPQ Switch\0" +#define VerProduct "BPQTermTCP" + +#endif + +#ifdef BPQTerm + +#define Vers 2,2,5,2 +#define Verstring "2.2.5.2\0" +#define VerComments "Simple Terminal for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 1999-2025 John Wiseman G8BPQ\0" +#define VerDesc "Simple Terminal Program for G8BPQ Switch\0" +#define VerProduct "BPQTerminal" + +#endif + +#ifdef BPQTermMDI + +#define Vers 2,2,0,3 +#define Verstring "2.2.0.3\0" +#define VerComments "MDI Terminal for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 1999-2025 John Wiseman G8BPQ\0" +#define VerDesc "MDI Terminal Program for G8BPQ Switch\0" + +#endif + +#ifdef MAIL + +#define Vers KVers +#define Verstring KVerstring +#define VerComments "Mail server for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2009-2025 John Wiseman G8BPQ\0" +#define VerDesc "Mail server for G8BPQ's 32 Bit Switch\0" +#define VerProduct "BPQMail" + +#endif + +#ifdef HOSTMODES + +#define Vers 1,1,8,1 +#define Verstring "1.1.8.1\0" +//#define SPECIALVERSION "Test 3" +#define VerComments "Host Modes Emulator for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2009-2019 John Wiseman G8BPQ\0" +#define VerDesc "Host Modes Emulator for G8BPQ's 32 Bit Switch\0" +#define VerProduct "BPQHostModes" + +#endif + + +#ifdef UIUTIL + +#define Vers 0,1,3,1 +#define Verstring "0.1.3.1\0" +#define VerComments "Beacon Utility for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2011-2019 John Wiseman G8BPQ\0" +#define VerDesc "Beacon Utility for G8BPQ Switch\0" +#define VerProduct "BPQUIUtil" + +#endif + +#ifdef AUTH + +#define Vers 0,1,0,0 +#define Verstring "0.1.0.0\0" +#define VerComments "Password Generation Utility for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2011-2025 John Wiseman G8BPQ\0" +#define VerDesc "Password Generation Utility for G8BPQ Switch\0" + +#endif + +#ifdef APRS + +#define Vers KVers +#define Verstring KVerstring +#define VerComments "APRS Client for G8BPQ Switch\0" +#define VerCopyright "Copyright © 2012-2025 John Wiseman G8BPQ\0" +#define VerDesc "APRS Client for G8BPQ Switch\0" +#define VerProduct "BPQAPRS" + +#endif + +#ifdef CHAT + +#define Vers KVers +#define Verstring KVerstring +#define VerComments "Chat server for G8BPQ Packet Switch\0" +#define VerCopyright "Copyright © 2009-2025 John Wiseman G8BPQ\0" +#define VerDesc "Chat server for G8BPQ's 32 Bit Switch\0" +#define VerProduct "BPQChat" + +#endif diff --git a/Versions.h b/Versions.h index 2428b95..4d7bd3b 100644 --- a/Versions.h +++ b/Versions.h @@ -10,16 +10,16 @@ #endif -#define KVers 6,0,24,56 -#define KVerstring "6.0.24.56\0" +#define KVers 6,0,24,59 +#define KVerstring "6.0.24.59\0" #ifdef CKernel #define Vers KVers #define Verstring KVerstring -#define Datestring "December 2024" +#define Datestring "February 2025" #define VerComments "G8BPQ Packet Switch (C Version)" KVerstring -#define VerCopyright "Copyright © 2001-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2001-2025 John Wiseman G8BPQ\0" #define VerDesc "BPQ32 Switch\0" #define VerProduct "BPQ32" @@ -30,7 +30,7 @@ #define Vers 1,0,16,2 #define Verstring "1.0.16.2\0" #define VerComments "Internet Terminal for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 2011-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2011-2025 John Wiseman G8BPQ\0" #define VerDesc "Simple TCP Terminal Program for G8BPQ Switch\0" #define VerProduct "BPQTermTCP" @@ -41,7 +41,7 @@ #define Vers 2,2,5,2 #define Verstring "2.2.5.2\0" #define VerComments "Simple Terminal for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 1999-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 1999-2025 John Wiseman G8BPQ\0" #define VerDesc "Simple Terminal Program for G8BPQ Switch\0" #define VerProduct "BPQTerminal" @@ -52,7 +52,7 @@ #define Vers 2,2,0,3 #define Verstring "2.2.0.3\0" #define VerComments "MDI Terminal for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 1999-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 1999-2025 John Wiseman G8BPQ\0" #define VerDesc "MDI Terminal Program for G8BPQ Switch\0" #endif @@ -62,7 +62,7 @@ #define Vers KVers #define Verstring KVerstring #define VerComments "Mail server for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 2009-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2009-2025 John Wiseman G8BPQ\0" #define VerDesc "Mail server for G8BPQ's 32 Bit Switch\0" #define VerProduct "BPQMail" @@ -97,7 +97,7 @@ #define Vers 0,1,0,0 #define Verstring "0.1.0.0\0" #define VerComments "Password Generation Utility for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 2011-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2011-2025 John Wiseman G8BPQ\0" #define VerDesc "Password Generation Utility for G8BPQ Switch\0" #endif @@ -107,7 +107,7 @@ #define Vers KVers #define Verstring KVerstring #define VerComments "APRS Client for G8BPQ Switch\0" -#define VerCopyright "Copyright © 2012-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2012-2025 John Wiseman G8BPQ\0" #define VerDesc "APRS Client for G8BPQ Switch\0" #define VerProduct "BPQAPRS" @@ -118,7 +118,7 @@ #define Vers KVers #define Verstring KVerstring #define VerComments "Chat server for G8BPQ Packet Switch\0" -#define VerCopyright "Copyright © 2009-2024 John Wiseman G8BPQ\0" +#define VerCopyright "Copyright © 2009-2025 John Wiseman G8BPQ\0" #define VerDesc "Chat server for G8BPQ's 32 Bit Switch\0" #define VerProduct "BPQChat" diff --git a/WINMOR.c b/WINMOR.c index 7f6763b..51b77e5 100644 --- a/WINMOR.c +++ b/WINMOR.c @@ -70,7 +70,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include -#include "CHeaders.h" +#include "cheaders.h" #ifdef WIN32 #include diff --git a/WPRoutines.c b/WPRoutines.c index 0b8e315..0658ecc 100644 --- a/WPRoutines.c +++ b/WPRoutines.c @@ -23,6 +23,9 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "bpqmail.h" +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); + int CurrentWPIndex; char CurrentWPCall[10]; @@ -121,7 +124,7 @@ VOID GetWPDatabase() sprintf(Key, "R%d", i++); - GetStringValue(group, Key, Record); + GetStringValue(group, Key, Record, 1024); if (Record[0] == 0) // End of List return; @@ -269,23 +272,23 @@ WPOK:; memset(&WPRec, 0, sizeof(WPRec)); - GetStringValue(wpgroup, "c", WPRec.callsign); - GetStringValue(wpgroup, "n", WPRec.name); + GetStringValue(wpgroup, "c", WPRec.callsign, 6); + GetStringValue(wpgroup, "n", WPRec.name, 12); WPRec.Type = GetIntValue(wpgroup, "T"); WPRec.changed = GetIntValue(wpgroup, "ch"); WPRec.seen = GetIntValue(wpgroup, "s"); - GetStringValue(wpgroup, "h", WPRec.first_homebbs); - GetStringValue(wpgroup, "sh", WPRec.secnd_homebbs); - GetStringValue(wpgroup, "z", WPRec.first_zip); - GetStringValue(wpgroup, "sz", WPRec.secnd_zip); + GetStringValue(wpgroup, "h", WPRec.first_homebbs, 40); + GetStringValue(wpgroup, "sh", WPRec.secnd_homebbs, 40); + GetStringValue(wpgroup, "z", WPRec.first_zip, 8); + GetStringValue(wpgroup, "sz", WPRec.secnd_zip, 8); - GetStringValue(wpgroup, "q", Temp); + GetStringValue(wpgroup, "q", Temp, 30); Temp[30] = 0; strcpy(WPRec.first_qth, Temp); - GetStringValue(wpgroup, "sq", Temp); + GetStringValue(wpgroup, "sq", Temp, 30); Temp[30] = 0; strcpy(WPRec.secnd_qth, Temp); diff --git a/WebMail.c b/WebMail.c index 05dc303..a21bf7f 100644 --- a/WebMail.c +++ b/WebMail.c @@ -19,7 +19,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include "bpqmail.h" #define MAIL diff --git a/WinRPR.c b/WinRPR.c index d087f47..af1a7fb 100644 --- a/WinRPR.c +++ b/WinRPR.c @@ -31,7 +31,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define MaxStreams 1 -#include "CHeaders.h" +#include "cheaders.h" extern int (WINAPI FAR *GetModuleFileNameExPtr)(); diff --git a/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/adif.c b/adif.c index 040942e..45e734d 100644 --- a/adif.c +++ b/adif.c @@ -26,7 +26,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include #include "time.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "adif.h" #include "telnetserver.h" diff --git a/asmDOSAPI.asm b/asmDOSAPI.asm index bda758e..42bc4d8 100644 --- a/asmDOSAPI.asm +++ b/asmDOSAPI.asm @@ -71,7 +71,7 @@ _BPQHOSTAPI: ; ; SPECIAL INTERFACE, MAINLY FOR EXTERNAL HOST MODE SUPPORT PROGS ; - extrn _GetSemaphore:near + extrn __GetSemaphore:near extrn _FreeSemaphore:near extrn _Check_Timer:near @@ -79,7 +79,7 @@ _BPQHOSTAPI: pushad call _Check_Timer push offset _APISemaphore - call _GetSemaphore + call __GetSemaphore add esp, 4 popad diff --git a/asmstrucs.h b/asmstrucs.h index 268b51a..8a46075 100644 --- a/asmstrucs.h +++ b/asmstrucs.h @@ -1044,6 +1044,8 @@ struct SEM int Rels; DWORD SemProcessID; DWORD SemThreadID; + int Line; // caller file and line + char File[MAX_PATH]; }; diff --git a/bpqaxip.c b/bpqaxip.c index d03f531..07083dd 100644 --- a/bpqaxip.c +++ b/bpqaxip.c @@ -141,7 +141,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #ifndef WIN32 #include #include @@ -3246,15 +3246,21 @@ VOID SaveAXIPCache(struct AXIPPORTINFO * PORT) #ifndef LINBPQ -static BOOL GetStringValue(config_setting_t * group, char * name, char * value) +static BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen) { - const char * str; + char * str; config_setting_t *setting; setting = config_setting_get_member (group, name); if (setting) { - str = config_setting_get_string (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; } @@ -3321,7 +3327,7 @@ VOID GetAXIPCache(struct AXIPPORTINFO * PORT) ptr++; } - if (GetStringValue(group, Key, hostaddr)) + if (GetStringValue(group, Key, hostaddr, 64)) { arp->destaddr.sin_addr.s_addr = inet_addr(hostaddr); } diff --git a/bpqchat.h b/bpqchat.h index ea322c8..05d0349 100644 --- a/bpqchat.h +++ b/bpqchat.h @@ -603,7 +603,7 @@ VOID __cdecl nprintf(ChatCIRCUIT * conn, const char * format, ...); VOID nputs(ChatCIRCUIT * conn, char * buf); #endif BOOL matchi(char * p1, char * p2); -char * strlop(const char * buf, char delim); +char * strlop(char * buf, char delim); int rt_cmd(ChatCIRCUIT *circuit, char * Buffer); ChatCIRCUIT *circuit_new(ChatCIRCUIT *circuit, int flags); void makelinks(void); @@ -687,7 +687,11 @@ int RemoveLF(char * Message, int len); struct SEM; BOOL isdigits(char * string); -void GetSemaphore(struct SEM * Semaphore, int ID); + + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); void FreeSemaphore(struct SEM * Semaphore); VOID __cdecl Debugprintf(const char * format, ...); diff --git a/bpqether.c b/bpqether.c index 91804c0..8de8801 100644 --- a/bpqether.c +++ b/bpqether.c @@ -52,7 +52,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses //#include -#include "CHeaders.h" +#include "cheaders.h" #include #include "pcap.h" diff --git a/bpqhdlc.c b/bpqhdlc.c index 83765e2..d8e3b3e 100644 --- a/bpqhdlc.c +++ b/bpqhdlc.c @@ -31,7 +31,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include -#include "CHeaders.h" +#include "cheaders.h" #include "bpq32.h" diff --git a/bpqmail.h b/bpqmail.h index ba6e27b..0a8bf06 100644 --- a/bpqmail.h +++ b/bpqmail.h @@ -33,7 +33,7 @@ #include "BPQMailrc.h" #include "dbghelp.h" #else -#include "CHeaders.h" +#include "cheaders.h" #endif #include "asmstrucs.h" @@ -879,7 +879,7 @@ struct MSESSION }; VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...); -char * strlop(const char * buf, char delim); +char * strlop(char * buf, char delim); int rt_cmd(CIRCUIT *circuit, char * Buffer); CIRCUIT *circuit_new(CIRCUIT *circuit, int flags); VOID BBSputs(CIRCUIT * conn, char * buf); @@ -1173,7 +1173,7 @@ int ProcessConnecting(CIRCUIT * circuit, char * Buffer, int Len); VOID SaveConfig(char * ConfigName); BOOL GetConfig(char * ConfigName); int GetIntValue(config_setting_t * group, char * name); -BOOL GetStringValue(config_setting_t * group, char * name, char * value); +//BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen); BOOL GetConfigFromRegistry(); VOID Parse_SID(CIRCUIT * conn, char * SID, int len); VOID ProcessMBLLine(CIRCUIT * conn, struct UserInfo * user, UCHAR* Buffer, int len); @@ -1289,7 +1289,9 @@ int RemoveLF(char * Message, int len); // Utilities BOOL isdigits(char * string); -void GetSemaphore(struct SEM * Semaphore, int ID); + + +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); void FreeSemaphore(struct SEM * Semaphore); VOID __cdecl Debugprintf(const char * format, ...); diff --git a/bpqvkiss.c b/bpqvkiss.c index 760326b..c639cee 100644 --- a/bpqvkiss.c +++ b/bpqvkiss.c @@ -34,7 +34,7 @@ typedef unsigned char byte; -#include "CHeaders.h" +#include "cheaders.h" #include "bpqvkiss.h" #include diff --git a/cMain-skigdebian.c b/cMain-skigdebian.c new file mode 100644 index 0000000..4ae1dea --- /dev/null +++ b/cMain-skigdebian.c @@ -0,0 +1,2788 @@ +/* +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 +*/ + +// +// C replacement for Main.asm +// +#define Kernel + +#define _CRT_SECURE_NO_DEPRECATE + +#pragma data_seg("_BPQDATA") + +//#include "windows.h" +//#include "winerror.h" + +#include "time.h" +#include "stdio.h" +#include + +#include "kernelresource.h" +#include "cheaders.h" +#include "tncinfo.h" +#include "mqtt.h" + +VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer); +VOID ProcessIframe(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer); +VOID FindLostBuffers(); +VOID ReadMH(); +void GetPortCTEXT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, struct CMDX * CMD); +int upnpInit(); +void AISTimer(); +void ADSBTimer(); +VOID SendSmartID(struct PORTCONTROL * PORT); +int CanPortDigi(int Port); +int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); +void MQTTTimer(); +void SaveMH(); + +#include "configstructs.h" + +extern struct CONFIGTABLE xxcfg; +extern BOOL needAIS; +extern int needADSB; + +struct PORTCONFIG * PortRec; + +#define RNRSET 0x2 // RNR RECEIVED FROM OTHER END + +// STATION INFORMATION + +char DATABASESTART[14] = ""; +char xMAJORVERSION = 4; +char xMINORVERSION = 9; +char FILLER1[16] = ""; + +struct ROUTE * NEIGHBOURS = NULL; +int ROUTE_LEN = sizeof(struct ROUTE); +int MAXNEIGHBOURS = 20; + +struct DEST_LIST * DESTS = NULL; // NODE LIST +int DEST_LIST_LEN = sizeof(struct DEST_LIST); + +struct _LINKTABLE * LINKS = NULL; +int LINK_TABLE_LEN = sizeof (struct _LINKTABLE); +int MAXLINKS = 30; + + +char MYCALL[7] = ""; // DB 7 DUP (0) ; NODE CALLSIGN (BIT SHIFTED) +char MYALIASTEXT[6] = ""; // DB ' ' ; NODE ALIAS (KEEP TOGETHER) + +char MYALIASLOPPED[10]; +char MYCALLLOPPED[10]; + +UCHAR MYCALLWITHALIAS[13] = ""; + +UCHAR NETROMCALL[7] = ""; // Call used for NETROM (can be MYCALL) + +APPLCALLS APPLCALLTABLE[NumberofAppls] = {0}; + +UCHAR MYNODECALL[10] = ""; // NODE CALLSIGN (ASCII) +UCHAR MYNETROMCALL[10] = ""; // NETROM CALLSIGN (ASCII) +char NODECALLLOPPED[10]; + +VOID * FREE_Q = NULL; + +time_t TimeLoaded = 0; + +struct PORTCONTROL * PORTTABLE = NULL; +int NUMBEROFPORTS = 0; +int PORTENTRYLEN = sizeof(struct PORTCONTROL); + +struct DEST_LIST * ENDDESTLIST = NULL; // ; NODE LIST+1 +; + +VOID * BUFFERPOOL = NULL; // START OF BUFFER POOL +VOID * ENDBUFFERPOOL = NULL; + +int OBSINIT = 5; // INITIAL OBSOLESCENCE VALUE +int OBSMIN = 4; // MINIMUM TO BROADCAST +int L3INTERVAL = 60; // 'NODES' INTERVAL IN MINS +int IDINTERVAL = 20; // 'ID' BROADCAST INTERVAL +int BTINTERVAL = 20; // 'BT' BROADCAST INTERVAL +int MINQUAL = 10; // MIN QUALITY FOR AUTOUPDATES +int HIDENODES = 0; // N * COMMAND SWITCH +int BBSQUAL = 255; // QUALITY OF BBS RELATIVE TO NODE + +int NUMBEROFBUFFERS = 999; // PACKET BUFFERS + +int PACLEN = 100; //MAX PACKET SIZE + +// L2 SYSTEM TIMER RUNS AT 3 HZ + +int T3 = 3*61*3; // LINK VALIDATION TIMER (3 MINS) (+ a bit to reduce RR collisions) + +int L2KILLTIME = 16*60*3; // IDLE LINK TIMER (16 MINS) +int L3LIVES = 15; // MAX L3 HOPS +int L4N2 = 3; // LEVEL 4 RETRY COUNT +int L4LIMIT = 60*15; // IDLE SESSION LIMIT - 15 MINS +int L4DELAY = 5; // L4 DELAYED ACK TIMER + +int BBS = 1; // INCLUDE BBS SUPPORT +int NODE = 1; // INCLUDE SWITCH SUPPORT + +int FULL_CTEXT = 1; // CTEXT ON ALL CONNECTS IF NZ + +BOOL LogL4Connects = FALSE; +BOOL LogAllConnects = FALSE; +BOOL AUTOSAVEMH = TRUE; +extern BOOL ADIFLogEnabled; +extern UCHAR LogDirectory[260]; +extern BOOL EventsEnabled; +extern BOOL SaveAPRSMsgs; +BOOL M0LTEMap = FALSE; +BOOL MQTT = FALSE; +char MQTT_HOST[80] = ""; +int MQTT_PORT = 0; +char MQTT_USER[80] = ""; +char MQTT_PASS[80] = ""; + +int MQTT_Connecting = 0; +int MQTT_Connected = 0; + + +//TNCTABLE DD 0 +//NUMBEROFSTREAMS DD 0 + +extern VOID * ENDPOOL; +extern void * APPL_Q; // Queue of frames for APRS Appl + +extern BOOL APRSActive; + +#define BPQHOSTSTREAMS 64 + +// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 + +BPQVECSTRUC XDUMMY = {0}; // Needed to force correct order of following + +BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5] = {0}; + +BPQVECSTRUC * TELNETMONVECPTR = &BPQHOSTVECTOR[BPQHOSTSTREAMS]; +BPQVECSTRUC * AGWMONVECPTR = &BPQHOSTVECTOR[BPQHOSTSTREAMS + 1]; +BPQVECSTRUC * APRSMONVECPTR = &BPQHOSTVECTOR[BPQHOSTSTREAMS + 2]; +BPQVECSTRUC * IPHOSTVECTORPTR = &BPQHOSTVECTOR[BPQHOSTSTREAMS + 3]; + +int BPQVECLENGTH = sizeof(BPQVECSTRUC); + +int NODEORDER = 0; +UCHAR LINKEDFLAG = 0; + +UCHAR UNPROTOCALL[80] = ""; + +UCHAR ExcludeList[71] = ""; // 10 ENTRIES, 7 BYTES EACH + +char * INFOMSG = NULL; + +char * CTEXTMSG = NULL; +int CTEXTLEN = 0; + +UCHAR MYALIAS[7] = ""; // ALIAS IN AX25 FORM +UCHAR BBSALIAS[7] = ""; + +UCHAR AX25CALL[7] = ""; // WORK AREA FOR AX25 <> NORMAL CALL CONVERSION +UCHAR NORMCALL[10] = ""; // CALLSIGN IN NORMAL FORMAT +int NORMLEN = 0; // LENGTH OF CALL IN NORMCALL + +int CURRENTPORT = 0; // PORT FOR CURRENT MESSAGE +VOID * CURRENTPORTPTR = NULL; // PORT CONTROL TABLE ENTRY FOR CURRENT PORT + +int SDCBYTE = 0; // CONTROL BYTE FOR CURRENT FRAME + +VOID * BUFFER = NULL; // GENERAL SAVE AREA FOR BUFFER ADDR +VOID * ADJBUFFER = NULL; // BASE ADJUSED FOR DIGIS + +UCHAR TEMPFIELD[7] = ""; // ADDRESS WORK FILED + +void * TRACE_Q = NULL; // TRANSMITTED FRAMES TO BE TRACED + +int RANDOM = 0; // 'RANDOM' NUMBER FOR PERSISTENCE CALCS + +int L2TIMERFLAG = 0; // INCREMENTED AT 18HZ BY TIMER INTERRUPT +int L3TIMERFLAG = 0; // DITTO +int L4TIMERFLAG = 0; // DITTO + +char HEADERCHAR = '}'; // CHAR FOR _NODE HEADER MSGS + +VOID * LASTPOINTER = NULL; // PERVIOUS _NODE DURING CHAINING + +int REALTIMETICKS = 0; +int BGTIMER = 0; // TO CONTROL BG SCANS + +VOID * CONFIGPTR = NULL; // Internal Config Get Offset + +int AUTOSAVE = 0; // AUTO SAVE NODES ON EXIT FLAG +int L4APPL = 1; // Application for BBSCALL/ALIAS connects +int CFLAG = 0; // C =HOST Command + +VOID * IDMSG_Q = NULL; // ID/BEACONS WAITING TO BE SENT + +int NODESINPROGRESS = 0; +VOID * CURRENTNODE = NULL; // NEXT _NODE TO SEND +VOID * DESTHEADER = NULL; // HEAD OF SORTED NODES CHAIN + +int L3TIMER = 1; // TIMER FOR 'NODES' MESSAGE +int IDTIMER = 0; // TIMER FOR ID MESSAGE +int BTTIMER = 0; // TIMER FOR BT MESSAGE + +UCHAR * NEXTFREEDATA = NULL; // ADDRESS OF NEXT FREE BYTE of shared memory + +int NEEDMH = 0; + +struct DATAMESSAGE BTHDDR = {0,0,9,240,13}; +struct _MESSAGE IDHDDR = {0,0,23,0,0,3, 240}; + +VOID * IDMSG = &IDHDDR; + +//DD 0 ; CHAIN +// DB 0 ; PORT +int BTLENGTH = 9; // DW 9 ; LENGTH +// DB 0F0H ; PID +char BTEXTFLD[256] ="\r"; + +char BridgeMap[MaxBPQPortNo + 1][MaxBPQPortNo + 1] = {0}; + +// Keep Buffers at end + +#define DATABYTES 600000 // WAS 320000 + +UCHAR DATAAREA[DATABYTES] = ""; + +void ** Bufferlist[1000] = {0}; + +extern BOOL IPRequired; +extern BOOL PMRequired; +extern int MaxHops; +extern int MAXRTT; +extern USHORT CWTABLE[]; +extern struct _TRANSPORTENTRY * L4TABLE; +extern UCHAR ROUTEQUAL; +extern UINT BPQMsg; + +extern int NUMBEROFTNCPORTS; + +extern APPLCALLS APPLCALLTABLE[]; + +// LOOPBACK PORT ROUTINES + +VOID LINKINIT(PEXTPORTDATA PORTVEC) +{ + WritetoConsoleLocal("Loopback\n"); +} + +VOID LINKTX(PEXTPORTDATA PORTVEC, PMESSAGE Buffer) +{ + // LOOP BACK TO SWITCH + struct _LINKTABLE * LINK; + + LINK = Buffer->Linkptr; + + if (LINK) + { + if (LINK->L2TIMER) + LINK->L2TIMER = LINK->L2TIME; + + Buffer->Linkptr = 0; // CLEAR FLAG FROM BUFFER + } + + C_Q_ADD(&PORTVEC->PORTCONTROL.PORTRX_Q, Buffer); +} + + +VOID LINKRX() +{ +} + + +VOID LINKTIMER() +{ +} + +VOID LINKCLOSE() +{ +} + + +VOID EXTCLOSE() +{ +} + +BOOL KISSTXCHECK() +{ + return 0; +} + +BOOL LINKTXCHECK() +{ + return 0; +} + +void * Dummy(int fn, int port, PDATAMESSAGE buff) // Dummy for missing EXT Driver +{ + return 0; +} + +VOID EXTINIT(PEXTPORTDATA PORTVEC) +{ + // LOAD DLL - NAME IS IN PORT_DLL_NAME + + void *(* Startup) (PEXTPORTDATA PORTVEC); // ADDR OF Startup ROUTINE + + PORTVEC->PORT_EXT_ADDR = Dummy; + + Startup = InitializeExtDriver(PORTVEC); + + if (Startup == 0) + { + WritetoConsoleLocal("Driver installation failed\n"); + return; + } + + +// CALL THE ROUTINE TO START IT UP + +// Startup returns address of processing routine + + PORTVEC->PORT_EXT_ADDR = (void *(__cdecl *)(int,int,PDATAMESSAGE))Startup(PORTVEC);; + + if (PORTVEC->PORT_EXT_ADDR == 0) + { + WritetoConsoleLocal("Driver Initialisation failed\n"); + return; + } + +} + +VOID EXTTX(PEXTPORTDATA PORTVEC, MESSAGE * Buffer) +{ + struct _LINKTABLE * LINK; + struct PORTCONTROL * PORT = (struct PORTCONTROL *)PORTVEC; + +// RESET TIMER, unless BAYCOM + + if (PORT->KISSFLAGS == 255) // Used for BAYCOM + { + PORTVEC->PORT_EXT_ADDR(2, PORT->PORTNUMBER, (PDATAMESSAGE)Buffer); + + return; // Baycom driver passes frames to trace once sent + } + + LINK = Buffer->Linkptr; + + if (LINK) + { + if (LINK->L2TIMER) + LINK->L2TIMER = LINK->L2TIME; + + if (PORT->TNC == 0 || PORT->TNC->Hardware != H_KISSHF) + Buffer->Linkptr = 0; // CLEAR FLAG FROM BUFFER + } + + PORTVEC->PORT_EXT_ADDR(2, PORT->PORTNUMBER, (PDATAMESSAGE)Buffer); + + if (PORT->PROTOCOL == 10 && PORT->TNC && PORT->TNC->Hardware != H_KISSHF) + { + ReleaseBuffer(Buffer); + return; + } + + C_Q_ADD(&TRACE_Q, Buffer); + + return; + +} + +VOID EXTRX(PEXTPORTDATA PORTVEC) +{ + struct _MESSAGE * Message; + size_t Len; + struct PORTCONTROL * PORT = (struct PORTCONTROL *)PORTVEC; + +Loop: + + if (QCOUNT < 10) + return; + + Message = GetBuff(); + + if (Message == NULL) + return; + + Len = (size_t)PORTVEC->PORT_EXT_ADDR(1, PORT->PORTNUMBER, (PDATAMESSAGE)Message); + + if (Len == 0) + { + ReleaseBuffer((UINT *)Message); + return; + } + + if (PORT->PROTOCOL == 10) + { + // PACTOR Style Port - Negative values used to report events - for now -1 = Disconnected + + if (Len == -1) + { + int Sessno = Message->PORT; + TRANSPORTENTRY * Session; + + ReleaseBuffer((UINT *)Message); + + // GET RID OF ANY SESSION ENTRIES + + Session = PORTVEC->ATTACHEDSESSIONS[Sessno]; + + if (Session) + { + struct TNCINFO * TNC = PORTVEC->PORTCONTROL.TNC; + + CloseSessionPartner(Session); + + // is this the place to run DisconnectScript? + + if (TNC->DisconnectScript) + { + int n = 0; + struct DATAMESSAGE * Buffer; + + TRANSPORTENTRY Session = {0}; // = TNC->PortRecord->ATTACHEDSESSIONS[Sessno]; + + while (TNC->DisconnectScript[n]) + { + Buffer = GetBuff(); + if (Buffer) + { + Session.Secure_Session = 1; + Session.CIRCUITINDEX = -1; + Buffer->LENGTH = sprintf(Buffer->L2DATA, "%s\r", TNC->DisconnectScript[n++]) + (sizeof(void *) + 4); + CommandHandler(&Session, Buffer); + }; + } + } + + PORTVEC->ATTACHEDSESSIONS[Sessno] = NULL; + } + return; + } + } + + C_Q_ADD(&PORT->PORTRX_Q, (UINT *)Message); + + goto Loop; + + return; +} + +VOID EXTTIMER(PEXTPORTDATA PORTVEC) +{ + // USED TO SEND A RE-INIT IN THE CORRECT PROCESS + + if (PORTVEC->EXTRESTART) + { + PORTVEC->EXTRESTART = 0; //CLEAR + PORTVEC->PORT_EXT_ADDR(4, PORTVEC->PORTCONTROL.PORTNUMBER, 0); + } + + PORTVEC->PORT_EXT_ADDR(7, PORTVEC->PORTCONTROL.PORTNUMBER, 0); // Timer Routine +} + +VOID EXTSLOWTIMER(PEXTPORTDATA PORTVEC) +{ + PORTVEC->PORT_EXT_ADDR(8, PORTVEC->PORTCONTROL.PORTNUMBER, 0); // Timer Routine +} + +size_t EXTTXCHECK(PEXTPORTDATA PORTVEC, int Chan) +{ + uintptr_t Temp = Chan; + + return (size_t)PORTVEC->PORT_EXT_ADDR(3, PORTVEC->PORTCONTROL.PORTNUMBER, (void *)Temp); +} + +VOID PostDataAvailable(TRANSPORTENTRY * Session) +{ +#ifndef LINBPQ + if (Session->L4CIRCUITTYPE & BPQHOST) + { + BPQVECSTRUC * HostSess = Session->L4TARGET.HOST; + + if (HostSess) + { + if (HostSess->HOSTHANDLE) + { + PostMessage(HostSess->HOSTHANDLE, BPQMsg, HostSess->HOSTSTREAM, 2); + } + } + } +#endif +} + +VOID PostStateChange(TRANSPORTENTRY * Session) +{ +#ifndef LINBPQ + if (Session->L4CIRCUITTYPE & BPQHOST) + { + BPQVECSTRUC * HostSess = Session->L4TARGET.HOST; + + if (HostSess) + { + if (HostSess->HOSTHANDLE); + { + PostMessage(HostSess->HOSTHANDLE, BPQMsg, HostSess->HOSTSTREAM, 4); + } + } + } +#endif +} + +#ifdef LINBPQ + +#define HDLCTX KHDLCTX +#define HDLCRX KHDLCRX +#define HDLCTIMER KHDLCTIMER +#define HDLCCLOSE KHDLCCLOSE +#define HDLCTXCHECK KHDLCTXCHECK + +#define PC120INIT KHDLCINIT +#define DRSIINIT KHDLCINIT +#define TOSHINIT KHDLCINIT +#define RLC100INIT KHDLCINIT +#define BAYCOMINIT KHDLCINIT +#define PA0INIT KHDLCINIT + +int KHDLCINIT(PHDLCDATA PORTVEC); +void KHDLCTX(struct KISSINFO * KISS, PMESSAGE Buffer); +int KHDLCRX(PHDLCDATA PORTVEC); +void KHDLCTIMER(PHDLCDATA PORTVEC); +void KHDLCCLOSE(PHDLCDATA PORTVEC); +BOOL KHDLCTXCHECK(); + + +#else + +extern VOID PC120INIT(), DRSIINIT(), TOSHINIT(); +extern VOID RLC100INIT(), BAYCOMINIT(), PA0INIT(); + +extern VOID HDLCTX(); +extern VOID HDLCRX(); +extern VOID HDLCTIMER(); +extern VOID HDLCCLOSE(); +extern VOID HDLCTXCHECK(); + +#endif + +extern VOID KISSINIT(), KISSTX(), KISSRX(), KISSTIMER(), KISSCLOSE(); +extern VOID EXTINIT(PEXTPORTDATA PORTVEC), EXTTX(PEXTPORTDATA PORTVEC, MESSAGE * Buffer), LINKRX(), EXTRX(PEXTPORTDATA PORTVEC); +extern VOID LINKCLOSE(), EXTCLOSE() ,LINKTIMER(), EXTTIMER(PEXTPORTDATA PORTVEC); + +// VECTORS TO HARDWARE DEPENDENT ROUTINES + +VOID * INITCODE[12] = {KISSINIT, PC120INIT, DRSIINIT, TOSHINIT, KISSINIT, +RLC100INIT, RLC100INIT, LINKINIT, EXTINIT, BAYCOMINIT, PA0INIT, KISSINIT}; + +VOID * TXCODE[12] = {KISSTX, HDLCTX, HDLCTX, HDLCTX, KISSTX, + HDLCTX, HDLCTX, LINKTX, EXTTX, HDLCTX, HDLCTX, KISSTX}; + +VOID * RXCODE[12] = {KISSRX, HDLCRX, HDLCRX, HDLCRX, KISSRX, + HDLCRX, HDLCRX, LINKRX, EXTRX, HDLCRX, HDLCRX, KISSRX}; + +VOID * TIMERCODE[12] = {KISSTIMER, HDLCTIMER, HDLCTIMER, HDLCTIMER, KISSTIMER, + HDLCTIMER, HDLCTIMER, LINKTIMER, EXTTIMER, HDLCTIMER, HDLCTIMER, KISSTIMER}; + +VOID * CLOSECODE[12] = {KISSCLOSE, HDLCCLOSE, HDLCCLOSE, HDLCCLOSE, KISSCLOSE, + HDLCCLOSE, HDLCCLOSE, LINKCLOSE, EXTCLOSE, HDLCCLOSE, HDLCCLOSE, KISSCLOSE}; + +VOID * TXCHECKCODE[12] = {KISSTXCHECK, HDLCTXCHECK, HDLCTXCHECK, HDLCTXCHECK, KISSTXCHECK, + HDLCTXCHECK, HDLCTXCHECK, LINKTXCHECK, EXTTXCHECK, HDLCTXCHECK, HDLCTXCHECK, KISSTXCHECK}; + + +extern int BACKGROUND(); +extern int L2TimerProc(); +extern int L3TimerProc(); +extern int L4TimerProc(); +extern int L3FastTimer(); +extern int StatsTimer(); +extern int COMMANDHANDLER(); +VOID SDETX(struct _LINKTABLE * LINK); +extern int L4BG(); +extern int L3BG(); +extern int TNCTimerProc(); +extern int PROCESSIFRAME(); + +int xxxxx = MAXDATA; + +BOOL Start() +{ + struct CONFIGTABLE * cfg = &xxcfg; + struct APPLCONFIG * ptr1; + struct PORTCONTROL * PORT; + struct FULLPORTDATA * FULLPORT; // Including HW Data + struct FULLPORTDATA * NEXTPORT; // Including HW Data + struct _EXTPORTDATA * EXTPORT; + APPLCALLS * APPL; + struct ROUTE * ROUTE; + struct DEST_LIST * DEST; + struct CMDX * CMD; + int PortSlot = 1; + uintptr_t int3; + + unsigned char * ptr2 = 0, * ptr3, * ptr4; + USHORT * CWPTR; + int i, n; + + struct ROUTECONFIG * Rcfg; + + NEXTFREEDATA = &DATAAREA[0]; // For Reinit + + memset(DATAAREA, 0, DATABYTES); + + // Reinit everything in case of restart + + FREE_Q = 0; + TRACE_Q = 0; + IDMSG_Q = 0; + NUMBEROFPORTS = 0; + MAXBUFFS = 0; + QCOUNT = 0; + NUMBEROFNODES = 0; + DESTHEADER = 0; + NODESINPROGRESS = 0; + CURRENTNODE = 0; + L3TIMER = 1; // SEND NODES + + if (cfg->C_NODEALIAS[0] == 0) + memset(cfg->C_NODEALIAS, ' ', 10); + + TimeLoaded = time(NULL); + + AUTOSAVE = cfg->C_AUTOSAVE; + + if (cfg->C_L4APPL) + L4APPL = cfg->C_L4APPL; + + CFLAG = cfg->C_C; + + IPRequired = cfg->C_IP; + PMRequired = cfg->C_PM; + + if (cfg->C_MAXHOPS) + MaxHops = cfg->C_MAXHOPS; + + if (cfg->C_MAXRTT) + MAXRTT = cfg->C_MAXRTT * 100; + + if (cfg->C_NODE == 0 && cfg->C_BBS) + { + // USE BBS CALL FOR NODE if Set, otherwise find first APPLCALL + // Unless BBS also = 0 + + if (cfg->C_BBSCALL[0]) + { + memcpy(MYNODECALL, cfg->C_BBSCALL, 10); + memcpy(MYALIASTEXT, cfg->C_BBSALIAS, 6); + memcpy(MYALIASLOPPED, cfg->C_BBSALIAS, 10); + } + else + { + ptr1 = &cfg->C_APPL[0]; + + for (i = 0; i < NumberofAppls; i++) + { + if (ptr1->ApplCall[0] != ' ') + { + memcpy(MYNODECALL, &ptr1->ApplCall[0], 10); + memcpy(MYALIASTEXT, &ptr1->ApplAlias, 6); + memcpy(MYALIASLOPPED, &ptr1->ApplAlias, 10); + + break; + } + ptr1++; + } + } + + } + else + { + memcpy(MYNODECALL, cfg->C_NODECALL, 10); + memcpy(MYALIASTEXT, cfg->C_NODEALIAS, 6); + memcpy(MYALIASLOPPED, cfg->C_NODEALIAS, 10); + } + + strlop(MYALIASLOPPED, ' '); + + + // IF NO BBS, SET BOTH TO _NODE CALLSIGN + + if (cfg->C_BBS == 0) + { + memcpy(APPLCALLTABLE[0].APPLCALL_TEXT, cfg->C_NODECALL, 10); + memcpy(APPLCALLTABLE[0].APPLALIAS_TEXT, cfg->C_NODEALIAS, 10); + } + else + { + memcpy(APPLCALLTABLE[0].APPLCALL_TEXT, cfg->C_BBSCALL, 10); + memcpy(APPLCALLTABLE[0].APPLALIAS_TEXT, cfg->C_BBSALIAS, 10 ); + } + + BBSQUAL = cfg->C_BBSQUAL; + + // copy MYCALL to NETROMCALL + + memcpy(MYNETROMCALL, MYNODECALL, 10); + + // if NETROMCALL Defined, use it + + if (cfg->C_NETROMCALL[0] && cfg->C_NETROMCALL[0] != ' ') + memcpy(MYNETROMCALL, cfg->C_NETROMCALL, 10); + + strlop(MYNETROMCALL, ' '); + strlop(MYNODECALL, ' '); + + memcpy(NODECALLLOPPED, MYNODECALL, 10); + strlop(NODECALLLOPPED, ' '); + + APPLCALLTABLE[0].APPLQUAL = BBSQUAL; + + if (cfg->C_WASUNPROTO == 0 && cfg->C_BTEXT) + { + char * ptr1 = &cfg->C_BTEXT[0]; + char * ptr2 = BTHDDR.L2DATA; + int len = 120; + + BTHDDR.LENGTH = 1; // PID + + while ((*ptr1) && len--) + { + *(ptr2++) = *(ptr1++); + BTHDDR.LENGTH ++; + } + + } + + OBSINIT = cfg->C_OBSINIT; + OBSMIN = cfg->C_OBSMIN; + L3INTERVAL = cfg->C_NODESINTERVAL; + IDINTERVAL = cfg->C_IDINTERVAL; + if (IDINTERVAL) + IDTIMER = 2; + + BTINTERVAL = cfg->C_BTINTERVAL; + if (BTINTERVAL) + BTTIMER = 2; + + + MINQUAL = cfg->C_MINQUAL; + FULL_CTEXT = cfg->C_FULLCTEXT; + L3LIVES = cfg->C_L3TIMETOLIVE; + L4N2 = cfg->C_L4RETRIES; + L4DEFAULTWINDOW = cfg->C_L4WINDOW; + L4T1 = cfg->C_L4TIMEOUT; + +// MOV AX,C_BUFFERS +// MOV NUMBEROFBUFFERS,AX + + PACLEN = cfg->C_PACLEN; + T3 = cfg->C_T3 * 3; + L4LIMIT = cfg->C_IDLETIME; + if (L4LIMIT && L4LIMIT < 120) + L4LIMIT = 120; // Don't allow stupidly low + L2KILLTIME = L4LIMIT * 3; + L4DELAY = cfg->C_L4DELAY; + BBS = cfg->C_BBS; + NODE = cfg->C_NODE; + LINKEDFLAG = cfg->C_LINKEDFLAG; + MAXLINKS = cfg->C_MAXLINKS; + MAXDESTS = cfg->C_MAXDESTS; + MAXNEIGHBOURS = cfg->C_MAXNEIGHBOURS; + MAXCIRCUITS = cfg->C_MAXCIRCUITS; + HIDENODES = cfg->C_HIDENODES; + + LogL4Connects = cfg->C_LogL4Connects; + LogAllConnects = cfg->C_LogAllConnects; + AUTOSAVEMH = cfg->C_SaveMH; + ADIFLogEnabled = cfg->C_ADIF; + EventsEnabled = cfg->C_EVENTS; + SaveAPRSMsgs = cfg->C_SaveAPRSMsgs; + M0LTEMap = cfg->C_M0LTEMap; + MQTT = cfg->C_MQTT; + strcpy(MQTT_HOST, cfg->C_MQTT_HOST); + MQTT_PORT = cfg->C_MQTT_PORT; + strcpy(MQTT_USER, cfg->C_MQTT_USER); + strcpy(MQTT_PASS, cfg->C_MQTT_PASS); + + // Get pointers to PASSWORD and APPL1 commands + +// int APPL1 = 0; +//int PASSCMD = 0; + + CMD = &COMMANDS[0]; + n = 0; + + for (n = 0; n < NUMBEROFCOMMANDS; n++) + { + if (APPL1 == 0 && CMD->String[0] == '*') // First appl + { + APPLS = (char *)CMD; + APPL1 = n; + } + + if (PASSCMD == 0 && memcmp(CMD->String, "PASSWORD", 8) == 0) + PASSCMD = n; + + CMD++; + } + + +// SET UP APPLICATION LIST + + memset(&CMDALIAS[0][0], ' ', NumberofAppls * ALIASLEN ); + + ptr1 = (struct APPLCONFIG *)&xxcfg.C_APPL[0]; + ptr3 = &CMDALIAS[0][0]; + + for (i = 0; i < NumberofAppls; i++) + { + if (ptr1->Command[0] != ' ') + { + ptr2 = (char *)&COMMANDS[APPL1 + i]; + + memcpy(ptr2, ptr1, 12); + + // See if an Alias + + if (ptr1->CommandAlias[0] != ' ') + memcpy(ptr3, ptr1->CommandAlias, ALIASLEN); + + // SET LENGTH FIELD + + *(ptr2 + 12) = 0; // LENGTH + ptr4 = ptr2; + + while (*(ptr4) > 32) + { + ptr4++; + *(ptr2 + 12) = *(ptr2 + 12) + 1; + } + } + ptr1 ++; + ptr2 += CMDXLEN; + ptr3 += ALIASLEN; + } + + // Set up Exclude List + + memcpy(ExcludeList, cfg->C_EXCLUDE, 71); + + // SET UP PORT TABLE + + PortRec = &cfg->C_PORT[0];// (struct PORTCONFIG *)ptr2; + + PORTTABLE = (VOID *)NEXTFREEDATA; + FULLPORT = (struct FULLPORTDATA *)PORTTABLE; + + while (PortRec->PORTNUM) + { + // SET UP NEXT PORT PTR + + PORT = &FULLPORT->PORTCONTROL; + NEXTPORT = FULLPORT; + NEXTPORT++; + PORT->PORTPOINTER = (struct PORTCONTROL *)NEXTPORT; + + PORT->PORTNUMBER = (UCHAR)PortRec->PORTNUM; + PORT->PortSlot = PortSlot++; + memcpy(PORT->PORTDESCRIPTION, PortRec->ID, 30); + + PORT->PORTTYPE = (char)PortRec->TYPE; + + PORT->PORTINITCODE = INITCODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + PORT->PORTTXROUTINE = TXCODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + PORT->PORTRXROUTINE = RXCODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + PORT->PORTTIMERCODE = TIMERCODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + PORT->PORTCLOSECODE = CLOSECODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + PORT->PORTTXCHECKCODE = TXCHECKCODE[PORT->PORTTYPE / 2]; // ADDR OF INIT ROUTINE + + + PORT->PROTOCOL = (char)PortRec->PROTOCOL; + PORT->IOBASE = PortRec->IOADDR; + + if (PortRec->SerialPortName && PortRec->SerialPortName[0]) + PORT->SerialPortName = _strdup(PortRec->SerialPortName); + else + { + if (PORT->IOBASE > 0 && PORT->IOBASE < 256) + { + char Name[80]; +#ifndef WIN32 + sprintf(Name, "com%d", PORT->IOBASE); +#else + sprintf(Name, "COM%d", PORT->IOBASE); +#endif + PORT->SerialPortName = _strdup(Name); + } + else + PORT->SerialPortName = _strdup("NOPORT"); + + } + PORT->INTLEVEL = (char)PortRec->INTLEVEL; + PORT->BAUDRATE = PortRec->SPEED; + + if (PORT->BAUDRATE == 49664) + PORT->BAUDRATE = (int)115200; + + PORT->CHANNELNUM = (char)PortRec->CHANNEL; + PORT->PORTQUALITY = (UCHAR)PortRec->QUALITY; + PORT->NormalizeQuality = !PortRec->NoNormalize; + PORT->IgnoreUnlocked = PortRec->IGNOREUNLOCKED; + PORT->INP3ONLY = PortRec->INP3ONLY; + + PORT->PORTWINDOW = (UCHAR)PortRec->MAXFRAME; + + if (PortRec->PROTOCOL == 0 || PORT->PORTTYPE == 22) // KISS or I2C + PORT->PORTTXDELAY = PortRec->TXDELAY /10; + else + PORT->PORTTXDELAY = PortRec->TXDELAY; + + if (PortRec->PROTOCOL == 0 || PORT->PORTTYPE == 22) // KISS or I2C + PORT->PORTSLOTTIME = (UCHAR)PortRec->SLOTTIME / 10; + else + PORT->PORTSLOTTIME = (UCHAR)PortRec->SLOTTIME; + + PORT->PORTPERSISTANCE = (UCHAR)PortRec->PERSIST; + PORT->FULLDUPLEX = (UCHAR)PortRec->FULLDUP; + + PORT->SOFTDCDFLAG = (UCHAR)PortRec->SOFTDCD; + PORT->PORTT1 = PortRec->FRACK / 333; + PORT->PORTT2 = PortRec->RESPTIME /333; + PORT->PORTN2 = (UCHAR)PortRec->RETRIES; + + PORT->PORTPACLEN = (UCHAR)PortRec->PACLEN; + PORT->QUAL_ADJUST = (UCHAR)PortRec->QUALADJUST; + + PORT->DIGIFLAG = PortRec->DIGIFLAG; + if (PortRec->DIGIPORT && CanPortDigi(PortRec->DIGIPORT)) + PORT->DIGIPORT = PortRec->DIGIPORT; + PORT->DIGIMASK = PortRec->DIGIMASK; + PORT->USERS = (UCHAR)PortRec->USERS; + + // PORTTAILTIME - if KISS, set a default, and cnvert to ticks + + if (PORT->PORTTYPE == 0) + { + if (PortRec->TXTAIL) + PORT->PORTTAILTIME = PortRec->TXTAIL / 10; + else + PORT->PORTTAILTIME = 3; // 10ths + } + else + + //; ON HDLC, TAIL TIMER IS USED TO HOLD RTS FOR 'CONTROLLED FULL DUP' - Val in seconds + + PORT->PORTTAILTIME = (UCHAR)PortRec->TXTAIL; + + PORT->PORTBBSFLAG = (char)PortRec->ALIAS_IS_BBS; + PORT->PORTL3FLAG = (char)PortRec->L3ONLY; + PORT->KISSFLAGS = PortRec->KISSOPTIONS; + PORT->PORTINTERLOCK = (UCHAR)PortRec->INTERLOCK; + PORT->NODESPACLEN = (UCHAR)PortRec->NODESPACLEN; + PORT->TXPORT = (UCHAR)PortRec->TXPORT; + + PORT->PORTMINQUAL = PortRec->MINQUAL; + + if (PortRec->MAXDIGIS) + PORT->PORTMAXDIGIS = PortRec->MAXDIGIS; + else + PORT->PORTMAXDIGIS = 8; + + PORT->PortNoKeepAlive = PortRec->DefaultNoKeepAlives; + PORT->PortUIONLY = PortRec->UIONLY; + PORT->TXPORT = (UCHAR)PortRec->TXPORT; + + // SET UP CWID + + if (PortRec->CWIDTYPE == 'o') + PORT->CWTYPE = 'O'; + else + PORT->CWTYPE = PortRec->CWIDTYPE; + + ptr2 = &PortRec->CWID[0]; + CWPTR = &PORT->CWID[0]; + + PORT->CWIDTIMER = (29 - PORT->PORTNUMBER) * 600; // TICKSPERMINUTE + PORT->CWPOINTER = &PORT->CWID[0]; + + for (i = 0; i < 8; i++) // MAX ID LENGTH + { + char c = *(ptr2++); + if (c < 32) + break; + if (c > 'Z') + continue; + + c -= '/'; // Table stats at / + c &= 127; + + *(CWPTR) = CWTABLE[c]; + CWPTR++; + } + + // SEE IF LINK CALLSIGN/ALIAS SPECIFIED + + if (PortRec->PORTCALL[0]) + ConvToAX25(PortRec->PORTCALL, PORT->PORTCALL); + + if (PortRec->PORTALIAS[0]) + ConvToAX25(PortRec->PORTALIAS, PORT->PORTALIAS); + + if (PortRec->PORTALIAS2[0]) + ConvToAX25(PortRec->PORTALIAS2, PORT->PORTALIAS2); + + if (PortRec->DLLNAME[0]) + { + EXTPORT = (struct _EXTPORTDATA *)PORT; + memcpy(EXTPORT->PORT_DLL_NAME, PortRec->DLLNAME, 16); + } + if (PortRec->BCALL[0]) + ConvToAX25(PortRec->BCALL, PORT->PORTBCALL); + + PORT->XDIGIS = PortRec->XDIGIS; // Crossband digi aliases + + memcpy(&PORT->PORTIPADDR, &PortRec->IPADDR, 4); + PORT->ListenPort = PortRec->ListenPort; + + if (PortRec->TCPPORT) + { + PORT->KISSTCP = TRUE; + PORT->IOBASE = PortRec->TCPPORT; + if (PortRec->IPADDR == 0) + PORT->KISSSLAVE = TRUE; + } + + if (PortRec->WL2K) + memcpy(&PORT->WL2KInfo, PortRec->WL2K, sizeof(struct WL2KInfo)); + + PORT->RIGPort = PortRec->RIGPORT; + + if (PortRec->HavePermittedAppls) + PORT->PERMITTEDAPPLS = PortRec->PERMITTEDAPPLS; + else + PORT->PERMITTEDAPPLS = 0xffffffff; // Default to all + + PORT->Hide = PortRec->Hide; + + PORT->SmartIDInterval = PortRec->SmartID; + + if (PortRec->KissParams && (PORT->PORTTYPE == 0 || PORT->PORTTYPE == 22)) + { + struct KISSINFO * KISS = (struct KISSINFO *)PORT; + UCHAR KissString[128]; + int KissLen = 0; + unsigned char * Kissptr = KissString; + char * ptr; + char * Context; + + ptr = strtok_s(PortRec->KissParams, " ", &Context); + + while (ptr && ptr[0] && KissLen < 120) + { + *(Kissptr++) = atoi (ptr); + KissLen++; + ptr = strtok_s(NULL, " ", &Context); + } + + KISS->KISSCMD = malloc(256); + + KISS->KISSCMDLEN = KissEncode(KissString, KISS->KISSCMD, KissLen); + KISS->KISSCMD = realloc(KISS->KISSCMD, KISS->KISSCMDLEN); + } + + PORT->SendtoM0LTEMap = PortRec->SendtoM0LTEMap; + PORT->PortFreq = PortRec->PortFreq; + + PORT->M0LTEMapInfo = PortRec->M0LTEMapInfo; + + PORT->QtSMPort = PortRec->QtSMPort; + + if (PortRec->BBSFLAG) // Appl 1 not permitted - BBSFLAG=NOBBS + PORT->PERMITTEDAPPLS &= 0xfffffffe; // Clear bottom bit + + + // SEE IF PERMITTED LINK CALLSIGNS SPECIFIED + + ptr2 = &PortRec->VALIDCALLS[0]; + + if (*(ptr2)) + { + ptr3 = (char *)PORT->PORTPOINTER; // Permitted Calls follows Port Info + PORT->PERMITTEDCALLS = ptr3; + + while (*(ptr2) > 32) + { + ConvToAX25(ptr2, ptr3); + ptr3 += 7; + PORT->PORTPOINTER = (struct PORTCONTROL *)ptr3; + if (strchr(ptr2, ',')) + { + ptr2 = strchr(ptr2, ','); + ptr2++; + } + else + break; + } + + ptr3 ++; // Terminating NULL + + // Round to word boundary (for ARM5 etc) + + int3 = (uintptr_t)ptr3; + int3 += 7; + int3 &= 0xfffffffffffffff8; + ptr3 = (UCHAR *)int3; + + PORT->PORTPOINTER = (struct PORTCONTROL *)ptr3; + } + + // SEE IF PORT UNPROTO ADDR SPECIFIED + + ptr2 = &PortRec->UNPROTO[0]; + + if (*(ptr2)) + { + ptr3 = (char *)PORT->PORTPOINTER; // Unproto follows port info + PORT->PORTUNPROTO = ptr3; + + while (*(ptr2) > 32) + { + ConvToAX25(ptr2, ptr3); + ptr3 += 7; + PORT->PORTPOINTER = (struct PORTCONTROL *)ptr3; + if (strchr(ptr2, ',')) + { + ptr2 = strchr(ptr2, ','); + ptr2++; + } + else + break; + } + + ptr3 ++; // Terminating NULL + + // Round to word boundsaty (for ARM5 etc) + + int3 = (uintptr_t)ptr3; + int3 += 7; + int3 &= 0xfffffffffffffff8; + ptr3 = (UCHAR *)int3; + + PORT->PORTPOINTER = (struct PORTCONTROL *)ptr3; + } + + // ADD MH AREA IF NEEDED + + if (PortRec->MHEARD != 'N') + { + NEEDMH = 1; // Include MH in Command List + + ptr3 = (char *)PORT->PORTPOINTER; // Permitted Calls follows Port Info + PORT->PORTMHEARD = (PMHSTRUC)ptr3; + + ptr3 += (MHENTRIES + 1)* sizeof(MHSTRUC); + + // Round to word boundsaty (for ARM5 etc) + + int3 = (uintptr_t)ptr3; + int3 += 7; + int3 &= 0xfffffffffffffff8; + ptr3 = (UCHAR *)int3; + + PORT->PORTPOINTER = (struct PORTCONTROL *)ptr3; + } + + PortRec++; + NUMBEROFPORTS ++; + FULLPORT = (struct FULLPORTDATA *)PORT->PORTPOINTER; + } + + PORT->PORTPOINTER = NULL; // End of list + + NEXTFREEDATA = (UCHAR *)FULLPORT; + + // SET UP APPLICATION CALLS AND ALIASES + + APPL = &APPLCALLTABLE[0]; + + ptr1 = (struct APPLCONFIG *)&xxcfg.C_APPL[0]; + + i = NumberofAppls; + + if (ptr1->ApplCall[0] == ' ') + { + // APPL1CALL IS NOT SPECIFED - LEAVE VALUES SET FROM BBSCALL + + APPL++; + ptr1++; + i--; + } + + while (i) + { + memcpy(APPL->APPLCALL_TEXT, ptr1->ApplCall, 10); + ConvToAX25(APPL->APPLCALL_TEXT, APPL->APPLCALL); + memcpy(APPL->APPLALIAS_TEXT, ptr1->ApplAlias, 10); + ConvToAX25(APPL->APPLALIAS_TEXT, APPL->APPLALIAS); + ConvToAX25(ptr1->L2Alias, APPL->L2ALIAS); + memcpy(APPL->APPLCMD, ptr1->Command, 12); + + APPL->APPLQUAL = ptr1->ApplQual; + + if (ptr1->CommandAlias[0] != ' ') + { + APPL->APPLHASALIAS = 1; + memcpy(APPL->APPLALIASVAL, &ptr1->CommandAlias[0], 48); + } + + APPL++; + ptr1++; + i--; + } + + // SET UP VARIOUS CONTROL TABLES + + LINKS = (VOID *)NEXTFREEDATA; + NEXTFREEDATA += MAXLINKS * sizeof(struct _LINKTABLE); + + DESTS = (VOID *)NEXTFREEDATA; + NEXTFREEDATA += MAXDESTS * sizeof(struct DEST_LIST); + ENDDESTLIST = (VOID *)NEXTFREEDATA; + + NEIGHBOURS = (VOID *)NEXTFREEDATA; + NEXTFREEDATA += MAXNEIGHBOURS * sizeof(struct ROUTE); + + L4TABLE = (VOID *)NEXTFREEDATA; + + NEXTFREEDATA += MAXCIRCUITS * sizeof(TRANSPORTENTRY); + + // SET UP DEFAULT ROUTES LIST + + Rcfg = &cfg->C_ROUTE[0]; + + ROUTE = NEIGHBOURS; + + while (Rcfg->call[0]) + { + int FRACK; + char * VIA; + char axcall[8]; + + ConvToAX25(Rcfg->call, ROUTE->NEIGHBOUR_CALL); + + // if VIA convert digis + + VIA = strstr(Rcfg->call, "VIA"); + + if (VIA) + { + VIA += 4; + + if (ConvToAX25(VIA, axcall)) + { + memcpy(ROUTE->NEIGHBOUR_DIGI1, axcall, 7); + + VIA = strchr(VIA, ' '); + + if (VIA) + { + VIA++; + + if (ConvToAX25(VIA, axcall)) + memcpy(ROUTE->NEIGHBOUR_DIGI2, axcall, 7); + + } + } + } + + ROUTE->NEIGHBOUR_QUAL = Rcfg->quality; + ROUTE->NEIGHBOUR_PORT = Rcfg->port; + + PORT = GetPortTableEntryFromPortNum(ROUTE->NEIGHBOUR_PORT); + + if (Rcfg->pwind & 0x40) + ROUTE->NoKeepAlive = 1; + else + if (PORT != NULL) + ROUTE->NoKeepAlive = PORT->PortNoKeepAlive; + + if (Rcfg->pwind & 0x80 || (PORT && PORT->INP3ONLY)) + { + ROUTE->INP3Node = 1; + ROUTE->NoKeepAlive = 0; // Cant have INP3 and NOKEEPALIVES + } + + ROUTE->NBOUR_MAXFRAME = Rcfg->pwind & 0x3f; + + FRACK = Rcfg->pfrack; + ROUTE->NBOUR_FRACK = FRACK / 333; + ROUTE->NBOUR_PACLEN = Rcfg->ppacl; + ROUTE->OtherendsRouteQual = ROUTE->OtherendLocked = Rcfg->farQual; + + ROUTE->NEIGHBOUR_FLAG = 1; // Locked + + Rcfg++; + ROUTE++; + } + + // SET UP INFO MESSAGE + + ptr2 = &cfg->C_INFOMSG[0]; + ptr3 = NEXTFREEDATA; + + INFOMSG = ptr3; + + while ((*ptr2)) + { + *(ptr3++) = *(ptr2++); + } + *ptr3++ = 0; // Null Terminate + + NEXTFREEDATA = ptr3; + + // SET UP CTEXT MESSAGE + + ptr2 = &cfg->C_CTEXT[0]; + ptr3 = NEXTFREEDATA; + + CTEXTMSG = ptr3; + + while ((*ptr2)) + { + *(ptr3++) = *(ptr2++); + } + + CTEXTLEN = (int)(ptr3 - (unsigned char *)CTEXTMSG); + + NEXTFREEDATA = ptr3; + + // SET UP ID MESSAGE + + IDHDDR.DEST[0] = 'I'+'I'; + IDHDDR.DEST[1] = 'D'+'D'; + IDHDDR.DEST[2] = 0x40; + IDHDDR.DEST[3] = 0x40; + IDHDDR.DEST[4] = 0x40; + IDHDDR.DEST[5] = 0x40; + IDHDDR.DEST[6] = 0xe0; // ; ID IN AX25 FORM + + IDHDDR.CTL = 3; + IDHDDR.PID = 0xf0; + + ptr2 = &cfg->C_IDMSG[0]; + ptr3 = &IDHDDR.L2DATA[0]; + + while ((*ptr2)) + { + *(ptr3++) = *(ptr2++); + } + + IDHDDR.LENGTH = (int)(ptr3 - (unsigned char *)&IDHDDR); + + int3 = (uintptr_t)NEXTFREEDATA; + int3 += 7; + int3 &= 0xfffffffffffffff8; + NEXTFREEDATA = (UCHAR *)int3; + ENDBUFFERPOOL = NEXTFREEDATA + DATABYTES; // So init will work, set to actual end later + + BUFFERPOOL = NEXTFREEDATA; + + Consoleprintf("PORTS %p LINKS %p DESTS %p ROUTES %p L4 %p BUFFERS %p\n", + PORTTABLE, LINKS, DESTS, NEIGHBOURS, L4TABLE, BUFFERPOOL); + + Debugprintf("PORTS %p LINKS %p DESTS %p ROUTES %p L4 %p BUFFERS %p END POOL %p", + PORTTABLE, LINKS, DESTS, NEIGHBOURS, L4TABLE, BUFFERPOOL, DATAAREA + DATABYTES); + + i = NUMBEROFBUFFERS; + + NUMBEROFBUFFERS = 0; + + while (i-- && NEXTFREEDATA < (DATAAREA + DATABYTES - (512 + 8192))) // Keep 8K free for anything that needs shared memory + { + Bufferlist[NUMBEROFBUFFERS] = (void **)NEXTFREEDATA; + + ReleaseBuffer((UINT *)NEXTFREEDATA); + NEXTFREEDATA += BUFFALLOC; // was BUFFLEN + + NUMBEROFBUFFERS++; + MAXBUFFS++; + } + + ENDBUFFERPOOL = NEXTFREEDATA; + + + // Copy Bridge Map + + memcpy(BridgeMap, &cfg->CfgBridgeMap, sizeof(BridgeMap)); + +// MOV EAX,_NEXTFREEDATA +// CALL HEXOUT + + // SET UP OUR CALLIGN(S) + + ConvToAX25(MYNETROMCALL, NETROMCALL); + + ConvToAX25(MYNODECALL, MYCALL); + memcpy(&IDHDDR.ORIGIN[0], MYCALL, 7); + IDHDDR.ORIGIN[6] |= 0x61; // SET CMD END AND RESERVED BITS + + ConvToAX25(MYALIASTEXT, MYALIAS); + + // SET UP INITIAL DEST ENTRY FOR APPLICATIONS (IF BOTH NODE AND BBS NEEDED) + // Actually wrong - we need to set up application node entries even if node = 0 + DEST = DESTS; + + // If NODECALL isn't same as NETROMCALL, Add Dest Entry for NODECALL + + if (memcmp(NETROMCALL, MYCALL, 7) != 0) + { + memcpy(DEST->DEST_CALL, MYCALL, 7); + memcpy(DEST->DEST_ALIAS, MYALIASTEXT, 6); + + DEST->DEST_STATE = 0x80; // SPECIAL ENTRY + DEST->NRROUTE[0].ROUT_QUALITY = 255; + DEST->NRROUTE[0].ROUT_OBSCOUNT = 255; + DEST++; + NUMBEROFNODES++; + } + + if (BBS) + { + // Add Application Entries + + APPL = &APPLCALLTABLE[0]; + i = NumberofAppls; + + while (i--) + { + if (APPL->APPLQUAL) + { + memcpy(DEST->DEST_CALL, APPL->APPLCALL, 13); + DEST->DEST_STATE = 0x80; // SPECIAL ENTRY + DEST->NRROUTE[0].ROUT_QUALITY = (UCHAR)APPL->APPLQUAL; + DEST->NRROUTE[0].ROUT_OBSCOUNT = 255; + APPL->NODEPOINTER = DEST; + + DEST++; + + NUMBEROFNODES++; + } + APPL++; + } + } + + // Read Node and MH Recovery Files + + ReadNodes(); + + if (AUTOSAVEMH) + ReadMH(); // Only if AutoSave configured + + // set up stream number in BPQHOSTVECTOR + + for (i = 0; i < 64; i++) + { + BPQHOSTVECTOR[i].HOSTSTREAM = i + 1; + } + + memcpy(MYCALLWITHALIAS, MYCALL, 7); + memcpy(&MYCALLWITHALIAS[7], MYALIASTEXT, 6); + + // Set random start value for NETROM Session ID + + NEXTID = (rand() % 254) + 1; + + GetPortCTEXT(0, 0, 0, 0); + + upnpInit(); + + lastSaveSecs = CurrentSecs = lastSlowSecs = time(NULL); + + return 0; +} + +BOOL CompareCalls(UCHAR * c1, UCHAR * c2) +{ + // COMPARE AX25 CALLSIGNS IGNORING EXTRA BITS IN SSID + + if (memcmp(c1, c2, 6)) + return FALSE; // No Match + + if ((c1[6] & 0x1e) == (c2[6] & 0x1e)) + return TRUE; + + return FALSE; +} +BOOL CompareAliases(UCHAR * c1, UCHAR * c2) +{ + // COMPARE first 6 chars of AX25 CALLSIGNS + + if (memcmp(c1, c2, 6)) + return FALSE; // No Match + + return TRUE; +} +BOOL FindNeighbour(UCHAR * Call, int Port, struct ROUTE ** REQROUTE) +{ + struct ROUTE * ROUTE = NEIGHBOURS; + struct ROUTE * FIRSTSPARE = NULL; + int n = MAXNEIGHBOURS; + + while (n--) + { + if (ROUTE->NEIGHBOUR_CALL[0] == 0) // Spare + if (FIRSTSPARE == NULL) + FIRSTSPARE = ROUTE; + + if (ROUTE->NEIGHBOUR_PORT != Port) + { + ROUTE++; + continue; + } + if (CompareCalls(ROUTE->NEIGHBOUR_CALL, Call)) + { + *REQROUTE = ROUTE; + return TRUE; + } + ROUTE++; + } + + // ENTRY NOT FOUND - FIRSTSPARE HAS FIRST FREE ENTRY, OR ZERO IF TABLE FULL + + *REQROUTE = FIRSTSPARE; + return FALSE; +} + +BOOL FindDestination(UCHAR * Call, struct DEST_LIST ** REQDEST) +{ + struct DEST_LIST * DEST = DESTS; + struct DEST_LIST * FIRSTSPARE = NULL; + int n = MAXDESTS; + + while (n--) + { + if (DEST->DEST_CALL[0] == 0) // Spare + { + if (FIRSTSPARE == NULL) + FIRSTSPARE = DEST; + + DEST++; + continue; + } + if (CompareCalls(DEST->DEST_CALL, Call)) + { + *REQDEST = DEST; + return TRUE; + } + DEST++; + } + + // ENTRY NOT FOUND - FIRSTSPARE HAS FIRST FREE ENTRY, OR ZERO IF TABLE FULL + + *REQDEST = FIRSTSPARE; + return FALSE; +} + +extern UCHAR BPQDirectory[]; + +#define LINE_MAX 256 + + +// Reload saved MH file + +VOID ReadMH() +{ + char FN[260]; + FILE *fp; + char line[LINE_MAX]; + UCHAR axcall[7]; + char * ptr, *digi, *locptr, *locend; + char * Context, * Context2; + char seps[] = " \n"; + int Port; + struct PORTCONTROL * PORT = NULL; + MHSTRUC * MH; + int count = MHENTRIES; + char * Digiptr; + BOOL Digiused; + char * HasStar; + + // Set up pointer to BPQNODES file + + if (BPQDirectory[0] == 0) + { + strcpy(FN,"MHSave.txt"); + } + else + { + strcpy(FN,BPQDirectory); + strcat(FN,"/"); + strcat(FN,"MHSave.txt"); + } + + if ((fp = fopen(FN, "r")) == NULL) + { + return; + } + + while (fgets(line, LINE_MAX, fp) != NULL) + { + if (memcmp(line, "Port:", 5) == 0) + { + Port = atoi(&line[5]); + PORT = GetPortTableEntryFromPortNum(Port); + if (PORT) + MH = PORT->PORTMHEARD; + else + MH = NULL; + + continue; + } + + // 1548777630 N9LYA-8 Jan 29 16:00:30 + + if (MH) + { + ptr = strtok_s(line, seps, &Context); + + if (ptr == NULL) + continue; + + MH->MHTIME = atoi(ptr); + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) + continue; + + MH->MHCOUNT = atoi(ptr); + + // Get LOC and Freq First before strtok messes with line + + locptr = strchr(Context, '|'); + + if (locptr == NULL) + continue; + + locend = strchr(++locptr, '|'); + + if (locend == NULL) + continue; + + if ((locend - locptr) == 6) + + memcpy(MH->MHLocator, locptr, 6); + + locend++; + + strlop(locend, '\n'); + + if (strlen(locend) < 12) + + strcpy(MH->MHFreq, locend); + + ptr = strtok_s(NULL, "|\n", &Context); + + if (ptr == NULL) + continue; + + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // Duff + + memcpy(MH->MHCALL, axcall, 7); + + if (ptr[10] != ' ') + MH->MHDIGI = ptr[10]; + + // SEE IF ANY DIGIS + + digi = strstr(ptr, " via "); + + if (digi) + { + digi = digi + 5; + Digiptr = &MH->MHDIGIS[0][0]; + + if (strchr(ptr, '*')) + Digiused = 1; // At least one digi used + else + Digiused = 0; + + digi = strtok_s(digi, ",\n", &Context2); + + while(digi) + { + HasStar = strlop(digi, '*'); + + if (ConvToAX25(digi, axcall) == FALSE) + break; + + memcpy(Digiptr, axcall, 7); + + if (Digiused) + Digiptr[6] |= 0x80; // Set used + + if (HasStar) + Digiused = 0; // Only last used has * + + Digiptr += 7; + + digi = strtok_s(NULL, ",/n", &Context2); + } + + *(--Digiptr) |= 1; // Set end of address on last + + } + else + { + // No Digis + + MH->MHCALL[6] |= 1; // Set end of address + } + + MH++; + } + } + + fclose(fp); + return; +} + + +VOID ReadNodes() +{ + char FN[260]; + FILE *fp; + char line[LINE_MAX]; + UCHAR axcall[7]; + char * ptr; + char * Context; + char seps[] = " \r"; + int Port, Qual; + struct PORTCONTROL * PORT; + + // Set up pointer to BPQNODES file + + if (BPQDirectory[0] == 0) + { + strcpy(FN,"BPQNODES.dat"); + } + else + { + strcpy(FN,BPQDirectory); + strcat(FN,"/"); + strcat(FN,"BPQNODES.dat"); + } + + if ((fp = fopen(FN, "r")) == NULL) + { + WritetoConsoleLocal( + "Route/Node recovery file BPQNODES.dat not found - Continuing without it\n"); + return; + } + + // Read the saved ROUTES/NODES file + + while (fgets(line, LINE_MAX, fp) != NULL) + { + if (memcmp(line, "ROUTE ADD", 9) == 0) + { + struct ROUTE * ROUTE = NULL; + + // FORMAT IS ROUTE ADD CALLSIGN PORT QUAL (VIA .... + + ptr = strtok_s(&line[10], seps, &Context); + + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // DUff + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + Port = atoi(ptr); + + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + continue; // Port has gone + + if (FindNeighbour(axcall, Port, &ROUTE)) + continue; // Already added from ROUTES: + + if (ROUTE == NULL) + continue; // Tsble Full + + memcpy(ROUTE->NEIGHBOUR_CALL, axcall, 7); + ROUTE->NEIGHBOUR_PORT = Port; + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + Qual = atoi(ptr); + + ROUTE->NEIGHBOUR_QUAL = Qual; + + ptr = strtok_s(NULL, seps, &Context); // MAXFRAME + if (ptr == NULL) continue; + + // I don't thinlk we should load locked flag from save file - only from config + + // But need to parse it until I stop saving it + + if (ptr[0] == '!') + { +// ROUTE->NEIGHBOUR_FLAG = 1; // LOCKED ROUTE + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + } + + // SEE IF ANY DIGIS + + if (ptr[0] == 'V') + { + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // DUff + + memcpy(ROUTE->NEIGHBOUR_DIGI1, axcall, 7); + + ROUTE->NEIGHBOUR_FLAG = 1; // LOCKED ROUTE - Digi'ed routes must be locked + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + + // See if another digi or MAXFRAME + + if (strlen(ptr) > 2) + { + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // DUff + + memcpy(ROUTE->NEIGHBOUR_DIGI2, axcall, 7); + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + } + } + + Qual = atoi(ptr); + ROUTE->NBOUR_MAXFRAME = Qual; + + ptr = strtok_s(NULL, seps, &Context); // FRACK + if (ptr == NULL) continue; + Qual = atoi(ptr); + ROUTE->NBOUR_FRACK = Qual; + + ptr = strtok_s(NULL, seps, &Context); // PACLEN + if (ptr == NULL) continue; + Qual = atoi(ptr); + ROUTE->NBOUR_PACLEN = Qual; + + ptr = strtok_s(NULL, seps, &Context); // INP3 + if (ptr == NULL) continue; + Qual = atoi(ptr); + + // We now take Nokeepalives from the PORT, unless specifically + // Requested + + ROUTE->NoKeepAlive = PORT->PortNoKeepAlive; + + if (Qual & 4) + ROUTE->NoKeepAlive = TRUE; + + if ((Qual & 1) || PORT->INP3ONLY) + { + ROUTE->NoKeepAlive = FALSE; + ROUTE->INP3Node = TRUE; + } + + ptr = strtok_s(NULL, seps, &Context); // INP3 + if (ptr == NULL) continue; + + if (ROUTE->NEIGHBOUR_FLAG == 0 || ROUTE->OtherendLocked == 0); // Not LOCKED ROUTE + ROUTE->OtherendsRouteQual = atoi(ptr); + + continue; + } + + if (memcmp(line, "NODE ADD", 8) == 0) + { + // FORMAT IS NODE ADD ALIAS:CALL ROUTE QUAL + + dest_list * DEST = NULL; + struct ROUTE * ROUTE = NULL; + char * ALIAS; + char FULLALIAS[6] = " "; + int SavedOBSINIT = OBSINIT; + + if (line[9] == ':') + { + // No alias + + Context = &line[10]; + } + else + { + ALIAS = strtok_s(&line[9], ":", &Context); + + if (ALIAS == NULL) + continue; + + memcpy(FULLALIAS, ALIAS, (int)strlen(ALIAS)); + } + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) + continue; + + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // Duff + + if (CompareCalls(axcall, MYCALL)) + continue; // Shoiuldn't happen, but to be safe! + + if (FindDestination(axcall, &DEST)) + continue; + + if (DEST == NULL) + continue; // Tsble Full + + memcpy(DEST->DEST_CALL, axcall, 7); + memcpy(DEST->DEST_ALIAS, FULLALIAS, 6); + + NUMBEROFNODES++; +RouteLoop: + // GET NEIGHBOURS FOR THIS DESTINATION - CALL PORT QUAL + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + + if (ConvToAX25(ptr, axcall) == FALSE) + continue; // DUff + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + Port = atoi(ptr); + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) continue; + Qual = atoi(ptr); + + if (Context[0] == '!') + { + OBSINIT = 255; //; SPECIAL FOR LOCKED + } + + if (FindNeighbour(axcall, Port, &ROUTE)) + { + PROCROUTES(DEST, ROUTE, Qual); + } + + OBSINIT = SavedOBSINIT; + + goto RouteLoop; + } + } + + fclose(fp); + +// loadedmsg DB cr,lf,lf,'Switch loaded and initialised OK',lf,0 + + return; +} + +int DelayBuffers = 0; + +void sendModeReport(); +void sendFreqReport(); + +VOID TIMERINTERRUPT() +{ + // Main Processing loop - CALLED EVERY 100 MS + + int i; + struct PORTCONTROL * PORT = PORTTABLE; + PMESSAGE Buffer; + struct _LINKTABLE * LINK; + struct _MESSAGE * Message; + int toPort; + + CurrentSecs = time(NULL); + + BGTIMER++; + REALTIMETICKS++; + L2TIMERFLAG++; // INCREMENT FLAG FOR BG + L3TIMERFLAG++; // INCREMENT FLAG FOR BG + L4TIMERFLAG++; // INCREMENT FLAG FOR BG + +// CALL PORT TIMER ROUTINES + + for (i = 0; i < NUMBEROFPORTS; i++) + { + PORT->PORTTIMERCODE(PORT); + + // Check Smart ID timer + + if (PORT->SmartIDNeeded && PORT->SmartIDNeeded < time(NULL)) + SendSmartID(PORT); + + PORT = PORT->PORTPOINTER; + } + + // CHECK FOR TIMER ACTIVITY + + if (L2TIMERFLAG >= 3) + { + L2TIMERFLAG -= 3; + L2TimerProc(); // 300 mS + } + + if (CurrentSecs - lastSlowSecs >= 60) // 1 PER MIN + { + lastSlowSecs = CurrentSecs; + + L3TimerProc(); + + if (needAIS) + AISTimer(); + + if (needADSB) + ADSBTimer(); + + if (APRSActive) + Debugprintf("BPQ32 Heartbeat Buffers %d APRS Queues %d %d", QCOUNT, C_Q_COUNT(&APRSMONVECPTR->HOSTTRACEQ), C_Q_COUNT(&APPL_Q)); + else + Debugprintf("BPQ32 Heartbeat Buffers %d", QCOUNT); + + StatsTimer(); + + // Call EXT Port Slow Timer + + PORT = PORTTABLE; + + for (i = 0; i < NUMBEROFPORTS; i++) + { + if (PORT->PROTOCOL == 10) + { + PEXTPORTDATA PORTVEC = (PEXTPORTDATA)PORT; + EXTSLOWTIMER(PORTVEC); + } + + PORT = PORT->PORTPOINTER; + } + + // Call Mode Map support routine + + sendFreqReport(); + sendModeReport(); + + if (MQTT) + MQTTTimer(); + +/* + if (QCOUNT < 200) + { + if (DelayBuffers == 0) + { + FindLostBuffers(); + DelayBuffers = 10; + } + else + { + DelayBuffers--; + } + } +*/ + } + + // Check Autosave Nodes and MH timer + + if (CurrentSecs - lastSaveSecs >= 3600) // 1 per hour + { + lastSaveSecs = CurrentSecs; + + if (AUTOSAVE == 1) + SaveNodes(); + if (AUTOSAVEMH == 1) + SaveMH(); + } + + if (L4TIMERFLAG >= 10) // 1 PER SEC + { + L4TIMERFLAG -= 10; + + L3FastTimer(); + L4TimerProc(); + } + + // SEE IF ANY FRAMES TO TRACE + + Buffer = Q_REM(&TRACE_Q); + + while (Buffer) + { + // IF BUFFER HAS A LINK TABLE ENTRY ON END, RESET TIMEOUT + + LINK = Buffer->Linkptr; + + if (LINK) + { + if (LINK->L2TIMER) + LINK->L2TIMER = LINK->L2TIME; + + Buffer->Linkptr = 0; // CLEAR FLAG FROM BUFFER + } + + Message = (struct _MESSAGE *)Buffer; + Message->PORT |= 0x80; // Set TX Bit + + BPQTRACE(Message, FALSE); // Dont send TX'ed frames to APRS + ReleaseBuffer(Buffer); + + Buffer = Q_REM(&TRACE_Q); + } + + // CHECK FOR MESSAGES RECEIVED FROM COMMS LINKS + + PORT = PORTTABLE; + + for (i = 0; i < NUMBEROFPORTS; i++) + { + int Sent = 0; + + CURRENTPORT = PORT->PORTNUMBER; // PORT NUMBER + CURRENTPORTPTR = PORT; + + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTRX_Q); + + while (Buffer) + { + Message = (struct _MESSAGE *) Buffer; + if (CURRENTPORT == 30) + Sent = Sent; + + if (PORT->PROTOCOL == 10) + { + int Sessno = Message->PORT; + PEXTPORTDATA PORTVEC = (PEXTPORTDATA)PORT; + TRANSPORTENTRY * Session; + TRANSPORTENTRY * Partner; + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + { + + // KISSHF - May be L2 packet or Test Response + + if (Message->DEST[0] != 240) // Should only be ax.25 address field + goto L2Packet; + } + + // PACTOR Style Message + + InOctets[PORT->PORTNUMBER] += Message->LENGTH - (MSGHDDRLEN + 1); + PORT->L2FRAMESFORUS++; + + Session = PORTVEC->ATTACHEDSESSIONS[Sessno]; + + if (Session == NULL) + { + // TNC not attached - discard + + ReleaseBuffer(Buffer); + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTRX_Q); + continue; + } + + Session->L4KILLTIMER = 0; // Reset Idle Timeout + + Partner = Session->L4CROSSLINK; + + if (Partner == NULL) + { + // No Crosslink - pass to command handler + + CommandHandler(Session, (PDATAMESSAGE)Buffer); + break; + } + + if (Partner->L4STATE < 5) + { + // MESSAGE RECEIVED BEFORE SESSION IS UP - CANCEL SESSION + // AND PASS MESSAGE TO COMMAND HANDLER + + if (Partner->L4CIRCUITTYPE & L2LINK) // L2 SESSION? + { + // MUST CANCEL L2 SESSION + + LINK = Partner->L4TARGET.LINK; + LINK->CIRCUITPOINTER = NULL; // CLEAR REVERSE LINK + + LINK->L2STATE = 4; // DISCONNECTING + LINK->L2TIMER = 1; // USE TIMER TO KICK OFF DISC + + LINK->L2RETRIES = LINK->LINKPORT->PORTN2 - 2; //ONLY SEND DISC ONCE + } + + CLEARSESSIONENTRY(Partner); + Session->L4CROSSLINK = 0; // CLEAR CROSS LINK + CommandHandler(Session, (struct DATAMESSAGE *)Buffer); + break; + } + + C_Q_ADD(&Partner->L4TX_Q, Buffer); + PostDataAvailable(Partner); + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTRX_Q); + continue; + } + + +L2Packet: + // TIME STAMP IT + + time(&Message->Timestamp); + + Message->PORT = CURRENTPORT; + + if (MQTT && PORT->PROTOCOL == 0) + MQTTKISSRX(Buffer); + + // Bridge if requested + + for (toPort = 1; toPort <= MaxBPQPortNo; toPort++) + { + if (BridgeMap[CURRENTPORT][toPort]) + { + MESSAGE * BBuffer = GetBuff(); + struct PORTCONTROL * BPORT; + + if (BBuffer) + { + memcpy(BBuffer, Message, Message->LENGTH); + BBuffer->PORT = toPort; + BPORT = GetPortTableEntryFromPortNum(toPort); + + if (BPORT) + { + if (BPORT->SmartIDInterval && BPORT->SmartIDNeeded == 0) + { + // Using Smart ID, but none scheduled + + BPORT->SmartIDNeeded = time(NULL) + BPORT->SmartIDInterval; + } + PUT_ON_PORT_Q(BPORT, BBuffer); + } + else + ReleaseBuffer(BBuffer); + } + } + } + + L2Routine(PORT, Buffer); + + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTRX_Q); + continue; + } + + // End of RX_Q + + Sent = 0; + + while (PORT->PORTTX_Q && Sent < 5) + { + int ret; + void * PACTORSAVEQ; + + Buffer = PORT->PORTTX_Q; + Message = (struct _MESSAGE *) Buffer; + + ret = PORT->PORTTXCHECKCODE(PORT, Message->PORT); + + // Busy but not connected means TNC has gone - clear queue + + if (ret == 1) + { + MESSAGE * Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTTX_Q); + + if (Buffer == 0) + break; // WOT!! + +// Debugprintf("Busy but not connected - discard message %s", Buffer->L2DATA); + + ReleaseBuffer(Buffer); + break; + } + + ret = ret & 0xff; // Only check bottom byte + + if (ret == 0) // Not busy + { + MESSAGE * Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTTX_Q); + + if (Buffer == 0) + break; // WOT!! + + if (PORT->PORTDISABLED) + { + ReleaseBuffer(Buffer); + break; + } + + PORT->L2FRAMESSENT++; + OutOctets[PORT->PORTNUMBER] += Buffer->LENGTH - MSGHDDRLEN; + + PORT->PORTTXROUTINE((struct _EXTPORTDATA *)PORT, Buffer); + Sent++; + + continue; + } + + // If a Pactor Port, some channels could be busy whilst others are not. + + if (PORT->PROTOCOL != 10) + break; // BUSY + + // Try passing any other messages on the queue to the node. + + PACTORSAVEQ = 0; + +PACTORLOOP: + + Buffer = PORT->PORTTX_Q; + + if (Buffer == NULL) + goto ENDOFLIST; + + Message = (struct _MESSAGE *) Buffer; + ret = PORT->PORTTXCHECKCODE(PORT, Message->PORT); + ret = ret & 0xff; // Only check bottom byte + + if (ret) // Busy + { + // Save it + + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTTX_Q); + C_Q_ADD(&PACTORSAVEQ, Buffer); + goto PACTORLOOP; + } + + Buffer = (PMESSAGE)Q_REM((void *)&PORT->PORTTX_Q); + + if (PORT->PORTDISABLED) + { + ReleaseBuffer(Buffer); + goto PACTORLOOP; + } + + PORT->L2FRAMESSENT++; + OutOctets[PORT->PORTNUMBER] += Message->LENGTH; + + PORT->PORTTXROUTINE((struct _EXTPORTDATA *)PORT, Buffer); + Sent++; + + if (Sent < 5) + goto PACTORLOOP; // SEE IF MORE + +ENDOFLIST: + // Move the saved frames back onto Port Q + + PORT->PORTTX_Q = PACTORSAVEQ; + break; + } + + PORT->PORTRXROUTINE((struct _EXTPORTDATA *)PORT); // SEE IF MESSAGE RECEIVED + PORT = PORT->PORTPOINTER; + } + +/* +; +; CHECK FOR INCOMING MESSAGES ON LINK CONTROL TABLE - +; BY NOW ONLY 'I' FRAMES WILL BE PRESENT - +; LEVEL 2 PROTOCOL HANDLING IS DONE IN MESSAGE RECEIVE CODE +; AND LINK HANDLING INTERRUPT ROUTINES +; +*/ + + LINK = LINKS; + i = MAXLINKS; + + while (i--) + { + if (LINK->LINKCALL[0]) + { + Buffer = Q_REM(&LINK->RX_Q); + + while (Buffer) + { + ProcessIframe(LINK, (PDATAMESSAGE)Buffer); + + Buffer =(PMESSAGE)Q_REM((void *)&LINK->RX_Q); + } + + // CHECK FOR OUTGOING MSGS + + if (LINK->L2STATE >= 5) // CANT SEND TEXT TILL CONNECTED + { + // CMP VER1FLAG[EBX],1 + // JE SHORT MAINL35 ; NEED TO RETRY WITH I FRAMES IF VER 1 + + // CMP L2RETRIES[EBX],0 + // JNE SHORT MAINL40 ; CANT SEND TEXT IF RETRYING + + if ((LINK->L2FLAGS & RNRSET) == 0) + SDETX(LINK); + } + } + LINK++; + } + + L4BG(); // DO LEVEL 4 PROCESSING + L3BG(); + TNCTimerProc(); +} + +VOID DoListenMonitor(TRANSPORTENTRY * L4, MESSAGE * Msg) +{ + uint64_t SaveMMASK = MMASK; + BOOL SaveMTX = MTX; + BOOL SaveMCOM = MCOM; + BOOL SaveMUI = MUIONLY; + PDATAMESSAGE Buffer; + char MonBuffer[1024]; + int len; + struct tm * TM; + UCHAR * monchars = (UCHAR *)Msg; + + if (CountFramesQueuedOnSession(L4) > 10) + return; + + if (monchars[21] == 3 && monchars[22] == 0xcf && monchars[23] == 0xff) // Netrom Nodes + return; + + IntSetTraceOptionsEx(L4->LISTEN, 1, 0, 0); + + TM = gmtime(&Msg->Timestamp); + sprintf(MonBuffer, "%02d:%02d:%02d ", TM->tm_hour, TM->tm_min, TM->tm_sec); + + len = IntDecodeFrame(Msg, &MonBuffer[9], Msg->Timestamp, L4->LISTEN, FALSE, TRUE); + + IntSetTraceOptionsEx(SaveMMASK, SaveMTX, SaveMCOM, SaveMUI); + + if (len == 0) + return; + + len += 9; + + if (len > 256) + len = 256; + + Buffer = GetBuff(); + + if (Buffer) + { + char * ptr = &Buffer->L2DATA[0]; + Buffer->PID = 0xf0; + + memcpy(ptr, MonBuffer, len); + + Buffer->LENGTH = (USHORT)len + 4 + sizeof(void *); + + C_Q_ADD(&L4->L4TX_Q, Buffer); + + PostDataAvailable(L4); + } +} + +DllExport int APIENTRY DllBPQTRACE(MESSAGE * Msg, BOOL TOAPRS) +{ + int ret; + GetSemaphore(&Semaphore, 88); + ret = BPQTRACE(Msg, TOAPRS); + FreeSemaphore(&Semaphore); + return ret; +} + +int BPQTRACE(MESSAGE * Msg, BOOL TOAPRS) +{ + // ATTACH A COPY OF FRAME TO ANY BPQ HOST PORTS WITH MONITORING ENABLED + + TRANSPORTENTRY * L4 = L4TABLE; + + MESSAGE * Buffer; + int i = BPQHOSTSTREAMS + 2; // Include Telnet and AGW Stream + + if (TOAPRS) + i++; // Include APRS Stream + + while(i) + { + i--; + + if (QCOUNT < 100) + return FALSE; + + if (BPQHOSTVECTOR[i].HOSTAPPLFLAGS & 0x80) // Trace Enabled? + { + Buffer = GetBuff(); + if (Buffer) + { + memcpy(&Buffer->PORT, &Msg->PORT, BUFFLEN - sizeof(void *)); // Dont copy chain word + C_Q_ADD(&BPQHOSTVECTOR[i].HOSTTRACEQ, Buffer); + +#ifndef LINBPQ + if (BPQHOSTVECTOR[i].HOSTHANDLE) + PostMessage(BPQHOSTVECTOR[i].HOSTHANDLE, BPQMsg, BPQHOSTVECTOR[i].HOSTSTREAM, 1); +#endif + } + } + + } + + // Also pass to any users LISTENING on this port + + i = MAXCIRCUITS; + + if (QCOUNT < 300) + return FALSE; // Until I add by session flow control + + while (i--) + { + if (L4->LISTEN) + if ((((uint64_t)1 << ((Msg->PORT & 0x7f) - 1)) & L4->LISTEN)) + // if ((Msg->PORT & 0x7f) == L4->LISTEN) + DoListenMonitor(L4, Msg); + + L4++; + } + + return TRUE; +} +; + +VOID INITIALISEPORTS() +{ + char INITMSG[80]; + struct PORTCONTROL * PORT = PORTTABLE; + + while (PORT) + { + sprintf(INITMSG, "Initialising Port %02d ", PORT->PORTNUMBER); + WritetoConsoleLocal(INITMSG); + + PORT->PORTINITCODE(PORT); + PORT = PORT->PORTPOINTER; + } +} + +VOID FindLostBuffers() +{ + void ** Buff; + int n, i; + unsigned int rev; + + UINT CodeDump[16]; + char codeText[65] = ""; + unsigned char * codeByte = (unsigned char *) CodeDump; + + PBPQVECSTRUC HOSTSESS = BPQHOSTVECTOR; + struct _TRANSPORTENTRY * L4; // Pointer to Session + + struct DEST_LIST * DEST = DESTS; + + struct ROUTE * Routes = NEIGHBOURS; + int MaxRoutes = MAXNEIGHBOURS; + int Queued; + char Call[10]; + + n = MAXDESTS; + + Debugprintf("Looking for missing Buffers"); + + while (n--) + { + if (DEST->DEST_CALL[0] && DEST->DEST_Q) // Spare + { + Debugprintf("DEST Queue %s %d", DEST->DEST_ALIAS, C_Q_COUNT(&DEST->DEST_Q)); + } + + DEST++; + } + + n = 0; + + while (n < BPQHOSTSTREAMS + 4) + { + // Check Trace Q + + if (HOSTSESS->HOSTTRACEQ) + { + int Count = C_Q_COUNT(&HOSTSESS->HOSTTRACEQ); + + Debugprintf("Trace Buffers Stream %d Count %d", n, Count); + + L4 = HOSTSESS->HOSTSESSION; + + if (L4 && (L4->L4TX_Q || L4->L4RX_Q || L4->L4HOLD_Q || L4->L4RESEQ_Q)) + Debugprintf("Stream %d %d %d %d %d", n, C_Q_COUNT(&L4->L4TX_Q), + C_Q_COUNT(&L4->L4RX_Q), C_Q_COUNT(&L4->L4HOLD_Q), C_Q_COUNT(&L4->L4RESEQ_Q)); + + } + n++; + HOSTSESS++; + } + + n = MAXCIRCUITS; + L4 = L4TABLE; + + while (n--) + { + if (L4->L4USER[0] == 0) + { + L4++; + continue; + } + if (L4->L4TX_Q || L4->L4RX_Q || L4->L4HOLD_Q || L4->L4RESEQ_Q) + Debugprintf("L4 %d TX %d RX %d HOLD %d RESEQ %d", MAXCIRCUITS - n, C_Q_COUNT(&L4->L4TX_Q), + C_Q_COUNT(&L4->L4RX_Q), C_Q_COUNT(&L4->L4HOLD_Q), C_Q_COUNT(&L4->L4RESEQ_Q)); + L4++; + } + + // Routes + + while (MaxRoutes--) + { + if (Routes->NEIGHBOUR_CALL[0] != 0) + { + Call[ConvFromAX25(Routes->NEIGHBOUR_CALL, Call)] = 0; + if (Routes->NEIGHBOUR_LINK) + { + Queued = COUNT_AT_L2(Routes->NEIGHBOUR_LINK); // SEE HOW MANY QUEUED + if (Queued) + Debugprintf("Route %s %d", Call, Queued); + } + } + Routes++; + } + + // Build list of buffers, then mark off all on free Q + + Buff = BUFFERPOOL; + n = 0; + + for (i = 0; i < NUMBEROFBUFFERS; i++) + { + Bufferlist[n++] = Buff; + Buff += (BUFFALLOC / sizeof(void *)); + } + + Buff = FREE_Q; + + while (Buff) + { + n = NUMBEROFBUFFERS; + + while (n--) + { + if (Bufferlist[n] == Buff) + { + Bufferlist[n] = 0; + break; + } + } + Buff = *Buff; + } + n = NUMBEROFBUFFERS; + + while (n--) + { + if (Bufferlist[n]) + { + char * fileptr = (char *)Bufferlist[n]; + MESSAGE * Msg = (MESSAGE *)Bufferlist[n]; + + memcpy(CodeDump, Bufferlist[n], 64); + + for (i = 0; i < 64; i++) + { + if (codeByte[i] > 0x1f && codeByte[i] < 0x80) + codeText[i] = codeByte[i]; + else + codeText[i] = '.'; + } + + for (i = 0; i < 16; i++) + { + rev = (CodeDump[i] & 0xff) << 24; + rev |= (CodeDump[i] & 0xff00) << 8; + rev |= (CodeDump[i] & 0xff0000) >> 8; + rev |= (CodeDump[i] & 0xff000000) >> 24; + + CodeDump[i] = rev; + } + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + Bufferlist[n], CodeDump[0], CodeDump[1], CodeDump[2], CodeDump[3], CodeDump[4], CodeDump[5], CodeDump[6], CodeDump[7]); + + Debugprintf(" %08x %08x %08x %08x %08x %08x %08x %08x %d", + CodeDump[8], CodeDump[9], CodeDump[10], CodeDump[11], CodeDump[12], CodeDump[13], CodeDump[14], CodeDump[15], Msg->Process); + + Debugprintf(" %s %s", &fileptr[400], codeText); + } + } + + // rebuild list for buffer check + Buff = BUFFERPOOL; + n = 0; + + for (i = 0; i < NUMBEROFBUFFERS; i++) + { + Bufferlist[n++] = Buff; + Buff += (BUFFALLOC / sizeof(void *)); + } +} + +void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode) +{ + UCHAR FN[MAX_PATH]; + FILE * LogHandle; + time_t T; + struct tm * tm; + char LogMsg[256]; + int MsgLen; + + T = time(NULL); + tm = gmtime(&T); + + sprintf(FN,"%s/logs/ConnectLog_%02d%02d%02d.log", LogDirectory, tm->tm_year - 100, tm->tm_mon + 1, tm->tm_mday); + + LogHandle = fopen(FN, "ab"); + + if (LogHandle == NULL) + return; + + MsgLen = sprintf(LogMsg, "%02d:%02d:%02d Call from %s to %s Mode %s\r\n", tm->tm_hour, tm->tm_min, tm->tm_sec, fromCall, toCall, Mode); + + fwrite(LogMsg , 1, MsgLen, LogHandle); + fclose(LogHandle); +} + + + diff --git a/cMain.c b/cMain.c index 7cf47d0..ea68401 100644 --- a/cMain.c +++ b/cMain.c @@ -34,7 +34,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include "kernelresource.h" -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h" #include "mqtt.h" @@ -2468,7 +2468,7 @@ VOID DoListenMonitor(TRANSPORTENTRY * L4, MESSAGE * Msg) PDATAMESSAGE Buffer; char MonBuffer[1024]; int len; - + struct tm * TM; UCHAR * monchars = (UCHAR *)Msg; if (CountFramesQueuedOnSession(L4) > 10) @@ -2478,14 +2478,19 @@ VOID DoListenMonitor(TRANSPORTENTRY * L4, MESSAGE * Msg) return; IntSetTraceOptionsEx(L4->LISTEN, 1, 0, 0); - - len = IntDecodeFrame(Msg, MonBuffer, Msg->Timestamp, L4->LISTEN, FALSE, TRUE); + + TM = gmtime(&Msg->Timestamp); + sprintf(MonBuffer, "%02d:%02d:%02d ", TM->tm_hour, TM->tm_min, TM->tm_sec); + + len = IntDecodeFrame(Msg, &MonBuffer[9], Msg->Timestamp, L4->LISTEN, FALSE, TRUE); IntSetTraceOptionsEx(SaveMMASK, SaveMTX, SaveMCOM, SaveMUI); if (len == 0) return; + len += 9; + if (len > 256) len = 256; diff --git a/cheaders.h.bak b/cheaders.h.bak new file mode 100644 index 0000000..80f4806 --- /dev/null +++ b/cheaders.h.bak @@ -0,0 +1,447 @@ +// +// Prototypes for BPQ32 Node Functions +// + +#define DllImport + +#define EXCLUDEBITS + +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#include "compatbits.h" + +#include "asmstrucs.h" + +BOOL CheckExcludeList(UCHAR * Call); + +Dll int ConvFromAX25(unsigned char * incall,unsigned char * outcall); +Dll BOOL ConvToAX25(unsigned char * callsign, unsigned char * ax25call); +DllExport BOOL ConvToAX25Ex(unsigned char * callsign, unsigned char * ax25call); +int WritetoConsoleLocal(char * buff); +VOID Consoleprintf(const char * format, ...); +VOID FreeConfig(); +int GetListeningPortsPID(int Port); + +void * InitializeExtDriver(PEXTPORTDATA PORTVEC); + +VOID PutLengthinBuffer(PDATAMESSAGE buff, USHORT datalen); // Needed for arm5 portability +int GetLengthfromBuffer(PDATAMESSAGE buff); +int IntDecodeFrame(MESSAGE * msg, char * buffer, time_t Stamp, uint64_t Mask, BOOL APRS, BOOL MCTL); +int IntSetTraceOptionsEx(uint64_t mask, int mtxparam, int mcomparam, int monUIOnly); +int CountBits64(uint64_t in);Buff() _GetBuff(__FILE__, __LINE__) +#define ReleaseBuffer(s) _ReleaseBuffer(s, __FILE__, __LINE__) +#define CheckGuardZone() _CheckGuardZone(__FILE__, __LINE__) + +#define Q_REM(s) _Q_REM(s, __FILE__, __LINE__) +#define Q_REM_NP(s) _Q_REM_NP(s, __FILE__, __LINE__) + +#define C_Q_ADD(s, b) _C_Q_ADD(s, b, __FILE__, __LINE__) + +void _CheckGuardZone(char * File, int Line); + +VOID * _Q_REM(VOID **Q, char * File, int Line); +VOID * _Q_REM_NP(VOID *Q, char * File, int Line); + +int _C_Q_ADD(VOID *Q, VOID *BU + +#define GetFF, char * File, int Line); + +UINT _ReleaseBuffer(VOID *BUFF, char * File, int Line); + +VOID * _GetBuff(char * File, int Line); +int _C_Q_ADD(VOID *PQ, VOID *PBUFF, char * File, int Line); + +int C_Q_COUNT(VOID *Q); + +DllExport char * APIENTRY GetApplCall(int Appl); +DllExport char * APIENTRY GetApplAlias(int Appl); +DllExport int APIENTRY FindFreeStream(); +DllExport int APIENTRY DeallocateStream(int stream); +DllExport int APIENTRY SessionState(int stream, int * state, int * change); +DllExport int APIENTRY SetAppl(int stream, int flags, int mask); +DllExport int APIENTRY GetMsg(int stream, char * msg, int * len, int * count ); +DllExport int APIENTRY GetConnectionInfo(int stream, char * callsign, + int * port, int * sesstype, int * paclen, + int * maxframe, int * l4window); + +#define LIBCONFIG_STATIC +#include "libconfig.h" + +int GetIntValue(config_setting_t * group, char * name); +BOOL GetStringValue(config_setting_t * group, char * name, char * value, int maxlen); +VOID SaveIntValue(config_setting_t * group, char * name, int value); +VOID SaveStringValue(config_setting_t * group, char * name, char * value); + +int EncryptPass(char * Pass, char * Encrypt); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); +Dll VOID APIENTRY CreateOneTimePassword(char * Password, char * KeyPhrase, int TimeOffset); +Dll BOOL APIENTRY CheckOneTimePassword(char * Password, char * KeyPhrase); + +DllExport int APIENTRY TXCount(int stream); +DllExport int APIENTRY RXCount(int stream); +DllExport int APIENTRY MONCount(int stream); + +VOID ReadNodes(); +int BPQTRACE(MESSAGE * Msg, BOOL APRS); + +VOID CommandHandler(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer); + +VOID PostStateChange(TRANSPORTENTRY * Session); + +VOID InnerCommandHandler(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer); +VOID DoTheCommand(TRANSPORTENTRY * Session); +char * MOVEANDCHECK(TRANSPORTENTRY * Session, char * Bufferptr, char * Source, int Len); +VOID DISPLAYCIRCUIT(TRANSPORTENTRY * L4, char * Buffer); +char * strlop(const char * buf, char delim); +BOOL CompareCalls(UCHAR * c1, UCHAR * c2); + +VOID PostDataAvailable(TRANSPORTENTRY * Session); +int WritetoConsoleLocal(char * buff); +char * CHECKBUFFER(TRANSPORTENTRY * Session, char * Bufferptr); +VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session); + +VOID SendCommandReply(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer, int Len); + +struct PORTCONTROL * APIENTRY GetPortTableEntryFromPortNum(int portnum); + +int cCOUNT_AT_L2(struct _LINKTABLE * LINK); +VOID SENDL4CONNECT(TRANSPORTENTRY * Session); + +VOID CloseSessionPartner(TRANSPORTENTRY * Session); +int COUNTNODES(struct ROUTE * ROUTE); +int DecodeNodeName(char * NodeName, char * ptr);; +VOID DISPLAYCIRCUIT(TRANSPORTENTRY * L4, char * Buffer); +int cCOUNT_AT_L2(struct _LINKTABLE * LINK); +void * zalloc(int len); +BOOL FindDestination(UCHAR * Call, struct DEST_LIST ** REQDEST); + +BOOL ProcessConfig(); + +VOID PUT_ON_PORT_Q(struct PORTCONTROL * PORT, MESSAGE * Buffer); +VOID CLEAROUTLINK(struct _LINKTABLE * LINK); +VOID TellINP3LinkGone(struct ROUTE * Route); +VOID CLEARACTIVEROUTE(struct ROUTE * ROUTE, int Reason); + +// Reason Equates + +#define NORMALCLOSE 0 +#define RETRIEDOUT 1 +#define SETUPFAILED 2 +#define LINKLOST 3 +#define LINKSTUCK 4 + +int COUNT_AT_L2(struct _LINKTABLE * LINK); +VOID SENDIDMSG(); +VOID SENDBTMSG(); +VOID INP3TIMER(); +VOID REMOVENODE(dest_list * DEST); +BOOL ACTIVATE_DEST(struct DEST_LIST * DEST); +VOID TellINP3LinkSetupFailed(struct ROUTE * Route); +BOOL FindNeighbour(UCHAR * Call, int Port, struct ROUTE ** REQROUTE); +VOID PROCROUTES(struct DEST_LIST * DEST, struct ROUTE * ROUTE, int Qual); +BOOL L2SETUPCROSSLINK(PROUTE ROUTE); +VOID REMOVENODE(dest_list * DEST); +char * SetupNodeHeader(struct DATAMESSAGE * Buffer); +VOID L4CONNECTFAILED(TRANSPORTENTRY * L4); +int CountFramesQueuedOnSession(TRANSPORTENTRY * Session); +VOID CLEARSESSIONENTRY(TRANSPORTENTRY * Session); +VOID __cdecl Debugprintf(const char * format, ...); + +int APIENTRY Restart(); +int APIENTRY Reboot(); +int APIENTRY Reconfig(); +Dll int APIENTRY SaveNodes (); + + +struct SEM; + +void GetSemaphore(struct SEM * Semaphore, int ID); +void FreeSemaphore(struct SEM * Semaphore); + +void MySetWindowText(HWND hWnd, char * Msg); + +Dll int APIENTRY SessionControl(int stream, int command, int Mask); + +HANDLE OpenCOMPort(VOID * pPort, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits); +int ReadCOMBlock(HANDLE fd, char * Block, int MaxLength); +BOOL WriteCOMBlock(HANDLE fd, char * Block, int BytesToWrite); +VOID CloseCOMPort(HANDLE fd); + +VOID initUTF8(); +int Is8Bit(unsigned char *cpt, int len); +int WebIsUTF8(unsigned char *ptr, int len); +int IsUTF8(unsigned char *ptr, int len); +int Convert437toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1251toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1252toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int TrytoGuessCode(unsigned char * Char, int Len); + + +#define CMD_TO_APPL 1 // PASS COMMAND TO APPLICATION +#define MSG_TO_USER 2 // SEND 'CONNECTED' TO USER +#define MSG_TO_APPL 4 // SEND 'CONECTED' TO APPL +#define CHECK_FOR_ESC 8 // Look for ^d (^D) to disconnect session) + +#define UI 3 +#define SABM 0x2F +#define DISC 0x43 +#define DM 0x0F +#define UA 0x63 +#define FRMR 0x87 +#define RR 1 +#define RNR 5 +#define REJ 9 + +// V2.2 Types + +#define SREJ 0x0D +#define SABME 0x6F +#define XID 0xAF +#define TEST 0xE3 + +#define SUPPORT2point2 1 + +// XID Optional Functions + +#define OPMustHave 0x02A080 // Sync TEST 16 bit FCS Extended Address +#define OPSREJ 4 +#define OPSREJMult 0x200000 +#define OPREJ 2 +#define OPMod8 0x400 +#define OPMod128 0x800 + +#define BPQHOSTSTREAMS 64 + +extern TRANSPORTENTRY * L4TABLE; +extern unsigned char NEXTID; +extern int MAXCIRCUITS; +extern int L4DEFAULTWINDOW; +extern int L4T1; +extern APPLCALLS APPLCALLTABLE[]; +extern char * APPLS; +extern int NEEDMH; +extern int RFOnly; + +extern char SESSIONHDDR[]; + +extern UCHAR NEXTID; + +extern struct ROUTE * NEIGHBOURS; +extern int MAXNEIGHBOURS; + +extern struct ROUTE * NEIGHBOURS; +extern int ROUTE_LEN; +extern int MAXNEIGHBOURS; + +extern struct DEST_LIST * DESTS; // NODE LIST +extern struct DEST_LIST * ENDDESTLIST; +extern int DEST_LIST_LEN; +extern int MAXDESTS; // MAX NODES IN SYSTEM + +extern struct _LINKTABLE * LINKS; +extern int LINK_TABLE_LEN; +extern int MAXLINKS; + + + +extern char MYCALL[]; // DB 7 DUP (0) ; NODE CALLSIGN (BIT SHIFTED) +extern char MYALIASTEXT[]; // {" " ; NODE ALIAS (KEEP TOGETHER) + +extern UCHAR MYCALLWITHALIAS[13]; +extern APPLCALLS APPLCALLTABLE[NumberofAppls]; + +extern UCHAR MYNODECALL[]; // NODE CALLSIGN (ASCII) +extern char NODECALLLOPPED[]; // NODE CALLSIGN (ASCII). Null terminated +extern UCHAR MYNETROMCALL[]; // NETROM CALLSIGN (ASCII) + +extern UCHAR NETROMCALL[]; // NETORM CALL (AX25) + +extern VOID * FREE_Q; + +extern struct PORTCONTROL * PORTTABLE; +extern int NUMBEROFPORTS; + + +extern int OBSINIT; // INITIAL OBSOLESCENCE VALUE +extern int OBSMIN; // MINIMUM TO BROADCAST +extern int L3INTERVAL; // "NODES" INTERVAL IN MINS +extern int IDINTERVAL; // "ID" BROADCAST INTERVAL +extern int BTINTERVAL; // "BT" BROADCAST INTERVAL +extern int MINQUAL; // MIN QUALITY FOR AUTOUPDATES +extern int HIDENODES; // N * COMMAND SWITCH +extern int BBSQUAL; // QUALITY OF BBS RELATIVE TO NODE + +extern int NUMBEROFBUFFERS; // PACKET BUFFERS +extern int PACLEN; //MAX PACKET SIZE + +// L2 SYSTEM TIMER RUNS AT 3 HZ + +extern int T3; // LINK VALIDATION TIMER (3 MINS) (+ a bit to reduce RR collisions) + +extern int L2KILLTIME; // IDLE LINK TIMER (16 MINS) +extern int L3LIVES; // MAX L3 HOPS +extern int L4N2; // LEVEL 4 RETRY COUNT +extern int L4LIMIT; // IDLE SESSION LIMIT - 15 MINS +extern int L4DELAY; // L4 DELAYED ACK TIMER + +extern int BBS; // INCLUDE BBS SUPPORT +extern int NODE; // INCLUDE SWITCH SUPPORT + +extern int FULL_CTEXT; // CTEXT ON ALL CONNECTS IF NZ + + +// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 + +extern BPQVECSTRUC DUMMYVEC; // Needed to force correct order of following + +extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; + +extern int NODEORDER; +extern UCHAR LINKEDFLAG; + +extern UCHAR UNPROTOCALL[80]; + + +extern char * INFOMSG; + +extern char * CTEXTMSG; +extern int CTEXTLEN; + +extern UCHAR MYALIAS[7]; // ALIAS IN AX25 FORM +extern UCHAR BBSALIAS[7]; + +extern VOID * TRACE_Q; // TRANSMITTED FRAMES TO BE TRACED + +extern char HEADERCHAR; // CHAR FOR _NODE HEADER MSGS + +extern int AUTOSAVE; // AUTO SAVE NODES ON EXIT FLAG +extern int L4APPL; // Application for BBSCALL/ALIAS connects +extern int CFLAG; // C =HOST Command + +extern VOID * IDMSG_Q; // ID/BEACONS WAITING TO BE SENT + +extern struct DATAMESSAGE BTHDDR; +extern struct _MESSAGE IDHDDR; + +extern VOID * IDMSG; + +extern int L3TIMER; // TIMER FOR 'NODES' MESSAGE +extern int IDTIMER; // TIMER FOR ID MESSAGE +extern int BTTIMER; // TIMER FOR BT MESSAGE + +extern int STATSTIME; + + +extern BOOL IPRequired; +extern int MaxHops; +extern int MAXRTT; +extern USHORT CWTABLE[]; +extern TRANSPORTENTRY * L4TABLE; +extern UCHAR ROUTEQUAL; +extern UINT BPQMsg; +extern UCHAR ExcludeList[]; + + +extern APPLCALLS APPLCALLTABLE[]; + +extern char VersionStringWithBuild[]; +extern char VersionString[]; + +extern int MAXHEARDENTRIES; +extern int MHLEN; + +extern int APPL1; +extern int PASSCMD; +extern int NUMBEROFCOMMANDS; + +extern char * ConfigBuffer; + +extern char * WL2KReportLine[]; + +extern struct CMDX COMMANDS[]; + +extern int QCOUNT, MAXBUFFS, MAXCIRCUITS, L4DEFAULTWINDOW, L4T1, CMDXLEN; +extern char CMDALIAS[ALIASLEN][NumberofAppls]; + +extern int SEMGETS; +extern int SEMRELEASES; +extern int SEMCLASHES; +extern int MINBUFFCOUNT; + +extern UCHAR BPQDirectory[]; +extern UCHAR BPQProgramDirectory[]; + +extern UCHAR WINMOR[]; +extern UCHAR PACTORCALL[]; + +extern UCHAR MCOM; +extern UCHAR MUIONLY; +extern UCHAR MTX; +extern uint64_t MMASK; + +extern UCHAR NODECALL[]; // NODES in ax.25 + +extern int L4CONNECTSOUT; +extern int L4CONNECTSIN; +extern int L4FRAMESTX; +extern int L4FRAMESRX; +extern int L4FRAMESRETRIED; +extern int OLDFRAMES; +extern int L3FRAMES; + +extern char * PortConfig[]; +extern struct SEM Semaphore; +extern UCHAR AuthorisedProgram; // Local Variable. Set if Program is on secure list + +extern int REALTIMETICKS; + +extern time_t CurrentSecs; +extern time_t lastSlowSecs; +extern time_t lastSaveSecs; + +// SNMP Variables + +extern int InOctets[64]; +extern int OutOctets[64]; + +extern BOOL CloseAllNeeded; +extern int CloseOnError; + +extern char * PortConfig[70]; +extern struct TNCINFO * TNCInfo[71]; // Records are Malloc'd + +#define MaxBPQPortNo 63 // Port 64 reserved for BBS Mon +#define MAXBPQPORTS 63 + +// IP, APRS use port ocnfig slots above the real port range + +#define IPConfigSlot MaxBPQPortNo + 1 +#define PortMapConfigSlot MaxBPQPortNo + 2 +#define APRSConfigSlot MaxBPQPortNo + 3 + + +extern char * UIUIDigi[MaxBPQPortNo + 1]; +extern char UIUIDEST[MaxBPQPortNo + 1][11]; // Dest for Beacons +extern UCHAR FN[MaxBPQPortNo + 1][256]; // Filename +extern int Interval[MaxBPQPortNo + 1]; // Beacon Interval (Mins) +extern char Message[MaxBPQPortNo + 1][1000]; // Beacon Text + +extern int MinCounter[MaxBPQPortNo + 1]; // Interval Countdown +extern BOOL SendFromFile[MaxBPQPortNo + 1]; + +extern BOOL MQTT; +extern char MQTT_HOST[80]; +extern int MQTT_PORT; +extern char MQTT_USER[80]; +extern char MQTT_PASS[80]; + +DllExport uint64_t APIENTRY GetPortFrequency(int PortNo, char * FreqStringMhz); + + +void hookL2SessionAccepted(int Port, char * remotecall, char * ourcall, struct _LINKTABLE * LINK); +void hookL2SessionDeleted(struct _LINKTABLE * LINK); +void hookL2SessionAttempt(int Port, char * ourcall, char * remotecall, struct _LINKTABLE * LINK); + +void hookL4SessionAttempt(void * STREAM, char * remotecall, char * ourcall); +void hookL4SessionAccepted(void * STREAM, char * remotecall, char * ourcall); +void hookL4SessionDeleted(struct TNCINFO * TNC, void * STREAM); diff --git a/config.c b/config.c index 85a55cd..02b8d4a 100644 --- a/config.c +++ b/config.c @@ -119,7 +119,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include #include @@ -512,7 +512,6 @@ BOOL ProcessConfig() if ((fp1 = fopen(inputname,"r")) == NULL) { Consoleprintf("Could not open file %s Error code %d", inputname, errno); - return FALSE; } @@ -1532,7 +1531,7 @@ int dotext(char * val, char * key_word, int max) if (len > max) { - Consoleprintf("Text too long: %s\r\n",key_word); + Consoleprintf("Text too long: %s (max %d\r\n",key_word, max); return(0); } diff --git a/debug/bpq32-skigdebian.pdb b/debug/bpq32-skigdebian.pdb new file mode 100644 index 0000000000000000000000000000000000000000..b226d64afea49c0c86f00c1c3c43fabe98a37518 GIT binary patch literal 1903616 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∓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 l1{p{3!rmFkfavrW5Hhumbeo80U&xwcV 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

!#KA_R(E#JSe)PhtGihG(_se3=^;<%{ee z6u!DFV)O`!x`b(N+)B8HZrT}#`-HM`zHrOR)g`#OV&ccI4IL(adYHBlzhH8mkxkZ|E1a4#f5=w!i7k zIYr<|M%Ix_GpqJ!6xkAh}lZIr-xDe;}8QBOrepyN4cd80Wnp zW$g5Zl%3Hll|#ZtC&DA4`ro6V;!%RLeVzSV@z@qG>W4XAC0sN9S^>9#lB2hSufsM_ z@-uNXa`T~3a`Vyf9e6x^7phN{ysUd_%~4azzu}4OwTzpGOZES1cOM-*={GNqCKiCU<- zppwq`x5)pJ*(LwSWp?hDAhUb=fJFU3;#|DsXz2m$_3;a(AK0Avc**;HzmCiMI-ia!^ zm#mppi?4t8&Np6Kn7z~Ghs`KzIywK3CeQoH?P&q!Lwz~FvGV*ZbDzzZ&;9f}jg`;+ z^g(`dyPuxOPo8hA{;09?{~ytgKzqB{{)4%4`jY^18Zw;I9Dg1Ck+0O(41|*N2Sd~_ z#x7tlIFxhAjU!tcFLxPvIc=IUEMRgwze$FB&}p&x)3F{vD(&WgBPo2;W1+V;;`-nU3=r ze=8n4!Rv84{!KWNZPo#7BcxfqMl-1T*&KF;TfxKN)^G?!S7hp`)&o$-9Bc*5bGL^V zalRv@OldEjC}rmC1Stat-IB38*aeaY4xcWMs&C&7QpcNI53$#2?4yDkgKNS4llA}q z?fze7-c}`#+Mj zP4m@MN`Ot-|7(Aawluy&a?ExevHc?EZ%u>DaXn*^o#CbMvt8*Y8Pp)D24#S6HwQE( z@J7y@Z?35KkGYffi;$eI{UWrF|KrD+z3M}il@*n`!kZ^wnohs$8P?faKg^ljUvCLijrllNP% z;HSs%vzKqIKEY3K&{%r~KYfCqe!y>Ch~N4HzxDiSCO_-zb81a}IBM;@n@{?+_uJ#g zp69gsJpC19RujIy-sXH7=s(ham45p;_^soSoH&Ml!S;E!FI>eruaYwa>X80I{o~P) zrqk##cwNo9hE5Qf(5(L-3j1+>98{Y-5}pHd;S`AM8oe8$x(% zmDaQ2R$Ey62-y`(=6p9uoTG=q3t%Cf3Ma#9@H}`SJRc(SMyEpQK`w@uK#e6Xh05Oy zob4-YAEIYA!>>I~$L)mE`5jPw%REThoOP$9IqBiLbMtRg0J2X{r=63HdKBl`sSg=o zzYCbS2WW1fVO#ap?X0c(^fUNw04~-4|Ni}dbIP=-E+hqyi!wKP^$6vY)-A57c%CmHz<+>04u|)=MTvp(pxXwUf-^TA8@*y0_%o(i3 z{%%<5G89KoP?kJx)+F$|yOhn^wNLS^^{F8o+Cl`BYd*aP~J9hF#59bQ+zk|tp+a+&E11oR@#T>Nz$<~UKh2D2t$H%K2K zk>0EY=m7h1E*(o2><*=C=>t7o%LvZ-+vr$ELFrh~YZx8NWH^>{)Trh=jQ7D}&R>Az zAg`{`sW9JfbSkn5VLm^)KP-cXK)Q9a*8fx}8<8qF2}-AO7MuXjgA?I2cpAJ6O1E+q zJR3d&qx}CCoD4sP=R&Rdm=1R(FD`+5!t3B);Pr4Ulx{@xGt!OR4F3vcTXH8{2=9Vg z(=i|Z1Ktg_#*?&0UxN$bFYp1lCrXwFVQ07q9s(bQgW+RPHYJb4)8JF^X7~^I6nq-K z4VOagEwK!$<9`OWhtI*z@Ojt=z6g(nFTp&x9G1hEVHCasr$h8#(QDy9;mz<3cn?G$ z7JU@H1)qa&!#ChN@Dunh{06RqKfn*+u8e0tf|>9Wm;))}=t=N1cnbU+Y7dJq;8eH< z&V;fl(Rg?*{FZt#2xrG$W}g#B^_HrxdTNgKgwy%2a0e(`64hVXko1Pssc3&F8xq|w z182uk@jWidMyEYahipqohSLIefU+_1;$g=_jxzxN_=aZ>s-S{cbY-Uu9`^O8=MA|E2VQDg9p!Uwitq>HpMxHI@GFAg5)k zwY$=1(Kj}8g2BkR^1+y7Wb%xWp`!lf#Ut6qBGYWOf!<={dxd(spw{ZNIkv}XoU$EG zHXqCD+kD9Uqljl2oe04iI<}CfpUuYrR~Pf?*7njtX?DH%q3|2hwaR^FkdO8GB}{su zP6ngFr_^plm9om3j56|!Qc(`5- z_;r(?)V1ZmUGElja|bw?nXztlIwRQqe_m&RxT9ou84cK zK&QIl2JUS2mKVC`gWU6F?zx^Xz2?8&^9|fj_ZZ;r@j6?Xz@dCsJt8}tuQ|yWW^IR( z%5+#oJS#R?pMHBD_i5G3JfC-l2r0hnh}T*oIE%q)dRp;@wwX0;$9H~buTKuYc*f_q zVM}lPA0OTN`P`=ru>ICCHI>fH@B1hr6VLiOyuS@lJo`L$*;%JA>NRg(voAlm{+C-P zHl5DQPdB#I#HYT!&@AI$-;TvkXXd9HOEdTR&vjsax~|6Rt{SVO^0Sj^>^i2#+AsOp zDf!ut`Pn1+*_HX(clqh0zjdr^Zu>Rc-{sKnsIBs3^FxsBb&d>g>{GfzU59SL?VHdS zaE-3p13z>X=AfB2IxE@b^u;FvkA;=+csL231*J1O4@UVPG0NzOro(eNzZUXabPhx` zHFiVO;3Cd1f#_?@{v=Ppt2kcPebAMa2VVP4u?^=Hk#(F`hfn9 zafZf%o`=6+1V<1T$g9~Hqz;BYNK|{wD^7Q2SMB5%uma63ee)EEU zGM3+(jK<1deq;PUyX^HoW1{r5oqt(8;-d%NUz_txLI3AEyjJ@A%TkTCGx)zMGyVT6 z8`<{3wj0T^(SbA{YD*4+YEKS^{b2HbdW#WWG@ke7<(dZ?w#(OiTPL@FxX z8Ne;J+_jZMecBpLBs0`c+kc0a7FAYMRg9|+bR2PDU^pKSFDpHr?VJOd6=e}VeOT!f zO)Pc>+GWENimEFr!c}=_n1hAk;=GaBLk6m1$s@0-vdAe4JFGA+EG;jnEDcq4nbiAG zz8;k~p{TH;qFUa0rQH=e+$DQVwo_DA>~t;)bS|$5SB$Tq>V|QV-c;G{xG{mwM-RlE zM&)G(zpU=v__e6-L@nJ6R$EU#={F?1|FB^HVOhbU{Goat+wE^y?hzT2GJ0fW)bY(4 zK6)hn*1L1ou;C-KM~oVrnXN!W2V{=43=Zv|6U-VsB0G~8%ovUj7?P#G1`gu^SqjZ_ z24(lpk>`lqOkt>A#^TM)s=UIQQgTo$u)}%b(!!#$>f$bu;zHd>cIv~14;(%sGnlb!m{$RbszRhbVEYEJ1)6DM?5?wr$1 z_k36Gr{DkTo@>3j&hJk;eVMa9*dAH`6Sm9lwI*w1d@s7+d(Ox2 zTVksipEb00+|PdcaFgCcx(--yL?rEodwvb>@$=9p$Xma)@{Rq*p5NTgsph%uZr}Ru z4w2X1+xe!Nf1mwD9{S^3jQ^?Myl~B}tH;kg((z{yF9<&?pqu<{gUm*+{XT6#{ZV(J+{hzW!>M; zY`4X_Wv|>DZ2TSo-5AWLrycQJ#~C-CyZ2G;D(-kow>2@4fFg=kuy5-`n3?dQv!j-|x14dEhtgoIBe)&cBTRm@ALDWpZWvA zXPyW(Co~Bj1u;1^dz)Pd)!tqSnI|%P#hnRn;{0rQGmJvb-Ds}pZ*T^@2VMj3gH!>t z7aa38#{TzF_yGP(A=4+(X4S|E2o>RR90~W&hutcB3ig|9F8aLviun zNqqw_^$oz(Hvm)L07w`g{E_$ni#K0QB>uO<{h0u3al#d7% zRa6$!>>|InY?Vfs8D5yo3KVpcs=W}p$IkqIW@pL9iTB82JZIy22+zKFg{y~P{gQ)7p>ik@XU_M;+6VXVv~lp4PmPY$0jh%zD~A;N6@DpycfRq2%mNZ~^QNk+F>&FS)xv=YNNT z;6jMqquGOf6qNiu8a@b5f{(#`_&8MCRtr&;I8VSb_#}+Lr(g|y2A%;Wub&SkuU`hS z-!yXnKJZq~vEz&qM~5}cQDl7QK=?E~2ukkn0+HpN4EQ1J1J}UApyYnOZyr@!+z(FS z+>`SsA%9EPz-syXmyc`i8FK z#s0tA|2L;SX(IXG4)0t8;FSEIlK)fke@g!U^T_{h^VLNC|EA{t*U|3gH%rNRdCAwH%|k=O<8hd$z1VUESV>jRR=2yPtL<|8&m256L4L}ZH7g7S8Jm?VA5s< zo;1k~-fc62r|f_Koc2Fn^VLMdf9Cc!KIf+Ke=XyGzdb|z>@5B4KK<79`K{mc(`Wt6 zSg4`BP5k!6@S8vKvv>4cx8}Ew%WsbqzwxTyyqMo!7JlQ*MMfUEd9TxMpV4+eham@T z2!DOTXV?dCYCpYw2KVVZ^5J7HdHC|Z>EB#oU{YOLJk zXK$Kj@~8fuEq?Zb@8&{-lfY}o!trghZb$4|Z zlX*ihK1+Ej-x4sx|CH`OrTb6m{!_aDKa1|)ZMvFj=)cLazqjsh&Q@x5v2VdrPJiA3 z55V4SP7ik-S2!}be5AYXuO)sn z(dULE!)vMs7dzU=QumqV`6(cW{Jr&niThjb;jRh1n()C$mH7~Hu(+VQz`X{OKKotU zOui43!}nqEm#tprp-K;SFT_XZC%NYbyXQZ;=jU->{k}&p8(!zKAvmA(lxte{MfHuA+r`z*Hq(5O2^Cm zCzel#5H1oaD)H_wJ4WS`!WNZ9hq>a@SB(ywn%d+~~J-*7}} zqq4E`Usr0a=_JCn)j!Mqsx^DoxSBRKzXb+cFN-%8y7bMu`K?>)$=>ubS3BU5Vx^Ag!CwnF&j7aIHzB7 z9%qHd71V^)ZmIr&hK|k3|pPE z189xA6nB{NyI#E1l*ik!DfTk+Z}a%y_42%-IC^=ldQ_;C=Tw%K6_{?ywBIUszj9F9 z?xokOjehO=V?JduWJep}(}Buj0`73q92j8gyVrpZ3QdalHL7PDD+|>T{ciiI_@&DG zUx;&r--=3XpQW}}9kowc71+E3T)r&InSOYt1CsJzeDC*72tzb3GA{!dqb-gw&-zi-L;eFxod4xq8R z{-vh8>hHVl_l>s3>UPu2{p;&+8>=gqje2_8hO55cYuogqS8_k@S-5@wZCM=er~95| zo?BnH?l)KAHwVyEI&{DNyBn)p_tTX(l}_Am&ckm`;#-Gyi*cxxORc=BH5^^>p9yum zKv$!7?j@)$ayismjTJBeUx%F_`x``c-CM8^d>3ZJ_uz@}19&Q24bOld!>i$^@NURh zE&34r5-iICG*YH62 zGwckTZ$TcyRL9G$F1!|3m^ah7RmD3EIm4~gI zta4IUGzF(4mvAblQBdVH8fv_70&EBKUo)Cy;BM z&mn(1U&0OWE4Ury`#szrQg@=G;lE%p{1ukNv@M+I#gIA`y%lZ+?}6LGry=)$co5tT_J?iYP`EqHgL}cTFaT>{TR0iEgIZtN0bT`(Tl8soAp8J!hCjnD za4QP-5V#9O)f1I&0X2syuN?Ajm65Zp{H^lZ8ow-@j^TtOxN{r~z!7i=%!Pk}V_-4l z`;1Wyq)vdU2Xk;%X121l?%N8#nK&I0!l@pd3ipGRQ1xIE)cwwYst0F6)q``Ou00Pf zh8Ms$;Z*oFya=v?l!4i||5DhWXI>6@hny>5HJk;f!mFX;IS6NEV4G&eQ}5^D^5^&) z;mAgZvWy1cy|6R9ANGb1zya_-dRqO3%-5 z7sz)oO?o!C*GKTPGNM`6XRhB4KgCT)dNccN(w(8=y&F6Zwt>gP-Qh`)?`oR8diI4E za~^<~!M5-!*a6-Q_lJu27@U>iY?&zD8jq{4>F7&1mCIpJqSldcf7lPIAL|d*UJZap z!AzJ1v!L#ukF&Cv&qBL)DD}s5lfsrE5GqAC|xi;mJ_4 z-YHPeQT|!^$8k>QZ#^e5|IQ+u(s2${`#%{f9p^!n-4v*JpASdD3t&E+3RN$rLDLRH z$y*mgFMU?Fp%1`6Iej-0PS3pw>bbW-hX2m3Q1$mVcr3gf=D|DQSol|10Plp$V4@xG z+k0DE-gMn)Y??C$>ZV%X=zV{3;q0{7cPkRVLD@XOJH|CjU@mOa*4h9sBm8RqUJ2L1 z=It0`T)=n)nzfJnL4MndM!YvJA2$Uz54Q}r2G{c1G$#X>gR92P#x26Fz^%o#ye=)( z|EJZh|Ed1}f7Ab~ZEm9Z|4{h^YyF#=|4+^Tr{@1t^Z$RI`F}NCO*Z|HE_dU$zg^sK zYkK?b45Kg1pdCIOZo3m>zfd!WdB^dgYT|{zbk^;mcck;jq{pRi?ze^fG|&1xy69c` zTi?;VW}L2f^c~G^`dxmr<9^*6@YCZ>xJ%>Vx;Eg~Lw-AOzj(MlX8u-V$ru69S%08h00Yu-utlkn?oPl5w;b)k1+WB>TPqP|7_DPCV22F6%w^i(#tnH($$h1nrFsLkPvPEq(x%@cU@ zA<1dhp2=slV$bAIL1p4RK@ImrA3wgj#Lg3Vbzr_Sj^iSn3Skm%0P$PSKtEOof>kCy zd-0ohs1D3dssk%{w(5Y&CmR95)`8yUJS%qIC+Yl9bKW=hdoNQD;^$pW-OxSbem!IJ z`8AYpWJIXCrm}ogLAXXke^Vb?LX~}C&A4%F>mQpBQa)6bo*o)po}0Q5vf-v(dY-fU=)QI(RjlaW?<)kK2$m^e*U{x4A|#-&+aScy=hs*C@}rx$RBV zorB+(Yn$N^pPVe}nuJL0GhSR1p~0cDaj5=_4IC!Fy!x-Y9vNT@S>JwDxtlz+ej^i$ zTG!~NiREm|G@ZLJKPEwy;|g2`E{YpS`~pew3l)PbJwk_i(-=C(c4^5GFy=!WRy?0G< z(_F3B_9)$Y|JG99I+721|DLvGZSvUfUGlEUbFVoDr3=|+uF;htth_}(}QvGlZU zkDdI{kr#hG=Z&$)95}CS`cqA}XSm<|Vq@pa_s&*4t9m@yYW*qSj(PjZoH5_-*OL)g zkAVE^?{&A-_}8D8ZR~ux-h+BMz2D*w<~#?p4MWYd57O}=b@S4p>OiMz=JSTZD>)yoZj~^{LFPQ!`^nB* zjf4TtkB430DA)%w#}buXast%z=Hcwv*^a68yq)m79;YKnIQ1v_kRoyDsH1c%if5Ff zQ=e}1{pZ0$IiCWLfK%Z>I34mVjc=l*a28a%eJ%Vuya|2}Z-&iy_FSm_%5H<2eXt6(L3A8J171E_iF4uKZggwHBja8 zH5>rf!a?vmI0XIxnakB&Pn0=ZGtWH(I?M}dOq>Q4Po>e0^BhOxKgF{dY%hO~y$Gjs z&1-91xQ~0C^3RUH?6^$VCFUP<=O+I&f35uM4*SENuo(7*O1I{z&w@w7C_D-(UJGz` z%wfkOy8q7jQLg3~NjT--7^wUF1uFkefRkY!ya=MgH+v=K!!5ao=BYK`ulebn;5ev! zErFRZ43CB7Q2AE@HBVOwXTmCos@|!Ax4?;TE}R6Z%hX%;TqLuKVd9*`^_+Y25~}b+ z25fq-k;tuS&NSS7+!EYs+y-159+;jsYR;WIeH-X8ySByPwI?sQ>m0`0yQuxx{BMk; z`u~l8Gvxp0{=YeOxheGjp~}iKR(+(_|E2c+NPYYBPx9^0Itzyrl=bdLzVQ{uk($DA zX;EHmgWvyKd;fY(SCdTuY)b#{efzt;1~wdz;PmH%xS!G;CxDTY^l_}P3lh1C%AlH7 ztMB6Hh7O~XliovPGwC5hWmX3yy@$>{9jd2;5AeVWg)`5xIt=Md6t*E9h8O1saiy7xNnv67oL*4DVOx_}-5jnV%2GQXd!9b!k- zeqn6Iv;H!CV`Xwb8M~=;6aOn&J*E5mZQmFCEw<_r-f`Q8+P1Yfa+}(zVw{eHk(Jf7 zpk6YzXx-oeoTHC0vTQFXo4(%gFo;|m&4m1IY}L_ML>F@2530@X4<*ZH;A}hSIBFmD zT(w*6aXL;UoX+#1;!}`too&;s?k4#<^bO`Z$;3pX&cN^6~LHo1i3oQJZ3+&)fL?zt@+h`hUBo(Hpa*`v29<24`P5 z_mlg3oqOUzopPslq&-20)X*BarqcOK{vXu@`~MU%~ zJ-;-;2Rv>69a>scSy5Fnt~$_h#DRg~*o%~vp3Y7Hfy|1s2y>d1PSM2T&E1R0DGEFO z+a|H+QC?~H-iPKDg}Y>r$!3#=Vy81ZDap)Zd_|!12?#PLM0y80kM52e!#0uwai{V1 zcBJ^M?%nvcsPDw0)E>b9!5+ZHT?dqwlh4_Ms`L-JfAk1?mVtdYYiH2Bs_NpBNM!Rb z_)oVI^XhS+b8b#*ThZUVt=Rw9s)aF5Rb`RmwS-NkR@mgYLf;b{%w(I^dN*@|nU&R9 zh2w{!Iv85TG@NAomaR_Zw=DB}aYeQLpVqlWtJ{&Qhva0A9+^8LcAk@YeC%A;<=9ik z!`5DB+19!%;~fUI8vC|{IXpjqtfrsR6oHW;zGkS;!VapaIAt^gZP^tu?v5SOXH0s) z*fJVF*)lFc53ws}vRf==kjyX|o1N}G6hM9@alf7o`0*4QZfrbU_XhkJ?$-;~)%wNt zC|2`g^&>6$aR}*)j4K}&ny3`n@r!4>h>nH4vvzBP6prxHxAZ{Rm_c5h&iM?JH}-yh z_KV&%OSq<(Yqn)ll{_$ZCM!2XN3&Q1Dh}inqgRqFa|M%XQIj90T&NhfTt@TTMrdZ~PXAuSAu+9rgJ;8(xF?tDN-dG=MEVz@QQNuD))m#mbuU?co-$k4 zujl-Wom5r@1HH^P{_n;7V}+yqUjKE>U&dx8zokR--ha*LxKUh>* zJ(OKu*so}GX?bzQL@Jx=+L(5Psei=sUA9!eHQ#L=vlUNiYt3_#>z!;pMFgs9Eokj2`W2`F5I@eN!y6oH1wO(M%Zw%YH0Fa zW#wM)Ra=w4o7+aL%kG{Da9Kd~&1K0DejbmY!B~)0MMdJ#l{il3QxGnDW#wDDnO5dj3hirl<92 zlb`fWxmf}E*S8Py)0sBbKB(5DtG+#5V|BiMx=Sg;($jX&4gFC5>>evSZD?IRch?7Q zrH|ZG{;QuX-sjQv7wviP)HAl5_x9_#*kYh~OHYd)bIv(OztZ)H%Kdjdck1Fhe!}L& z$ha?DGV-DCzqt6$+K-06UVKZ*PxR9j`q@AD>GTh3t9Uy92tE3@OWrzcX7e)( zU%7kYyN#bSxXmiO!z%Rl+5TUOqL_wII_rKa5L+x_|3SNhol`t8@T%H-#Z zyS9D*o;@cGI{K2H(T<<&^d_&isSnTZd_wc3XXJkqnf&X`Rwr9}q|$rQoXXZCPuqR_ z*_T(%I%h+RCm72aJCpkMCw}&Ees-*WI(5=>PzF_p{ov*$1xYtiW|7+@1efvj0dssg^M?bsBMW$Vfy!Y{2J;r9u+J286 zZ;0IZCI)=cP0P;Pj?3wT>8IKMpw7M+M9=8H8EQuFBz!*Hfolq38(0MQg$NSHE|BIi zn#DQYg|SCtC~Wpgp`VM6!Jn~jbONk|=fEmB9WuZ(_Ej}dc2yIg?5ZZh1yH)~$KV;T z7E0&+0X!Ri1~Gq%u7l^opWylM-*6h-jWk^d+rf+B!Eie44KINM;iZs0C7jFPiEsuS z2QP;ea3*9=2*|R}*j%MGL8{u5e=fJz+&F~?33w#pN3`W^&!PqCV zcY?7``~=>P|5`X7{t9UhqT5g&3!vnud!fo}5YCR*Z6B@jLX3#*3!(jBbS}HPmhcF8 z2s{cN3fX(Y%wY_Gx}VC>j)mWai13VSdtr@#P38S(X zJQ7m(&Hf3p8>G%Tr$OqJa|WCT&w`J^v*FgH?_5Y4%>Fk$;5D4H)Xl*LG0HLn?OzjR zuLP~rh&~DLfX~6d!Pnq@@NM`2{0Ke>*T9FN#^sMduU^?Po~>6oJS(wYwWl0)-Vv%^ z9RyXcIziQ|F0c>8&+Lb?cro^lxDB|~frjUzynPuoClw*%$B+Td_vPKgHRqbWs6@H2 z^`qESUSPk+wu1BNZK<=k)i~|LFp2A?;{sFTdog5iO)jn)HxsuAcjc7_zn^>Kz{!tw zTCkzR54;mRCq3=Utsfru*oU9rvU_g3Maz$UgThVq|CP5gEN&9}KdbGkPTBvZ?Eg~s ze<}Mv^D&bIlfu&S>JSTi!+CtXq@sd(b-xLMc)*d&2bNVZfmfdJ?Gx8RVM5VHyz}g* zP2%PY-Un04E6S@%N+b5t+{{4dL`8jd(`^8-srmms(6<*cm@w63M0U>b5xK!Z!-pR` znzq2>8- z5iT~q(sivT{Y!|@+4zrg<8E}7=*Sc{*+$X3W}uJQD6$=4+2!`iL6h+{2I z`LG6eGWjs0I+owDd@#yTepfzdjMTufS^3b6&bP$9nU@dpBOi>f(!}Js^E~c69x}_s z#nt8d>+fmz z#OJN9Im?s*^Hx22>keiQj<}zO(8+!pUK_u&;^BJL_v@Ka->-*652GvH*n1aWH*WRZ zN{@L*ljdn`xcWL+_xd_ISk;}xc?#*omy-{6o}sR8-Td~r_nRIXSD35*uV8*c@hK@N zFAj%_&7Kh}@w4};uU~)rJsaMeBhvj>;g_tB?`HC*S*z7#&LA4pXE`5-V5WCO&wZZw z>bahtUhkd0SCB>6bMTM2VO58hxnYxa?W$+GPS2Rnb^qkL;}QD2XDn&p8AAv+4|g$P zgKk(oqjoc%@u{xmy78ymXK21>k$asQX`L_TyoSFuFPRv>nKW8I;okva)QjH&!Upxu zmfJ>N`LYl{Gmk01(W+m~T4~NFkLw3=ebBvLah~r!yM$TMpj#l`Gj+`j_nLnuTrT&U+=D4@f$n;-**ap6>UHd#?Up=Q(a1z2BF*=Y8nw z_4|kJx%d2Owrw!+RX%xbTY&iM*k&8swtY$7nfXr>J}p?MZKEvJZdOOlCFUQs1^F9k z+s-^E-nQLwfyp!4ww}FovD&uV<9@OBF2T>VPYQQ&JY1~3OYn=eiwS-`^bD0dN%9?2 z*m*PCnX0_~+P37mK;2jUo_qbK+qTQR_N9TgjXGxAp3QIDlG`e!Rq=oO>jv63{F2*3 zuWhTv;J7`BuI2g&3RmyFexCRyw{1&ybesTTP21*%RX!|p!}_&tdd4)aGi}@C_%oI? z@QjXxn}RcKn;TZosNIZbEabW?Nym7UVoHZ;%iQbKwt4M?;*-yNqP8skym)*T5;nOl zTZCV7TegVjbmV%qWyiSJD~=1?XEoH8>6$$EnkN#jneTH=Vq9l(o!YW}r`WjaS@V3Z zQ(LC=?X?qeBNHxjuTkBY%QcB@nZnNI-fal`F8BY%4Xb>c&3miz_xgnEIrsVm^_O!v zcWnadbg+uIoZ6trQBo$hk=wa-rqXT0x+k36vBQs8}1jj{KX=p!fG) zGvqt37Lfk3{#qix`P8K*eEs=Fzj?$}`{@4l=L`Mj=ltf?{O0Za=9^D!tLN6A5Bo4% z{;SrvdE>q2XZ~<|m%n%Y@t_U#Ab#^|e)F|{^XPu_e}41*e)E@2HDBmAUp~u}V}1Jo zKRbiQ+6VZ}>-){)`ppOX%~Sf#%lggZ`swK#YX{&rkM3vZ5wh=By|r$B^W}c?wY9sd z-qyFb@Us{3vsdt&kN2DJ|JJegmsU4qbw=!SVPxp`{H9|xV!iZ8V<1CBv#*P;%g0$= zkJSO`I{luD(-9&3R**g1l$N$4%iyc7Pe zF2(9t^c=c3GoAxxe-}OHM#5hR=fKP0&5*fQGoLDX=Qhs&25*N?!aLwH$Zuvp{=dSv zIlmKr2Is>y@NTH|_QP5IhShs09$Vtq1E=E&er^uAx0(NX3hu)B5~y+FKcJW2Rxe@o z70LPiI^mSxE1|}%Z@AYj+rrZ~P)_`y>qhbWV*GXdn{d)i(BYczUXa&je|ycRmgC(F zYE0D}l167sxD-+r(N|#$_!it1egt=eO8*{E`^{*cRP(WvRdgt93sHZV@0LX2ew<$d z+r!y#e|Q5t05a!jzF#sQQpQo{8XfKbQVY95K1*lze;EXOavp_!;03TRd>bAPcO_$v zgnPk$Q1h_;VG$&5(d%F~ybT@=?}3A1UkZk?P&5+`foH(s@GQtYX7oWg58=Fa+;`RK!!_Z z9(aFx)XdIqEnqEq0x@KVT7D0(+MA3g{f znna(17eVcbIvu_TFNJI1<#2DbQ&+)G@M_oxUJD1o>)~)nnMRL?H^EEc&G2zZ8Ao4$ ze}%8YyWqRAYb!Ae?o~qA5>RWUCb-YG6)#rc0qv7lDIJgqh zwm5GM5d@OyY1`~i};&X15` zpo8r|lss{Mf~4K~1(H_t{gm6GzMDdtoEGp9>gP64_9FUTifjOOgcD#od>-xuw<2w= zq3ktwg?wwmd_QF*+@14_;oeZ+McEhf%>(n@l!K^`*rr6g!~NhXumdcE2f#aECwMRH z41a=M;1<*`;uh@-d%+xt?L+hg$R|ys#qe-=5j+ykguIK;V3v72H{^|Db#mZrolWo6P^Tb zgaz;}SOgz~A@~w3h2Ow1+@JbY1`meV;zSRHmGCe)5oW_l@CtZ3oD0u{ufwz98*noG z0GX^)j_^|06J8Fp;FWMFoC!xlY)PWi;cWOQ#HJ+r9K0F63~z;R!`t8| za2}M-<*#r7^>ZN=dj(GR5$j+&6}%J9TdRBh-tOa zWHT{@>(=RfU*11l^MJLNSOQnzTC}DA;+p9&kFcxZI$ZO1=rHkL4V&*rp7GmE+(O(6 z+&WzAx6+&*xHRJ2gMak?@*1c5f12W! z{>Nso@cmC0?4}m!rWWbu)iDeDZT3G+Xa2+7TFX`wc4J(Ig2QheuI9{Jo8RN>a5rT) zq;)wE0Y#uNnTvr<|KId2F+8$}nMB_5e z1ti*+Xs)B7b+~38i13;tS&W?1hP*nR^BE>@?7ZUJ|F&}^$@W3sHAA?jm}|DpwBuyO zVWrPCnltg{WF=SVI0A=g<7jg8GUhCzCO_(~!{sMaChjFVO*~AQ=(+kGU0j29RlkRQ z$L6lF^U~a2!UIPK9AdA7*kNpJY8yzi^j`kRHoj$whFrt9qUOPRnYky zp3Wa@-SpL4^V;90F^9RR1`hM)%64dT*St1V#@4Uynpf)=x8c!TpY)g3zwX?%<`h?d zW#56sHLsuIHX6zyzcsHmP7SYl?MQuko;>eL zZ3v(Yqzy8*0IS^iCeKxCzOOF|A%!a-?kc-={JntZH+KHcPY>_6ZoqGyXKG&0?(^@r z9@1}K&u^VuQ_bu7%^RlX`-Z%F&0XmFP<%AB|9EP?@1_TiUGF$eHQ%?fdA)fwWOf`! zKSEy<9TdC)@}X)*KOMIt{L~KyfpSZ+>{pjdsMqRKMZOddjw=Ho$tEM41Tj? zCp)In{ip^`dz_B1s1iEw#C`UIo#D4|70!-H>{vzD?T+6HoQ_h$Y5byjEL6)IUC*ev zPas?_oCv4G)8JL`40taRac)U7L_(x)fu z(!KSEo<6-HJ-T%0-uIv9q@_6#o;MS>0Jj`>;cCg9q2r~m<<#ohxU+{ zUH|td+xIV}Mog&@f0G)K_cFGOP%R)ENIH??!p+^l^gr20*xR?yZ=XI-C$mxm4vvp- z8c)26+aG;Iq}Y7-vw|<*7F1VM28ceN9Jcxj=|H?gy1_Az8{MGslD=XEGX1Wc$)-Ou zWWIxlZqR-gQ+@;Denyw4@5jdd?Dsc&$HM3v!dteApWE*i>zcSA9(rubNs)5=%=4(tp3B#j?zzFsW$Py>e|Pn z&yF46{fh^#|8Y(8{HGafn)uh>cc`Sh(lw+oWAYCBJ>2($DII_M^-O&v(Ig; zU9+EktDoHOX9w+P@9bya>Sqt@XHV^Cr|hRM@v}?yvwQW^fAlc*zrMYBQ`tNF*)RKz zpHFP7dQ#s`+)ocQ%f!FF-L&MQh@5HnYCim5Ihtf3=e~yVK(dn zhd>&&=<%=zyaN)}?1$4EF5?_szp=+e_aE)Vxo7tyn;+TwG!NK)P9$rr#>rMk`f z4-<+CD=HFx*Q$JPMSQq`=CP^9|5`4S+W#Xp|DW3bBenm>pKSjR({wf2M%|m5$M@zE zW&f>1Q;MjxoEEsrjA7Se?9P8?1Mkqdf|_u3|1e*HAEXVZ{pU*=@^aLfBQ}@W-I_JW zCw-$CJ5R6!XZdMtUxOzCFJa{^1d!Q)@zOz|5iNNmjUv8`=F` z=oZ#bWPqK_w|09IbJ{6%5SXeHx~696E4SYnmuVxx!{S# zZ!UP^fYC1;{!sfl>#+Yf`FYKld-D(2X3w{FubmoNkT>8W?7sI<_{^cR2Oe@%t7q5r zdb078yow{uzs>?Q?2dulUe*^9KW%;pUmqg+xp3# zezJ_8jOiy6`N{s05j6g{ZLn>dHFu)+xjjw?^EGD9g>YtVmt+C&I@^A+ZiH~j*B$9z z*B`3AL$o)t#1h8*t8mR%$JYmVYXc-Jcykt>?f;U1-5XJIgJhJ%QgbE@?4oH3fCuIlR`A;?vkox{lxS)J|L8R2Q4yieT-!dlA9TK!c~NvPNyk2>s`GSCr zTV!BqWmUD2cWk`%TWj~XAqBCY%ze-2w}s}uRi}F*lKYts_8YO^Qu3RV>EeDnX|=KZ zWFCe*P#@e5H;4P?9H6D&X=MeH`0$g=qu>1w$#9Yz6JgYs4R*HFGH|Evvd-F%h3uL37 zOA*0Aa!lUt0#zsbQAt!c)SLy~iuqgeHZdOqsw5m&Q;3YCo%5mUVSc@5#p|N#tam6c z)-P22`x!6INhYpW_yEN{!*;D#=%xUgUwl9A$IMebKSjfYdp#b`%w6i*1iv2j!*xU5 z-*A0uiuXbH(0r@j@j{w~QxbGFT8E%^+iS4BcxX*S;(VyeUh5-ln@~NWAZ+8S{VP>R z#tj}{UQrn``z0NFpvgPuT*^ab=CxV#-7;%Wc-8#?Zk{R&1w;KP`b}-tu$nUNVBSUD zeskNgMG~Mm9>GbK^dRm|;*w$G610(t*B!UnBUJrEd86OWvo{i--|HRJI+Fz?ej#bn zJ9)TCgVn8h>o1bmrX-fjkR5EA63gW-@?x#c3-$=Moyn&95~WZ1V045D^Xpz6w)qp9 z=vF`KO1wQu?q@P20CQZAQ~Aus-A$YVoo!oE8H!aG(?)Krd=leT7AiBZ0Jp1CXUsUp z^;6rRxK>$}rqNNV-Shl{6_uql>-aZol|$ZjBCg8!?j~-V&Udf9)c$rlX5iF#oQ=DO zG}gw_XfobZLgjt3O+}mg>$YE>O@-_p4r8LxwqMpZqHpZn+E^$|+^-j9v1O}_SeV|X z9gClLHSJj4Fo&9aX-s)HRsC0+WA)I9ebA25{yNRom~nL7($<^K@LuvX$D}RIt;=*p z;fSd>>f`*5#CnrxFA}TIG-45b$}yomRgs!L(;L`vuJEnl=Jc@&t8zAN>Im=6v-kLv zbSJmf3cm*bfa0Qe&OFzK*P0;Hh7(2C+r&j{Y>Yp*=*g);>$)qEOtA+_T{l z{1gRq?TPVlu`yMGAF^7cj^8s>P#nq6nPZe(WL#_Br`p8l8A;N<1Bq?5>e`t5>-K*d%PI+~2M1wLWBWgAD|le+ zypQ=ketxJq-#7MqFH_&*=kfkB?$>jZb*q|qB+9Revdc<0-TTpI^S`q%|0v!4OdQf| zK9lamyjD2B!!NJp&#R&@`if22J#r&j>y>O~qD&hyo=cq}ix8~n`AEdvlN-}NC#_FNg z>Z%_yV-$@!^mP&UYW=1@)cmG#N5XF^e=lD7Qp|9?k5ilQ5>D^NoI3H!DjaVHF4i`o zp|SDoqLezpxMj)k#f>iBuw-Z<{(Z*RP@vR64L zmVIKovBu4t#4=l?cf-WXD>IYfFGmFr*UTN~auZ;&FvA(fB% zB$fL^+-E^NE_`dFZd?fBd)PL>j^Wq1_Z~oK#h-ak=MnB*Yww*Y#q@yFs?khtdTgYz zj^Y;JMv+3*P1`00@GCTTvVLki65lVcjnsQoIQ*1?(_ms*EMzU1j%ru`GyOmEe(0u-rw}adcRf1g&Lh8 z2iZ8p_t~hhMQF+}u^iW9*P$be`zaIIht&Cr!kJ<6%zLM3Z>%cUlw*5yh5bk6xH736 z6Z@RUsl?n;`4`KV$Krmm{7dlbt~;yT7RJN5`B%p;R=x@0Oj&yGwd(FTwZo;=dR0t5 zDNl{R+TfUfvsR19({<#xUT zcv^$ZaA~N#dU)Z>kR`^-1}eC_{XbSwBBNj{C*(Gr=#GpKr&*#qu-3 zFP5JPekMP?Hsn##L_2BnQR6D3gUGC4{%ex5)U+)P)!7Z?YYur-Oqg^V2Xhav-Y56d z8jC&chSM`uU%md(O!RP{9dONXipLX#ak7*zCa(uqjVcJ27Uxu$Z=vy%@)-CYUiw}j zRAhophwfX0D|Z`O)hXRX2`xk~Q9POtq41U|Zb!QHU{yPQzCXxOZZLapC+6qrf|FD;)?aY`sSv7oxDqC8kSE*Pq; ztRPbU`5)k7{cigx<*lg;p{lCVit=oGwS5m&9{R>e!tbUIsZ5Ojz?$-+YOdm3`O-S( z&s3S~r!k)LD(KGVC|}jp8owlaN3Xu+b|3%;`xKjZI}bihK4rLRrk@*JWji@fm1NQz zFjv`s6vs9X)Rol|-{}hn#@^{z|1>S`7whj5{9^C<+3|3_^fa}baldXE_5Ex+ysD7hE{`QWlGrGUhw~V+fkH>{? z+-232MQk&#(5m~J6PGF6$BsdDy{+?#i|bEsW&IQLX1N=m#4+eI+-Ge(K8!)@$4Bw< zI}-07=x9T|>(>3};S1c~IXc$XQuED#C8EYwp_ShD_A>LY>P_#Yx}P{ccz#fizPLGX8pN@qLH zRrge&g(I;WDIHf@R8U@>7&GNXd-9Nj@H4ELJaglnYtZ|EB3xi>cJcMuWbKs{jkWL1DyNxyS!Vjb2y!syH$myN`*Q<<;2 z+#32FUfwG|y?Ug)SG<<0uVO47tcet>hRI)fx|A0pf`4iGxC-4@aUJMbxo^?I_DNeZ zu*u-h@2DTIaqoVcifd3$xA6_wTbO_K?u|)$_Y(V^orihvUiTilK<~;wW;>KwE3>@i|EjvK*}?|1>n3vm>iSFv|yx?#*H`AJl zo=fn53xA9hjIZwLz0akD(R;X_GZSN{kYuT6~uH4agJnfRH}Ap`xIkf~SR7$xa9 zQ(u*?)?C-n_)&d$L*qyF@zy^+ehlE3JbqMPx3Tf#G&iqQHe2Beanh4yF_D0Y<&$-Yy;5^ndZy%2wRv;m&s0CdW7P$e zR#lf48TnYxRXz1P^j!U(`0ggwEw7K9PMZ4>2lbJ+n`hd3?Da9p{et4|>D89GahIx3 z^4;z@*~lz7&gK`A-FP-oi5aQ+*C{UK@Zzj8^6VMpr+Oqmb3bU?gLF!UcOpaoGB1Hs z9QMY&LmZYj7>D>=Ai;c(&5eh`rE?d}%Z>|{7nIQv$xroHbvw*gUxVc~zFwZS?rI;K z7+=|ntWAn9Drck0-c+7>?Um&FG9P=9#Jp5|{qk~|8#jrL%FDL6_lR5ih`QzJ&5QH% zM%oGeYU`5f;l|>(F?$v-KmFo7!?YT9?^Q3(?-S>K_2O*u(^P=pJ5RkhC)W$LU&-~t zYo{ZoXXe`j$}3z4+=rwiQZF5e)?WXc{8##vmgMxQ9rjC~*GTI9RQl9rd_?-@B&Dxn z+_#8KC(JxR{%%7tpBR`EdK2u#ARIJO) zn4@(M#-rp(nDeJi9@ux;Zyc|2imsW?HIuof+sODhMYcey7Y+3#Ui}|rMxnONQTt6i8q4!ns-VgT$@t)xqZ`TyCDcYdM zwwp?e>P>Q5ymw$lZ+_%>8d*kTktdYAPwDr@REm$7U*fxi8BaS<8(iz=~(=+oMc&6LsnCB+zm$)o;Y^=PJ+k3^ut3$PJ zTvP_Se;eFa)S>jh*z}`Am)=yn6=?BNZVATFB`B`+VCBWe;v`*_*H;Ib>ucyPp6!Kc zqr$~??w^=vvR%=!GfvM`IcY95qfR`HzB5zHzW8~Q@$>RbfyX6W>cvU^BsJley0XYZ zH%^HyOgBj_0l-k$&sV{npX@?YH2!Z@_2=6b2U=|6uuJ7hJs6wA}mOeQ;XyM=yGheqmRIum25Lzx@sT*6I808}Rxd z-T#Eu=iQ#O^UfJTc*2j!_?$wJH z?(sN!rDM%|RJ-cUckcOc?}uBSRP@;o3%4%u-~Ed?YlR&yxOtf zES?P|BU}%uF3t_myWXzvwAUnGzu3M0QK&Kdm_wvOI#SIqgk4b_qI^+ZVC1L9iXN?(xINc6I2(>cc!EL z;BGJnVy_ZC5$*wla35Fz_k~KguI)!c5L?ZfMm;|<-n|H?^WKox&U~xbT*nwc-@Q)j z6LnoCu08%bMiXB5J^}WE%&A9@g!ym~EP%sdF&qO!ump~WS3rGJm@+co6uuFj!uj2> z4BihT@ELe2d=~0k!t3D#_$!gJ2XA7iTi$ndaNU+}oMT`E)o9 zUJWmTnlr#J`V?f|Ao?M^9Ik=az+d2XFpad|09(R2uoavO1Ca0NM)!w&tH69iSKkii z`?SuTFcaPdhr;=A6ucYe!@t3kA>ZGPR>1op-*0vP4ljoH!)xFJ@HV&z{tZ3^AA%3V zN1*nCUjiS6FTuy)8}M=X0jz~zz$f53_$2%nB+ceqzE8s)Dd%TlNBA7fhA+Z=_zEnA zE8#@=7CaZe1J8%dFUQ_9yB5;E8>%;n@99s3)A`Sk;e+!l48VW8*XQ$h1}`;PZq_yG z`fYHzI34Zywyb0r^4fempd*~f`N429<7K}V6FQc zy~D}zJgJ$D=UAwCk_RT9?B8#$&%w{G;8XAn_zcwZ zo`t&qO3K`>BeUzvbbmebdHi)0l0VAdVyJkRK(!0{7V(~tIvVW_CqSij5>(lo2GtIo z1&g7+L0ki)@FqAJ{uQ1F?}Ag{!;tTyN1uYz;Op>0xDx6c#Vx1=mqOY?X9nyFFNfXX z6>ucfw~A?l9NrQ0zF!B=;rx2YyY1Wnd6%7AAn%kj7rqL4m!hx1JK#E~Zx-)N9^47{ zfp@_UkUb8}`}!aZa?U#!B~P4(q2AZUum(N`xyGr5cf+Tk@|1Tn`YK!w^*-`<^n3VE zxB=o9{T03i_t1L;+rl+41AYY$h2Ow|@LPC1PC4Aue0w{M za-YL_Gq?n91>b=!;D>NqxCU+qzlSa1uaLGNx^;8b>A=Gw->Z)v19yWXAm96ro&fiO zB`^R_hV9^0a6dQ)?hpS84}=duzH=X41UthIVHfx%>;`{;-J!nq-2*mXjJ%{Rd;(PfG;Z(oBg{t4A*}RuOLcJ$HLA{s1z`1Y( z^x6x%rmf+2t!;DLUbKU1FWSQZ>;QHBBwVD2ZP&J=zA0WUabcW}p@hqT$H7^UJT~pv zNGSO~7wWwk4b?x6fs9$40?63IDTZIb5JdEF_^x`CA)&+OR_6J-w_R&xWn(>`Vs_f& zbeuytokyW`9Fr5SvumdszHX{}-8A>Q3*GB-xQAT>W!FaOd5Pt43*i)}+o0MpzWZ+4 zu{+^$oX>|E&)fq~hI|j+q=SCaU1LNYqrT|Anj)EtzmA6phqqG;RlZL^mB&-?Xt)IG z{!c?x(aus>2%q_X?41u_R@49Z?`qLsZ51Vzr#>N6)>?lhTl=HcYO}3BG}iWPwP}AI z+p}6q9uXmgBuPRDVG{B`M12VPA3}(dFqD$;d%e%OcYAualjgI2U!RZXa(eBY=bU@a zJ@?*o&-;De=Nu^glUHCNd<~YuH{f;fO;`cnf=|J>;cB=MZi62}X|FcH7H|t347b8F z;C3ke44!o#eF3Wy^3An&!3MnlyJ1(j4~~Z+jriWehLC;Nt;66m@NoEV*aW@~TfmRu z5pW9(hhf~vHjsDG;+;|LNPEb;V|9Sij&y=Up$nEm-YMS<*cDz5kB9SMH#i^iuKAva zC&O1^1pFE@M_IKSy&>s*!!R>H5l=ou>Y{-4M2sVY6 z!BgQCFb>Xv(oeh=(#NyrLi%pjd`O?nx*4v43*j5^R!CpTS`6j7qj7pZlAfm|<)^ul zHM7;Rf_U=0J_4mZSPA8O`zUM&AA@opc{cU@Aw921&eIf^fs}Hk2)8RCj0562Y;YBbNUIL5Y zELa9-Lu3x?3iuqn64KYVu7c~}wNT`m8=%-M&Vy~?0!Sa(S_CJ;TVWBr11^AfL;5<_ zJ@9#WA6yF`g!E~w3ium*1fE1aJ_>um$D#CFo`P}kX_yS3gQ;*el>4v*H#bIq7v+7E z`ylVX)S(<3i6{5r1K1ya37>;op~#2Z;0E{&l=}S^ehR;bTVW;q0sa7gf&vsfc^Uzge#Deb@v_ z|5@ID=|7(UH$gei9-OxC*LMGMy&Z5;$8xMCKdH-gQ1lo7hSEN)hw`4k4oAQZQ2GgP zL1}B>f!XjqI0=3Lr@@clO!x_$13!f~z|Y_!xE0EK{xy{6Z#xvZ<{Q`s?tt>%e+!R; z-@yo23DMD5Kf(di z4DR{b;Aa3v>8)DBF8JHPZtzIx+!t*lU;Fo<7x6-g9|@bo-cY^=QLr8C1LZtY=i0Wt z_U}O=@#OvvgYrEXZl|l@Z*8-#bPeiy(J3J3Ag9UH}EQIpj z6~S%L14F5|64(WL;V@VR(KT69Ai5@NDwKY}G${SJ3*kL*1{AsSVkqtHOt>Ci3gvni z;5HA{_d#sN(fNH3w@!q3;p2H zZ~*KH2SPbdH}cXp&ZOrC%6YorIuI_$m*f`#w?cW}zJ@VyJEVH79WW1m2aDkMa5DS> z`rwanG28<;!hP^#_#6BRhER?$@(P90ZXX7tU}Ja=Y!0Q~j)3!EID8Er3BQJIp+&v4 zgVG;A8p`+a80dnXU?OzEaj*+4fX6}6%bfrpg(pGzUUrA;U=Jwre+2vq_JUGha;@4n znQt%oOMUtG=bq=f!U$UnrQKQwrN8kC%!02%X*buy(WHM3j)Sj5H{1X(gKt8~PwH6P z_G+77X;=N*gYC4N^4_5%RQLB=*a`plQ2IZW@H8lT2sz(aoVI`E+aSN=8sn01awKz| za{e^f3Z4Z|ff-Qr!so)XU?v<7vtc%LL+AY3)>W_L5_tZp#FO)12n*pwa2A{ipNE&g z7vU`UCASGC%=lyoSblQeg+agK1BwkmX9INeg&%=)R zU+_z(Z8>XC_qmC9vosYk@EuFTplXTL~IQ^UIib5H^2%wA3hA7`qDOK z+SV+v{BIFY?(^I5Son@#I&ITMTR?bVy4`lVJ@8-r{)?S%4}WX>DQ#CJ>Eu1%fs>AQ%Ch_FHUjpTMxD3koem0x|uY|MV9JmZ#4W0WKP2{kWZY zav$%2E#aMZIuC!3x6|Fj-;z%Hl{q*$RuL~0J_V(ne%em|ER^SYHI(n#3ve`i5pp`~ zWhl?`I(P$o1>Ol?g;L&VoVKB5;E`V~pn z9Vf?c#1sGTP|ouQlw^mTv`PNYS9kOnJ<0BeaF5JonAy(dv z^fCE+{AK7^a0_r5_8`-bi8 z!o8qD_1_aP~oB|2rbz zT~b_HJi+VgmU^Nq+2hSG%)cPNXre2&xX{BSwGzucCFfwvV7UvdS~fwn_c~+?#Eksj z{rhIP3wp+njknx|IaUw1tB0(8H?f$ltJz)gY)^l-tnQ5)%ihlkxT$QpDEoHzj^tPO zz$xy$tddNx4(uJ7nG_#0GBai*yKnP{?9N!V)8EK+_W6#`8+})Y{=@hGPD~h>AzMvO zc6*D93raI&L$7T1Ay11>N{}tWB_N};gu9YZe2`K1+bh|5@PZq62RG^XE1Ptr$2uE# z>!z!ow(0n@Jvy8@kLwyqi>v#a(kGXG<&SOYe|^lXS}PXv%|zM zivj5TcXEQpd{E|-7yD%I>l|Cwd18-`J8T_v)!glOp^sDHHEo4WHrXcIT+?23dtH)m z&3(H+Ls$q4c_wk_j0at(%(-TLWb=8iZsVh5+M~{7%R7!fb8Y(&Z>Ap}7V`R0oj-f% znDU`#T;0dlZEGt8Oq0!blSGMs{B6g+p8Mp0ZI=|@F*qS>90ICouRD`%x@nKPRR}&| zA)Zga`Y7t$xcM#Gx4hM}_(K#<9aTAByE^TOUEf@F-|8=u-_JR<;i>3D8RQ5H`7&hb z?#e%!KX7BOXL4@4?yDWxTOe~_YkSbP@AYL!Um_qy@XM>jZe&ehGv@;saNz%$w6n{4h)w(lm}Zj%kM$u`|&18thCV6yc$ z+0dJ8{T-czZqsyI=L*@sPtB{TWNYLuC0p|-Dw%#LZHaELbUP;Lq%CvCaCkJ8js!7P zjU9A_G%vEBjvAvo0ZO~q4K9I8k?l613||d5;wo{?kJhrf$m!kq`#`d`D1`mx?Vb2T zk+&VW+u55>%k-0_IUX2r3+u5hDd>Q%`@|y!i#vVkz88~ximc?B3j^F!l++9zb zbV>g|wLbd)CEmj9;_@lB2w?pllmSGRNFI}hY@kJh9&sa+)8bP{C&tE$_%LEfY?>y! zyQgHvC8oy5vbVe1^?gWEocxtA5^*9&e zL74LNjgp|?9^k<}!2g@}07n?r%~w5%0raU4yjP#>6Ssi=b5crddRlsF)%r1v!Ky>X zS><~-2L8f_tzw}q7f{j>tS^~0F?UFD`54uAkI+d1|5bf=nLFH=UnH*rPr6#^ROWPw z9$bgTW}%9yT6dCYYA$@4UYgrFiM5RPk^d?_xD926KW1x=Vw)1Z)L0^`fRz-o29HWF zb3$buU(xwb7+GADC_4*gv8v_dTqQGXaahg;T|bN3SQ`kdKh8%PM>F{Xt&K zoV=7RsR*fGnVa65%FG*6l#`Oha(GmV+zXlK z{xjE@HzmJ_RnNG8ja7N{`2tI#&n5Hp%c!^FQcppaS880s8>_k^onI~KI+Z#6JISMr zMXMxFk=>+@bBoGkYejWWl~&otv~3~q^kNPs^xz<*zW|GfbJ5f~H5 z`Q+NvBAzdR4(Z*;3pFVCPe$2gCAi*iZ-|MG*L%V+9`*H?er zBo~j6sMOg(JHhYMG)%G%lbyT@mqe!dSoF0L~yB&yx`ju*_j^|!*?rd~TR_mQoAdxC`M19G046<^NgrjB_eEYFN&}|9^{j2c7ko8g$KwV`!^UwU@~rd= zBB0m?V3(@K#QQ_Bp%?&pU98jKNpLU}+tp~u-_{VA1Y=<;jEAU(tYPqcI1-k_6nF*X zHY;0J#`Tp=(OCEm;iOTv#pgh=>7=1owwyVzBmN0+Amp>>ONM;zmCeaCNHc3)1n-0w z!`Ii~kTE(oT@43wRRy)>T^%tEyMV%C z2J-#&pLR(myAKeYk>xEYoKTC>w5YhKG%w$yllqPS*-ck{iT=oR2bTSvaXRd^)!{Ge ze-}C)Ff+~WJ;5)Ussvlq#H2FrgfF+O*#_O z*nX}{o|BsEkC|k8lP$4I`dZts*hU|`yl!>)evx-;+2&S0*ODo;(_j7g@i zFWKFsYxy%7U4NssjGjWn^#4OfM{fU1GI}QI{;o2*hc^9SWOR|u{pE6hdEAl3{pE1~ zF?W%@H=&H!30HM$VEs4h`VZCjU*!LK8UHWLFLkq1OmO_4(eEnD;Ghm5r~?S<0BoVX zTD1Qg>HwVPtG<)~f2IR)*2>?8TqVcpIBAPd#Z5#e+3=K%Mao`8`Cd<^lrGafp}^~& z?9KFMWovt2iC@d1bOHYR+T;_-k#Q;;GG`1~Ff3%>jk`KD4RgPh{#Bps7BNkqtUtS5 zlPqBBN77+l=B?% z!&9SPX?E?7buT~UYPs~T2kOtB*VKovFWJP{hj+(0yif(H}W|5|c;dOQNMMjYJ#2H5%h@Y`UX^+$z zQiEU|eivg|gK*B6nlqLr<7m#9nbS94||{(bR{^uL31fC5$hRXXm#9}f8#fZX%? znE#iRU0mXH{)6-Xg5!TZg7g1|#>WQx{{IDif8BJ|Q^)_$`u#mEdymG$Xm~k#@g1hi zY5faIzu&V>js2-I$?s~+k3M_Nqw(PqCi}F?{!Gq3tzxTr8Ye8xpO}@+l-n$~SJ`TM z_}HhD=UURdr1D_BNo+$mkRX)!@#XGZWe1#^>n<+IQNOu<`{R!gev2)}khk1x{~~45 zd#JVcPnRM)Xy=xca(>6&bNbhM4910>q1abmX8Xqv{9_u+G08cJY7D9T_ZeHGSBKA@ z8T(A%$G>ZMA?wOvKo%Bq<&u&%X;Y7GvEaJW`4{bL{5*X;?p0XG^0S-8_uCPdwy`*Q zTHKHq?=sJ?d{fCw4;;DpfHwlaCw z-U~W+z9cfKfbod`EBVSKN14W8O!AURt~SYIbsak~$!~R?7ip5aOmb9R<*~n`+$HN+ z>NZ`s^&)p+0%&!Ga`Zv2q8VlWhhA$`8&J56LTSq4(Ac_nt5qwtS` z($Xf%58N;)QbF+n}`ROJEmxJ3J2F0Sln(*ap^87D02?Pm;EI6HesS zb%Z-I>-1x_jpu3@>SB&2zpa5wj$v+SXIqZtZ+}@fW1p6Tqsb#3mqVomzyHDS|9{i> zU*4h7^)dg~<5BAXWo4FnOYA|wVU3Oo58)jOt^@QBo&%iIYe;?(2BYyqu?meJidp6v zshEc*9Ng+a8KvHwJdfw#6a1&k3Ayd(*wrJQ+16(;Lwm?%T(4RTcWW#*)vISt0arSt z{GcM2m)p@J4ikoYsUsv3)R-jf4^bHu@>uES-K^xC$++#_A6vQ@!jYTm`^-L z<`X;ql-N;K{_|<-=D>WY#~;N8naQ+i-V&B-lR3dL zTJ~;3Jk#Oq8M_ucWRENl#4KTn>&!crep=2HnLDf}8A~`(ZM)^P5;wU?e392BALbBQ zZg(C(=yOm!t25V9;E834w(4_qaN-u{LRbrTMWb`kXSa*~4?-&MuoUf%OAL@8q0w5cld2 z;fPCQT!J!X(`-pYxXih>^Pikm&`X8Myz1((NR^-HwVK=I zE=8{;{w}s(R#g=LX4_w8|32I<(;2pZV}O6R?U(a86J|S3)A>>$S^tov`#ZuB&Bv8^^Zj?=_yQlz`FMLP5KU#e#)fZF|EgB(hr*SfTsECruDGux-OVW-&9wd<+|z_P2>M2 zy_HFSx}d#0Z?Pj5B%E@3_$xd5y&Usc{}m`;PNG2z3$b3xedfWdKRR_@==AKj9+>h8 z>#`p$;rV&RZ+tauN79a|16sR>f5L~=q{lSblIN=TtET?fWG@h+;@>>>;qfOlIp(9I zS6`l6kud~?%46z%x&D-SH@~=H)`|_EU;O#j#bOH^rO$uR>l%n&M%s1VH_&~CO8hiU zRcmd(fZj#=5ii14@O3DCiZ`I7A4>zTznA(uD(So7lBD717-FX%4rPt*WOzKJ$ya+@ zrNYzjr@=GeSa=Q;eOC??y%xGDrPo3irSw|UU>4z*LD6U30*ULp1Lnc|AZgT|JfhE{ ztg<(u?>#7btxeDae}U&i`L2|}7L=KB1>cdd9Cm>h!V}@uQ1((nx2EPn-we;gzYv}e zZ-pzN=&_!Jx4~!O?eHylFWe0uge<#lRY0aMTPt8^xDx&gJ_@BD@)*37a`~@49%!TC zKi}DZEi-2gGtqmc<7D0NeM7YVt6k@68;(^2L)4n#vVNJfR``}K)$^Lm(Sa?(1;78g z&k_9oXW#kA#C3Z|^q*dNIWKYW`>)qQ{X2dCrR}a?{a<00C#e4m&H-@N01WE?{3r8c z-)FB(-z%v9t8M*&|AnOm9Q~iPgnxJaUzDx?8^w3vFx;8A7VYQ*vM3M#sWk|aVn<|o zv<5JSpOrqVjr?IaO!_R*tH@X_T}CUZ*qv2?vPI&C*qxldq1P?gUnfZM zYUl(-_b0lb0#96Ssi-UQ`|JMDAIc9ZzphUD06n3|A)?=@i%xJ|Qxb6eLf%q;>}jp) zFZzwz>rZP0bsY{;DQo|ymDMQR{if`TPV*C%NAH4>-((j z@$7RKmwEk9rOM;1t(jj`{gdP^>uQQ#Gr!2uZ0d4uvd@JM6&ru>$cy?uglvyzT;mW z;D68dC!&v(@-@=$f+c>rhDh7Cl6~9Mfhh;NX}u@k{cSBPTE!2^9FbKrDW(7=e^#1m z%XI9(e_bj-?l+H}CF^Hqrc{qhNINF_1$8o8_w3kquCepa;dhdW4#GMfUGR82{|P0z zxmkpnZ5Unrs_$V{-Xn4gi%X`e=TDybntAIniVP_jM;uPtqiEdo)XxT;cd56e$Sp64 zx6z`>vJOJj??d)LmzN(D)wn z-V#CG^?#mmVcBsvjDb&?0xN%{_s@zBdp|Gt^0MlzbEO( z;kFPihv=47dMLVU(H)-*X&cr4n8k1iews$L2B+9ars6M$GVVAXl1Ax}C&L-|`Aw~r z$=_-%%z5xK!bOLDIlKeThUklx&iNrII_D?hwQx0@3txrT!_DvpxD|>{`X|V_lr82W z*p3gj=%!^qcF|2A2bV%wm-KF!4tYG)zU!i+UV#5TxES6KWnb{+@Lnjo>!;wOP}cH% z9DWT~!EfNRunl$h96ScDhAfR~Jr6~<{SuV1_LpHFxEA(w<^4t%_>AsWRzggx)97dQUzwm7ND7LyEbhp)gpu3~)Lwoopztf$!-hy(z z@#0i}M2*8(az06yj+0{-@d#4)fiAMexQnup-V2YxFZZJ>l=~0~>13)ostw?HymDV8 zpA?+#_vrpmCH}*4aX2|5NH5pX3pR(GOTG6|usi-fuph+vqG5kH7aq(QOIRoB7x!o5 zENfhUKEC1|Sv|(GhVhjxxaKbQGQ~yX(ibOO{A~A%eaHRG++I4pVIkih{^W#bzu11q z(dk`QZ5aN+(dIcU!LjSsM|e*&&pr8HAG?OZa!!|5r)}h$9gvf5zilx8t8(Y96xE|6~8F)%9Ys8&kjff6+6AkzE&DQf##? zZyWqtpPv*Pm&Hao91>TG=EaFVUxG#d*})0()8|Y0pV{+@K3R^-kVWL$XF|F56yvp5 z+w)49O^4{|pZQtMeg2R1^oOc$s&wr1PyKCocTx%CsT2Cq)_PS3y3R$n9cVWzdTqH6 z)tYD>*U`s2I%h|>JkdViRCJM|yS>o%JG$kSwtt%adu2fUd|jWGlm0r}@92V8`uYD8 zy`xD#_upG$CDH%Wi1B)xp|&{wGgJ&a0^UPI{}6JTe4IRo~9eA|6e2ATk+e@uqy_|a|pis5KD z9gc-G38J4-d;es?dH8eSLogRU2J_$_FdsIeEa$-{un3C2kaPObVJREg9*}eUhQS$7 z?0heWh43nfZdK_mXTWRm`ye_NpL~n2gR{w}uKJegD8Qb+jQ+%s>iu~`UDdX&YxsLk zXFZqOWYg-ea~X?{Wjd}n=6~<|FVA7UtpAmnTQu30`-AI$1=s&N=o!BM>4e?jB;BfQ zbOIL-G_ESBH^`BFaBAIy=O4EJPnK%B>Pb-lv-Lk@UT$X^-K3N;u|sKlhuAhNVKOkm z5zcs%^aWbL4dIrxx~JI0XgfUQ@c5$1`6b0gh3qG*4f0B3v^?HUrLOT0XVG$**fxng zoy8fPFlRnvQ5y3e$LM$>Gpl&%g&rrK$bVAiDOF*5?x)4Lf+IhQtSU00iszSyBM+)P zD67bRAFFF(T#Q1Ce~;~N!1apXb&9qFa(=(e_FruO{VP&v#O5 zeg4I^ziV6VU+w32WJej3s;kUlnjdJIpK0n#n&!o=bxGf|<{lY!wV5#W@js(JYuiuN zw0^Q_9-wLdr)hmJlgyW^uD9m=x9gNFyZecSQ4d~X&Hkb9cg^-NM!^zzDlCK88Cc}$8v&<5H@pz$Ly`H)A-B(W5xfM>g0tYYkh1yifFcv# z50UqL55Ozo6L1dv5MB+pz-!<(cpVhG-W%XyeC2M0&ENtk-%hSo)r&I+auJp4tX1vI zgOo9P+4oh(=$&=I4zzZ)%yo3+!X+$b_6{;(Zb1gSoVyFXIXyiGv;J4``yc%NJNvBv z9lrl`bLx5g-(47-{}O=Pl!P1&8X>2^ zoqR;4&RktgbyhuYAUdB~4$h?4a+f(?vX;Bp5d3kxoUcCT`u*S6uVZ`QJGNSPBx`nc z=Q*rwjI6{3Z&LNB@3o_kve)>MTZfLQdcK{k4<^qWO~G$!|>) z@@QyTd;g{CS)FGS^}nB813Ak1oq2Qiq`9i|JL`v04mXdHitEfHtg?+Dwwy!sZGLtX z>RO~MJGt)FlttEu>C&~jEUSrs5i%+3+@*-=jdSfX-_OM}=SD*(*CS5!X2b1zJBDXb z{8_f&v9Wm4zRr&J?-_Ppmy?IYUwoX-+nKND($8Pj{Jn$#|GEJGcH1xcOPPAw>*iJ3 z?@JsPAZ<)EPUfUVoFMsy{GZK9%XNvKp{9Q6pSI50^Gcs~;hWn({OE>Lc6ZN6i@L?T zY_w(7mko%?rY2~sUUKKnC`SHQZ9PnSY?GeZq>nY}UF*7@nn}-W(g%j9_MxW!)?~9| zlIKnP68uOz*dM3+T6*n;K-&p>B8x(!t-Eby+8WP9%A{0H}EAR@q0bU6|f@1se85I5A4k&s(*&pCqDA$19iH~_y z$~K1K9Az8ByeBm`Z4kVZ-^anFFbk5muNd9~C&PQ;ba)>WTap#RuuK>1#SHq*>JlFw#3_HRvU?;d8V&CIy z8Oqvyunp`52f|2rI+QtTv!TpUn+yBF>tQrp0TbZca47r~4u{`BbjZF+m<)e}DNy>? zXNsT8O2O%VgPzAF&x(jJaX2|5xqf+Hdc)>03bulMVRzUM;*_2}8V#>R$EGJIC#7W+WED-! z^5pMt!V|RraZ80{X!9mrss5E#R9sY=m+z4{Z0`{3>Y){NvVlNBhTVYu|I*hv`+qBe zm^Qd2P7YxweiznshY8{-k-8B!H`7XMTM&S$9 zIjjj;`2}Sqq!Ak((T9}ryK^3q)9G7S<$TlUx0UZq&c4pEWt}JX__)K?L3+K)y4tmv z))6<^otVCFCON@mk5X4V7?YjK55uG$e#j`_^FxESx2$~shl-4nZAy<<7P--f+EDdlo5EA^N5jgGwJn2V_aHV8`*1t4 zaR{_+SY-E2cfwHmq4Vf_rlq?Wx=Tw>${d-TkenJDpE)9CWMV?RgeN5Cpv(g7Xn7;BB z;yL}~_?VH>&mEIEvUjBXJ2D+TUqnVs~8rxQTh;047aTBbo}Vd_@PmmXA~4?XBCVpnv!3X6CI`OP#j%b*ASf! zHJ3VslKxr$^s!k51qt~D-rSOevLbgha<)3hQIxCUQL0?2x!$spqLgB;UB;EdID=X% zR3LNL)ghE~uWw^ti>!B_S*D7GEU(Tf*ZRBjds(Kp*dyan`h1)1vWiWc9Kv72*w@^y zo6_7owy7+dcwV8W>a3#QlX^y!x8nTvS?p`u;mH0`awKRdN*OckbL!G%=4BP-a65F} zIpxW)(+1XEV_he@?$s(mcGdUltFFDogCAPhw3t@ z>R0EH{!#?4+i>Y8v^{-V7T{LlHsL~#Z(#jXzW??A{`d6C%+bI4{yS^9 zui?JQfmxh7gz~(!iBsz;WsXcbBXeZ2H-Ey^Uh4S}d&wyKH}zv&eyJzR>&|09IPm@d zmwis5ob!iRea_6R?4sh5Lf%u}{=)pCEN^}>Yg(xDJMYkV`@M{}&moj^baT$J|4XFO zO55SGf1g;EiK!s%2kDGu<~OHZ*pibN@`>JyEX$Q7ZP$v)F}}I z(trN_rQHwTvherh$2kvqcRzvl3+KVj#Z}-o;3{#=yEU+OV4Sn4C-ynrENdffVv|!dN5`k8 zB_@wV7d=dTJdN#{M<2uQDe+@t<5SY*$JFFe>4_tGA?3G0{O0#lJZFiM^B&J{X9fIr zMj|_A$LMo75uJCatNma;!h0xs_lICM>BqbD_*`zeJJ;i_{$|R)3i6$*PzpJ(=e28zuolZme{dKEB41582gK*?A6^H9WFNKqXQ)uKyT#9q90YrlJ7=T}NPv4uXbv)o_CStzfxI|J=L)HeHhIEUZlc{mG~LmU@{ zqo+68t`OA|idAE^rChsluMPD~NI9k77I+Wj+L{wC>oEk@iKLNhkg?n|(sWMzsjk@> z(t)(gqL!EwKTvF4>djjv-2Oogux zFIn0sJjwU*(73}t4ez-NTR-kYZFW)v)OBrXm>7S>(yfp7n>p^J?&))A6H8S1>qm9| z?4e`Ihn{hDA78hvtv;-~-E*ocPwRib?Cbr;rGSc0vg3g4G7=i#A0tQ>sX1sRXr z-s1z>^Pg3Cnd_1-rkpkF+FP%0zpd!HT^Js=9Vzwj-aRX$nv9uv;ectUEV^a%nlpKp zRro`bx_mbOmM_oW_gvqEardvghIPMG`0V%wao(j{TX#6AL(A=-fBqTYI2FG6`ZdF6 zCf%Cy@-WZVib?zCTGmY}yyiId1{J>X;?-Ad$^OpQ^q4c=J!VEU<*ZcUlfT*7`r7aZ z@0lC9>B;DaU*5!AZ{EwWkY|_wT(EcU>+fIl#-0~$du;d~=71zi_@)boZ@YiPW4j+5 z_Uu(Ry|U&R^EmiceSari_WaWyRy=>gnAZk9(RB$Q>0|k}hK1C$-|eN&|HXj9t#`hD z#CzZ5Pkng$`At?}bEv{sK0EoouPU-P^gM6iiQcJO*PG}6dsO_C4_a27QS4dx!93qn zC9mdvfefg^?~FR>sk`0Q7ynwc>a(NHS&w1n!zz4wk9|#_Sbst5`&W*>ea#d%VQ;DM z{;8kd{8XRqp(`5nUbyDx^nS>-Dty@DOA0%lHT;*y?~330(w7acXKk1ixvmvcvcCvx z(fX2yZ@Te`$Qxg~mo)`c_?T&-Pfr_q<3%mfn?K(6iciL2TdBem+AMkZ#>GkPBVTFJ z{`KEqy3(>fQ}4?&i9=^R=sIQ2HR~gr&wF*-5te0I|KeP}OJO1VZrs(OX_))1^soA4 zw}@%_BziR39nrC+(C;Y6Ex|>1q+P_V;qNUt(a-IKG8P@irVh@0`|kWb2sfP$qv+*U z!%!D8DPdmR5?p0Th_!^URs5~StkK(vKrA}Br!npgrS09oZ`*KN_*?XO%`XYD8e@Rl z0XGO2{Qd{O|H1D+?`XjHA20I-WQG`hjDZ<4WcOzp+geqBZjVQ)01LBAi?Z|G#aaTu zwisLB5Lex00QZ!fgDsEcF0jnzlhrG(7xauD8*jM_bF3a{or{VKiYFFh#=?%HXM6f% zzS0{vmgVFUa8ps`quGz^9m%imfm4udN;17V5T$<-LjajEBjYkh1jhmn!WJmF20(BP zfPdr~09CDFJy{iKy+}T-wMj4HtOJk|J8BGC4P@EI;WFnqMSH7sgXuD9hGQTSsK~K> z;RO1F8!l}qa%`qk@`8fQ0*NOw>>&BWaLAl&kzwx-W538=dX9E;#~-Qv{G|5GEP^7x zwt;I2^q|Acx92xYxXAizxkd&!+?iz_rC@OOmK4Dr1uE$qhF3OXJ{5%*nf(t{Zhc;7 z{&i-2xu+ykZGCF%4t1IiT)U(xnI+e%(>U|EO=(i>G++9q5q*c0Coql3+Kzs6lU>i! zWfj!{qKehN6@B2#1M6AWF+UrRy4JBH^ZU5}J68QElUuIsqwEad61kY3&2Tvl>;@~amV-Y{o; z(av{hGilSqLK=5kHt3GN$>%KY(Ku$;5tGp!9B8hykM~u7U-WlJY}{KACT*x3eQeuy zR3AQ?NM+m3CrQoGJO!Rc_#JRCWY}1(g*Y3=;J+CTfzLyxZ}^Vjd&V$~50iGa2BLi5 zP^YLlq!FA$x4)Jp?Xr|Z+UBk}IZ#>o#6KEJdp!n983Wg$m${pw;|N?=&@@j}^cv~7 zH8^KpsOT;#FQW{XqeI4t{zB%1&gsrPP~85j3kJXcavy@<|AXse9nAOtkjVdT&(zFP ztpNzi|G~Kc!8HK;2j%~NuKcfC!+O#H{Mo#MKa>9*xpbZg{TvLpsJ;)~7%Pv?*7#9k z3uVjys^otzmpXQbEX3 z(g~}te!i(cZ|b+3`uTP3&o5T;W?EURD=xflrR%q=(kC{WlRcDms@2$J?1%*kr<@-C z%8q_7$2``51>>YqF6md-ltWGZe^Wo-)DJiH+f8$TO>?SEdH|C=Xp;9$dIOVQ!lX|y z$(ts*(xhjot3JUbKOSm%-z4|dRlYOn4NUq0lRm+uH!#WjKk}aQsN3z0ZiiOmM-;Q9 zJ&?nN+!Y7?=Vxr9p^`CVk+Eb9d9FQPT$@}a@|4I@Tacp+vkwk9mc=3wDgW!+3rYXK zF7|)S*f?OFp*MEtmFT|zu;AFAj3NHB=K%%B{(^n~e`?=fTEf3szyBS(-#?2Ft^9d0 zJdb{VbT73QuZ;Uq>gq8+(UqDGr{Ax}{M3ru)#HBBpLgSAJWu-kbsgvHEa^FcjPq?{ zoNoh#l`*?iRZe|g(>R~V4?XR9R7v*uTU*j1PFP3cq_4Ok(*Jr7UdB1lwK{T(kB_Qp zyvo$iHub0L+CMh+gH8QrQ~%gBzE#(LvuXUyG(PvXl9#6aeA@A!_Afm8x?h_-(B$k0 z?){-2S2OkF5A}GPXB>8|F2c?YmS$jm|x_!o$o&2QS(sp;n z$syx%;vWO0Js%6jNafGQ=N!3F_WLsJ4}GAqIgusfdNL-5!{E@kw4h#A(3XUP-~ZtE z-_Ks<@Amztt5VPMzuP<2ldBW}!Lh&K*k5q$FF5w+w+`4pX#cOmY~2#pQwM-{`p?Gx zWN)QO*v)RDzr@V&{Gxm>^J?{8-5GSG*sD8jYNfsq^71m8hRsTxetO}@`b=W04b#v2Z zr*0zqO9H8{`PE?qY6+{l<~h~z`Z@8c!+J;542x8CEjk~$9C^rOQ!G!`#9SR$&ncI1 zY`D~ZSyGR(=azpxie2^o^{DGs(&gi%9y@)i$BLYK9Tnh@-@O0t<$ivt_milHjeSH1 z#DkxkXD?jP_4J9kUj2^5rbI`d6*F{b^x4>QHwxe2#MSG2Oki!Gf?RK{@w-aJMB4Mk zT}|>NXS%LRc_+o6=jWHYa^B;lW~vxg8-y<&d#e#FBTc1>U-gV;%G4_NBz-;I zbrSWjT#tP;63X?6t-tJdXSyDFuf&eP|9OxwS(`xiMsw55$S5jWqRr`|`I z`|l#tl8GK~3E>jQnKPfyyCi;Ru6%yUM5Sx&%Khrkbq?TuE%`#%i{vM3B*?i6vh}&7 zUZk$Bx8ukjYWjU{DOF>&lR|S$!O3+G$H{#U`%>Na*c5MZQfy+JdY}F3g;@I^N6-#S zh*zbPYta7jRsKfdr|G|G|DyQ4QFvUHzqj%aukuGKe`=M#m$BacuVt=%E$>neDeIfK zDctwD>O3JdO=&}7)Aap5(6vZCO1t2HFP(efyxWqm^X@Dc<6w?~JZChYR(B}*dcIP1 zsPY}oU+$$d^*cN+!AdF89b-5Ppzz?}U4Jh_=yigoTvdcEf_V$1NYf z=8n}3E`9OWr`Wd&gYB@8R=0Ob9568L$zfMNdEJ?JXJ@cR0{xq?kf?U!J6CfFKOMKt3i!M6jtzM^<9N%*0Y4mwrB)q0w{-L%zG1-fl>_SX-Bqsf~Ngr++ z*O;&Bx#s*O)3}C7A6{3zx=DYYtM7aN@0ahGy(M|!-D56j(O^AoqiJ4>X?(;q-eH=D zW18PGhxQ~aWck_6;`{B0OWRnSJS}d>i+7pFr|PP=uP^<4B0ci3kn-PWY>i$WK6_^D zGkqWbuHl8O&Bnx!u#l$1FaC1yRo^f9;M`#+F6$ijB0A|~CH&jNpPcaQ7u)YRI=#!P z4Z}ZRvW7`NzgFFsn)-9oIG0IpUte}t2h!Uwvn*RSqK`yBMCPZoB5V;(j!O1IlYZh) zus_@hdBm(;@Eo`s=D@wM9MZ7*ZUl|sov=B49JYYZ!!Y;;Yz04r>}TiO1doJY!glZ* z$o_V|U9cnE13SSVp@;HmIYrAaQeK)@D+kIkn0Qj&(_ufz5^KKGVLXh3?2D)NG)#mv z<<@XG37!crhiULSh-s?tc9;q8gE??5oB+4LiBQTn3H}ZXU^B{41W$w>*c+Y?2g1pa zedw$ykp1PXa(E7$3d`UH@M<^>E`!tI(-7Ir_dc8fzk@#540-Khcnq8cyF&K&^D$Il z&4z>EmGBIBHRM{YYhW^*2gkx2A=hHfhm_G;1WV!Vkg{0!L6HmZhf?nga9WPg@`cp9 zd|>C`?RKMo!P`JAe~{G{GH;%7|8>I@lsvHk_mf~=3Ko)_8MZw7wq%r_gJ1Zgs? z9`GL66FvYVpgc#t;72eD{sj9#Dc>r}qh$l0DLM#Jz9_<%LpdJgI_NG~k3tt*1&@bM z!btcO6an=)7!6m$A@F%P3a)|D|5*!X!F5oclUL!r@O8+i(s~0v3pYS{uHT0H;5(4I zu-=8o!uO!85xWs`t=5OIAN&X=!A($}>5n1H&RCn_HSjZdBm4p`gj-<+{06=XcR+bw zzJr_L_i#J>0rKuzJ7Ih7>(6i?+znIV9+(b)g~f0myd1L6quL{~K|}W3#2*Tm!NcGR z*ciS7o4^lXGx#NJ0l$W=;E#}d;`<%4KctUySw};W>DtTh)X!L)mRER3_*?Fu^xuzeYoO-p+!<;n`5m@55;sf_uQ;or5d7jImoc{ut1*bs|ybxXmXTaN_53*d5*u41Ogxo__jxh4o zGKH2kq#Tl8D3s%R;)(pR2=Y0yZiA!Y5-9D|ov;Khg^S=_@O5}M+zjuBEjbs@glcad zg_O;D5-x*JL7eprl;`AGxC^d^jk!NBKxt22gwgONI0U{7$G~+k1Fnbie7pv4hHpUL zEo%eh9kAYlJge3_@HhA_l)XX8Q|%495sHlUA$0D)mM65FA@$;a|4-&Q5!r(@KDlo_ zAfI5XCp-zB0*Ar~I1KiJ=fX%R=a=8Ktf0qs<@|EKK{z>LiAQm)5wJNN3F)3&$Nv=I?azmc2h$ci(^i&wWEeEZGBe358sPdk5#NH$SJdUbiZgf(Op~+)0>>F;`Hw4Y+N% z&|X{vZV)aV=fTa!)z|l5-mi1(d;K3ZZzQuoTLELU6|@5m+5rdcfP;3x|5Q8RB1UeQ z5lxF&Z-aq=UX=HsLIbYUfc0;YwM>Y$0Y=|c$kWWrx zky_Ds#E@8`s&rMlQSut4mm)%ku63hwjndB)8|6KX=(iq@jEKJp9eBDjD*Yz%l}ZR) zBgjd!j5HJYJ%P@?lSali4wR-HoiOQte#yq~P8t~(Qgxorh7hA;Qu!Hut6uX_g{LV4 z?KE!|GokF?rOupjjZC>CDc%z2H_^p9Wt>fZXLG*m_g3e(+K}HGC)|*qj7_TY93a1^ z$!{+CEkN*buJ38m9jH#a^YBWY^hQW>(o{Hk1=Pt(C)`lRRnG6Vl+n>q%Q}iC9iU0q zS63aK$yU~+qZ`**bYeBvc&e+8uJ4(0o>8}q+kWog$NM$9qxZKrHedU7{mof5={!w3 zH3P?gg=zva0omX#zFL%%I5puFcClcH!?-X0%t<>WY*T(&Ivu8(luQV z$2GL98{j!`KFonP!OI}}6yIF97|w@F;O+2Ect5-gu7LN#=Ar0F;Sq2-YzH5JC&LHf z5V!&+!AGDQJ_=XC$KX)D;m^Y~_#(U)u7UT%wQvPo2cLrfhA%+$Jih+)nKr<|@GW=_ zd>2Yz>OJV;K8UVi0d6`j??$a_5P9E!{V2yi-ADUUo30_$_2=tHiT+?;Bo)kYFyDVJ zyoHd9x+GS{Km*GfwA!YE? zECb)N1KSP-=I?K(l!1&bN8q*2-7gZwtN|Lj_u zH`Dn9Uh7={d?uD;dDsF~V%D{7IGAho+GPm5)`}WssC)kYZPOmZwQlgwr@Co6B-c7# z8fuQfYptwNhX3qZx7hgvw!>j5e($TNs%bhT*P0{))f|CsQ$&q2{IzR!zHRI6d;+gE z#Xp|{wA4JphvNB-I->UL^aSQlw7TZ+|NO49^9j7px&HaE7ht~MJ_5RAwg3B9+9g-Z z+OM^uMj8IvwL157m7Px!*-6*sJe+)QHu&c=DXU1DV5w927MPBMy-sPX)Vz&w#t{7W zLy&vml*=WGAP%unk#7)D)i)^mOuzR^%0>8Jxz`EIDd^v~=3JR_-Ak1DzL^DTwoFRQ zD9V^skSmiiGT8&N%p#taT$i3W2To;8o)a%BWbekp}vPrjTvYV|h9rB%Licb66 z2i`88^W(?wz4_kO!xsJgz#XjlfK3i|`!fqdT)R?cJag-bZ?-Fa}}LE!Wprm1#`MH1=c~bD8gw`&e@-sRJF$I&I zd3~(|b0GWh2-=DiB}-|UR_x}a-S3K%BL$ta_<4nV($7hSKSD|8vC~O-V7gIuy3tT% z@iBhs<{luwv+Z=_pqw|uFI~j}^2@Z-jfavS->0f_Za6@GId-~SDEUqBOILY-{POK| ze79A8w9{4jg{i)-8c(TFFX!9oN}%+SFf#R2lW(oT03l>FxSrQ2|T z{A3JBp3k{Z^1IG2UF8Atn`fuH5lVjZ{nCZ0K3MJV!_9WOg;1WCTl~^R*y$uZ@czk| zla%u|D9`5-zjP@F$nS1D-91pwyUZ`0=K%TLZ>L)hB|jOva_+<21LRj>r+XMmek=Ua zRU9C{N9}ZvLCNoNJKa$J-e9MbzOK~EVQ>&mj%SD`{%2t;xEe;m7vNa921?)fCFqo2 z_eFIdHL#x75l`w_#?->$D|WhQ{?`3Cm4_-vBf=yvIo>7SaqvCZAAST+ho3;|!uk|G z4gKwWWi4UV=d9c~LQv~L5;g&Tw$i}T{<;+Esq;kMwce(X<+ z>yJyvd2n-Z%W!LOn{kKw`%jnC8d(qazn(HLn`G)yzu!ZT3b$OrJwL~!ofc6Z6%oO< zp*`Y8Ca1-xj!uk?$CrtLS(;|%h?tbjxWv@>*mMbqO-&z?6eoWrjAY<2PGVy_JTyLr zm8jz~Q`2JwBZ!-j^H;WOEy&My7kYDgdUCSmL}ZsaGC3hRH8!4tj7&_3PgBK6OHZmL zJRvbBCp)XCC^zRI_WVpd$l`G&#RnO+)`h44*B9u{$S-2I+QfkM<_fZkCT4l^<;Q}u zoLn7&t!}eQ@^edjmh17xs+560WO)k;C%E@d{7)O8lQ&5()Mqzc^)^I@j^Xg|1MSi2 ztW7&sKB64-*HquUSpKGC6LzcC+vtL6odut*sw0fQ7Ca@>I-~iB=yfz4orSFbFioYm zSP!DVTJPb4D*u20e_!SIRQ=u$y+xz&YJV>g83N+;!LRiq=kps&Z75xb=ruayXI&Sy z;ikWi&|{B%?c#SS`&OJ>=aT)dvufE8rK4#>oLUZbCehLCR63dlwUkA#cOMOSu7u&y=+I~qd_2S%rXPhjWlSxH0uhP0i>P5v5$#lyD zfUqGtyKemM98!m?0`9;6nm@1JrphPlA@!Cta!+5W4pZHyfUsUpyw%n5B5MijQ%jir zjOb_LZmt=?BPxzRCX9q`NqY2b#HoFXcY-;ME_UIxlp3_FE53p>rAKlJJ^qJDAoqHrbKp>hsiC zE1<4@K9has+MZI*2^*HRkBq&!A~$Zug5Gz(K<9d`%D1N7sY&-@vS&8wl=iCg*WAm~ zq^mLMluSCby6Wysx)hVH?7cIk-ZEA;=z7WSZKtdqaQ}!iOGBq42OcZo&o2MDVDH@5 z-@oRKJulq$*zi5bCPztl%{4U4_H2y$<{suv)7FKBJd-$d#)Ga?=3KKrviZDMw=te%(jlKkOA{7y z<|lndESz=cZDTv8W%t|tA@{|k<2BhKn{=?IHDOG;K9Oz5;`H}gf5*r2MW#8cnj=la z>XQbNM%OnG(t!G6U_Y1&3Gz`a2k9RUJO5mm%uOJEVvb32Bm)H;`H~-vgA7_<(FZ!OL21CL_8^<=xC@DrJIp! zyA}Uaa4~!d-UiphC2$kG1ByNKQg{Ts3({6wJXXF|@LuSG_ra4P=k&?WX;nmN zBVv4@Fm9C6C%dzYi@ibl-(UaXDa$U%cV|pi%8P2X$bP{YTD?*I>*(0@o?)?7zd7{f}*+Wxp11Ry+Ci0X^>xBpRL7G~v5Hq67Ac?UfUsV4XGG@JvY*Iy6Y{jb zIe)(vMt`Ghg8H35mA!cWBgv?Qu^(E_C zAC6s!)L(vSEHjO6cE@&xneDpnD@} zF16FNcu+l)DvjyhIQgt0O$_;bW~Y(56y4Sm^a{>8%Q8P&UDK2is_>!{aMI6iVp&)5 zE)9ODI?t8F|FMKX$EMhE2CCQ zQf@9@&NmysoSQ)%<)35!em=j;`RC#{ojGwpjI z`E>Ys|9oBe{qyZ=|1SB;GujRRY<-?yeeDy;y-(trBFLAunylyTd}V%n4*9BjmIVhg zxvP_NOT4)`>fWp&O?T4V$aOx;qD&;UB+dB!(l~FE*4;@xiW5EG(f0kkit>oxdFC8F z;HV0HPDc-TgY6#>uH!#y`yD;vzJT9Pcvz=*elHI2KM~+>Y~N4k{E@cbNuO@li}U*o z+wc57->w6vy!YFF=l6z>>GC+~JK28c_n85|-{SYX)Y(qzw}NNPB*&ZdA|^TBq_;5X zC+e!lGU=;KdYVUiiGCvTnJedB+U&@A51k&Lm-2XJA7t{*5?*s&{(P5&*VLDo^j>w< zW35%^uc_x-t@Khg^;M>M_I1@q=Bj)zuc6m8>1j;*n7Zn{4o9I97IJP`ue@a|mtAq} zO3{|vQheey)^l)2Jf~RHulxGuUy&wxt{ckj+A=X`09lt zp5Av$MgKRgYtp)Bt~0N(YSQD@RbOk;vzlx_O?uW<-KG4VPrv#o>fE^bE!wxd)wB4+ zv(U{QBjH)9wYDsTmzqhZ@_2aXYfVHdY#I4iDhn-?NY;rdcJ>4_$I$c!MET5_%=KhZiI#K z12`Fe2xq{L;3W`UrEeCb&V1KEo(bO#@N;-G`~oh4Ti{aoHGB_lho8W2;1_TQ+yTFZ zyWn?lH~b!2p{ykc8$q4{UsJdfc7Q*_D7Xin2KU0#;XXJN{sxD^KcK8>6T;e$V_*Y# z4r~a^U?VsK9tJOm*l_u-fUHmKyB;=!^I%JOGYo@kU~Bj)JQBVS+rn?)(Qqeh4-ezn zJO;Lcti$T-3tdpwVURUyGhr8)2mb|q@HluYJRaTwd%(}(DexN@0e^?RAYBiuH*5f- zU^CbUwt)R$E7%`)h4j09J>VeN4-STj@N_r|#=_|^4&DOe;XN<`R=_jhD^S)({Tj;J zsNcaeVNWE*G}sTO!(ng~ls#(4z>#n)90O&o)U)6?crMI;HPi{ z`~toSzl3kWO87SX6}|%z^3__Y4d4g(8^aIbI@-f6G&EZVH?a1>FdCZ(T)WQPXPAc* zyTj;?`>*$58fWlz+JF5I8E*(>tYQ6Se?IOI6&|9-9#)h07Tivpj6cK?UcOAPIXVXh zjz_RIk=5pOT=4r}V{ZxhuJWA-*0qxL(Hpic2TRoIhm%9JK!puKCj= zIQ|zL|Epf_m!(y5ONz1zG8l_1E-olF+8$R&Op7fpEG#bCfBbX5fxhYyzWsvhI{xQ0 zUG+2qC}Va9w*Pg;pt{o!m!krCQ*;N5q3oZv`3AAy49UzYkO|)uFRv&^;?*`57=b`* z8Vj5@-?0Pj9VzL>=JSH;u>Q4#NikGBPj$RrHN&E6i5EG*iP!1Is(h=)7CTmlRgEqB zg+)1OTUW9Y%h!@;hTqGA1cw7S4Fp1Xd$p2tdVg z#v$|7IR=EEtn;_Fa30a=$NBk1hwaR{-)P^v7fC1I(C2XZZRww@dQ*ELR^2(dZxW}L zgFCLv(HMGhPFZEBQT!NFm*qk!%a8h=SbbY-zjc$U=PLgZet!S5dgSCBpW~z)=pfWy zgyU8Gkjx1sxw%eZ>snSR*A~Bg#a|iV-{O3Kt#h%Rq_MDy({pzB;T|)%%uJ6|dShpgJtF>OHRx>!sdbxpwFIl-9_(AM#E)!0 zTuK?8_t5`YcHYC%GPgUow6yA7>_Ytf(wyAMQ1aL+W2gK*f!DM8d;`LJEY$a4jQ!jX zvi&md+I`{fMo z%lr3~&P%mn{`He#zdXlNCRB%3SKLhMaI=aZl37-?e+5bX5T&-G*7r^F{yiY?ouu~B z=$OXvOk;kgF~Yiz`PFqyuD-?y*SbUpT664ez6w8dV}7~n{58kk4)vJdp&r}omn!x7 z)B9oTdi+23&IB;Z;{M-nxI{%w5b@T9qM!nD3y2CN;m~jh;Si%amn53YWP<@w7f)I* zP(-AtSn<|^iinCfDi!Kgyee8$RJ_rmMWxo`|9ocNcXzYd7l=*&YJYikc=FE9J2UUx z-|rmnjrSe1_O35h>{~nS4eILwvLAH8iVNp__|Ps@$1l3K#Rb<>&W}Xzg$}yj-i=?& z9G>5tTtnyfey%yZVv}F3*WY;9JsnyNIr;0$uKl#^SsI)bH2TR&S06p^`lr@ieE+(4 zXTAI3ja#wvoAdLV+xsbVc<6-Pv9%p@52h7U2Zt`%Tr|RF4v&GSS&Pm9Iea|741^i* zSeOZqgQr7&3u|mP9L|E8)4Ljuhfl%@@FjQ({5zZi--oBd%`gjo1y6%}5Qlu&4&rCH zJuHNW!82fQsJS}kBeV`H%>08{i+?gaoBPwCbaTuHIMZP{oC(qMn>o6f@O|rR zM6GPr?klZt+hGXa4bOp^+dCKD2QPs4!#VH)NEdGA@FXKW%Kew58}V?* z+h+dG?1of_MZC@RD(+hF*LtYI((7<2d;^m9W^S$szR5jlY|abNSY#vjq@|goBW<)_ zF}xUl1ed^%A@5-3=!mPCqgw^Z)3D?K&CPl7vtw*K_Es9i#%~wq4irDlp-~2$HgGDW zEQG^Qb7|y-+A*_dp)H)x{oYXTz7PBZ)Et_^JrGJBISBq49t_vPL!io^=GNYVnp0E! zMqtLeV`7cL6~9*44Z`TsoS5zhz$4*6_y{}}G7sh)2eY8&$mCxa{JN}h_9kC}q z=F%Kj5B@q8YEYUDkA=A~73M+ekiHvg_ND}30r!QFGH>R*DBEU^>k?Rsz4Dr}9;OVM z?*rZfL)_m9HOKWJoCYZ~X0A)=GL!q4;ki)haULX3o%7)ra2DJKFNLj%`)?s>?_36t zhL^*s@CvvHUIlM~bK!mPYN+}%5B>wrhu^{Lq2vM0nR(@7qaBl*F>8GJco26=FU^IG zg!maA3s=EY;Ny@s!_0xH{MK?`1)qSI!9T;R;Tm`|)La;K%y|YXA76mAP;+1JK+Sb+ zf|~16{1mnw8|EOAa1&-L#+z5u+}2tmwn>p2!g(5) zL6|JeOicXV&8Ggp?i!vJWW%^dIRB%hDi}PoxL+Ud-2bLIfTlTsra6Grra6FL#~eVU z`D(05fE~>NczWdTG{E8-PWPq$wlAE!AAR!eT5Fw@?L|y(66p#z(s#z{|6_H9Ki(-n z>h1;lA!;{3eo9{zV<&OQ*nJswr=eP$T~yB)FX1p8xYundOKkM67DO;vL zI)07+c@%y+w_nTHx}iF>KBhd>Kkwz|(yjUF%KUWQe&<>=RJRti=}>Q9$M)9E-{E+D zneF3}X&u{Qhpxz6$hAhsJrN#{jdW;1I2uX@9Rtsy6FV6qkDK-ISHev0wQha_WKE}8 z7mqw^*2S|nQ|EGoH$miNv+jHflIc~I|sEnET@zzy(v_%2)s!_bqROkX{!Oxf%#oteWqrTA^?z>D)mW1Mj@SQr z=Raz{k*>Ee`tuEp)~h75>4pFJx{BIVEBCkNw*;5zu;XR!0~~t>X8%2;uRpV_{Oq!b z@MzXVAzKFs@EknsZo=CBnkdQ1k{!38>~E;d*HBr+Z>;S%wp|mD>{4G=Yv`V3zx4}# zdwc!%Ec@-r^^-xL8>jeRe(b(CK7Zw3+dugE;&uB8r%AizC-apK0c&d%}CT?*ku%ec>uN5UR~%4%p1| zCX#(5x5V$Gi;a-kE%odpvnZvX11Ov8m0R{GQzlN=gFVhSnrFipxE5ixaxC zihK9(mzCeMrp9&NYbTti_Og!cx$3`;{=d=2|K-JH#i6G0f7AFsdvgDN*=)@iHZV&A zz-jp*v*9DZGL)THQjj%1J!6=hWMPq2Rms`*P3QmHjV$p812&!i_y1@APqA&f{{N5v zd$@B>Giii-(+{2j&HkSr?)cwC%#Qyr=XdYYywl*7b?5yGm?vtFy>xfId+K@3`$hNZ z^orUkDB~{1jxO5Tb&I;AyN$@vLDsVgB@bu$vK1xM$L;|%3?~=$MN>o!8;F&e`t_tj3bZ{Ko5ka@4m0$vO4U9%`t3)lj+4PagAIhy1;f$FBI|g-^ZH(l-5!h!K zZIfMpu1;hIavL(7Sy!G0!~8!H&VrTj0*Ly`%!w_8tVuQU;FXXntC!$D@MWm8!Cryg z;j2({i@Z~K5PS`4J^XrzC~DT7SHVxXzYlJP&%n>%D^Tmm--chnZy}XwbG6An9vWeCvcO?T_1Q>(C_%+Il543uR8h%T(R(VrL)-Pk$5|524 z1YGLxf3E(YY-ybNKcs(7S8Y1~qiO!HN&YwARs416{~{j`jG3&fDmXLYsT;0+Rw4RR z?}LQy$s3WM3W_UAs;lb#{)?XfbDOS4QvmE}?QdJ==MHO2-@;geuf7@~(OVC^xuxk6 zoKAqQ*rBU1GNm)7IWjAI*rjHrp)D@6a;0@$?_blgNN(KMJjecPmKCIC^{D2kV^*=NvbjZ;!+W1#2p=ypJH!hd)e>&x6J(Hi^kGRbRVzy z9CCd3X1!1S=evjAQaSwLjVp*}pMdP^uhm>*?0-4)#D4O-pPZg#;$L4L_tS6qo#p-C z)?@hT0p^?d*Iz^KryuasFZt<_BvZ`=;>J+zrC$s&St~1~%VUp8-2yk9sBC8=eOT!!Z9#CR&o# z%vp)ix5?Ha6za@S&2=uqczY+kGecwNJkt)f`z9rmtj5Hy_1sE;+AFE~PstwZ@P9L= zb$T;L9}jLpxQ+4suY3;cYMTG!liE$=|EBSO)A&DLKKLb%|J|mm(T4wwW4C|)pErNh z+MPd=?ofSKb7Bsy}hD# zJ>`$)38Qw=^T3)%+(pk9i`w<-}W z@#j@}>k)N!`uVs$#k{+n&(oejJs-bs&wFMQ&z!^W>tmkr+aD-d;Tju1$CDjyaPMRH z#|7vR8*7Z}Hx~69+x%MQ&T`Dy`=%dGsrg;_qzP|avS`m!CoW}RfzRelm;SSo-IZdH+|}KKjY9k{N0-$!@!&_B%j> zyAO8DF}m0jX7;r)Z(+`E=?d3k-wkRm3Nbr82GZ__Cqf$K@HB{A5WWKTf%9QMxBwml zZ-U3dTOl%rIlJk2cpvve;VLMZ<|&vC--pO3;eW!BkPgE+396sTfCa=ShmGfnzFXk8 z-z-+{h+S*xCv()`KOgLD>i_u??1^K>Wly9BZR-Eo=N}Alw5|Gi&S*vF+0_3tZ^-(h zuUgylj%lnnF!zY>x4i|l|JmhuGMQ}Qu zW8!Gm>~2rS*E4H*<`SOyW6U#tGJfoNJ{7KRWev~bjq@bNoCR*)F)2G=>FaIg=QpLZ z`alT-ZU zL_hsTW9^-5ti5f1`h+zBjfd*b<@w3=erqQE=Exel#yDu=Q-3Y1-+0b%?`T8UX3sTx zm`8tee9d*&v>M*6xZ}s4w0;WVw4rN~{nlVNR3GA}Z%H!gv-5M8e)=)Laj>7B%}*cV zr+4z(^Vv}ShTon{zd3Y2eUYDDNcx8b7~5BHJ`F9@V2t#_mt%AtiF~hqr8~@o(l@*X zu`}PJMGQ6Hqm`cFZ2BG=a`Qb}=^Jk2Uit=(P;>gj2Ou3s_z|c%)D7@B_z9H$;Tt#v zst+3q6+h-aosG6XanuGYer+HkpSh%$P`i8@tbjT2Y?ud`FEwXjUjT#LUkMB0oshjA z<_z02;iKH+U$_>s{?nYfjbCB9p#QpFW5kGNP7Wqml2uZimtPtx=w4AkjB!BV^bx1ZBKh3Nn@?gEa+hMk`tRh(a0UR6Fd6zDSUh`^{^?gB%q zs)D6?CBaHLi|EC)wqIAZg`rt9#zEHnl$~n>W8B#?WtO$e(4?06eu{gq^%c5*#J#7T zGxtxr_ZqnB_ZQuJ+C#@%KStZ+JYo9Qvsym*LXTa~UG?7UXMFVbz0ZzeO~&CGdu(ZY z*)vz)b=lqbbszS~Q`gL2%J|*5Upwyor$$X&`uZg$wJ$!rG3z6zs0Pb*0VHA8M;*H43V291S&$CD<@qY|OX8_kbsJp906hy&*xH^(D0P;r+Rv01t!{ zAxlThnP@B#F=wK6fZ5pp2Ij!dFc)flz1rTRVF6Tns_pF!r$VKd+F%I=w69?a5^9T& zg=awe1M{u$6JUt@R7n1X)8I6yG^KqHp9tw2%=Z++a4h$;An)SP-kZMaLO7ZGiy&)n zH2yc=TeuVwM}5o5oRdEn61LelqJHHX?#tjjcow_?s$|^+TfmvL*FWd?>wk&;ztW|V z>VHA;w8pLflrwXQs{iZOvoFo9{{&|0@0ZptQvdal2i#<++^T+eckjLWf3$n=)&Jh^ zeSG~tg36Sf^rt}%>j3`-Q$u)rFT*(e@gwYAwq84Rd>uqo!9;p@9(hw@ajKx zFx-s$lVK~UdfpnUp6>>UT6j;WdfpbQp0|UOV0%d23GW9_fh-|2-yJywp2~dyX2Fgy zA9jKTunSauSG-i;dqCo9&NNg#SNf@*_k$XH^@r3sv;V6UG9Tqo_skiN-VcCeb$BRz1IoV*Q2FsDR6FuEoCY^S;_kc)FM=P!#qcAz z1bz%}gP*|r;HPjE+zkH&KZjq!1(-QUH>Y3u<$nJ|>C+hXzoew9sKi!(<}ClS{(lp3 z(sihXq3VCQM+;m3O<-0(HOJbG8Z$24eB@H=S{7Qnap{>;(#O$x#`f!Z6tLu^XQwYM z>7R4$D!bco3v+g;mc_g38y`l>39B+6ATM zWY_^_!{5Lh*cnoX{?GLPyl&GKlLC!b|4XYItNuUd#!r2YE>ZPAFx%)=^<7*7v;F%3 zYgd$8Sx~2*FEsUhc~useClX_kXha z5J72qfwu&}Y5M+8(;2``XMi>B0gl;L{7YT~7-_y5X&Jx{=K;3T?$#m{_|Dxj(C3>g z-*rf>?oin6&4WH1blIFwZgrf7&Jp$r$e&;Sen`K4j4v_xNlyBDSlaX7Kiuo)4L^La z?wI~du42r?Kq)z?`|k(TJbM4ZmrVMk+rnE;evJW8L+3vI<}4dJ_nBnUx&9od-`w7O zj+^{niBd>1h4dWDIhc-{24+Z?C4ePxCtj z@0~f5HZ6P9P?iRd%}yIREvPz=?9M-aZ zN)qkcE@NYh3t^ECvZ0k2MZWu$ZZ2u_1ZA3^F5AGh>I9qpqo?vrOwAlKCS!b7Np4wTZbh+9`zeW>1QhQ<%9 zV^b$g8lO3i*0y!ZSv2Tl?JaAylKGW!NGtuzcjYmN9M0~T!}%qL1Nn5Okd73p$Sw-z z@{euC?Rr?8`CdB9S31t-gcmGhK?Z-vuD|uxH>3M>)W;l$S-2l(|G8mS1w)d#A_2y) zzr}f-%Uff-Kz@`Jnf=9Df9u|lDJiHMe*azUS@xNa9A%0f) zdRH%f7r0^V&2I`ziN1)ioa1czo>d*JoL=W`cbvZR-#e>muo9Y!`682+z2Ld{A8`H8 zM>w1%i`QkBSZJuFX!MH7vJ!G=}ptG z8W{Zj3y!lUAoumx5BtfEezKb1I#oZ};#(7L{e4_5QS2ut7ZvVvBlz34>$bhubLzgOXV7&a z?i7!UA=z%$$f(Vp&3!lcJE;EQGME7`I?-P@v54PV1nikn-nVjePE_Dh<~I3l9cBwA z`J`q}A584!^&?IFKkIx{;JAK`{lCgmW61wic^W_31wc)6e@%0LO>=)ubAK_qfM4?5 zpWAdb*393I<#tbQT-VZc8(Obt%V01ME2z&Lu|YxL%zcM)DTcU zzppT1*mJxjr%P^5bdHyvxrb+d%kLw{>%HwWIuAe5GkU*%u3lrd+wbZjz2A2jy`Ph5 z(@*c0%l48p5p=ONX}7(1^TssQxQ&&Km*e?Z!eWT;v@(A0_`USZMLbi&GZ)7^vor6N zqXg#KjM002gt=6M3qFj%f7vP*@$cn6f}=W@$+O6N(b-pww?e=2>vH1Pg$Ijyq?Pe| z$M2=WDA%Of4=e;A3^=`DgBQnbA17V%m@r9zvh8d&*YI zugX#R)T!fwg~jH8YvqH|=_d3N6(fSV1PP&$D9-Wk;>BC`q}K4t@@hnwx@kRDo^h?Djw`K~ z!&`}WV3IAzHmxgz!I-r6dt%PUjR{>gBZ?zRo*Hl$PFk z35u@?$PND^&kr}YdWX%fAF=7K@U}$l%`3=G<Kvy+eo90#=TqD&|Q}2 zM~bbfZ~g6K=3mJt?p3D@D1#VOnAXh8SJ}nqx0kP^Rh|6y@-?qGG|l{wQEtkC^49Zb zbnxt9lunc9%HQ}rSN_K5x$-wQ&z0WF+t@Oz_$psj4kF=?C^q3MZ|%F;IyHe)K;&L| z>)LzKGWN>f7{5*1Q74aM%8l~a3o|oFBg3V(@p!jCUFqi69%Q!CeOGup`Q_xr)K{Bd zQr%a2?b>#9&XGJ**fH@m<*1wS*KzJ543(=H?!DS<-QVoq-|PPVw0rNBGcVqQlJzF+ z$05FIXFh;Sc_ukOVY~~ywo38tYM!k3PbA*zvSQ<{yighO(vit)&b_v6i^_Dyi-|=Z|l{DyY`W`UUe+CU5_32gxcB! z?&{X-YVA9f0lbt*O<%iO$+wEb@1 zvmAR~5T%pqQq;X|pCWyx2~dus{(E&Q zzWl3R+4^pqCzVI9O(ea|Ks+}TRC8IsZFJjK#nY=_$_r!7`zoHQUnZVzLm;=RUtZc6 zx8$4Z*jB;_P;UL^ks52wZbRqUHu}t``OW|M%{Ly=SMyyfj|<%QyGQr_(@|SCJytWX zDu*gcdFQ;!clu5^cS4^5$=%nEYBl8Ull{-D^!w(j-@I1g1o^+_n9H{9zxSNmx*j^K z_Rod=UZQ{BNA~Y`n9y$IoulV}6!}SN(ZuzjGWY6y4gU`R1wVlJ75)f*59J@}AKA~Rs(P#_X>9q>;qY%W5PWiQg)mZp!Cdx;V3u+O7}GsD%`;s zt533eCWXroKC<6+GVT=aX|OFk9Ucg$!f~(=%AbwR=nIX0$Lf9L&nejJd$zjH#hv`Q z3ho83hVthcsPvl$2g2)MDqH|Z!$oi+ya6iR7DJ`U&9DS6fz^;R%fhqZz3>Y7M|cff z3Kzor;XUvnct3m?z63ejEUbR~QMem%ehjvOwXiRI0`gkUlduwUoKJWbTnp#GXW{km zuTbZ+uY-5P7a?IfufWIQ8<1z6H{r|hE%-Wo8*YT}!H?l4_!Z;~_wc_VGHtjSX~=Kp zo5P>M0QX+#PbJ zT=-C!43B~-@EF(@4u*Tfk+2<{0QZH3@IY7!4~BE#A#e%gyU5`uVF$Px{sw*sJ3*%f z>k(mR*cBcP`F?WvSlAP0!agt;_J`9TpU4XT4ju>ZfJ5Mua47s7rov_v;Nh?>WEn&F z2sjE3f#YEaX2J{MBzPs{+t=ai;HmHym;>*IxlsDce5i7+G_ZOs>NNH$=RxfIV6UqN zce}vpP-5`!U?;c;_JB7)wYxXLA#gDq0sjC`giD~>iCbYdB=5qs+0NZi?baXRMQ|y+ z1TKSDLB1m$o(q@58{i6f8+-`f2Ooh?!$;v-xC*`qAA>K$C*ZsADfkKeGo;OQo`$rU z&KmdwTnioE`B~T+J`Ypi3y?O=SqBe;FT&37CD;SL412-9!Q z2EGGT&)$Q-gPY*h@MB2bbv}dCQRg4<9k>Nsyi-@#*{4!S)Kc84cGj!SWd!0|8@>Z5S!@EVu_uZN@I&2TKd7iPj0 zkh&j!0`e)0@H#jJegIF0-@$Cyiu#cU_k;yddf*^bJz9pb`bE-^d{aHz8>=O<=i=Dk zFy1(%fmtVY5gf$*#V{3e{DWEN^joNY;R>k!U@laBxEiYcpAT2S1@Jj|JzNKGfQ%`e z8{vEKCiofr1JwB9R@jO--43M>y#p%Vil@~tTD_z0J7b>)bv>@<;GdxSx#ytTf#>0& z@CA4nTnD?s7opmNmtcSRDpbGs8k`ErLvxnd8}K6T{|+yKZ^LWhJ8&s{7d`|(fKS1X zA!8(GGyE8S2ET?|;E(VNsQUX)xG($)s@{GBd%|syv4!(5$k@X99*%%N!qXsacbGAN z=8VGUz!vZ#*b1ti?gDRtt>G%TE2OV<+Ccg`XHQ7q=IjNPuE~^9t24B^M5U|RzZSCR zIt+J8*S@eVWZkhDKhRg0b$ElI+SlXYFgO^FfkWUFm&#;O}4tRQq`{ zRQov&-U6u`;kzK;w>RTpzHc9XlKa!(yYO__LUDx$z+9+yumGmRAY_c>OobP~GvQoF zT?;RU4TiB;KOh(+z98xkKh8h6)u9bmClWDZ}Q|1@KAUwJQCgx z2f(}FPb{&EpQ%y6>ufwxLD_5cmaF_Qm38A;P2s+@MidD_yl|! zz6aMp%BJ%d_;2_e>_mg{JRAo93dsxS6<7gZg`|u?@?11^Mrhd0A_pxU{2 zA#svk(u^-Q!DqRDAHD`Zf*-@p@E`DVsPP7WhYyEaU>f`q4ue}^G5iW%0Kb7cSB|za zd>{M)J_eIo5*}m>W9r3h%Gf%it7VKteyd(+x@IOuS9{zMymJ6dfrr4u;Gu8;>;d0|ec@h&(GMO12f{9J5H#%{X>E11oR3DpijT%8X|m@! z8F$j>mP6Ikv)}=+8g_)!pz7sxsCdnQr^9n#5j+o`1 zons+wqcaFDfycr7;qmYxI26(*IjQhvI1Fxp!y#>qGXm1KI3r;#$rPaRUfwLpX-#H! z!HmEZVJ^ok!@PpohDkkhV?z ze^OKb-_-yAI{JT_o<>>!Ul}wDL(B$%ruF|#>;Ie9|2M7w|COx&_nNLo+W@emef!?p zmnLXY)K|ERo(oVh z)GVPkc5V1g^D`_Q)RG#rCPV8!WLI8UTsU5qTv}(LH6Q4cSWi7J7^yflxJ=79; z8|BYr-gbP3c{k&?)|$kwWAWCR7{5yjOn6$Sp*4l$?R%MZ;ky6Az4z829y8kd+YBMv ztVxW!f5W}^-0wT4uKTo@``GnYX|45kb{Q$!mQCnE%LY?c&!;Sex0(Yxfa7 z7JHezRyyf-rBkWCp`pd`0V$Qa2<&Yy#i{qc&wA3Lwb%)FQGJ^QW3JFjg` zK>qLi+KCm&JIP5`A3g8-r`BD3|GIZ)z5C&fTd`~CnhL)??tb4`^IMbC&@~u8*P4n2 z6O?X;?eg!6P_xm`yf$Ls>7TXuaT@wl8l6OEHZ*jtMQDWlxpv{7!q1#u*!$BJi@w?1 zBIjxUH5f&`($UTUD82T&=T6d()OVn>Fl(| zL3%G;gK1vX$qj|-oYJA@j)p<4ffxzLL!L2ntQl}B_b0Z1ANjqxhOiQ42sB~!IV_-Ryj*;pUrW-Qf%aE>d zI`>oHIZ$&e=y=VX!TIn??!)}Q63&8a;id3z5dEl`1HBS{$o(An8Jr8Z!fPOd1@nEJ z10ed-@Iml;*aa?xy&-zx@IZJYlx}e`90PBKC&Swz^W_fnA>nd(4?GK!7U7xjK6nv) z5YB@S!G#e0Z1{e-3ND9_!#}}V_%vJ%nNK!rTGm1Iuwmwh&DxbW;Slbb2R3U~K7py+ zH)qVm{EAt-Kw5@VxMzMUd=MN7nNM{XDukIQb?CaoeIaRV)-p_hW4LEsN0|9chixuq zPJ($YGZ%3>3}c@MH8w;4Yjxk`sqMFIf39*zm+7QobTMBNZUsMvd%(?bZ}>Sp4{m|8 zq2{PH4%iBFV65&h_Dsom-Co8}o;hUabb8X|jU}^6Fp2vA7KGav`~OR$;(ycG-@TgT z|0emrN&f$p$p3EB)o9{Ba{4YQ#mJA%oYXPnvL~jG8$WW)Xb$>3QFpzVOLF(y6{(Zp zqMtVMzxsy7k_iVJnQ&siZ1!+en5_&(_CH9D-AnRZcx7GLUwbhah9kGOO!>F*L$dz> z_dR=h&qVF|8MzWMv0lHDNPd@$=)LzQ_q`dn zIO=~c#OS?~>EI;`+YGUiu;gI5NpLX&i0T_+`!?^~I?2XtIU^sUX9j5hx~}^%iiXB- z3VX2|b`dGA56pOyxW618@D1OSt>bkV(kFav!m<0(bw6UL-Z{EYU29M!M>kZS@>{F# zx2D}s4)T+~{NyY@xoITjJUOZ6hx0xhT$?icg4D68-&ufu^1Gj$-X|dc>z}9p%mmrr`P{+V2A}i8V;{b=aO69CU0CcdxBAItesZhd z-W)$U%TI3g)6XYay0#q*@MFJf-oY8Ig<~W*$Hqj zIl2SPgTH|luoFav)w)O{_jG{@k8aCt>y7N{D7<#qAxD}^@~7JTMR0d`6QnzJZh--K z2Rs}~{=`osf2xC${5c4+4$^#w@&P!S`{i&fd;n8!RR4o!7T12JI2bA-a3)( z$&oDr{_8^SKz3Y>*@$WC|4CqT{crUDs%PVzBvr4UbYgW)X(+oq?~E*sn{;ZA`w7q{ z9YB)~py}}b>?R$+uS5qBX}%gu3$P;{fVT#K^{M8%f`9d=`gfgwk<-u0!uDAHs$fYl zKO~2eN89L+`d2eqN%r*4G$^mCD9H^?Ew5w%q4NG4Lk9iA|X4gWUWl~|>C%Nwy)N7l#w1x>TH@%vt=={Mn z^a2|Do$rRJI4Zqj*A;kjz2eOo6C*f{hf_0b+$782?>_6DleN~p*O@2!{iC@1uiSg@ z{k$@<$<i4&NgVM{O2dnHB>JAx6xaCJ!JBXn>s&sUi;QZ z_x{_PcRus;>7V@cNq+i_hUzW+^hSRAp;ZXG$w}W``_)0aCFj4C`9a^jJ%;Rt!04x! z>SOY~zJ4fZ?fd`Zp3Sp2jahixq#1iOdk&%5PjBI;AM(=!HS~;%FKG{uKiz(YeKGva zRAC*te;D$ln*HH$4>$t0gCik20p}`+8#B**7M#mHDn93GcoDoAsy|x-6`uMG+h3UT zBusdc?+Y-xYH_FbZVl`L*TSywIjH&j7vKr-MK~6|3{Qivz(V*clpOyLXu^UDw+3VT z1~b=f!c`wyg3;BId0o9{Ybbf0{r~1{g*~C#;1tNao3kBGhwZsnc>BU)xIdKco_$&2 z%V0OCKB+rY`28`q?Y8GBD4knir+3pe7wL)d$=y!L}=ABgsU z_{sF%zK@-mAI_v4Yu|_Fh`XH9%t^!KU}j=;#rC;P{eSeVR^`e6O8>8N*ckKw8js`` zo!PYhKk`+;|Ks2P8@ZF;KWqAMUs=tkkWJ$vgJ**zS! z(~u_KuBT_WH0rKb0=vEm>=bpg7Qj2JkuS04mlp($%)8q9=d5sL`WM`Lt*_Vk!b=Np zO@QW|G!|-&(b@3H916pKM&9K-{~gM@o^GL<=Q+nc@?FBM@{`Uk7|?h=!TMR&05rz; z*8Sv1zwx=Boai_H_nRMXs9fwPNBGIb-6tx2x}9**@S_$V+i}Rrm%KPL-Rpp=y$eaL-i7VdWMGT4H~K+XsEpJr#J9hN8q;(#ZPYaTkqdk>r?#n1Ab?t`pNH- z%NAp7TWi~3)N2mSlGaC?>v-fxHEzh9X5M=!9L#+hJQ1eDlcD5e=6sEOoC{ClUb@5# zI0NRuD`6g#&awb5f#_b%nLWso=4`GaxDtEK1FJ7UmNoLD;$YiBE1xT#Y9l*gbXDR` z_f=5sCi00n6UmbsJy}ub1AK>(jOfXSEdtSb-##fx_09xXjx6{bMt!sULEo%GKI^P6 z4aeyABR>@sSCmxq#pk%+quqLHW_fu@mG4J`qYj(;|EB)mEB8(Pf3G(4p8aS~|9wqA zwf}EHd(jy3|5QGd;m!Sj+oFAnL%XsIBS2RFJZ=9SRh(a0UR6Fd6zDSUh`^YNP;qJT zjN-Dw0OS0M;u2<3^3N{V*)<4Geu+a16#qr?Y595O4p?hR$fwGSRUv$g$8U&MgKszNxdm-o5xWf8g2qMY)yPA#2&Y zS9V7FkkQ#gMyF+u;ty=K#vg0`9i4exkD4BRdi1Dc+a&*+4q{q<$Tax*l_5U-P>?k~ zJ!4q*WaUj|D2wl=H_88=eCtz~>&U;JC|i*>zy4`5J2azY^f3N~_-G}RU=Jh)9 zNGJcl)&HBOtFeUt9m)RQ8u*Q_?0+a@IgRi3fQK{q42-q%t|Aq?7o6raawY4pIQd`S zXVBVz$@-5QKaBhzk=GxK+G#4qxQnri=m{Q(x{K%uV(gCbo{h0{^#sxV=&}3$V{`)3 za+z?^KK_c9tnK9eH8)gl!!!B=^Gy}xaPQk_(hq3f{6*J|H;=y2z4!L$w{mp~-tYUw z=@=yYkENsYlV$$fGD~BvsrQp*{N^GXI@jqZ^EPyB(I+5XLH#+<9HT>+G5d$7)@5DL zHoyGv8@~DY`F20w8hO995RIinCC ztl(aKjdTT*xsTToEJ8n!=o~>$U$CPy1T~ku34Ot@Nd9l?|6}F}V*2%_{{K+52an(L zb;-ALUU==wzixZ-rWGe`bDW=RPu+i`|8Fe$KUh3_2b*-T`xB%a2w@k0_MG?3W}bPP-`Bd& zXdQsQH5t2p-_s`)+V_imcX*n6@97h6j{ALC%J6?k`hmayrsr+hp^NSaUNnEv{B8e40Pe!y?fh2PwLlOAC0OZTw}`^XL|pY_k4^jka8Cm{Fr^;Zqm53Di%)IY<( zPai0K!WN8e|82j3s?#|TyG9-{Sj*0wkF+_V0CxJzRYZ_>nP z{a;i6Z_WsA>i_>Q_Wvzt112_L|9?egFt56pd0hLi#`Psx-XQ>?%Cf5bP-RK?P-zAJ z4s_b5Bn2A24uD5@IELQSX`ON#^JQaGCrlcjIgWXv)+uN4GGpy6VJGt|`cB#yT^h79 z?c^MVX}&*t&sXgj`^4(fip=uV^0H8IS#@rxxV+5n8CAM?SM=}J&1C1TG1d7_q1;Ng zV`_h=J$L!@{Rng1=*XVhwz#)$(ua=aZ;c|0#XV;5cMX0D4J+Fzc#^8!?Md?)Dux>nbMyIaWc(f#qDtzP7v(e(A zwB(g-+(JdU8so>NQCf4wtZv+PA#P#cxb^z4#!c~UM|>1dCNzyK6dT|D+`^(hZCA_~ z%roDMSt+=WpHZnO)cklrH9_{p9saA16N(b)3JHARt*AI{CoA_FkSx3j(@M-xcxP}*Wy;@ zwd%cBh0lAb5dRw9tDW0G9Zr1JEOf-AP!5;HyjMnfS>YL_6}FnW?*$TFb=u3l@aK%Y zYKF76jCgzT+qo691bZbb<6LKN+-`KkR2!YDdSkwIVB4O9a8v)Xby;ORw$AEI0oB>~ zva9nNys~e%`Bx4pD4-P6Ug#N>e^mw(b{aLEh^f3&1x&h(&KRDmJs5{z=jmGVV|qEy zGd=T>>Ie^}4mVZbts{Gx{&R!-{wLzEnt@@MeR=<}ubDbxf*)R;TUiiS_uVD~*RhvP z1BEGX6V?5F@Z0M@z4xz(sr%l$tL|rK>ud|HD%o*eFWSw<;}f7_B-v+w!dh(KBU|}6 zK0UKCx2%dYGwk^nsuTU~W9HxJJK6W>NV}Z1Ug_Z2)|MW+?nuJM%kN#ic3bb1=v`G- z;@dgpZ+uzN^D1-k`geMax2et+jf<$wdA?nrxh1?FCFC8lr8BI_uiX)P&X5- zq{P|2LVJu}B?1+338nm8&u{LP~QM-G*W6MtB=qr9AjIh42ze%+adO_UX+N?m&V$VIp}{nUW|igBgS>To z9C^~aOdQp=Di5|77jLZpiW?X7Ev6kE4?7W;9&TKQF)AEVRxqKgq`2%%^S<@N-`O-$ z8f?XXeV1~Z(W$Q9wSU0_&;99a_QlGYr(|l-oORD&phI-6OV5BQ234eli7IVw{~c*aevkq&tHGbrowqgFTJVx zQ-^nZn5;cO{@2&J`K^8Wi}B~E=S%l*cFFK1Z?C^~?8FYc&tS|xS^k`RMb_H;Gbg+^ zvDL%JopeCYZ~0~dgX83+bHWEKUD$d4_bc1nb>+TO&VQM69Vn-XbcTL9Mn7Gm-?_Yg zYxz#!SK;pb`6Pa8nbOVszID}$tKPWv`V;^0z?u=mujm`@@?l#RG>zRy{!d(d zSKBWGeHPTV9=YX=Wp|%X`WyRMdtXxi`8RVuxN^S}-^+OUdO8?Cow?tfLNgO?<;5TF zy4Me{WR*<+@r2yhTi@>g-4eh3dVX^gOHDjK+vSm|kH7cHANJ4exN_Y|uM?)y6 z=sfB9K@WFYOrvtBsbAMmzAvZqt_S{o|JqA~%d&?4&VOy~3KRe2UDtHTojGgNq9ZPy zS+!^z4f2;|}NM{9DCmiR;g;u0M>Qj6X_Gt7kCZ zu5seilj>u>$^8*j=le0-8*YYVtMeJ`2ETxkiNArVa2p&3{{=_DZ=v)W4(HP-{);hI z7hq*+g)8A<9!3}IBEur{2_`+eK*?`Mz*g``s4>G)ur<^3H$9vVG!sPQV?`9ew{r>8$U+D4y9+ z@yvmWXD(Dc^Pu9H4;9ZMsCbsbUa%4>o>frs3_-=S8Y-UCpyGKpR6J{-7f&mzCywW( zxWl<~6;wRuK*e(|R6MVSisv;@@tg-0&ugLL$$X57=S@)YTmlu(TcP548&o`Rhl=MN zQ1QGIDxP;i#j^*-%HHf<;O_vDQ9L?fbTPkU;`IdV$o*>fxdr@fi4a3`~W`!eMX_lz*DT zvvMZiQRiO%DgJ3t*SWY;eq8{S4tzVwv~w53{kXpb9sp-UrR^N3_G~Ux{#_0AzSqEX zh(0%b61*0k4Ch0JInH&E`8;O<)O+6mOW-X~`m!ak65a`G;9Zcm&bb?&2k(IwLB8Q+ z&bi_nPT{%SKM1db55rsGWAH)v6kG-Qrjwb2dC$3*Utw!S|uQtE69EW70!cv-`<=j))6W_j)K>~UT{9_4X=lNpu*(aQsJ9mKX@}72ycUfpyC_G zSh>&2gNm=pcL<|vEbdgUCc*%m1doKL!jUiwj)JE_wZErB<*(dNfq9TV(#eN~FbJz* zA)Ez^pyHwOZRIlBUj9}*V$1h*+^HVVfReS&fhvD9;bHJx*ae;k<=@p!}N$RUhU+<%jO&Zw1E6M`oVd#B+D-3NX5E#GTUXCa8Q} z41WWcLHTZN`k4wu3Vcn2Ks+HZqf-o&pi&744XGp9c$6SMe0=HM~f zORw)l7BKM8I(e-UV|xcK2XbUBX!aG(^wAnH0P^m|7WrYZ2i5 zI=BVXCWmv@xevfD9e5tI2(t;JbK17>eBe^fljHX`7bZC^c-aj8&cW1R7GY{Jn=q|& zn>k%E8JG&pJj_bWddxP=etFHD!I&bcM(EsZ_8g2f+r2OonU`a)#dk{cB=Kt01x5Vgbjp_V!-E#m&<#H-OU`SP! zzF};q4$*D4Oquj@Gbf3mH}e?T-dCVKUD{xN(51Fp{)>kpul8`CNtG@?7-B}h(AA__ z{iVXE8Fi*IMOeb5y*J+|yVyAg-4~oa5?wwMC|m~+pYoXa#Hf26PuHeC_3cw6lkYle z=GKla=Z`C#KBIh(lXriW@@_l#SNOfe1;CnMtzh7-6aOtT)1FdfFN zrmvn1mBu7-SY>t_{Z&){|DX5&javU%5K&x!n%}MeKj-=%Z~nH#tuG9>9KEZ`_~En_ zV;MvzF~>jAjq*4SbLS_c^Orln%bUA=KAAQmH z8rEOSTtJh~zj1Z`e!6`>-MHVrgP%)>ukncbUfX8cHmjEV*fBAExapS%L&@kGS7^ME z1~tCW_(5&2#t)uc%1bY@T%{{qXGP!gDi}I19G3zl}|FC{(65~VW z!fE?-zb)>*$FKZ zlMF)lOB$L_i_veiNja>o(bvk3r75NKKW$QS+M`cq(AwOo7*kSU=k$jam+-svTWwM_ z{@j%C_mOKU#0J2Og3@sfdWrs4y`MkV3R87 zMwgXCUW|8s3^D~jE5?S*0v)YW$sn!JeLFn~?*V(h|)-@3Yt~>o6 zyLMu+`wmJwy+c=w&L-X%^A5;ZzOS*Pae=pfCO%$@)5mU{Hn?#*(Ou7xDPfPRA4YNN zh0%9^lHIY1(sdlmH_9u6|3SQ(r=SOS1{2nA@c&Tb_m0!Cuj|f>v&J&|j_-ea%;M}T z8FIIiX1#y>W&c?G`spVgaYu*br~KDhG;}Pp(2Ql?xq0b`?$ajRxpMt`lU{DVYyi4b zgvaEh`t#U+eOlAlr}CET&v%@Lu6OWT7vr}M!f)M5L)S<6t)uZd#!Kv2(3`7? z9S@f9+ycy6%wpow)c=zRF;`RnulBOfemMuvxHxt%ea!)~A9TTr3+H_J&@NTSFS@tI z1=qKs+%XnPw4U-m?f+Gu8cqLST~<)WmL~5U;QwU&zoW6ZWPv{DrM8%Hy-v!Op1w^= zf9wKxXe=8lKQmaCnaiw#id}ntQ{S6oep4*%PqH6~eTK2GWYWOe?`W*=o#C;`9Vfp| z7`-uTFuRh6VJ5xJSiia~Z8V`_ZFFS|^oM&T!!5On>tN%{ZqC4z$&A&>YGj?3DQTC> z4|9&Enpb0&8nx3fpD}#Pl&D>Qb3Y*BW`KDndf!t+<~TRUkZyJza|MN=ygF1-9U331 zWb#CAJ>BL+BCAxBowqkdy3Nwu8tXVCSXRg;6uEDQ{gI?;#gLMc@_g%uWcJC(!K~#D z$`koP^XsVnE-0>2HVxxLpjF(O-?V2%@gezw`Q?=;l?zNVO3vv|qHH9^M&R~6lg@U0 zUc@s`5^nSvE7M4>Io_2aE^4k2Y#+4??1$;WGdU*Bl15OFM&*{}76vN^4rHE(wcynp zU|ebPPIaaJ6)<_{=`_3Jm%HuKq(y5Uh))ZubDgw^jb9r#5cD37;x`=A3qKbph@V-i zZlm{;;wM>aqb(~YK8lA|uGhCDV6HbXD%WcW*mz`TyX88)CULn=e!{<8KX^s` za=koi7b(|KJ6o>riny`mI(i>1*HOElw_Fb<&nnp5F*aA5JdG;Cl07}zjel+qzPUXE2qrI-0XCjx+y`KkG3Us z+Ynoet)1FGw+*57u=QSTN20V-{ZbnelXi~E>rl5|FVTx}9ZhZA6&?j^l?N<3q8*NT zt$_o}D}ppA<&^=Q@E$nQyrf6p!0ol6*QRWS8k`%q=)?Kf3>RZIFW zCVq1Xpd}L=G+)N8_T4F+D%?pPz2j!j&Yh6bJIZ)--(A(9a4`%uc`2P`0pTq+{wA5e zX5herJc?eV5#Nq3hE%d~k&Y+}f1+(yn-pzI>p?)x9lJ47@$ht9mn)IE+R&b=c(KOL zS;F(RHXe1-8+~QnMjZEwQ;|HVf0;Nb-Fon@t1+p#`D;wN#iz02<+-0tyejeGh`BbM zRY#Q0%9E<;rFrEgwl`M)qx@6(o0iXlfy|=nvNOjARSuO8`i8cCt1ielmhyisammDg z=G2{WuK#kYv#Db9+>4LgUxfR|asSU4_v^j*AQ|a zbMfVw^c^4bY9?)Qp5U_OSz$%`Ln*_#rjll=kCWkTgfo~$cnQLp5<8a3(S!9bQxi3o zQ6Ju(H`6{T^?5xoQzukthB!5so#v)3pvU)Q0j2j01u(L#AV|NWG~Y%#R1n7>s5A>r zoTB-oG&k;fPHq?NhHQ!3Ox*5uZ8ZOt&#GsI!Lndwaek)0dT#O)_eHpWn&&T!aqpFv zg5uI(S(P~w!o~&n1-REd?*iAo){yAzb@|U=!IT~Clbp*Sj#@KSI%qF%J*wY*zIrHxC ziMwAAci%tmzD3;q5*A{_`ZwFX*BJJI>&$PQL)X{z>#`dVwNsHG&040Mw!m}ON5c(c zH#2Hy#;tO9Vbm_--yfoO5&u4o*!4cf^RLbIUKqVK>@#5;6}9W@*^P_Z_3`Ykh}uQs zw<>BEiQijMyGZ;dFZ9CgHNf-lw5VNw&+fgbT_i5uejj-@5|?kHc9FRBT@-N_2{$up z7fF|IB6d9^{+)b-_iWF|vz1Xh^Q`JpwXy#*|;R$zI#;_0e8?qxw3Vy0>*t`aTMqTZW9^YJVl4 zSYOn)*gfxRA3|1zCU^Vhgv=DII) z8N(=MqjsBD?B zP-Spxv1$8lxl)-qJGU$}w7g)to|jv%jKt=T*Pr3QTrbgSs{N$9wBwS+D&zIz>UP1V z&s=REjsLe!+gY;2)RjovIoZ8e+qCiKNIig%FJSsJ^69%2GiUC&VHlZ1bx-9}`O6_M`9Zf* z#piR0n|4ZQ<+J)ZjX4&ZcdVaw#f4?Tg0yHSY5E_YNpP7ojP2(%C$5Vl9`7>Yc;(5Y-FOpAQ*JcIty{+U776d%iMafZ#6R{nlLzFj zZJU0BuZ#gA{kJy{x!R5UHhxz+ZN=P4+}E+eAVJ(mSL+C;F;gS&P=)&)rIqrtE8)~) z=HR{$n-pvx8(mix8#izKkh2etxi;ch<$nWIx~?hHFNsHe~e!%j2Gl?FZ!Vs@O1No>iRH?nd6nOR1Q6$&JcXB+d!k zsLXriM!H&E(@0IKs#642zn0yVu-wG>W;#;yQpzKfsK%!{|3t>!>c2GZRy~j$qjbx4 zGedTYv;3G^Txl{$Wvzg6vzamLSCp65ciXb2K3{#b+~!smx}$PC)^35@k1)T-eV*%H z^+scU6JBK@eWsOX)i;-y6b$$BOyTw6*-e-~;`cW(;calAr)<|5dxvnl5mSu&Hups9 z!fM>dj=j~l?r7}Y*?ry{dwc!dN2HUk*)%As;+x&RqsP75%^#yCq>D0jI+^rHa7FS) zvW4=-8+$Lg&wCd;HXj>z-_^ZW|8l8`hdI+ZGG_dJ)GpHJIDa&L>g?w}ii@6Y7q#o{ z*}WRE^UiN>nX-aSQM`isp-A8KS=7#q52bT1Tjsgz%e%BpnIF0DW9}c1-1j#3UqtSE znL0}{{8&LmLI`8o`HUnB%XOrP8E3Vip0pq@p5We%ccd(-Y;j;`c^M;1y@$$}SH?@p zgyO2ITx;JvWsU|NxR<{p?7I?|-BZGZJ&1l%S`DucRGCz`HMrk^dpB*2|7u?pZhV<+ zja>*c9`~Qc_+OxRqYQcSU=jC;%A^;jhZ=c}bO;`)L*uRh{4co%-wnzB~6kI3iajOA6FijrS~CNp|I z;yTkK7;j?!2^d?i|DAxJR_9{#No`3ru9fHB-ip}#D~Jh1_Fg-MA0w+KaL^l<(qJ?G zI=sVX@ab|}f7I4X4p2Txox{kJdO3|!;>qt(Jy1xh9-{?_$^!DU7C)t@+wh?EQ}1z) z>*qASp(mw;;-x-i815^)RAGKV_A6bteE+<+a3 z=DAgb5#YI8+EF(hO*r0o$J?vm)h{osOrH5Iaa&~5**qipDn4#{&a0E_-8%UwVXIC) zgweQftf?pU>g3Ses$hZF9;i<4hx-JVX=~NKC~ed(H z@RJde6SN8AB~p6xGzx#PeLVi(NL%lPUlyZ3@W#CNbg+-{Nk$WPCpYXKFDDE;H9^yD z$5rv6+j|oSuN?}#V#|wnKRI&WGUav?p2jt{Jg$n|A7jevi;??&=Kh1oy&d2DJ92Nw zH_6s-{OfJX)8qhpTHf_X;x){=ty9j`Cq?_b<|%2oU4nTU_hm8ey|Ikkdu6~YgRyzq zwWCdd;eeuVbWC@Xr*lla>Xj+Gh0^sH@Nr?FMags!kP@1Nk>ZrHKqys8?lxBD)NlQ*u(>STRgj8BRa=ZBba&6ZaambI~! z#ihBG(<4zc<*ZABh+$>o=9R0A^>xcx|G4|Duhsp1qkFG@tJB}iZ@xMDA5?tvqkWX)$T4agSH0^QYN_ zR{OV_JfBP&4mAAA&S7{#T;_qiH=^(0^-J+`_~p3&4l@DwH@oh4Ru1>_C^j8R z+;q4d|5S%=!6=^se^*@Ur9);>W%=1&CMl1$SAP_aS04HtiN{={TH0`?w6Jw`UE*-) z6N~esHPw~}_2mh!I(4<(J2nlyy4xo1e)Gnh)xh z$8}Fd?rs10QsmzDkMBh8ZU49>a&PrkKSu6*MaKH->&CkCRLUc-Z4c&GRYzWqw^2HJ zI*h6A14=t@4E2hecI|MhF>F_j77ZqqP+0*%}NhX9@)^%rbj>Ot8`zz+SACc@?Tz4)Vq<$zs<qwp4kqkO@_$ zvI38MYo4-^d|B4ro(K50iIc5AUVeJ%qIdG#Z^FICqN6^xWl(A6jYW&gSnK1)KlYh+ zq{Ep!bD8^$;x3)M(wVisW%^C-Wk$Pe3p<(K%$&D~Ezgp6_v7CABw^a6yG_2Df_mk~ z^@@kfEiNN#xR<@sF(iZjP>ZMe)2zqv(yVGmYI*6{ zlIkiGi}4k~{Nky_EJ#xN9Yi_=h>X7BHPF0=O>dR0YWykVRPxxg@WyRD%m{#2Cm~dS zK7jWUw>h6COpAnRARR!0YrAzv`J{BHka5#Sc1nZza;LHypDs!-PbZWYla89h+nMz0 zLRxj@-COa#51Mzid7!>lVNH!W6i8uty695Q7-s}vR*`vSP3c?U)=~8zUOkU-OK)NI zRGk=sti`CGe98@1{&@X!tjxJAu=a%tA?z`|_VLG7BpGVzmUQKm^ zc$!+|)d|rn3tQc?!1=4DUJQgQcqaU1!m?o7l$Z`ueN5tZ#r82uN3UIpmHG8PDho>Y z;wrB`FsDd-wqW)k9`j8cq??T9FXO66-S%{ITg`}(`94aY%JW{(%!T|j;X4eetjwM6 zHY-LZUBol>ukFWQ`Ls=OC2Y=|bzV1N+4axfc%iJk&T;`$XG6H(jQNfLdu@&8l~;cF zzN(3u(#`85a$_PKn}^=}XZ5kM*PvT%o03Cgkd0p@eE*DKO);t|d#sl7Px(;)vhT0( zys=KL8;-gmg`=U587F?7Fq}+pN@Qok@ZKfEjOx{2+3}RdFDrSMv5CVlUs3Wp35A36 z1ebjml_Re#=1;S2r5;e*6I-Xey0O70E)_eBiz#BJY$b>cRzEF1B}Rg(pEh$6$A`{8 zSQ#wK55|`D#)yxXSFgD7X^*EG`?kaUKla`Q&Z_DE|KHVg6-^0An39T5x=STBT~)eF zHzhGM%}g~l&5W5TMR6n*6-g4pPzXgTNf)LIq7Xs|MJnWD5JLDrUu*4sX3y!IX@-2i zzyIU$U5{4JbM~yW)_c9z?Y-7sdoR_8W#Q$~ZLl6P5XGTlyKDKs`c(g01-(EV4BBYZaJ{@ziiQO1fvUKWdAi$~Ll-`st^;glqgl$Kc z4>OOm*-|`~-Xit4{yu-Un?CPJaWI|rJ~mTsO(LW(g_=7rZbgy|`rXm)Xq`4_Z9~22 zjxTGV@;W<0TEj!~8hdmiT(Rjy%B!w>_Q1IDCPFifkPf^##C%sS)mi_%E$-K_Wze4+ zP&`mp=yvom+qYlmQEhBSLi^6l@acuMxx#mDN9#P-O)H%w)lZMYOXs=a)4H7Jn0R0= z)0D2twy1O>>D<50G0u(9D4n$ZdKhpq7jU=ZCEIOyQ}=%ht9Kb*S?P&sL5oB3xzk~O z>37Gj6ByA8Cl8MlI~dTRq1%o z1=Z3-20mRpY*DQEz42_i8-Hj%c887Mn>O3qPRdMi^5Ug+C_-A!??;z& zaz+O|WNLP%^x}P;X~j&-Ys<-cm7B;!)OVbM zS2@LoP0urfvWu05r@t2Mh!huE7ZSpz(IF{`Q{{rQLkg=n`SI%af(U5^!lqT|c0q~K ziE1A{Pq|^!v8Sf0Qz~xV^U7zWn@$yyQ~8{TH|;2FI_jnRq^C_Np3b~9wna!|d)PFT zi{hu@)$6vg2#yQQ$L=s`jAXm5x7jrKylOG)f!F^mh&9O_Rp;`F#mYJ=?Z;Mlxy*y;PX|N=2%Dz5)iMH7!Od12(C80TbjAvfk+3BVi+V>=cNiR8LAZBTnn`%+%+ObSygZC_VW=BcFxs7$HwdBhvIXxuTy#0c~NEJ^Pk74>#_xM@aY*H-6!Gfc0iF0G zPJ0J5_tu`qrsyfx%2nuDeDmi=MsZJ|7x zINP-GqBdnwl&!3MN~S2{(;6UAo3y=h@77sY>{)!Wm+Io93A^2-=f*{I}9=OFG)DUR1iRx{wA=_n^J}Rwvyi z{~%rMU6*2uN_UZ)?oz_3jGw>{LEZ$LuKP^3L9xrUBfk$XMh4q9q)+vwX*@u$I;1@M^P@a@_UR5cPe<7%Wlf$6 zy*nT|qjyI4WVTG}PLX>!YUDB3)-AvO%)vRxrF}i>duzcibyU5nOPDZD#g6{l zn0$H9*6a$=rPp@OAuRV%P5aV)0Q1ZlL!jbN7~O|Ig?`C<{=n<6V|yqBI&@_n3Ogff zO!@F--M|J9{XeX%J0r*{op`b;KYm$NH@@In6T%F4pQH2SKQ)Z}>shLn; zpIsrm{B*71T(GW2YNOs*pmO%!^WN|>8&zwdw6*+t6=^T4=uey7z8lR=--+Q*Z-JX$3nEZ@(vi~34@s}fn9Q6B{OzBnSGgyV;QV&P)uh*?l0Sdh9P2yQ z*a1q<*Z%E~&Hsh_-_ZPdws62rPs6qoAyXedUYnD7YX3}I#n6YE|i|R!^C>HEyKT~Q?Gq*bJJ@> znyND`@urT3q^Cz+BG;Fq%g@VO+ulTutA|(l)xoR$@(ZPfF5M?<{%+ZM^<|lxUTEH8 zPVm>yR376QV3vas=WplHOS5ht#lqR)c;!*`#n@ei(lmLrH&yui-_56|*ST(*p>-xu zD9zMtFTvub7upWJ^rGSr8W);gZb*8)GX@wd^q;1euq5Aad%;3IOpP~74c=1|+J!SIeCHc4OjF;v%H_gy8j4706YBswy zylPYIG$Z9RwqN*s#uiG`$rx@m^yGoetLjKRU1Ld9>7q51UI5E}Lm zpU*&{G)+GJ$rf9`p?US%xhxrNfj8rDk@ankLv<|MMs{rU9lXwR;e z{oRCgcj6lp=Q@*C!9JOfEQ zw*yvPdM0KY+nYZr5797;3))PfdGO|WMmEL8kCl&n@{o%R&G}x-q*-)6I=Eq!59KSt z{rl=yH*(OLfuGrOf*g(xnOS z&AAJfbN+4}m+mt0NwgGCACpMOyhnoTlG`iaR)e^J^UIV<*zNy96@ObgK6>)jMvkSYguw`b^Xq9iGR^)MWb)QbjwREfFfyee)9!yk zrnbbZ&L)yfUOzbg3ah7uKxOv;K7-#jVfIvD4+P8Z5``{KJ&!bZ_3F95FFxYR@GNml zhNtl9$gulglEJIjei_UG!lXL|FI)Ns{Agszty;7m=yt-nVBe`Zw0{g$U#Y&+tH*vB zT3l(PQlBk7e1jjOvZz+H4AJ&yq5lVE2wn5?Wbo>(`qn8!NQd;N6U!u%o~=8Q-;S%? z9KlImzsv_*nK$t_$-Ew)CYewEcVzbJwPbEZ+IwA@hne(>Cv)0Rgv4n&Dw!|EtE`>s z;bfi|tn(_jFfOR);*_=5j~{epP@k##mWo%qiwi5mB}Le46bDZ2JW~4pHs__Ec9nj` z`y=s6Ut_Y`T;9KszQ4VB=^u2{ugt)!^vgk&LF_5v%HW^n-{01}^wX{;QQX&f)q%Zu zwY9iYBd32X`!!OZ=e0An8!!EXZu(lsSNfHp+D+_f$3K0q&G^%w7*C?O%iQ$4!0VO& z8vgW?Gvf4)V8W@-*PgpDE?EB}^)p`i`|U)nll%LTd{-W&q54oAzYTeEPdD=POUX=Q z<1IVM+O%P_|JbxN{w|rq_rEbgnban>5|960k~eo6nLs4Crg)`!0sb|@<{h18LHdL# zZn~Qyq#Kh+Bsh&VDtnDJZxYt2c~o8t4ZKRXIDNf8U9YeA+JO4av;^iH$Uv9x)HZ1r z^dF|Ka(|aUnetH?dHP!H%H5s6Nnf4tZz1>W$Z}t)W*%8zB!Tn#E`K?Ba;sk&kN4M= zTEp}!`|f`3PobST*^Vg6xy*|R+XeJ%$K=nlZ_#cFAa#Wg0X}l21T%JfbuAtus`W7^QGtNv(Ko*9+7x0_491ax~y?<4w8+b<>3HpOmO|0@X!-eO%|t zem;Mb-s<9~ptn3LyLwPtHM~#!J#yV3YMj?5ZuHd+uT6v-&l_q|j8p$088lW+5Ur*A82myNI*_H=ok^ zt)v?hCS7-lqlh#M>)M~!psu5iUgHQIySBM9Ek+jA-AC|qktxo~lv$vR;KyRu+dF*v z_hc&0cp5tbA#nckxku%5mMNd0AZ^;X6+(Y1=6E{KCzHQUtaj6EMqJWEBm8{A#vGMq z;Yy)&!?)qWbs{ufuTJzFNkTYv(MngQ^}VE<5H4Ncx>xu#3)cyMnx6i())cz#<<$wV zKilcbr0J1lV%f^siA>vFnSvuzn>Lxj&xDDri^6if>nj7VKeJ=EeF>4jf9jb^#5lDX z$}V#eYJ&v%{wA^$^9^yFb6hsSnx0_xh?(xa_zQqnIh>jk{P`|U z9ckuNQ8(=zc4brlDgCPdJPjLYyOoX8#p1JbveeTQ)mK|tymb9zh2}zI`K{MidFd`0 z!$n*VywX+MIh}N)YnyuDrkkFWIidLJs*g-&0a9|v7BO;UC7JKCQ9LXbW$4Ut}Ezy_3%WkYGdoe`jF_~6o{v6Jg)wF7em|fxMVESNa@#dYF^{zTv||2~uix9{w15BH`$%Vu zo`0(3x!}-yHP?Oe%grCQ+tB{L9qCIPC(`-$S0ih5sUvs)RI5^S(mMn8T{!H-4wcsM zEU~$se57`#v9(8B@JX%xZD-8>`cc-bO#ItU9uoEH^$Fi+-t_0g_Kerk40_R}&g%5l zPn{=?a_(zbdBFD3@eIn5&TK8|*{pY*qWB-#-lANWcMh$XSf})%E$=+b`oRSz-Cxi9 z{k50RKY7N3Gfu2|>KlXJpOScg#4vx_tB-YZrNY4 z%AY%ircL;(y*>9n)5O2@kA>g9cV|NE>7~z`IqQVMgx_Q0S>L_e4Nu23o_YJWW|ik| zJ#Z2>j@nA8PQB)LZQQ=<>jy7-rNgRL%X>3-GV+wV^x=cME?Vt$i;b?acgXI&%=gXp z!(|s;_w&l#8HZ0;FtFRUE|VPRadUm_LwC)8_sS&+n;*$5J!RAHYq>AkO7Tx?_1%(# z)A#nd@3BGGomhGs_YYee`S+IT-sRRs`=7gL@|EW|95Ay1_cYD*H&3m-=b1Ccce`}f zg@Fe9t9`^iStDno`&*A+yZp?lgTB7tzJ+~NXQ!KTd~wOoX}`^W^Ml*pI=u0rRhJ!R zZOB~D+x^ie&wO#_n#xxtef9Io^4K+B7&!wyZoKi*cbZ<3RjKPkTAeesg{A=&2jleY9*t{XuWE zeW7kHok35dhrJt<&su-@wlkiccKr#9cYQzyb+*y}y@Q`ks9*7nk5A8=ma=SU$J-p| z2l|iMczb3d&%^Us&jeI~Q}A*_OFIGGE4~`GhVd{Ka=dGRs-`1e1N|W97zJeij(|*O zol#JIZ8p3M=D>&H1o#x32w#Ac;AVI;d==gTC094RJsZ&4ERw4n*JQuQB_p=vWI7l? zt*2Qn2fXWLR;0xkKT3cdL3S$_7MhS2!v)&uHeSr-qS3$KC7cQ)(-$xGl;I1YMx zvuE_s>A-Z5fP(&q3*J6_nndhtk_>D7~$P(%WV@7rqQ1fv-clQ0EP} z2EGYjhCARE_&)UHvgga$ciDx?wVQBM2WJnIzCMSI;9l4QehFjYS5Rf}4b=F25K@(# zZ=sfu^Px=3?;$#Jet_4*AED~bAvgp63?)|r!>2hM4qL$Kuob);wuVo_wr~Z!1ik>3tI(t@~ z^c=;thIqMwgp=(%2-3|ugCX69bA|i+WrVk9$=NgJ^n2CgMNsZ4!l@l5K()VQcos~B zN@Emk2h-pnm=0-!P8Ou?IMd)9cpH2W-VUFHv!VKfJKI>tQ?i64Y648(<3D2z4&rW;hGJ3Lk-6;0pLUq)%|R zLfW{)>zf1HAg^r>d;;HvUqfE+8u%6RO4R^u#Cac9zyReno~GRC@7y!$xSzylm0KmK zzD4d6!V%p06sn*544w^l!)EYvsP_K_>;%7rgW&-<1bziIed1L(CcQY~w`bLvzTc!* zpTBqJy4-h!Q#$!j^@G=%n0Cx71OgZGoyQXc10j#<*jwOH_AK<$Fq7+1a2hNJAAxMe z54;Z9=56W)6T!e&eCIZKARlt;I8chTxg8tOK6q{?25LiY>zVeFI2 zAJDtmSYkHyge9}UV7|vdwd*?YMpzf#0?&na!us$o*Z}6j^WYn>A$$v70Co0DBUq7g zY6?$)E#NuuV%P?@h23F$sQS_ontqZ(vS-fGx6vW09MygXsPJ%?5{}DGH>iHHJ5)c} z1IEBUQ2la0cr6?NZ-vS5PBefVcOfRa3bGthiY$kz`1Y^Tnz7qOW{NCRhSF6!G-W$m{R58MgAfNE#^A!C|z z03L>4!OH0TPuK{SD$95XOT$E122Oxca0V<37s7IIF{}XTL!3&GHt(DOX=_ep*qjbI z8g_tHA-Z)=hLhl_a27lbJ_l<+WOq)7q~X+rjo}%v3p^8E3Twl7SO+G;x-cD{3j?qL zyahIc55eYe5xfZQhHc7`05pIPq!H?i3xF5a@e}=C>^#ik2_jC1Z zA$42zi4TqWJVh69uMiIIz6~#hoLL;W3cd%gfgiw0kn^Pj_rs6iLijO! z5$=K;;pgyO_yvq1t$k4K`fI3r_TRv4_+NMvq|O9xhd)5c{3F~B55f1~ukdsD8>~RP z`W3|DI@l6UftNsR5vM&g{WH3;=d#)JZB#BQ z2ieAQod~CT(;2GXbb;r-g$H#iLTfXVPOD7(KWR5}{d?3rxXCgexyoWZrd zc)2*jX?*VsRsQ{;#@YVxTsQz~JRbyO;b16x>(;PX&@*lPGNTo0wA7vW0y5>$RR zz}MhLxD#%MN8l^)PxvZS`M(a&hg)G&_zr9f--W60J*aW~eRwPU5YB@iL)C{}P~-R) zQ1hL=a0C1j>K(;jLD@y$K3psag>v5!PW^E{JPm#iWv~7K>%pJk zVE8M%8vX`T;O}q*JOZzQrOGq@!E$gqtN`zZm7(T~Rp4@16-r;#;3ilDZilDC&tOfc z@@DJ`oQRBP!dj3qEYK3hK(&{8us>`7m6!j(QSbtIGi(f{za~)g#b)px*c`qL84Haa zbP@cC?`@#w9~Z+@XeS+DGuRP!fL&oP*d6wRz2FtF57aq#eWCQdg*fbaYWAEp>ANM@ z*Fm{~ggY4yg$?0VQ2j+RRQpMT6JR==3NzsS@EZ64%!1O_1gJjhCa6AaGE{w=0&9@g z45)fG6SjqS!jAARsPW})*cZ-&1L1r)2;K|T4?PI)gbzd6;g7&a;nQ#>Tn5*}74U8N z9L$H$L-jwaVKjPK2TzCVVNduX>d-MhD{OJ4?ln! zZ+1e~BAi{2IluE6WNz*3hQr~P@FutqGWT`9hRi{oZy|F>=R3&U(fI)~2Xuaf%+;Kq z;X(K-RCylevpqM=o+qaAoXB;Bk^6&i>Sz9hRpDPy^8}}Y6Q~Qy!V6$I*a4P@J@jWd z99D!GuoAo(o&oEV&)QJ+uMX@C&w;9Eb>S#@F1!(*4_QNT8ba0#oJNo}0*ABZ1FQu& z&7kb%3*j<&5nKmb!w=xakhz-E9PLR2l(-|_Sa=Jj~QchRc8Fq)Vn=gad z-cB!A2lj^8xK1C4&Fb`p*mzEVh%M$^4zZD(s~~;4b2WSn#zXp4CjsWcM7R?s!98#U z)H``c!s^(Usqj=d3O0h*LfX1B9(IEhq3qj9FcsbaWxw7CZ-oI!8*pxc3*c0^2;K^x zgfrlWa2EU=-VUi7JnPN81)B z3hfh~19!oT;Fs`B2AZ#6JNONZh2O%V@DN-Ce}>P(!>}<8>Q@*Ce}|t}W4wW7=s-(X zq#PA zw`a@Imr{;u{{gP^*qL!>6OL->#6Z;IoC|Bh`mi264>pI5;pMO?%z-Uo0Jeb|FE563 zVOuy4UIHJ19pDn!1@3{};UBOkY=SJkpw?UB;0V|k%8nQS)o%}k8vh2v1@H>E2*yL% z4a4BOFbVF2!{Grq0)7ogLCyb1L-pV3Q2qBca3IWrSHW>`7`zs855l<)j)&L7CGZCL zBD@i9f|KF9@Mfs>fvNCoI1PRWZ-b@ipJu>va27lj-VRx7cILqI;a%_|I1g%mL7NNQ z0w02Rz{T)E_%wV0u7GdD=irBM6=V(0c^>jQY3BvVnwql){tVYcjR6~AS;~APblfm)aN3o_T&qGf=&y%PoJ z!Sax~y;A``3QvMBz^afrt8+5^37!f|QQ>RCy6{Xm1)c@5m7TNUI!Ip^ID>xj9Efe? z(C3+ThWc<7-y1;2YUe!2SnV`~zrYJ1V=wQ0U|gj?rH>4p09(K_;f3&Q*a|jBjt1A)B{`$GC?ryrz` zb}olY;b5r#VLINPb4OpyXY~(sgT_Vx&Q*j%9nRHIc0m$67p6eXJF?&ima z2?Ovhcn7=(-U;VJ*@FwrBDRJx(~uvlvRwrBL!b z3onDq;f-(=oC%+YvO8D9C*TY4Ik*OHfNSAaxDLJn*TcWy2B`Zyo1o;%-e%11Nd_10RA7;lr>oTnU@N zRj@gH4Yq)i`y%)$Yz242i{X#(G8l!NjKzU+un#;1_JgOv{;)0_2$jD$ygi$awFf>+ z?^I)_GhU9dJU}<%TnQQ8oU7p3FdjC6!(cPWSRd#DQ=nI#_RKkSLY$%HIf`&9&(W|N z%!JZgHtY?@!K>hSsQNVls(xJu0}z`ca32gnmG5Nu7Q7kmg}1<;;B8Q8&w`RGU!I#J zTyk>hul2Vx_^jW^%^{rRx(lkF&4ZHbUMRU1z$@W{Q1$;Icny3MPJ)Y}%JWIM6t09? z*Lx1C{8qs);PdcHxElTf*FcpQHc;RK_!87QOCIb8H^F$g8ET#7RjB#m7O3@=t?)JY zCe(QNF8l_*50!7VcYCHAb%E>3xALbsf!xQ0Q@(e>v*2e??fi2nz3+pnuirrF{UBTo zzk_NEKfq|>{0TOKhv6mgci0{tfxRIcz>Pe+`Do8@qt0_(@~EHLftQOSoa8ABWuIXC znf1$xup8e`ges4d;6PXfYCg)t{DDMx3e1A1!2qlUrGvBJJ+KaZ2A&I_h4tYY*bu57 zHHF{6X7D%I5~_UKK*`z`s-J2HwRY11o({Xf2JliSy(i%98Ey1oeAaI(!U1@>o`h3A zd%}B_#Av5ZiFwu&G1F2^?^LN4{n4%z)i3mI(-Fdz4|q% z`R-QO9=-{+?y?=q-h2lp!5uIIz6WoH%uUSw-raCD-}gcGXgK?!?8>hpdn250;9>YJ zJi83-3tj^Mg54nx`~;F=6x8~3c{mAHgt8|qLGGD5l_B@Yos-~0unJrOPlj4os1CQm zQz7?)oiiZ!be%fzdsr88FV?9C%SF*%Aoo(829SHG&Ux@mcs@KIHin&GbJzv8fJ0zQ zsB+f08negt!&?7SyYP>z7ZXnPwH<5(V_{3!5oW?J@J4tkoC|xvhv8*#A?yv8!9Gy+ zu^&`@><>40pv z^brM>PqhJiMi*sE`AYtFTt5Qk=)BE3(@l`+sWTZihRn&$`s^)G{mpbZ72XCle$IsV z!rP(jpV@FDyc231oeO0L-3?Vv^P%jYd!hR42cQ)GAXGoS5bD1FB3KVDhRxuUFb*z( zL*Y_*2Yec;-*^TtfzLv%TQ7&N!WD1_TnXQY&%pz571aFmc^HLmUVyrvwFcIKYhhiu z4z`9Hq56wWQ1jKza1wkOYMtyAI2XPSAAsB7X80a_1-=hIg&#r9LqCQ!Xs5g3)o>4F zkBzez>b~dQ0@JD$Q}yk2dMeukMJD$3v31t zL)8b>9eZ{eV-9*yeK?J4oAGi-2&ek|7t}a}5n{%j((p3AmW3Ix9K0S@fYV?_co#eY zJ_S#N8gHt?SK-Og_<+rq(+H7IjGqCHgo?*!F8 zJHzW>7pU>JE4&@{fa>Qig9~9#sCg7?SZ04;U-$vv`$P3(1EJc*<*+icUIEX6S3<47 zBtzL{!(j(F5^8_TXsGp=F)#yWzyQpGGhjB9K4PgO_G~fKLdK#Rfw^!ld;~rK7s4mtBB=KMD3qQ3 z7?hp87=8<%f`{NU@HhA@)IOgT@HF@wYz&`=+NbgY)cwx&Q2SF}f&sVzYCqU3Q1=(M zz=iO2sC_Ei;1>7>WRIou7W@Nlhv!iSJK%+IC)9oH4`DC(2^wBhOfaG_%*Bpe}JsT2H5-J)Q4xn2Jiyd5O#qVz`n2%yb3mk@vsS;1e?Me zVKcY^UI^cSEg^dqoYs&%3C_iky$8-EunhINBRmauf$S}Cy22)~JM0L1LGF<|y&?Cq zojx!d#=)myf0zdczz^U+$URZ#a<~r;hH6I||LwVK^a<1#wIl!dKa_9;cjBS?%OrR{ zOo6T82-pRVghOB|912H4%@Z=A`p+z={v!vulm4b*cax&{!sODAXL2^1UJAdpz6t0P=$Xr zl>IRb)`v;31x$gh;Rx6Troz5(6x2L74W__ssQQ@$C&O#u6gVEvh7;gZ@OrogPJ&)N zwdY^ix*4kHwdlnQXV0w)Ur*P$;S$J`J#&hCDtuOaYTpC!ayOB#^fnnvZ#ToU;S{L$ zeGAn5U>a1pOovy(TcPZr+o0s>hL1L97}0m|S?MU=ws^Ta2&eCJU>kTB>;dOP*^T$X z+u#D|<4s|$xxR4rjHU4LJ?smo&CG~z`i4;bKjI4)Q$)ChzHqT_ zIN8;q@jdAam*9pAmyf4>;Q~d3TjC3sTST~}zHoVNxGG`fTkZ>I&(*Q({Gs_+;S0CB zi1=3e!r3!zSi=aD?sL9y_H3H);huNHss7pXWmwyAbAa`IqhxB=FH8(~ej z32J^)Vj<@Hr*gBxU`R7?v38!{>E0o;c}??$O^azu7Y2{=b`e!Zvv%h951@xW4LC|V6piLtrtUl;p}-S*o6E>&TC`( z{E9D}J>MjJxYvB)?0F>N!)0KBN8Ba|A_a2nq z--jBPK7c(Twu{-H_94vV`$upxM0dts+XWxvJ32J_n$Bl?rU-S5&vGZ>Cz?;}yCs~` z{|>4h~lu3U+VPZ=~nX*6CO+T7zGx2iRu%;cH4AmcTFD%d<5;mYRV=W+%4$pv-AZvSO zJdc621&6t0fI9Egfz)9KTR0%S*M*d!gH3GK9avKe{LXjAb~Da5fU4gruY}T8zBlujWwYg$-fK>q4mVVy>a z!mHr-FdqI4lc3h&N5E<@6>45R8frZ)4Qf4&H4L-=BNJ-B2R4-1FP;PS9LTj$>uuxV z9dH7?6J7^3-<}9njxqS%Wo-nMvQ`wVNA~O!+9rROlUUWwC!W9ag|lat(8k@r`NMtY z3)iHQEx*&kgxli_7w3kPeIndwt<%7m@ClzHrOjaBafGx6c>7FI;XB;ST!37HTTym!CqXi4AbFBQ2j?f-oB&W*hG9QyuQWQW9^I6WXSH@D4@2lY{{4EjIhU}#@`A&k3`JN2bua1PW`$j?ORq@+*!-tPQ zop36*45;|CpyJPl7eMxEn)t^;#XkWme#UeY|42UD_q-DapXDmyhni2Ua}rMRv(|3n zzZok2DNu6X0u}F6sJb)_s@_e9L*Pu93U7yAe(XEk!{=uX;gsH8Q2Ch$l^$#RCO`9` z(!Ccd-3OrZ^APmnx9?*QA3wH=iGLAP{EtJ$zZgniPe8^0BvkxQL&dN6PALBIc>8X3 z?sf24t};H&d}2H%oaA2x760>4@w0cq#Qy?R{A*!5xE{(*Wbdr8r(c58`JM;wgWS6` z^>_>P(zowb$0p$Ka{lzU5l-p90hR8XQ2BZbY8>4Tm9KZ8^0fo@gYQAv#qYy;@B_FQ zegc`RI-fzMFWKxn)u{t~mOBYQ(R|{*2;tOU?S&`9FJWD{4{E&H51YXQQ2GA~#zMUx zqYwNR4ub!MYVY4c)gPWIHtSVC!yEX{UJoOWWVG*BH*+_>6}BUf5Kj8|14^Dhq2>*L z!6witWBW6nIWhKlSvZ5s<)QRn5z3ya1ed`R;c|EqTnnS&CRi1|2dl#m;3@C`JRN=m z&w$e3Y`lH9I%^+%maBsIbjjWoBX0~;`PGIhzdBHQs|!cL`Y;o656kRN;29Bf|Dqwh zo9m6C`oShJ7dC^^RdcuowubBB#ZYp^;`Ok9DbP!wab@wIO%+Qx>9G^613SackY~Bg zxX=SCo&>yo2YNw&qTda`%k?3g>PH;ZJgzT17xsfnt3OnG;5mIWZ@L@~=lfu&dN2ff z>EyZcUdMGge>&XLH|fMfrIP@aP9jt~!=Tbhg6d;a;59H6=D^WV=}5=+o#wPNDF=4~ zKG}RCkAzcxGNI}@&*T`rWJA^89C#ZX3zx!i@LhN<{1r}sWr+7iSPtF}f;^&%A{GD);>tU$=Zy`JjJ_`Q>AA=nsdn`=7 zc>+q8PeG0AOWSuu7E$ml~D7YHLw!o*(W2X>biXwI(q?J zIjeDP0bXt+;UwoKD7iMn=I~`G`CfsS!`GqO+Z#}Q9D6{F{@#XaKkveq;SMOhy$3&p zJK<@h`w=`Fehiz!-LN^_1G~U4p!6Dpx9>`~_j&kg+G2Hmzgae}J{&At-z6R~Qd}gV#g$vYGzYiL!F4t=RX4vu43( zxe9oV{c=%+Q#vdGnEsYMF#*}*Or6cRR{>tc_li*C5mT8!Cy2=#)PsEwIDH@2<&+0c z4?M3E3}@fnZR=lXIG%Mg;nXJV`?hI|3WO_zS6h`kn{d)Y49tYJ-QQ`Px9`n1YqxwW zJkIXMBvL;2K())yq3Zb;Q2jM+CeRjs2`_{D;83_9j)9B~Mz3GNJNQl;59Gpcpz6a& zynT0eLEl-4Yl=f|v@3TSRQl<#2F!*R!W<|)^G=LFEW947UQdLQXEId(Fa>(`&%W>4 zK0nN#3hDiJUpV_tYTCa7;r!(@+ZWEhOFDe{%yGl1p4xXmQ%)wf!t))y*F<`r2Tz9g zK917|3vvw`Puh3n>Bd86)wM}gsaK5Wl;U+vru~Dna4l}$g??t z1h^We!Zk1pu7$V47vT(;2k(X(;6rdT^z>=p)f_(mkNU#d_bW3-^LshJJ|B0(#d6)g z51Fw`fpBHXWuVeP%i#( zpZdbtcLK9EYojX^-)>(x`wm~$K*NOl!WYiIQS-;I0v>Mg6b@iH^j%{ z2jkQ5Q}7G$EAiX#d+~?y(M+lu;5*}o;&brR@wxcb_-*)ne2r%agCB{Xj$ek~g^yw~ z)(W47KU@o2i~(i|em>uiz`=A7i}^eoLw_~ghL4IN&-mR_X`^rlekKLD7avsz5+-IE zpU+_|JEVg&&EZ zi_gO!z*lFI8H>-vFTiiZAHg?RL)qX1_(k{~_$Vf#E$~C}Gx00&yYUs+Jl6!DjGvBQ zhTnldg0I1(H5NY-uZQ^ONVMPW%yk%m(y?PsUHjFUD`dAHY}GNM7;X@FVfl@r&_W z@CWb}Ht~0SH~dKabo^rc7W_ec_07nRPsUHjFTroaAHY|B8NJ}+@HzPT_;vVQ_$Vgp z4e)XJ9Q*=&9zGvm<5lVgJ`+C^zZidmgI&jX|9|7ZwNwAj_^&?ja;KE0AWqjdL;DTr zHFR83PDVyr_RyrPocLjB$wT{h>De`YaI2QOFf=nOW!TtMuG;@o({obNhK?VT6Q40` z)Xnd!WKzY(7mUmNGts-i8;W;r4`kfckCo?B?OzL&1 z=_6t|hc+`cEhURk^p8&Es%K$f9H1{Mh^lC~rR{N-Kx_3z%NcYLo7J@kzZ`Xr}9 z)yMdIT&ID9`VZ)5zF&h<;_O$)duMw>X~kylCmTEK5T8dNU+fLqb5klPUYj=Y1A?L^ zb6ykS^xpIs=s84o;_@U@thrRlX;XDO@A}rbSj{P0wFQ&eQkd91dp9-aiLNy-xF$2p zg!5g~P-Cv0R}fCqvCwNR!(3}>uh}zUQpi*MfRU-$T-3YCV-RF0d7s7i+f7~-=LuDJ zBX2Fj=8Ul?&gz=ZN!2y~8MJyoxOXN5b3Ef7Bu>@UnJ}5~aYp7sI_ym~qCm6VGOQL| z*Z$P}Fa2fa71rN2pZ?xwM0WKT%

z*JRckz4)#L^}MwpTu{%xYpuNB`mVWpw)z|9 z)8AR{JJh9<`1s6V9kI1nb^&iyhzJhB(z51>N>+i0DaKZZPyA~`@-?bv^FY5@h#mst8I~#Y2>#uhn)m~SZ z5<*p3nM*^L&ScXE#K(_KH|JP%OG(RAHzvLD*NCnr<*m7YCgq(mHYamzPVlG&Bcs-( z)xW7vRU`_p2y^h9Gi(F*v`0+qZ`HY88=st-oj5ElrE6N^2sLB+nR1SI4o89}gSfeF zeV7c@24ig=f_0RBEZ7EpRl(#bv@I^nD_j@+ZE@Yr!MYGEyR`+^f^E@vEm&usEeIE^ zGrnuVw&=U|KhhQtP!D)Nm1&E=8@<^+F;ZLfbiYi6je7>)1pfej47%TK@>wcAJ}E6F zG2J)Rso=8&b=ZqF;Mdw)BeG$p8ew~dsoI%trwiH}zZTiKmf-n*@^qSGJ zepe8#6=Sij%clSLWuaiSw&O`MdRu}XIuUPen@)+_;~{>_dqka*o|{n8a}Xk(vv9o5 zM~HMDK_WFf+<6C)&MBzL0!4JGURN$#zP#P~Mt^1gbxzOJ6Ic+t++2Ta@{N})IOpjT zx-Oga#xH*kJfAUPkiuWGwZZHMC!g8APR8RK<~G{MX>8#qPRcW#~3Yu%Cim$iD^xxIhG_${p6o}ueU zpLa0F=-+ucW&P8$KE8Nv)Wl)$EWP$q*2K;A7w5fosNsq|6VBdtPtVUUi=u~*x=Qif zS7%?&ojWSLGQIkS{fj?3d@Xz9%=L%YjcobXsV{XsYeDt5PVZiiMbI0~^=0+G&U$dx ztz%9rGw$t;`;$K6-sxb)6J7Du+KCfy>b2m!X%n*-9A=SZtqH&C#6SOA?S?B>udTnj z?S<#RSBd=w_WJd+{&3o^9kYJf4^J=OJav5Jb2|1JJ$zYvVD;`#^qzMh=BqB{4<>M} zk|~#a+O+Kc&60`(d!+Zdee&ymmN9xXZk@C&$s2* z41e*9uOB&mK%LdwFZ=Lx))0;UUm8?>)YjfD^3Pdv-{LWEwIh5<&u58r{>w-7xzVNW z7&ZO;zAfgTmwD6X{vWkG1HDB$7pA1=j6^yY=KhP6?wrPR@7=WhmgU<&zv=V+4<4$+ zJ#_LMU8?x!YeYInrl|8ZmeFV0xr&{`#4zrk%(X>$xpvH>)!Da)ZD1_y0P$v>n>8!5 z-|kYF!1Znr(@<+`=J~Ha@D{$u!8>7Jco*ys?}b#!KpwmTZiH9Ew;|Qu?B5;%nagY4 z-aHpJ8veld?RYys;F(EemfTd2;B&q-10(k}@Di?1gv8-Yf*EiMTmX^H>?3FSD)2bp zr^D6ocK9;98`6b1^WZM{0Q?O;2z9@SwQ2MI;YVQ)zCQ;0L*g>;hg=F1`Mwg4fzQF~ z;VQ_UOJ^O_d}lpWzEmF824_8y>!Ia=OeSBiK$XX4UG`d<_k)}PDKqCx_%u8Ru7&4AbmsgAs(hQld`Ov^_Y7-aGu~+fW8uZHJ8TPe z-?Jke1i2PqFR(+|2d;tq&Af-UFPzNxeo*csYSXSdh*L8QDNm<|nXE9!N zwClTC+l`7rUK+(_7;Zgx7jad1gSBaV0zQCWhA&TI|2O{s|9|{v>Umt(|FlGRJl6k? zwQlFF{cW!ndF}65YkS_jo;~o!(L9{;c=bb9)nrU&6HMW?Jv$TEJWP6v!GCO)6dn@N>@JB6X{8Il*%XfX!(NW z6R)-4qa-a#S44{Q>Qbn#yfxLfZhZZI)^<0$a-8SNagHlTendHHA6GfNvyeF-#5jK)z7lz3;zR4su$18$Su!P#n=U*V z*10ms?os_X5q~u@#2qC=R^qj$3jaMBq<=5X*m5L-V=57>lkudPc~qLgDp~A&`|Dn~ zI^fB$*_9#Mm4T_HlZXtnkCGu+7mu3^S`%UqhZAYdrle~>$7_uz(%MF(wVz09JdxI5 zBCY*ITH}edCJ|}vrzMk&=u&O_ZEyJA=1S{RR%N|(a;at;kDu)qX)PzxnU;~(aw4r2 zMOqUo=~_;tHMx?m4U}~4B+{Btq&1cQt+k>^Yc`SAU?Q!xlyr?J(pj>R)?gy7ogA;V znMi9eC0(o5TvGEyYlB)_C=NSSriaFOImWnv#+M7AY>`IL`<=BF8TYsz`n#rtQy<+7 z%C>Co`<=D1!l%c4-=udDRC=v^zq7WFwVgxLWB-%+U0WzyiGe>Tr?pe;p2^VPX{|{4 z8v>Q*D}BGSc7=UsLg?>?xxY(-vO|)6zq59L(cMvc8R7npsjb=5nCknT9lPybxzO~O z+Z#E@K#le3?(Z7#*^a+D)*->4WLId3Jgl z9Lo2-+Y8V4HP4TVal+2)wMVPM+w8N!_ry=Zuf*r$tG~mZA^c$cM0_qj)K34u@n7R% z@z22ie|`MNWIBHH|LmOPl<`TKIqm|$Ki&VMd*26(nBRMQbF_XZr+uJmANBEVusd_D z-HB9f+8|d7Ta7Z(eIE%{Z2Pkb@p*H3+1=h;FwaffKi`NY?Dp_!7ddf`JSU5hsKL2l zs4SiyrnvL>n+VJ8JoPJ1Yh=;GJItGpsRyl3L~@#k9%jz&Pg~dYn+r+f+L4Jl`mOd1 zZAK>nWa-BDnkH>sw`(F}xfb7X?C|*P)ax|)k}l=FJn;OLag1fAEY*izMEtXhnFrP9 zNY0x}A3F~xRIN-T%20efv&1pl1!HCPUyQCtXww__rXhuKOr$}eJPl<5l(gWc;$g-XY?F_$-`YH?c&#$ChlC@ zYk#UQYWsRXEli!iA=nSNc_?Xp@z1#zxoMw6TA(IlbbK11=J{7;8%kZVEHmfpLKkVOAS*ZVWtHhJMkf`t2PLLepj5`ig3raTBa=*? z%@$G9uScpQm%Hnw2V9x_bz~ZG#72;*=vq>goH1y*pq}{URM~hs8m|r;=hx9q$degC zo`0$%uddu^^j1(;Jb5^q$T)vpnL^my2=bWPVhgZ{He=)T`jb#yc=ikDpZ?9Ep4T&tf^Cvs6TFiIZN@|00oiXvXM$rXQ zt=L~|*Jv(<`u#|}opnmn0Xgnl7lb+uhw$R zqy~;Aoa9Y|Y@u+p)>9v{$H?sG&{__<(7S=m`!}*-0^gbMnDr!`K|Uco9A(v&zSdKJOuCKdM=b)(yz6tZ5@zYe*Hd9IMoNP zoA(nv0UPlBNvPjT7Hb#V_W8kQU(i0?Fh`~XSZ#akQX@3M7bpy%{Ezw`F- zltFQs!5{W9X0gI)&MebwDd}XI*t@eM%09cJXXf@(5PG~W_*253#dD0ho<=8J+6n$; zK+2fRw8Wego)c?b^*19k?dVjy4t0p%@h*^vd-D%pE}La@#a78Hw!O5bN;?kDxwsCU z`tjPc7wLX$9`coBZ~ik^D}2>@n=bg|?zoA4ny(sh+T*jjaWDULT~9uo@c6KWD@JVj ztyimw`P1r^d=Gv}@3%&}uNujQjdY*Aq_$zC`==$nA6nA;ttGurIv7JNy42E`$<0Tf z_i2yDWuFgxv^=_fH6e{?%a2fJ*c#U=u%6DoY3W>gPr^D%;2mrOuoBkaf%cidFvHF*I$$ zU+oh=tboxGX}?EF_ivPR4}PTkyz@=II`x|0wQ>8ZuOGbVl@6;~Eyu`>v~MBOT&JY_ zLu3;r;BDV#`@B59%T^qKmt!0bs6UfUdK$bAG9;V(kLN(yj_1KyP&VWpunk-U)ptGy z6<5w@JeMC|nmtwAFT@YPClgWbXFTf<$MaqHAlLCdDyHz>;vKri*suGL2l%Xg%7@|c z82{DXSI5iw&;M|ml{K~@RqLqtDf`d=h;hSR<%ZL^*%>eQPtO0yb>n>z0W09C%Ky)t z|G^M){Ko&085yIq|M$m#Z(nb$JN~P#m%*QcuUC~hQP?T(V#7bUd|N;>{WdX_TMzR`c%o;_akj7al|Nb`e8 z`#mGsttH(D8p)oG^h{}_`9~ysvyz!V9sLaLeq;ac9X)l!x{sD^s6Xh9wlCDpr89_R zhen#mM4D&(K)<4a#Ey-2A72b(gSynZc)1?ffBKFsXx1M4Lj7(e-i~p0?9=ZmacwAG zj;3hr$Q`z85|EWksbPEvvw-GG=c9o!VwTt(LCEY9V$JhhwhkS-*=(( z%5kkNUhZzf(M6hPHmkseeAn3jIFt>z6l!dG8hY~EF~Y91hRVN|a2iiGL&^OzL>JEM z5FI#M;Q*+$K-rFOz~S&+$U3p}AT1iy{%dLNtYX|11ahW_XEwh$(t>$0o$4z|5~kJ`i98NL->PBy~-jsO2Q{;#Nb z$H=5dSKj_?IcujKxAni_Sv-#Q@7w?0vwyMJc9Guubv|oqveP@8Fqyq33``%Hn4X-L zk{n!L>zbN|Np9Lj@!kQJ>)N;4my7D0o^WMN>`UG+sP)A7?(Qp-SXk^TRdh2cDhXq2 zYVIA$d2@@3^z-hzBJI_#CH|LAE3;p3>~;J8pWmzTy;BB>jaPSK!c~W6O|o+*6Hfiy z_xfGevFS;63DR8GoI^kAl$et=vNKyE>AV$h4A)FLy%MuVr(}_uy!@3SgXwFP1HTw!DW-rEhQylOAukBoI$ zDcNJw5J>X~Z+?;ChEqIteo=)|r2i|c|G9@nxk~@D;VSgM%!Du1H8njssBzi$olWTQ z|D8%-_$BXeMDE_GaX{9Dj=VW;H1j6A=jnf<9AXfyuwHFBgexDFL%8z!4LvHJ5g9qX zRHS?~<@vjNy?b^)9*Sg<+r6&xQT&u<{~UKNs&ZOH8R=O<#$e}X-hrv|)LQo;!tYjj zb{d3f`0H)rEunmbE74%E?Br`R;epM&Gs`#gb+Ve#>+E zPM-T)mB9hRexh`{q~~~HdH1rT{eJt1^Dj57>aOO=c}7NQK(D^CI@rne)zpv7OjFy< z9~6EU*NqOm;MsQ1kk;p43C3fuX>Jw_n_{1NRGn15CHr*bxgU9YueJHsd${!eD^q{^ zCyq-Ab~pAOfZoq!+OPdvuoZjtU-aJH)=-rkcP_p>p5foAM}FegDJ{F7S9_CEy!#h+ zJ6G^4!Hbw@$LPi_3C)+YtHuWcx4YfH9h(m{lN07~ITk;DR zc?}?j*uu*u{doJzVwEtiAzmNr;s1jSxshc!F82iey7HITPFEg(d7X}D{hc+e=IjhcDLs%W2LRtCx&8`_){7Xq4k?yOu zMyAS&z;#snLe5M7f1=Mky6-AkpQ$lbeVV^t(paSLUcaRLs7?6$AL-Lu=gV{T`6R!Q zE*`_LL!W^cjXvYe8$jLOIC355M>tP+{(eX8Px=n+(-bzE>tCynGWQnk_~_m5?50T< zE)%cjnTB7FTuokbSQd_x$ku}k7|va*AHhQGX}A5V^k zZiF{b&opmJgD)XRZWuWRWY}Ryay0a%tbFFLQ~qMjRjr3^rcAURs`bx9dCHS{M`~*Y z(6t|f*P2~;^tGlJdDpfE!!>Pn^tEQ;!nO3mEe`&+SyS)Y;|14Rgt^wtyLNv;xL|y~ zYfTi+d%nuE`8*rYZ`(;X;cUC=Tvz+ZNz59N!n-6jysIt+$FPtwzbEyj0cCY7ziDh` zGvPH3DxUQ5ZX|qG`N}tP`;St7rtU~kBjIoJ& z9H~QcGA3}gSqE01W8&+QorAHGnmy7?PmJDMx_V!y!pG@ZAGNJ4xCy=Q_Vk{XX2i7C ztkr*<`Gx&YKd-UAaJ$O&wW~gxgY7EV4tfW#HB*wNtrc8r8fgTD>7=S{(D&4O*ncTH+GvOHf9uEo*Uyf*&#?RwRL>O>sc=49*1?opZA z$z_&ToTeRxn`?RXt`5KJ-Sg5;dXKf@*+LmavKeJhhO-$}r({n?s#B6%w((yGX}W%Q z9$1g-o(&uv=j|4j&U%ka#sqVV#QZ zIo24>m^=b%?8`KDw^Wz$cEAfxhKf5b7iZk_og0bqn;I-yW6pO~gf@{rMdDrR|gbR*e z1=pIn?KW~-QC-klgzB;64DD0ZPW*kU%2ajeGqYx)2^SCl4Y&Q8;_$|%tdyK_ddkjk zM{GG+QnQv!r=pJMF#Z+v8E5jqdP}H2`Fl8BCKRE|)*KAz_F2KcqgBDRU?1qa7VPhu z7laGy*LThBE03=(rLSL-jGh!%!8P4GJz8(- z?=V-)*gD+Uzr8bZI`GE-Yi)le9eDSG6HYPWYG1nP4?4ov(LwZUDsObqA$io;?3}m^ z^O|#;Z>n}g=gDL0zc%m@(zDIB5O3xfyT+k0Jkzw16m@Nd>T{kelk}_mDAM6pm5V16 zOLd83M~uuVn9-Z^AXr4_Z!Z;2v%$WkLdz!G1K%M2fEPdOlEET(Cje?MvTq_ejUlq% zOuoH^Nxn-TDhsO*9wy_n!m7R~A(H6y=SdCah_*bboGmtqFzBa}-{4w9vI1 zr70WAKkraj>0D_h`POqZ9&JY7QRw7T{&v&rrkxwP)gS#%A=qUhTSor&6XiAltw~9b z_28Q-(FdB?*T+t-Y4SgoX*^h_duFv3H2fcT*I^#$T@1OK!8JM0Gp(JdZR(*`*3dt3s zbCrea6yuUvJ^eRy?%55y+Ku6{xFV-)sG?wbYq>^n9kYeKNH^*0aCtF93=qVt#Q z4(fwk7G5200$$ga`O@gb=_%Gk>ue4#QT%RoAT)g^KDUUmpxj%MS99`~W^5=M{N6g` zyWKzYt4Z6;;hQLajR6JME)0$V@2DcYw)|xd%<^`_esIO7wjD>xK@z&!6zR( z@98!!M`4wB&koqG2?#D5ue>MXcdE=y_|Uxf&q?ka%zjuqV0RJuFV8z@{AFJ*_}%8e z;JeNLZ(;IZaIGN!jf#=~|GN2CSby19;6Az>`v^?_bua!CmA#QIH2;^UBo$Hi&WnmW zmKe0Rp|A~fS1_-(?B@i(+q@Tiw|So${Ch#!7hJp0jF;*IK1SBe0-M zlFec&?-^AP67O$q3XOr`YzK{hp|*oJ_8Gl|*a?-XCk@E+OwxPD)ro97-M>QUJVQ^f>f$`b#1^J4NjU9r0YIU5Z%;Bct&%>(> z_o%#i50hCxQ&;Ro$KTW+`8#`kwsP33s*|#7WK&AUbtWEe2NRlTNq3{dW>Ix^yU&;Z%E0sIt*tU>$j@+q3x(7bzbFgh%^V6jr{yjr2VCl z_P<4XCi(E?dYA{m+-rZnt1*)Z}|H-XiVi%tOE7o==MO zd`YBzhKa2d&;Q5Xxd3)O@Bjb1T3Qqv=@i8`3ei%jREn*&G`hD`EZf>vjoqv*D{{V( zgrZzJg($aEM;&qGh+?^=<5m=fsG|^}sB>`Y_&=VX_xrQH`|kUhP2=zU>{IVOzVFZH zegC{a@5}3byP@mXOLk4)KJc!Ghfiu$=QYYDuX(B`r^@`YFIF7)(Z#)Hf1S3u$vZE+ zlHk3b$!o2<*I6UgJyXQ%?2ziNnfID!=rteJYyP3vI;v4ly4AmS>XSKJrmws6wu>J* zs_BkTnJ?hH$Jo6ak6XFr-`8zEclCL9ZOB`~epj@mIg6!kQsRvde!BeZYcDyyZSsuM zy!Rj|bj-ieq~*3pk1y$a{>;vS(>^`=J@2zXyyi=L&42cqkLtA#rPm$-Uh}2B=Cyjw zi}sq2>NW2)afH(I!&}#{eD9%o7kvHAsE4m9it9-|Mca30X;3W7Eyc#Zsw?d8u4$Oxf_ZuLsd`$leJPnx=6nG9Y z%p9mo91t-Eq9IMXKnM6X90*w|9>|0r!7*?<%!fPRB=`xO19w9F_I(cV%l8FD^x)eK zH^OfrzkPe*H}F63U+{>!zJT_zYY5xHBcb+bI|_D&F|a%I!(Olr90WVS06Yuc3;RQ@ zX&C^YhL{Pw0tdr);86GhOolt*F!%+$2!01gz$0laT?|{mOW+wW1$KjMMHM&)vL#HQ zH_U?l;8<7$$H56O7hVMmq2}BbL%lzWRXZ2S%u$+4+7tbM z6!;{(AFhC<@Hr?MUn%6A7~k7ab7Zzagl$syD&$x_`ZS?H+&6S z!+nq@f$v{%IE;)m-$^|vcgLa3+ziT7>G4zFSqe%Q4PEE^I|k~zI~JY}{{Xwertn8U;tgRCqN^gV(`K zsB&i<{4=~9J_&Onq5+*_8F(FD3EzR&K*jS~_#ZeG9)X|JAm{k_ro*Q2MyT@YCaCnk z8Fqs+pvr?eQ02jVsJSEyq1>I0GV=}0JOsr@>7+30dKq&nzt+Nr@Ev$Gd>1MmKZot% z7f|KdE_fdN5-L4+L#5}xp!#S3g$tn0pnn+FgMWkWne(UNzZr)&RR3oQq`Bi;26N$K@GAH>cq4oQ{s}$_sbYM8hf5&m4Fp(&;9CVh zh0nr&!583<@I@F)7+!*1;2Nm2tJcCR;XmL_@Krbmz6SMez5(xoZ$jKkbYGhic_1&@SdVKkKc1-NI%Ud@=S+&_c+nNSz=E1mZd3%jE~3p(Eq zRQh*=li)cp0DHn~;UIV$917>bWcUEQ1pWi2K-G7b!mnW}+ym2~-b*^v_b?iA9+oc~ zwuP5L)q^=uXHMlo$-E2Tcz6XYf#cz1H~~(BSHY!lGJF?a12@Az!VlpTxE0e{gfp@`?a6Z&n_q|Z*J_}{WXwBHI z()}pzO-1QijyZh?D_|Ua2KIrgU|+ZnDsOCn`W`kyee3VTN8tzXF}MxVRQG)fKZc*d zUGQ_L^!@^V2fu<&e&Dwm12to#3fB?b%S7pltZ%|qANEGy04iKZ!b@N@91Z^fOW^Tv z3TzHlF8bjeuqC`3wubUo^@kaEboR-rP=B0_xk$`(g-WOHuqiwbDxKn?%B9}07fgh5 zS7nzOJ2dle6`#7?YlYG^2y@a8hE3oQYcCpeW=xPa55HyaM4m@NT~A}~F!(amcee%} z1J}ai;Ttdku7g*>x8PK`0p12TLVXvT;osnUQ1!zW$i6_nkD$i%w?dWw+o0O1|AZ>1 z{tY>w&G$W2J@5nU4G%z-TmOZs4}OF?V=pp_`U%#D_reD7DR?+kd36L-dDReZfk#4> zTgO6;kv4%!&sR`pjL*zRSGp+O-1_k(%qd-3!A7t(RJxo7o5MCx_5SJb9C!v)I<JTpEcg`c1QmwP@EzC%Zh~jSZLll+9Cn9#SLZ<0qdj0t*b}N9b1qbT z(okmHj`z)P#is#GM(Mf$bBa$tsQ26-M#BM6`EMW`2?xRPa4=MPI25XmO@{I(fHGrp zW~@&BD1F>^S{ml$Pdb!8nebSc1rfda#z57_V__F~8GHs_4%fq6cs6e4LC)Lr<->GX z098H~LWM)$gBde3?I?vq`M(uP*VUMlelm=L*I0Y{KFv6o86T6q;P%~Zm^%wIw?owv zcfvS02dduw6I43g1=HZ&P~nlgW}L~4KZUycDCXquGAMT+hrQw7pz66N;3)WamA)Km$hnRDJ2ag$F&ByXEa-=0 zVLZ%+^6zqZKFozlFb~Sz=_n(Y=H2pJ?gqDqi!rC~Z~|01vk$IQo?it|Lw_|?`{{Z( z0ZxO{;0$;#ybV4KZ-+0zKf%}FJopB@8?J-*K+ezfEjX=?FYZ(JhC$6hX|ES|z3Xd+s_|t$)3mN=j>BOQm?Gy=Nl*#1*#PPkn-!-Gvdp-RK*p z)p6FyYHyka$5TePBp#^k{N4^D$x!Zk*{+yvNCl$xxLP~g4}BWyDZ;!JwI9{?Gwz`l zpw^)z9B7V;LuI0_M%{y2iQ0~eLTJz)H3(INnu~f7^%<%uf`m9!CTa%i3DgIu{ix=< z*y|2eTmRSA|G!54uX1KUHTM4-Uzm|HTGLolG;uB`r6jjVn?k2#=8w-!FEDF={~OsP z#>aPA-zj}geOYDDsW11>&M3?upFgI^f7*~X{(&6Ek()h9XGbRF=N4q=FeNnO%1mFM z?kPi(`=?CcD9QYs@hKUFMX96lY-nP?KB*(RcF~O#!r;ru@r~)8GMfE#3bV7uw=3z| zg_$~78=aqDq}wUkXUTLVk#b)?p^YU}@$L9NEFU~;~boIv$KMOU&AA5cu z)I@)5@8L-UdY{#iry1R^WGP``YLU5lR>#zSiSYwc;|KIk?avSHrec=c{2h?ovwcbX zPVL*5yW4x<@S)r{w_|$`7&tU>$VEvBi89o`SHe)k)c)~6z|nVF;0^YXGX+l?cMvSqS&Vy|K6`(p=Yl8^;mI_Va#Db*jF zRK?&Cll&?gEf*6vAH#WT>n~yLfr#Le!yaQ!6LqC)B zqMY0@8Rad@^@8HjIoTO0<1;VUGvjE*`zd2dKgA^6qs;7roZ|5no;$N_^U`w^0q4Um z7w{m54jLRvxsaRBha4s(6P&dkWpUzQn@9y!-+A{j=0m zcAlw?+sS#>JAXXZ@(a!5pV|-AU6+VF%ehYBugZ0rGQORJWA`OKpXN)Nh~hB!HID+6 zT{3Sk8aHOnkorgplM|S{d|&X~swb>pa!YpeP|NAm`BRKvEI>=er@Z{sF@^cLsg4sE zar_PPOSjz1(_hVv2%V$h#<{&ZYFv`*$=y>?-xIS1HXP&KaB#npxg+KL);nS`LNTw5 z8qD~;fDTJRQKpjyuHVQ%oJ;2Ax7zx%Ft;u=4Y*%rzj3JGBICK{taQSU*HD+@#sV+>GL`HQJpsVuRQXz{0~W^Wc;B|>ZH+B$L*g^qjq-Bz7gu+akWP29nv-M-Kowg z39c}@iStuwQ&~Q7*9!;x@nsXr0qaL_BK#bG_7dDE0>8$OoH63o%ch?`xYOJ=1yf!e z`d*jg-$UMpU(pfY-h2D)XKYKpx6hPk+b-MR`d{>O=nFAd;`z)IR(*HV@sHhb)e(z6 zcsH5;rE~x2g(oC+>pt}93uZn&d+@`f$$MFj`xWQBoa>B%?2pGxO1+#a~sIPO>619wiU%+Klf?5Ou1Jga{5 zw+ebZUHk)Xd(E|Z$4Tc-P19OTx_wJfFAP2O`!yymue z&DFZe$#1E7&rZAfu<6OmKm7aj$W=Fegq((tJv!p_q%}`IF>v1xlUlaCu4BI(^1jy` zpC6oXUa~2_`6G|69g>`T*rmHWHn_xl4wu)Q5wE#y)t#GjpOendF8No^53}BQ_m(%m zd;Y#BFZ|BTJ?ZLm>wU8?#&;2>d~3^nUDVeBm3R5@NO%QQdx84gktLPz+tlSoCMx@q z-R7WlU5Po_zX~>oS3}h^li}I$kFXoO9wx$RZ~(jkroriOEW8m)R>tdg&h@w%euI7l z%GBYe{*wQyPWqvA&BvVlzZWW8_ras#LMYkc1CUqgdkCuBeHfk!7eTonhcflFkyFe4 zdfe-T()A4H~m{a%|mvG!Y70TVypxkW(6@S)RIqotJ z>Wq>2zCs3u>V%TqP_j8!b{HxrbY+BlDIeW^@|N*!MEc9`lc%}7uG~*@zCpO1gPMg} zf_eqD9p(EPzfraIe{KCA9Z|aVy)BP^+~VnmqcV2>>xuf~SP$XU!xiV4{x8=5`Zh1B z0r_7>L4hL&yb{4(?r0+iyr5o_mn~(jm$w*vlMT@nfMI#ffj%d+{6>UjaLt@3Bk^#DBBJ~@T zFnnn85ad>s?`aw;!#Y5-M#eIkq^aT#_Mv|UTN-d0n5X! z7|W5NsZGiJP+unN`(!Q}eKAVw7L)RNO)O%g0Ih9m%su)dK4a3>zdyt|$+RAweQo9sx|Mt{iHu~cz4 zcC-&=5x>3cEWu7s3~#V@6bG%tlbzr-neIL(nOS3s$7f{{ueveJ_wp@5U4z+ys}K75 zfrp=x?P=ZSkJhgw`uNhHWbHFqG-1GzS6sW-g&p~t99glJo3DL ziTA2-x_Pj#mBA@LC=crW?HpL1 zXGVT~{#yqkH+=Q!bFAe!R`++b9M(8#@qjs>`FAGP-=)1_yw*E;$x6LsX7)k(Rx z&q&MLyMgcAdB0U(hvp@N^pXX7$?&{nZ=Fo~SCFB4$&!b+P`m=y=0y1S4Z3F4{5I>4 zIkKoH@-XLqMVV!FWv&rUcq+-U!p6){BjXb73ZAt)1EhfhS-#dN8L~CDb7t?Ga0+^@1G^4xf!D)5a5_}@+M`TeYwBR7m+IkGC|$EKC;e=wy7@M1 zuP?t%-DlcJvZr(K?PsUhdUHbwcT) zm~`ALg>vr&7z~U44&UD+Kwe|nO_42j# z|C8(Y{n2}@^#5x8AIW!gagECV#!CQbPXT0ZVXX{Ma|(aoa{+5*fZ=DScBq|R7&P_I zPCNX+F}={00ou~AhNOU!8Pt!-j**-vVc?L|ixP(nO&U0WS*90A*HQ8yBloMS9MIk8 zXghK!U95$6_Ndqb_ad`cMnjr^$Y4xNQgc$rW#tq&8~93oQDCfv{#EnR*(Xi1tR{rk zt6lamaVfxYnU@*KWoUFKaKueh_kBt)x!D!u ztqd|wunf@O!1zEH!s#PvPJr^Cj}9FFg7TjUn=u{!ul!NiLS=wIC$9y|^%mJMDs1Yv zsn7KtVf33Yx}S)Xc0BUDeopul4tHK<<0FjIkJAm(JNzsBfpElyrj;YLH|f>NGc=le zY|^Wk_o8#fCS(~4vGzw+g6PQG;Cd8!I9eKWxG`Zdy7Vc$y##_=xyf5{-dWVAIU zQ~b`6^^wL`Sr{yIaXKkGGgM{PWnm9vJ-Y z#mr$lQTHp(=kt<%cJj;o4ONykW*PGp_N=R}Y`D6z!~gX$2QS%hO^q$oluXx4rtT%X zsIKhUYpg>uM#)c2ThX*3C1aGla+exAT$0hMa7=-JfPBIMM1;O{csk609Ux_N;7&+$ zBJe%Tf%R~M_F&*BNV6euGL%eKdj(B~UEv>L0u1o~Vt6ea3$KHd;8ZvZUJvKNY4C1% z1AGilhfhMyLwz0I3g3nlD}hhoJlK+Y_HHPd@I6rbr`-z&z=cqIq1+D(;DfLjJ_c1k zOIEuEJ_$EL$!I@@E8q`s6+DazW)18I*TUiOA21KT2`9nz@Ote+z;P_M@Ld;LhS*z1)c;co&p*V{22Cx+hKpW17^cdAp1M{zJ?3n9{334T?d|n z``|{%J91>qX}l}bhNOJsx8A>+OUWo*?J##3JQE%T+rwjFN2vFI7VHdBj?B3W9D@FA zI1F}$`0dElO`DK)|LElp(JYgxM=*a>`Z~~Qr$N`AX{Rx#HHBXR5<0h{r>p|!V_ z-=GhbYU_WqXKii$KeTP?E8gp& z{x8@66yY^I{}(w?PVM~PgS!LN%KmHT|AyI_qE_}FcK?9?3tI~W$^LWN)sW=Bs&fB9 z3B$_f|4Pm}$Y{&vgchl>%9V&xdoBU>Bl3u?Z0?Ih%aLc%=lV%mR2$elLeS8*_jOqf;sCcSN#d9>A0F>nD0F4U=#Sdd> z^mJUn&*1cIjJe?SR2nz6_r~O;kCiM^_cSJP62J3`b4P2yMlw&0n}m*$NLH))$yZ=D z!`jt6SlP`@FLCC#%0116E#_Wk_E_hh>`8{|$kn+oS!?6^%n>4jlh99a(#iPiHP+$U zS%#fC*x4OqM`xlco|VaG)2vJ$(V3H<)hCQ3z0$69-jNwM$kFWeUp7xGZS`J4->dSv zJ0E+u4QueXSC6m;Ojw<)T5R)^!s~VET__KQ<`w0is(gFyyz6@oH$DWX-C=}hU2xhJ zo5cGm+4iA?sj{@&OAsZe@RB`w$+)XKZ}yj#S)Y-nKIj*T=Y4YEdEXyym>9cw>+Uz+ zCBOJ}zvBGr>dvFCslKe2thu@}>rPI*{&!{5Uh}j|Da6B_ALF%mg_lg$OD5qpul|^0 zdJh*=5PPjWZzygZppU#qD=e7w0la?s~OY- zrE4=qu-ZcJL6xDBm9~Kl)i`?^Y=vqk{S(T)rpV1q8^E*;O1T$@YRG+ELolcI!U)(E zroeMyD(nN(VJRF9FG7Zw36axj{f4uzv1H#9&}T!28GV<*KSQbpXCL-lxB|Uo;%~qL zsC~bwzXP8`>i@uQH~~fwrYqs$umnn0I1!!-siOmJ;8n0ayaslG0sfDIQ=rbrxE4-? zl9f+|GvPh(R#*yW!LQ(KsPiyxgWteApk&*K2m|}!Tv(5Uz7L)R7s4~)1F!>p2zG}L z!#KDY_JT_xXItnTx&UWc_?E#8_&B^AE{9jZzeB1nN49Xx8tYqttL(<%lk*|g? zp?@B}0bhXcz!xEBJ~;E8KY}l#{{*guoblj$74C(vL&?_Pgpt%u>!I3)Z^3VPukBH$ z{bBai#$O-CeXUTsKF6H&UqH22c3FGMr=~3sKA(PT?NKK>`Sf2<`Sd$@2K)ggK;+D2 z;V^Z1_;5s8nQlF(a2y6_z_e)x=Xp2h#)}AltGWk6xOv{=7FC?~|QBacOFs+93^4+A~v| zQE9HF4f#*X044v4!dek30~I`P?lj~;y56E4D(}=zjY75%UEU8zFeBMQC&zU7KcW6e zUeFMup|Td;mmDipz9rd&J3r7}lP|NHci4b1W#_rGh>fgDa}RYtzqqLEL9iTWhK-xr zTB<+KLn-}s({UjE+~jl8PtU!s(0IuYwRZ+#six!_Uj5JN&c*ZU2YU5mz2pd9>x5I% z)UJt{(V#`f*d^!8{!jEN8K*o$!~f^DYv%Sj^SqdU>^l49_$RwQKA`$@?!4CPuJJ3J z73DQv{ctb2Qb!u>(GfjgJ#E&#*S0wCr2IdxzN6jqe2D+Q#^RG`e@92;6}{IzW$Tuc zmM#C}*~3%%|B3l5PJAoMalGVHUhA zztoiRrtIJCE8j1r-|F;BJ3!S59c@2y6TeNlZOU`mYl?aerK=C-RK{NbkB0r>iEto9 z#-P0i0}n#FFV6WPL*PpE7r{Mn1f<~9ys-d%P}U=w^Pw(*G3X^fIR;(|Pk^bg6-a-;C5IHB|nioWHFRH!mB37xkjQ@^#YcJ15gu&OuE_{nQv> zZT)ZR`P%w_3P+39Z2zB8+M@g`YUlsd&i}FJfYh%2tzG;3J3Cs$^#5&1SVP_aL+$&! zWBw&-*l;DH>Y@@*N6^>Znq%fmIHAt&;&jKWI_7^-bon_e>YHB`Q-010M#{}uQD3(Y z^LD1Y^LDbvjZ4qV+32n);OYt}HQmoDVpR186G~u`84w9LK$}S|MkiF2f zlnZD6p?q34<{oX^viVf9tM<6=1<(I^5kF}fmYe^hIYFWF5y>Gw=6(TYGf;9*aunIE z%KRUNS$5sIJevPA6@7`5PA1N!xTo-i&gF6KX#URvp6?H`v(m#(u$(E}{GX$EM}y3K zHq9C<$|pa&`&Z(H=cRb--Gsh_s^n+ASG||e_Zl45z3NnP1%G?dgtZ_zti|Tz`=!4< zw>(%7W)4scVJZy`6AAcBhN-gj+e@1LUzh`QOPXXXcMV^Y+A=D+TmMl{T`=<0x%5NV z_;tUcOvy{e@`Gc(qAaDRWMy8myV^NG=A0TY8JCw##!Ck4B|ELIjLS>*=q3BAuFTC# z_WQpgLvv+3ri~MRZpGWkZt7CbY=A27-+^u5CaAd;@4*C!h~JSJX&#Mf2e2NHc0no6 z;!%ycuWJzIPKA{oCsdd=m@ zhMLQxIYGz3%i$?77iwLq<^;t->f=BksJTD`A$4zH1e7c+4@wqR3?&P@5=s_!4ZI3Y zg_EJy3u;c#68J~B3R15J-hwwm&H1?rYL3s%uo($B1D+3O!X$VL8~`Oly9myLmq5wT zGT~kDDR>Wj58ewufD7ObxDb8;AB11RMex6HF{~Tu^F0C^!lke=Tn3MWk3&EF8*B%c z!?WO1Q2Sgw0|Rg+oDMZdXfAvfE`ZO$zrs?u46cUH!WZC+@Fn;fTmv`2weVB;DpWg> zcUv|G$FxPl&%yZ^b415kf2%n-JFGqB5z{URpI>)bdtbtanEe{Ifyh&x{Q50SK)(+v z|F=h(Iz4*;#^}IoR19WTz@?|56%YiR(m^+OBH1Hs&2gvw;u)jO?$P zYyWCm_UC?gyKGn!ZCDfFvC2;-tWIH3owa{IC!E2u$hl$UeoY9EHa{v$yK1ie`#E9y zJ;?o@OOt$W(M$J@?Z0)AdD#)ynn;d>NrgO9<_q2z+Qq2z(z zz!gw(#HV2tTnS^~v#=Rl4NrtGz}E0Z*cQGFC0ASv)8K1R`{lh3$HO<^)$mO?6|RFf z!}V|`d<)(IH^RH%yYPOv8U6*n4ls2(enKpsy+0bwbz&5 zrmYeFJCM9j_Ne@w??C(K={tyo`VN?v>3j!K&<%%aD})b6H0I+txo#vKCCGEa!*&~0X?dezjQ}Il(>duk(n%n+d+Hbf1Cu`NH z{4aNOQT~+~we$aK=l|8t|0~SS8sDy@Ywi5M-{t&2TN3_840W6|_dD@2(L(!Ea?R?- zNnz0Vq}J?ttr_!bU;NhktJR&4vO7)fnjU#u#$7uz>#dG0|M{1?H+P`n>$O*n*L)PO z`6XWSkZP*`>9uD}mcM4?5ngMte(ANHr?*u2E1o;h$;7L|c>y)m&h?syw8ruGw==)3 zruN(Snh)nSzv{jd^Su^Hw;$uT6K&{+YJPiVg+$tB$Wq$#US8LoJ$9B+lv zbrt5MmwZC;n+*9~u0QE@CaKONt!#bW)@kMUr+KP-gfH?W#v{1z?oXrhNcW;NN9DBx z%uhklthp#5dEmf)LsRnd^Tv!d+ynBQz2QcM$=y{*C13fQy{gjmlNCa?5G6C&39V!!$AKIU%7`KDZ<=kD5 z_8Ae$MrI(ng66W&#tggeOm?(Sk?c$lvZJ+cmD#x(JJ-{Oz4~I~kHWOc+8I5u zC`$)u>fQ(TUIr&A>Ymy|I-}H2rCWfXk{#&m&bzETu8iY(t9Q@#eEbsQkNbQ;%0Yeb zoQ}*W9OcrukJ`!-f9RbqaLh-fy1B7BYvF3*T?Wf(cG)n-+c2I7n-j)T6Gk_PKPjjA zIeAUtaL>>zvh!bHFsYW|BGkkp_vG~)=X*HzisjtXl-EC#2g@}}E1 z)}B;JdBgJyGtGIMjWHW6Lu-m&`JFzLvwpNW43(kD59M{43qD6P+&3h@^$jVHoAYlE zp0Bx^_tS&4xrJvfoU|~0R{Bj~rvN*0@8VRGH|2-&dbsZ>IL~%E0!v(UtDHRB68iNX z(wz5j@HKG%r(}1();m!6_Yxkh7pbW>yqAp7YYm{+T$CUDk}*_V8(7^nWnQwnmg#bT zY3IIQp7l!Kx{q#Hv^QpJ@>}%x9e*p%;i)NEo!1&VFPW2hA01wSWC*ZI%AT_uW4C?Ms%Vy?B3VovAPU=NaTP$Ljt?3qr@Xy<|~dGN6tXjm!LjCxqH7= zRW7~3e{jv@Psyg2MAY@IL+wIE)~W0BqZ;r*dDZ`sg!>|&dK!!gm~Q%njH3Mfobf3c zg+-~Ob23wgCid$iL5^;uj4#Yc8C{&s?d-gwteljR+@jR{(U+xU6cjkj9-mP(u^_8< z|8I8wKC5Hx{@<~+`+wEW|M^|c|8Yyg8Wa7||E(XBO-WtHmoRWh>P3k|h9(UhklH`~ z0_i$R))p0`Gr_#}|8(a7Wva5~>V;B07Kh6I1NC=-S^JYQj^o|3Gt-Om3xBfD?ar5> z%~iI3Ol=D3bJNGuR5NleSMH+o;dIf)aoQxbd3-0)c!{F{D7j0J-_9f?_4%91g2yE} zLln?;sJ$Y2q$+6zQgL!cHOc^Wl|H^QtDo3*iFDGsk`XHg$;7+UF}puTIz`?lg|p#x@J|pkPTyQIkNeTj zf*-@%;J=|{8>-{xK&3-plqvsBy-`Z$+%3)9T7+LOqPC-ak#(K1l;3;(AIUqp zxJLW`R~{4q;CAg9V6`%US{Xp?8lWM+QyG9Q4QosYP*oW~zd;GZhb9lHvP@uuDs?XE zYo~sv9`~I}Uo-7-Gan**yorfBccifldt7cmmnzwpfpX_RNbkx7y!yGa>-JMKF|Ylh z6`*yf;pp#m!eizngqz!-{%pBEt(7;>-tnofn}TXXyrTc+#LJDA)2G#Qud8ZtsvEP3 za2DZbdmQTT_+jQi1;@z^qbrZuYs2^l?9ejt9g9*J7r0^M6nnS7{!?=xsuE85ufDgR z2>;gl-qr0pO-on%dBy0GytViDikbN1z|-F;$?sfqeede_RlNG-UVWJA_T_$SeR!|F zdQJ7oy<{I=GLD++!&kRY{!906(uWMFzo@>j`pD`FGk3r@4Bh}Qf-@oId*Dtu5~}{W7|Q=~C{tcL zWA(mL^oOHTP`c7Er?Q)(AaE3<&Iud?vml}jXZ}DZcp3Vhuo#Yo>RU5+!0B77UQpj! z{;BLXWv|btGPx9`a=8^s7k%`A^n^8VEL2~aezb2Bi)zF5Z^P}GyPt93pBn$wx!INV zal@UV-H(3lIFvS9sjdIXyS8qvt^W_Uy@Kn1eK$2Z{%@Nqwd4P_p^g8$WAIbeSmUDIMo*Blrcut>+%LG}yg6CvnOe!>3~M`mMOHVPJ@XnQ$wb+r#e$FQq1-oDF*P33FYM=O#c-+K zbNk#mwp~Cs+wn*3)iclp%F^J<^qhim>8`v(?p3_}&aKcsx8fNt{_@`)YhP#m5AO4~ zRiEFDr}4hZacoIQl=i`K9Hf`R)fA<)Y=Tl8OAp4exS$}b@Ry3?6V`v_9r=GWN^#s; z7DvbXLyBXk1~|$U8b{xYZeG4JC;!S{E{=1p|Dkd88~>3nIo=;q9Gj}M6DE#v2jiHT zJvO`OmwJy^TmM7jm{t}?$NNKy<9_Q0*`xgTcrcD7ZX~Pv-MimJW#6M)9%LTjB(c-q zQ(jfS&iNk8;%K~=sp|S4{7r7R{)Fb&(u46FUyz>Rl)TmTr?PmuLi20f!8oSpo#zDJfP7dkuT{}N@+O6z}U znd3M9YYax-|ExH=<(gj&MJ}~5RDR1#mEUn?ex#4j$j<&*ez`V$(@ zt>xo6#{DL0I-a{d;^|*w@~a!qF+Ve&d$Fgzp8tQ16?o0N^ODW4K(HMhG4;JS&pQ5w zCm*`~ty4y9d!{}@XD=DQ*BDw&&9CzsGyJ9JP|HcN{^ILo2 z{L8WSsO%j7@`CKeRpMWfwO0%kzX?J1(kk)qDr@g*sQ66|vKO%S!o9!iti7pF?~ie) zgYVDU3-|tRvi5FA51vUZ4{Hp0oB!q5NANWN$$w{=IDNt%36Il^}bimH79Dwf82Jf9rzmZLP$=jn>{f zQ2xCeWY6cc+rq!UE!N(LQ2FJfAbWmmFWmdvZtd-WdVik;*^9IGbU*Za|H9ha1*3WX zWstozYft5BsJ(A77s>s-Q0?D+LG~6{d%7QL?+0t|Kd=kW_XpYAT8V#kSko>4>O#32 z3BQ5uQKlVg+To%8Y0bLq9S-H6)~;`b!Se#NPpS42b=QPfWiG(hY0QJbvlsp4=edWQ z1CUm~uJ3BpT+|b&*HE9Kd{K3M%~73DgHYp8Q&IPzoVb-mSaR_ZiD&QXJ#33tveaL?|oKBo@R8vQp+V%i_A@y^!H1QACMY9pm%D2ew5qgb3k&>_9g8*wQpbU zZteNMwdeoVp8xy%JO4M;l!P@j{C}up|E>)2>cc3CxvoX&=QSve{cqjr&hsxD_I6~5 z>LXUX{LU>`hFOFz1LZaTr2Ag$>|FWnRvWGkxI;_DcP2{X{so`gdHPPUsy_DShACLC z>COeD59h?a1S;;!j30;<%Z9^$)wq99pMY?+Ln-c(IVkS!zp!yP!{knwcogRHJNH80 zM{wMWqKviBxThIEh?jx_H*H14Iw z550GH$o^N2d#MdqXxs_|p=Di~@q@UR4e|e~aevB&EA)Hc>c-s+{U1`?-LZdprS_Aun%Ym%-#F=; zO61MpKlcx%KA9ea16(S;<&Uaw`Qvx}Dax-Af825Y&bTl6jPi}zsqL*lyh}#;o#A&U zJ~C7B@;kRe+k{?i^x(E)fdsc)@>g!hqMWw48&4YmZdcp?V1L|k{?K?XD;H1AGN`^k zmBrKTb0lk^jw{%I#dCN0c>b(7BB%CkB9nOS;Zxl?0A6GIbNm{stvDw8|7)!O3umsv zgQ=tEw0yPUrur|uy!o%=rYvDi-*F0OySuxUJo9*qX~RE{z3YL&?_SJ)F(>MN#WT^o z_U5YY9Dte{yZ4$);I%&0YmPxFgDlYz=j~p!aMXXhelYgZ>wGbvN591yLI$mPkNaCD z6t^53yQTT3J#L=$`9t)}Ptg50nzY>Z=jaN4IwzgKf({a$1DUUN%oYE0j2 zEZ%EQPIc!1c`uOw%ihBlh!(-$N z;lq=SIfdsksPN=Kg(uhAS9pvZAbfZVF{kty4;7wbsPJ3~6`qMu;kgRB;W2&w@Zp() zIfaMzqZ6K~P~o}W+E;i?-#mPHZo-_xb2C(UWsEL)#_fj3^tHl=NBg!bJV!%?NBg%c zJnRAQ*jIQ=pD28Ij>DY7qkY{Ko)%EyIT0#6Euq45GIYaZ`aj|GkM?_4csQ%T2~S(7 z{1a>KD?Fy35o~FU#5+12!`wt(=d;l{YAh!2_fkP9AT$GfMsL28SdnF7tWc%(xslAhiBqk*5M#7NfUj2IO zSDyiF9N1f?nTyajF@BJ44oOZB`eU|F=FfRuIoYE#a*HzC6=aTPV6=8e1Gh~OeRnibm^p9X5D{M z6@y1i@~dbx?7wSW^rD>H zF&X78|K1kq>q{~Rb(^B=R~n~xWlw$;^j!48d`=w<+Z|6`Ji*8;vnfwg>DWkKspny? zI=UAu-(%N)N3XU>ODwTZyiaH1MWd&0r0MX18`%xtxN17G>)QE_a_Wb;C~j~S(il+ib6|VIyv&4I>8Kz(VQw#j6|Zt(K8fc)H_VEU8)oe< z*c83ukv%>&FKg_<{Q;E@W3mg!7crK~W1q}wE`r*NMOnE8?!CR-lbaY*kQ{T`LtpoD zvhv0jjXRh&a#I$yH(@IhLVF2}LI0$a4#w}w&K;GV1=tylo#t2SeH%NKov|o88?Zyr zeD4O?@j7GC4O}u>JlpOTu1LCE$N=+;&C`@&t>%3>zP7mxTKG_ow+Hub}4YgzP zN3@eaw%h#C6T8Z@-B2CLAMGcZJe^a`Jgq*$&&?mfc{(_Z)73!cQafF3+T&222xEZ_ zW3dgS_c#Gb#pQl?hmwaw!|2Y@X@9tUaO6BrIwtVD3t^mP!h3+2ZL15aIGXE_47&Cm2hq~;Vf~3skH3sn5^)pTw0#YV>gT& z^o<6UVe9O-XwFJ4Uxq0@q2+<%r1br@%Y#n5yQ_~d-{~$V9e%nzkey}Np@{K~xa#0H zS6O)=JK9U25IfDTK4_=1@<4W)@}jQA&dot~Dk~3Uryq7y9(+9cpg)zB2W~$8&gKvG zg~=MuH`kr~vEUk09ux;<$yxCByfU$$Opk=(R4=YtK%v^G%wM zmdwA~=}%6+rQM)C@1F78xP%9jn|YtH>NVEjHRiIF7ZPqv!D}qQYwV+h!ZSLe$F)yh zH|x{C*B^iGJxe36oyR-(S_gWe6R*Qg8U23WRbS01J@TRZ_D}4Q%UT@Aor>pBc&&$R z;e`M7nL}6X`{L%mlY=r`OFhmeRtFGkKJ(95sN-}H`#kHSg$i> zt2@TwwZ778Y|Cp*&}*#0>zs?~j){1Utwi{hZWYHwyvE?xIOZ#!ed{$A={2@e-7!k9 zv9sxI75=wBK5_Jd3qKi`m49xl3B#4gpK|UOeDultoi6P?r_pgo%`dq3y^-GM!g!4t zd5u-Q8Jz`NmexES6HAA>XD3U~`VkxJwa*dG1~c87Ce zA2<&Vf_K4@@NPI7-UBa#_d@ko7C@y_dnsE#tG%jp;<2w4O4lmPQ8oCUh25aWC|rA{ zt!V1APC)rYHwPE@$UwhfW7HZ_VhkYd&;!4WUnR4eV>v! zH$;CfEP?02tuPKEe|BWak3h+iV@byZ*a;@W?ohJhI4GI%V0a?wbwO_P-5Ig*vlUa$|Qb z-UlrhCqZd%-T4FxW5)wG0*MCoNEtA8$a-;H8wJwxjl)mOp~W1Kj<4 z)hAH@Kz+7e`tEEPH6EZqR#iSpv%ki|=*m&!kiqIYAEk6mK=me!X*P_G7}b1gPB>*I z%vJ7N3mp##PRqtv*7p=n*4~u+71Pp;52op*aQ%gZ)XxWDAHuoJgwq)haMQA@W3s}Z za$!7~$L{;qctCI%Yic||ZvK+xL2usO39Cl92I{3|1RvyUC zUhEWNXGf5o%E|-TIgJ-}Ep~<@oQZ&1H&I!6AUi48QF*X<>_Iz~l?QGBhk1SoJXpgkN0A~EBV1o?&&4R^pZ<@oj+Y&IlGr! zvZmw&o&4%YR+LwI$vwC70>h0Tc*#SXbd>q16BhNjzstZ;_r^xW?>lnBk<7~;t^1=U zJ+kr6)6-rlXutl)AD=&yj8#+PD_-)0HBNjh%CD;{kFIY2+iP4Q!U<1B`TOC{T9Jz9 zntP4wc*)iR$z^pfwN3n#+f@M<^^PKJ^f2l!8O*OBiR!E4b^ zfY(9Ejc_}4-+vlPzW)Z4eE(f2`9529IP!hT`6b`~ z9!kESNQ^<7!MaU)d|)2(+1mQw ztVyh`|0|cj{}$_im4QQQVE#Y5$i?y9Pje2o9`W1Hi%W6Ecb%p>IZE zQR?WN%#@*t{rYH`Kngcf#uu_9W9=G%@Kg9}*8u!FV*q8vR}BpVa5jgT6J1)5!jQ{P zTZaF>6Zn%PnFoy@$@QJxMV*o}TVk~;%Nei0Y{obOLHr6VgWcgKcn9RB?-X4%Rqt12pI(g>r`ibsr`vYI zR@+WEmS^Ks#aCr_R_2VXW+YG#@pMS+f=eFJd4hub>DKp1Xd}v_;Y+802b( zk{^FSNk+TQ_>n44+%vs+6z=l$r?UOF+&I2y{g1(A`F|9uKmPA7i(_TyvsEpQz3ZD` z2dCoz{D}U{j4=_%>|Y{|?wI$IF#Zq3|Mo%tccD@7Q{!8j!w~LWx?^eW^^hz0T@J#Z zLFN28xObW{UE%M~dY@`<`e`_L$>qJ|?_T3cUUDO^c1(5KtzP48Uh8$d*1LGE5;2$`l#mG>0aYHUgJ+*d$5ITk3KS({c8LQ|0SF9n?LgC+9Aoghh4h6V}nZ=JD@Qi zZk>nM_@dW2Xa-8lQH@VN4xn z&Qy+~52dod6-t--QPQg)r8-ajDA%5;mrNNKYA?>(i-)SmdIi}t^@%whSUVleIS&Dt_}|3JhMW z;I30xInAt52)8yNbWB5Q5!TUf+>Y9>{$o?tBcS3?we>#Ry01x$nlj4sy42h__2u$s_w-8|aG{GS)1Y3+)#N{V>a#CLYgE1k_fnT_z7C&zKWE@gqtuH(7A->L*Na<7iu8x-j)eAVZR zY1!A+|X3GwBhtqXp3artK( zf}SN=dyTD|n}VMul()Al_}R$v&vpkt3-NDn(6d4D#-7qzariFy*@*IP61lSY4H^+& zF7A=R&qDl*3VN1o{d3c-VeqqF<^78eerDZV^j8y3zjE;DGskJbHeF7 zYn7Qbx_GQ{w+9(Z&rf?S=s7(W*`=Cyo)tV1^lW^IlL3O?@jdo=QAVqz_>j0P2zqvL zNE$qN@L6)Gy+uLKTzglO6&2QH=9v=^WmJWQdPL7k+pc!PYVu-7^Q^IbHY@lUg2muG z{9y32PUcx1GJxDHcp=D51dp>25yOMSA?`nb0K|LxTO>XSBg-c;xxzjMRg^P~u!@`6`=sQW*4-jDbB zA9a27)U)~Av&S+d;@wXUA12N1cKgWQ=YKeUur2&=>->-QU)i|-g6Dq}7(a-+Ise10 zr&Vt|n^MMEI&a+kA*IvY`d8(S>SY~F>d+W*{>D)zxMhyYAeBWvza?<5ILaHhJW=_g za>n>=g<#eo=HhDWf76B;R9pYo*8jgi{XgVrGmdIMxu|1kpXifx^-|J|H zOtN~KaK3lXF?-Uw=G!yic|?%;OA^cLPwZ1(pWLs!KC^#$ee&S)`X`2z*MBgyy#AVt z%IjD2-2{gx>eBN1ORp%eKZ-%&VDmF*$OP+8p{Ek8FRW8upH8PH`1z{C%Imw+01AG7 ze1r1(0qCQH((kmE<)6-6;88&R?G5#{pB1~wHAj^FM<<@J{iE3a>R zNqPN~Y!V!7|8HsK^^c~P*H4}q;jAOE0gAeLPI-O92g{p(=Hc@C%N9rYV%*q}zrR@# z;cFD6Z?vkszS-*$YAU-|)T}omG?&@ABKVv2VT74`Y;Hv@JFAZ7JUdq%{^ky?E<)7hW>y2*1Vc)_WGY>xN(cQpKJNhKaKnKh8uo4&zfszIGA!y?zCK_ zeZ`qWcegP&lZ67h-;;#`;_9c2xfUdz!n2e(>Hh5Z4P*SqT%#5yydxr9&m=G1AAF3l|2l^XNWbw3^L*(8Mn8EP z=M!*0o^(_A7P85e%+0^Sm|M&XlK%Dj#=RKIf9V(BYyDYZ*!e`m?`{0PJksb}u*s6l z$J_PY4KA|b`Kw`zkBzyFo6UP2b!~)G->i=^{?%)2%pF*1&Cf7vc^_+U^ew5(GYiiipZ#c89F+bbptBozq{Uj<`*;}>JgmcFM zV{Y*t!(qLwevM(HKbw4bpw#GNcNp3A0orGBXWv-Mlg#?*eU}-15{EPB`ImG(#r}3J zea}mczYG25`OGxK1B{u-+>RfNKiLc-Nx#$jv&X*AEBHQif3?l;>D`U}ZO0mW9k!b1 zTR0a$=33hKUhfq1eCd7W`Fir9o;Nt$m|H!?=to^+IIq7kx5MVIcp5V@pYAu$vuWH( zzhkR$H=adS(#JdJJ=0!kV((L+DL*c!^3e0v$6I@(gY-Arbb86Y-`8!vi`{F??X%&W z{deQu;Csw{ou?-ItL7WdTVN{n3Wqwi$DcSQsSx&1|@KvdBsLdA2+% zZf~CNwD~fZ^iVuje`n0?L+B{;9c+2j?Kx}TeqVb|HTo^Z#+@lPUB9FqEPMOjGdz}d zko4a(G9ixi8_uLW@}uw8%b5F;??KOBviW?YwLj06j~i+4>3NbZA4b{yl4#=*Px+wm zPiAtd%*{+N?p|T@`OK~6{;F$D{=8z3G5?ayZ}H@B*=vMcSDbI-8)4tu#M6xZ%{G0O z+V|hW=F5e&XJl`){T>nz7(c<+juXJGS9ED`iE>f9_VP?`QE;_1NQ!k=ZyJP^na8d zOKm+_Z@b9{u{OVb|D>^htj$kbYQ#-D|@p3AOl^f9))TiV6Ax7Nmg zg{|+J(VtPc_SpBknl*mXFC-rjMBhmJ-S43j;h4L?m|t(XqPNk<(r?xC-2KMfwtt#% z?X&sgF`NE9|6`s{w(obgP2bK7%>C?^#+`b8XUyN1W$T5-Ha^9+JuujQ7dziI_U6%^ z(EEGImgDi1LrRy<7uxVzcRJhji?!u>v8^{p#T#>zV~zjWTWvh-d)RE_abSg+SMid~ z|4H^ecChh_SYYg}vgJn%odKoG0s1xK>@$q}#Wr7$wBJ{Z4gdTTZFqayaC9?FxA|_O zEiXrLR*u{oY2&-ueoq~2efOoUZ+qH!&%?bsn19HoZwH%>uai!WJ2rpJx8K=Iw%ysN znR&k0Z4cY;Zja4pomto_`;BaQx7OzWE%yE7ZZz>vq`fBpCo)nl^GRnK?y&Xc+8>O$ z8(%d3ZJukM?>xcqM*BS#`;ETMdSkBtEMsq@EvMIhW$v%A?k3vrv)Hzm+Su=5y={MH z`;Gl{YyNwi-#ZgdlGJyneLwYV`b>G-Pb%aHV-*Vg+zqjS@>(*Si zG*gZ?u=lUnX5ur-eh&}XcucnWajk2Pv+f+Q?)0?&#an-N*!I_Cn?Ksv{NCVn6aQ7V-Lrh3(QkRpFsru-SI_?% z{|4J~=1$s2djFeS8FP(nJv`g?7h-LC?6CP@k1dCmMwonlg^k}@oBzHXVBBA8>!}<4 z#^1@dz0>+r^L(i-r?N=u<285^Lb2; zd0&ledn4Dnv-2ZU&d#^*b)PM7MxJckUrs+j?Vt!7u2J@TSQugYlP%6R=8|l?=P~;| zCE4(7v-Q~g>uh>_WX$j2yU}}@V#~Gpw%nd#>&J&|y*RFPeAzrdV9V2bR(`POO!Iu{bn`r&^icSl+46OvZAa`| zX3QnoeBS>Q-kgMn^<7{$@<(p&)sUnWxwMc_B;OGZ=N@>-{E@OZcMc0&vIMuO}6#PFk7xH z{LHv_;8VkqCz!oQcG~YV*4lsQZ{~TijaTb56Rrd2n)}zcH2Pten)lJo#%GnyM@wye zG{vS{8(ZHlw*7}4w!YYD+dU&~_=nkgai=Y38rX8Zr)@{9w)Ix5?H}#4^?kg}cg<}3 zpjk^Z{?xL8DF;^*4!!qWt8Ya9lbmL=jYlKf9?3f1r0YWK&r%z{bXz}19Ao0W&Azv+ zRFj`t*m_{zOD27?Z2Ati<#o(+#@)@fd~RXeQyrc*?V`=L{C)j^u|Io{;TBuYXW4%D zV%rXlUt#^R?U9}~f3LFX)qw9*@taTigowqr&*tlH)HAx@$bJV0=zrEnzrv<>Gh6QV z{Mf|vPTL=7^qBE)3UYbn+j+MBm{0lOd>4G5Be_4=rsw*`#@uq-zi%pCZM#^a=BPzO8?*=wRH5x8=dw&PJbWdF&O&-JRCndfN}IhkI&=*E`vS zYkr2Y7h%7L8zW49m}kS&%(j0bY&#&=)(iC>Htt7!ZR{U=o~13n8`yGq@_i=UE8O&0 zZuG03G+fAgRyswjGHmml;YM37POJr!Z|O9NZ~E+1v$+j)eszth$)J#9NJ$+q7+)HV4dG0nug(N1H2q%CJ>lHcY3 z+Bm~>oBmhWeEPaAN88wZlDOZPJAgb+_C{_qeBH{y*7r8g@3iUnl1=yiHvKlAV4k-i zAIttI>t3R*Z?0cy?k`?p7(x2$J6~bjRXf%h`|-A48gIYLmu&xet(6Zfw(`U&wj9f{ z-@`<|b>Fr#=JVYs9FN)Zc)2ad`rly8by#KMH_GOtMvQ~Xe3tDOOtk5^!-)FHcXA*7uBEqKEO#2>_to(Uxp?RM@Z8H5&;#@>xb87{W%t8O-3HuF8n{cIb*o|ZdpdtmmR=6MI|Pnqj! z<*Y4_tmE{5>e=*MO1_=T^Ln^P7WZ|t{`^1o-af3(yUO3)q(n&@G-%LJg99zJL4$=B z6f{_9p#=*T6cjYH(1L=67Fuw^2oAJR!9s-^EGQ@_Xi(yS5)Cs!Ne9ND#0d%t4k%dY zBn~JzL_rhh+)vhLdvbExnYqqi=Z{~n>+ag$_xJT(&$D+}#ad5)&1;C(B|OgN$wx9@ z&&QGfX7M2fCfjvR>ZWXbE84%l%D0PtIC}ns?BOZd{pI_tztQer%Jr$7&uc9`@|V`1ZtP&E z-ITo+UrK%_T&(w_9=jQds^)+;VZ%Er@x*jJm|Mm&rYp#x%6I(?%_e% zUH;myXR*Vb@)}E%h$Y5$gU{XIWLsnD%AS= zf7|l6DqJOdv{d_SM)BKvj>*>ex83K^ZC6YGw7$;jTf1!iOOxKr=lf`9T={yRQmW_q zT-gOxn$P3dInI2AtKTEs2Tr-OI#;K4?@dTwWl4`k=32gRw_eh?lB7>sbk8^Gxo<}C zO`50TpICp-VvIQLHOS7%Qa>)}o=(ZOb~dz65_CV$#aet!jL8dn|T z>7o5Hqy6Edd7ja}ThP53y=3DG)_mn^98=7PvtAV&R^P;;NgwHLf9=yI*$th{uA>)x zZ?bs~*FN8neLM33iyzm%@W{9N5;RY(ieHi*n~}Y9TzYKoJ63)~_KyFEg%f1&Oz6BV zebDM{&^)Z=*!Ws??}XoE;iS`+FZ+J0r%vUk(BBNnm7?>vq34bm*&`d(R{nzY&1pTq zp7?^*GpKVo9$HIYCp31pD{rSUl&VH@X_f5gk zrpMYpQejsu}kuT_V_dXt6Mer)?N zOLo7X&hfDHPnz!IG_9wf<~?E6%8y9z`?5aHd=_gS0@eP^8!X?H^kAIs;R^Jub3QtC z9#0Qh`82&>8`gTAkp1QnV80j0`Zvor^>dRO`W~|VY5V@o`wOraT*e@X9ove5%he}Z}c>^#rr>pmRT{;Ak&<@+M7{MkX1HM(!I^xmad`ffye@S^nb z_OC2|>!?XzoriqwlP2`=mHCrBwW0Plsx00|da6bJZh4dSr%-l7_gxnDmEFFgb6+U^ z)gn7<$=~$12YQ{aN?hJ=v~o=vSA+CI40_1vPePW-T=l12_imQ%$BVMRvRAC0eCd}E zo!=1m{r-B5d&I_5x@!4P$e!|M-ktWkbsj_2?*{d6M0)08k>%Ua`ZVbtY0^E_pz@Jg z*BR__XI%+@*Slid|68L0N^bU&_~vUWqcPn>Z! zsb7WC=cU8e&PD01?6+AsTJsdF_1d1cditc7COmAOJr+$r%zfX=o%ou4znCOFaY1^b zN%I@1=a^lxXW{~^T$S`qdxnLFrPqCZt=$m4Ki}pab@qYBi`LFr?XzI%k!ad??2ifQ z0YB_KXWZSn)?c6dtp3Xycbejx^xU5LQ7OC~QKuyRZDCNIuex$u{i9*BDi>P?xl^jBOql$a2{0w^3nb&UV z$0?n&3GK&moyWRYSv@Y$o);V3 z_qonRsM5=IFRjSV?bi3Zae9v5HDvV-YCe{8Cr_5NpE`=C(ICrjvy zn`y^a{k$l9Az$}+q4rCt>RFN=$QZSHPiP-)uwNW~=J$y0mxLc!`q>-{S72vj8oSzm zY3p-B`>f)k<;z$6gzU{Kz5lN~4_eOl)>E5^>@g7$r%o{xQHCk{Vk z<$d+uCC+UR$!_-ki}h2^As*S#9w+aLG^UX{#lVe@;#w(^0LM`AHF{@ zx%5SobzLUsc%HkWuepC5dG6d1H<)5_ zh56#Bx;inMo%le_L&e`%JxSj(87g};)uS)d=qE{lQ ze_QKYEWO~Xb@bEz-O&5f3CVoPIXy27s=Z>_4J(@OHQ7-qeDC0l!(aB!3E4v{0rvgG zMzp2J=>7djpoJIMug?6AKWhJ8WKHWGLVeEp_W2jPH@mejH@0jZBK3TgqVpa7NvkhK z^HHO7zVi>3e*`_@?3aWUo1crZmcLMTL$ubbN&0C@`@e>B>x?TUNB7J{v&)*otX@Cq z(+25-I-Rd_ox2g;wr(>y?MR$HfFo#P6|>x_Ft=PTxZ%NM78-21GB zHx^8f?wqr5Fze-%%hve{(K)aAzQrfK-sW>y{qO#c#ZO5;$7mnd+-327x)(BDZQ--B z8^XV9{Tr6Onjx8`ed&F@w%Q`_)_Z zWYt@}X&P5A`^p*b0_CcZ4M- z9eTHgtF-_8bkE0Wzt71&xS)NzYuoC($o=KibM}PEZLRA9{c_@CMoi{wA5>|bB7b7> zHToWETkm(XWY@RLF5fA=fBd)BUMc6(8Rr^ywNrnb^yb8{{X2jY|7!7pl1;jAkGt;y zoo{dLg9+)|HAxTMpXEwFtNS-W`(RjlKV0@#i1yF;SG2#GKc{~Q*n^JV3D)yU=u?(n z>TBchk)DYDu)>;`BG~~Bf+|7JoNss`!!bI@S9Cuc){d^ z&SjtbTyj4+?X1YoPLba9)j6w^U6lP#R=!T>r$Wycemc)9S(aWd{pO?d6RG{s>1*xy zN>4Am*20lm&lSe8n|^HYzbpNby>MFhREVE_-%+gRYk$4ph?D*4Ut#mwtM{tWC#^rZ z_nO`wyxYPn><`BtE|i|GQ$L%u4jVe3JN3OtmCoVZZC1WPdTU$v*PPx56zd+^b;|Pj z{m$fB^o4Ve4YFQ&$ZS0y4C}l{YF_dgwX;ulC0MzgwKkrHCrw_~{ZSlf^OL3Lgl+DF zcFJ|Ce}kl7sYmu>s9W#VSUur;t$g8|b$-n}4+9{Wv6gg<|oqF%ru5(i-yRL=&skD+Gv+&a<-)acb{`*4;H-^`!7!KX?JRz;mnU0 z`C6rK(ligJv5P&3zmRS7e4LOI_WKWwTXxXd*I77i#-z9Iqd`4SdbsaL-LG}IR?nQ~ zXF_(6kDen}m>;K|Cg~xc=PkUZdJ?4f&T9Xh*7@j_Y>4(7p>(AW7Hb1Suu<-;+kNId_hPfx4c|D=uN9=UJ=aJpku&QzD zeZ>XYJ^6Q8y@Rr&<2aYEqMnrt7T=(Iv`_a?jr9MF>d%#3H}P7_@2z|l58887jm|-a z!lxyphb;fL?#n{GkGmLPP*WUYV#Aly; zT~2I$$ey%}>vs6%_SC-cX|Fq7JND$pPTtkzQ4;pq*Y$om;@|Imb7Iu(dlPrJ?5?}( zJqZENJh}6Q2li$TUH8Hb@gDD=e5m@>9|_s_q1Sc4>KCy$pQueLt$d~?B&%q&XwQz6 zSNpxSy6gA#pWk&|oNKh$)9bp|_WaA+4#Y-yxDMWM-QKuc+ip!b7|?vvxtrnxcJ&@j zX{~(ZOyf<}UbnpZ`s;43e(FG2!Hb?xJ#kCm_3yu{EAP2IvG@LI*0uZj#;B#cj`=2e z)V`S1<>?Bm_D-W@p?D`>pI0u_lZ&g*@i*ma1k{kL2L|92oPlXrgjMil(_)&s_Co~3LNeq*CDg-F z=z@OW`f{P7T;~AQ>f&{QYZG>JX$L_VBtj1GPq1CJz(1vS@z0@M$6yFXVHTF*5_oa3 z?S%k12#&AHcyRt4&tWKlGN^_|XoDUYfKzY=rePJfz!L?x9}YkS#6ljFKqb_}QRsqx zI0@(AJZ!>l6w^Kk0>^*#At-`cXoewJ2Ip`5Tmmn2++K(R=kGKhh5{&qYG{KVaC{-3 zf-^7;i?9WrDAYZ$9}YkS#6l+IK|LIWei(y!xZ;BtTVyxvgCGclLy!pRPz1Hm498#y zMqw6~VI919&fE(Da1f%v@r9ZShoJzs| z4irHxG(!g*gCQ7&NtlIYxCCC<1OX5Q@sJ7yPzKe|2yJi*&cHM*!YXWm^ViY$!vTnZ zSV)FUD1oEU4<}&^&cQsKhfUxQXu5Vo5QM=YNQ89Afm&#W4mbuwFbb0}3(K$$UfAe+ zApj0S6vRU+9ELJ*{`hPg^uQ@N1JkewtKf;fz8?-i1jIrzWI`U4Kqb_}QRsqxI0<8L z4$i|SIDfEjH|&ET2!lhA2f%AsuqS`8(7{ zpca~82u5KNmSG((ffu&nUI>7L5Cw;!0Lq{m8leq(;1o>5Dr~`i-YFb_2#AGb$b=F& z3SH0-Ct(cc;XG`D%Xw~wAP57;Z*n^1KoQhJ2ONVT7==lgg=JWWz5D4qL_sPPKp9j+ zBecN)oPskj4U4b}p4g##-~dEGEF?oF5gdV9Xoe0r2BR2sv;BnxO-RU>3Y?Vtye24njPn!eJecKkO_HE0+mn?N1+S);UtW~ zIXDlS;QRyb-4Fy}a0n719de)ujzBFmLkA4OC``gEEW;)6I>1;U3gY1~6hIkNLnE|7 z4-CL5n1)4Ig)Q(5VN4JU$&d+oPy&@u4@aR3`r#yu!8w?R^RNm0QDxU|*atxn28SRK z(jffCz|% zWXOa(D1l0F{@(sk=mN)w*g2SoO*j-vzabrRpa_mYGjzZ)7=>9_hIP0EUT>rCa1f#( z6$+pX8lepa;1ryJMOcL`@Vt%v1^eLuL_jPgLnh=w2~h?F_?$*unCSsg59tWf*=eIK@J>&T4;t27=lrlgjraIb+`mx@1*~55TYO+QsFQZ zKp9j+BeX#e48SQk1B7a0$F(SQ`j{C`g3@D1&NfgdR8p)36Auumzs)W-PED z4nPFNLNa7R3Dm<;I0<8L4$i|Sxb9>e5Cmb62fLn;(N8B{|fw7~$JfoWKTRoDX0IK~49AOd0`8SftE#!$}x}O>o`C zy1+gNf-pD)iI5ICPy|Py7CPV<48bT&!YnMqI$Q#;yBQM%z(I(DR5%O;PzKe|2yM^< z18@q?z%(qvDr|vgJnIGf;Q&NHEM!6nR6;!*g)TS=V{i`UVH2D`VzwLhK@fz&AxMOD z$blkgh7LFeLof=Hund=A?>&qQ4nh>fLn<7G0w{xOXoMaZfKxCHtKfMrV}bo}03sk6 zG9eF2pdOAw7xcqP7=v>Vlt|y<2-HF|bigqff>D@+Sy+a3xCDEX=r|O`hF}zC zVHqxg*ZqtE4nheM*wa^U5Uhq(crAK`k^x2ONVT7==lgh5vK^bMCpn|Ihu;p8P-e z|Nn39|2Mc^y5KM&V~ z@s8J+lE(`lE&GV`2yNx|k+#G2;Dd$5B@aG$e|cF+dD)eG?=K_cN8@56NO$_`)RX$^ zEA>24_V~lOWe+~_f7id~O8m#4u*yksUfWpx&UZSD zz85-uaO!PlupX}1tMe4(Km3StUg^SL`{2B1Wh@UqT=2*fWskVZ-mE%GsE7Cu(nNkr z;iHA|<;70agYJIpC-B#C-d*(A6Ztkv)c%M&Pr%iA?#(Yt&V8aR;qk{GdptJG<>Ybt zdhS(M+Hp5=r9G#u*xRH2I;TO`)N^#{^Pr`0<-7a+J?l54Vr#}1KA!(@*<+7aSYO;_ zW7!PO+?KJ4@U8i{d;jngmMe2x_-IOQ;iIwPJW4+1PIG*bQs&BxQu7_RYp$Fz_EYY! zbADf8QPGv%8S6X^{2za?2hrNMZVNKgxh3T<(0% zca48-BmME*N1rHST9lD|?z!u_Iv=y}*Qywevxl5MIScdGy53)upI>ri@5Q<2$9XU6 z%nt{k@L{%>d++50IPv_J_va>~@6c}y^johw{nn=4)6$FkEWNnZP*7go+Y)nrqt(l6R~``!;(eEv?0UwX#Ekzcd$g-Xkp@{)z?wk({X-|CKh$l|-- zYUMKeOtzji>5*ya8~UyA(oZbDN&hxxP2*T7w)mZ$mOq9cd#v3PJ*scb z!g223fc(AXAHLJlo2r!W8Oyg4Yw1~UwfLPphdcGR)S4W*&Sct#$&Ck0R*=cb*H>%h z7CvTj@F|lCC^;v6_oU`yms7b$zmVfS6lOxL(-|#t;fp0R|r+&?8-Y!38?KP>L3e9iL9Ts0Y zZ1r`z*H3>dc;yC5k9n2-Edp=tgYFQ^pY&bzBiq_-@3-*EXRKV+%@$sJ-qQV(Ej~f* zUjCw`hwI-;uFYGxM(eclNz3p3gq2_Vw59tq>(0FQg(Wu^m`^dRqycy)%!Jz&*0g~spn#_$pzlEIN@#m4V1vc7OrWxb}qWlbC89nPFOwV zTk21&h4XK>aw}aH-_UI`Fx28N|IDOExaFVty~&jg%NMToYtg6hF;S^jPP7QHvX!Y!Y)@}-}&aIb!o-ko9P=d{n4q}Nw6EdS+inheZTKeP^k z=@#Fv`Spmkuy2gzi#%lY_O4iXuG`Yfb?#GIEj@R?r3Y%CY-|4gbpKSj_s`RoFQdt1 zmFBnRQx^948a&+xQzbB8Sb?2jzmqWzSn`J4Ny z<)8YrrRVlo{+4A6`&_bc+GkAmYP_XdPoICbeB(_f2R~!=%@kUA_zx!MRBx;70q-wZ zeg3~P$*ZR;7~=pa@7|nJwM)S@mcp+z8H-^Tl&HO=T=YJPb^=G_EU`Pq8J`DoO+}6 zZ|CyYEnm}*t=zWm@8b$*Yu`-VVdWOyVe<5Etz5#U$&vdMKX2h(|7G=E*1GzvSbVqi zQ>p$9M(>1`8=SHHBf5WEr1$-$ZyU64^J^^s#xs_`F45|3lKxK8eU>IYvi*;$|0|Zi z&)4!N==|)I{n9>e@e?}#K7Ceh+uzEq$-YR_`qSMj^H?MO;`eqdzo7G+rhVtvp>}_3 z`FCkPazAh3xIb8W%&Sb6YM#5)NAWi9ecb|5s}-L3Z$h!r8JDoc{F9{@?y~wi)n1_X zPvim1-@9V@vO`RUzsln4f~;Jx`X48IwvQj6I{mEBe3xpyvkzJM5$VYaJ!h@xJ{o-7 z(!Dj_<9eQ{(YZ>{{z=k(obV-Uze09oukMH8XVkwjt1qp@@@3aqxaKVuzNmf`Yh72q zY2{ADn`}R0`SL$!VQkQZ5HpLeH{5ki@*E>i}%&}8Pxn@ zc3zpU(!aO#6|Gz0y2WpQ$jXOn-_^a@@+Ih8)=B@I)$`<_>|r0}U-*@k_g6o{A5nd3 zcSh&7PVMF1X7z7;*z);(!oq!Tv~ao3^KgfSCt9qZ$G>X%LS;YHsow2JRiEysD%qLa zve#K&I}9@ajtE3ovXZ&-TtudV#9V2f|pynE>09r3jE!hf;! z3)0UGvR4!GEWJv0%>}JXinqm|(7jQ()AZhy&hJ8^+MPC8oMH9n>)tB8)%usKd24;l z!c`5HFH-Yes{R!J%<5UwdLGwvSHtft|Dc|8J>GBi#OVH839|Hd>6^3CgU3H(_2pMs z{$PbK$j(SOZ{>pZUc+PD^3CYpoclW~-+jI1n`*Q4ogNl{{M{BGuIKk9*=03RmR`EY z(v!YzvZB_?SLpeyrb*#AlXX9|d^=@_Uw**q=`6GO(0Y>-C6<5YP77bqK5yYWDo5|s z==^5uo~V=EkfD7yB|W`;x7EM%J60~?J62za?wiw^*H*3LxX$m+w_5%N%|oG{qfhAH z1cgdJ1?E}4G_7-Zg4N&sNs}IW4-oh{&DSqXW=W4F=-#+IV)bNy)?~P!m9Lnv{Jzp# z!!wrNtLK72ozv_WEq|fB zYgp;ovLBDjuBel}AF2H`sOQ$R4_kSE>F0z_3kUwi`cb8O`1G?D4*jCZg+rEaTj#7( z&yha6Ek6Ia#uIJ%SM*$-^038^=swAiJ_`S?#rNrX$4~cApZd|Hzv0pHnAO+xDwBn> zBU>7+eD6opzU<;`?ekvsw?XlB>UaC1`lJ2Pt^MKioYj-`Jxg!Z`-QYgi|^jH_QQ2< zy3bg?U0=5NxNlo{{C6g^zGZT_*vk8T(!v`G$NkabeLXFHTlUAsJ1o9;%Hor*GkI3e zQTd6M9`YKK6B@7gQ43epYM$P0GDYV$So+|s)^kmIqFi>T@BLPO_-QLw{$&fdf7)bR zj`gGTKP~<8w=Dj&=6^}&e?fNOub_Nb@l&=mCv8F`uu-o=~H@c32w3Y zcGcGsZutY76|ei;L-viw4ohFs`gCf2lXQ-9_1t#(>y|I3&Sbmp0pEVBcldsjktrq% ze`@_o)A2)@>8F+dc9>2 zZ6{iMoc4Kik>#&@#ALgk3yMEx`OZFI@o~C82lakqT=&Da&RyTnEdTbOEq}&mES#_L z<-6Ba_kO}3E#KPf%nsPr^F_J){*xV&^k?gTljiH}kFDP5D2q?hxkzcV`YNQ~tF#_j zOBP>OY_jSHR_^RWCQE;A@u#)Fa&=$LsGq&M|Ht(_e0fvjeAvph=$u}Z9n&ejy!KP8 zKm41Pzf^X7`5SD!;qg|!BF*wmyxnA!!KF6jH7(}%5G&3CQbMa6gPyftZE26@16?(4I% zivyJ&uJ_;J@m5cr<{|jY7GCMKdXF!ftkb^ijI;VmHIFgU!)qEx_w|-OPODD4g|}FJ z7bTNq=N(r#_(sdWp!2<;a%Z!xT<ExJ>kyM_^^U0DYsuEG_n)jDpFdgHSLY!^ z=X+ewZQGjX>{_d5O?F#@^iji$R!>~4)f4l#mM{9U$@XxI&yZbwQTw#FO!ITjfm;$E_n=v0~vq zos$diweZYsmOife^ZufRTV$^ecUgU*($C$}15+#Nr}ja)?#YyBE5D(24%K}yrTXSh zTKa;%2Tb~g)l+`P`ctak=-!d zVD;^cvUvaBnj9>$@<|s=#z}u))_KoX{#Mx$!&=8|y_Y$ybGxDU7{_C*-ld04#^{`H zNI%EiW9;@p=IXpw z{L=b)@j8{0ewmP+82&!1XJxrPXO^XBX+5L0&lYt4qW{_Qd3#y8B(>L~ z{W9@{l}~xMrJq*6rZhk8df!m|eye9r`)=hrD|cGzhtsg`_@nTF+qRBFml1_o74H5 z`;vvzbnYjluf27THf36US^utbAGP*c*DO9p`nyT@LvN?WZ^#br)BX&9(#j9sWpY91 z&O>^vPW{}_`W~;adUF5H@<;bbe$V8}g!QBFOO`Jq-{OzpfJ0W}GxaOf#=QDUt{Z25M zq;s0|Nvmh1%i`;#S1)V5ecCL3V#K7k^jn(j=RVm{XHQzcM_z5Rw9e#Ovq?YcBk!G7 zPs(Sl-OKN>aQ=@iUz*+@1?ry7J#FccdJZe~vT|#Y7M~=$E?0UX?iUu{^t9zm%d&d8 z{;$|q-m1q(`X~ITzfz?qNKbX&Wa(+L zv;2Q!;nTO79M-wb`h$go-Oqjb*1y3ITKvT&lQFtqhc%u$y_ebehNXLJK0@_8l(Nt2 z3DI-4kKRv523dTk?4(lZ%jhAi=eW+t@S`@)5ZP%fx=%80w)o*YEdOQg3qRSnG16n@ zq1KMa`_#WA3;XH4N}SG3xXxjm^i9^?mcORJ@`r9&dxiH~e2Vs0>sbr?++ecs+a~=# zYx#4(U~)X%>X{0&aCoZ8vtyP&_m3vCW=+QE{@VV&*O&}{y_NI%Hw&-n`}b|#v(eJ)S*zC0hMr5# zYMul2Tsfn27x-hV=c2}2_-P9_={ei?YZl(lw)(QaVlrgI;_DKuKiTg6s`*&ZeOjvZ zT9F-@`=6G-A;VTU5hnHFWTT>9j~M=hN4Hp}1ow0V9JJZJ0t1%BA_ zhf8kOFnE<;(h^g=fBIVZV=?Y|uR!s{RBQS$PlHZKc{j7yfAFcTHNk z4cQ~X-?ebA?1(AZP0^ayZSBt!O24e<^TE$sec83vp7*l)E&C)?c6EaE>E#&9KX<3e zHSMeHeb)X$v&HvmzLRv1`s;nePU(XfJ-2ke)ADbZnDp0v9Z@-7J=b|i?_Sn>`Zd|v zzIxBPt^1xB^V3po^?9e8Y?VF@ z@3nlb+P~e>hfUJsJCiK`w&Hhv-tzls9iuhQsw~TQLUyvxuPtAp^x=r^gABb78Iheg zqUW!c9vjD!?81D_PjQyjv;7H6PkO|{e$w9;JT2@kJKFo}mT&5<7QdkPPT`7gDYW>x zW|O<-t=|oQF&U|Q)K7L`x9Hmt)So%)A&+=D#mCj|K z?$ekQD>w5g%U@h+?Ih^?(hdEM+Va~iJx+G&h}L`ikmVng9d$wXOyA9xKRDIW(;l#P ze7qH}@M+!0DSFO5>uKjYrOEPTJY_Oc^H}vKtFPOAUUV<+)b|`|(qHAW!*)tx9ShqJvR6EmVZL;1GZ%cw(hd{ifwCu`yVVkC3`4x#p=5#*{A!w^*)PFk=~nY zwem}PeofPTJ93ld>(sqHcdvynYv1?Do?p^)(FyGzpI=)){Iy?d+N@knrL~jr0ShPS z{s`8*jmw^DDzx$=b(XIx*2;P4{ma?&mVQC^ioc$ZF5YeN?V9IO>A56*Uo;qD^~6X& zE+ts~-g+NhsB;~neLVa*OZV@watqqm9E0gH{Z_nW{R)+yY8td~beYK%-4khVwt8c|c+W#4E zvi54U4_9_uctQ7MwCt>z?^`=@=S|KOTfP+C>skM4^>6&p()$KXmg|1^n6mK2FIv8} zRx3ZQ`_)%^KTvl3gq}xx^DN(qFI%}jeGj?uWs48L&gwm__zaCN-`CR1gG^RvKP;)9 zkms%3E}g^E(#zo!)=r$B0}A#1$%O9hG<|=Xl3?Y}c3S=>>4Cxyi!atb%F^>>xc2?p zLzW&c`zTU&s86o?lV$ZSY2GJvuCsJMjHg+?q;MNo!wVMf{;GwSbp9LkoRoi~)t9gE zg>#m_c-G>l>aASp4wJ#3Hkp;8a;kSi_Ta+rtbD89Q_S6G;iZ?<{^zY++*d4qSDosA z+QNnId-E2{m-2TOzwi$xyLIlev`-RlwDQLbEr0H`$po!eLzAVSK4S5^c9`_n`<75W zKg>wa1nRv-!p|*V(!ZK4{({QwHW{LO{&b&(&#GLK=6hTFc~JVPP~~>%`Pf7EyNC93 zzVun=kF3A>{w7;wm)7aLZ%AJks-1;9ZC-*8SU*l{{3|zExN6eU<33{H{7+hV{27yV zs}|q)CkyAdS-aheZohJs7W@%QAC#Tgr~7HUWZ*7IA_9WRHZmTK>}Kt^Dz53&%+>3_oY_ z{-3h=?(bQ>b9(=OqQJrn-!?2lw<9aI}to=12yP`?Y8~&k|UZeZkdykdt ze7ngw40M*%)u(lt(!4dyTYSxnmM=r^E4S~l@UZNGgnp}^*Em<|nOd{-Q0XV%21{>W zHrXP*-=ck4{%6bICp)84c5!gK)ffDAlPP6Ze(nbrKK^l&tv+)ARCKovYqH zmD9RMOOL0?p5Kt&G_3gueV^5vpJ)9Tkv`a#U9waC8CSn^WoNa2+VUsGTls}hlgIzX z+COo?!iCZ|Gcy*x_^g%l&^_K4YvDK#jYs9u;#5A$(l^xqR+X>P^G~?;QIguve$w(! z$et?Hc@F$1tB=>{SI*OA-3tq{OUiY=mwsmHF=f^tk7$z@pRjt@<}7{U0h7ME=P&9# z#^orb4_LWCl~2)m^mFUW`z*av_r__hYl`fe4B2^0O;%ru=4ryu$~EZT$@;SOD_Y}i z(D?}bqvfme*Zlp~>IuAA7JX@JhtC$`MrN{<-`AE`GU1? z;-t4*w6C^*Z289%Eq}i5f$`fdJg$9nqQ&x^mA=c>dJYd-z76f`LG>?O`e4^Dto=0I zzf}h;f8T8uAO3EWRsUh}q4$~$j~z4b;!{7wKLzf zdd6o==2lz&ZjCRx&+<3v{%ZM0%UAdblPB~(*I(y2L;AH-_hp5iJI1AVs z@2CDAFR^-;v_BRevi?lz{cm@d^=D1?LW-VG>NMXcZnpe|t0r4?52fk(Wgzi?2l~eohq&Gg6@?GJ+H;| zSiKeXR&MQP3x{idYGl9f>a+Ag%~Q7arJwBD{AKH}-;OsIY@FWRoO|8Me8cX5>mFR{ z398R5o>)B-9`KV--aLHkkKc6tV;{fG@9lXp5g)rP^hn{3<)nPiy6Z1~=ttqneY zg@fM;=z7{??8dlf7T;g>$FD~|8(q8IlJLue@B8r1cQxG>t zW@>VSC-*-3qn_t}o%Pao?AyG4`>%-~_{iQD?tT7&pxa+_amXv@$+tZ4n(!Un@0pqM z_{yn2u0EX7*YWVF>c!pRPv4OB`?~`sj()~t;@pqFTyXBCuA?`7`V$Y__Qp>v-CygZ-A$i*?^_2v zZhhf3fr%gf=GR~F{^<0dU*$7d@O<2@@z>v~Fv4p;NivIGgaAUw?4t^>^L#wZ@E( zefz+T(WjG>zI9-$WzXk(U#i>xcQemkml8Pf`iBPRz2k>&-0`!2ZX2DC|K_)A?|Jdl zZ}`$fU%7tos|LDbANbHOzq#o5)9(&;#s1~q&%Ld6=bpE{yCvYJw|zS2yVo`Ne7@_w zi_x=>wtw?TeCkIdKKS*6_t#H6@bUV)G8WIBz3J)y{Cxk5EkRF}ee^BgnRw>5)biZm z>WJGXQ-74O{H1T@-LfOL*}MADyViIALr!ac|O`Fxy?DFbT7;4C`~_*p?6 z|G)gJtP-~csPw=3SK)E_if@%nB&L{)>*9)!m7~ahI0<8L4(8!JYyz+OuJ~8MmDK;k z$4WZk94LbSjjxq;@?Gm|#qDS1S|2Mf=U-*flQDtgUu6$CK2{ck+>Xie61V-x4)HRd>(~Kn1yS7uLQiBwE^evp*g-+%AgS( zzbgaa_*|KWRdD-Sx!TW4vKM`a5~u{n-%1y_{jHqiv*UB+Jh*nzcX%bAD@RDX+UJVn zZ)Fl3Un`fu?QiAa_54vb_@8{NoFb3g&&nd79bYS3;CTaU0FJ+v0}uhpaJA1B$KT3P z=z^E?yKtCgZxBG_Nj7-u-mW7 z%lT9(cs=6)$FE8wxP7Y(@Y(UN;`Xhw#pgYH*+X!(UzHNVFXvC?YF{cYU(Nx%oDY>E z;*WsiN2LS)H@;N%zJav>$Dc|(q=MV0N;RJye=0q23eLd){HO#$7#sq(FBQj+isM721FrU=GE3O;qjCve&WB1W2Kd!}RIc`+GC-Q+ zN97DSzEl>$@ulK142e5%BQ<5%S{6hJlnPkvRl$aA%C6}Mj%w@;O;{i$5- zOXa*jItln=epmddgdyF2RC4%ywI3C?50xR}N5S!k0jtb*Ie%KiY(2gE`$=-z%rU z@w>7JFXwwD;ugjM$>8?Ca<%Uj$LGpCxP7kdyOln}EBRZw*4N6_epc2g=k~R-_W)-C z-2PTl`Rw>yx!T`~+t11w;vHWrZXYXqLYRMu0LRyg<7dU~W92BH|2zLG{9)27zE%D^ zzbZ$_^Gd!|W=UIySMssq_*Y2<$H$7}U!@w_;A$T$(}dkVR<`);_*vNl*ZNs;e5_pU zU!|Y$7&v}aT%qV!2!b$h`&qf#$4V{nSNmBRB0LGpaIK#e$Hz)MIDS?RgWK0iHJ`8c zx8nF(nFhz-irdG^EBRM(e5)J<$G=KHTWgT?tvy2hAC>qR_6Zz@YyGLT5$E_+IR$RND$d_jzt*41 z%lT62dN=(Bw?CD6KD&LX?7oxp3XU(8L-2CGRIc`;@^U^@ykfb(;2=anJfuPaR6`r| zzyO?rX;_3+*n&NA>~%N*5s(RaPy+RE6uO`vPQn!y!n7bjSh6M~dSc<>maMypm6p)Fk!{T+j@13iA(F`#W*_IvFGWmHeE9y_bH2Ws%RG z?`QwR0dV_9$>X!zH%dL9-TqPf`Rw+Q@^b!Bg3{?XI6hMTC*LT`?F*pyK;QAo*4*MVo z(jfO&!X(VXGOUBwhv`2Az(I(IR5%O; zPzH_A0|RghreP6QVGH&gX1?J7L_jPgLnh=w2~f%Asvd~ z2sA?n48bTY!#a39K;Iz%q97hp;V=|H88kv048SQk1JkewtFQ&04>AVW4+kIuVj&sw zpc3k#3;JOU=HWbSf-8qHKoEq%AxMOD$blj_0=3W#9dHbWU=$`{7M5WhF2UYh#svo< z9u7kpR6`r|z$rKb)369z;Q0_^f&Fj*A|Mu$ArtbT1S+8(jzSllgfTb=^RNl~9%ej{ z4o9FCnxO+GVHTEQ9rotYUxu2*ThHBtkmm zKoJ~)TIhfwn1oqahIP0Edq2iF;2=anJfuPaltDE#LL2nJ0GxqU@O*@^z zkO_HE0+nzSx}YD%;2g}uChRL>91sSFAQ94`2#!E4bifdd!XzxiB?u^{-w+R}a2N`p z5!#>!2H+G-!zyfn=cDu+A|M$uArC5{9*#m6^gExKa(>d`n%=>DdD73F)XnF02qU~m zTEIc(;7;^Q)B#3G@D^h%`TPjMMJkBmhIIdR~EusGuRxc)=RO_Cov zV3Ro4kLe2xz$8S_ZVq(udzb1R%;_BC1Fv6DC&WSyR6-jJ!8urkQ{)f3j{Wc~{pUG~h?aGhKCaGi&PPcya+57#lu?*6@pEA|iMC(lXbJh=Yo;R=9QIL7ai68Vi~ z{Uz!E$3I>kzm=+oemDo4aM-oO<+Wpn>(aM8_+G%nHOBAF%3i(0brep*EL?&BelL{> zCD6rhsm{PU6ztyNO7`C2DuQ|FV$7M(u@~Ry;o6;wq}?%oGqVXnk26l#I<&)em@jGz z_$@>&q_5E?bdcXO*26WxZ&QZ&eC`{R`L>6v`ILvNWSBV~VeY@mJVHI;z29Yy|B?Ok zPpsM3+4oO*xV+AB*1!-L?Z?h0Fso z_9E+g4}FC_e`0TLb4HMk&RtAo-pE%5%`gB#_j$PDp#U1;;QKsWMH$RDjD3Lde8|If z+pq(6;21k!?BFkvE5stwsza!Zlw8IqvhoKgZK_kBt z8GtC(aQ|IJo2kA#F<2|^8w0PD7j`F$jgF9UD{EoSdJj?&aIiZfd3(WZo z)b%2;w}#f((~v{H1DhVM)ol+~2kjOlGgm1)T$T5;UsHkKscmr{reP1ii3x*rsDvIk z1Lxrozq5_!EaX86zj-bB9{qr*)0{PM(e|Ex57+uPIB)l`&-fkLq5s_B%7hZ=7^FYU zS@OG?ANF)5biheSWKW-*VNHL=yu%nQ!X?=MbM_+~h9htk24J?Cysbz`|9A9!3~T=@ z!kneX73Tg$bon%Op+64(hPD1FXT(v4$O7c%N$wlgJ(bU~3)Bxspc(q%3~YUwdeJQr zzhhp$%U)zm@o==1I%%(s-_8xeB%B8qza`oa5s(T6Pzzmf3Z`Kly!ef85FCO`D1k=k zfl-)+b=b|{nmGXRP|F$jI>p)jGwTI=F70rQ#xYNDzLN7-OaCGL!}JTzz!n@l%)CGj zXLuU91+m#XT&Li`13O&(FbS6+{(cWv-ahg}TRfjR*NeznWLScSs|Q(&jIAOK_9il? z$N@M<{GMFS6?V~K==vz<0xmtqzAa%6AsGr_s{}p4ye22G9-Y`G9mK;WbkXwrIcvAj ze>e|&Zr$M;;*5tmpWz&zQ<0VE`B~1@C1fM}Dn1W=yhz^}$EFi5;2e~Dxb|_ba=^8N z&v)!_ML{~0K{E`%B&>p01akuMkO$S!1*c#hj)gN%T%KEe-pB8qBOn#3uSZw(au%VB z&s)DlXE2sampO0$MW3Jp2FBR`pX03Y+sRe%- zA?PLUTPT4x7=u;kGv((WV!yLTqhKtFyEGj=dmr-zTaXMcbZR2x@wp3}cS?@0-1G*< z^bBPan0NjyS_#xb2Mj^-Bb))qtV{|u~%RppJSm2Qh&9>wRm=iE8xe> zDFpm0>jiBv4_^ErECTYN8BW63Pe}VIY15=Z4m86kY{LE-&IYvojJ&W8`+v^)gQ8jD zVGK55{~T-l3)UWHVb8x2zl2`;0&{^b+5biExGu)kjc&n)>Ds2fKe3;n4Gs=5H(Q*0 zXoN%m&OT<`2NLN6^3o5`F%$IRhsYl>UdWq7U!a$A-a@+{LEjzXt|NZ{`}tTUeFLv5 z@<2K?!XoVcBz=W`hrg#E)s%->*jK~4!7S{15*wG!XBXA0~Agq=7g?&YYra zpc;B$5MH`R@N8uza!`^P< zAqVPV0G7e)%ghO6LNyG*B6xmaQJKND;R)9 zhhvoMqkSlX>aR2ZFb{ja!8rQan=lVy=$^eWhW?3Q&-JtC!q7E6FbS?u`jn5Y%H5d= z9e2PxDHF~)xE-B~42b03h~O>=!#;Q$w)z2Vo?Ez2kZpvc-p+k<8+pL_o^62q=c%Ir zxe7;br9SeeB4?ujc{qf;(d2szHuzgPPqcgN5O)(aQlIk!R_8m$9_pJ6rXTO%98#v4 zemLJv&c>i;=vx~U&==?T7PZ6$gz{`dKl_ObdpFfk_8j#$(@xt#+M|w4`nCt+37;Z= z893kPZqbL`&`7(^_oJSSEg2%H*ZHnFhrU!Z#(ByeppAXh^?$MVK5%wjRlWFWo3>Lr zX(uI=w4`+?LkT6oBuzrVz?^%}{r~qcb0)Jj2dvjh*6_&(5R7Dam0$n7c^qfh((9@3I;9D_g!o6wa-3xZrW)r{^;*xK3Vt9 z-D~Z&_g-u5wb%Z0_BjCl^JvF9lyMk!I0?G*sDC%gzYlGoeYrz`O(=hU1>&j*+SUg? zz`3AVfpX@MbOfq64Ojs)?FHNiIE^syB=VjE&pGhUp^OVC>ln)1in2?n!&9isanxxR zb)$Xoov71<|+CMHK{V4$L zl|Bl3+CP05-(A3+1Z^*9>-c^OGI|o<51}48)M*9E+=VvIpxm=)TNmm<`_Jp~_kEzH zz11gCZ`wbeN1i#<;}qKNpnXl$?G%8{wX6Zn3FJ*dCvZ@0#woP9 zlggs@0jl_Y4)lisW8k|MK>Pf2D9c9~J!o4Ukb>N2u0|T56L1mwI0C2xa`ZRkT?eE9 z7$Thx=<1~Y~oq=9Hk9z(A=)VN{0LIE_58x8O z-35BU+(F3X1JK!zqdxD)Z+wqjz?cO%4Y&a4`YP5sFtUW|G&|8z^?xTUYHlI z{U-2$`d8p*@ckrU=eL0WXUri1T{s}V6L0|VC}1__k@o?P0apAA=J0^#zk<(Gmc!-3@3zVC#+G z(FJ~h!)e4+fZUa+%l|@M0H**Q=RgOT2W>hU?q?f3AFzb^pRV1Akc%m7XRR{s^` z0XT6UI=L5Z2Al@WZbzI2IEXgy%p*Q4Af5r7DkA3e5x2Y*aS>qWm6)dh=6)Mx{14d3 zJY);F@3Sc9chH94MH>K*o`G%rKFR?+^aqeHpm`Sl_Yct~)G-Bk6mWJ6)`tEk{sweB z3VQ&o{Ufv&F!Fh{>5oCb0DlL_{Rwbi01cq~PoaCjsmEY{e+In(4u28ffHi*(y9LxA z2kuL#AN+dkBN&74g8%sje9`}gUjiKZKZxy~M4Sy6c?;?aaQa|FfUPe2`K?F?90PQ} z4E_nQZY^R#z+u2+e}-}Aix~SpjIk6jdl+NlPhfm}BYfAJ01?O(aPIpN^8pS5#@>v0 zA29Y7@ck-cGQf!_(g@%uPCtP@^L4}!=Mb9#{C`4S0lkZeF#-2|3vuPY!p8zG{xjwj zQMB~{>hL2ddjfqbhWg|C6kuBuvISJ9A(MTOKVaK_$Q;l!1Ni|Keh}lwLy+-1Am5!B z2LY!6w12$%9{3eN74Qh)EZ`EL=U&tc@DPCZeIEm?_%HA)fSrI@z;VC=fc9>??}Oh0 z&>rm(z&zk0pz8;Dz{&f;dkW(-U|R!a z0=70$#x!CIz=?gJ-;egrz~4TAG9QHB_(8M_5lw8jsqS8 zV6b)814@900LK6efQ}!74+J~}I0bkD(DPoD4LAZ=0Cc<${SojG;1pmHu<0P`0yqvh z2Uz`n*ahHWz!|_(fb}0hp8^~PoCU1-aqt1m08Rid0M;HtT>%FGrvOg?dOrx>fMbAj zfUXaLH((ZU3h)G==fj9W0EYn!fX>5c2cQl(0+V#{fOT(2A9?_7dJujFuoZpc=sVF4z~k=%ZWd|B zkRQzd_(H%XfPXvsC7}ENVx0ic@o(^_fL#vOiUEr^V!fyfYe=iG4vhOK4$^uc zzE9zf$8M|#E*!x)HjkL|v#`0}MV~kW8~i==f6&yi=GXHX*fZix_blx95796Bu$}klYeg!_S zh;%@&5B~sI|5o%R!1{i)V*vdU&|QKb0MMD63xHiQ@QkA$C-8R?{To1M+t#Fk2OP>^ z+y^vszyaz%hFIzS$PdUN&OWmNesUvx;xYL9lkj8z0KWu(+8cx41oXZNHUyXlTmo!b z2iphC0WJa_zXNT21;#DFalj&A?mT=F;E}&ZedA~wpf`bb0=hqrG2shX55c^J|O>}f{i@_+XI{e#C{s({S0*TvnU5pItsmf46z5G`Ew`>aQfR=SNIo<74Yka z0q#Y#ISG9OQhy5_12&z-cmE8qm+5+L?=ln*!uShE-T00#gIfX*t$AHWFUDBvPs z(**E<1;CmbcmYlTE&*bb7-s?J0Bh^u1vm^i3%CSWe?RB}2LY!6PXM~7u;u|c0GI=u z19UbpHvm)tj{wdBo&>CIqAviNfMb9KfHMugfSrI@z#QNlpkp6&2e=P#5O4}`0kC>M z>IrB9jsX?`n7lZ>fL(xtfO)_oVC@4a5AYD+1mGN?<3Y#(aPl3{(>uWv(D^RN2~Yem_k$;37H|Ua zIKcS;$^{$-%mbbT^!zx=1Iz+W0CpaNt^jj@r#=WiA3_@ej{$NYhE9J1a{LJD2RL~Q z_5bf^|8da#0^0mbXzvM>^K$S6Jc&L%^IrHwfcrl9z9GbhyWxL^@eMdO0)JG&*Z}Am zMPCLS+=IUMTF4V{7SQ<$z~7+lfL(xx0VnlODuXfWv(l z3nK7?NP~-Xb^*HJUV48B`M(Z7`c?Rw55s5R+x>6wU4SFshCg{7JsR>S!AE%;!-L#yGtkmnfu$r->o@+*MTr{E8ZkjdX*{P=B*5r7h4?(gAq zKZdvod~$0bQ&egZaH5XREZ5wpZ#CUNCZ~|}!uq_Y& z4{*K@zQTijY=!Oq73>*0dkXrRhkoXulc%7I1<2lqoG(DmM<7##YR)0Z;W%I(unlrJ z0C?zr_*=C94B$M#c|G#O&W>jhzo33Q0cXAm`vW+TK{w#si_y+S$Qy6~a2jw4aLxfO z;1cj_@!bV^tp}t4?iKL;^!L{wcYL1)Jai@ECDfra4cp0){4h2`HfztrF888d7^Al$ zZ3MpL5y0bsH9rVl0mcAF0A~T00KE^vP655B<{AL~JM~v63p!Z9_od&0tdC=?!FM;x zdMXQkfK!0OfI1)txQ}%B9oXGA;Gx$;{}=K?dyWEjK?V=gH{fsr<09Zp6t)7pn}JLZ zLVu_40H3$~=lb7&uKyvnbpCVw?{e#ZG$*~-qxz}+`Sw+g^KbZf>vgaH#zGqX_BJdt z!jHj~JHLf-i@txNQ+et&(9`u>rG`hGRW zJWBsE#!veG)MoYjn;4_$_jT}>^nDw~1o|HQTgCT*H!1u_uTtN)K+c5!;Lj?$-@}+j zzh4qlzd!pj^?L)x21?)jr;7fk5jWHCFTtnM_c+ER{=Qdz-*QBK|H)qU{r7jNe4iLr z-+zKR6wyC$i^Bi4uII|XP`__`Kz(o4^87Z&7^1%gV=I0CHR1~T{@B}8`lmjoet-2k zg}?31>iZLjKZ)+T(~9mL7l7E8%|u^9}m` z=8viGIL2N2eTA0CtJkXEAG}+AuY8B%cg4%q?@u8fB>G?dXO;d{ZI2&~so(G3slM;W z84IHO`WF=a8!-puafU(qH=lm2XJP1Jhbg|IUx9^bLLL z``epT`Zs*_`yFpp-{1MTN}t}R_zwP^`W;7HNBk@L`%79MAH*0*>2G|O!oL#dx40gd z-_rNj5#P}FpBz;9%~&V8!YP+4GsBg7vphCb8>v>NHaWLCH>^Sg;_SMb@#)%lGhJ&m zhibzWgZtbyjPu4S!+T5P6O}2q*SQ1vkj;4ng%as}409Olatcw<$AOX$~|ehxZnDzr8Zt zbRk|-w?j6++~m-RH$ByuoQmL?R9ET`GY@zXO?r5;Mih?Y-mYa+SM5(s4NX*b)JFWN z$zc~Q+$!~c*ya(djH<>FS%m0Jy${%Q{$!*1*6HzPzS3w6?Ws`3Z*^YDGJPCA0?}Dz z_w=6n)Of8qipmI%_9Cxzc1(>oEBW!-cx-y2?%d?Oit`PfW2g(WjnJy&z;{(OPelHI8 zxS~SQU1`&K#X=$Ol|TyJDEw76zCRvI_iF<1kg`_TG|_kxr4@G+a>Yn27AZvnx@&B@ zcp(<1a-7%cGOxAiLj1g9-Y*v7h0*}E;Vwemt^0lFmhFLrXXjo%*cPbbpx*we`Gr8d1PPZ>+D=qafLWv{VmV{ucqwBZ&TPl7MjcBJ_{D)V=_gVu!PJmjbiX!4 zpM#{lNP%s=P)bKi+^3~(x7oDm!9uox?r7PQ^!?jyI@PnuX&AfyJ{#X3*&(8(R*CI* z+cePS&U<3;X}n@Fn~s;!mC#E9nY|)RllLQDsq7bX zY44sg+)_NfJ=X*R0pAb)n#e>;~BKurd@Yg zS_mrdhuZc!n-;xvAU!}PQP!d`%@cX`+Vt!T$cXxjSn4)5wjo5b#Ky#Vn6_=S=~>zF zoh3gPNf+|zfqbMy1}U<=+otsgqq+1z3ckfSEs@tIn~n@T7r7_wu>zi(ZQ6l>+~CeK z%r=VNnPtUF-`NtPi}$1Jm&G=N8U%89Lx{eRP9);7vi3JNJ@t!gxzB1}-5*buWAQ{} zFvq$gUrzWd91EYy(j(zKo?gc|Khi%CFQa!B3u!NsOW(_VlkyTCD^HA1^rzt+b3Bev zxrD>h0gM}ndxQPy(miDqL-r*26&l}PES6$LZ!oX>hw#2y<82(-3-P{I{C z7UBb5#7FIfrbF_RyJH%m2Lg;Uh^tAGQl76F3j}`N15FlZSF$#EspU*ury@4ttHtfP^5465oUWfRTqpmCf;9I;t{yj+Zu z-+;k*DK)$qnkx(q2M+OEe7g)&TzBGw_)qg8iI#czAvUBB!e14}50Lc}p87H2R~Y;b z4%^^J2TJ{E1cAi@`DdZKI-rY3iyqvrKj`Npg-9~aW=L`-e%BbfD8|C=>00O z!4XhsQ_g40GX7g-br?F*RxZzyiI>R_P{?T7cAcS<9uvjrt9&)pB|o++b*=aY5ZTYB}9#Xi}w;Umk?rlUpqNh=O($ z`(tG-B2>}v?J{`=B8hmJ#_Cd0nU9h2Dnm0^@X7-b42jeZ^d2uuyrle_3|%r_qCQ-P z(PN^Ji*wJ0bD(%jji+L>FE)8fk!Tb?G}>R>L18iYQlB81uQoL7zHCy!f1d-D?7Ts#@UAWuO$4c-*M;-A>&OAURZi0Mrkn&~gY63S}&!#;rc zt}%2w^Enbbc+faSxLXWP>Q{>A5T)DpDQ&#f&?e$>)LZKDGLz0@7COH_$P6&nQ(w9X zWzl_|S2&(m_7GIUyAw}JyOPrK10M7blMiS)T_+0dRZJT(w35Dp(g#Y240AkgNcz>3 zp70ZqffA!6{TimfhYAeQU(5Xc9lDC9jvYE(>PYRrgWCfOCMMB{M;#iSo<^@iu|H2u z;r`kKI>+(gpOWz`440IYkLRNZmD#=t54i;R7~EY6zD1E39*{>sGY~IPrx8CzJRqA8 zUdBRkoa7gY4zdd9lJNp!7nXtGA+rFVE^Nm@j>&A?)D^M|@aoS5CT!ecNWSa1J=-Jw zY=mi#T2DT5*URWHSrRBrPE8C|DTVU926-H(z+`{RjtKfFhr`xez*=9pafL@ttK*HtbR_1I^(;ToG45F(cj zU58>`wT_sxz02(}jmCupnDKmVjL}yaZ9;y?fc(`_M6YFIbM=2?lgC3Q>8NAR>(mpjH zTTTq3Ym(hiI{F9FX-+aQfUu*0*)G?SaOfAn72-P(3nLyT7g@$QH$aYu+Cenv8=xuX zVr^)o4D<`)LqicJ6ba0&N<3yze)I|8O8tYdJ~iD|(`NR|yexw-9Nw7*1d7!u5Bdr6 z#COtcE|)-ClQgkWixA}!LYCqn$JQiAqKEGR{a}GdI9Ny|TI?qRsULh0)en9byrKw% zHSP__i(T(~2K+euDEMk6Qa-}r$w-AWvmRwzgj*pv4A)64K~P;N{c4jAzlK1Eq(a&u z{2RGEsMAP0AIZ?KjjFES5ByEe;M6z`AehggV^9#sx_>ipm_q=UqK+J+K#OdY(yt8C z)Bg5N&K9KiaXJ|^CQqc5bg~SmUBDt2!pDB7lbiz9mE!%mh^NNvo1M;8{Wnu>sqIn! zt+ce**n@Ibu9`*~-5<;8l!$*fA^k>_^Dw86(b_W8K6n$2=|mf;*9%x!)wHWX+s)~v z@n*Hcw09%z0OyxANs`6NRZY+l6X<|erBl47jilS1j#azi5A*3lIyRX1%kcI^ikk}w z>{3zQj#VSTZ>JT^ohpzK+zr6JdL@zl=WbHKJWmDlfdUDly!&5v5Z9` z{7tfjm>g(jfTrP8pLf`0nd$z_oq zcVHSG!K9vOsBFUP>M+e!u)CbUy-2H-R6iW3o>CNe)wu@dqoqj#Ov3TaJ+k ze}L9fcULs>F9jmy7&Q!=XCTln22PES}a+i08}ky?=OYXiAq! z`k`GC{A9a-7-jl)sHxUJc|(KplU`EESQ=9zra#1Wl(}Kq#5-`e@K+riiy5=l;{~!qgd4ZbBI!tw;PO0%n=(jhxgET~a`#Z!VP=g-%0~<#(^f0Z`K8E}> z9QI?lpkPtjqs@%l^E*UiMcVPg_H;j-!}hzCd?*&T@e+xH9qZ^1fG<}khli>x(m+Sr z-pP(W8q$&JT(%i;zHaawRIZnK{}km@Hjdt<>QDV5Mw40nn{0UBP`QfzLe4|u4c0s` zOQUEpuz^UaNG4%qfIu5->H%J}WI3;Zk+cyr^Z!}Y$@F>fBi()qWs(i~mMv3%<9ce} z{>M}=1bBnG4)^0*4>QbzeLYlf`ve;MQs@96Wg4>Ga+$1m%OEbR>x`vciq zzK6#%WkQUna&#ADzy9%%Zmj;pBc}9nRercXsxjp^b=wEhdEXuiXe|7U#(BATq%i20 zlW?bGYS2tB7!8!2eOYK_%BKyH+D^9rD}uLq0rw){&I3nYgXB?1nl>TR-a*x+xPRUY zTJ5V~wSlktNu)#6+*4$4_w*GMJL&yx>@_3eiH%QTr+ISf0c?X$2;KV6DmsV&fhD5o z0k1#pmk_z_uHVmNx7sg1Y4h`{lZ^^E-n|j5oVx5up9;~~yY5b1%A04JcQNyF0nt1H z4D861)y^pSZdKkPT`wkCQaOrO6x|uSOqoXVHZyH8yBo0~zDH$HwI@JdTbelD4e*Q1^AYg--b0Q-M*W%Zn zTI%+Ts&3oIr<&74)wfPpVCow-I(ca$x=FyRl?ow<@Of0#O;aSMDivbmOo^R$+jVN8 z5*}X*@rYE>Vd+doMe4Gt4G%+wx={PCM1B9|Zr1x?O`UuhiSs#_@yQyD0H;jY-d?Be z?Xcaxr^z98OsP7Sstp`~**;XAuJHMh8ENNPn_rl!DLl3r9&j>>2X%pp@R)DmVNo^3 zwo8Tw%bN0UC;s?$7FzOaii+fon!HMS@JFmi%6q0IuTW6kNVZQZ*_J9(6XP`;?xB+} zIMqTzXo{W=g?KEbyjk?5&e{xAE0sEwGDSL}G8|Pu2>59f0ElsL_m1O--_GRYi}FweYa1gvW8iLsi0?oUYNy zCXr=_ZS$x{n93O!z1(2hL!Qo`#8DzxI-kvQg;%bHR}1C1^q=1`yyCTe<5QEhiAoK2 zIyFADyIN7_rN*WIoNsC4WpYeW9Z0uV8s6GhdKd&$qo$yMA>oT7dm;I^rG|eIn;uni z>~B^FYx{BFjG8WbIA_-}MB$`Fejifx>K__!RE)n+@=J;Q+z_87lvR;mRn>)^sb85I zr3G4?v18S0_k-7WsJ=R8^Spcx_M;@frxfqvbhAF)sGQcqfKdYhaObijc`1!l#An)fzB>NEq33bc+x4n-1zRPp{WO` z1*eK#>Y^P8YFO-OSBO^&6=WcM?p(qLVZ){%-n#EHe3;7O<10St2F9B4 zksMFn(AP}dHe>T?rP4YfTiG7s<4ulK{2_IkF*qOjP#azrCF_@XU8CflZmE zBxdpfG7o2?@L1DY2a8I0{C>bgos#Bo*)hIrZ7UCpO4{+opdDHgv~H~I9oGZ|_D*sz z6hS){RDKFCD_EzStf`~d8$>5FHjh>+=!D85+u5yb2NZ*i$`lSo4`E)C^U^VNX0h*G zExeXeI^$yBKV$gOTK~P1wMrUmj2J|fx6IB05gQ7C}i*3r4w$cW# z-PX(GFm2sx+NwsN(gSr&UO66YNL$BR+S)?Nu`ua(V;f#PfpzH0GG%Wgm*vHjW}f&- zBex*#M&~hh-?@a3MCY5>wy8c}3FOus!o0Ok=Ao7mOx%2+RbF9AQ=fB&m!8Q`UxD39 zyJlPXgsFo0Z?9<=KMOE2g5iv2MZ)J)8$J<3<(RoykKt2{=RKd+iiXK&AUod+XWM{K zh<$G{dGnJa)76R!t64BcPk2NpWJj%(R9}*DXA3W~O4_=E4PjaztR)@A-*%l8zYsOg z--+je%kadL6U5WRk&92ulc|ku9y7M7;ue?hic;U$CtS-%BlWUCNitH}JnPBi|T?f6J?4ED>GRTk6_E$?~DL z7fc=TpwAQ{v=Z+}rAJVtj5&|C)Nd)J(<{2))t(YS*6=sTlt~GQYb3&FSW>Dm#V3P z=cHD*@=?AjFZS!V=fhOe{xin+dXp2FB4Z|4!}6JeBR4c#sIdHwG()W9xj<*^!cL?t%pP4u1FfqoQZhLjQF-HC=Iz2jy zK#~Mt)&e_2JeN|69A|VrhnhoGoDzx8j5leeX^Vu()*6>!4mor(|T*S(JQ!Ruro zbDpsIgsE6(_+TwmfQX=*<@O>xg|cW^IAHWRW3~p)f!cda@HfC(_%YgpH+2Q zx~E3_v9y&F>|w1@{IK#%a{^w`?U%B86t2zFv%%bKo1$s+1gbM2I`369_7kx~BA2@Y zT<}D#WiL2vNA1mySF5UmTLa&Clfoee(o#^tl<^VaJSv~gflb4q_oz)Q5}Y?Ss!O`XF}8jBq~bXncu&08?y#=UT7doJiWKET&c0FMMjTXGP*;~9O|-{7EN(nAcO0i zc0~trqV;2P*JJA}vUvGf1iq z&8z8bggz_+oe1A9zIy@o(R`33UzBv$RdzF7t74@yNOyEPr#eCUQO!rOaf)9=y-|#F z@gwBR`ufVATiy_lxXHMWr#f;Ox34D>Y0yWLKBnlSMjylUQKydw=%Y*@OxM@rrRfLL zKS+Nu{Sf^%K_3&jeq(u&g_)Zj(1CFfV$}EYmPFiy4eM7ET zOyJS|qRZW+ZxgrNg`G?ZpSf3v4GrTq2d5}w@cd>~_9!L+Tr!T6iI1NfrTad345-xh zmFd(?WvX18)P1y1#+=i3T{e2Tgda^Le7D4Q?>c?rD-ZGgDDVm8>Tr1&^S?T_amu69 zwP7bB^`Vyt>N3kCjppw0p@#EwTKDr-IUBs9o6hI)NQI|#&mDRXDU|inq`x?Ag#HTj zx0`;B(?_6q(I38(gjrq|tM@t)8OLY;)T;l`1J&_8V>IqWe^Iw#(QZS`bMe$Y4vuU4 zeflgB&!sV$wI?RGiZcKSr$$Lw;WRIZmz$C5=Zmle8? zG|HOi(zr+RPi^UBJr3&|qQ<*BRBT?YL#qwPrFK(#Q>Sw(ldW7;14gHFGCd$onEKJIav z0t4bt>6;|IQlq5`*iFYO;zN-nKSk7X-j~kO()n{dL%`?H>74UE&=1^$N3HTI{v;kV zNXy}Ql6G8(M-vmDoUcy#OKeK_5?v2_)3GD!sAARy%(;;+$ry*#oZ&F$V3IDwoZN{- z_Z0yb>S}^puyE321h?%)CZFjMQjZe~CuVHsE{Ct@JUU%!LNZqMT!+Q2>z(0!%@IuW zA!F*dBrKamoFk$c zsjf4s>(%`e*7zRHx(HNacoHZc_|xYcZ&5K3iRoRtoVe%WS&WPyi74Gar}ne3So|z3 zcE5qg=UOwC>Doqw&q07LnU>h|0lQ7G=X^3A$@+0MKjmWE$&SZ_7AtBW8<7CTevaB@ z!G8Q=ER#t_RJ;+AUy0Pd9RAOt`O4C$UJ^$*e-^~T+J|I*U)$DMyUwwso604#xD?6{ zY%5I+gAtlX${8+1S56ys$hfTud0$T|uj-_~IU9@qIkDC=e`=uP^oi`D1jo$3y{wyz zW>eXCI%q~e)gcHbr|D;~DzTk@vW;?B$ygN==tg?W?!jEK$Zjp)^>)3YX*ZhkvYwZS z1Qoli{jtHZ;JIQ!lumU;<9oiS%ShQ}WFxS<1g@P>A$7>?xDDL4QR2ZL+)5UNniV6% za+@lmNfI;UQ{SA*>K&kTwsLL?mlUX+OW|o$5P>yFrx+hr@-vfq95urzr8j}sW2hNI zgo`b<&n3a>f@G~@eKqJ~y<0P$m-oDM&P}EI?w*+FySu@z9OKDb zv|Vns>7#Mi%O*1k&-dIB{OjxO+nmS0zRl|I2k=iBc0|g{+4;9d{b<39rK2wLY(Sn3 zLO&wwA3JUOjb5gZh-IP)*Jt6+j zD-*jb+-F>f&8!7gL-Z-!my?VaBW_`GUzq+bEuR^i{tX$_*Ta2YZV?Zh8TL|_S)2Ba z(NroP$z@~i04{j7nU4tXgEsw^Tr`?W#Ngf|(|e{H+~1%lp*?KVZpx?QcqkR?Mw#iV z)(G~=h4zsUtyjq9lhI@zv^DPDBo9M-%%ZF&mArj>B}1h3JyFCDe~# zRRsDC(>)%dOD8gkIBq~9y@u(|h3In8WF(r*W=W@Ey7M8rbS#m?El+9EXPE9nh|VkI zW7!cbM+U z5M3sn%B1{soOG<{#C9&(bXzfTC>D!_qB}6uoSuTSR>YR=teCKT-WJ@lmM>)UX*V%7 zZm7)}%}$$kYYLaFq_ePz(ipBHHq@5CSYy+_F_udw6A=vGJ1Qf!ij~3GagR;^hG-;` z^b%g)Elpe0V)N^5+ASGeDin_;Q*M5;7Su#&H-%`6MQr;;GVZ{%MQZjnx7oCJC*p}r zA)Y1iDarAf2lxQT4bi2sCXTDHJd#^jZ?Q04F_F&tu>#2~OqUALLK&6{gFD z=;&%W+~QErsZ^#ds>2NBtliMVQdO+{M_9_*0)b ziB+0I!((m%CzkhBV%4g<4RhJ_M7_!n6Yx1^A{nnX-r~Bw>8R5qd?-E$_(Yn|l}}3X zQK#abHJhM5Ih)V3ZkE%N(em*_?ZSEU@zHS{XIQ?35bMht%B36io;kKJD1IpqJA96V z>~0QgSiGNZa5x@_ zt(-j+4+rd<*=KOP%OnzTs~@3xkXlD)2h7&UCS;=S92u0oGbL*QD$IUkfDRY21kJSq zjaq&0bscHnDZ72iq>J8xffjQKV*LVc-`lkQy0I~5=77WZ z5tx<8B4ACjj#LLn+#&q8w(yVV3%Df>9=nbFcy7gou*0(^Z<~nP+Fo!X8>Np}{Cjh5 z(Tf*x>rleQrsF8txwW6jF*dEC&Ex$YS786Wx18hWx(2aUjYn(Tw?^Ww9~s0JA#UoR zMPu5!oE~nvu1o99eYc}b%I|(zt+kb>Ib%~UbdkO`gSiFwr@BFzGgC+ZDV!ID@$xjS zH-fd(#5{uY?x{}h<~>!^1?$w!>4sS=-HEkV&0n%tZ`O6AcWBf81J1tGF?Fkv@1{B* z#acMEc|)5v@4VNkk}oB?1x1Ihu4Gekmu6@$BXEJ>BU*e3_wZorl)FC9T-th-9vP3ipE9n~wu6rq5uB6K5*YnK)L6UYL3t3fW{!+i=xiCXU6W z7jDyn{wJqxwWBF`6k$a))joT)%MF|s9<0rA7o6i(d>4-k3(k`~(0h4EoEjvMN zV+)=qbDVsI+V?{Mtl|fK4uNoU8YgoLgcf`bfpF&)PEaP^?YdI&8_@F+kn*_(%Hy`+ zh+kC4Ds{WPc{hdkk>C61;%LKUs6bt|N1!_@v!d%ws(-iZ@{u)jXDq zZkbaCQI)Dr%NCwmgkX6_Wxuo&r1Xg|*qzR8o=JTSpc9L=QXdx?u0NScvA6g@Uzr%Lc<@V9{K5(!XR;AtHLH+hvd* z{A?x_(`zzeJ#Yhq!nmnyTDZUFpyPQRKYSs$NO7d?={%;HhmSoFO|!t(%GV#i-zkR{`K!| zMc~m`^viF6^eUbsqfY8uQqwGN{?&wj9BJpFmUgDGChz;H@c9|3gg%t4xDZ3O3DyA1 zy-sOCZwXH~TeQWwI6wC%DdL$GB`tb(?d+Jg^_j*7x|i|%cCPpNRVN!|%bMr&+*VSj z(dTTaok=(AC6hRrav2|DTgAUqpFD2!h&4@|)S#$!^*kw+8Y*^ssimET1ST2SxgNlfV%{cpQS;pOH0(<3I zEEIpYjb=X;uq>l#H!>~TXe4f*3AHncJ&8Epf#J8b^Vvoad1}OeSlljcS+v`N!v*nl z4DYbQshW_^zPHMpuEj<_Ml=LuG1xbf8B$D>^I=>6pjf;V#%CZObjNim-Hj4MDe?-(r1d|z8mfA_(4mbDLiMEh~!h5<;LgS zL!OQ4E~yF|^09rN)@821g^gy87mK(toCQV9 z(JoegZqi)-jP&v%Rh2f+2)mD|bu1j(+soHW4h`VkvD$mzrE&B;Gal!elri)oewcP9 zvDbhX1SSv@@EFRAfqi$g%iKtx{q#|y4<7ORIFaZ5!CC-ImOprjk5!`xnTCUZ78&b? zj}0=R(gSlGpc3Nr!An$J4W`~nX&)M6wIvaEvjyM73olimXnKH- z2RvdBSOHgSn$m)<&laSr#c>v6I7?tm+!7{a4oX){$WbV_ouh#2pXVTzVXboIs!wzU=c&TSspwg^m8K?cveHFhSf~s8`AD*mM(ds*NADG7rbm+`cd3dTBx_T<&d_(e|{~NUrqViAF zk$vEYv`;u`KZERHpCad{vKe3b1a?T=2f63-l#%XKLzRBkTxC>5`Z@Ct5;+y(br zCd1l7B*Gqw{&1bL)Zz3eEWd<0c!dbwgql!$C7ho<1eeTSBycZ#h!y)psnW8a22ynx z)p9s**8zK6=_0nv{hak&SxX&Q7+jG+O6;fkgKN!yskWLkc~6DPNxFEH10zaSm9yL; zmOK&aL`0UWKN*%~+RNfKjyQQt^;m{1!}EqV+YqeTLE^?^4}YjOmc`x{Ufp7^*eeoT z_uqzX5nUon^ET=?Gw4t1EUbE8m%4X+gnc_)roqEs;$U7`(8dRmFT4>R)#@e&HwK?) z+WgB^JCK{~sJZ|?@dNvRHUwEmNnuoR1ZHtX^fXZWUt9b@zMi)b6;6aiIcCsV- znc}Cn&wza!`xJQ{X^|Hm^6;XuyjpzZSRyFoxyoz1)=kGpt-gTgxp3SdmPpw9VL@4} zrOV1o8Mur=WM^GsXY5Xcaq9r=%)$jWwpQ5~jFaq#Y|ML`u4gqQlcbx>;U$PTx&QQi zmQ5;<`4W{n&b^xdfe?Q@f1bmWVL3cSLd|LsLEzB>)=Smtxc!>@ z*<{-S*cam*fQnC^fP7c6d_`1rf3w1^g?xci2V~XZ;Wxc0^fREHYe)OeF!7z5Z!cn- zC47UExbQ)${1b{Ua0l!S0{zqTi$UE3A2WZH=N|iq8|E&q>=(2Qx8Xp7nPcGEk$fbU zO~!KJHAn4e)z&5ZWge4xZ<0S+gKQQwYghH7AfUFQYR0psIUMH^i$5?O@W?z&A8fPi z-pd5xO~4Q6HqF>=!UI~Ec&B1`qaoYee?;}we6>g7Bwybpf#+JFRgYd=$Cuz^7>N9g zuJih$8WA>g9g^Q@nJ z3fly`LjK7E2G7QW?o1h6N9M>!Y`$1$$tUsPEFNZHzAub9vf1b8z#Si^FXACvKY>@A zr?M^U8rlX}ATbgK%S?sFt-61H0CmOl6OF983}kpz#Jz`Q2_(s^Hm&o8Ug5JW@*$+Z4C`!WOIa-gK;CM6n_VaTYP38MDt|4PPszc!O6A-}=`G`Aui-P? zs9WtI`rC<~lf!(zkJFjGc@F#<^{$F?d30)Wf|f+^zue@fDu>5#XQI^GQFO!@$xc&r zO67GkUrG+OGu;b;+Y}os1)X3L|+A?W8=oHh;Wo3KHCtoQHA3&Xm=| z`BYhwQFPIJm$Zp(Wmx2M81?R}h|j`$u2iP3vXac-wd&tc5u^!`c%Z#FaQ z)h9W9x`xa2_S7mPq(9~;{2oJJ;e7gK^OAlBb9_$MFSeI?I_dkBM&GO+qwiVN?Uk(W z;Yr**&H5JkoVu0tdU+k)$!#TV2Rb@_3*~9AqxFnis-wA=QJ(fXTFrH6ucH{}3+qVM z3U_^nbyVIz$!|0*+t1K;ywdWmSiZ*dmkHddoo_p~1pY7ZdK@CjjJw@6WgPL8$bItA zRG`0#^wCcrdHUc0L;}3f$+>pdn#oM!X=)QZ2QZ75ozY&yNQEw8F4xsdd!0PVhIBQ5 zqnd{fk5-$wWwP8H+U;aCZp4yJZ!GF2a4sG96&Br5?4NF&YEJZR+=qLFCaaCUjhK^^ zcUMRHHa05N(eljJEovXL=L?$GMRar%-{Uq9zmQDC;$ERu&K}95kGDfP&{Tga`SOy9 znimmOumoZSv@8^!{<30th~lu7GxJ|TP^;MT3MOMXGplwt6~G}eQ>zVAEU&l_8Q+DC zZt2w3eck+G0^B!LYmVZkYrI;ytn3VZSjjlILeKU0*KqSb+&12&i|l+qFTS0W;4QS+ z?)jfpwmY%AIk|tBmLTy?F3tCdrJoJBqy;gy7b|AfoM{NYs=U&byV-+VG1&lL0FHY`W+ zsKByUXhKzW`eZ-p*hj6pU_Yr?$S0CH7(O+cJ1+XUJJ*B>Z9NAI@4>vXyAQ6F`(YA0k9(j#u{JT5WXT^3c>C^)5updnkn4vWc+{ZTVii>gCw?#m&W~a-rCt zkK{1EtF$X9&5!rvmbDU52oLY2Vw@M@EX6B<^Ap?ooW%X8bGhWDFbOxm^-uo& za^SIDj{%Bgg*%|}dtx_T^ikn=#n8RbbWb(Cw*)BETg`K5-{)0MXy^w{#a#0^d+;h} zKiNV3E_qCfnP*i67_ssnTmmvdZTnKeDiq8K}0ZuaaZP_JcROo zyBg})SF!&JXxOzdH6zrh%Y8Cnp>i`NpA6(K{7i$gi>8`04-1c(euhwCWOACu0qSj7 z%NM_R+-@^2rYlCgEcU!qjKIE#Yrto-*nhC6;#%?m&1p=+_2V4eVn(p<;lJ4JFgEZ< z?7#ybXi1pYbXx?+nOo{HP3?81E$*u<{iU&<8BNAwc*ZgqOTPEp@)_;pAGOQ4T(tP?W_{k)k#@Pl4 z%OCUy>0qDepktk_gS;CnBvTRJK4ba4uLEhzkx*N3hjJ#J$8B^7!xuTk1h=kbEK7tpvur5uKOrAY6?3i6hwFV^o|*-*0QUfn zRoaV8^{8++`;SnSF1J7f)z`FLE`-`i``4KQ?v)RpC6S0^*N3-PU3)NGLPF_i3_W6xpQysm7K$xd-u^iEQR@j<5YH6hiZH2 zFG;(?shG*1q+N+LdJiRqs5F{VIb+WcceW;*T@?0%Am27xji*!2* zmNQK>Us>mrFE@2C^P2P5Fr7VhFmn?ZYpd%nGp|{&aP8(bSd=n(WnLq5<@tAp*HNLr zn5^^6-a&meyv_p=n=`aK4_bX0_l-oOUMx|}TW4ICt6xj2D1hd!JD?jW--E~~?xB%!ERK&(I{bb!kT~aG zuW@ya!7_;GaMy3D^`=q0e!4d4Ozze?Jo8FRhuEU_a3C$0!of7-$2ee^rk^Yxc2~hq zZe9>#*v4+|3U$YoZH-nNyQfEkcN)#jnmIO3kxi-9EIfWf>oh-rKJs+yG`oQpSp=e> zb)=Q6D!_>>L=?0JGmi9J8tY3~>*~LU)=oNBO(3nmlq-Av=~CK@%GA(Aij6TtJSf}?0KK&Xbjl(|#o1^jE?i4cKo`BuFru}$JDl}sV zy&DB|mI?#=dKi0;uepqUt-%>`!L_rm(-vOrOU^Ct!oAC{acF5eyku6;zGj5ST)p;= zM@Zu_yecIX-csYK11p+ciS{R<4UnFVnqSl6*Sn>dNn!KDr<-xSG%X3^o877lv6#DM z9gL=yZPVBqd!6QY&gQojPwXf1=|ap^G7K)7cDHN#`|Q00oF9VQz^b(!11`f(ERQK? zTIwObI6s7^rLa?`V#}vzx#So<#T!2kdukVN$lgEM)Ouqp^loRh-b~Myt-p+&JPbc0 zxOR539=o-I7dsK(x4<}i3nQ@GZK&&dwjp>_9l1)r69yObj60x}b~XbcoN^u z-OqLzxXOm(iOnu3TzOxkCXSShwy;mvd-XxfX7L{5Xci`$Nm%Q3fvnh}E!V-c7nm#c znw^%u6x0iCN|-hoyi~hx`l+)|@){iV(*(^4j$lmLQ5-D!gQWxo{SwPGp`X_x&T1V_ zWGs3LV`d0H9OONT*ei^q;sX4`9W8iv3&9wO&1U2eipCX$`@P^%29&U>^?T(WH$>i~GQb;fJC0)^H4{J94iYT2}9O|EX+ zCeFj)!PP=E8?ojlfo(3=EUn(#_cq<$gQ0S;HWbIZ#5{dh@p6k;5~(?6%-Qv!7Fpvx zzos8(oMoA0TSbY3j+(r~V(bzKMMPi6?D}J>pU%_!?d)e(gGO*Tv|M2=CrChL#in+B zSlTIdb<(DtOK@zD5y2l_iib5j20z~h4-SxVaGv~icK?PH1vihrZR5b#;C%FL;6mcR zL-Tw};r2D8Ib{)jkbm8EnzztcypOfwP+_SPy{nt+B>BqZ-&9*pFU7I2#3p*L*Yo~H ztu;2Dey6r+dPlj5MG!OkUc!&3_1;BG%*cNId5#&6*yST;#Pg}1U(k1(zc6A(W7oJ; zgy)zTL*e4m)7c2nYOD7FdVTVk6qj>|KCieekaTjnc#DXw`v;u=Ab zUlkulaZQMCOH4#@O&A~e)DH9+yG$Agq|EtN9I7iacfQynyQ))_HNXR}we2yDIdnRQ zcfYrr5-h_UttbL}i*M6D_mNN;xYIrw!|Sl3YL98T=DswJaBl{GUK9CqZl4Go>)9&( zkV=OE3~N=GaVjF?!AZMr7!Oi7y^~3)scB0W;1FRMLRe08&?UO@9~a%|I>JW^uHK5X zbb4qBE|>?*Eya=Xx?(Fc=oh`zFSJyE!&DkC_$>&niD;okI>73eEr9OWdu((mjX$5s1Ul#6yM{Nit@y`bqDZn@c8rY#|} zCcgN~Dkd9k(pHT#Dz%C;(4}rD#Xg;0E=VF$_<`H-qGa0p9B0hR7#1}pqFWkwb)9O6g zvRQ5-rtBwWF-G*Y@H5(`q*0b_I6uocgCRvrzFvhZTgx;Cw@Yv`17zQ!eSa%Xjk6i? z9rJ<@)(^sg7#TUGk956MmuGM+yR_J{D|qclp%T*v>a94| zv+A2i1!qDY(xt-9x8RhX74Cd1j`gf?U2Ak*B|hP<ZwO~ZW|cLIeiOo3wtpY`jm1-s2fCl4U)VU(g4FSh z#)&AUjxqEJ%45V~)#FSH4(v?Z?n8eGa$=X&|B5cXi9HeV)b_w~dYAbaz#wGCYlkryjsluakSnE0_;;@;ycLzG%W%YW%cky)rdi!IN7xlx6Jc z2>Qt;N0nlyUqnCIgj;yk($yd%(}kDnsFG(r`bfa9 zjyo9i7^l)NCjvu9;}7!Frh7Y41-AT49CMD2R;L?dm>Y>L&#CmXLYU_g%^9z*9qQSz z;mL`K@upJ~op+s3ak1tXr2mq#A%2<-cVUYCEZFpTKQ?5II||f%;?Va6=i7F}r%L)! zeeSJ>g9eqM3A9Dsr>xT@nbZFYYa+r2XT!iI&@uVK&5krZ7%O){$D5tjrU&^R)p#Yc zAf4_5rF@i5O_93}<~bdw;Z*yK?43cD;plL6vQa_3#24)RsgN&-C-HJbPrv$(mqt0V zimBj${Fsca;Q!y4qTc;VscYsw_NK+U2Egu*h~OI&nl;y4d# zyXt+vC10#PX7YHKSR|$9sj?5xLTyrBb>cx5N1w$J4ZCpZ*JN{B9HVj*pV9cc1-8Ss zV8j~41LuE1_bVn~LrT$k#v1Iq?RiDm6P-&We9m8bGEsaG#nNw(axMF$euBy=KiV82 zl~v{3)Y%UQ^6PzLI+0DmJX3DD!HWpy;1u5nLu^iKe~Slf$;l+dR=V{3db}~zY(C&5 z?$!Ev%;LWRbGuj~U&M>|)H~2v;jA1s4A#(YlJqova9X=L{o6I~+#NyPH{kqhKH=qX zSCd-n{den}H{K(k)@5C^+u;@Q?yqbl;^kEg%r#|m3Ug-(p7xw|C$;HFffr# z)x@T@?F#hL`$oJ_Bc4y zBafX_MaOxY*5~1)K^-^HGXh>N5=#`UH3!xK`wK3ZKc3k*#V?%*@rS+O&EbWlzBj$a zHkoyBa#l84p6wYC(`NDmZ-TzGdp7!7+^_w1wXvV1VaDyBS9+{ADwVzCTQ)&V@y}~H zox;QLW-giHvo3h+O``ofA%dfD?6KL4vp?qsGd5^Cx0uiwRMB7W*6q}$FrNH2PTbHdDyb?T?!Rg>aOvTh((D(lNHnDBe^Y=r~TAijp zZ4LRwIG&*{X3|+bUk}D%mLp5*na#RS%B23ZjmuR1$;5FwzC-Fbf^YXGZ9gvNUKF2G zxF+`I{JOF?JP0-3gclm0bP9s&U9aZ6!x+CNhsz_o_c#UdE3>--TkL&%{_&`4i*5=%yVbdB zFIL6RUqR*9sn(qKO7f*wa$2)8HG#C1tDZobZZk)y#FHjIy1a@MM|Vh2{<6Ovw^9W2 z2%4+BS;2>;kolgCm(mQo}xiU31IaQv(MPck02!~^8%&R<9ua}z-)GJ7neNQ?Ak9EQw zW*}$p^9e~Z&B%- zs_)D~m7LjEs`Q=1^(L;)Mqjt)$ihoy!fRU;uLZ@6Q^mK-+`770j_rjPaBWzzAq=RQ1Fq{^Ltol2+2IFglSytZ$AYO*$gHIV_n3#<_d z&Mo-5?n4ueJxUbLl+@>Zui`^bqd`XFvXfmyG35X^G4%FH z6cyC*bdXPQQB;PSfe|-RpATyfn<3H z9+ry-_fGj5mq?U>jYbLux}}Ny{K{3|x{3axf8}y|VrWk#I>c)mJf`8ym+t%0^FCgWV~D`l~ZxNji`U%?v0pMb*$gT>L!&B#31)jx$?I;9KDd}8P57Lh= zt2m(RLFLf-p_X#E?B7G#IX_%F)w$Eux#-jLlX#L+H41H_IHeo9%lYaOQT~lG%B4ZR zK#S^egUL@fVg(f;+%3T6{MOo&?mJe!3^*;vnB4%`=b~8Cb#^Rq`Eqa8V~j7Cn-k$7 zLGm9$jN)QG8+I8%4C7$^)!?~RS%ey00t%15z(o!C9hmn7mWoA7y}SC7Gj!w}4L%E_a+;C**O*N#kL zCz_u`@GQIxchnBz@rd$ed+Ld5BSK-m<9vhaN4TRF4&&GMq3U!6$zy^)X5lfw7`*U4 zZs8O3;y+x%Ah!NE(lX#F^e#SD~r^t}1!D?K}lMDwxb=lbFzCpNCeJx0p(uq3MI3eR4fCzc(eKJJyoYd2$jCu_JK!m~?{#}qS^v`XAf!Tv$F z;2}3!#9{e>K=QkS2hNhMiQRm`Q%?jBVS##23XelVLuG(Ym9Z83+)BQ#-L9OQ$$AkF z?_hKB21PbuYyM~UYj?fbjx%Wb(=c_O(_yJK{}E1V}8D-?@`Seo)AwON}yQm3k&XKS{Q zN<<48%E6sTkxTqGf1|XWr>5q)wnZb8kxCUYrIL!;k0Cqn?o#{Ta*$00)BxnIFP4gn;sHz?iB0r|mvfEOj?<%6~FW?1=Ac>8 zYl~W$=bMmE>(x@LYR4}l))3k2ydrvzMToZ&n^=m938;N|=5(MO!As7(a$>Ncu5_Sy zf$GV)M1MRUpg2ak5#^=w#n>ROJ@I>;;BoInE{e+|t@=p(LE~T4!IDg9$Oz((<5)v5cTwKfhoEnD3#OcdH&}S9{6&?n-9=E& zBd`(hqjc)Vuwf-HAGS#C3A%A0t5YGIbYrzHL2W)S@e*d|EwNJdGuk)KZ3|+94KX}> zn=d9YC_meGYM6JAv|QH4Cu8jgfq8k0Kls=-?=0{-I4}0vl)T_3cP+?xWA>ac*!7Nj zZZehk3aM1<-W`vIykHlsR|Tha6<2e{_fm)_?FZuROBA3GU9fD=%lrS2y|<6C?6~g4 zt}MzLuZH%FOw+WN){g11Ld&%LdS-e!$jH7jCy^J9sguw*}LogVFFc^a{xC;g#5+*Aob`zG9~5OMh-9;;`&QMdQ>RXyI(5#eQ&YiB8m^#X>RJAEqx)gO(ddCY zS7vY8`-_hLh2zyy+^lx$X$X_Bm{c;&0eQqt?19edje@H*VhX$W-T0AA;*9BD3@2`5_p%a%B|NCFz5VIWOEluBV+dk~~p^Qn++1fWCqHDp`j8$pZhgAqj7e$Mmp=1A%KNX6rehf9sPSc=j(+>n|XRY?m&ySskO z=o08!OjM9lMn5?dntr4m>=f3N7HkL&KMtDMcG?W%+`E4T`^SUG$MW%DeR+KugMiI) zYi#nwokbZ`iSun(xO&j;C9jNp`xw5Z&@{}EN$9nA{~ErFT!oG!-P_}wl-YQKo#6r~ zGjsDI_%jiCERt7Rqkon067$KjoSNv$@4^0*Yir-dR{OWS6OM9$n#t=I%s1mtzz7wO z`?25Hx*7S7T7G@W=lwLSAegR^;C-(V-afz!-gC1w3>mLq2$-rXzv{O;1V>RF&Rs^p zw&#u-nKaTqzUkM6i?iOYrN}!RE;c(03);iMC~K(g-t^nG!(yl1P7&%CF4WqUDa{ZQ zV)*D?zg-%(ig8qLLlh!KU9CN(VcoWS-)~ne7sD!?2Y7{{8O|Rq92UU{6!EOzEQ|*0 zYk2oB&Dt=cVaRFibMa)oCY_$mOkb|~403>8E+dj8CH#;8omH`h| z+_~BHflS?4$9ctvSBde?CPZE>i3v|a9b_gZv9n4hjM2c>#ZA5A!z!j>Gi_Dt9lX~l zBNdOag$0ko+$<4vfMgTOCLOVCir-zZ+GXP{mBU6oj%pF!0td`PlIhOE#7jCb(-`78 zsF;yH9%!(0`j8JRE{Bx}NA|dEU_I<$-GX+Z!dsbuS1Tf^d4%_N1vg=Qi`%a7#vmS! zTsqe0eRy);yi;vd$~blkJn_z3c*E@xMv8rRYrD6OX~hj2!+y(dtgrg8FxGmlUN6^? z8%bbU_LX_v6}K+=Bpd4oKD<&rOe#seTE=@GfH!m8;pWf7VA#i+SRQPS2QoDrtJ3bl zqCc<2u+pv~4Susqc)5A)Um8MO_g7>td&At44IS;8Ie6``+<{f7&8e92rgnSNfnz!* zqlXf?ImPYC94x$Z7*&zE6#*GC*4vrat<~JTzUae4e&S}afoECGoo!6%;_4ct#!fjh z>VIXsKgP%bESrb0@aAFHdF^lJ=3zYtuMsv{t$HWIo9P12n}=QD z^+vg|K9_@64y&DNtBRbjgy+epv+D!M%*}IivwXV9G9o@=w7S##}@tXy&m0;lJ5= z-R_!eOB~yu>t5o)n`z*E?IQHU)aLPY`ZU#l9tn6FptkO!?=()5US{WiXYLxpFcEED zfQ`0bDZR`tpAVs!P#T*r`4(`I5Jbk5?cLAB9zPvQtF20cGj<4}Z+C9kt9ATWy}C14 z4k;3t#6_F$@BgFYKLEWj+g}bNp0xyXCr(I5_#~sH-vJz*MTtT2Nc#UT^2XulpY-pRv1ov`y4U->gjL6g8 zcnZZT-cg7u)kaxjB;wX^G&ncvZD3*cMD^R=Jim!Y{4@)?@KE$8mM+&kI5Rk2mOGsk z5z{7aZ5m(Pk|eh=?vIRCVHQ7rU)jxXp@?^)%9VQDtYiOGiI7Mj1=eFx)(2d_q|jm2 zh;^MD^We-NiM^|`Q_nLxzOA{u@?)+jhKMxPRl;4YU-zLs|k{jmZ z!3fs05l(LA4E_Mlo6Xcb#3**q|9KzJs0pN}JBK}k^I$pX z=0XAVjP|Z6^Fjz*yE8ZoqFoNnd+i*(@iF0F_}nh7b$dwHtKV;U-=FZlzm|Pp&eeb2 z`|j6&FZ({s)xQgyvFUf%_3Q7+z9VI^*Y7^>yQzQo{xN8*_#5<%{X@Ph{`ijS=KB`D zEB=>=zWc8D<9n{Y;(v+gyZVYhzUS&I{+EcptFQRuyRC0*6Iblr|Ff_H!DIpYJG)F( z;@9|D-hDS`)AtI-#?f85yxHA?fmaSLUHE&}mwGtLXZHYZ*m~tuDgO4Qcp(K9c=;NrudPh zuW#8lrS2j^LDx2U`0mD_f*bpdtWF{TSSag3^u~Q<>Wz583q@W|p@J80rI7pZo63Ai zyVvbKMH$7gyRkN2?)q<^9sHcF7yPJ^HMMbjuqhX}eSET7v|GiO{P!)|}IyNbJs;~>;AhBj`{n;UT3CA@w* z%6#L6-!}7-uQOgd2O27z8x?b=@al}lf@=deWQ2 znId$0WD_eUu-MDQabKqbLM@JJ8)wuVYZ zxY>y#FQ5LzJ(LfPOJ2u#+3&X~K9NomRqVUK({v1aN8EU)-I032((=`N-5L^FHXBJX zX;@m4cqfIb1V%FQ_(v_?D(iRf)~Q)mSzfGbf6wrMnP&!A6~AopdObJh3i3|2l1>Z+ zvpK#wtuIJDqRBayze2E9c8}od0oS(;;7XQk-|y!7hTd2$A^0M_Mc<4@=Xr)lp6Li= zy4#hX%=NGFo}I@n4!A@C{lw-|$CWi$CB-Rq3tpSRft<~$dt3HZ%7`)QYrRi&#tt%1zzOPNa>Is zbab#}h`(xaxa8v?d3s?WDwi4_f8L_24@H6?()ID2-#^Y(rA`#3)w;1M?8**(zRO|B>WrDn_<;Rx^|2}HnCwvo2h{^rK#o^McWPt-5Ubxrp!6&KL51 z+WF!VzE3+}$oFaI3;90nd?DYboiF73wDX00pFCd(Juc(B>ifRBuVt$1n=(=L<(vV( zGTP}mWBM_}oMlZ+&dG8O?KBSAh9P4<_UwNWaBTeWA|i$~5{G49m9lcXo1CuUjbuat zB?}7xlldaWH(v|7_PT|;w{zh8<-1YB)tC7czZti#o}a#DzAc-Y4sM9Eu)tL(u%4fv zHQ&9f7S5L{cd5{3Lfsaa#vlF-mak}79P}`Ah6`~Xk#=w_=T%~iQ~kJmU?Z?~gez6Y z&gk&8DbG}2#rJLV-BWuV%wvex0ZdsMUg)I5_eEDOsvqXV;Cl@5Jhq)x2_4KgP1%G> z=xhwve~@iGRl=1$g|{vMSIX>G@5)Z^^ULzF+6An0*UrRhE3SJaw%)>2nWJTIxiT%{ z*-7~8+CApnLnnO?`8ozmIIaa_<(p^lM;-jJdVaih9>15Z%>VlDIenl2KV_$a8_6~3 zL%8RJXoT6`Xt2Ere^b{078llNgg5TtgQ8?rIIsC|N{z78Znir~S`3>KvZGGV98dalU_5swBv(L3Du*ytF7(`mZ(zN^ZX%@Vk4EcjV{Dd^zvn8- z-Y|J7GO`&fsG*3|`5n}GbJsejAjkHwjzY(s!n2zolzbQXx$mIA%0(PugN7;fSi+S* z=gK{naOJX3+V(k?Q21AOmUB=lyYKq=j`mUx%v~RsTpfRkGx`6m>E(o>_K$#Uk=()S^;(pF0E;vO5S*`fyrdwb`k{(;9{I{c|Nrk<;kgo)OwC>s0W5+HX@t$WApuaFN6) z^v`{9yFa>&0dMz{OE|H}33g5A?RmdlCyYy-3LcU|>_GpVkO^I7Z6a-R0j!1ThZxm8ZA~$ ze4Ts`?pj`goNo1S2}oePh;Ki>f1!(@^QgJd>?~Rszb|!zi(6|8mp8`kV&S;`F7+Ql zeYxKEr8JQqK!;E6|2n>ZEV!_;)Pdy}|C4WjfN$RyTv%UQyg1nL4Hj7WCi?7;qTZBi zg@CbV{{yIlnZj1tm7@l5-n0KF&9~*j`u4`=a4?n`KA0$159NlR^#6BIM{vR=$X#)ldZM@gZ-7;8 zB)kKu?G~Zb%b0)U9`ti|6O+W%P*O*O;P5WmwUQQYPj-vZLJb#=fxvt#(l;Jutd<6# z%gj+3zl`ZO1ZFmT4wc`36W_hHD!MSgOKt}sQ0k;#%FYmi_gCmkJH>!j`CGXB&KkeDy zH*ICegW=^o-G!#FNI69Qc>!v1|5MOt!3>piN?NoRNeh`%h>9R7nK}I{(>K=q7f&Sf zj;1oF{}ePzZ!)RUzl;}+v$>FOf9~T1E|qb-B(Ns74W;*IQT|6#PToo`iEmZ+7Koz$ z=q$nyfwS@>#ztQF_XhWNXAy7D7f&pRhg9hKs+n6Sdu>NjR+TmjNi<(VR1qE)6*dKE z(n;ogu{Lv5|NbGVTQZasp87X}qg@;V>j5b@IExlpLb&gi;3<49G=~frZZC~5Z%EP- zFucGxfbX@%g%CPPTl}I{&y1HHPxYZ+ZJNj&zlF4Z*gEk2_%lSX_8lNiP4+&GH1nXeUN6z(=9F z%%Tsggg6dxTc?pimoeu=J(uk`FJ6=|U>8?%#fKAD5K4@c+fBr3)gI`iu-^a29?jdhTwB2l5DnOp7gurCgmZZt{|wH8)C=j{EU{xa;c$cq z(b0N{DUUv9`T2vN;Mwk?O{jZzD5pA{vC=5UDeKWkBb<@+>~`R5^wm)&pX-kr5};Hy zm;X}HmA?;wUtt}iUzdAp>+sWwa(z0DPFJB*KYG&kd-}id#sF2f*1-d%F?|ig_>;1>vI{Xjiwf;u*s)ym+RY?U*e*BPwX*d?%}XEK@`F|cAdX)d zlS79B&HSEAswBnkMGAVzOm;|~8p={kuLxT^uNDs@9vnj}It$o_rWEV-%7>`2m~<4_ zLd!q3<9gd4SEVI>EIb9$LbM_(PK=9AiVriQM1T={{_uP&j4m=^k*LEJWd?9Unvj~L z1T%b*65jZU&miPIDdEc-|6F+2@Fkb;1apGf!JfgU%A)3P`}~R6@Cbq7Z9Hsb*S=fs zvzz@MY&06MCsrt|Rk86%zx)1M*KZ`3TY8fnM<;lW=bg}~+r|kt3}vYVJ;mjck4sWV zPN+sZ=``DlOAj0A6f2q$m%v5W#;ZP@V~u)(C5NzDyNc_uC$7Hd|>6 z=P$IylS$;vFer%(z>tI01&?+!Nz$szmLM^%WUxr%zTfa+7LgmER*Y)qeM^T&sSM|f z{c{~$Y=70?1Wl#WyFR=GcD*K^O2#H~n{+Cv94P#bhkqOH%W}DfOamA|w6Zcn;4{+b z%@!`1(O{lFZ^s?_?H-QHcvGYy8LN&i$jyx`Y;jKUt@`yLqT)scnxW*fKWbRT+%mxJ zqb0EdUR>K+T|P-{yF^gEys- z2q4sbz#iFyNYCHT`1PA%rIj{IrCKe-&N-6Pw-o0PD;c_^!DOTGPl9MN+*Ki#l&!72JtRdrFsApBsY>+m zz59O-WqT=mZP{SFC57U9Is@Fzo~=`Lb|`&HyU(ND0k+#F^%Ur5Q1=5wUMzRZ zDfMqT_u=j&8>x-(8uZF`Fin+^u^8VFCcvwMLkZjs>ZjQIaVg{cSb-{ZH|?Hb*;!~G zGW8qgrUK{1mVd&BYlDqhZhSfK!2e}o`&&h3n|ucTE?b@x=>P#*I=>d`oa;-3Sy5b2 zvKURCGO4Hf_fxnZXkmFQFXd+cDEx-8?{_b54K|jt>%U_;b8vt8++En$a2|o94EHv~ zojcMs`{dWW_1DA`G0jr7SjHk3P^jtM1Kn19zq@1#A{2v)j8n=#*ioLp@0!|pvuYb_}MLPuAHdJm`%AI^Td$ltZ2&Zn03e*p9n2e<4d&Ri&=;qU>jch zmZ>v2tc=euffoV8A9A)nUIQPd!6jrJSx0c*HsyfKgYd-}6MKhXyG;o;-O@ehjsMi~ z#7rkl>dm;Crsmo^l|brnj015R7L_Zrfy~?addG(&dv`1mPB7 zA#cH=RX6`qaf)=GJ@%NRci|XPkf(7Pl|nEArp7J6lCI~%myM6Ly8@&8XtW6%RjP9u z{1e92jCyNKRktZRrmUUV>tm)B#;tl%YuHo<90Z9&x{w$~O*Xoi%9-KsnV21Vnt@tx ziShcO;&oNa&*FBVB(?-kY1?aBHWXfz6f}suMy#mpSG(m=k^M?yAfDy>A7HidV3>gSD4&H7~HNWbR3p(6u?ljv&gg z;lgjbV(enafL~gTkT)fn@6O}J%33=Sdg~r`57y=vC)c+LZ-s0y>-%Ze(Pb!OGy8+w z3;$vuT%Px1_3$Gx)0C?o4PWyk$ zmg%&q+&S`trcHjzWNdcd`I6mtu&;=Lj0rZyF?=f6R(^ctUmJa`+XFK%4>NuA9azOw z@0&T??lhaFsAD4Cbcx8nAxOC`_Bn}xYNn?;^Rc7t`;d$C7}L(~-5$FS@^5M#q=+HtFhc{mY$@g{*(SiR)kVgsF9G|GIV#yrlgPB-!Q` zNR{QvYCh{|RcIooS-Vn>afb=FcrQpzCnAEe;QA}r>q<nruIj+z0Yq;2Tt=jgccS_5~O0{mhQ%-VnU?-#RE(r1aQ35je$K=H;U=Oe z(t5oeHW8dwYw>I!g69oSUH{*GI5^hyM{CPKUuEUDd|1s8sZ5XpyWJFE?GvqJzDB|C z;0^!14{rmZSjd}u+2xCS--m}V)S5gn)QkjPtJ6-9sq#x1AL?A4`70l$A%x<0*XO;l z9z$x(D*Q*WfFtuLtWd{$<^3G2!8$U#g2YYfOXSEU$N;%>(ZyRC%g8#0XJWFvF8lnx zz!&9uHEG}wEuCRO!EI%_)ARZLe*L5Eq#T!%Mp3H6uwbh4zJ&DqLBaOBy?JiCH(Eg| zM#$F{D}gTn#o(ZfLF&G*b)j5(6|&rD53*m30forQ&_5@)DG;BcYcKsj?S3ZNlrjJ& z{kQ0;)K#)NyCBp>Vm8xc`^y%u8{T;3jf1pYMe2-_iDh-W9L+#f3%JG8oaR)89hyjh zr+3Y`_x!w@>zN{~B$W!-M=mo9F+F93dI;k~r}f26*Vz2@DkrfaA`CZmD6u{FW~yIK zC!yzk?qHeocn9^{MD<80^Qb&|?H$XLk||0ijmz2H%L{Z(*>Z>pmp&gSl&TbdEv$l!wV=myLQfG0x+B(9Q;k&{_EhkZ12Fu;Bo~? zTRZg@$?gb7;!u_2g$RYaEOi_%*WfI1x#bQR|o%(p*`p=N)hKv*4|b7 z-ApINP&U`s{@Sixzc6sQGheag%XH*PJ{$-!ul&aUYT9GvBN5(EUkeKLe0prg%{BJE zW|EZZ6+Am5l0sm4dtY|<;O+llamsfZDvt$!W6E*x?&4K7xd5h!Wisi8twV)rqLS_d z!G9ziMMY#{mXG_%AI*5YYqO?powWQLdt>5zFU5ML*r}U)ySFQsJZ{vp(P}O6 z69T?v;8HM<4apU>^^qZs1mKYd;)mb$lF%d1o9rEl{?r@*Urm?=^pl+ zH(v-SkzKe89gO+crR}r1@|FG&i7oKC^k){&>pt!AE?FuL&PLgsOF5^c8&@nv0A(~6 zIz=~W!+I(;+#cX4P9eZX{#AQz!R8cmH%msbJKPXHzpvWy6b`v{H-k6SJLPt@*72@j z+!3F0bR0HC)P#ujUN)OmU1ibhzc7Og5J`mVFp%&u85yVs2UX`f?bkh2L?$9+>z12D z^7fRgpZO@S{-xcYCJzpg%d2t5gE>>dTW(Ra+N|3*nI`7*mQ%?YV=eAFW?5m@I27~O z?adu0mvkgg0E96p6u8*9$k>hFR~Y=dozvI-F-b0?q+F|&Qtw{NolojSi)l;TZka*< z4=o<=`*^@cj}5cYEZO^h(`f6&#m;3iN!y!{!YcyhN=85#{&0U=irTYhq_lhRu)iKE zVY`iVVVVcQVMVFTxtZW`4uRiTg)JyQT?>A5^;lVHH>c|&_3$Xnv=JTFC^uzUns?S@ zUAXrRzu)jV(kqDv-fq24cRB6Bnn4_As~UAW5_2!TPMZhPdKYoUg}p5;Ieb=ifBNEz zkSD|xcOo)2r{z|8JIUWY40cuZuowNYK>i{Z9d41@JQWjTVLt?MvjLetWI+7Q`PQsgTyruHpQp6L?VSp<3|J5yz8-nM%1@ zN{cO|z>#hM@1WAA;MJQ!?!Z>3Rc%;f%LHjSikng8Z*`h=RhGiwaXaQq{#bBtv{OTd zw?rJT7&KQ97Ze?@90wN^XZ*Icd)4n=*<<$!+B8fE}W-W4oJ3<+(jtk)+IXf8vsW)-`kn-dh5-XJ|1mcADG};j` z?h!@G5AWv20~-WV>NRWjPW=SwzT@MeJZ3MB2gwz?4@JiPgg2h+4}1K8`6-rhB5Or| zoAX1h$t`P62uF4&^P~IelgHe63Wsrauf}OP4!bb>6lCm>qgk%8D_-3hBW}`Iq9L6!-Y_sEtI5sEM_>D5bUo>Xj3o`EL^&!RYGFUE%bdKKh$AdX4 zSK@k{_<0hh&5@f5H$4-=CYvMNVY@d7)LsFRY|Rzo-!gAW>Dd=$;@0EQSNz`FKvd7o zEX$6~#_#>#;T*1KadQ+mFt^Z7&*IX~9>uY@-o5|7gl41mK{XEw9zTJYZncGbrf~Se z_VOJ`n9;$@xDN`umG}n#5ymt$cIUGULzp@A@bYSJgsxfskEPt$*3COV(5|oR?(~s{#L=yFI3zMKghDYIb;Es;X0_x2O)W2)~%<7n4ea>UD3K@+t057u)m>bW4FTt&-|KF?974 zfpdSVekFIq@%{4Va(BIduD5)-tC9EP?)rJr<4k;;#Qvj?41f+q$NCg>{#-d~N&tz> zzSemeez{y7K^XCQQb+Zzckg4JoEcopKKOB#nPfi3#Fy!ItAE$PwkEy|-u?HRHaNSe z`6?@W&6Vj<7iD+v|DT{|YaEQ6c{B`0eYeN{z&neJ@3v7x5<2UfnK-)L^4vPjW=X4NXv$52jsz*$75EdagpD&mhnB!%1IJ!n*3L6hZ;w66*hT^13^{_%m~g`D@*4v zEe$=ZU-id~yEv69vYo@+M|+B5fqzQ^nLp;`w?)&D{A8cRiB=$@Na`4;8jtC;wDJcl z@;6gV6ACRy{K(cE8-aO#Il>_k_#5jmqF9hK_s4|a-t@*(I96=cYfaqplTd|RAbm;W zhu-z-&y-7HlGJNS4G)9J(0Ht8pBxZbfF18LTg2=u($d+&Sk;WfO^{TPM`R+OBiI{# z|Cq`sIxRo=Es@{a^5dlZ0GQmY+js%YI{x;ef{ZNABd5Uk~(s(I+Nn=kdDZR4Y%W$Fql){{qsId+?#E-kn*$` zpy%e&nnY6siTa{m@o_8TK3luo#H&$myd)Z_$)VlM zJO^gvQ-E9+q!TX{@He+codh#Wzd$}F^W41Vf>fkXa5lZULKwtqo4fzI$LFxkC8Y*@ z!fA_xA@+{WG_#XkCOTyY#Eg}Rw{(p8)ICaa^_}E?uGe?O{$Tdd*I`RJ8Jv|TQgN+E z_B`|bG;Am*gJq}**?pjl--9asi<(0@6kDwLv<~2ofXHjvLV2$M7K*_JQWWD46dycl z*Ts{+=EhxsPYAIY2zzWGH`--9jk6(noa9zw;jq2TnaO*v-3(&okcl_0R4T?ci^;5U zW~{s?Nodh0NhqsHKdvt$)JQw;Z`LFk$wj6TVYvGrY(v4wf~gmSy_q1N5E>o8aP7EU z1PH8f_VW%reU&uD%a)w}TcFPgRv+6w9}ei*Mm!jvJTZK93qX@X;CJmD9rR)1-Y6o* z+X%BDZY1Np5PO2Y95=mW6!t77skWsu88_Qa=u*_M1M{^lwNA*7X7)pV0nK`1ll>X! zi*N8=#kubA?B@K|dZoWPkI;m{2JMzoo>r3fJg!r8T22>z7W{uIcZWh**V*CrU>%&o zT?m^N9P7aP(hEkmOEPfLLcqsumHWeYxejN`z^;dfr)&`@1 zJ`L0j-sU)98y%(!xv%T!ZQG}`*M6;4UDi>g^$yG!++@8?t0#KnPk-9=<+_q}zK3Pg z8mfLScgs%0x~mAP{xA0?@O>(JTIVUy6LqGdrg4-9?lpd$EU(Oly!Omkdh1MPoWdr{G}bwW-zFqca= zmFuQ#vpZbJQzBEp6m+Mc?(ED*Km*j7KwaKxGB(}-G8W}Gds2XW2(*I2n#c<9%Vz)L zGP3m!kp@#^9Irzw&dS#FWiKvyWs>Iu?bLU9M&lVJJ^@)-On`_n;#+qB`oDYs*Kn5G z&A)RL{58$5BJJphu>A|#rhDVVIf!VWn9TQ3m+_i- z1$qq`PfW7aU`y7I(DePO5jlb`g4a}*=IU*3Lt6Kg zE@ys}hKldH#Wgm2AwhM>zE6@C!`EDuls zQWvlNBG(LjNZv-F_0QP)B8+dR{#DkW+gaCS*(p{E1!QG-l<&<6KCig?gY*6FMZ_}= zHYe%|4gV4}R2obn0ce1`{UWayJ88F>oJ^WXKdtEkkT?%dIDt1zo#xFd0Vmntei(Kn z2OvdaB|v~bQ~D8Hb`H=HPxmpoqxa>U?vOqC0XSyP9|P~R&csvz4Xh7SNAzh^$B{_N zKX1$T!sm$cTTN+J|C*_Pt0~QJJOEjLD;(vH`7|8AZ29;4<4zXIeW+@+hy!hE^l$3L zRG|4ARbKw!pDuK2%6%x+@4xB8M;e@RTrJ^0@7zgcl3FH@M+AM-n@`g)aEETHrBsml zBM*t6sI&5Jj60996-P?ws?nM-FbRG;Wa+gRwn~kU!QGQur;h7<#bPKzmX9wVAC7$Ll+p zS9iGLp^URRfegQW*!dHr%o*PtJ&fGN90jZ1I10z{P+kR(E7mxjxW1Nc#?Twgnin1M zjLubd6;))DnJ>|i&*1ERse2CD$?^O;^f+yM0P;fG8XrIJxyco|Y`!G6GSFPTv zHSm5PX*zAbI7^OpQ|4<+=j&-7Hs-6+h|;KqWCMY z3hSFJ9b6}zK3_eZuk5OCe!hA)nJ;PU&6iwR%yhr2-{QJn8?UULlLyLF=B2;x%_HuK zAeB@F?-EaaXOmn`k-;(U@U_#;d2*F7GeK_1F> zt5`w)ygON{WcYmQ`-qQ?}}+_kf3_Y$v*o z&z_r7$D*6*k9!a^M>s^+1aT@N+SfC=IvUJrYAMg(1HDz@_`@k$e!lBB9A6Z0$1H{8 zL7p}XYgh#)b8HE3>8-&QXN^J8=E?&0&d|;4Lp}`T){E=)I$pL7&-X{0{qU2_f)3-XOJfdHbc8TS&Fk^pYu z5{lbI*ay3mreSbU<@O~XZ^&)jbxE-fcbD641P;BOav+jCJU4L0d&k-aU@sO965avd zSe~&ty5vkLb>+RIS{v^jmFAHSIzd{CcHCV++7qP1IMH6r*1`*r+bcP_EFzjuJZ84N z`@WxbB)812{eI5sf#3S%w7Ttqu(d@o(+fra?`LaW(|t&OH)CVC1pANjaiLQUYt^J4 z*Q09aUTBy#FEq@}VvfY?j{jT&<>uNF_f@qKUcmwX<03!CeO#Yj+qw*4H8{V9%guw0 z;d*}qci%B)6U(i2Z&H=#^1l&UyMuX_^+@83e*p0q&1k-YoNqX-S8(DNJKLW9pTc*) zO;V{L5}B#&Df&-ra|buh&UOSqY5`k9^D&kKGV*^3gqCRIlM3F0=J>qpnzOMaW9I4q~7 zdb1dg*9RARyz3qY%Q}vi{knLcrd*HO9UO9ocpGJFFn1ioP=YP#x#*jIn`3FEQ*Xpk zEgX*KjxQtotI{gdec#B{uUFw*Dd)rtt6lwVzy6_mwNxz^o6T^OM~5)zDemw1 z^;%)G)~?3YMjFEAKX?3zes3M#ILj;IJ(8>2s@BqC6%kXg9Xp2Cd3?mLdl&&_h)qD2qV2Ke5Um&b^$wLQb-C0Q zqn@2StrzF&wUZj$v`|&HEl+3TsrvP1>#ej@t|O)B_~IJUB9r<-CUa81ZV_(|;{lo! ziQZAWzp^&Q@<(ign~J$#uzYbQ*ADkm8zsCdfFO)9?z*!bQs(ua*zT|EatK0|b6dH# z$Uji5R$H|?7PYqB9&ID*6^~P45Ne-K<=Q1-5i6;hiU84ecGb?hRjX^|IlpZ?Y`_nM z*arlMWlj4d9@p^(lH&QI-=>JSlUmhgT1l&3BX|uQqT23dza8EUM~Vl@80R-bg8Gau zfsXGrza8Xwt=g$pkZs#F+=NhNk2;OnJihI>O~XdBRYzzKCSAw3ftzC^yZ8b(7dQNN znEzrLr8vd4yoPW&E%$88Yz~93dTSlDN%5#x6^~1~b`U8m+atXCK#tQf-ji#Gc&jGt zN)3#qb8VNmU9*FFo@(aWHN&*os?=IY#FDkcj*MMaC^QR&V0&qOjVg8+TvohSb1-78 zu}X~W-`<#)aVHb$;hhM{c<{+wThJMIDI2f|x&CqZMXKO6OD^U2tA0DAX)P5I9s`qc zhBhQ2URdDVDBoW5+XyctuVRZtBm)q=h`8T5~kf2V#>0<@Ww7wsBC9c zy1bfeSBA~JS#BonG!!im0sxBF-0`JR?}@ebHDy3;`&O#WKHO|fvlu z7$fE9r~Nh$;(2=Y< zisDj#oqWm3)46-uZv$5WboY9vLH&m_o+%5Uzqa|N-$q1ITyG@UCX_plXPSd+^G3c+ zwT?%{TX^=+wQ=i2W#8L=8_>Gckjx47qHE+17?<;>i_3a8`5*_Q9M)U)Rt33X9E>TN zyk}#)=Zd%HU^i~mYDk+IyXLAD2f;|^?||P1a;(#?R_di@)irt?+i!M0$<9Fqza8FJ ztQRZQPTFzJ@V=v6dXcV$NBlP6F=l+LA@5j5SnSwqOG|y_XzaZ@kE!2Qo^COC8rUe!C8y42dC_F%K)-Te4Jh>p6^pGZHFjIv>;sO9d?og#?lW+O$9)^RyRc;}TrnchLsm}kNU-xji4cx#X zsA`bO@-w`jtA|7qH4LMUigvTmJ?sO&-my+pOY1RwuoDt37$~lL{-L-296(-unPt*+R#q-@_)S(!9lTWJrr;{Ms*_WD-l(+tlTAJ^K}pw}G-Ys!-c*>-NO zTMD}p-;GpfNlPpMZKvC`^SVv=XSUyCUfY=tgbLL1xPm9lgkQMAX+@@XOg)u$=UrT( z&a>B%=FXkDAb&bQ`M?kMLR$rlqkfDtH3qvm9e6bFk~(N;h0G7gsw{R;ezz0*d*7920lJtPgWaJ^k)q`6&jF-xg6ecN$IG79MFpKY7&(({OUAbDU;-wv} z$M{^a2g{qXNTpo_^33Iqf143J<*|wLI+)ezCP?C)&Z9o6pdS~9@i_Qg0%LZjLv#N9 z7ho5CJXk~!_*n9Hwy_)m!^CO~VWWRs-fCQX9AX8oElI0H2h=+jubc`3PWOGbNG0;D2#8hjxBH?GF)H%)C3G&;Gmtt zNcTz|833NRDd7yB8X=;1Jb1U1FCwqFZS3KpbzYgQBjlMF?@K=@aFn>!{w3qD&0=NJ zAF{ZnBZTsfF3ZEo5@Y;};uo-T&KGl1_63xwj6>P76>%8<-TUWIrZR3Mwz|BC%VgY{ zKHn$+>+pU|`Oodzx$(TWu95q&T5EPXNMyyuBQii(or{tSn(L8*g}R1Yl>AfcbLmo` zu@RRzznNMEfoa0c2)QMHNAly2@7rbAwZb4Q+cAazl{Y3lv)O7?8l}1k`>nO$&3yhlxv*u!?u8F!tr>eGB@* zi@+O*w|(_wBA4f1SX`d=ae?wtFV>)p)=kXLoz6Mtz9D##IY)6o$)Q8sjVm_Jp0IH~ev7eH0@&b#T_?`XEo8#tHRi3F8QZx!<*Yzn|;7fgF$UC&6^UzS(qMM$Y!+6iz?qn^XA_Ulz;W zEejqu@5`)g4r3{r_D}^BZ1>hs<5Pq<21LsdLf2KaoS>Vu29dK0?ldB$oBYf1*7c36` zg3j`CzddhbOXapmZ#e9li*=#Owi{Xww#%2h^U%7vYl55E13Zdg9$V8Gy$8XYDCcDq z2j0b|KWoeL;GKOvfNrfZNHY&ND~(Re9(c`>G56EV_d%5vd;GT8$4ZrIt6hOjMF@gf zv5JyoHnJ0iAZLQ~rsy!!QI;l@XDoI)lbJI1uJ+=G{Js%K(uDQ2VKn+gz_6nSv<^pXtcF4cjyArrViG}bB7R*)?LCI$A`xK^|Ree z$Bxb&0{SlB@D&f%4B}J}N&r8Ry;m@~C>8}q!}h zIr1F$2x3kI+A?IWapsDs%AZpQd&gUbd!Hz{&s#sRCe;wc7r{f~WGzLKzVzkKNeV_J7q-pq^U`* zK6Uf3o4u3ygMWEOW zw+qH6x5we$4xGS9*JK|=rD)(N+{lO@Q6o#UfZgynm8=FA52NqcP{(d=hW=Qf|Fk=` z4nht;TvDA0gvq%aJJ&CzKNl^R?}wQiZpB>aLzzQPP(MF6e>Xoj31YL5?c3ZV{m#wJ zM>kZ=jjq3;Hbl8Ak5z8=0E}z`*X91c^j_xE+a%&R-fC~ zpY!0%)GA@4-E7tysrQ!Qx4Hhf6MqM0R_2&I1)^a6sVqNuzc?%*3 zK-jtJCU}0_|Eb4w@V5~*>afO5#0L|-rUI@!6x1fic-%f#(mWjy}|?(o79o#lSPD&N|4@^TJv+RX8I%coL4SshCzq@u;l1~iI2AR%{3l_o0#4ieExrIdrkH;yb zpBY>{74&uAy{_+3yE?_erBm5{SMbM((4rJ^-%+Dz_>+=?48~1@1LY`4F8)THhT}0i z69>d5tV20UmgbT(bx1X^meC#bU+OS%MlzC-&CPj(#cV<7EG9xdkS;j*Css$konJ>+$N90=LP zvv6qzZ|qWTOspeL4q5D$dPql*CRK?VCzu?{+2Rk*7RPYMTc1uSC%DUb8QFPx?T5X! zgdBqnJjGkbi^d#}Y8Cj`>7DGIGt3{Pm7$_Dd064(3n^SX`ggWZTwry&4|0Oxtcg%D z`~KQ3I1Q*!rjR7~qbP)%Gm030rt-n92olWjqZDkg`eFVgw@kvz0l%X^!t8GSJ%7&| zA8bw~$)*M-F*GHXx7h1*2#OGzQ{UKwIoPnHwW~>~Tr+1)LkD6_wkJm$ZrK45d%MCs zHUYEJY~qz-1PYQ&#Mdow&iC#xkJ>q|`Y>k^8Q;VOAnR@OW=nb?KR#~TrG7ia+@(oe z!cA$x^h|Lg=hOcg5sKr&>&6PZsq?!@r$@g!0DpKncT|lpBrQifi4aRjRTk4A#oq{w^x~iT zl9%)Y6rJw;;7@w=hQBvX;&!_UWn5}b(EAQZok(-81`@6)t?(o#t0(4}a~G zTF0}n0EXJrUxB~Iz;NX!{XXEALHQJQ)4>q`%p?qeKp!UN;_K7xoLTt`T8_M#nGxA* ze?MY$w=^GWV)dt<`e$mLqK z29<61^ACkG2S!m$Echn{BgZ}x^qlTFNe>h{{ifjeFFrLqaMrF{c}5~Sy`6B`4T1B= zj=(?2N{z3m`mhlfRV$avI4PL1QOpv12XRpl=kCXE9G241rqs$!UqR|?g=w#Z-*G3L z#p%2eX1scJE92vgSIOxZ`&aE6dCng@<`pk^L;FEIwV>?S&P~-F&FfUPkn^hR>58@G zP6{imr%3Bub9E}dKgq9iBQay$A&{aV((~Xe{#dJ>Fs*m+VmV^#cN;5Lh~q1A!LB^) zt9>LA`HqBh+Y9#{GjA*=&+~`*NbQ+_lO??B184gTVH3!FmAHyoL(;QS3r;i{$A{nB zJJOsw2T{9?z4MT?xD$J5w&{m5cN#yvC-ugIV+FP|q+3HG2mzm8TvRv=sWzmPlf%8; zk#m9qWMWpqxvNHuZ8#I>hVa?g9|-HyT7TPA3Ayjc{;}^2ecdpJZ4F=;6Ps8~_2B3H zvBFnbE7m%&j!u0~^A7Z~+s4w7)Duca zRc}0atF(rvof8^rxDuUAO1*kB7(Y;UZ=`^Dn zZfBLMp*aE2wsoOw*2~O7?*KKr`f zw?)iLv0Z@y4SCj(9SeyMkQ@rx*_HvqQo(mz;q^q&#GRJC4h+5j+Od{VlmWG7Q_ zPqUP&F#R@*QLEkV7@hjVugg2f?hwg+2g+G~UFw;3^@PV4NKu)S^8R zoD<{vuF*N&5Ji7(sUPgLKNk4G>NVVEP7&xpDo!|^l=?q!+RPh2nQYLCv2X4_W#5vOk`~CG5y)&8pi# ziAt&=fOl?r#cu-|v|Gp!Qf--gC)0T7yOaj1-|d6(S9PKp=&IF}Ioo&l4}aH;$1o7q zL>9X_^vBZZgmERRCT-|d&;A z-}d?7ZGS8{A7F22B88uqWBJ=YAe=Jn#sk7Z@q7N#j|PGD-N}=8zUz*D(JsoZ?{C5{ zD!#kB_djxP=DYJ;cc$zN+FIY8DYFg@>G#vn=dDk0dBP>g91I_aFST36qdbYg+Cw}i z{ipzeGIQtX>L-F=lYUhAK#dzP^N-4Va;7YEtd)roRqs^#zPESD=X8T_Z< ze>Ls%E)Q-0jwwHBh+t?O0e3Bz`~_&AE63KFD*|z)yBJ)t`aA056f%<5tbsy|`Q{h}^l@$u6$# z`An2i$)G56m3c{m-}r+v^pLj{T&&acJ|3`HmGIm_z1cQ*jVYU)kBRWv9Z}ffw$xb8 zSFFFwpMR*e&9t3IQQjB*;OO<9EH*b+sn@O0(bb}OtGM8D-^XEKUf1z9sOQlXb zu9uL2Ngn(Xk!@_`MZ_f?Me2=nc-qUek()wV(fuD`mS63L5C3?lT*Un?gksGd#j4td zStv8e1;L#1!v}twhdXICO`AxRJ9ku8nrjhi{kv*z&n`S1BZXI5jUm;K(o&W6kmU;7 z>(@sfWO&&mp`#zio;9kg&gZwO#^AYPyIhic<)BL$i)2>bMnaB2$*P5Tcl@_HxHva^ z8Cn14W``pXJ$qC5%n7qU@lD}nylOiR+PVin=k+rKdrzf`sI?*jeS`za+Y`1HZbsxY z?zFe3HgiFI2O_K7dGuJ_&B+X6#A7&2I&f%m9Ag;_*8!Ks9Uz`a%a0^ZNzE&}g(yi! zn6=-u^MB1B55g007;TiBkryYy$>C6(EV+$sp1Umd4$dUS1_ck54{A$-itvxb7G1zT zxd(dy;$w`6n20SJVlTXhUiRm3xGc?*@DbId8G)w5% zmhIg(p^~d6`rNOA7cGn&2+3)*1zao+(@=n}zK89pry)+fQ)0MV7V9Rt3cg}^++=!l zU1xQMh=)TR=!83-r_Azqzlu6K=Y^wby;ebX>|$72UqcpMnUgTcVt8N6)orvAB=)Sp zUwjT(X*p4-`&mmndBZM)d$?0c%gDEjt3UH(k}&IEaw}x@jwWS1(k2lQ$Uc{)#LfDD z+pl}1T5olrSCzx^sBe469;rIm!Fxmo%*ouv+Z#hOo-jO|9_6yalb8|L9Ht?-hTsJo zPlttDUhka?Hf4^b>>7%UhJZB);BeX`6XJsD`*1HWi^Zy5~%cNM5EX<7K7fxFu84Z8;L zlEOW+ql|#Fey>Fau4~&}l2@K}pMTe%1BCw8lNJ)jApx*ag`pR0^fv}0q-bTwX1mAN zC3zWQqaOm#NMYR%S(?o~(gqf5CD}8|ct41*bLL>o$|Jd|S=F`y@$yy}WN!z=KCDA= zqlDzGd{KuQ825+uvYn5rUl*zz)Ray$LSU5TdT1tt^ZCy0BO#EZN1bnU-G>84EEe&E zNg0OKTf>4U!o9qTQdK2y$~vOe79Fq!{Q#yW^|aV=*GX^ZMh(lH6XD;!9(OpVOQbv zaLJDSEgwJl0$Oi@;7vh>i|KD4)q{U&alPT=iX#uMKZyV~_6gc;xxSo~o3`+k zj+6NoWPr+Z_?P6nQ<&32l}SFXRN}?chnX8$mf4=Z&%O!Xzl6syR?Ob1d+*x~v$xts zYYKy)^JkVWr*S^W))DUZBQnoPdWF* zo6BM1R6wev0)@hW^*BDl;~)uMIfC_1NPTwWD#Y@Q9kQit&xz_O1>E`2c`3l|kLRwM z(0c0O;wpk}ijoHm+5#^17jW_}m4|b)7klGOe~|0_PG@-1hXqn#uSbfr=7h=sb^5rc zKg}BT130ay4s*?i17l93QBUI(+IU{~*bUIi%^x(`O91Ev*5#DBRPUNWUnlDWb5cUy=cHUTw-axhV+0{xPvxYu+#G$YG^v57V}Ng}g_|;Q%ky_|Am4%gYGOr_>OzY!_+5+JV>#R)*W}26 zw{P7X5;uwyPTxndH|>sAc2E9kA1~yM?8GI6q97l?pih*h0@*2Bcip2WB#pAiGsr@r0ii=k+g8N%VSov&PqTPra{fdrZ$!?`#>rw*T1;X&Joe7w2}WNYzn+7GxS%@x z1vq`(0?tw&#_T=>vdZJ@uoY$a6q_MZfTc}5F*hNPWs2skIPc48(yWvqJ72%78!Gd%41i2*mxocFAdf@9ptjUInRr)Wos~W zHn+Qe$jyJ@;ZC)Yl+#8%j3bfDRD_(qve&N*cTTn4Znu!2DMTjI-f(#pnvW&^Al8**%-M^57gLmQ@4LI;xja$JHQ<1>YwHkiW+1)_Z zKw~Ap>A{~VBGG%jjQ8B|rjZB^uEq%+dO-@P6H$@nbS}v^I-a@zQ>Erc%Wo=z=JXu$ zZmv(vQM(o)e^Grp(pPjk$5G4ltrU1$88}_8@aOP=ZQFvrNNr|If6K&Uu}F6kL#@1 zw~Rrz&srMq+fkOgBxNYLr4Edf!CVe@toH?@Wg=`8q zk;?J-mn?R(ioWf0PIdFQa`<$@I)agKP0aI+POM=rFrxC&pM#AVG19p$B;MSb4U#)C z$eGo-R?g~}fE#bGRyeP_I@t4_%f*Lt!_~olJ-tp)$@&5|+_s1EaJIl{+xgv#fG1s} z1=#PCD1zr4-ld{(G8q2@c?P=^UJ{wu!D+;Yzxb;bUcrMmgY#dzjyLX_yNg&FgULPA zlQ?;+8P9Op(qhhKNN(YhcdK3MhQX%F_(i{eoWWbQ7GATj8ks{jAeT~N^EdYW;a0WI z*V|Q}&hgsWvweBpP+X+ZuUfhceY}w0IW8mCqKtPN1%Hl)h*0z|$hVIU%?yWUCvm9a zDY;h5*e6I^%2b{@KWZG5KfaN}0rtwcmbCGPrIRNU>vw`dxV@1DD3kevl%sgzZ=@zU z$ILAmME9?tk((>HGqHo&y=!5OlQvhhbM}^pYoS<$&8^a?$4J&=>LckSnj;P601wT< zBkmtTHZ*4UJs(CK;UV;9v0VdW!jlZY+fI8IF1?Mnmn5^f(Tp$2H8_aNav12EJ@~`^ znyrS=iet!SzM(3f-d(^7kW9~(bDP3z`tV}J8`N^BPf7GOH}FHXS1t#rAW9)%)mCGv1~U=$vV14azo zGt7>khxLr{at;RKX7M;;DQRNO&&EBx8;mnFT7tppR|N>p^H?EeHnn#=I1+={Ov|-M zq6LJ4+;1rzxdJAn7Vx=PP`?(ujQI##_{PCH0>h1kC>=#6rOQ)l-&a^*l$L&LHpRlVpM?$*m09A{zd zXEG5BTX2exx7L=&khOi~XNfb&_B4oC0P^8Qs1<_yGXD}|K7J64WIZbPna%an5;CPPiz1IS;P z9lJsCPs&<;Gb|&zrJNP6f6>erwA^!>{S^(&XgpxcU!Ph|n{~!>Hzw++lU8Buo6q;* z`D!zrE8J0sh68q_yfxfW$M}3%33I3Qu1(Y%oIN|nYZDrClI8b$ow1T40f_FwM1ho1iH}Oh@dUbMDMoX3qb> z^5OfrvEaPWX_x9St~frVY;lJ-9v_?nguTJaw3}l`u}4;Q@CcO6$dRl9=*){!5lvTtqq4a<~`98#o~8zd_D=bG%y zcV;oq$Cj^@-825Xx?4RPqM&-tE4$TmtUvdMmIjMH4dDN3*V;&FWboD&p~%GR%PFhxQf@!ojWmfu~XmlVIl8G zJ8suoct_0mB&Xr&?)&cbcn|Bo)oNCeH_QtnaU_~x zqi=`qTpSMaZ4;mG>xx_taSu=SVU_D*h{xxu%H%Us%B>vD;?+`b>;*iMn&QwIm1@|d z#SeUEG?#mERxgnAvP7Xm-CJK=MW*=G!TJhto4eP>iQ4!<4If0Tfr%egJBV)v(fdF) zCJ`EY&i;$Ae`FcjA*LZu(nl4_bH<7LS&$2stGl*p&Y(TOP^2?uxVWKeXj>zqjHQXXXFemY1OE`RxVo zCx0{u4j9~q<+agxiwcjyTgpU_1TBWmURDISckh1+WhPEFAEt`ou-{)p`Ah6~`~*Rs z7@LP~QLa32&EO@|XhT=cySqZ?Vr}tcQj)rR*cW4oV?4UsEj4k1U6g+0joyETep1-m z<76K8XmbZl-q0Ndsv-ys^Zi|Zx4MH}AX@e!;(A6w*9&KL@z@gk?w8r78QipFC}Rqtavez&M)Ft7g@ z%W%}myZ!WjMHO$CpifhtbNRWsUcuWJSsku&6Lqh=a3d?vx%@Ea=1u1&Vy#C3?S@4; z8wrnRZCNJ$=iGr=`8SX&J?q;oC*%Y(Jo5qaQk(MWZUi0Y>Q8FDNprb-=lZS(ChFwO z<*wd4rrwTc5cn9JH?}u_%E9x^xQf&1pVsn86RxArJ6WB)30LX2^;w0HH{WXc8(DeY zbgShDAJq186m}-Z&sq9D_cMO^G&5I#{7Yt@@_Z->1p2`!2`Rwepy4eu3d21e>UQc%f?fbrmr`Pj_oZ3?-+S&nY^|9!T#GI6IGsBm8O>rs5^9TY>?(}<@qvI%R`D;NK=@c4V!zvK?e z@FMM8ka@=5ap2!tf1EAop55MrI(Ame3t73bkD21&tmq&AH};4Zm*hOayDbJV`VzhM zhLl0WMw#u=e18smrNu>2f$Y9}|6ig0^>Yr^`bH1CsO&qYtgzezqEVqxnrsdwSe&5pS7~)FQ7B_=CE_hX;!XpnY z1MZwpk1mh+JV)wr8~@Uscn9sgQ@Gln)5jdH1=uL#a-&$rt0E!T0N0V1@g$CU4~J$) zgo5C}AL4AG?N@W{5u2AFF>n+2;n5!I=E%4WYVPhXjR#@&mi1F!`x*F%n#HtLL*fW$ zJ0Zb%e&g_I0-||_=RsO$_=BfAM_8JDNj1iHvRi}v+Mn8adeQG2apdKsSjBsqCcc>F zC)hQ`z(FI3>zz~c-SOUG_zQL{dwF}Ys zBXN_{DEzpgdzIWx-JYDA$mv;^P<|oku5GSu4Z9ogymXh(u5T@0*y?Weu$^#k!au=_ zI6KLn)$J|eCEe~)f3JKE(R>k?!-R%{qoH`mz;;{^gGFM za>?M26=dGZl*mL30Xm1#KM&#@JhhHhJe3v#>#~7`JR?1Lf1rV$i!wOJ0B1_Ms;}8u z_BEV=QLoh-o#&~jx8TmK%}=pR^bYE?_gWk!_V;oy-WqK#Gk`+%Il<|PY-i)-k^Lo9UClvFCl z-Nfa>AB^NE$;#o9qJN3oCU-7#$8W-Sk@H$iyF7He(2bm&fbLx@%$}9iCo5({(956K zCk<^&@bC9VedRXCw-15erq;3Zs=V_U`0YuLcT^{rwfoEHW87cf9<6O%?k>|jxQrXK zYkjT{qi=%sf={4ccicPMN9`_B)UK@eM_n2U-97Q)OESk(>OmCXZIbM5=quMvz4bwF z1u!>@%HFx8*@6S2u%(r#9|?ZFnp+g#ZrUReo;4m6{z`#${p+C4@OhcUFb z-re53h_E*tmHO(#``V9~@^OE)D-ZTQzA@BVa;;{h_5PUk!fsMM(v?171x3Vr8A^SK z6OF6)zj*Z)M=+Tp{%3JeHIsy@Y8U^%yn2=O!BTJi1aU*BeXX3RPCS^0fo9<()Q zqwr(WAIO#?ZNR#pW8K9Jx2vY^6f(+k?3RL3_#k8FHrygJk9 zFXNNaHV#fo+9%_mm9JpCf-*wgh5=my~bVa(@C) z3|g4-zp~}MB_xu76{HJ}-B1KjPuJEx9{y%+&7jQ8@m18j5)geY%4jXTgfgfR)8Xs9 zK8SghdijZ!ahN`yv*nZH(DFBI`A$0Az5nx=%iU>g`4q&;jR8&6R(#2o$#Tf@XRJPJ z@|`q|)<`~MpDVqASxAf>@3SYf-LT!Orp z)J;U;+L`vvrwiXd7jb<$)J?-C|>x(ioxV&iu>fOz^(qytvav!n#FxBxL^g?EmRc3#_cHiU7%`6pH^sa{Bzg?0tP;oJVo@%5jMR zrDigt z+1c6I+1=UQQalqSSjsm|bb#8KEktPxP`8C9c{Z=V3F&+D(cS4}Hw<_(O?0ls_dUX| z%V#&--925qH5uWZXzUeOS2m`7GkwnkryC0_yI;2*W`V!o8ved@i{Z5c-&YV3{Mcqb z+po}tkx$oNczjM5H$c~ZiPjFdIhxLuZIHH6n=Yet4%WsN7Pdlh?xQhDDeZFcPC9D{ z`-2Wnj2;~78yg;`@Fz7iYV5_GxA^`S#!#K54ji|o$9n7%*-#E1(A$s>j^E&zf&%$b zUGxvTY7EAM3_eCd4^Pw~li!B>{QNMsjO7ZQB^*Raa6Ct#{UZ||@)X0_RrHM}pGYX5 z&Hy{*3kh6e=f-w!SjE|LS$UMj^E>R#v2)1l(EJYj6hw{Q+E1b8VPv`Vr z_lwP@%B59Nky(Ewee6%k(mpsVvyl7vRAq)U*|cq1hTr!V6;>5V+Yg^Il*W3hZTr&y zsXWV#b*w{hiSAnsU47J>v-iQ~wW6PWgB-S}hV2-c{lQ(hj4Z^ieRV~R}LQjY$;{UTaWWUp#S;q>-Lks51 zO(61Am^84bT5q=BI1l;W$@0}Al>UUyU9#U%U*aRsIio!09yvHQOh@)19$e;-={zY) z8#$<_dIe`1^>4o8s34x4jY`_Eqt}PCkIKD=2iX1~OI4P%p$U+bRF#$dCMA93;Beni zKgy(as`PCgJAoiJ;FYl^r2U(-uXOe!azjThUyODjir%nmN2BB7w?@8U=Dy?8(YWOZR9_d`_bo6 z-Dn8Wxf$pQ(xuvvukNep+>5s@qcEsc}mf%)L#;OOs=`cuIp$SYnIC)}qpO$$WUb zpBF-{5no>{PhyPh~_7`9jp$SiRW z$$cP4*J;2#M4YFC^Yn|^U}*LL+UXmIOq=_+q+g&ps<-?70%R(FOR3<=`?lU;L^4yS z5+|Fp2K7>|PPe$Tp)_6&4&lyMr(4{`a2m$c7zla!E?1{p+?Jxpvt`-w9H~lI!?wKP zwDMptW$SRuRp}0Q)=$IXv}H^m__*^`=?-_%PqU(O`7T$bJKPq(O_W7c4mVPj?r>-Q zw8?R7FRtLZDqRgr-7orSxAl&as3a}8$*TiOgCL}hlOOU9%t=!db}B`7L)(B!*tT%G z!+q3Gb8@n{J+ONgm(v~YnNXUQlf})$?mApbcRW4lrHM)jzwNN8g2!nntEA0^(zH5B z8rfTihcplGE-y_~N$^@=hXs$*JiKWyO~fO3PgbURc>U`fO;I7iJ71aR;gw*=g%@Lv zZQ1F31wRdFHmx0YUeZMEB)^9$(>%P5SX&o7qjHw!LltQ{KizkoY9YL7t#oGhiTE8Y zAJ)2Bo|-(00kw@|oeO=p{b8@@c%-Publh2Co^)(#68)R@kLJ8}{smT&cBh2{5T95;PrQ(9Kul7E~!JRTZzN6-B&FJ*AQ z$=<*2Jb94Ii`e{;lX=Bl4+$8^t)RQ^n4wvit_SCST8mI)b1z&)7j@;z`zyMo>^`tQ)GUpEoPe%s*=-=he2$Xv_%4zNOY{%B z6+Fd&9KGNOgw>`Dp9?;pMI7Uo>PTctUU&klD+`F_!9vq=X6==>u;|l?MT1gjzJRj< zX#bPBpQk2`HcIBop&y9f|Bm6S$ESzuHS?u-KGR{&{}%bNz_d_|F#b!oYzsMVU95Io z9b8@>W%4&JwAMW%%o^EiS5ayvONFYW8d!Vt$g8xy88wYX+EV0Gdfass3rH?vRVBrS zEx%rmx%$T@FqlN7qf)ntty1@~UXC23D~7S2Avir>V46b{WbkQM9B?GR)=%jCh9?FF zZX4R(Ce8krOge69xY_BpVQ56tf;u$>X<|4x$`e(op+{dWxU;#zqKN-j#Mt+ ziKSf5OUlj8J=Zm>g)Mh1R6H(Lfj;e}f7s>ce?Z1>pmuMA(GgJ*I~Jq0`*$LCj8p_> z$@Ztqu;21s>QU->>3!N}VSaq9uX}9b(BNo4P}4F$@bFzurjZ*uak643u7L{xNaIvH zKVa`6n0>#dhf$wrvT-Drgy-otc4^Z~#Ehf(kp*(R;4er84@%5QcwM;Zq<*Me? zsty>mrM&YW)aAu#>!V(Q6XH8jU;HkLbM>l4(jw?@k>-im)is?Bj5J@w)?c55?~Akw z5xagq10NUZ6)d^*)9_`HUeUd|^vB?rBE6!!SGa~z`%)3#W?=piy?rd#>7QH7mBEIV zE0X`=#>aM-$GqG-=(c#Pp_>TN#TozUV!8kY;kJ0uBrCfeHAd?s@5MY$0xKTS3tl&Q z7Ok7)i-k1)mN^hw-+wpON{y+s@>Zw|@4GZT8>?}k$F(hr)yOAtC{s4$?f2$lvpiU9 zS+V&ap54$=P`kue91hXLSy#C0vLo85=Q36yRatr|8|mR)G{))KM)M0j+jM_2G)4=RG(UR9`u`2tE}8zWOqiG&^YeWV@@0C! zm+x(mDSu}&r1x649&mp8&}f-lb=Nnl3jZF9HOXrYp8AlhgUI$YE-X)GaGD`*tjQKO zY608x@tb|v9qAGsDutWui-qFeE`hbSPVyu^;c0`P0?jefn%t9mj*&|sKDpr^keYpI z>lCwhx0y%dL=ciR8h?9fG;FbQ+7FvgX_EaPn)2TJE3UkoaRgYtP{-JM5;v)T1-#I-hKIJ1m|7bI*e4#TI%X)2wdu(Q& zX9@WqW{?|>X){>&)Ozz+h#q2bi`elWGxL9|kg9Am=M2pYK23C%SY<(wZA`aA@eR0} zSVeF~VE_Le*h;?0KfPl80Ccxrdwnm)fMX*gm^pW6y-m(}`i5>hk<6uY@Xe?m+;Et6 z$0;1^j;<-G_d9($sCSt7L}#ZtbC>I>CXLD}a$~hS^q9YIcnpD7qhmB=1KS0$GvrIn zqCAw0@3ze?qFu>%K@%Lf3+jKN&z>3{9v>KSZIQnJvi{ybbcDX^@$pPVuhpTGpBNmb zcwRkkM)2?BzMnoj_}1f^XTXn44XeP1zlY)JGsPNc-EO^_#uB*CV|0*qO6i+Ca5W*W z@tE3TcqrdUc6k>&6WA;(kvRdb(!sSK1Z-ZoREK;*vgHe_;$#5r?ki|PXKD8aAl|;1 z#VPBlq+X-0?~{3>aIRz?r1L*oJzB(9+Z4(JcWfXYKc2yXj|9gE?3{%MhU|3PVFtIh z2*GRGQ7NzDdqZ}0V^{9@F}jhYhLrZ-q6VproIszZWfGu^lI zeu?SjU7RK+0WdPhT7td-hI~CQW0y|)SSwxfxDov&(yLe}K16@+)i8+k($}Jh{+uy= zGoL$$*t+!i*c1X`5cQ_-^C23ieHc136`IyRFrKGaC!w?8!<03^p1xk{6S+Oo`yn4@ z((dRU6&k1fbVGwP0j|J2=))j<*JH)P%=<9ps|;Z)+3g$?X<=rcugBnAZj+ZTw$6*% zK)*p=vRy_>G8{gX4P55b&4?@{HEKBb_QIA zoWFAcmJ6pCCyTm&u_6RW5+{`S-g4eG?kMp=Js$$L72 zFd>`~6fG7~TCPuxw0H}8z9}?ma%tpAqDltp78%p3F5MdZZPcOxphZi%6zZ0L!MmY zBG(7}vO=!7CMgah#lYu@Ck$#vYcsWTOMVgFwyg^QD1AH0W{chwS>;YH7iQC&*; zmreQEaB&~Xp&Ol&-g>i^52y322%kB^ww~iR(7npsLu7)ep^`-*I zMep;G{`#|{1Ac(yCLMm=<*udVw-@(6-j@uab0&$aiSlAPgdfPQ=D`S;qtj2^ew%+ zxEBF6^!EwySfYs5B1R(k`yKQ*<2XN#=14xE=hY}1-xWX^w6FJwhN;oTy0OKB@Z~@! zWIkoAe;0f-lZRJEO}iiSdPu8i0?tP~@1=W316$ti#k`;3hmRL_G`cYNCoo; zy4JaLzZuCMn6+377JQxVHz6lWS~ISO(@jWZ8DI@qV8YGE$#My?TmmD_M|hpaIMz=y zO^4@_sC~9Xb=%U13xw|@nkniuIqbw-NK_OXxWV@;c%HhHjp3YW*N-{Zh(|PRYS}B| zf7nUl1h)O$tuY99C)!|gX9hlH?9(2~GZ|!~I8-iU_Vl{!R?Ml^JhkW@>Glaa+r?BW z-D@av?14^l8jT#VdZ4e-pgJ()+kj=YZ+ka9aXNn$hZgyCKd3@UHfHIF)~Cl5Iqkv(VHP$BB}jmUrl~a#8hs zs9j%ZY1e5qKH+q;o+$FCIqMI>X82-~v{uL%X`IBpDe{uE2fQ?$rLz%aqkvbLrJe6+ zG2;v(3?pa9pQK~IqVuC;{O$J{9HMiFjLgsEJbxPQe6BN&$Z;%C zlJK|&xyq*LPabJ;t6-V)sAQE^XV3P0ER+w!eG&x^p;^rE4s%x3}Bi+!bZ;`V8~zaX({Mro0k1*e%^CF=8nh7yr@P=3RoY)iyJI3kBm(f=GUrlW z5q`6UCj^-z_s;?9Z%-kw%sQtvgWOJ-?&Z|GKlIXhx&G!_~0%oW1f2G1prVk0g1{doX#u z*U{`KY(qb<^PKx5lP4EaCwGm}Ar}L@Mn`-tF&%6XKt082vmBrLLo$r6T+gN0-tLKR zs?aRfaDo7k*eZ-EKzAN~r)(kky5^2>fl%v<@ZPntW0<+>lKPDp_!OMv=xG1mQEW!IjmfGQ z?Z5hc__kc96pT%q^T7=56Z=Bt_vrX|FBbdA-{n1Iov8vqd2iJjz$W()vYve-KY9`0 za0uhrQRbfdgXZ;L$2WMg-LMCv6I_dYoD6S=Bb@IbT@*%WQ%7B%9Ph^|S9~xH@%9+e zuBZfpr?kuPmOY&V<7Afxrilj8{|V9G9mNq8g;WvWsQgXO0Z%zP%P+P!mEvu34Etli zX7nyzmI~n$9qim3q$9CV3da8`;B>1Ib#Q*`S-^h;)T>M+zKh>FiFH4~)G&b#hSmma z!fbXh2$iS}1A`!WWgr~TsWxrkM|`QPv6c;(Dl;WCo~WIUtsX8PIuob{4RGRM?nggv zU_z#eV1$AIe?yxG`L z+Hb7)BCAo6-veOoSGD|n^NMn5dslh`tsU*^mfelJF-@=SYHU9id3bLu*~M}N+|3kB zWDGC^VxN!n(X$n>dc&)M-k#3vzlHmNF7X|yjm)VK3%35s(0eb9v-RcO4CcIala;;$ zj`)ro(B&g#-fJSG`mNd2JNi+KbPhd6x7({)!det|z3$--TW4=v!;z}69&fbvdl|RIhkGY*GJj@hV4OB=AH(eq0~3AY zLuHRn>a~W>=@6X+?!V2()2U93Gq=SshPKqWs}(MA##qkapAF&T{@g@IG0~AntZ*3L zV}WnnlkkE-sVwOo)z=dtTC~QH!YX_eYaQFDzpGV_ZNf+Mulq9XL=0~_-I-6&T{7t? z2K02#0`-wYeYh|W%XV~|&|xyru1r~>wb7>)&*PrIY%Y`MHKcTO1ba&k)AbNXFk(l= z;JTGEL`)tO@6m@Fh@x0pr4TI~`ISrN)7T-(d8Q}EVh2Rf@}NW^zUD%-Vz~DvmQ8W^e>&QKY_xZT1O=NijVP&Y{pS1uDlYEE z#EMuu@g=%=+NTx6eaHE1iEi%WGFoD85z|r8GX)~|C7(_ld$;4USR!la4B)JEDdP(A z@@2K|eor5;ZkEX>5;#Pi;$G8HpVkWU@@ch&Xr-_|n#L`O1&SfAp%1>WSn?sc9|+Mx zR8}g1(C{>^AJw4cX_c-+B5m4|!#2I%l5sy?4-J?F<_EaX8E~V0Bg)n`K zQtOgyq(JoTF&_ubJDbhKVK!(3fwdV&4)K=IPy;s-q`HVonVUcA=^NsHIy&-+9PU=C zMa#&2>RMCg4LNNcccf6UkIVt|%ajF)nB zZi1t=f{ctxTAAM%qE&z`O(6WKL-&UhunWED9oS&;rB)t?rqy`$cOXOy*P!8U)=~}^ zMiXEC1BVdc1|1JER-vX&7w!nrDj;4ZUCI}XE=a$#3SZ(=-5a8n2w(>~5~ZlN1*Y<9 z6l{#<2&s+F`}pm}STvR@;?!0u6?_b@wc?me@hKnjabl?e8J2K=h1TT?|EI$5*(Lct z;?ro)=W+E%hHtV=M;>8GuZTc^0H?VpG+}J)PZxlUZs0H#Zd1Q>Qm%G?}+Po;Bd1PM~@Qh0?2FZ{3b;kU+dSZN}W zrzTC|75pDZQUqYdmboQ_gX?eN@luM)OhLB%pTiNpuJUoVq;QX65mWQT7s8hvUxCPI zLl{1n%%n?Mcs1DLB+wT2>M(2v?hQl;a=>@Z8BPM>qcsd$OcpycDYArdbi+a!?O5P9 zhT%(DT%5!88}FzIzbOP?C>F4E87B{l>PufNMY6Q?uM5GKN*&Q$DaY6Fp}urT48x0D zB0l`)M4_XzkS|e7@@NZeTL?DS5i8~p=LT3(G#x_~yv($f_7H3-*OAY0Kc6w6B99w= zxGkADu1zaq8zFdwGoZIxF~34L@ZmRSGuYw)i~tukG6%baZo-G#l1z3au(6XGMOdF~ zoo9Ub&81Q?fo4J8gAVi#$vE%BZo%G!4EUxt6xMG`yX3=f?kp5=5KoB=fetwe)kjwd zHkB^mn(QLcCcUnZ@%|7j)U=}j1lDPSv^sIXhuhjon+p;YS*7nz+(??o@KVn`KK$kk zR1SS0MZY=yi3)6g2(~kuO2?vUk~8VQ=9go36rRBlY_1r^ol8Z?0epn=ANJw4#G=s{ z&cK1qxj`U3`&16WCgX)Pb{CTEp!^*Vwk{@p_|1h_vJj8O*=`sqpe;$+rhV8g865Fe z%u?Gkatq1$4j;BXpUI`7DU5WmZ;OVbyx%@uW;5+2LyE?op1P`Ua z(b8G26Ra-QursqE_+%O}W?9_$s9`;LDeFBxJPwP`=U^Vm&gc`HLcE{wVYj4;2{>Cg zE7#~hhsK5A?+w9|-Nj*OWH-dVYdx|0I_JahNR{HaLXu}`WN>HTh4+&_d|M%#K@coW z>hN&Lz)Jr#@553^V>X*hVNgM*C7KH?L~m`s|B#Q94@%iQM*W#2a1iMNC#8>r0^GRe zj1RvfSM0=Y7}zLWS}-}jokIs)f4u0!-jK=WlIb|2^KiJq@HjgarV)u*TnY0IC}}Uz)}*biSm(p;=qTn>9dTTFhqCq$^i}Y2l@HsV%;XZOR3QUc znm$5QfDUdBc>WirMXh}y@2eTs;u%pGw6pq!Jr~vMW zQeDXW#zsTGyNsoqQZJoMhS|9a6l`TFMd5j41B}$kCXII}IZahIG%50B5|vqY7bwp_ za9?h4k)xoYTBc}ns7CEFa`0T}@){7CdAHun15=QoI>DzXjX5Z-=Pq8$h zk~YkcLd9;1CiinuXh8-)C;}tUYc1@P5RExaBVlJ4gDMiV7;X1vFEc!177RbhX*D40 z9gFW_es)DdiViNY(4Q#9a5jX=Y^=h(=>vEfxS0FKBtxJPi=B7~LI z=c~Iw3u(6qZx8sqZBFB+gA|Y3%wWy#SDg>R74n5V{8v3gB>hf7+RuYNEJBYm={QCg zrQq>>Ql%rOa877mC#H5aUE#Io0jan7 zPgLTsY^p1&z~1|F)h9eV1zktO>&)eO1b~^G8v3paZ+-ktK7L!eBUk9acJtuK*bs*v z2B@Yr8w z3dh2f$8i_|-$~C`xwH2!{>`9|b3;B_$`mo|z)5UJ;C#6}?A+C)58qzMCJ|Twx82TA z8-r`%XMFe_C0q%Vh{4>-QmWWzdynpMA0AhRXVR%$Dh4H_**dFxb}1HziT3W=Ho}3H zH19hBq1(CQQz2ThAe&F7it((TcV3MarhR={kNdRZu>iN@VCb6Ad{xYkdULbe=*G_h zEWZ;i>4RE6>9wysu4F)%3NI4QdZ(BMrZ-xaJ8sB5i%y7oGfcR?ps^qbU`nJ^_F5l*TO>pBF zE22|{$vXmPdbM6oZ077Eg0G{a>K72>G&W<(`f^JapKiIlIBbzo#+T8ySQ3*CIPOf_ zb|WL}-!Fe%_$-YNjr5KmTRl6mxn;?(wj{^9A90INzg>+0II2s2v=Mbg#tdM ze0TW#?!am4bIBG?wL zcIz9@`taLus7XAY!(1>d_=K57Qno)j;lu7oq7RSeaC8ZR2}UU>N9xPYdC&RqHzd&$ zb#!Di!4!N_AH(wUfDgZ;kcwj4I<`CSEp!x$H=BhH(d`F)*zM_fHeSMl7UpA)Vn)lb zBCrqnush=UbUGXDOa(aqvv<7Plv3n(+J|k&Szg6_CP{N;xZig37Fp_YV~a%}en%eB zWVv`WuHj{FP53w)f<@R_38&$eG%W6XV|5qUr4Vd3p30z`OBp`+kSjO8cixBHp2U%7 zxL1>Ay}+|@EOeKB*tP_=Hl^Vi1qa8*%!C*8S70yru-jt=lq`$6^!_n8QO0~&ScTwH zIlm*aEuPDEMzc8hh^|zU>aeiQAy_ODK<%)ma0nMdiOyNr79X|^Q-yGYP|r-)*atfv z-{8Y;&Bd~%&N$W;Ace4OulC`##Zny|r4EECO$_2TJW*ni!A2jp4O_rz{xKSm>IkT_ z6@M$XA{pPaclG8AJF`#nxz5jLTRxZWjHlpC4&V%Kk)K`1ZS!HVT#ifUan%Nx!a-Q@ zrVr7m)HH!!(d&ed(~dQ*XaPrd0tL=IMJ$@a*tyY?55GO1!$LSZs~`j|wD||l`DmsCGYwJJzbg68 z_^{hCpWGSEW4%S|V1@lU?!#`2(|Q7~BGo!5gj1%!>Um6fm%UTj(f%RqM4hZ(4sjmD zPntU~bV`%sy9W+k1a>5W0bjxJp}r^hNsRDD`v>}|JzOL&m=(q(4$X*gj1Tq0v)>nb zPk{~%LK6|9WA%F0m*w_+7MEG%utK2wJ0ncd>k~fg4Hyz8i?MvkIZy_i=Z=WSnST`? zwaiwx04#OlFC)Ix{eGUsAl(s-l}eaC=o=owh;l0~ouE}4y|hKXl;Az+=m22U% zxKSBo4Il6gEgLXAZJlo17AO~v?7cDN0ozNBsrt2a~ zoh@5D-VviGABimYVCDhqJWp}#TekQYZ$n9%B9}RfzmMiiu)(cf8eXzasL zSpvZm^H-BI=g0I2e6)}5tKU-|=o>nOohh}^l+9;G@Kze~YQgQXbt3S3(>5tZ%`60d zLEyI00eIO@wvgnxXkato#Z4Y1U>dNe-}AZEe`=axxSQK})s?oNPeCk#|!8u3df}m__8m3QW(M z8Za@+$=%%=xvUxn2jIqV+8Nh{=+x3rFKMT`EHG)^C`G#EFkC5VWc0H9L60$Vop5ir8z zAe#lw+=%YmO#0I+JkM&_p@|AmQ@B86KQC~56WD0S<5G%DAPLxZvuyLtPWWJkL0-e7 z_SO1KBhS5wE_RM82nugK8g61>GCQGNSIJmY6u23o7Y360v29^a;0j}1$rbWk@bV0S znL!_uB*;BNP*JsRF*jf=V zHr~MIKl9T}dsoI~4Raj{$lQob7gvCRRiZW?6}Z-)sZC34rNGUu04MfR+W686a1||_ z4U@F7NQ>s-by6N~ui^46tpH=nV{O*l`r5QA%M-!D|780s^Q6&W=eq z+*n1R^Llh%IE%r=*?v~?+UhaXZqr+A`j${S=`-cA!KQDafhewx zYi2>0)j@P!Y|w&%jgZ<}P&TRTlad@BxAp3PNYqmAZB|ZgVF1WSV5YD1>(qe-?aJnp zv2?aN055&NP2Unqr}j;J_S6(V=ba0w%wFls-vdJh zNPD@*(%I?(xb*vN`j$|-w3m6C?&Kc=gdLFbs~50rO&xeprV^}NHJo1uVPNf3*s)@d zg*WvOhISPJxk(<28V7aZH7TwP8&OLQjK?J;?{%LN+v6i}rRd)UgNtrBRT=Ks_RI=Bm1978ze{>x%R65vju31a8BF(pUJy zd7T3RiVRPO>a2<;RosZsyw1|p{g0nH$y(rUv~ao~s)QpwMO~|mg>&^+#X-B9Ba5{9 zbyx){71=BGQqu5#J=Q{^%CvLWn?$~dCy2CRmkh$*aa=4dktRy~pRE45Ilr>a$M^gF zQiC;537)MkU;iPJKnnr^c|KLU! z_>A+>^!FzI&YNc}>>B>2x8D$^(724hn@v34_qF_8_5wNhpW^Sbr^tPO8h^(!HWmkn zzVLE6e=pOdug$ZWzsVjcN8{=I-R`%-Sz7DXx1*e`yys_<=Hz%8fapIPX*VFPhtoze zfvJ<|`%Zk%^Y?KaUZ}qlKR2?>5{2#~*wNLm^QCW13g3oC%~$t*4SmPr`6%X->DCFM zaT$HbwT)xrbSBsp>t72RdY_Ru*Ryk{>^Z`?O~dFtMPV4bzX)Zz*1+lgLt!|XKs9@b zINyCOP#50U5`yJ@LnI?QSDRq;KA{i{35@wFuZ0g-O~cODarSbu*I`m!tdj4QpTCj_^`{vJASO5^CgJYk%4?R`DWdw}}UdyYalBjNo= z`V7Tu0n1AWI7g7qRNN0+m>M6TV+B$F%`DGyANezU>>>29c|Flb{&sGgQV5r1%Uv@* z$o(xi>Q~*T?A9H~OP`^29)qU)lsDJeM6ADoZp5awVw~R z2ss*bm(6`DBn$5k(nHPD!qckYWZ!BlNuc#wcP8G+%L$fE>CTAXxYr@<87ibHb*Xy94)x1{k*C2kSUeDCT5eq}}pzdt9{-3Qy zUtlFhmEt&^{MO>!{c4^Bht(Q;BOLtWXg?M3)q5O#{-0L`kNTlSDOWop#{;l!6|@0s zW2&u89<2%I;-X1@q96NPYtf{+$WR=#r9J~Y5g5;Eer0{u@o>5(Tt$pB z=t`aUY^+6B>W`NSEv$NJJy>1WLbv&4wP4+XC6!HJ%Qay`F}4=A9k!)f4t_kJliB1e zR)W>KEV4aY8_vX*Ih{X$T`hXD812VSxw0+2vKEeBfFS9T9JY&n!M#>B%C;G;Yr_g?0klPenzY_m4?)ns9tPTnnx;{sfGOZESySO}f}05$+r2YQcH&CR8?|*}SU; zO)|+8JL16&*Mg&L4V?Hoi`8(oV@p6=e8V89!2`vVc=^xOgfX!s9<8-FQJ_NhI)=of zHCYYQKjz{^tbhI}!&!G4N0K62 z*DLJze1h<+zn`x7E@O%tlLT*ke15s{#>qNq8&fOe&DBY}I=wR9i5h9rKMZFG&l}QU zL{b%(0Xa&!&N6)Ma^0C-nTJHKI!(^_UhvaQlm*qDq(?eD+@T4MuAp?`Y0gid;HV0# z({vA^tp_i9!l!?y4})j}-RryjpYYQqQo_r>zthWOh(aG6?xLT@kqn${%N7(WaCJn4 zO@Gi&caab_eG@JLu;m-lVG_P+D}KhInn{d#a^8E(;44_ zSQ-az^73$z4VK2DpUyNSqQT{p*d3zbI&JVG_Vbhv!*oJmR#zf>JX(6S!Sb@~r@@Ly z!~)4vbn#4=hYuJ%(sk)q?eo%|xv=;rS4qF|=KA=HtEBhuuaAGBX8LO^e|NmD7QRbY zSopB|X3OW&?_4Fl{q^@cneN`cL{J zZDyvM@ZBQ&&YgPMpI}6npQwUy@IKsdPbDp{LOgmKdsl*cJxc3%dw}8Cw$nn#&YkwB zgK0fz%dcT%g&TCF4(_Rh;Z<#Cuh;fj8YT}%f5xS0`d*PlhBx*TzO0k(+$q1PEW*P= z1&nFxwjLrFW%@My<}Uf1tcH>1Zt2{3i0IfwMa*P7ZiJV`N*Ke8)OX@=1r47WuYMk? z0VfOztW>~E5flr5>;!2d*#giS2I?CoH@HNXqr%rU;* z2kbd(bLI}W8VoasH4*l+$6FMdmbFOpg57q!f22*t~OVR0d>q7;L`=+=~OE6DCq zm_3eUFMivUujSV~8Rj~I&U~yRlf;fX;*>8xmvUQIH9e5C#!@XLoy)qFf>CvE7d zQjt}%;lph7kv7Z=*L#IVt#S(?uJL-UslVl!S~^d=!+8@O^0jKZkw)(LG>D3hF^F~k3`f^O*Z1Eh9{K?_Gu>a43tWX2i6s|Z{FIEk% z6W%#byag&MaW6}6H8P`d(x~4o(1<3ifY67mTmORRDs}z^6tCj9pYgG=Np3_8L+h!f zD;WkRuA41fcPDoBVABv)k=W(dCZ&E|*;cr7XN9FBSm|r}QMqAQTZ49;2gia`z=SQQ zD~a`ybtMnMC0$;Y*`F~y=Jcn!H^73{+5kt@u0TH0$epDFpoUTk3sUlytuH3ES;?$ zfQt)z*rsm@rAy4rBR1W|t%Lw^7Z=6CupKw$>P~wQC>U+=L2uVy1(&Q1C+&Vl)9WTd z+luSPp_+qZ{ZB#g8)JsQIYZCp%6)TP$`VIEM)IICE|@a5TOxewBM#OyJC1Pdv%oGJ zSU$H9*-)4(lq3Wz?YXs^b?V>%c4k&;RR*+;zc4AZOB#N>_b7{}S{w-U5qXz2?5>E% zGoqXAb|;}ZYv9_#a1x4a_86_>Sb;^>J@nQ!Y=!vJPeNYz`?i8A65qr=+yz?_N`JG> z*sdw9h0sGVJ^CVvR=KYMKj8IE(S1$VfVTLscU9um)FL{E$E-KQ4u$gZOe%SBm0E(V zY&lL<;>tMaHLtNS!P*M?0`np_UOrO^qqV`($iuFMc$C`rcv+}~aU!*J5>Kn3BfMa2 zE;=(OIx{fcH&Eu|uMkYdvHlb{9w9iA!HIi~o!U_W$fnA{En7R%RtwP58wAzMu*X5i zk5_=gu8HlNwe+@D06CaB3$vvPhT0wRKWSl{eX9Znn6!~aBNyDx7#56Y=c-_ntHY`u zZvVR?6`&N#8jan=J7C0C%&rEj_bf~b{6by$eG#!O5=+?At9hxcDHDgjnGJ_VviHrZ zgBoXZ1^=dB+blAPXt<+;xX`8&l>1L8mz>QqC%Q#ruvi>tT^1A3d@_%1uyQbq)PeSn z5aJ~H^ca33WTE=7oe9Uq81+Rb&10`=(WO(8&yFxKsj%qz+RxbjxEcuY#R9h+>XXBu zP+uE|yus4j8U{klyzsTj!fdI6p>mS`wpkdbzm>oulUXB^?sOQ!sV@0>x}USK$<<*O zR+csOimWMOSC$Rz<|?veR+QC~mOj%S2###nP=8eoip)=k>dyyeBgAp*B&(8@Ra+%h z0<>pAuVX)0Y)~aAg$ZjrN$-A3lk3sXoZ#5!A-G8k$8}i+C-!K@!iDRz0>Rp&z{pA0 zsSo99RqE8ja-G(O6`N76Cx2V3YEtTT&cJiMR*8^$;vP#hST3X9W~gyR^A^OMmMMzz%l7!fsg+R_2@* zEv&2i6_GI2Br;vLI%4QP`~ofUmD}Mz z8d{RNb7y^#T3Ma0g)3B$hseH!F?m%UZ+3tpgEL_`ZFw};`nSjTf#wiRfvwBAK?AR*u%z8Ac^OpKw ztc&;NYDCll*&V#p!`!xa;l`BCV=GZ(cu#xiH+&K8ckd95UGj$3h3yQM~&{tI~9A{3qx;=bXhDUdO<{6y@kH z;et-^CvEHz{QelIPfv&0#An-gEIyz68Zes0sr@*qbONW>^&O)k;}VVixD`Z=lmAEM zj->ex@F^_Y;b_%clDfHhs`wj%7jkj(Bw% zrf>D-<1Wdr*-=?Pu0VZgvfMo|*gZHlcH0EL$$o$YZ0E<7rGWhOI6HxYeca0xD3<1F zP78BP>C*4s3!}dV^L|~~E!bO3P0yM-?K}Wh^lbPZ6WYr9qrbSLOB3m9WRphZf7N8mef!$=s*X(oG0uIEFztO_7 zFI@rKj=n+Lki3OsU%CoTbYQ=QbN*Bfsgj>=$I5d?7TTY#Ano~4B0CGuKK07*BoAuS zGlo9<$<^TGDrbp*f?ZstUYr}QCZ8+x|B&huYbj*fI?2wdA!|?m$fWU^hy`TmR)F-zPJLMQo~Z#o-+nUcJd^p-D#4@E+T6{ zEnhOQQ9fkf1U;)EON-OdZ_uu_sl$VlUfYLN>@GlX!rP3{f+eYGG>EFH0}ls&7f^~(-s@96jn6!km-$$9Q84*sU8f$?KD zbKW0Wl)UFXIEuj5wU)q)q#Ba3SL81oEe~d3n88$0$~<2aZqH>;=AeE!^Rf5~qJv8Aeggv6ML)2T*)HS(0FzPh>O^YMR{qba}D_bmdmvBEtS913r za~>J$Py7GD`g;EV73)8P{&mYja@RvDE?67s`7D^Jjg%d*D0Of+IPIK0SgM`fL4@hS}$HhoSWv{#Mj zRGU5rbK1bvFnRDVw63lDIZdaI$+P8a-e6%XOrEFf4yKODbMm<3IhJM(ljq8^>GC^597;DFD9X#^98gQP;le&83>Kbs?+&TSPhjsLmDm#&Ns!!)$TKy}( zkkU0v;h_?wGNaN!9$ zXoVvaKv#U@`4Ig$E*-^{51lC;U%`PzjK>i<9Kpm<4jhc*g)wR0T>OI)q93KK{kSDc z7gr@Eea56ydHwV^nle8Y;;A#p7P7eA1E(EQHY=97g4tC>KzsDR3%!Kf13w-tPSO~Y z>TBJ4vQZKAN0+H2v0fO%wg;*UN_#QhZJd_il8xaoA4--n(`oc6zoDpDBfYq=h?;@b z6Y4*ZW~;gT(D>L$HuH;`%*`h1`*Y-4orz>UFo9x?Ak=^c9!>#ABU>5-!c4s$(VM zP^#Ol&T`}yhtF_88V7i};2Oq|Kr0>8rvHi%0s1e1svR_WFZ+2TE-#ym(goFYbFtAI zT(lC6a(o!)!K%$#eb9n%maJr6D33y@yl#)pyyxi)FZUUm zSNk;M#UPXF%x3ci&G&y-Xnf@{A_6&VObd@?&VP5=Vlx;^bU%Hg{~%h`A~q`ce5lS5 zsj0Hh2LI$&F6cC^4j5C2-d#Xj_=M{A)Wpp>kiLFGNs{}12lUb54BI)1JoHiEktN!c z&U^ly!WVA)8aW7E?ABL>VSVxKM!s{mcy)%$J@R?nUb2E-u()d}M}=OnhH#J}8yeOX zwu;W~hzz^MmY?zYfGsZ-GqH}Wsb{A<^;xLWA{8Aq1sirtLrPM#3AdUROg3G8J_gZfVjzyWHB8}<>?mm zh8hs=7rCyuvTL<6slQ#)FKxme3c1&2bF4FnC2*%qK0!CMRoR#NmB`vtp;0=!ZyPye z{JgTAL1zkAv8H1>K33vYxyy4oJ>}};BZELJO?lflf2}2u|DLVJMJ=$^;xj4qbDt#J zP}OYRxZyl(#3x}RCPw?{s0Q0N&A>*4aFHhc*+$q2#$jvVa30|AEE!YYz@PAzy|sh?>OJt%rw*6?+eI&u8@5duRf-j0VrcYVy0L4okjWhf5VtX7U}; zjP_%=ALMSQm!!|~W_^@eG!{E#xplW^sx4zoyLn4xaxHD)8a1C1HTr;6v#+HT9#Vk1 zESPeh|5d+TM1xX1l~1MV`ePKV!sIy9$}#DFA6t+<_Ckmr`q+3WnJ%PseEo{_G^sYq zlY4mj5h$^ZXZ*T|bp-inF&d5M>GGgxl>HC3-pn4`2C0UpW*akN8+(q5-ax^J*~Uf1 zHqPiY*8Qnr>MylzT=a3K7zsFfJVzJhFD};|cz*+lS7YXlcIGx3oWxtjCUGZbiN?Kg!?bv(4ji@cJp$ZNE!9E1}K1 z?<^CGa~;a@DP>X6Hg(=h!&;7r8=M>)qFwPSDtdQ1q$klJ*4ddz>f2ze_7GI)-J7V| zwT7r&qIZvk=*5H1Y$B1*CFoMYI`lNDO1%^QOXFewQR;-GmHo6SE}TtUs7&+fkaXeG ziZ;+_k@&jsY0PKg^Yoj%x+Puk;92leC!`OOrde=0LF=7<8X=18?+>-7Xpk@BYV2q( zU0+sJZGNN3{=pEvcn~jT3%Nok-O5@=_O;u*$hYOo+CO25=r&jo-HqrpPV(E}jE|!m z0&rX#{7o(2P#bK4?waqRHuxaq{+p(r{5IGFxf=LT8(ik^x?ZIXihT0#@og-0Gu44L zvV!)DMo389mpCm;tMFLq!6Lgd^Kh(}3mQu*5yrLwVP_k(*c z&Q|wc+z{BIaNbp(P#hUX;i_YhE=m=b25(#zg}*~PnHbm+;C_gW_MrfJ?$^(}>K-{b zId-(qYcC71JM&6^2DlwNEbrHSH--&_QFH9jf83Kh#tyk;CY8zKHbY{P<>7?yJTYd~ z)c_|rf%3158#_d0>~NRQ2h9U@#^_FJJ?^|%7OFHsS^D>f=;N;PWJfffO?!U#f2~85 zwm5g7Qr61$hYLDQs=@kREpMp6(PM?IjGIsSJVR}Bm}w~GqS_z77ze+Oe^lT9z1b=> zXXE^E`c5htl`+G4pGS-tO1LpPn>Ie+Q`esgP14!Pn4$Tdp04JiL9Qc_>MWLYdzJ>q zEw@57rD|)roIFpkmonm3%>M{{Eg$i}DmbYN&d<$PK$zHzqwVi9`n}(x_FdInU7vLt zwm1ZX$`Jc7*i31H?r@g*yQAxm4W_Za;c)cWKy-S!toxU~K|Z##mu9_hF}C%wyjK^v zc0Hc#i05*MW+q9p{bE76xhS6b;>z*tsOQK+r+&}SzTnfwwIMOwB^=G;_GqFl^~`3P zZKSIao+{>VoCm9|HMr&qVx6U_jks6cn+`>6@-dRqQB=`Le z@b+1Sr2|7wbZh1dBB!Ih!{xzVa9t7^&gnD|<0Q-5g2S0AWAfR@S+Y2sWN{P%Cuw{O zV(i@eIITKOk}OW(aCkiIkaX8b0Bx!RNM<+q1Hm?PTH&MoAw#dus(vrpF zB#Wc|#FnW{F;qTIt4>3iILYD!4ySh*r}6nXJr;+PEY6I>DUV|Zt&cNjaX881EI6FQ zrb~$YPSzko50rB{)5LRsRE_F97mgY5V|i(IzNrK z%xOeX)Zew4GTIE`8TGgHXik~)Ix6b#aA*yLakTnd92y&G98rIV(+-=&IC|J?aqi{s z#G7cc!?_y$1>UHWop7xB+kQ!5{)usn`ip!9`8(lQ)wek31xKsC%_qN3%41dE z;ylc0Ag5K|;zX_x9IN^kXP(o5qgCJHT=mm}V^!bc+{0;<548$s!!KAItNIpa`e%#` zwCdY@9_8;O1FQNL=K$gZSZ-SN?RSbB;O|=XEv=2{Ep%P?iRxRN`*j*;M5-@!unw^U z4u{p=rro8}1c%ffX=dKyAGPl~G-35OC2h9+J=3>n^W7~t^ZGmJS^c#*k?$KEt-lth zM}G&7)nALVBsf}sEl$hh!kg7!^kJIs1)FB|)Y2>cli|VYsm7Vr-@${`Q;XB`&jv^9 zspTQi-+^QG)Z#1%j@DC)vuts!o?4ulADDc!o?4s*{hjg=6K!!y%LYg5sl}Pq-+^QG z)Z(=Mi^0))YH@n>ci>n(wKz+HqxIC{wEQd6qR!W7D)|yxrmd1TIaZYxF=@yPs>(WP z@ydc{5}}edZ9arY7F<~#m&15g$7!zVx5<86HFmac1~&S)VTZLEyLSE~N-GqY1~DVTmb*=Hkb4ClFe6$(1IH&nLaIAVeoCC18gk#LG#aZC*z_IG>aQ4IA5{@y$)}}th z-<^7k&rNn1<-`QB_$Io~ze|7j)Y|1ic9?KPt%cU4{;oemrRDn*A$X-egmy*t`NTWD zaT(SZ5%ic&6wEpRPc!k*hzChLT;i!Mo~)5kuUSsv5sn*M)e33gnXNbtfGUooVKhB3 zLfGRU8s1L(yd`i)Q9Oq;f6SPXM^zOpgw}?5CW5EdcwEP0OrCEQq*|l(Rgr3~9c?Af zy|LXl4F4B={!`JQvm>33rjyJQe#hLubY>N2(a4aN-i?4)hurA8e$L8QR6V#;U=Td3sTQ zJVM;LNej-C|KX;i^JHv>aU_@}o}p$yJ@deTjKgh?Q%o1Y;D3I@E^L@%BNZ&nczGFp4 zW8i8Ina5A~b&2?)Od^?!r%HMrmy67$Z6oGasa6|@G52Q8AFbb*40B=!%jmy<2mQA& zB5Oq((MSJ|nwHPjZYI#D0^XF!+28Z}yA;*J--Z5^=u6H*zYTpR=*wr@k947r1H8v7 zoDid*!+xC>gLJktR>&LkQK9E-=PLDF>P7Y2`szh}rR95IzEdG6LF%i=eCI+?0+zX? zzApGYp}snExk9Xv(rbb%)>lpTcG>&}{rchz?Hbbz>g#h{Uz!msPt@1vsJ`@PD}Nf- z0^XF!)z=06U5aYqY3xe$sbYwf&`+SgKwm!F`szo00bbXa$y>%o`@!oeT4TmNED{sq*fac%V2Ks?Jp=#rcNKr2AZ}%e!05lPn)s( zVt5*{{MR?BL40ri6Z~Tygruu)z;-H?Z!(|MPky3*5iR;9f9tQ$P-H*oX~lnpH2v?7 z<6r&s?L3eeIC7wKqJQAXmPlmV0X~6oWT5{*JXX4C>y`uf_vWF|XsHCagHwmy-`C^P zI`!W~*E~o;{tyCaRiz&K`-|~!-ltQqPyzY`dT5`{0N$k!{6IY)u&=;>{|yFkgvfsv zo!MfU67TrGYHexlq5j1^Kdsh=Ke#0PI#_^n`#KFU~h0|sPRLbkbwzamb*n;3aEo$ak@6U>#9m(B=WZo1PnU%)J zFuWgW#lEV6iPlZi?b}~|pmV5id~9Ou&}8dAOwRNUx9*vo>K__wZ9lLt&PBfs=YtLm zgNT$~^Ry2ApU9uul6%&xNce+G!mqu1lDi#8;o?uBI{b{N>@hDN%FjK|Qp@g-s$c0u zcBO0%_Ye8G=<>`V{$~tST3bpz$Wndf(=YkHCVHhhZS-jg75#F2+_dGqPQt&}Uw_~r z?ZV}DJbYk!WRi}`InY<0>c*XeM{~Ae8wP zNNfL%NYenm;i!C7OPG^GBLmyp8pH2kIugSLE?_Y{6+Pi_GK9*VZWPVP-jk6?M&?-G7f4o}W{+2a_IJ2fq&Qh5I5+ zAH;h_9wEm@b4e;Ud!b1^h#yqf>+tu~*crOn^L z3(wVRV@11)+#hNBbEI4Zze6~ozMc6OU;w@!Tpf6?jFDyg%g?hfSBFPjR?u)! zGM_V;EBaeg=P;cr`uIP7o7q&Ys+k0cht3wGIzNLtr_Qw=pS_cphRMxBvaTI|1@rLHV!VAw!s_Q*+UcG1)J}*_h&)#_%&YIJ09j>0r z#b#5)C2qS`OBHSX6x#ZA_4!0wZ+Nxq_WtrUDgakB&V3HCXP=iUI(Lws4BimAI`fXR z{5Snm%MXQqmJg2K)=;BFER?a=r5A7F@wt?_eO{*SAOJkGc$1+jtu-zxe%IoWb`hYH zA89C)$cxyR*|bKD;Ro4w8iRaRJr}1@({qiYJ-_tKA)Ojl%lWfv2Y%OW>zAOfoc+Tl z^={-(vNWzIv4v-o>H& z$-aqlV>?O`)BH=;sN;AOk2p@ld#+U_0>IM@A(9*ySC?a>-6BOSEN@w>zJfRL_(Hth z^9s!vK5T*ziN}RCtJZbj;CN$O4e?lhb(4B8-qdDV@&3uy4^orBbDese0PqarKXm14 zewDf|x8Y)ikw%Z;blYBhnNqid2a@HB@OIBD+4j)iXTdMYa%q+Nz7$N7sAl%AQ3KRB zg2xvVL-x5|wHo`g1mi>P=E5qn1dk(QM;bf4BpxHzu2HA(gX;Sw`1`v1eni6|Jg-s@ z8jnNjd+~Vv{suf=l6Dh(6Z0|PhrFt+^Y>#!FYtn*=sMSV)0X}ztK7g zcc`&VM^UD8a4e+0Uut;Jug$i;&p@Wk^6EO-a3+bzp8r^*9>$=IWQiuEZo6=FhvspM zIzaM#UG{9Xf|Zod0pZy+9( z+Nw5Cr;eu`jy&;u;ru#yFDmUvy9f$T6?BVK7z_Sawa(vRbXXPdg02JZBL*KK&6__K2g+lW?` z?WIr!;&tZz&{d-cG=}`?f4h;~c|6eJu~KQLi*|rMa$;y&!Gm+OAH} zL>?a6j~2muL+?5Do;ti9p-B4CzJ@YM@|>Mrqy7v(NS?36-{+5A--Q>R8&uab^)EV) z>Q(r;Y2P{Zfz^1#8fHV8M83o)&Ap#CeA2wqmxp(r#0$?(MSBhC5m`3xIj8Qc%jf7N zk)`?ud_2P=$?Jwl%e$B{^=ftRGxV1;ulbl-zDI!=u@Ra!F)KXF*OHSrUUEIQ#0O#3h~ndBLn3&4N#=^9>I-H$7aB z&r2oChnmzD!y{Gn@XKEFSHvNDP<6u8_q=K2Ew`Lg->AdmC18=;cgrWyw}T_9Z?fHI z&IpfzIzWqtcqkr(b@I#2O@C8|$3usPrzQp)+j5W{7OZiOhmj-og`@xcG}JylQ8kP@ zre_vo57No`(m8d$9*?-Ud7`mH4wmE6Z>&)-GdykwAH$!&>v7@>tLN%C16DcYob@lUdbfc%(ju?CA8LpHuC1cszsw@X^M)zO0iyzwhFF zUJE|{@{#wNy3X0SlyVV|=3c`>MxI?Z*pGr`kx1)Oe;5v_i7n09kg|;d64R zpHB;Js4$T)@j1JxNqq=ENS-nL{ky+NJKyLjsH==T2TdQo|6Fs^L>)eHj#T{CR$$mt=z$F&_Jc`aJfIA3oAhQIZ^cUel!Bg*VAD zf%m81d3*^kJSBC)_>?{1mBttIzw`t3jygP!43G6ScC}B}b*a5cC6PbzNbiq)?<=3f z3(rmJypdz8vE|b@om20v!y`5Bk%_|%m7~Nn*N&0et)`71)PnZ?3UEFB>xesBg-1Hy zrJ+nBU#jmJ_>@0`H_0-M_rG1a`#(3Svtb@1&uLP> zg8Yfc4Br2|u#3Vq@N}v9EA_7yj2o$6yzs?y>Qk%m*x%b&QIZ_zFsh-tq_vf-X36}! zS1p-e7w`saYJQcu_P)W}CMFx)aEQmC>oP^AkmQ)d``~-7B3B5{KGlOdre_|DKiA-W z;jRdw@UudUE`=NSmWLXvm6Yjx@iO&!2#WaZz~7hubCgUXo|~;d8Gv6JL!A9~^J;vS z`wtG^HgyT~Qf+TiF9i?8XCCkW{k+K%UU>GadBfEv@JaKS8y;9yp47xAaPdw<1xjqR z-nT~4^bhgbiT58TX8r>&Jg-w%kyDIk3H45G{1jr0;DdPGgLitzH{XL7o^I7*cpZdXY5sNo?jNY{)|2bl=#lZsz6M)R zQOncscg&@4l6|geR){%{to5&*|~Sna@U=a3su%Z3&Sb5N7WsnR{!wcCGpn zY!AtEFSvX9m48Cb22ZcrZ{)djxtSyUi{_>$>+{)g%S8IQjDYtm@FpIq?k_+2x5m03 zR8N98dYYl1885(bFhY3!I&sGGmzJcD5B@KAnzB}_}yzum^O;X?Bb0fZN zdjAjAbL#PlT_X+d)>qkwF<{3SwqL*gyn1mR9!G~K;1>68X|O3;h0jBZ_QKP%g#XCg z_x!O+ zkJyOE9>f(!@g^S0Ir_+rT}iy~4BGj1G%@z&_P-MIZ*_Pa#;v)Hc957y7!|QY)P|fL z8d9g9$;_%2*dy|b&tq>$whoWdPY#V9!F)$!Yn^zV`RgY2&v=tOZ^ir0myC?zh3Abb zVQgjzb^l6yIkDK>w7U+kZe z_ro@lEDz%EvHnFGV&WOK^Ki$(FV**nziDo|V--H>(A|bsIPxVv7xT{N=)>Qxt&gdD zjXZl$4jQv%UU6Pwh*wuH`^Fn>H8r0z7n&3`6Ov~?-d{TQf8UQ6p0YY``XB|}-Gnc# zCn8P1yc(Zlmu8K(WkZvCE(A+_4uF?y{^+alkm-4onl=7WJNPAgbQZA@AFjja;fZcK zFL12U9un32@*_&EGkhMx-!l(Czl;~2adp=4c^2|~8NRgKeO7&s;F! zg|HLW&mqWcx%h$?-H+{Jtp97O`YV_!rbh8IR&jq8KZ^Ym4 z>wOCiE%6*tbRHxnC%*M{yzm@T8_gO4-KR{0_SQRppq{A1 ztLCw>!%CKCtjWrA6nr>&-mV^d2Js4;4!xu~8THN|sQ+A*$A*eg5s&5m%hccFO>!hh z_?jaHT0+P3^XgHfm&c)(G>0(v(MZ$f_`T8`0_4~~(BRn^;&D0aVmYYO`Qu-D^7rt< zbBCQnptbYs@MRfiV?3)qkAtx34HYEuSh_~{eI&;>;qSY*yyVq*;rRu1>PG#G;uPBO zbMC=N)75o&^xDqgKyPE+IO4SxV<1vL;&mKvHwX7-JMT{Nq&naKF~slGK;8}oQM1K2u#I{~vkH_)Jk%ME?jWt5Zmt?8- ztg(F3T!Q0sMl~BgY5kXUvjuZ-H>}QQL#0VPE+U498a37T6yCq^@Av-*FFe0w=MVz$ zbuGSZz#i#%JswAJgm9x5RFN<7Sw6o;ZN?Ac^9cU_(zaWE4KF-zQCE{QkB91c(we~B>(8lub$FC^ zj^iRP$@<08SDF;fT@$ay6M(gm}+ud+?ll64rMQg2|{yg|`dioCagz-ISU5M)b5=yXRf%B=nV@cKk;_Tb^dj7Jk>- zr#di=sf4!1%8__%j^o@T!{aaFZ}+@gwV{sbnZbW(KC1P{=kTw7R^+j?9eZP ziQ5iu-mQ6gkD|3ndNL3swei6N*gIFJ-AEh9t#6|Tap+Zp#j53s{pc?QFC^Eu;QhM) zyyXjc;kjF#FzvhK6vD_W)1kg zs$}F#z8%ST;ns8NqZh~TB{?@C_-m}I8(!}OKRpI<7hQO^N~#Pi$nzWvkx@k8QRsb5yd*Xv(2H$r}X>DMAnU%UvOhbBf2(p6s# z6)^HA8Ml0R4ZM5J?>q4RgMa=kSIWRrTK zv0ul*$Cs}Es2MZgqlTfY^qhsSLh~atZ~1}xRvjLXVjZE;f@EEN-x`Ijpj_|o1Rv{f z{aA+hLiC^Yq3J$ilH-|ybL#Kv@HjSch%RevXmt~hiyv>YXCJ-`f4^+#KkmZ|&->Nm z#!k(_PSIHTMAtd>Pjz@4o){Ro4X55Wlu6{vI*C0wq=eLl-i`N{zWbdY;f3b|yqA`K z^?)}Thn_ipPF<+OCvNzeoNVwR8eHE9+q?t$5|4L*kNYqG@r#HrNKVmST6!XQvu*lL za}ynmy;2`FG}ibiZsJiPUUnm1#N&JL&Q9KWh(0UzK|W`hev!>2IZnUxoO(t*9aAp{k=*VxOud_NlF@qIOg3P}G3Xnz^$ zbI|vHpVZ*ij<7&OAkwjsLe;Gl(la62{mn{mX762uNag|oS(sUX9IM~NI&O)p&b%=QyC4d(T@Hy(uI4Ae{cT`6TDVw zvHm_u_ahf&#BWB1>6Ec|vJEGZUTi5vJD$KjPf#Li$0al!u=ZUlp{A|Y(q8hti^G&o zdbsiZG`~XpP6#(r#lz}{yx6S{mDBWlIDRFK@?pF91s6h0*J#b@5Ad>KO54R`WgjR+ zIq`T@x{B|`{$hlRVZlY1e17Xo%zM&1`_C7x)uv-VlV0>)dIe0qbFtIpe}yRLi0=L0 z$2V;+wtG@JORjldo7edVML85(8k=oTTiFVvpPG2i{n2ma$Sr`9E!3Wx6z zP)-{3)8cvWJ!CoQ=l#h&Z7=@%9w*j!+K5v*S6~{xIlUVucx@E-G^KaR{%xi0i~ro7 zNkazxPp(l&c@6h6Ls_VtwBG+w{cQ`CP1C*=zyGCoF8{VD_1u44kLdcD|H-w6)-B=U zexFp%l_*oWTnk!J+33#MnWcU2tk@r}hqulJul}e0*$2;Vda;8i@}*3@*h*Hwl*+jZ z)A0od`>&JRr0v0aPA@n5C+#EISDC`b>TKJ44I28wzfh+#8h;McaH~~O#?_cVcx-HW zOz_&QW%$cT`^44oNgBq^NjhcZ?^;Ioq#xP7`27&)lrZfE))Q*aH7L*XjG|Adr1S*T z$jMCUcTYL1h1d8O!QTH(J^UNOjoTjg`NMe%8ISRi@yX^qw*Sd2io>K z%uiVO?Ndzf`dPd0KjODxT%hf2C47fKzw7x=eF0Ble&NNXvU;wC<0?g8+it@2;y?RO zVuF`TODy1fXGQ<5jJK7hp3(09H)VbCMHTI884)g@HMkk&dD`u2NnZ(fYES+9*P4HO zYq6fup6Ha7o{avYS1f8zvo%o0Em_LAON+)i33~DP8iO$_0{b$dmOVWU{C`nKE6&#& z{bSWul;^zbfq(yhx3&juMlTmCL%UnnAJVkkI%UMe!NsG&U+k93x4-JV8m`glWLodH zVY;ttWLr$|+9U5XNaH`P_o)t3SYe%V;(_1EFS?BS-z)6ZlCWQ%+q=KXyFL z{?xGy9lNDt_jT;Cj%nC7fJM9byS1nz)gil%(f7Qe;_~ZQjE6 zzM;yQ65={wN;A~`*2qHLU!2T>^92)1 zNl$%8&6K7GMq?$X>3o0kZp?}}15UJ1Te0>hbG>vGm_h^c8+A$43{AMRiL^(sx8|GAF(zLMeF%fO;hEWG$>oRlyJvK8|B6MwO$FKfzmCXv{F^keH zIPVrMPLU>6yd2(hnwj_B9nWTh{ixWylG#_Xd3EB9)$rY(61Vbt{1c9I!BP|(5}UDu z&4NLqMz%m%-n;hT4qHks`j#+Y?)Dp>v6*8-*TDwE?R#bYCg%D2Lyoi8)-(*u(te+p zRGQ79V=fq-+~hWMKAw1&WGk|18HH;UXMzn>@>{g1E+r$(@mkjPZ|wc;E!o_#P;Kpng=#b0=3Tp> zJ zAF_GhpeJ+$(tLKmpzMPti>CEa?Dbwh*5x<{OkG!YU6^8LbGFYqF4^su2C}(f*tEzx ze6VHkc{Xp5J?&QJ7MlU1URfosaKTTomiv~GImomUWm>b@U=@|PO|8SOvpHbYA-PX< z%sjQ8%>~0xXr*nd*8giZYe#z8aKL)5K1Z_}{#;9el~-)VwagK0F4(^CKwQ1M4BluUJhid-(B$$n@h(+eg8zu&)6lf4X16$$h8kDaGDMn?`M(y zHTB@fY;M>v%upT39*;~~!)8I@LdDr(YFwGOcE~hwn@ri4D9!VuZW=gEQ;?N7*Z(pb z-I(j&?n6QKvv;Sb8~MuFAJedB-)!bM7p$It3?XZH=qc86Ut818#8bu2UG62mh^I8Q z_d}-{>RA^ejxs?fq&B1W;peOg!;<_QpRuj0Qa2s58kqxz&8oECn?9wXL6`?NRk8C= zKWxHgvtv9}?1E_wbsG_v0xO}|Cuts8KPT*E#a_SF{9DY5I5+Iyp20`uG|UUsf%>po z2Pcf{K&IBg3*$PFsdWhT96idPkUpcMVL*Vu#Q_G?q50r&W zEsGh(Wg%0`;)ZdXk*RGK>V8L5&QQ-VBeR_fwDqf)-&kgO6ortbz<$7#>PPlL+Cv&L zg;~-9V;$KVPYoKTgxO#v@t)(xff_@FXDK@KZcNP`uX7oHUmblk(i-!k`*`HIl-0!qZzj7wvT^hvt7YHk&i(`Z$oRQ9)GjvI1%JCxT;}yr zqcG7#oBf4##osPuv6p9WWwT)~2amcC^&@*J@kBN@$8Fq)8Z%UPvWT^(%COnIfo%ub z@n6=)vw2~3B?`yQp1*7{n|2q=4>N3;VdWaIrNHtkaUZT5^oY%IFR&j%`KkLME*Sle zg3~OrWD!OjU)>!K0{fU0m*8D)WAnfYVTR-Wj{Qfj?;7?}(WKJ0*eBz!{e2gvE~DRn z9NvLV`y2gESq`V;x5>KOQJZ8X%}T%Cll9XcYuYt`U!ro}{kggvU%b!IU$9iToE=*% zmHTzZh%j+KXtthFZ*1eT7;qA0nNpVO{}|*p)rAF=Ln#a2V-L-U`$~k#a|u+IUt{MK z=QJ%j!?cG=TQ|I7kn52JJLM@#nwM{_*~D?7o=rpf*>H2Zy#BZ*n8vPYB=>jiFiulg zQiIi;rs1W)?*z!^JiqN>Gs22thT4T}WR-UBvzcMM{K(XG%mS;a#Jzdz>Q0VJff*Gm zHRoXln-iAZ-`14oKj)`LvboTysU5j2Ro=SMh|Orga|yx%)^qqz>)9;%!Zeo>S90`> z57`X)!{jjuAmzFd^1 zOUFXpyMW?C-J6-rj{7zb_2-v}>9ayDS79RRKo9Ul(};4CrG)<|_k}~mv)rrI2(+&;gsfY z_AidIrNDR{BOCnsGnuAC$DFY7_ zVK*_uXoYg?a`V!ttaxCgVmg{`QS(s=P7wR55 zWFA~9`)|L&C9~EJJR4{_=Ii&lDcdvD^I0g)gx}v#C#UvogDF3|MWh+xw>e~9*bsl* zLQKz{s<{9YQ3o4tWI~@&nlw$ccJ*K)%n%pm`z@c}mV6g;jxE<SCPR;!^zEfEJkF z@l*3F9A|0}xQ`dvmRk8}So4(yMvY5lF=F~pt%aR25vJj{aBt&1TjA;NJz@*>J2@(g z4RKBUakS!%^j5ZGBGPohYT-SlNp{=ZH;zqf7#QowR^*74`%*?2k7s1lMosC@aiN|? zMrH9LZab!wUvo@%zw$P1(nVREjl$%)ShDWVdiG<}%z-h4EL;4`dDzUb!kFPSH@E*l zj)PWMNyY9w*(3L_L;c>2%4xuWOa~~GCatj3Y#R)iXk-r9x0tdWdH>b7*sM+e{a%q2 zXKxXx0}V)7&UwJNUkj_fYyhX}g=PBJKeEhqi<70b3hckw@FCw*k@C~<+oimiP&vsC zUhYkY$)XOSe%C~Cp?)hU>hMOG{GNnXpqZt9?!svrE&u*|E{Y5Fdo(gbhd>=@!>8^; zhx%XsUqi27+A0g5gAJyNcJ+FOCKoZ_18so0m}ys#da;WP`( zhzN|yop4_!#p1#{e93VxSSQ6^8+&^doAK>1ZCkcLnjbZ9bb`&(B}^--SlFFXa(krp zz?iJmVRYxZH#yGGGfca#*p5@{X;;-(_ugUJJ|)dI@85XJ=I$4!RaN539scb%Hm!e{ zb~Q3k&W3H*>}3meeI%07%!9+^Ib+)B=e_LuSjNG4-;HeS(CoD8_{upnFt(B%fA`P< zwovzlrTi=u=dS~;BxPJ}KI6EQVPTpVGqn834j;*}g3UfWOrEA7g?*Q?K9boFy7}Q>wA7(I-41W%SJ`JkPW}~WH*~OJ8=9! z)@$pvo@^#qMa)oHESU1MsHmJ)*n0oCM0Wq zPq5`@Gb{?z-cjO`)_;RDN4_{4tbk(b_9zARl485|v^>Ibjvy=9zn$!7q3%yg^)oEN zF_*ue)xi9;_`hWR++T#zSNEb_$kgjmv@gTt^&w>H^(ZD-RV7Zn=ESNKmjbJz#HrV@ zxM0;4Q?FI=>cklxfo&s|Q@tL=tYcPK86`jUdXy9$bHV=cFZX4Aizv>t z9{Uryd~uU8|4QBA(>X5GbJ8i!u(~qI!LOny8>KE$X zw4z1+GWQ?mgTd87d3eTV5SV6_2vxUigc)IK-1ChG z<#y8?6lY(wcreFVgW`&%y-TOseD#B=^_x&|80~5bbHLR4wf%Jq9byP`1?88}>Zg-z zo}m1W<^J#(pty9FsjkabcfU+RJf9{Ju+#EAZK8>)1lw zlZn=qP`?W!Gwl!4PW$(hH(j>w zG%TVD{#3M!12)JXM=QdS4Q1t6Za5OAtyOI8?0#|`bHQjkOJ$+BxY52}+l%_a=%AI1 z4kr1Y(q!h}!n8;J`jK5+SV-n)J|3taS&mVce#S(U(+cZ__nf9Jsp3;MH;fvBZRS?7 zI*2&qiNJaGCGi@3_zlcW5ofXT~yl;AW&J==idOy>jZ(Gbi}x$v8j<3c?b zj^b<=u&;rwDCI{dlADa}N!1l~cckMO&%bXDu4#LM`N@;e(G`h=N zIc8}$!hGM&`z_m=6)W*4BEL}2TodhbJCLR|C{3F;a9ky<*VLN$6Om@9XOB^uhQ9-C z)&ukVZ3BnNI2i9MlHC}4b30oKtSM%weq^?{9H-elK{j}sr8t`@Gi%(im7kVp3-wGd zDrXAfHsKBB7mfK<+q7dGXL%erpGBsAH)w@nH(JIee*NY{jx#C%kyW!`8i=_@rPVa^>-5+%fYS>x1WZ8NOCf83?~c8-c2%4MFKRVc*%?4Yg*ol_sX_e8?RGP6!xIAw|`E`8#c>y*zY;x{E znm2~#f0ZrNvn(l1XIaEyO8dekDZu|CGnWgO-)k4b{Nkh2GKtAdmBM}d-iQ-}00CZP zcGw`iq58dy`R}x14cWXHK+Kq-{EE_t{+CFzO1QXx2gT8{s=D+MTF{rK_myz*yMDG} zmEWdSK;&nv8ZOTnQ{4TT@v<+OVUgG%aomX?j>K}Dr55goj2Sl9_#xl3xnYMDYd`zW zH*BGv%|&&v)D8C?Tc8f_W4_h(N3=;7<@CTd;XUO?)~L*pGi;8ya4pW?m&l4n9qGj8 zi4T|Gg^<-5^_E<3tqsx7{B2G4R<<|fzKzy6TyDe3nnzD7%xPL+w8P0|X~U9D&AW3$7kA=qxTUPikRqJE|(fqqT-rB5hLyE?+`uqAlUaoLC8I>2UZ8m^5} ztarUbHP{@m-ioz#d^eFT)IIHKIfQz~ESal$xYo?yX76IY=Fv@9bA07A;GQkT@Se&^ z=C-!`lg$L<{SdN##^rLI4t1{=%Fl_oahOt?Ut>Cd%<9gVh;}jIS<+vn7SOU_MLE<%@P#Xt?~ux=AvC}L2*+a{eal>j~{D4e*Z>WUn30A0^Ei*zY0Bvfl&ih|eg^Etv9s3`ALA z)(FKam67|OTCZ?<{*&Uato)cZ38E}!7;V%z&9j@@Y-F>-sFSlDzjKf_FCxwfqi3G7 z&3IUj7MU<>QeeM;@*AFUMdoLRUG&Elgf+Q&?E=Ty`eR?i-!4`0;nyWw)@O4J3~W=$ z)ZYSoVXxscDhrwVTVV4bEL$bbPk$WxDyQij9C$tl#i_pqcEeWt%Skru=mL5CXCEF| zMr7(ftrON9pK)2#a%#8_MW}Lyx<>^qTPNah3nr1~X3P)hl>R0rqODD%0?U?6UA8tD zFIzHo*`~mF*^;Tt)&twFtEqdM1GEOfn`f^>aumg<{EtEtORR(Wx_igXPp(U z(V#)?LiYH|CR%+(nl4x!#j3SxBgZAWIAn%m-gGZ=@WrLT4x)i5&1(28-BTP^{>zFNR~*m*$*6ds3R}HS2a*NhMCbX5FC^=hBJu zz?}ZROB*+S76bK(l={(BbjB0MX28Y5wf${IX*NCLq+v~@X;~4DrziySE7D{T%?k6v zO8M(YahctA^kp-w3_KT*tc>x_V>ZjGz_AnAw>2NPVROT9>94HczQ1p8V)LxVGV&X( zYyEdkzsY961CQrohT0_-)BXDc-#a;BZ!4DI%HLYXtqa%az>VWp_u6-u%?-<|*!|&7 zSw9a9U$@G*nl)pNa-4lbAg(1od{Cf+?AIxn)kO-nw+mqhoXd0gt`L$TpJ5{E?tt-rBw5j~!w0dsV4W~SY0?IzW!=*=*o>RPHM758 zlQsCMP*b*0_pPL|IJcrNMf%cQiTMf6CLeK}WqY{1c8l!$ch)swv%|Q}$iBX?UheNY zU{&!Mm6NQP`FHwLQ9pN3no|t7_Ood_!nI-k9cAILO4unGS5q%_0lpMJt|wmsn* zZ45bX-RF&-vUy6F&FJ#Xw=r0U$8H%lqJvZGXvSI`@^;MnBg=(t+enbHp79y zeh8K2XqShj*{rY_C9dI;nZ4NTu**t*9lISGz-Ie3T>d78(lqYO&V2nTZJtD$rr*Nl_4*Wd zAj;j4%?TTd8IF7H+x2JJOzvX=Q(?67f4 zS-!opW(DU*7ve~*{uo*6fYgnyi{SMcvTAj^v3D1l48*M*FYcngd8Lwa; z#lMWm#vR)CF6~sUk#jR*oOJ{ZO89(oEMf4Gt|~(>Tkph8Cmb6rvBE;6l8pl zHOkKdE2^Za<`?Rj#}wzh7B0VE8G-q^b>|nrL@d8h_Zy@*+jZ-?N+{_x+;**+5 zY$-5W&|J>%rz*d9g3*e~X0!~P&2g5$vevJ0=3jEZCk3XK^XJToTR6^eD{Fr15>|Z2 zW`?P8xpFRYv$=xeemOWTJDV4#ma|QxMu}|p+gZyw>X+!JY%Z7@_jR_4)7dQEtZ~PU zYvlTn0#oA})EtqAd)^?ZQm3+KRRQbiT~b22=ZIh4ZtTa-35q&KqQ}mwYbAYty~J z^P@W;F@AnDtq*qCSiGTfk{zA@o!sVV4+3!?2E|!mJid^Po8F@pCZc|!o?%J(S)YY# zhcJjxesmo8LFP!>1^2Z}-UxZzPo^ICr@-pqGmcY_``xe^imAu_Rzrl=H{2ITgX7yX z@7BXa+8umG`O!{B z&Tc2Di-`QR*CVtv|2BZ^@aCQ$vw7M@$n*1LlPh&D%@*o;chuIVpl9I~T{lmzV^$b- zEGi3?ciX7x?Kw>+Y?r^yUdM+yZZ)Ap6H!ikhX@Uq9*Q`!)Wj)rn`-WeXFcFEO0zYl zJ!TY)$3(cq@BBE{ma41pWM2l!dJ4t1bFUd;8y?=_i&$=Lor9M~&RDzFm&< zcGx<9Ta(%H|6GpK4D~D$O4Ebql+Z$4H^J%--~ijci_6WyK=u|0VYejCkgTT9)Vr zOVT*b0?Vhw&CWZbCz}mcRIvfiO2}yf+NR#pJwB&E!inDMFjC#9ceMp#_`PFEEBs zT&2zJhqD=BvoXVEnbKnL1~wOLm6B%m+6QCVYy{0tOvoAzhhT^$Bk-jw7 zBF!qJ=nj_t_8b`~3z=FLJB-Uhrk2GW6sMNO3&SC>>zLOth6wDm_3+8203X)Z;;q}7#Z*GQ!4 zwMS^U<%P7Jm8bm9ri~A5$0#mr?%fG&X4qUMZqM*55p14MBINZi6!-VO0amt@2@&$z zW!fKU-8AJjHaD5S%|6D5AM~vu_caWkM#%4o$kgu&Ot7o?jOy18)A~&}biqW_FVu5O zQFJZTJ<`cSJ>N^jO^=Y*(|(1&-E}I>gNew`0i*pgDht`R^SNm=Bg_jsqu9c^_vQNF zo`LpMtn$(ompRThGeUdS-!7D9r7}6?agHM;(09qkoIBTy<6N+4EEO(G`?>``Vsp=l z&<0?JZN+i79DB@jBeXtBei!?G@D9g0=SOIHat7jzt*75*bA1^>w|S9icB#Csxim+F zc1$VD%1688w%D;eLZf3CE=z$6KgDyLaYcl-Mk!0bUw6rKj_#EaTBZ`$uGll$J=DVR z&%G)_^WY7qIWTk9d^YEr2>Nz2S6Q^{OY$qvxUuj8rx~b zh7K6>CY$kWguG88j!$zqcT3 zu^RqAZ{KAj|&pX*+PRzPX%35LmKIL`ARLOgSi z<7SpSaEHzK5bHZ;*k%+gJd@1=E2&tH^Rdg>Y(dsL_xKOkQi83|6nUQ338RMR{Puq_ z<|U4E2U+Dx)&FGk>X_m0!13l+2$=okjT;a`$=ifviZx=Fs0!m z&GIO8pRmEyHaj?}Lurn4zy@N5^Yg6sPGmDYj?k!a+4@#l@rca9vbkVt zo23o{4h@o^Qk=kH~EJY%!M)UxEc zw@~&MdytJ-)M+!vIf86b!v0-sE?8d7aNSRb#dTtH2U)l8i^_4=6J+_1ydvxF4a)D) z_p$9bO#^O#sn+516;DwjqpPeyy$<4905It+U2*V7zl4vW+ZEB4xfv7^|m;gQ-ACC$n|{J5CS z6cMSdN2$4fAD5nJX7gl=%+erX82ZGAU7! zS~10@=V?s40vbNjoH--K^Ax!(2g}q8oI`@e;&0d*4;V9^<4n0Cvn=ihzU?miLsY0LHY$-u$8upJZ%jSgDSK@{&?eG?x7gj;B>Gzxk*o?We#+5wq zj+e~}Q_FHOxyleWJ524@_kO!e-AA;UBgnd)KPJZ)S5TUh+WjV%gC{6%=lSGqoTe6? zwJZr`vVF#8gsEjIbLQ_lZ04Z2dCxpG*sMWu+t0j5yN;p`_Mo_d&EHwe=74p;3@`VG z&q{A$(_V^{-!p%O4||-iB-bN5j0P!=TXfM$i_Di_o=ADk5XH^$d>F;%4zg^c@4n4u z%o{0x!$@%ztKOAkath3X&p1Cd&ZA={L!`WRgwnh`Z3i9Nh;n*h8GTxuZIQ7()QS2scep-k@CDZ#SPeU{}`JKMj3D&@^?Kg$4{+T*7khAb&A|>rodV$ zabJJ`%Q8;W11qgqzscX=sKB@U3@=B@bCpz7{MMdh-r?C@nbHYaSgVj~_- z{DaL}JW^g~OlhwEvinvx7mUxhk{xSTstcPJMhB*x-!a!UxxLj&WG&~yu3a-Y&ImIp zaV^(WD8*)htx~K-YHBVvtz_0T|GKyOM>Zp@rV=-+&zSyfR#*?k)?VKs%i@N;tJt@V zn|;i2rcznUSzzo&xzA^XRa4@Q3_V?%<5FOBLW0|+VvWwV*gUYNif!-SPp%IpW9a%> zVCq=@diGMZShO(wv%##Ga()d*%A>sU9i66DFIQ9&w-Jtgl9D zCDC|PKeAa`k{k!^rbyp;MVzY?0|W)(MQ!bXHN=$SXb|7EJ6!D*wIyO+phUwTS9kc7$ zBpsWsW3zQERmZ;2vE@3pM#r2wwnfLb>)0+GbHmDFe~b1NCt$kvkJtjVBS!mR^aTUk z2F$K`)e|`Pq}U`_vy-jYQ3%CZVJ~A!X|BXH^=Y^FFcD@~;^<^f^E-9qc@(E&bRy*I zk2lVvIP7wZ7wsdO4JH@9qL42#`dpay|1F9Qar8I*qPS@M<1~Yfju}H)K^>#-dPBt( z*D<4xmD90GIz}hGLY0M%Z9^FyAB3_v9gEj7vyL^_u>>7!qhl5w>!@RiIz}fILbZ!k z$9m~ll8*J)F`JGJ)3Ib78>3@(9h;zIlXYymj-}|>Tpdf*vBf&((6Qw@wo1p=>6lZ; zHtEDW0P^XOQ*j%DcBO&#;<*nJ(#)Ul^JMjOva zIBoxE$}h3~(=qz)FH{^|I}yt0zTcs&ppM1pSaBUQ>R34)tE6M}Jzc0em~<>w$KrG> zUdPNj)?CLDbgYezS#+$UjwR|?7agDVwGOV+V5I%a3XJ|>lk zU$p*BV8gnJ)5`KiHd)7}>sX48&DF6~9b2qp4jo&rW2koi-Q2v{j)~;=2%htR>@rl@xn?C%qSD zH8KbOXAx!=aS1g)qFp^<__<-0G&lY<|2bP#bjk`M&X-^Lb*09$;pa!Tt0*s4VpRunr^rt$uyP{K zBy32w!hP9NVSR-8;zk|KPG3=p`e_&d$_Vqt<$EbtG8;WF(WKZbo*qNk5@F4S`O=Ik zLf`87(p-#pVi{pMP&wahT&fovJUPWrxbcvu}L zTk-6;rT{aPCfW0&=f7pE3M;7C!4DeUW;4UkG$M}T`9;g2d64z)yIuB)gdm%?Kj#{X zrMNahcI%tX`PnQ%_V$7vv)DSqVlYE>pt2Zqv>3?NA68hg#FQkl8d81}U|c_nTlZ=w zI{hQck{Xm|@d}H@`5uaM!l+=JUu-Ha0Qbl3fzen+HW*VwMxoEy4h7j?;b)Gp9fxsy zQrvs<+QqS@1;zDQGp{0>2ZqHf^E>rs0W(_$jFu^vrRCLa!`OGpl~yczc*B((*B@3}u^(S5{5RWj7`HXmugJvFvhLep<&?O~-ai5GETY)VW8zD8n&DTAT*b-oB+|@BPr?X9l#VTobZP7@4 z2TgU~4pZAR*L!6qaGV#`OT?MQa&K_#oGfP|t}vOZSn9$`a%|0jHCJrJ=3nSfHOkMF z3+sepdB0dpmzf9~15?{<=gf8U*&HzHJiPqQ^jysN4>tC(=@^84>>>MnzK%}F0d+!<^OzsMK-$EFRhFmm-b)}WiRTtA4b5IeJ`8!B~5FmSiKs}vDAF|IbpQ&b6MKO z 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�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 @bGV_B~PjJp?9< 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

uo3&{-YDboJ`!ZJ@O}GTJlo@b)@+ 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

PdtF(tILx`ja`>r`Is)gsw>`=^ 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�tDh0;Bknt;YwojqGA46Zk**=AG5ode~Rf2l3 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

XvP{xHXMGXdL%$hA?p?FT(u)x&>-D@4l|7sY@4 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

EdF&b2nP$Ep3F#6~St`o?GNfk!rrbJR2& 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 T3ZV$Tczh9oFCy^&ZUp`hYYgvM literal 0 HcmV?d00001 diff --git a/debug/bpq32.pdb b/debug/bpq32.pdb index 05ac07eb0e5dc60ba8d7facb5b414c0316e78adf..b226d64afea49c0c86f00c1c3c43fabe98a37518 100644 GIT binary patch delta 5364 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 delta 6353 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 -#include "CHeaders.h" +#include "cheaders.h" #include #include diff --git a/lzhuf32.c b/lzhuf32.c index a6828ca..f1ef0a4 100644 --- a/lzhuf32.c +++ b/lzhuf32.c @@ -764,14 +764,14 @@ BOOL CheckifPacket(char * Via) return TRUE; // Packet // ptr1 is last element. If a valid continent, it is a packet message - + // should really accept .WW on end as it is valid if (FindContinent(ptr1)) return TRUE; // Packet if (FindCountry(ptr1)) return TRUE; // Packet - if ((_stricmp(ptr1, "MARS") == 0) || (_stricmp(ptr1, "USA") == 0)) // MARS used both + if ((_stricmp(ptr1, "MARS") == 0) || (_stricmp(ptr1, "USA") == 0) || (_stricmp(ptr1, "WW") == 0)) // MARS used both MARS and USA return TRUE; // Packet return FALSE; diff --git a/mailapi.c b/mailapi.c index 690d2b4..2aa1b9b 100644 --- a/mailapi.c +++ b/mailapi.c @@ -7,7 +7,7 @@ #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers //#include -#include "CHeaders.h" +#include "cheaders.h" #include #include "bpqmail.h" #include "httpconnectioninfo.h" diff --git a/makefile b/makefile index 58534d7..7f870ff 100644 --- a/makefile +++ b/makefile @@ -13,21 +13,21 @@ OBJS = pngwtran.o pngrtran.o pngset.o pngrio.o pngwio.o pngtrans.o pngrutil.o pn MailCommands.o MailDataDefs.o LinBPQ.o MailRouting.o MailTCP.o MBLRoutines.o md5.o Moncode.o \ NNTPRoutines.o RigControl.o TelnetV6.o WINMOR.o TNCCode.o UZ7HODrv.o WPRoutines.o \ SCSTrackeMulti.o SCSPactor.o SCSTracker.o HanksRT.o UIRoutines.o AGWAPI.o AGWMoncode.o \ - DRATS.o FreeDATA.o base64.o Events.o nodeapi.o mailapi.o mqtt.o + DRATS.o FreeDATA.o base64.o Events.o nodeapi.o mailapi.o mqtt.o RHP.o # Configuration: CC = gcc -all: CFLAGS = -DLINBPQ -MMD -g -fcommon +all: CFLAGS = -DLINBPQ -MMD -g -rdynamic -fcommon all: LDFLAGS = -l:libpaho-mqtt3a.a -l:libjansson.a all: linbpq -nomqtt: CFLAGS = -DLINBPQ -MMD -fcommon -g -DNOMQTT +nomqtt: CFLAGS = -DLINBPQ -MMD -fcommon -g -rdynamic -DNOMQTT nomqtt: linbpq -noi2c: CFLAGS = -DLINBPQ -MMD -DNOI2C -g -fcommon +noi2c: CFLAGS = -DLINBPQ -MMD -DNOI2C -g -rdynamic -fcommon noi2c: linbpq diff --git a/makefile~ b/makefile~ new file mode 100644 index 0000000..58534d7 --- /dev/null +++ b/makefile~ @@ -0,0 +1,43 @@ +# LinBPQ Makefile + +# To exclude i2c support run make noi2c + +OBJS = pngwtran.o pngrtran.o pngset.o pngrio.o pngwio.o pngtrans.o pngrutil.o pngwutil.o\ + pngread.o pngwrite.o png.o pngerror.o pngget.o pngmem.o APRSIconData.o AISCommon.o\ + upnp.o APRSStdPages.o HSMODEM.o WinRPR.o KISSHF.o TNCEmulators.o bpqhdlc.o SerialPort.o\ + adif.o WebMail.o utf8Routines.o VARA.o LzFind.o Alloc.o LzmaDec.o LzmaEnc.o LzmaLib.o \ + Multicast.o ARDOP.o IPCode.o FLDigi.o linether.o CMSAuth.o APRSCode.o BPQtoAGW.o KAMPactor.o\ + AEAPactor.o HALDriver.o MULTIPSK.o BBSHTMLConfig.o ChatHTMLConfig.o BBSUtilities.o bpqaxip.o\ + BPQINP3.o BPQNRR.o cMain.o Cmd.o CommonCode.o HTMLCommonCode.o compatbits.o config.o datadefs.o \ + FBBRoutines.o HFCommon.o Housekeeping.o HTTPcode.o kiss.o L2Code.o L3Code.o L4Code.o lzhuf32.o \ + MailCommands.o MailDataDefs.o LinBPQ.o MailRouting.o MailTCP.o MBLRoutines.o md5.o Moncode.o \ + NNTPRoutines.o RigControl.o TelnetV6.o WINMOR.o TNCCode.o UZ7HODrv.o WPRoutines.o \ + SCSTrackeMulti.o SCSPactor.o SCSTracker.o HanksRT.o UIRoutines.o AGWAPI.o AGWMoncode.o \ + DRATS.o FreeDATA.o base64.o Events.o nodeapi.o mailapi.o mqtt.o + +# Configuration: + +CC = gcc + +all: CFLAGS = -DLINBPQ -MMD -g -fcommon +all: LDFLAGS = -l:libpaho-mqtt3a.a -l:libjansson.a +all: linbpq + + +nomqtt: CFLAGS = -DLINBPQ -MMD -fcommon -g -DNOMQTT +nomqtt: linbpq + +noi2c: CFLAGS = -DLINBPQ -MMD -DNOI2C -g -fcommon +noi2c: linbpq + + +linbpq: $(OBJS) + gcc $(OBJS) -Xlinker -Map=output.map -l:libminiupnpc.a -lrt -lm -lz $(LDFLAGS) -lpthread -lconfig -lpcap -o linbpq + sudo setcap "CAP_NET_ADMIN=ep CAP_NET_RAW=ep CAP_NET_BIND_SERVICE=ep" linbpq + +-include *.d + +clean : + rm *.d + rm linbpq $(OBJS) + diff --git a/mqtt.c b/mqtt.c index 4f7571f..ff1c716 100644 --- a/mqtt.c +++ b/mqtt.c @@ -8,7 +8,7 @@ #include #endif -#include "CHeaders.h" +#include "cheaders.h" #include "asmstrucs.h" #include "mqtt.h" diff --git a/nodeapi-skigdebian.c b/nodeapi-skigdebian.c new file mode 100644 index 0000000..a675a9e --- /dev/null +++ b/nodeapi-skigdebian.c @@ -0,0 +1,858 @@ +// basic JASON API to BPQ Node + +// Authentication is via Telnet USER records. + + +#define _CRT_SECURE_NO_DEPRECATE + +#include "cheaders.h" +#include +#include "tncinfo.h" +#include "asmstrucs.h" +#include "kiss.h" + +// Constants +#define TOKEN_SIZE 32 // Length of the authentication token +#define TOKEN_EXPIRATION 7200 // Token expiration time in seconds (2 hours) + +// Token data structure +typedef struct Token { + char token[TOKEN_SIZE + 1]; + time_t expiration_time; + struct Token* next; +} Token; + +typedef struct API +{ + char *URL; + int URLLen; + int (* APIRoutine)(char * response, char * token, char * param); + int Auth; +} API; + +// Auth defines + +#define AuthNone 0 +#define AuthUser 1 +#define Auth BBSUser 2 +#define AuthSysop 4 + +// Function prototypes +void handle_request(SOCKET client_socket, char * request, char * response); +int verify_token(const char* token); +void remove_expired_tokens(); +char* fetch_data(const char* endpoint); +int request_token(char * response); +int send_http_response(char * response, const char* msg); +int create_json_response(char * response, char* access_token, int expires_in, char* scope); +void add_token_to_list(Token* token); + +Token* find_token(const char* token); +Token* generate_token(); + +int sendPortList(char * response, char * token, char * Rest); +int sendNodeList(char * response, char * token, char * Rest); +int sendUserList(char * response, char * token, char * Rest); +int sendInfo(char * response, char * token, char * Rest); +int sendLinks(char * response, char * token, char * Rest); +int sendPortMHList(char * response, char * token, char * Rest); +int sendWhatsPacState(char * response, char * token, char * param); +int sendWhatsPacConfig(char * response, char * token, char * param); + +void BuildPortMH(char * MHJSON, struct PORTCONTROL * PORT); +DllExport struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot); + +// Token list +Token* token_list = NULL; + + +struct API APIList[] = +{ + "/api/ports", 10, sendPortList, 0, + "/api/nodes", 10, sendNodeList, 0, + "/api/info", 9, sendInfo, 0, + "/api/links", 10, sendLinks, 0, + "/api/users", 10, sendUserList, 0, + "/api/mheard", 11, sendPortMHList, 0, + "/api/v1/config", 14, sendWhatsPacConfig, 0, + "/api/v1/state", 13, sendWhatsPacState, 0 +}; + +int APICount = sizeof(APIList) / sizeof(struct API); + +extern int HTTPPort; + + +int xx() +{ + while (1) + { + // Remove expired tokens + remove_expired_tokens(); + + // Handle the client request + // handle_request(); + } + return 0; +} + +int APIProcessHTTPMessage(char * response, char * Method, char * URL, char * request, BOOL LOCAL, BOOL COOKIE) +{ + const char * auth_header = "Authorization: Bearer "; + char * token_begin = strstr(request, auth_header); + char token[TOKEN_SIZE + 1]= ""; + int Flags = 0, n; + + // Node Flags isn't currently used + + char * Tok; + char * param; + + if (token_begin) + { + // Using Auth Header + + // Extract the token from the request (assuming it's present in the request headers) + + if (token_begin == NULL) + { + Debugprintf("Invalid request: No authentication token provided.\n"); + return send_http_response(response, "403 (Forbidden)"); + } + + token_begin += strlen(auth_header); // Move to the beginning of the token + strncpy(token, token_begin, TOKEN_SIZE); + token[TOKEN_SIZE] = '\0'; // Null-terminate the token + + param = strlop(URL, '?'); + } + else + { + // There may be a token as first param, but if auth not needed may be misisng + + Tok = strlop(URL, '?'); + param = strlop(Tok, '&'); + + if (Tok && strlen(Tok) == TOKEN_SIZE) + { + // assume auth token + + strcpy(token, Tok); + } + else param = Tok; + } + + remove_expired_tokens(); // Tidy up + + // Check if the request is for token generation + + if (strcmp(Method, "OPTIONS") == 0) + { + // CORS Request + + char Resp[] = + "HTTP/1.1 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" + "Access-Control-Allow-Headers: authorization"; + + return send_http_response(response, Resp); + } + + if (strcmp(Method, "GET") != 0) + return send_http_response(response, "403 (Bad Method)"); + + if (_stricmp(URL, "/api/request_token") == 0) + return request_token(response); + +/* +if (token[0] == 0) + { + // Extract the token from the request (assuming it's present in the request headers) + if (token_begin == NULL) + { + Debugprintf("Invalid request: No authentication token provided.\n"); + return send_http_response(response, "403 (Forbidden)"); + } + token_begin += strlen(auth_header); // Move to the beginning of the token + strncpy(token, token_begin, TOKEN_SIZE); + token[TOKEN_SIZE] = '\0'; // Null-terminate the token + } + + // Verify the token + if (!verify_token(token)) + { + Debugprintf("Invalid authentication token.\n"); + return send_http_response(response, "401 Unauthorized"); + } + + */ + + // Determine the requested API endpoint + + for (n = 0; n < APICount; n++) + { + struct API * APIEntry; + char * rest; + + APIEntry = &APIList[n]; + + if (_memicmp(URL, APIEntry->URL, APIEntry->URLLen) == 0) + { + rest = &request[4 + APIEntry->URLLen]; // Anything following? + + if (rest[0] == ' ' || rest[0] == '/' || rest[0] == '?') + return APIEntry->APIRoutine(response, token, rest); + } + + } + + return send_http_response(response, "401 Invalid API Call"); +} + +int request_token(char * response) +{ + Token * token = generate_token(); + char scope[] = "create"; + + printf("Token generated: %s\n", token->token); + + sprintf(response, "{\"access_token\":\"%s\", \"expires_at\":%ld,\"scope\":\"create\"}\r\n", + token->token, token->expiration_time); + + return strlen(response); +} + +Token * generate_token() +{ + // Generate a random authentication token + + int i; + + Token * token = malloc(sizeof(Token)); + for (i = 0; i < TOKEN_SIZE; i++) + { + token->token[i] = 'A' + rand() % 26; // Random uppercase alphabet character + } + token->token[TOKEN_SIZE] = '\0'; // Null-terminate the token + token->expiration_time = time(NULL) + TOKEN_EXPIRATION; // Set token expiration time + add_token_to_list(token); + return token; +} + +// Function to add the token to the token_list + +void add_token_to_list(Token* token) +{ + if (token_list == NULL) + { + token_list = token; + token->next = NULL; + } + else + { + Token* current = token_list; + + while (current->next != NULL) + current = current->next; + + current->next = token; + token->next = NULL; + } +} + +int verify_token(const char* token) +{ + // Find the token in the token list + Token * existing_token = find_token(token); + + if (existing_token != NULL) + { + // Check if the token has expired + time_t current_time = time(NULL); + if (current_time > existing_token->expiration_time) + { + // Token has expired, remove it from the token list + remove_expired_tokens(); + return 0; + } + // Token is valid + return 1; + } + + // Token doesn't exist in the token list + return 0; +} + +void remove_expired_tokens() +{ + time_t current_time = time(NULL); + Token* current_token = token_list; + Token* prev_token = NULL; + Token* next_token; + + while (current_token != NULL) + { + if (current_time > current_token->expiration_time) + { + // Token has expired, remove it from the token list + if (prev_token == NULL) + { + token_list = current_token->next; + } else { + prev_token->next = current_token->next; + } + next_token = current_token->next; + free(current_token); + current_token = next_token; + } else { + prev_token = current_token; + current_token = current_token->next; + } + } +} + +Token * find_token(const char* token) +{ + Token* current_token = token_list; + while (current_token != NULL) + { + if (strcmp(current_token->token, token) == 0) + { + return current_token; + } + current_token = current_token->next; + } + return NULL; +} + +int send_http_response(char * response, const char* msg) +{ + return sprintf(response, "HTTP/1.1 %s\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", msg); +} + +/* +{ +"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", +"expires_in":3600, +"scope":"create" +} +*/ + +/* +{"ports":[ +{"ID":"My Port", "Driver":"KISS", "Number":2, "State":"Active"), +{ ...}, +{...} +]} +*/ + +extern int MasterPort[MAXBPQPORTS+1]; // Pointer to first BPQ port for a specific MPSK or UZ7HO host + +int sendPortList(char * response, char * token, char * param) +{ + char * Array = 0; + int ArrayLen = 0; + int ArrayPtr = 0; + + struct _EXTPORTDATA * ExtPort; + struct PORTCONTROL * Port; + struct PORTCONTROL * SAVEPORT; + int PortNo; + + int count; + char DLL[20]; + char Status[32]="Unknown"; + char ID[33]; + char * ptr; + + ArrayPtr += sprintf(&response[ArrayPtr], "{\"ports\":[\r\n"); + + for (count = 1; count <= NUMBEROFPORTS; count++) + { + Port = GetPortTableEntryFromSlot(count); + ExtPort = (struct _EXTPORTDATA *)Port; + PortNo = Port->PORTNUMBER; + + if (Port->PORTTYPE == 0x10) + { + strcpy(DLL, ExtPort->PORT_DLL_NAME); + strlop(DLL, '.'); + strlop(DLL, ' '); + } + else if (Port->PORTTYPE == 0) + strcpy(DLL, "ASYNC"); + + else if (Port->PORTTYPE == 22) + strcpy(DLL, "I2C"); + + else if (Port->PORTTYPE == 14) + strcpy(DLL, "INTERNAL"); + + else if (Port->PORTTYPE > 0 && Port->PORTTYPE < 14) + strcpy(DLL, "HDLC"); + + + if (Port->PortStopped) + { + strcpy(Status, "Stopped"); + + } + else + { + if (Port->PORTTYPE == 0) + { + struct KISSINFO * KISS = (struct KISSINFO *)Port; + NPASYINFO KPort; + + SAVEPORT = Port; + + if (KISS->FIRSTPORT && KISS->FIRSTPORT != KISS) + { + // Not first port on device + + Port = (struct PORTCONTROL *)KISS->FIRSTPORT; + KPort = KISSInfo[PortNo]; + } + + KPort = KISSInfo[PortNo]; + + if (KPort) + { + // KISS like - see if connected + + if (Port->PORTIPADDR.s_addr || Port->KISSSLAVE) + { + // KISS over UDP or TCP + + if (Port->KISSTCP) + { + if (KPort->Connected) + strcpy(Status, "Open "); + else + if (Port->KISSSLAVE) + strcpy(Status, "Listen"); + else + strcpy(Status, "Closed"); + } + else + strcpy(Status, "UDP"); + } + else + if (KPort->idComDev) // Serial port Open + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + + Port = SAVEPORT; + } + } + + if (Port->PORTTYPE == 14) // Loopback + strcpy(Status, "Open "); + + else if (Port->PORTTYPE == 16) // External + { + if (Port->PROTOCOL == 10) // 'HF' Port + { + struct TNCINFO * TNC = TNCInfo[PortNo]; + + if (TNC) + { + switch (TNC->Hardware) // Hardware Type + { + case H_SCS: + case H_KAM: + case H_AEA: + case H_HAL: + case H_TRK: + case H_SERIAL: + + // Serial + + if (TNC->hDevice) + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + break; + + case H_UZ7HO: + + if (TNCInfo[MasterPort[PortNo]]->CONNECTED) + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + break; + + case H_WINMOR: + case H_V4: + + case H_MPSK: + case H_FLDIGI: + case H_UIARQ: + case H_ARDOP: + case H_VARA: + case H_KISSHF: + case H_WINRPR: + case H_FREEDATA: + + // TCP + + if (TNC->CONNECTED) + { + if (TNC->Streams[0].Attached) + strcpy(Status, "In Use"); + else + strcpy(Status, "Open "); + } + else + strcpy(Status, "Closed"); + + break; + + case H_TELNET: + + strcpy(Status, "Open "); + } + } + } + else + { + // External but not HF - AXIP, BPQETHER VKISS, ?? + + struct _EXTPORTDATA * EXTPORT = (struct _EXTPORTDATA *)Port; + + strcpy(Status, "Open "); + } + } + } + + strlop(Status, ' '); + strcpy(ID, Port->PORTDESCRIPTION); + ptr = &ID[29]; + while (*(ptr) == ' ') + { + *(ptr--) = 0; + } + + ArrayPtr += sprintf(&response[ArrayPtr], " {\"ID\":\"%s\", \"Driver\":\"%s\", \"Number\":%d,\"State\":\"%s\"},\r\n", + ID, DLL, Port->PORTNUMBER, Status); + } + + ArrayPtr -= 3; // remove trailing comma + ArrayPtr += sprintf(&response[ArrayPtr], "\r\n]}\r\n"); + + return ArrayPtr; +} + +/* +{"Nodes":[ +{"Call":"xx", "Alias":"xx", "Nbour1 ":"xx", "Quality":192), +{ ...}, +{...} +]} +*/ + +extern int MaxNodes; +extern struct DEST_LIST * DESTS; // NODE LIST +extern int DEST_LIST_LEN; + + +int sendNodeList(char * response, char * token, char * param) +{ + int ArrayPtr = 0; + + int count, len, i; + char Normcall[10], Portcall[10]; + char Alias[7]; + struct DEST_LIST * Dests = DESTS ; + // struct ROUTE * Routes; + + Dests = DESTS; + MaxNodes = MAXDESTS; + + ArrayPtr += sprintf(&response[ArrayPtr], "{\"nodes\":[\r\n"); + + Dests-=1; + + for (count = 0; count < MaxNodes; count++) + { + Dests+=1; + + if (Dests->DEST_CALL[0] == 0) + continue; + + len = ConvFromAX25(Dests->DEST_CALL, Normcall); + Normcall[len] = 0; + + memcpy(Alias, Dests->DEST_ALIAS, 6); + + Alias[6]=0; + + for (i=0;i<6;i++) + { + if (Alias[i] == ' ') + Alias[i] = 0; + } + + + ArrayPtr += sprintf(&response[ArrayPtr], " {\"Call\":\"%s\", \"Alias\":\"%s\", \"Routes\":[", Normcall, Alias); + + + // Add an array with up to 6 objects (3 NR + 3 INP3 Neighbours + + if (Dests->NRROUTE[0].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[0].ROUT_NEIGHBOUR->INP3Node == 0) + { + len = ConvFromAX25(Dests->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len] = 0; + + ArrayPtr += sprintf(&response[ArrayPtr], "{\"Call\":\"%s\", \"Port\":%d, \"Quality\":%d},", + Portcall, Dests->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, Dests->NRROUTE[0].ROUT_QUALITY); + + + // if (Dests->NRROUTE[0].ROUT_OBSCOUNT > 127) + // { + // len=sprintf(&line[cursor],"! "); + // cursor+=len; + // } + + + if (Dests->NRROUTE[1].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[1].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25(Dests->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len]=0; + + + + ArrayPtr += sprintf(&response[ArrayPtr], " {\"Call\":\"%s\", \"Port\":%d, \"Quality\":%d},", + Portcall, Dests->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, Dests->NRROUTE[1].ROUT_QUALITY); + //if (Dests->NRROUTE[1].ROUT_OBSCOUNT > 127) + //{ + //len=sprintf(&line[cursor],"! "); + //cursor+=len; + //} + + } + + if (Dests->NRROUTE[2].ROUT_NEIGHBOUR != 0 && Dests->NRROUTE[2].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25(Dests->NRROUTE[2].ROUT_NEIGHBOUR->NEIGHBOUR_CALL,Portcall); + Portcall[len]=0; + + + ArrayPtr += sprintf(&response[ArrayPtr], " {\"Call\":\"%s\", \"Port\":%d, \"Quality\":%d},", + Portcall, Dests->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, Dests->NRROUTE[1].ROUT_QUALITY); + + //if (Dests->NRROUTE[2].ROUT_OBSCOUNT > 127) + //{ + //len=sprintf(&line[cursor],"! "); + //cursor+=len; + + } + ArrayPtr -= 1; // remove comma + } + + ArrayPtr += sprintf(&response[ArrayPtr], "]},\r\n"); + } + + ArrayPtr -= 3; // remove comma + ArrayPtr += sprintf(&response[ArrayPtr], "\r\n]}"); + + return ArrayPtr; +} + + +int sendUserList(char * response, char * token, char * param) +{ + int ArrayPtr = 0; + int n = MAXCIRCUITS; + TRANSPORTENTRY * L4 = L4TABLE; +// TRANSPORTENTRY * Partner; + int MaxLinks = MAXLINKS; + char State[12] = "", Type[12] = "Uplink"; + char LHS[50] = "", MID[10] = "", RHS[50] = ""; +// char Line[100]; + char Normcall[10]; + int len; + + ArrayPtr += sprintf(&response[ArrayPtr], "{\"users\":[\r\n"); + + while (n--) + { + if (L4->L4USER[0]) + { + RHS[0] = MID[0] = 0; + + len = ConvFromAX25(L4->L4USER, Normcall); + Normcall[len] = 0; + + ArrayPtr += sprintf(&response[ArrayPtr], " {\"Call\": \"%s\"},\r\n", Normcall); + L4++; + } + } + + if (ArrayPtr == 12) //empty list + { + ArrayPtr -=2; + ArrayPtr += sprintf(&response[ArrayPtr], "]}\r\n"); + } + else + { + ArrayPtr -= 3; // remove trailing comma + ArrayPtr += sprintf(&response[ArrayPtr], "\r\n]}\r\n"); + } + return ArrayPtr; +} + +extern char MYALIASLOPPED[]; +extern char TextVerstring[]; +extern char LOCATOR[]; + +int sendInfo(char * response, char * token, char * param) +{ + char call[10]; + + memcpy(call, MYNODECALL, 10); + strlop(call, ' '); + + sprintf(response, "{\"info\":{\"NodeCall\":\"%s\", \"Alias\":\"%s\", \"Locator\":\"%s\", \"Version\":\"%s\"}}\r\n", + call, MYALIASLOPPED, LOCATOR, TextVerstring); + + return strlen(response); +} + +int sendLinks(char * response, char * token, char * param) +{ + struct _LINKTABLE * Links = LINKS; + int MaxLinks = MAXLINKS; + int count; + char Normcall1[10]; + char Normcall2[10]; + char State[12] = "", Type[12] = "Uplink"; + int axState; + int cctType; + int ReplyLen = 0; + ReplyLen += sprintf(&response[ReplyLen],"{\"links\":[\r\n"); + + for (count=0; countLINKCALL[0] != 0) + { + int len = ConvFromAX25(Links->LINKCALL, Normcall1); + Normcall1[len] = 0; + + len = ConvFromAX25(Links->OURCALL, Normcall2); + Normcall2[len] = 0; + + + axState = Links->L2STATE; + + if (axState == 2) + strcpy(State, "Connecting"); + else if (axState == 3) + strcpy(State, "FRMR"); + else if (axState == 4) + strcpy(State, "Closing"); + else if (axState == 5) + strcpy(State, "Active"); + else if (axState == 6) + strcpy(State, "REJ Sent"); + + cctType = Links->LINKTYPE; + + if (cctType == 1) + strcpy(Type, "Uplink"); + else if (cctType == 2) + strcpy(Type, "Downlink"); + else if (cctType == 3) + strcpy(Type, "Node-Node"); + + + + ReplyLen += sprintf(&response[ReplyLen], "{\"farCall\": \"%s\",\"ourCall\": \"%s\", \"port\": \"%d\", \"state\": \"%s\", \"linkType\": \"%s\", \"ax25Version\": \"%d\"},\r\n", + Normcall1, Normcall2, Links->LINKPORT->PORTNUMBER, + State, Type, 2 - Links->VER1FLAG ); + Links+=1; + } + } + + if (ReplyLen < 13) + ReplyLen -= 2; // no links + else + ReplyLen -= 3; // remove trailing comma + + ReplyLen+= sprintf(&response[ReplyLen], "\r\n]}\r\n"); + + return ReplyLen; +} + +int sendPortMHList(char * response, char * token, char * param) +{ + struct PORTCONTROL * PORTVEC ; + int n; + int port = 0; + + if (param[0] = '?' || param[0] == '/') + port = atoi(¶m[1]); + + PORTVEC = GetPortTableEntryFromPortNum(port); + response[0] = 0; + + if (PORTVEC == 0) + return send_http_response(response, "401 Invalid API Call"); + + n = sprintf(response,"{\"mheard\":[\r\n"); + + BuildPortMH(&response[n], PORTVEC ); + + if (response[n] == 0) // No entries + { + response[strlen(response) - 2] = '\0'; // remove \r\n + strcat(response, "]}\r\n"); + } + else + { + response[strlen(response)-3 ] = '\0'; // remove ,\r\n + strcat(response, "\r\n]}\r\n"); +// printf("MH for port %d:\r\n%s\r\n", PORTVEC->PORTNUMBER, response); + } + return strlen(response); +} + +// WhatsPac configuration interface +// WhatsPac also uses Paula's Remote Host Protocol (RHP). This is in a separate module + +extern int WhatsPacConfigured; + + +int sendWhatsPacState(char * response, char * token, char * param) +{ + if (WhatsPacConfigured) + sprintf(response, "{\"configured\": true}\r\n"); + else + sprintf(response, "{\"configured\": false}\r\n"); + + return strlen(response); +} + + +int sendWhatsPacConfig(char * response, char * token, char * param) +{ + char Template[] = + "{\"MODE\": 0," + "\"RHPPORT\": \"%d\"," + "\"AGWPORT\": \"7000\"," + "\"INTERFACES\": [{\"INTERFACE\": 1,\"PROTOCOL\": \"KISS\",\"TYPE\": \"TCP\",\"IOADDR\": \"127.0.0.1\",\"INTNUM\": 8100}]," + "\"PORTS\": [{\"PORT\": 1,\"ID\": \"RHPPORT\", \"INTERFACENUM\": 1}]}"; + + sprintf(response, Template, HTTPPort); + + return strlen(response); +} + + + + diff --git a/nodeapi.c b/nodeapi.c index 0d2fa49..592d92a 100644 --- a/nodeapi.c +++ b/nodeapi.c @@ -5,7 +5,7 @@ #define _CRT_SECURE_NO_DEPRECATE -#include "CHeaders.h" +#include "cheaders.h" #include #include "tncinfo.h" #include "asmstrucs.h" @@ -56,6 +56,8 @@ int sendUserList(char * response, char * token, char * Rest); int sendInfo(char * response, char * token, char * Rest); int sendLinks(char * response, char * token, char * Rest); int sendPortMHList(char * response, char * token, char * Rest); +int sendWhatsPacState(char * response, char * token, char * param); +int sendWhatsPacConfig(char * response, char * token, char * param); void BuildPortMH(char * MHJSON, struct PORTCONTROL * PORT); DllExport struct PORTCONTROL * APIENTRY GetPortTableEntryFromSlot(int portslot); @@ -71,11 +73,14 @@ struct API APIList[] = "/api/info", 9, sendInfo, 0, "/api/links", 10, sendLinks, 0, "/api/users", 10, sendUserList, 0, - "/api/mheard", 11, sendPortMHList, 0 + "/api/mheard", 11, sendPortMHList, 0, + "/api/v1/config", 14, sendWhatsPacConfig, 0, + "/api/v1/state", 13, sendWhatsPacState, 0 }; int APICount = sizeof(APIList) / sizeof(struct API); +extern int HTTPPort; int xx() @@ -817,6 +822,37 @@ int sendPortMHList(char * response, char * token, char * param) return strlen(response); } +// WhatsPac configuration interface +// WhatsPac also uses Paula's Remote Host Protocol (RHP). This is in a separate module + +extern int WhatsPacConfigured; + + +int sendWhatsPacState(char * response, char * token, char * param) +{ + if (WhatsPacConfigured) + sprintf(response, "{\"configured\": true}\r\n"); + else + sprintf(response, "{\"configured\": false}\r\n"); + + return strlen(response); +} + + +int sendWhatsPacConfig(char * response, char * token, char * param) +{ + char Template[] = + "{\"MODE\": 0," + "\"RHPPORT\": \"%d\"," + "\"AGWPORT\": \"7000\"," + "\"INTERFACES\": [{\"INTERFACE\": 1,\"PROTOCOL\": \"KISS\",\"TYPE\": \"TCP\",\"IOADDR\": \"127.0.0.1\",\"INTNUM\": 8100}]," + "\"PORTS\": [{\"PORT\": 1,\"ID\": \"RHPPORT\", \"INTERFACENUM\": 1}]}"; + + sprintf(response, Template, HTTPPort); + + return strlen(response); +} + diff --git a/pibits.c b/pibits.c index b839131..fdefcf9 100644 --- a/pibits.c +++ b/pibits.c @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses */ -#include "CHeaders.h" +#include "cheaders.h" #include "tncinfo.h"