From b77dbd8ce169fc6e575b3f471150b14832e6e991 Mon Sep 17 00:00:00 2001 From: John Wiseman Date: Sat, 29 Jul 2023 07:23:11 +0100 Subject: [PATCH] 6.0.23.81 --- BBSUtilities.c | 7 +- BBSUtilities.c.bak | 29852 ++++++++++----------- BPQMail.c | 6 +- BPQMail.c.bak | 7302 ++--- BPQMail.vcxproj | 690 +- BPQMail.vcxproj.filters | 224 +- BPQMail.vcxproj.user | 6 +- Bpq32.c | 7 +- CBPQ32.vcproj | 16 + CBPQ32.vcproj.DESKTOP-MHE5LO8.johnw.user | 65 + HTTPcode.c | 2 +- HTTPcode.c.bak | 4849 ++++ KernelScript1.rc | 2 +- LinBPQ.c | 5 +- LinBPQ.c~ | 3950 +-- MCP2221.c | 427 + MCP2221.vcproj | 203 + MCP2221.vcproj.DESKTOP-TGEL8RC.John.user | 65 + MailNode.vcxproj.filters | 646 +- MailNode.vcxproj.user | 14 +- PG/Loop.c | 46 +- PG/PGTest.c | 104 +- RigControl.c | 9 +- SCSPactor.c | 128 +- SCSTracker.c | 11 +- TelnetV6.c | 2 +- UIRoutines.c | 102 +- UZ7HODrv.c | 16 + VARA.c | 7 + Versions.h | 6 +- bpqmail.h | 2 + bpqmail.h.bak | 3208 +-- cMain.c | 9 + debug/BuildLog.htm | Bin 0 -> 6660 bytes debug/MCP2221.exe.embed.manifest | 8 + debug/MCP2221.exe.embed.manifest.res | Bin 0 -> 468 bytes debug/MCP2221.exe.intermediate.manifest | 8 + debug/MCP2221.obj | Bin 0 -> 34772 bytes debug/bpq32.pdb | Bin 0 -> 1584128 bytes debug/hid.obj | Bin 0 -> 53913 bytes debug/mt.dep | 1 + debug/vc80.idb | Bin 0 -> 257024 bytes debug/vc80.pdb | Bin 0 -> 77824 bytes kiss.c | 6 +- tncinfo.h | 4 +- 45 files changed, 28927 insertions(+), 23088 deletions(-) create mode 100644 CBPQ32.vcproj.DESKTOP-MHE5LO8.johnw.user create mode 100644 HTTPcode.c.bak create mode 100644 MCP2221.c create mode 100644 MCP2221.vcproj create mode 100644 MCP2221.vcproj.DESKTOP-TGEL8RC.John.user create mode 100644 debug/BuildLog.htm create mode 100644 debug/MCP2221.exe.embed.manifest create mode 100644 debug/MCP2221.exe.embed.manifest.res create mode 100644 debug/MCP2221.exe.intermediate.manifest create mode 100644 debug/MCP2221.obj create mode 100644 debug/bpq32.pdb create mode 100644 debug/hid.obj create mode 100644 debug/mt.dep create mode 100644 debug/vc80.idb create mode 100644 debug/vc80.pdb diff --git a/BBSUtilities.c b/BBSUtilities.c index 487b6a9..1d82b8c 100644 --- a/BBSUtilities.c +++ b/BBSUtilities.c @@ -9668,7 +9668,7 @@ VOID SaveConfig(char * ConfigName) // Save UI config - for (i=1; i<=32; i++) + for (i=1; i <= GetNumberofPorts(); i++) { char Key[100]; @@ -10192,7 +10192,7 @@ BOOL GetConfig(char * ConfigName) GetStringValue(group, "Version", Size); sscanf(Size,"%d,%d,%d,%d", &LastVer[0], &LastVer[1], &LastVer[2], &LastVer[3]); - for (i=1; i<=32; i++) + for (i =1 ; i <= GetNumberofPorts(); i++) { char Key[100]; @@ -10654,7 +10654,8 @@ int Disconnected (int Stream) } /* ---- TAJ PG SERVER ---- */ - if ( conn->UserPointer->Temp->RUNPGPARAMS ) { + if (conn->UserPointer && conn->UserPointer->Temp && conn->UserPointer->Temp->RUNPGPARAMS) + { printf("Freeing RUNPGPARAMS\n"); free(conn->UserPointer->Temp->RUNPGPARAMS); conn->UserPointer->Temp->RUNPGPARAMS = NULL; diff --git a/BBSUtilities.c.bak b/BBSUtilities.c.bak index b60c77d..c5c7e48 100644 --- a/BBSUtilities.c.bak +++ b/BBSUtilities.c.bak @@ -1,14926 +1,14926 @@ -/* -Copyright 2001-2018 John Wiseman G8BPQ - -This file is part of LinBPQ/BPQ32. - -LinBPQ/BPQ32 is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -LinBPQ/BPQ32 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses -*/ - -// Mail and Chat Server for BPQ32 Packet Switch -// -// Utility Routines - -#include "bpqmail.h" -#ifdef WIN32 -#include "Winspool.h" -#else -#include -#endif - - -BOOL Bells; -BOOL FlashOnBell; // Flash instead of Beep -BOOL StripLF; - -BOOL WarnWrap; -BOOL FlashOnConnect; -BOOL WrapInput; -BOOL CloseWindowOnBye; - -RECT ConsoleRect; - -BOOL OpenConsole; -BOOL OpenMon; - -int reportNewMesageEvents = 0; - - -extern struct ConsoleInfo BBSConsole; - -extern char LOC[7]; - -//#define BBSIDLETIME 120 -//#define USERIDLETIME 300 - - -#define BBSIDLETIME 900 -#define USERIDLETIME 900 - -#ifdef LINBPQ -extern BPQVECSTRUC ** BPQHOSTVECPTR; -UCHAR * GetLogDirectory(); -DllExport int APIENTRY SessionStateNoAck(int stream, int * state); -int RefreshWebMailIndex(); -#else -__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; -typedef char * (WINAPI FAR *FARPROCZ)(); -typedef int (WINAPI FAR *FARPROCX)(); -FARPROCZ pGetLOC; -FARPROCX pRefreshWebMailIndex; - -#endif - -Dll BOOL APIENTRY APISendAPRSMessage(char * Text, char * ToCall); -VOID APIENTRY md5 (char *arg, unsigned char * checksum); -int APIENTRY GetRaw(int stream, char * msg, int * len, int * count); -void GetSemaphore(struct SEM * Semaphore, int ID); -void FreeSemaphore(struct SEM * Semaphore); -int EncryptPass(char * Pass, char * Encrypt); -VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); -void DeletetoRecycle(char * FN); -VOID DoImportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID DoExportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID TidyPrompts(); -char * ReadMessageFileEx(struct MsgInfo * MsgRec); -char * APIENTRY GetBPQDirectory(); -BOOL SendARQMail(CIRCUIT * conn); -int APIENTRY ChangeSessionIdletime(int Stream, int idletime); -int APIENTRY GetApplNum(int Stream); -VOID FormatTime(char * Time, time_t cTime); -BOOL CheckifPacket(char * Via); -char * APIENTRY GetVersionString(); -void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename); -void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); -int GetCMSHash(char * Challenge, char * Password); -BOOL SendAMPRSMTP(CIRCUIT * conn); -VOID ProcessMCASTLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen); -VOID MCastTimer(); -VOID MCastConTimer(ConnectionInfo * conn); -int FindFreeBBSNumber(); -VOID DoSetMsgNo(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -BOOL ProcessYAPPMessage(CIRCUIT * conn); -void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); -void YAPPSendData(ConnectionInfo * conn); -VOID CheckBBSNumber(int i); -struct UserInfo * FindAMPR(); -VOID SaveInt64Value(config_setting_t * group, char * name, long long value); -VOID SaveIntValue(config_setting_t * group, char * name, int value); -VOID SaveStringValue(config_setting_t * group, char * name, char * value); -char *stristr (char *ch1, char *ch2); -BOOL CheckforMessagetoServer(struct MsgInfo * Msg); -void DoHousekeepingCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled); -void ListCategories(ConnectionInfo * conn); -void RebuildNNTPList(); -long long GetInt64Value(config_setting_t * group, char * name); -void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); -int ReformatSyncMessage(CIRCUIT * conn); -char * initMultipartUnpack(char ** Input); -char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg); -int decode_quoted_printable(char *ptr, int len); -void decodeblock( unsigned char in[4], unsigned char out[3]); -int encode_quoted_printable(char *s, char * out, int Len); -int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress); -int APIENTRY ChangeSessionCallsign(int Stream, unsigned char * AXCall); - -config_t cfg; -config_setting_t * group; - -extern ULONG BBSApplMask; - -//static int SEMCLASHES = 0; - -char SecureMsg[80] = ""; // CMS Secure Signon Response - -int NumberofStreams; - -extern char VersionStringWithBuild[50]; - -#define MaxSockets 64 - -extern struct SEM OutputSEM; - -extern ConnectionInfo Connections[MaxSockets+1]; - -extern struct UserInfo ** UserRecPtr; -extern int NumberofUsers; - -extern struct UserInfo * BBSChain; // Chain of users that are BBSes - -extern struct MsgInfo ** MsgHddrPtr; -extern int NumberofMessages; - -extern int FirstMessageIndextoForward; // Lowest Message wirh a forward bit set - limits search - -extern char UserDatabaseName[MAX_PATH]; -extern char UserDatabasePath[MAX_PATH]; - -extern char MsgDatabasePath[MAX_PATH]; -extern char MsgDatabaseName[MAX_PATH]; - -extern char BIDDatabasePath[MAX_PATH]; -extern char BIDDatabaseName[MAX_PATH]; - -extern char WPDatabasePath[MAX_PATH]; -extern char WPDatabaseName[MAX_PATH]; - -extern char BadWordsPath[MAX_PATH]; -extern char BadWordsName[MAX_PATH]; - -extern char BaseDir[MAX_PATH]; -extern char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% -extern char ProperBaseDir[MAX_PATH]; // BPQ Directory/BPQMailChat - - -extern char MailDir[MAX_PATH]; - -extern BIDRec ** BIDRecPtr; -extern int NumberofBIDs; - -extern BIDRec ** TempBIDRecPtr; -extern int NumberofTempBIDs; - -extern WPRec ** WPRecPtr; -extern int NumberofWPrecs; - -extern char ** BadWords; -extern int NumberofBadWords; -extern char * BadFile; - -extern int LatestMsg; -extern struct SEM MsgNoSemaphore; // For locking updates to LatestMsg -extern int HighestBBSNumber; - -extern int MaxMsgno; -extern int BidLifetime; -extern int MaxAge; -extern int MaintInterval; -extern int MaintTime; - -extern int ProgramErrors; - -extern BOOL MonBBS; -extern BOOL MonCHAT; -extern BOOL MonTCP; - -BOOL SendNewUserMessage = TRUE; -BOOL AllowAnon = FALSE; -BOOL UserCantKillT = FALSE; - -typedef int (WINAPI FAR *FARPROCX)(); -FARPROCX pRunEventProgram; - -int RunEventProgram(char * Program, char * Param); - - -extern BOOL EventsEnabled; - -#define BPQHOSTSTREAMS 64 - -// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 - -extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; - -#ifdef LINBPQ -extern BPQVECSTRUC ** BPQHOSTVECPTR; -extern char WL2KModes [54][18]; -#else -__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; - - -char WL2KModes [54][18] = { - "Packet 1200", "Packet 2400", "Packet 4800", "Packet 9600", "Packet 19200", "Packet 38400", "High Speed Packet", "", "", "", "", - "", "Pactor 1", "", "", "Pactor 2", "", "Pactor 3", "", "", "Pactor 4", // 10 - 20 - "Winmor 500", "Winmor 1600", "", "", "", "", "", "", "", // 21 - 29 - "Robust Packet", "", "", "", "", "", "", "", "", "", // 30 - 39 - "ARDOP 200", "ARDOP 500", "ARDOP 1000", "ARDOP 2000", "ARDOP 2000 FM", "", "", "", "", "", // 40 - 49 - "VARA", "VARA FM", "VARA FM WIDE", "VARA 500"}; -#endif - - - - - -FILE * LogHandle[4] = {NULL, NULL, NULL, NULL}; - -time_t LastLogTime[4] = {0, 0, 0, 0}; - -char FilesNames[4][100] = {"", "", "", ""}; - -char * Logs[4] = {"BBS", "CHAT", "TCP", "DEBUG"}; - - -BOOL OpenLogfile(int Flags) -{ - UCHAR FN[MAX_PATH]; - time_t LT; - struct tm * tm; - - LT = time(NULL); - tm = gmtime(<); - - sprintf(FN,"%s/logs/log_%02d%02d%02d_%s.txt", GetLogDirectory(), tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Logs[Flags]); - - LogHandle[Flags] = fopen(FN, "ab"); - -#ifndef WIN32 - - if (strcmp(FN, &FilesNames[Flags][0])) - { - UCHAR SYMLINK[MAX_PATH]; - - sprintf(SYMLINK,"%s/logLatest_%s.txt", GetBPQDirectory(), Logs[Flags]); - unlink(SYMLINK); - strcpy(&FilesNames[Flags][0], FN); - symlink(FN, SYMLINK); - } - -#endif - - return (LogHandle[Flags] != NULL); -} - -typedef int (WINAPI FAR *FARPROCX)(); - -extern FARPROCX pDllBPQTRACE; - -struct SEM LogSEM = {0, 0}; - -void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags) -{ - char CRLF[2] = {0x0d,0x0a}; - struct tm * tm; - char Stamp[20]; - time_t LT; -// struct _EXCEPTION_POINTERS exinfo; - - // Write to Node BPQTRACE system - - if ((Flags == LOG_BBS || Flags == LOG_DEBUG_X) && MsgLen < 250) - { - MESSAGE Monframe; - memset(&Monframe, 0, sizeof(Monframe)); - - Monframe.PORT = 64; - Monframe.LENGTH = 12 + MsgLen; - Monframe.DEST[0] = 1; // Plain Text Monitor - - memcpy(&Monframe.DEST[1], Msg, MsgLen); - Monframe.DEST[1 + MsgLen] = 0; - - time(&Monframe.Timestamp); -#ifdef LINBPQ - GetSemaphore(&Semaphore, 88); - BPQTRACE(&Monframe, FALSE); - FreeSemaphore(&Semaphore); -#else - if (pDllBPQTRACE) - pDllBPQTRACE(&Monframe, FALSE); -#endif - } -#ifndef LINBPQ - __try - { -#endif - - - -#ifndef LINBPQ - - if (hMonitor) - { - if (Flags == LOG_TCP && MonTCP) - { - WritetoMonitorWindow((char *)&Flag, 1); - WritetoMonitorWindow(Msg, MsgLen); - WritetoMonitorWindow(CRLF , 1); - } - else if (Flags == LOG_CHAT && MonCHAT) - { - WritetoMonitorWindow((char *)&Flag, 1); - - if (conn && conn->Callsign[0]) - { - char call[20]; - sprintf(call, "%s ", conn->Callsign); - WritetoMonitorWindow(call, 10); - } - else - WritetoMonitorWindow(" ", 10); - - WritetoMonitorWindow(Msg, MsgLen); - if (Msg[MsgLen-1] != '\r') - WritetoMonitorWindow(CRLF , 1); - } - else if (Flags == LOG_BBS && MonBBS) - { - WritetoMonitorWindow((char *)&Flag, 1); - if (conn && conn->Callsign[0]) - { - char call[20]; - sprintf(call, "%s ", conn->Callsign); - WritetoMonitorWindow(call, 10); - } - else - WritetoMonitorWindow(" ", 10); - - WritetoMonitorWindow(Msg, MsgLen); - WritetoMonitorWindow(CRLF , 1); - } - else if (Flags == LOG_DEBUG_X) - { - WritetoMonitorWindow((char *)&Flag, 1); - WritetoMonitorWindow(Msg, MsgLen); - WritetoMonitorWindow(CRLF , 1); - } - } -#endif - - if (Flags == LOG_TCP && !LogTCP) - return; - if (Flags == LOG_BBS && !LogBBS) - return; - if (Flags == LOG_CHAT && !LogCHAT) - return; - - GetSemaphore(&LogSEM, 0); - - if (LogHandle[Flags] == NULL) - OpenLogfile(Flags); - - if (LogHandle[Flags] == NULL) - { - FreeSemaphore(&LogSEM); - return; - } - LT = time(NULL); - tm = gmtime(<); - - sprintf(Stamp,"%02d%02d%02d %02d:%02d:%02d %c", - tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, Flag); - - fwrite(Stamp, 1, strlen(Stamp), LogHandle[Flags]); - - if (conn && conn->Callsign[0]) - { - char call[20]; - sprintf(call, "%s ", conn->Callsign); - fwrite(call, 1, 10, LogHandle[Flags]); - } - else - fwrite(" ", 1, 10, LogHandle[Flags]); - - fwrite(Msg, 1, MsgLen, LogHandle[Flags]); - - if (Flags == LOG_CHAT && Msg[MsgLen-1] == '\r') - fwrite(&CRLF[1], 1, 1, LogHandle[Flags]); - else - fwrite(CRLF, 1, 2, LogHandle[Flags]); - - // Don't close/reopen logs every time - -// if ((LT - LastLogTime[Flags]) > 60) - { - LastLogTime[Flags] = LT; - fclose(LogHandle[Flags]); - LogHandle[Flags] = NULL; - } - FreeSemaphore(&LogSEM); - -#ifndef LINBPQ - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } -#endif -} - -int CriticalErrorHandler(char * error) -{ - Debugprintf("Critical Error %s", error); - ProgramErrors = 25; - CheckProgramErrors(); // Force close - return 0; -} - -BOOL CheckForTooManyErrors(ConnectionInfo * conn) -{ - conn->ErrorCount++; - - if (conn->ErrorCount > 4) - { - BBSputs(conn, "Too many errors - closing\r"); - conn->CloseAfterFlush = 20; - return TRUE; - } - return FALSE; -} - - - - - -VOID __cdecl Debugprintf(const char * format, ...) -{ - char Mess[16384]; - va_list(arglist); - int Len; - - va_start(arglist, format); - Len = vsprintf(Mess, format, arglist); -#ifndef LINBPQ - WriteLogLine(NULL, '!',Mess, Len, LOG_DEBUG_X); -#endif - // #ifdef _DEBUG - strcat(Mess, "\r\n"); - OutputDebugString(Mess); - -// #endif - return; -} - -VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...) -{ - char Mess[1000]; - va_list(arglist);int Len; - - va_start(arglist, format); - Len = vsprintf(Mess, format, arglist); - WriteLogLine(conn, InOut, Mess, Len, LogMode); - - return; -} - -struct MsgInfo * GetMsgFromNumber(int msgno) -{ - if (msgno < 1 || msgno > 999999) - return NULL; - - return MsgnotoMsg[msgno]; -} - -struct UserInfo * AllocateUserRecord(char * Call) -{ - struct UserInfo * User = zalloc(sizeof (struct UserInfo)); - - strcpy(User->Call, Call); - User->Length = sizeof (struct UserInfo); - - GetSemaphore(&AllocSemaphore, 0); - - UserRecPtr=realloc(UserRecPtr,(++NumberofUsers+1) * sizeof(void *)); - UserRecPtr[NumberofUsers]= User; - - FreeSemaphore(&AllocSemaphore); - - return User; -} - -struct MsgInfo * AllocateMsgRecord() -{ - struct MsgInfo * Msg = zalloc(sizeof (struct MsgInfo)); - - GetSemaphore(&AllocSemaphore, 0); - - MsgHddrPtr=realloc(MsgHddrPtr,(++NumberofMessages+1) * sizeof(void *)); - MsgHddrPtr[NumberofMessages] = Msg; - - FreeSemaphore(&AllocSemaphore); - - return Msg; -} - -BIDRec * AllocateBIDRecord() -{ - BIDRec * BID = zalloc(sizeof (BIDRec)); - - GetSemaphore(&AllocSemaphore, 0); - - BIDRecPtr = realloc(BIDRecPtr,(++NumberofBIDs+1) * sizeof(void *)); - BIDRecPtr[NumberofBIDs] = BID; - - FreeSemaphore(&AllocSemaphore); - - return BID; -} - -BIDRec * AllocateTempBIDRecord() -{ - BIDRec * BID = zalloc(sizeof (BIDRec)); - - GetSemaphore(&AllocSemaphore, 0); - - TempBIDRecPtr=realloc(TempBIDRecPtr,(++NumberofTempBIDs+1) * sizeof(void *)); - TempBIDRecPtr[NumberofTempBIDs] = BID; - - FreeSemaphore(&AllocSemaphore); - - return BID; -} - -struct UserInfo * LookupCall(char * Call) -{ - struct UserInfo * ptr = NULL; - int i; - - for (i=1; i <= NumberofUsers; i++) - { - ptr = UserRecPtr[i]; - - if (_stricmp(ptr->Call, Call) == 0) return ptr; - - } - - return NULL; -} - -int GetNetInt(char * Line) -{ - char temp[1024]; - char * ptr = strlop(Line, ','); - int n = atoi(Line); - if (ptr == NULL) - Line[0] = 0; - else - { - strcpy(temp, ptr); - strcpy(Line, temp); - } - return n; -} - -VOID GetUserDatabase() -{ - struct UserInfo UserRec; - - FILE * Handle; - size_t ReadLen; - struct UserInfo * user; - time_t UserLimit = time(NULL) - (UserLifetime * 86400); // Oldest user to keep - int i; - - // See if user config is in main config - - group = config_lookup (&cfg, "BBSUsers"); - - if (group) - { - // We have User config in the main config file. so use that - - int index = 0; - char * stats; - struct MsgStats * Stats; - char * ptr, * ptr2; - - config_setting_t * entry = config_setting_get_elem (group, index++); - - // Initialise a new File - - UserRecPtr = malloc(sizeof(void *)); - UserRecPtr[0] = malloc(sizeof (struct UserInfo)); - memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); - UserRecPtr[0]->Length = sizeof (struct UserInfo); - - NumberofUsers = 0; - - while (entry) - { - char call[16]; - - // entry->name is call, will have * in front if a call stating woth number - - if (entry->name[0] == '*') - strcpy(call, &entry->name[1]); - else - strcpy(call, entry->name); - - user = AllocateUserRecord(call); - - ptr = entry->value.sval; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->Name, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->Address, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->HomeBBS, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->QRA, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->pass, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->ZIP, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) strcpy(user->CMSPass, ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->lastmsg = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->flags = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->PageLen = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->BBSNumber = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->RMSSSIDBits = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->WebSeqNo = atoi(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) user->TimeLastConnected = atol(ptr); - ptr = ptr2; - - ptr2 = strlop(ptr, '^'); - if (ptr) Stats = &user->Total; - stats = ptr; - - if (Stats == NULL) - { - NumberofUsers--; - free(user); - entry = config_setting_get_elem (group, index++); - continue; - } - - Stats->ConnectsIn = GetNetInt(stats); - Stats->ConnectsOut = GetNetInt(stats); - Stats->MsgsReceived[0] = GetNetInt(stats); - Stats->MsgsReceived[1] = GetNetInt(stats); - Stats->MsgsReceived[2] = GetNetInt(stats); - Stats->MsgsReceived[3] = GetNetInt(stats); - Stats->MsgsSent[0] = GetNetInt(stats); - Stats->MsgsSent[1] = GetNetInt(stats); - Stats->MsgsSent[2] = GetNetInt(stats); - Stats->MsgsSent[3] = GetNetInt(stats); - Stats->MsgsRejectedIn[0] = GetNetInt(stats); - Stats->MsgsRejectedIn[1] = GetNetInt(stats); - Stats->MsgsRejectedIn[2] = GetNetInt(stats); - Stats->MsgsRejectedIn[3] = GetNetInt(stats); - Stats->MsgsRejectedOut[0] = GetNetInt(stats); - Stats->MsgsRejectedOut[1] = GetNetInt(stats); - Stats->MsgsRejectedOut[2] = GetNetInt(stats); - Stats->MsgsRejectedOut[3] = GetNetInt(stats); - Stats->BytesForwardedIn[0] = GetNetInt(stats); - Stats->BytesForwardedIn[1] = GetNetInt(stats); - Stats->BytesForwardedIn[2] = GetNetInt(stats); - Stats->BytesForwardedIn[3] = GetNetInt(stats); - Stats->BytesForwardedOut[0] = GetNetInt(stats); - Stats->BytesForwardedOut[1] = GetNetInt(stats); - Stats->BytesForwardedOut[2] = GetNetInt(stats); - Stats->BytesForwardedOut[3] = GetNetInt(stats); - - Stats = &user->Last; - stats = ptr2; - - if (Stats == NULL) - { - NumberofUsers--; - free(user); - entry = config_setting_get_elem (group, index++); - continue; - } - - Stats->ConnectsIn = GetNetInt(stats); - Stats->ConnectsOut = GetNetInt(stats); - Stats->MsgsReceived[0] = GetNetInt(stats); - Stats->MsgsReceived[1] = GetNetInt(stats); - Stats->MsgsReceived[2] = GetNetInt(stats); - Stats->MsgsReceived[3] = GetNetInt(stats); - Stats->MsgsSent[0] = GetNetInt(stats); - Stats->MsgsSent[1] = GetNetInt(stats); - Stats->MsgsSent[2] = GetNetInt(stats); - Stats->MsgsSent[3] = GetNetInt(stats); - Stats->MsgsRejectedIn[0] = GetNetInt(stats); - Stats->MsgsRejectedIn[1] = GetNetInt(stats); - Stats->MsgsRejectedIn[2] = GetNetInt(stats); - Stats->MsgsRejectedIn[3] = GetNetInt(stats); - Stats->MsgsRejectedOut[0] = GetNetInt(stats); - Stats->MsgsRejectedOut[1] = GetNetInt(stats); - Stats->MsgsRejectedOut[2] = GetNetInt(stats); - Stats->MsgsRejectedOut[3] = GetNetInt(stats); - Stats->BytesForwardedIn[0] = GetNetInt(stats); - Stats->BytesForwardedIn[1] = GetNetInt(stats); - Stats->BytesForwardedIn[2] = GetNetInt(stats); - Stats->BytesForwardedIn[3] = GetNetInt(stats); - Stats->BytesForwardedOut[0] = GetNetInt(stats); - Stats->BytesForwardedOut[1] = GetNetInt(stats); - Stats->BytesForwardedOut[2] = GetNetInt(stats); - Stats->BytesForwardedOut[3] = GetNetInt(stats); - - - if ((user->flags & F_BBS) == 0) // Not BBS - Check Age - { - if (UserLifetime && user->TimeLastConnected) // Dont delete manually added Users that havent yet connected - { - if (user->TimeLastConnected < UserLimit) - { - // Too Old - ignore - - NumberofUsers--; - free(user); - entry = config_setting_get_elem (group, index++); - continue; - } - } - } - user->Temp = zalloc(sizeof (struct TempUserInfo)); - - if (user->lastmsg < 0 || user->lastmsg > LatestMsg) - user->lastmsg = LatestMsg; - - - entry = config_setting_get_elem (group, index++); - } - } - else - { - Handle = fopen(UserDatabasePath, "rb"); - - if (Handle == NULL) - { - // Initialise a new File - - UserRecPtr=malloc(sizeof(void *)); - UserRecPtr[0]= malloc(sizeof (struct UserInfo)); - memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); - UserRecPtr[0]->Length = sizeof (struct UserInfo); - - NumberofUsers = 0; - - return; - } - - - // Get First Record - - ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); - - if (ReadLen == 0) - { - // Duff file - - memset(&UserRec, 0, sizeof (struct UserInfo)); - UserRec.Length = sizeof (struct UserInfo); - } - else - { - // See if format has changed - - if (UserRec.Length == 0) - { - // Old format without a Length field - - struct OldUserInfo * OldRec = (struct OldUserInfo *)&UserRec; - int Users = OldRec->ConnectsIn; // User Count in control record - char Backup1[MAX_PATH]; - - // Create a backup in case reversion is needed and Reposition to first User record - - fclose(Handle); - - strcpy(Backup1, UserDatabasePath); - strcat(Backup1, ".oldformat"); - - CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak - - Handle = fopen(UserDatabasePath, "rb"); - - ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); // Skip Control Record - - // Set up control record - - UserRecPtr=malloc(sizeof(void *)); - UserRecPtr[0]= malloc(sizeof (struct UserInfo)); - memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); - UserRecPtr[0]->Length = sizeof (UserRec); - - NumberofUsers = 0; - -OldNext: - - ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); - - if (ReadLen > 0) - { - if (OldRec->Call[0] < '0') - goto OldNext; // Blank record - - user = AllocateUserRecord(OldRec->Call); - user->Temp = zalloc(sizeof (struct TempUserInfo)); - - // Copy info from Old record - - user->lastmsg = OldRec->lastmsg; - user->Total.ConnectsIn = OldRec->ConnectsIn; - user->TimeLastConnected = OldRec->TimeLastConnected; - user->flags = OldRec->flags; - user->PageLen = OldRec->PageLen; - user->BBSNumber = OldRec->BBSNumber; - memcpy(user->Name, OldRec->Name, 18); - memcpy(user->Address, OldRec->Address, 61); - user->Total.MsgsReceived[0] = OldRec->MsgsReceived; - user->Total.MsgsSent[0] = OldRec->MsgsSent; - user->Total.MsgsRejectedIn[0] = OldRec->MsgsRejectedIn; // Messages we reject - user->Total.MsgsRejectedOut[0] = OldRec->MsgsRejectedOut; // Messages Rejectd by other end - user->Total.BytesForwardedIn[0] = OldRec->BytesForwardedIn; - user->Total.BytesForwardedOut[0] = OldRec->BytesForwardedOut; - user->Total.ConnectsOut = OldRec->ConnectsOut; // Forwarding Connects Out - user->RMSSSIDBits = OldRec->RMSSSIDBits; // SSID's to poll in RMS - memcpy(user->HomeBBS, OldRec->HomeBBS, 41); - memcpy(user->QRA, OldRec->QRA, 7); - memcpy(user->pass, OldRec->pass, 13); - memcpy(user->ZIP, OldRec->ZIP, 9); - - // Read any forwarding info, even if not a BBS. - // This allows a BBS to be temporarily set as a - // normal user without loosing forwarding info - - SetupForwardingStruct(user); - - if (user->flags & F_BBS) - { - // Defined as BBS - allocate and initialise forwarding structure - - // Add to BBS Chain; - - user->BBSNext = BBSChain; - BBSChain = user; - - // Save Highest BBS Number - - if (user->BBSNumber > HighestBBSNumber) HighestBBSNumber = user->BBSNumber; - } - goto OldNext; - } - - SortBBSChain(); - fclose(Handle); - - return; - } - } - - // Set up control record - - UserRecPtr=malloc(sizeof(void *)); - UserRecPtr[0]= malloc(sizeof (struct UserInfo)); - memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); - UserRecPtr[0]->Length = sizeof (UserRec); - - NumberofUsers = 0; - -Next: - - ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); - - if (ReadLen > 0) - { - if (UserRec.Call[0] < '0') - goto Next; // Blank record - - if (UserRec.TimeLastConnected == 0) - UserRec.TimeLastConnected = UserRec.xTimeLastConnected; - - if ((UserRec.flags & F_BBS) == 0) // Not BBS - Check Age - if (UserLifetime) // if limit set - if (UserRec.TimeLastConnected) // Dont delete manually added Users that havent yet connected - if (UserRec.TimeLastConnected < UserLimit) - goto Next; // Too Old - ignore - - user = AllocateUserRecord(UserRec.Call); - memcpy(user, &UserRec, sizeof (UserRec)); - user->Temp = zalloc(sizeof (struct TempUserInfo)); - - user->ForwardingInfo = NULL; // In case left behind on crash - user->BBSNext = NULL; - user->POP3Locked = FALSE; - - if (user->lastmsg < 0 || user->lastmsg > LatestMsg) - user->lastmsg = LatestMsg; - - goto Next; - } - fclose(Handle); - } - - // Setting up BBS struct has been moved until all user record - // have been read so we can fix corrupt BBSNUmber - - for (i=1; i <= NumberofUsers; i++) - { - user = UserRecPtr[i]; - - // Read any forwarding info, even if not a BBS. - // This allows a BBS to be temporarily set as a - // normal user without loosing forwarding info - - SetupForwardingStruct(user); - - if (user->flags & F_BBS) - { - // Add to BBS Chain; - - if (user->BBSNumber == NBBBS) // Fix corrupt records - { - user->BBSNumber = FindFreeBBSNumber(); - if (user->BBSNumber == 0) - user->BBSNumber = NBBBS; // cant really do much else - } - - user->BBSNext = BBSChain; - BBSChain = user; - -// Logprintf(LOG_BBS, NULL, '?', "BBS %s BBSNumber %d", user->Call, user->BBSNumber); - - // Save Highest BBS Number - - if (user->BBSNumber > HighestBBSNumber) - HighestBBSNumber = user->BBSNumber; - } - } - - // Check for dulicate BBS numbers - - for (i=1; i <= NumberofUsers; i++) - { - user = UserRecPtr[i]; - - if (user->flags & F_BBS) - { - if (user->BBSNumber == 0) - user->BBSNumber = FindFreeBBSNumber(); - - CheckBBSNumber(user->BBSNumber); - } - } - - SortBBSChain(); -} - -VOID CopyUserDatabase() -{ - return; // User config now in main config file -/* - char Backup1[MAX_PATH]; - char Backup2[MAX_PATH]; - - // Keep 4 Generations - - strcpy(Backup2, UserDatabasePath); - strcat(Backup2, ".bak.3"); - - strcpy(Backup1, UserDatabasePath); - strcat(Backup1, ".bak.2"); - - DeleteFile(Backup2); // Remove old .bak.3 - MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 - - strcpy(Backup2, UserDatabasePath); - strcat(Backup2, ".bak.1"); - - MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 - - strcpy(Backup1, UserDatabasePath); - strcat(Backup1, ".bak"); - - MoveFile(Backup1, Backup2); //Move .bak to .bak.1 - - CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak -*/ -} - -VOID CopyConfigFile(char * ConfigName) -{ - char Backup1[MAX_PATH]; - char Backup2[MAX_PATH]; - - // Keep 4 Generations - - strcpy(Backup2, ConfigName); - strcat(Backup2, ".bak.3"); - - strcpy(Backup1, ConfigName); - strcat(Backup1, ".bak.2"); - - DeleteFile(Backup2); // Remove old .bak.3 - MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 - - strcpy(Backup2, ConfigName); - strcat(Backup2, ".bak.1"); - - MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 - - strcpy(Backup1, ConfigName); - strcat(Backup1, ".bak"); - - MoveFile(Backup1, Backup2); // Move .bak to .bak.1 - - CopyFile(ConfigName, Backup1, FALSE); // Copy to .bak -} - - - -VOID SaveUserDatabase() -{ - SaveConfig(ConfigName); // User config is now in main config file - GetConfig(ConfigName); - -/* - FILE * Handle; - size_t WriteLen; - int i; - - Handle = fopen(UserDatabasePath, "wb"); - - UserRecPtr[0]->Total.ConnectsIn = NumberofUsers; - - for (i=0; i <= NumberofUsers; i++) - { - WriteLen = fwrite(UserRecPtr[i], 1, (int)sizeof (struct UserInfo), Handle); - } - - fclose(Handle); -*/ - return; -} - -VOID GetMessageDatabase() -{ - struct MsgInfo MsgRec; - FILE * Handle; - size_t ReadLen; - struct MsgInfo * Msg; - char * MsgBytes; - int FileRecsize = sizeof(struct MsgInfo); // May be changed if reformating - BOOL Reformatting = FALSE; - char HEX[3] = ""; - int n; - - // See if Message Database is in main config - - group = config_lookup (&cfg, "MSGS"); - -// group = 0; - - if (group) - { - // We have User config in the main config file. so use that - - int index = 0; - char * ptr, * ptr2; - config_setting_t * entry = config_setting_get_elem (group, index++); - - // Initialise a new File - - MsgHddrPtr=malloc(sizeof(void *)); - MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); - NumberofMessages = 0; - MsgHddrPtr[0]->status = 2; - - if (entry) - { - // First Record has current message number - - ptr = entry->value.sval; - ptr2 = strlop(ptr, '|'); - ptr2 = strlop(ptr2, '|'); - if (ptr2) - LatestMsg = atoi(ptr2); - } - - entry = config_setting_get_elem (group, index++); - - while (entry) - { - // entry->name is MsgNo with 'R' in front - - ptr = entry->value.sval; - ptr2 = strlop(ptr, '|'); - - memset(&MsgRec, 0, sizeof(struct MsgInfo)); - - MsgRec.number = atoi(&entry->name[1]); - MsgRec.type = ptr[0]; - - ptr = ptr2; - - if (ptr == NULL) - { - entry = config_setting_get_elem (group, index++); - continue; - } - - ptr2 = strlop(ptr, '|'); - MsgRec.status = ptr[0]; - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.length = atoi(ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.datereceived = atol(ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.bbsfrom, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.via, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.from, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.to, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.bid, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.B2Flags = atoi(ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.datecreated = atol(ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.datechanged = atol(ptr); - - ptr = ptr2; - if (ptr) ptr2 = strlop(ptr, '|'); - - if (ptr == NULL) - { - entry = config_setting_get_elem (group, index++); - continue; - } - - if (ptr[0]) - { - char String[50] = "00000000000000000000"; - String[20] = 0; - memcpy(String, ptr, strlen(ptr)); - for (n = 0; n < NBMASK; n++) - { - memcpy(HEX, &String[n * 2], 2); - MsgRec.fbbs[n] = (UCHAR)strtol(HEX, 0, 16); - } - } - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - - if (ptr == NULL) - { - entry = config_setting_get_elem (group, index++); - continue; - } - - if (ptr[0]) - { - char String[50] = "00000000000000000000"; - String[20] = 0; - memcpy(String, ptr, strlen(ptr)); - for (n = 0; n < NBMASK; n++) - { - memcpy(HEX, &String[n * 2], 2); - MsgRec.forw[n] = (UCHAR)strtol(HEX, 0, 16); - } - } - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) strcpy(MsgRec.emailfrom, ptr); - - ptr = ptr2; - ptr2 = strlop(ptr, '|'); - if (ptr) MsgRec.UTF8 = atoi(ptr); - - ptr = ptr2; - - if (ptr) - { - strcpy(MsgRec.title, ptr); - - MsgBytes = ReadMessageFileEx(&MsgRec); - - if (MsgBytes) - { - free(MsgBytes); - Msg = AllocateMsgRecord(); - memcpy(Msg, &MsgRec, sizeof (MsgRec)); - - MsgnotoMsg[Msg->number] = Msg; - - // Fix Corrupted NTS Messages - - if (Msg->type == 'N') - Msg->type = 'T'; - - // Look for corrupt FROM address (ending in @) - - strlop(Msg->from, '@'); - - BuildNNTPList(Msg); // Build NNTP Groups list - - // If any forward bits are set, increment count on corresponding BBS record. - - if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) - { - if (FirstMessageIndextoForward == 0) - FirstMessageIndextoForward = NumberofMessages; // limit search - } - } - } - entry = config_setting_get_elem (group, index++); - } - - if (FirstMessageIndextoForward == 0) - FirstMessageIndextoForward = NumberofMessages; // limit search - - return; - } - - Handle = fopen(MsgDatabasePath, "rb"); - - if (Handle == NULL) - { - // Initialise a new File - - MsgHddrPtr=malloc(sizeof(void *)); - MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); - NumberofMessages = 0; - MsgHddrPtr[0]->status = 2; - - return; - } - - // Get First Record - - ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); - - if (ReadLen == 0) - { - // Duff file - - memset(&MsgRec, 0, sizeof (MsgRec)); - MsgRec.status = 2; - } - - // Set up control record - - MsgHddrPtr=malloc(sizeof(void *)); - MsgHddrPtr[0]= malloc(sizeof (MsgRec)); - memcpy(MsgHddrPtr[0], &MsgRec, sizeof (MsgRec)); - - LatestMsg=MsgHddrPtr[0]->length; - - NumberofMessages = 0; - - if (MsgRec.status == 1) // Used as file format version - // 0 = original, 1 = Extra email from addr, 2 = More BBS's. - { - char Backup1[MAX_PATH]; - - // Create a backup in case reversion is needed and Reposition to first User record - - fclose(Handle); - - strcpy(Backup1, MsgDatabasePath); - strcat(Backup1, ".oldformat"); - - CopyFile(MsgDatabasePath, Backup1, FALSE); // Copy to .oldformat - - Handle = fopen(MsgDatabasePath, "rb"); - - FileRecsize = sizeof(struct OldMsgInfo); - - ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); - - MsgHddrPtr[0]->status = 2; - } - -Next: - - ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); - - if (ReadLen > 0) - { - // Validate Header - - if (FileRecsize == sizeof(struct MsgInfo)) - { - if (MsgRec.type == 0 || MsgRec.number == 0) - goto Next; - - MsgBytes = ReadMessageFileEx(&MsgRec); - - if (MsgBytes) - { - // MsgRec.length = strlen(MsgBytes); - free(MsgBytes); - } - else - goto Next; - - Msg = AllocateMsgRecord(); - - memcpy(Msg, &MsgRec, +sizeof (MsgRec)); - } - else - { - // Resizing - record from file is an OldRecInfo - - struct OldMsgInfo * OldMessage = (struct OldMsgInfo *) &MsgRec; - - if (OldMessage->type == 0) - goto Next; - - if (OldMessage->number > 99999 || OldMessage->number < 1) - goto Next; - - Msg = AllocateMsgRecord(); - - - Msg->B2Flags = OldMessage->B2Flags; - memcpy(Msg->bbsfrom, OldMessage->bbsfrom, 7); - memcpy(Msg->bid, OldMessage->bid, 13); - Msg->datechanged = OldMessage->datechanged; - Msg->datecreated = OldMessage->datecreated; - Msg->datereceived = OldMessage->datereceived; - memcpy(Msg->emailfrom, OldMessage->emailfrom, 41); - memcpy(Msg->fbbs , OldMessage->fbbs, 10); - memcpy(Msg->forw , OldMessage->forw, 10); - memcpy(Msg->from, OldMessage->from, 7); - Msg->length = OldMessage->length; - Msg->nntpnum = OldMessage->nntpnum; - Msg->number = OldMessage->number; - Msg->status = OldMessage->status; - memcpy(Msg->title, OldMessage->title, 61); - memcpy(Msg->to, OldMessage->to, 7); - Msg->type = OldMessage->type; - memcpy(Msg->via, OldMessage->via, 41); - } - - MsgnotoMsg[Msg->number] = Msg; - - // Fix Corrupted NTS Messages - - if (Msg->type == 'N') - Msg->type = 'T'; - - // Look for corrupt FROM address (ending in @) - - strlop(Msg->from, '@'); - - // Move Dates if first run with new format - - if (Msg->datecreated == 0) - Msg->datecreated = Msg->xdatecreated; - - if (Msg->datereceived == 0) - Msg->datereceived = Msg->xdatereceived; - - if (Msg->datechanged == 0) - Msg->datechanged = Msg->xdatechanged; - - BuildNNTPList(Msg); // Build NNTP Groups list - - Msg->Locked = 0; // In case left locked - Msg->Defered = 0; // In case left set. - - // If any forward bits are set, increment count on corresponding BBS record. - - if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) - { - if (FirstMessageIndextoForward == 0) - FirstMessageIndextoForward = NumberofMessages; // limit search - } - - goto Next; - } - - if (FirstMessageIndextoForward == 0) - FirstMessageIndextoForward = NumberofMessages; // limit search - - fclose(Handle); -} - -VOID CopyMessageDatabase() -{ - char Backup1[MAX_PATH]; - char Backup2[MAX_PATH]; - -// return; - - // Keep 4 Generations - - strcpy(Backup2, MsgDatabasePath); - strcat(Backup2, ".bak.3"); - - strcpy(Backup1, MsgDatabasePath); - strcat(Backup1, ".bak.2"); - - DeleteFile(Backup2); // Remove old .bak.3 - MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 - - strcpy(Backup2, MsgDatabasePath); - strcat(Backup2, ".bak.1"); - - MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 - - strcpy(Backup1, MsgDatabasePath); - strcat(Backup1, ".bak"); - - MoveFile(Backup1, Backup2); //Move .bak to .bak.1 - - strcpy(Backup2, MsgDatabasePath); - strcat(Backup2, ".bak"); - - CopyFile(MsgDatabasePath, Backup2, FALSE); // Copy to .bak - -} - -VOID SaveMessageDatabase() -{ - FILE * Handle; - size_t WriteLen; - int i; - char Key[16]; - struct MsgInfo *Msg; -// char CfgName[MAX_PATH]; - char HEXString1[64]; - char HEXString2[64]; - int n; -// char * CfgBuffer; - char Cfg[1024]; -// int CfgLen = 0; -// FILE * hFile; - -// SaveConfig(ConfigName); // Message Headers now in main config -// return; - -#ifdef LINBPQ - RefreshWebMailIndex(); -#else - if (pRefreshWebMailIndex) - pRefreshWebMailIndex(); -#endif - - Handle = fopen(MsgDatabasePath, "wb"); - - if (Handle == NULL) - { - CriticalErrorHandler("Failed to open message database"); - return; - } - - MsgHddrPtr[0]->status = 2; - MsgHddrPtr[0]->number = NumberofMessages; - MsgHddrPtr[0]->length = LatestMsg; - - for (i=0; i <= NumberofMessages; i++) - { - WriteLen = fwrite(MsgHddrPtr[i], 1, sizeof (struct MsgInfo), Handle); - - if (WriteLen != sizeof(struct MsgInfo)) - { - CriticalErrorHandler("Failed to write message database record"); - return; - } - } - - if (fclose(Handle) != 0) - CriticalErrorHandler("Failed to close message database"); - - for (i = 1; i <= NumberofMessages; i++) - { - Msg = MsgHddrPtr[i]; - - for (n = 0; n < NBMASK; n++) - sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); - - n = 39; - while (n >=0 && HEXString1[n] == '0') - HEXString1[n--] = 0; - - for (n = 0; n < NBMASK; n++) - sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); - - n = 39; - while (n >= 0 && HEXString2[n] == '0') - HEXString2[n--] = 0; - - sprintf(Key, "R%d:\r\n", i); - - n = sprintf(Cfg, "%c|%c|%d|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, - Msg->number, Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], - &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, - &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); - } - - return; -} - -VOID GetBIDDatabase() -{ - BIDRec BIDRec; - FILE * Handle; - size_t ReadLen; - BIDRecP BID; - int index = 0; - char * ptr, * ptr2; - - // If BID info is in main config file, use it - - group = config_lookup (&cfg, "BIDS"); - - if (group) - { - config_setting_t * entry = config_setting_get_elem (group, index++); - - BIDRecPtr=malloc(sizeof(void *)); - BIDRecPtr[0]= malloc(sizeof (BIDRec)); - memset(BIDRecPtr[0], 0, sizeof (BIDRec)); - NumberofBIDs = 0; - - while (entry) - { - // entry->name is Bid with 'R' in front - - ptr = entry->value.sval; - ptr2 = strlop(ptr, '|'); - - if (ptr && ptr2) - { - BID = AllocateBIDRecord(); - strcpy(BID->BID, &entry->name[1]); - BID->mode = atoi(ptr); - BID->u.timestamp = atoi(ptr2); - - if (BID->u.timestamp == 0) - BID->u.timestamp = LOWORD(time(NULL)/86400); - - } - entry = config_setting_get_elem (group, index++); - } - return; - } - - Handle = fopen(BIDDatabasePath, "rb"); - - if (Handle == NULL) - { - // Initialise a new File - - BIDRecPtr=malloc(sizeof(void *)); - BIDRecPtr[0]= malloc(sizeof (BIDRec)); - memset(BIDRecPtr[0], 0, sizeof (BIDRec)); - NumberofBIDs = 0; - - return; - } - - - // Get First Record - - ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); - - if (ReadLen == 0) - { - // Duff file - - memset(&BIDRec, 0, sizeof (BIDRec)); - } - - // Set up control record - - BIDRecPtr = malloc(sizeof(void *)); - BIDRecPtr[0] = malloc(sizeof (BIDRec)); - memcpy(BIDRecPtr[0], &BIDRec, sizeof (BIDRec)); - - NumberofBIDs = 0; - -Next: - - ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); - - if (ReadLen > 0) - { - BID = AllocateBIDRecord(); - memcpy(BID, &BIDRec, sizeof (BIDRec)); - - if (BID->u.timestamp == 0) - BID->u.timestamp = LOWORD(time(NULL)/86400); - - goto Next; - } - - fclose(Handle); -} - -VOID CopyBIDDatabase() -{ - char Backup[MAX_PATH]; - -// return; - - - strcpy(Backup, BIDDatabasePath); - strcat(Backup, ".bak"); - - CopyFile(BIDDatabasePath, Backup, FALSE); -} - -VOID SaveBIDDatabase() -{ - FILE * Handle; - size_t WriteLen; - int i; - -// return; // Bids are now in main config and are saved when message is saved - - Handle = fopen(BIDDatabasePath, "wb"); - - BIDRecPtr[0]->u.msgno = NumberofBIDs; // First Record has file size - - for (i=0; i <= NumberofBIDs; i++) - { - WriteLen = fwrite(BIDRecPtr[i], 1, sizeof (BIDRec), Handle); - } - - fclose(Handle); - - return; -} - -BIDRec * LookupBID(char * BID) -{ - BIDRec * ptr = NULL; - int i; - - for (i=1; i <= NumberofBIDs; i++) - { - ptr = BIDRecPtr[i]; - - if (_stricmp(ptr->BID, BID) == 0) - return ptr; - } - - return NULL; -} - -BIDRec * LookupTempBID(char * BID) -{ - BIDRec * ptr = NULL; - int i; - - for (i=1; i <= NumberofTempBIDs; i++) - { - ptr = TempBIDRecPtr[i]; - - if (_stricmp(ptr->BID, BID) == 0) return ptr; - } - - return NULL; -} - -VOID RemoveTempBIDS(CIRCUIT * conn) -{ - // Remove any Temp BID records for conn. Called when connection closes - Msgs will be complete or failed - - if (NumberofTempBIDs == 0) - return; - else - { - BIDRec * ptr = NULL; - BIDRec ** NewTempBIDRecPtr = zalloc((NumberofTempBIDs+1) * sizeof(void *)); - int i = 0, n; - - GetSemaphore(&AllocSemaphore, 0); - - for (n = 1; n <= NumberofTempBIDs; n++) - { - ptr = TempBIDRecPtr[n]; - - if (ptr) - { - if (ptr->u.conn == conn) - // Remove this entry - free(ptr); - else - NewTempBIDRecPtr[++i] = ptr; - } - } - - NumberofTempBIDs = i; - - free(TempBIDRecPtr); - - TempBIDRecPtr = NewTempBIDRecPtr; - FreeSemaphore(&AllocSemaphore); - } - -} - -VOID GetBadWordFile() -{ - FILE * Handle; - DWORD FileSize; - char * ptr1, * ptr2; - struct stat STAT; - - if (stat(BadWordsPath, &STAT) == -1) - return; - - FileSize = STAT.st_size; - - Handle = fopen(BadWordsPath, "rb"); - - if (Handle == NULL) - return; - - // Release old info in case a re-read - - if (BadWords) free(BadWords); - if (BadFile) free(BadFile); - - BadWords = NULL; - BadFile = NULL; - NumberofBadWords = 0; - - BadFile = malloc(FileSize+1); - - fread(BadFile, 1, FileSize, Handle); - - fclose(Handle); - - BadFile[FileSize]=0; - - _strlwr(BadFile); // Compares are case-insensitive - - ptr1 = BadFile; - - while (ptr1) - { - if (*ptr1 == '\n') ptr1++; - - ptr2 = strtok_s(NULL, "\r\n", &ptr1); - if (ptr2) - { - if (*ptr2 != '#') - { - BadWords = realloc(BadWords,(++NumberofBadWords+1) * sizeof(void *)); - BadWords[NumberofBadWords] = ptr2; - } - } - else - break; - } -} - -BOOL CheckBadWord(char * Word, char * Msg) -{ - char * ptr1 = Msg, * ptr2; - size_t len = strlen(Word); - - while (*ptr1) // Stop at end - { - ptr2 = strstr(ptr1, Word); - - if (ptr2 == NULL) - return FALSE; // OK - - // Only bad if it ia not part of a longer word - - if ((ptr2 == Msg) || !(isalpha(*(ptr2 - 1)))) // No alpha before - if (!(isalpha(*(ptr2 + len)))) // No alpha after - return TRUE; // Bad word - - // Keep searching - - ptr1 = ptr2 + len; - } - - return FALSE; // OK -} - -BOOL CheckBadWords(char * Msg) -{ - char * dupMsg = _strlwr(_strdup(Msg)); - int i; - - for (i = 1; i <= NumberofBadWords; i++) - { - if (CheckBadWord(BadWords[i], dupMsg)) - { - free(dupMsg); - return TRUE; // Bad - } - } - - free(dupMsg); - return FALSE; // OK - -} - -VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user) -{ - if (user->flags & F_Expert) - ExpandAndSendMessage(conn, ExpertWelcomeMsg, LOG_BBS); - else if (conn->NewUser) - ExpandAndSendMessage(conn, NewWelcomeMsg, LOG_BBS); - else - ExpandAndSendMessage(conn, WelcomeMsg, LOG_BBS); - - if (user->HomeBBS[0] == 0 && !DontNeedHomeBBS) - BBSputs(conn, "Please enter your Home BBS using the Home command.\rYou may also enter your QTH and ZIP/Postcode using qth and zip commands.\r"); - -// if (user->flags & F_Temp_B2_BBS) -// nodeprintf(conn, "%s CMS >\r", BBSName); -// else - SendPrompt(conn, user); -} - -VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user) -{ - if (user->Temp->ListSuspended) - return; // Dont send prompt if pausing a listing - - if (user->flags & F_Expert) - ExpandAndSendMessage(conn, ExpertPrompt, LOG_BBS); - else if (conn->NewUser) - ExpandAndSendMessage(conn, NewPrompt, LOG_BBS); - else - ExpandAndSendMessage(conn, Prompt, LOG_BBS); - -// if (user->flags & F_Expert) -// nodeprintf(conn, "%s\r", ExpertPrompt); -// else if (conn->NewUser) -// nodeprintf(conn, "%s\r", NewPrompt); -// else -// nodeprintf(conn, "%s\r", Prompt); -} - - - -VOID * _zalloc(size_t len) -{ - // ?? malloc and clear - - void * ptr; - - ptr=malloc(len); - memset(ptr, 0, len); - - return ptr; -} - -BOOL isAMPRMsg(char * Addr) -{ - // See if message is addressed to ampr.org and is either - // for us or we have SendAMPRDirect (ie don't need RMS or SMTP to send it) - - size_t toLen = strlen(Addr); - - if (_memicmp(&Addr[toLen - 8], "ampr.org", 8) == 0) - { - // message is for ampr.org - - char toCall[48]; - char * via; - - strcpy(toCall, _strupr(Addr)); - - via = strlop(toCall, '@'); - - if (_stricmp(via, AMPRDomain) == 0) - { - // message is for us. - - return TRUE; - } - - if (SendAMPRDirect) - { - // We want to send ampr mail direct to host. Queue to BBS AMPR - - if (FindAMPR()) - { - // We have bbs AMPR - - return TRUE; - } - } - } - return FALSE; -} - -struct UserInfo * FindAMPR() -{ - struct UserInfo * bbs; - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - if (strcmp(bbs->Call, "AMPR") == 0) - return bbs; - } - - return NULL; -} - -struct UserInfo * FindRMS() -{ - struct UserInfo * bbs; - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - if (strcmp(bbs->Call, "RMS") == 0) - return bbs; - } - - return NULL; -} - -struct UserInfo * FindBBS(char * Name) -{ - struct UserInfo * bbs; - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - if (strcmp(bbs->Call, Name) == 0) - return bbs; - } - - return NULL; -} - -int CountConnectionsOnPort(int CheckPort) -{ - int n, Count = 0; - CIRCUIT * conn; - int port, sesstype, paclen, maxframe, l4window; - char callsign[11]; - - for (n = 0; n < NumberofStreams; n++) - { - conn = &Connections[n]; - - if (conn->Active) - { - GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); - if (port == CheckPort) - Count++; - } - } - - return Count; -} - - -BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type) -{ - char ** Calls; - - if (Type == 'B' && FilterWPBulls && _stricmp(To, "WP") == 0) - return TRUE; - - if (RejFrom && From) - { - Calls = RejFrom; - - while(Calls[0]) - { - if (_stricmp(Calls[0], From) == 0) - return TRUE; - - Calls++; - } - } - - if (RejTo && To) - { - Calls = RejTo; - - while(Calls[0]) - { - if (_stricmp(Calls[0], To) == 0) - return TRUE; - - Calls++; - } - } - - if (RejAt && ATBBS) - { - Calls = RejAt; - - while(Calls[0]) - { - if (_stricmp(Calls[0], ATBBS) == 0) - return TRUE; - - Calls++; - } - } - - if (RejBID && BID) - { - Calls = RejBID; - - while(Calls[0]) - { - if (Calls[0][0] == '*') - { - if (stristr(BID, &Calls[0][1])) - return TRUE; - } - else - { - if (_stricmp(BID, Calls[0]) == 0) - return TRUE; - } - - Calls++; - } - } - return FALSE; // Ok to accept -} - -BOOL CheckValidCall(char * From) -{ - unsigned int i; - - if (DontCheckFromCall) - return TRUE; - - if (strcmp(From, "SYSOP") == 0 || strcmp(From, "SYSTEM") == 0 || - strcmp(From, "IMPORT") == 0 || strcmp(From, "SMTP:") == 0 || strcmp(From, "RMS:") == 0) - return TRUE; - - for (i = 1; i < strlen(From); i++) // skip first which may also be digit - { - if (isdigit(From[i])) - { - // Has a digit. Check Last is not digit - - if (isalpha(From[strlen(From) - 1])) - return TRUE; - } - } - - // No digit, return false - - return FALSE; -} - -BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID) -{ - char ** Calls; - - if (HoldFrom && From) - { - Calls = HoldFrom; - - while(Calls[0]) - { - if (_stricmp(Calls[0], From) == 0) - return TRUE; - - Calls++; - } - } - - if (HoldTo && To) - { - Calls = HoldTo; - - while(Calls[0]) - { - if (_stricmp(Calls[0], To) == 0) - return TRUE; - - Calls++; - } - } - - if (HoldAt && ATBBS) - { - Calls = HoldAt; - - while(Calls[0]) - { - if (_stricmp(Calls[0], ATBBS) == 0) - return TRUE; - - Calls++; - } - } - - if (HoldBID && BID) - { - Calls = HoldBID; - - while(Calls[0]) - { - if (Calls[0][0] == '*') - { - if (stristr(BID, &Calls[0][1])) - return TRUE; - } - else - { - if (_stricmp(BID, Calls[0]) == 0) - return TRUE; - } - - Calls++; - } - } - return FALSE; // Ok to accept -} - -BOOL CheckifLocalRMSUser(char * FullTo) -{ - struct UserInfo * user = LookupCall(FullTo); - - if (user) - if (user->flags & F_POLLRMS) - return TRUE; - - return FALSE; - -} - - - -int check_fwd_bit(char *mask, int bbsnumber) -{ - if (bbsnumber) - return (mask[(bbsnumber - 1) / 8] & (1 << ((bbsnumber - 1) % 8))); - else - return 0; -} - - -void set_fwd_bit(char *mask, int bbsnumber) -{ - if (bbsnumber) - mask[(bbsnumber - 1) / 8] |= (1 << ((bbsnumber - 1) % 8)); -} - - -void clear_fwd_bit (char *mask, int bbsnumber) -{ - if (bbsnumber) - mask[(bbsnumber - 1) / 8] &= (~(1 << ((bbsnumber - 1) % 8))); -} - -VOID BBSputs(CIRCUIT * conn, char * buf) -{ - // Sends to user and logs - - WriteLogLine(conn, '>',buf, (int)strlen(buf) -1, LOG_BBS); - - QueueMsg(conn, buf, (int)strlen(buf)); -} - -VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...) -{ - char Mess[1000]; - int len; - va_list(arglist); - - - va_start(arglist, format); - len = vsprintf(Mess, format, arglist); - - QueueMsg(conn, Mess, len); - - WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); - - return; -} - -// nodeprintfEx add a LF if NEEFLF is set - -VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...) -{ - char Mess[1000]; - int len; - va_list(arglist); - - - va_start(arglist, format); - len = vsprintf(Mess, format, arglist); - - QueueMsg(conn, Mess, len); - - WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); - - if (conn->BBSFlags & NEEDLF) - QueueMsg(conn, "\r", 1); - - return; -} - - -int compare( const void *arg1, const void *arg2 ); - -VOID SortBBSChain() -{ - struct UserInfo * user; - struct UserInfo * users[161]; - int i = 0, n; - - // Get array of addresses - - for (user = BBSChain; user; user = user->BBSNext) - { - users[i++] = user; - if (i > 160) break; - } - - qsort((void *)users, i, sizeof(void *), compare ); - - BBSChain = NULL; - - // Rechain (backwards, as entries ate put on front of chain) - - for (n = i-1; n >= 0; n--) - { - users[n]->BBSNext = BBSChain; - BBSChain = users[n]; - } -} - -int compare(const void *arg1, const void *arg2) -{ - // Compare Calls. Fortunately call is at start of stuct - - return _stricmp(*(char**)arg1 , *(char**)arg2); -} - -int CountMessagesTo(struct UserInfo * user, int * Unread) -{ - int i, Msgs = 0; - UCHAR * Call = user->Call; - - *Unread = 0; - - for (i = NumberofMessages; i > 0; i--) - { - if (MsgHddrPtr[i]->status == 'K') - continue; - - if (_stricmp(MsgHddrPtr[i]->to, Call) == 0) - { - Msgs++; - if (MsgHddrPtr[i]->status == 'N') - *Unread = *Unread + 1; - } - } - return(Msgs); -} - - - -// Custimised message handling routines. -/* - Variables - a subset of those used by FBB - - $C : Number of the next message. - $I : First name of the connected user. - $L : Number of the latest message. - $N : Number of active messages - $U : Callsign of the connected user. - $W : Inserts a carriage return. - $Z : Last message read by the user (L command). - %X : Number of messages for the user. - %x : Number of new messages for the user. -*/ - -VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG) -{ - char NewMessage[10000]; - char * OldP = Msg; - char * NewP = NewMessage; - char * ptr, * pptr; - size_t len; - char Dollar[] = "$"; - char CR[] = "\r"; - char num[20]; - int Msgs = 0, Unread = 0; - - ptr = strchr(OldP, '$'); - - while (ptr) - { - len = ptr - OldP; // Chars before $ - memcpy(NewP, OldP, len); - NewP += len; - - switch (*++ptr) - { - case 'I': // First name of the connected user. - - pptr = conn->UserPointer->Name; - break; - - case 'L': // Number of the latest message. - - sprintf(num, "%d", LatestMsg); - pptr = num; - break; - - case 'N': // Number of active messages. - - sprintf(num, "%d", NumberofMessages); - pptr = num; - break; - - case 'U': // Callsign of the connected user. - - pptr = conn->UserPointer->Call; - break; - - case 'W': // Inserts a carriage return. - - pptr = CR; - break; - - case 'Z': // Last message read by the user (L command). - - sprintf(num, "%d", conn->UserPointer->lastmsg); - pptr = num; - break; - - case 'X': // Number of messages for the user. - - Msgs = CountMessagesTo(conn->UserPointer, &Unread); - sprintf(num, "%d", Msgs); - pptr = num; - break; - - case 'x': // Number of new messages for the user. - - Msgs = CountMessagesTo(conn->UserPointer, &Unread); - sprintf(num, "%d", Unread); - pptr = num; - break; - - case 'F': // Number of new messages to forward to this BBS. - - Msgs = CountMessagestoForward(conn->UserPointer); - sprintf(num, "%d", Msgs); - pptr = num; - break; - - default: - - pptr = Dollar; // Just Copy $ - } - - len = strlen(pptr); - memcpy(NewP, pptr, len); - NewP += len; - - OldP = ++ptr; - ptr = strchr(OldP, '$'); - } - - strcpy(NewP, OldP); - - len = RemoveLF(NewMessage, (int)strlen(NewMessage)); - - WriteLogLine(conn, '>', NewMessage, (int)len, LOG); - QueueMsg(conn, NewMessage, (int)len); -} - -BOOL isdigits(char * string) -{ - // Returns TRUE id sting is decimal digits - - size_t i, n = strlen(string); - - for (i = 0; i < n; i++) - { - if (isdigit(string[i]) == FALSE) return FALSE; - } - return TRUE; -} - -BOOL wildcardcompare(char * Target, char * Match) -{ - // Do a compare with string *string string* *string* - - // Strings should all be UC - - char Pattern[100]; - char * firststar; - - strcpy(Pattern, Match); - firststar = strchr(Pattern,'*'); - - if (firststar) - { - size_t Len = strlen(Pattern); - - if (Pattern[0] == '*' && Pattern[Len - 1] == '*') // * at start and end - { - Pattern[Len - 1] = 0; - return !(strstr(Target, &Pattern[1]) == NULL); - } - if (Pattern[0] == '*') // * at start - { - // Compare the last len - 1 chars of Target - - size_t Targlen = strlen(Target); - size_t Comparelen = Targlen - (Len - 1); - - if (Len == 1) // Just * - return TRUE; - - if (Comparelen < 0) // Too Short - return FALSE; - - return (memcmp(&Target[Comparelen], &Pattern[1], Len - 1) == 0); - } - - // Must be * at end - compare first Len-1 char - - return (memcmp(Target, Pattern, Len - 1) == 0); - } - - // No WildCards - straight strcmp - return (strcmp(Target, Pattern) == 0); -} - -#ifndef LINBPQ - -PrintMessage(HDC hDC, struct MsgInfo * Msg); - -PrintMessages(HWND hDlg, int Count, int * Indexes) -{ - int i, CurrentMsgIndex; - char MsgnoText[10]; - int Msgno; - struct MsgInfo * Msg; - int Len = MAX_PATH; - BOOL hResult; - PRINTDLG pdx = {0}; - HDC hDC; - -// CHOOSEFONT cf; - LOGFONT lf; - HFONT hFont; - - - // Initialize the PRINTDLG structure. - - pdx.lStructSize = sizeof(PRINTDLG); - pdx.hwndOwner = hWnd; - pdx.hDevMode = NULL; - pdx.hDevNames = NULL; - pdx.hDC = NULL; - pdx.Flags = PD_RETURNDC | PD_COLLATE; - pdx.nMinPage = 1; - pdx.nMaxPage = 1000; - pdx.nCopies = 1; - pdx.hInstance = 0; - pdx.lpPrintTemplateName = NULL; - - // Invoke the Print property sheet. - - hResult = PrintDlg(&pdx); - - memset(&lf, 0, sizeof(LOGFONT)); - - /* - - // Initialize members of the CHOOSEFONT structure. - - cf.lStructSize = sizeof(CHOOSEFONT); - cf.hwndOwner = (HWND)NULL; - cf.hDC = pdx.hDC; - cf.lpLogFont = &lf; - cf.iPointSize = 0; - cf.Flags = CF_PRINTERFONTS | CF_FIXEDPITCHONLY; - cf.rgbColors = RGB(0,0,0); - cf.lCustData = 0L; - cf.lpfnHook = (LPCFHOOKPROC)NULL; - cf.lpTemplateName = (LPSTR)NULL; - cf.hInstance = (HINSTANCE) NULL; - cf.lpszStyle = (LPSTR)NULL; - cf.nFontType = PRINTER_FONTTYPE; - cf.nSizeMin = 0; - cf.nSizeMax = 0; - - // Display the CHOOSEFONT common-dialog box. - - ChooseFont(&cf); - - // Create a logical font based on the user's - // selection and return a handle identifying - // that font. -*/ - - lf.lfHeight = -56; - lf.lfWeight = 600; - lf.lfOutPrecision = 3; - lf.lfClipPrecision = 2; - lf.lfQuality = 1; - lf.lfPitchAndFamily = '1'; - strcpy (lf.lfFaceName, "Courier New"); - - hFont = CreateFontIndirect(&lf); - - if (hResult) - { - // User clicked the Print button, so use the DC and other information returned in the - // PRINTDLG structure to print the document. - - DOCINFO pdi; - - pdi.cbSize = sizeof(DOCINFO); - pdi.lpszDocName = "BBS Message Print"; - pdi.lpszOutput = NULL; - pdi.lpszDatatype = "RAW"; - pdi.fwType = 0; - - hDC = pdx.hDC; - - SelectObject(hDC, hFont); - - StartDoc(hDC, &pdi); - StartPage(hDC); - - for (i = 0; i < Count; i++) - { - SendDlgItemMessage(hDlg, 0, LB_GETTEXT, Indexes[i], (LPARAM)(LPCTSTR)&MsgnoText); - - Msgno = atoi(MsgnoText); - - for (CurrentMsgIndex = 1; CurrentMsgIndex <= NumberofMessages; CurrentMsgIndex++) - { - Msg = MsgHddrPtr[CurrentMsgIndex]; - - if (Msg->number == Msgno) - { - PrintMessage(hDC, Msg); - break; - } - } - } - - EndDoc(hDC); - } - - if (pdx.hDevMode != NULL) - GlobalFree(pdx.hDevMode); - if (pdx.hDevNames != NULL) - GlobalFree(pdx.hDevNames); - - if (pdx.hDC != NULL) - DeleteDC(pdx.hDC); - - return 0; -} - -PrintMessage(HDC hDC, struct MsgInfo * Msg) -{ - int Len = MAX_PATH; - char * MsgBytes; - char * Save; - int Msglen; - - StartPage(hDC); - - Save = MsgBytes = ReadMessageFile(Msg->number); - - Msglen = Msg->length; - - if (MsgBytes) - { - char Hddr[1000]; - char FullTo[100]; - int HRes, VRes; - char * ptr1, * ptr2; - int LineLen; - - RECT Rect; - - if (_stricmp(Msg->to, "RMS") == 0) - sprintf(FullTo, "RMS:%s", Msg->via); - else - if (Msg->to[0] == 0) - sprintf(FullTo, "smtp:%s", Msg->via); - else - strcpy(FullTo, Msg->to); - - - sprintf(Hddr, "From: %s%s\r\nTo: %s\r\nType/Status: %c%c\r\nDate/Time: %s\r\nBid: %s\r\nTitle: %s\r\n\r\n", - Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); - - - if (Msg->B2Flags & B2Msg) - { - // Remove B2 Headers (up to the File: Line) - - char * ptr; - ptr = strstr(MsgBytes, "Body:"); - if (ptr) - { - Msglen = atoi(ptr + 5); - ptr = strstr(ptr, "\r\n\r\n"); - } - if (ptr) - MsgBytes = ptr + 4; - } - - HRes = GetDeviceCaps(hDC, HORZRES) - 50; - VRes = GetDeviceCaps(hDC, VERTRES) - 50; - - Rect.top = 50; - Rect.left = 50; - Rect.right = HRes; - Rect.bottom = VRes; - - DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_CALCRECT | DT_WORDBREAK); - DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_WORDBREAK); - - // process message a line at a time. When page is full, output a page break - - ptr1 = MsgBytes; - ptr2 = ptr1; - - while (Msglen-- > 0) - { - if (*ptr1++ == '\r') - { - // Output this line - - // First check if it will fit - - Rect.top = Rect.bottom; - Rect.right = HRes; - Rect.bottom = VRes; - - LineLen = ptr1 - ptr2 - 1; - - if (LineLen == 0) // Blank line - Rect.bottom = Rect.top + 40; - else - DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); - - if (Rect.bottom >= VRes) - { - EndPage(hDC); - StartPage(hDC); - - Rect.top = 50; - Rect.bottom = VRes; - if (LineLen == 0) // Blank line - Rect.bottom = Rect.top + 40; - else - DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); - } - - if (LineLen == 0) // Blank line - Rect.bottom = Rect.top + 40; - else - DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_WORDBREAK); - - if (*(ptr1) == '\n') - { - ptr1++; - Msglen--; - } - - ptr2 = ptr1; - } - } - - free(Save); - - EndPage(hDC); - - } - return 0; -} - -#endif - - -int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup) -{ - char FileName[MAX_PATH] = "Messages.in"; - int Files = 0; - int WriteLen=0; - FILE *in; - CIRCUIT dummyconn; - struct UserInfo User; - int Index = 0; - - char Buffer[100000]; - char *buf = Buffer; - - if (FN[0]) // Name supplled - strcpy(FileName, FN); - - else - { -#ifndef LINBPQ - OPENFILENAME Ofn; - - memset(&Ofn, 0, sizeof(Ofn)); - - Ofn.lStructSize = sizeof(OPENFILENAME); - Ofn.hInstance = hInst; - Ofn.hwndOwner = MainWnd; - Ofn.lpstrFilter = NULL; - Ofn.lpstrFile= FileName; - Ofn.nMaxFile = sizeof(FileName)/ sizeof(*FileName); - Ofn.lpstrFileTitle = NULL; - Ofn.nMaxFileTitle = 0; - Ofn.lpstrInitialDir = BaseDir; - Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; - Ofn.lpstrTitle = NULL;//; - - if (!GetOpenFileName(&Ofn)) - return 0; -#endif - } - - in = fopen(FileName, "rb"); - - if (!(in)) - { - char msg[500]; - sprintf_s(msg, sizeof(msg), "Failed to open %s", FileName); - if (conn) - nodeprintf(conn, "%s\r", msg); -#ifdef WIN32 - else - if (Nopopup == FALSE) - MessageBox(NULL, msg, "BPQMailChat", MB_OK); -#endif - return 0; - } - - memset(&dummyconn, 0, sizeof(CIRCUIT)); - memset(&User, 0, sizeof(struct UserInfo)); - - if (conn == 0) - { - conn = &dummyconn; - - dummyconn.UserPointer = &User; // Was SYSOPCall, but I think that is wrong. - strcpy(User.Call, "IMPORT"); - User.flags |= F_EMAIL; - dummyconn.sysop = TRUE; - dummyconn.BBSFlags = BBS; - - strcpy(dummyconn.Callsign, "IMPORT"); - } - - while(fgets(Buffer, 99999, in)) - { - // First line should start SP/SB ?ST? - - char * From = NULL; - char * BID = NULL; - char * ATBBS = NULL; - char seps[] = " \t\r"; - struct MsgInfo * Msg; - char To[100]= ""; - int msglen; - char * Context; - char * Arg1, * Cmd; - -NextMessage: - - From = NULL; - BID = NULL; - ATBBS = NULL; - To[0]= 0; - - Sleep(100); - - strlop(Buffer, 10); - strlop(Buffer, 13); // Remove cr and/or lf - - if (Buffer[0] == 0) //Blank Line - continue; - - WriteLogLine(conn, '>', Buffer, (int)strlen(Buffer), LOG_BBS); - - if (dummyconn.sysop == 0) - { - nodeprintf(conn, "%s\r", Buffer); - Flush(conn); - } - - Cmd = strtok_s(Buffer, seps, &Context); - - if (Cmd == NULL) - { - fclose(in); - return Files; - } - - Arg1 = strtok_s(NULL, seps, &Context); - - if (Arg1 == NULL) - { - if (dummyconn.sysop) - Debugprintf("Bad Import Line %s", Buffer); - else - nodeprintf(conn, "Bad Import Line %s\r", Buffer); - - fclose(in); - return Files; - } - - strcpy(To, Arg1); - - if (DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) - { - if (CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL)) - { - Msg = conn->TempMsg; - - // SP is Ok, read message; - - ClearQueue(conn); - - fgets(Buffer, 99999, in); - strlop(Buffer, 10); - strlop(Buffer, 13); // Remove cr and/or lf - if (strlen(Buffer) > 60) - Buffer[60] = 0; - - strcpy(Msg->title, Buffer); - - // Read the lines - - conn->Flags |= GETTINGMESSAGE; - - Buffer[0] = 0; - - fgets(Buffer, 99999, in); - - while ((conn->Flags & GETTINGMESSAGE) && Buffer[0]) - { - strlop(Buffer, 10); - strlop(Buffer, 13); // Remove cr and/or lf - msglen = (int)strlen(Buffer); - Buffer[msglen++] = 13; - ProcessMsgLine(conn, conn->UserPointer,Buffer, msglen); - - Buffer[0] = 0; - fgets(Buffer, 99999, in); - } - - // Message completed (or off end of file) - - Files ++; - - ClearQueue(conn); - - if (Buffer[0]) - goto NextMessage; // We have read the SP/SB line; - else - { - fclose(in); - return Files; - } - } - else - { - // Create failed - - Flush(conn); - } - } - - // Search for next message - - Buffer[0] = 0; - fgets(Buffer, 99999, in); - - while (Buffer[0]) - { - strlop(Buffer, 10); - strlop(Buffer, 13); // Remove cr and/or lf - - if (_stricmp(Buffer, "/EX") == 0) - { - // Found end - - Buffer[0] = 0; - fgets(Buffer, 99999, in); - - if (dummyconn.sysop) - ClearQueue(conn); - else - Flush(conn); - - if (Buffer[0]) - goto NextMessage; // We have read the SP/SB line; - } - - Buffer[0] = 0; - fgets(Buffer, 99999, in); - } - } - - fclose(in); - - if (dummyconn.sysop) - ClearQueue(conn); - else - Flush(conn); - - return Files; -} -char * ReadMessageFileEx(struct MsgInfo * MsgRec) -{ - // Sets Message Size from File Size - - int msgno = MsgRec->number; - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - char * MsgBytes; - struct stat STAT; - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); - - if (stat(MsgFile, &STAT) == -1) - return NULL; - - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile == NULL) - return NULL; - - MsgBytes=malloc(FileSize+1); - - fread(MsgBytes, 1, FileSize, hFile); - - fclose(hFile); - - MsgBytes[FileSize]=0; - MsgRec->length = FileSize; - - return MsgBytes; -} - -char * ReadMessageFile(int msgno) -{ - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - char * MsgBytes; - struct stat STAT; - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); - - if (stat(MsgFile, &STAT) == -1) - return NULL; - - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile == NULL) - return NULL; - - MsgBytes = malloc(FileSize + 100); // A bit of space for alias substitution on B2 - - fread(MsgBytes, 1, FileSize, hFile); - - fclose(hFile); - - MsgBytes[FileSize]=0; - - return MsgBytes; -} - - -int QueueMsg(ConnectionInfo * conn, char * msg, int len) -{ - // Add Message to queue for this connection - - // UCHAR * OutputQueue; // Messages to user - // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message - // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. - - // Create or extend buffer - - GetSemaphore(&OutputSEM, 0); - - conn->OutputQueue=realloc(conn->OutputQueue, conn->OutputQueueLength + len); - - if (conn->OutputQueue == NULL) - { - // relloc failed - should never happen, but clean up - - CriticalErrorHandler("realloc failed to expand output queue"); - FreeSemaphore(&OutputSEM); - return 0; - } - - memcpy(&conn->OutputQueue[conn->OutputQueueLength], msg, len); - conn->OutputQueueLength += len; - FreeSemaphore(&OutputSEM); - - return len; -} - -void TrytoSend() -{ - // call Flush on any connected streams with queued data - - ConnectionInfo * conn; - struct ConsoleInfo * Cons; - - int n; - - for (n = 0; n < NumberofStreams; n++) - { - conn = &Connections[n]; - - if (conn->Active == TRUE) - { - Flush(conn); - - // if an FLARQ mail has been sent see if queues have cleared - - if (conn->BBSFlags & YAPPTX) - { - YAPPSendData(conn); - } - else if (conn->OutputQueue == NULL && (conn->BBSFlags & ARQMAILACK)) - { - int n = TXCount(conn->BPQStream); // All Sent and Acked? - - if (n == 0) - { - struct MsgInfo * Msg = conn->FwdMsg; - - conn->ARQClearCount--; - - if (conn->ARQClearCount <= 0) - { - Logprintf(LOG_BBS, conn, '>', "ARQ Send Complete"); - - // Mark mail as sent, and look for more - - clear_fwd_bit(Msg->fbbs, conn->UserPointer->BBSNumber); - set_fwd_bit(Msg->forw, conn->UserPointer->BBSNumber); - - // Only mark as forwarded if sent to all BBSs that should have it - - if (memcmp(Msg->fbbs, zeros, NBMASK) == 0) - { - Msg->status = 'F'; // Mark as forwarded - Msg->datechanged=time(NULL); - } - - conn->BBSFlags &= ~ARQMAILACK; - conn->UserPointer->ForwardingInfo->MsgCount--; - - SaveMessageDatabase(); - SendARQMail(conn); // See if any more - close if not - } - } - else - conn->ARQClearCount = 10; - } - } - } -#ifndef LINBPQ - for (Cons = ConsHeader[0]; Cons; Cons = Cons->next) - { - if (Cons->Console) - Flush(Cons->Console); - } -#endif -} - - -void Flush(CIRCUIT * conn) -{ - int tosend, len, sent; - - // Try to send data to user. May be stopped by user paging or node flow control - - // UCHAR * OutputQueue; // Messages to user - // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message - // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. - - // BOOL Paging; // Set if user wants paging - // int LinesSent; // Count when paging - // int PageLen; // Lines per page - - - if (conn->OutputQueue == NULL) - { - // Nothing to send. If Close after Flush is set, disconnect - - if (conn->CloseAfterFlush) - { - conn->CloseAfterFlush--; - - if (conn->CloseAfterFlush) - return; - - Disconnect(conn->BPQStream); - conn->ErrorCount = 0; - } - - return; // Nothing to send - } - tosend = conn->OutputQueueLength - conn->OutputGetPointer; - - sent=0; - - while (tosend > 0) - { - if (TXCount(conn->BPQStream) > 15) - return; // Busy - - if (conn->BBSFlags & SYSOPCHAT) // Suspend queued output while sysop chatting - return; - - if (conn->Paging && (conn->LinesSent >= conn->PageLen)) - return; - - if (tosend <= conn->paclen) - len=tosend; - else - len=conn->paclen; - - GetSemaphore(&OutputSEM, 0); - - if (conn->Paging) - { - // look for CR chars in message to send. Increment LinesSent, and stop if at limit - - UCHAR * ptr1 = &conn->OutputQueue[conn->OutputGetPointer]; - UCHAR * ptr2; - int lenleft = len; - - ptr2 = memchr(ptr1, 0x0d, len); - - while (ptr2) - { - conn->LinesSent++; - ptr2++; - lenleft = len - (int)(ptr2 - ptr1); - - if (conn->LinesSent >= conn->PageLen) - { - len = (int)(ptr2 - &conn->OutputQueue[conn->OutputGetPointer]); - - SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); - conn->OutputGetPointer+=len; - tosend-=len; - SendUnbuffered(conn->BPQStream, "bort, Continue..>", 25); - FreeSemaphore(&OutputSEM); - return; - - } - ptr2 = memchr(ptr2, 0x0d, lenleft); - } - } - - SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); - - conn->OutputGetPointer+=len; - - FreeSemaphore(&OutputSEM); - - tosend-=len; - sent++; - - if (sent > 15) - return; - } - - // All Sent. Free buffers and reset pointers - - conn->LinesSent = 0; - - ClearQueue(conn); -} - -VOID ClearQueue(ConnectionInfo * conn) -{ - if (conn->OutputQueue == NULL) - return; - - GetSemaphore(&OutputSEM, 0); - - free(conn->OutputQueue); - - conn->OutputQueue=NULL; - conn->OutputGetPointer=0; - conn->OutputQueueLength=0; - - FreeSemaphore(&OutputSEM); -} - - - -VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB) -{ - struct UserInfo * user; - - Msg->status='K'; - Msg->datechanged=time(NULL); - - // Remove any forwarding references - - if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) - { - for (user = BBSChain; user; user = user->BBSNext) - { - if (check_fwd_bit(Msg->fbbs, user->BBSNumber)) - { - user->ForwardingInfo->MsgCount--; - clear_fwd_bit(Msg->fbbs, user->BBSNumber); - } - } - } - if (SaveDB) - SaveMessageDatabase(); - RebuildNNTPList(); -} - -void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) -{ - int msgno=-1; - struct MsgInfo * Msg; - - while (Arg1) - { - msgno = atoi(Arg1); - - if (msgno > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return; - } - - Msg = GetMsgFromNumber(msgno); - - if (Msg == NULL) - { - nodeprintf(conn, "Message %d not found\r", msgno); - goto Next; - } - - if (Msg->type != 'T') - { - nodeprintf(conn, "Message %d not an NTS Message\r", msgno); - goto Next; - } - - if (Msg->status == 'N') - nodeprintf(conn, "Warning - Message has status N\r"); - - Msg->status = 'D'; - Msg->datechanged=time(NULL); - SaveMessageDatabase(); - - nodeprintf(conn, "Message #%d Flagged as Delivered\r", msgno); - Next: - Arg1 = strtok_s(NULL, " \r", &Context); - } - - return; -} - -void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) -{ - int msgno=-1; - int i; - struct MsgInfo * Msg; - - // Param is either ALL or a list of numbers - - if (Arg1 == NULL) - { - nodeprintf(conn, "No message number\r"); - return; - } - - if (_stricmp(Arg1, "ALL") == 0) - { - for (i=NumberofMessages; i>0; i--) - { - Msg = MsgHddrPtr[i]; - - if (Msg->status == 'H') - { - if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) - Msg->status = '$'; // Has forwarding - else - Msg->status = 'N'; - - nodeprintf(conn, "Message #%d Unheld\r", Msg->number); - } - } - return; - } - - while (Arg1) - { - msgno = atoi(Arg1); - Msg = GetMsgFromNumber(msgno); - - if (Msg) - { - if (Msg->status == 'H') - { - if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) - Msg->status = '$'; // Has forwarding - else - Msg->status = 'N'; - - nodeprintf(conn, "Message #%d Unheld\r", msgno); - } - else - { - nodeprintf(conn, "Message #%d was not held\r", msgno); - } - } - else - nodeprintf(conn, "Message #%d not found\r", msgno); - - Arg1 = strtok_s(NULL, " \r", &Context); - } - - return; -} - -void DoKillCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) -{ - int msgno=-1; - int i; - struct MsgInfo * Msg; - - switch (toupper(Cmd[1])) - { - - case 0: // Just K - - while (Arg1) - { - msgno = atoi(Arg1); - KillMessage(conn, user, msgno); - - Arg1 = strtok_s(NULL, " \r", &Context); - } - - SaveMessageDatabase(); - return; - - case 'M': // Kill Mine - - for (i=NumberofMessages; i>0; i--) - { - Msg = MsgHddrPtr[i]; - - if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) - { - if (Msg->type == 'P' && Msg->status == 'Y') - { - FlagAsKilled(Msg, FALSE); - nodeprintf(conn, "Message #%d Killed\r", Msg->number); - } - } - } - - SaveMessageDatabase(); - return; - - case 'H': // Kill Held - - if (conn->sysop) - { - for (i=NumberofMessages; i>0; i--) - { - Msg = MsgHddrPtr[i]; - - if (Msg->status == 'H') - { - FlagAsKilled(Msg, FALSE); - nodeprintf(conn, "Message #%d Killed\r", Msg->number); - } - } - } - SaveMessageDatabase(); - return; - - case '>': // K> - Kill to - - if (conn->sysop) - { - if (Arg1) - if (KillMessagesTo(conn, user, Arg1) == 0) - BBSputs(conn, "No Messages found\r"); - - return; - } - - case '<': - - if (conn->sysop) - { - if (Arg1) - if (KillMessagesFrom(conn, user, Arg1) == 0); - BBSputs(conn, "No Messages found\r"); - - return; - } - } - - nodeprintf(conn, "*** Error: Invalid Kill option %c\r", Cmd[1]); - - return; - -} - -int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call) -{ - int i, Msgs = 0; - struct MsgInfo * Msg; - - for (i=NumberofMessages; i>0; i--) - { - Msg = MsgHddrPtr[i]; - if (Msg->status != 'K' && _stricmp(Msg->to, Call) == 0) - { - Msgs++; - KillMessage(conn, user, MsgHddrPtr[i]->number); - } - } - - SaveMessageDatabase(); - return(Msgs); -} - -int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call) -{ - int i, Msgs = 0; - struct MsgInfo * Msg; - - - for (i=NumberofMessages; i>0; i--) - { - Msg = MsgHddrPtr[i]; - if (Msg->status != 'K' && _stricmp(Msg->from, Call) == 0) - { - Msgs++; - KillMessage(conn, user, MsgHddrPtr[i]->number); - } - } - - SaveMessageDatabase(); - return(Msgs); -} - -BOOL OkToKillMessage(BOOL SYSOP, char * Call, struct MsgInfo * Msg) -{ - if (SYSOP || (Msg->type == 'T' && UserCantKillT == FALSE)) - return TRUE; - - if (Msg->type == 'P') - if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) - return TRUE; - - if (Msg->type == 'B') - if (_stricmp(Msg->from, Call) == 0) - return TRUE; - - return FALSE; -} - -void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) -{ - struct MsgInfo * Msg; - - Msg = GetMsgFromNumber(msgno); - - if (Msg == NULL || Msg->status == 'K') - { - nodeprintf(conn, "Message %d not found\r", msgno); - return; - } - - if (OkToKillMessage(conn->sysop, user->Call, Msg)) - { - FlagAsKilled(Msg, FALSE); - nodeprintf(conn, "Message #%d Killed\r", msgno); - } - else - nodeprintf(conn, "Not your message\r"); -} - - -BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp) -{ - char FullFrom[80]; - char FullTo[80]; - - strcpy(FullFrom, Msg->from); - - if ((_stricmp(Msg->from, "RMS:") == 0) || (_stricmp(Msg->from, "SMTP:") == 0) || - Temp->SendFullFrom || (_stricmp(Msg->emailfrom, "@winlink.org") == 0)) - strcat(FullFrom, Msg->emailfrom); - - if (_stricmp(Msg->to, "RMS") == 0) - { - sprintf(FullTo, "RMS:%s", Msg->via); - nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", - Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); - } - else - - if (Msg->to[0] == 0 && Msg->via[0] != 0) - { - sprintf(FullTo, "smtp:%s", Msg->via); - nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", - Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); - } - - else - if (Msg->via[0] != 0) - { - char Via[80]; - strcpy(Via, Msg->via); - strlop(Via, '.'); // Only show first part of via - nodeprintf(conn, "%-6d %s %c%c %5d %-7s@%-6s %-6s %-s\r", - Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, Via, FullFrom, Msg->title); - } - else - nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", - Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, FullFrom, Msg->title); - - // if paging, stop two before page lengh. This lets us send the continue prompt, save status - // and exit without triggering the system paging code. We can then read a message then resume listing - - if (Temp->ListActive && conn->Paging) - { - Temp->LinesSent++; - - if ((Temp->LinesSent + 1) >= conn->PageLen) - { - nodeprintf(conn, "bort, , = Continue..>"); - Temp->LastListedInPagedMode = Msg->number; - Temp->ListSuspended = TRUE; - return TRUE; - } - } - - return FALSE; -} - -void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context) -{ - struct TempUserInfo * Temp = user->Temp; - struct MsgInfo * Msg; - - // Allow compound selection, eg LTN or LFP - - // types P N T - // Options LL LR L< L> L@ LM LC (L* used internally for just L, ie List New - // Status N Y H F K D - - // Allowing options in any order complicates paging. May be best to parse options once and restore if paging. - - Temp->ListActive = TRUE; - Temp->LinesSent = 0; - - if (Resuming) - { - // Entered after a paging pause. Selection fields are already set up - - // We have reentered list command after a pause. The next message to list is in Temp->LastListedInPagedMode - -// Start = Temp->LastListedInPagedMode; - Temp->ListSuspended = FALSE; - } - else - { - Temp->ListRangeEnd = LatestMsg; - Temp->ListRangeStart = 1; - Temp->LLCount = 0; - Temp->SendFullFrom = 0; - Temp->ListType = 0; - Temp->ListStatus = 0; - Temp->ListSelector = 0; - Temp->UpdateLatest = 0; - Temp->LastListParams[0] = 0; - Temp->IncludeKilled = 1; // SYSOP include Killed except LM - - //Analyse L params. - - _strupr(Cmd); - - if (strcmp(Cmd, "LC") == 0) // List Bull Categories - { - ListCategories(conn); - return; - } - - // if command is just L or LR start from last listed - - if (Arg1 == NULL) - { - if (strcmp(Cmd, "L") == 0 || strcmp(Cmd, "LR") == 0) - { - if (LatestMsg == conn->lastmsg) - { - BBSputs(conn, "No New Messages\r"); - return; - } - - Temp->UpdateLatest = 1; - Temp->ListRangeStart = conn->lastmsg; - } - } - - if (strchr(Cmd, 'V')) // Verbose - Temp->SendFullFrom = 'V'; - - if (strchr(Cmd, 'R')) - Temp->ListDirn = 'R'; - else - Temp->ListDirn = '*'; // Default newest first - - Cmd++; // skip L - - if (strchr(Cmd, 'T')) - Temp->ListType = 'T'; - else if (strchr(Cmd, 'P')) - Temp->ListType = 'P'; - else if (strchr(Cmd, 'B')) - Temp->ListType = 'B'; - - if (strchr(Cmd, 'N')) - Temp->ListStatus = 'N'; - else if (strchr(Cmd, 'Y')) - Temp->ListStatus = 'Y'; - else if (strchr(Cmd, 'F')) - Temp->ListStatus = 'F'; - else if (strchr(Cmd, '$')) - Temp->ListStatus = '$'; - else if (strchr(Cmd, 'H')) - Temp->ListStatus = 'H'; - else if (strchr(Cmd, 'K')) - Temp->ListStatus = 'K'; - else if (strchr(Cmd, 'D')) - Temp->ListStatus = 'D'; - - // H or K only by Sysop - - switch (Temp->ListStatus) - { - case 'K': - case 'H': // List Status - - if (conn->sysop) - break; - - BBSputs(conn, "LH or LK can only be used by SYSOP\r"); - return; - } - - if (strchr(Cmd, '<')) - Temp->ListSelector = '<'; - else if (strchr(Cmd, '>')) - Temp->ListSelector = '>'; - else if (strchr(Cmd, '@')) - Temp->ListSelector = '@'; - else if (strchr(Cmd, 'M')) - { - Temp->ListSelector = 'M'; - Temp->IncludeKilled = FALSE; - } - - // Param could be single number, number range or call - - if (Arg1) - { - if (strchr(Cmd, 'L')) // List Last - { - // Param is number - - if (Arg1) - Temp->LLCount = atoi(Arg1); - } - else - { - // Range nnn-nnn or single value or callsign - - char * Arg2, * Arg3, * Range; - char seps[] = " \t\r"; - UINT From=LatestMsg, To=0; - - Arg2 = strtok_s(NULL, seps, &Context); - Arg3 = strtok_s(NULL, seps, &Context); - - if (Temp->ListSelector && Temp->ListSelector != 'M') - { - // < > or @ - first param is callsign - - strcpy(Temp->LastListParams, Arg1); - - // Just possible number range - - Arg1 = Arg2; - Arg2 = Arg3; - Arg3 = strtok_s(NULL, seps, &Context); - } - - if (Arg1) - { - Range = strchr(Arg1, '-'); - - // A number could be a Numeric Bull Dest (eg 44) - // I think this can only resaonably be > - - if (isdigits(Arg1)) - To = From = atoi(Arg1); - - if (Arg2) - From = atoi(Arg2); - else - { - if (Range) - { - Arg3 = strlop(Arg1, '-'); - - To = atoi(Arg1); - - if (Arg3 && Arg3[0]) - From = atoi(Arg3); - else - From = LatestMsg; - } - } - if (From > 100000 || To > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return; - } - Temp->ListRangeStart = To; - Temp->ListRangeEnd = From; - } - } - } - } - - // Run through all messages (either forwards or backwards) and list any that match all selection criteria - - while (1) - { - if (Temp->ListDirn == 'R') - Msg = GetMsgFromNumber(Temp->ListRangeStart); - else - Msg = GetMsgFromNumber(Temp->ListRangeEnd); - - - if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop, Temp->IncludeKilled)) // Check if user is allowed to list this message - { - // Check filters - - if (Temp->ListStatus && Temp->ListStatus != Msg->status) - goto skip; - - if (Temp->ListType && Temp->ListType != Msg->type) - goto skip; - - if (Temp->ListSelector == '<') - if (_stricmp(Msg->from, Temp->LastListParams) != 0) - goto skip; - - if (Temp->ListSelector == '>') - if (_stricmp(Msg->to, Temp->LastListParams) != 0) - goto skip; - - if (Temp->ListSelector == '@') - if (_memicmp(Msg->via, Temp->LastListParams, strlen(Temp->LastListParams)) != 0 && - (_stricmp(Temp->LastListParams, "SMTP:") != 0 || Msg->to[0] != 0)) - goto skip; - - if (Temp->ListSelector == 'M') - if (_stricmp(Msg->to, user->Call) != 0 && - (_stricmp(Msg->to, "SYSOP") != 0 || ((user->flags & F_SYSOP_IN_LM) == 0))) - - goto skip; - - if (ListMessage(Msg, conn, Temp)) - { - if (Temp->ListDirn == 'R') - Temp->ListRangeStart++; - else - Temp->ListRangeEnd--; - - return; // Hit page limit - } - - if (Temp->LLCount) - { - Temp->LLCount--; - if (Temp->LLCount == 0) - return; // LL count reached - } -skip:; - } - - if (Temp->ListRangeStart == Temp->ListRangeEnd) - { - // if using L or LR (list new) update last listed field - - if (Temp->UpdateLatest) - conn->lastmsg = LatestMsg; - - return; - } - - if (Temp->ListDirn == 'R') - Temp->ListRangeStart++; - else - Temp->ListRangeEnd--; - - if (Temp->ListRangeStart > 100000 || Temp->ListRangeEnd < 0) // Loop protection! - return; - - } - -/* - - switch (Cmd[0]) - { - - case '*': // Just L - case 'R': // LR = List Reverse - - if (Arg1) - { - // Range nnn-nnn or single value - - char * Arg2, * Arg3; - char * Context; - char seps[] = " -\t\r"; - UINT From=LatestMsg, To=0; - char * Range = strchr(Arg1, '-'); - - Arg2 = strtok_s(Arg1, seps, &Context); - Arg3 = strtok_s(NULL, seps, &Context); - - if (Arg2) - To = From = atoi(Arg2); - - if (Arg3) - From = atoi(Arg3); - else - if (Range) - From = LatestMsg; - - if (From > 100000 || To > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return; - } - - if (Cmd[1] == 'R') - { - if (Start) - To = Start + 1; - - ListMessagesInRangeForwards(conn, user, user->Call, From, To, Temp->SendFullFrom); - } - else - { - if (Start) - From = Start - 1; - - ListMessagesInRange(conn, user, user->Call, From, To, Temp->SendFullFrom); - } - } - else - - if (LatestMsg == conn->lastmsg) - BBSputs(conn, "No New Messages\r"); - else if (Cmd[1] == 'R') - ListMessagesInRangeForwards(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); - else - ListMessagesInRange(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); - - conn->lastmsg = LatestMsg; - - return; - - - case 'L': // List Last - - if (Arg1) - { - int i = atoi(Arg1); - int m = NumberofMessages; - - if (Resuming) - i = Temp->LLCount; - else - Temp->LLCount = i; - - for (; i>0 && m != 0; i--) - { - m = GetUserMsg(m, user->Call, conn->sysop); - - if (m > 0) - { - if (Start && MsgHddrPtr[m]->number >= Start) - { - m--; - i++; - continue; - } - - Temp->LLCount--; - - if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) - return; // Hit page limit - m--; - } - } - } - return; - - case 'M': // LM - List Mine - - if (ListMessagesTo(conn, user, user->Call, SendFullFrom, Start) == 0) - BBSputs(conn, "No Messages found\r"); - return; - - case '>': // L> - List to - - if (Arg1) - if (ListMessagesTo(conn, user, Arg1, SendFullFrom, Start) == 0) - BBSputs(conn, "No Messages found\r"); - - - return; - - case '<': - - if (Arg1) - if (ListMessagesFrom(conn, user, Arg1, SendFullFrom, Start) == 0) - BBSputs(conn, "No Messages found\r"); - - return; - - case '@': - - if (Arg1) - if (ListMessagesAT(conn, user, Arg1, SendFullFrom, Start) == 0) - BBSputs(conn, "No Messages found\r"); - - return; - - case 'N': - case 'Y': - case 'F': - case '$': - case 'D': // Delivered NTS Traffic can be listed by anyone - { - int m = NumberofMessages; - - while (m > 0) - { - m = GetUserMsg(m, user->Call, conn->sysop); - - if (m > 0) - { - if (Start && MsgHddrPtr[m]->number >= Start) - { - m--; - continue; - } - - if (Temp->ListType) - { - if (MsgHddrPtr[m]->status == Cmd[1] && MsgHddrPtr[m]->type == Temp->ListType) - if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) - return; // Hit page limit - } - else - { - if (MsgHddrPtr[m]->status == toupper(Cmd[1])) - if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) - return; // Hit page limit - } - m--; - } - } - } - return; - - case 'K': - case 'H': // List Status - - if (conn->sysop) - { - int i, Msgs = Start; - - for (i=NumberofMessages; i>0; i--) - { - if (Start && MsgHddrPtr[i]->number >= Start) - continue; - - if (MsgHddrPtr[i]->status == toupper(Cmd[1])) - { - Msgs++; - if (ListMessage(MsgHddrPtr[i], conn, SendFullFrom)) - return; // Hit page limit - - } - } - - if (Msgs == 0) - BBSputs(conn, "No Messages found\r"); - } - else - BBSputs(conn, "LH or LK can only be used by SYSOP\r"); - - return; - - case 'C': - { - struct NNTPRec * ptr = FirstNNTPRec; - char Cat[100]; - char NextCat[100]; - int Line = 0; - int Count; - - while (ptr) - { - // if the next name is the same, combine counts - - strcpy(Cat, ptr->NewsGroup); - strlop(Cat, '.'); - Count = ptr->Count; - Catloop: - if (ptr->Next) - { - strcpy(NextCat, ptr->Next->NewsGroup); - strlop(NextCat, '.'); - if (strcmp(Cat, NextCat) == 0) - { - ptr = ptr->Next; - Count += ptr->Count; - goto Catloop; - } - } - - nodeprintf(conn, "%-6s %-3d", Cat, Count); - Line += 10; - if (Line > 80) - { - Line = 0; - nodeprintf(conn, "\r"); - } - - ptr = ptr->Next; - } - - if (Line) - nodeprintf(conn, "\r\r"); - else - nodeprintf(conn, "\r"); - - return; - } - } - - // Could be P B or T if specified without a status - - switch (Temp->ListType) - { - case 'P': - case 'B': - case 'T': // NTS Traffic can be listed by anyone - { - int m = NumberofMessages; - - while (m > 0) - { - m = GetUserMsg(m, user->Call, conn->sysop); - - if (m > 0) - { - if (Start && MsgHddrPtr[m]->number >= Start) - { - m--; - continue; - } - - if (MsgHddrPtr[m]->type == Temp->ListType) - if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) - return; // Hit page limit - m--; - } - } - - return; - } - } - -*/ - nodeprintf(conn, "*** Error: Invalid List option %c\r", Cmd[1]); - -} - -void ListCategories(ConnectionInfo * conn) -{ - // list bull categories - struct NNTPRec * ptr = FirstNNTPRec; - char Cat[100]; - char NextCat[100]; - int Line = 0; - int Count; - - while (ptr) - { - // if the next name is the same, combine counts - - strcpy(Cat, ptr->NewsGroup); - strlop(Cat, '.'); - Count = ptr->Count; -Catloop: - if (ptr->Next) - { - strcpy(NextCat, ptr->Next->NewsGroup); - strlop(NextCat, '.'); - if (strcmp(Cat, NextCat) == 0) - { - ptr = ptr->Next; - Count += ptr->Count; - goto Catloop; - } - } - - nodeprintf(conn, "%-6s %-3d", Cat, Count); - Line += 10; - if (Line > 80) - { - Line = 0; - nodeprintf(conn, "\r"); - } - - ptr = ptr->Next; - } - - if (Line) - nodeprintf(conn, "\r\r"); - else - nodeprintf(conn, "\r"); - - return; -} - -/* -int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) -{ - int i, Msgs = Start; - - for (i=NumberofMessages; i>0; i--) - { - if (MsgHddrPtr[i]->status == 'K') - continue; - - if (Start && MsgHddrPtr[i]->number >= Start) - continue; - - if ((_stricmp(MsgHddrPtr[i]->to, Call) == 0) || - ((conn->sysop) && _stricmp(Call, SYSOPCall) == 0 && - _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0 && (user->flags & F_SYSOP_IN_LM))) - { - Msgs++; - if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) - break; // Hit page limit - } - } - - return(Msgs); -} - -int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) -{ - int i, Msgs = 0; - - for (i=NumberofMessages; i>0; i--) - { - if (MsgHddrPtr[i]->status == 'K') - continue; - - if (Start && MsgHddrPtr[i]->number >= Start) - continue; - - if (_stricmp(MsgHddrPtr[i]->from, Call) == 0) - { - Msgs++; - if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) - return Msgs; // Hit page limit - - } - } - - return(Msgs); -} - -int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom,int Start) -{ - int i, Msgs = 0; - - for (i=NumberofMessages; i>0; i--) - { - if (MsgHddrPtr[i]->status == 'K') - continue; - - if (Start && MsgHddrPtr[i]->number >= Start) - continue; - - if (_memicmp(MsgHddrPtr[i]->via, Call, strlen(Call)) == 0 || - (_stricmp(Call, "SMTP:") == 0 && MsgHddrPtr[i]->to[0] == 0)) - { - Msgs++; - if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) - break; // Hit page limit - } - } - - return(Msgs); -} -*/ -int GetUserMsg(int m, char * Call, BOOL SYSOP) -{ - struct MsgInfo * Msg; - - // Get Next (usually backwards) message which should be shown to this user - // ie Not Deleted, and not Private unless to or from Call - - do - { - Msg=MsgHddrPtr[m]; - - if (SYSOP) return m; // Sysop can list or read anything - - if (Msg->status != 'K') - { - - if (Msg->status != 'H') - { - if (Msg->type == 'B' || Msg->type == 'T') return m; - - if (Msg->type == 'P') - { - if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) - return m; - } - } - } - - m--; - - } while (m> 0); - - return 0; -} - - -BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled) -{ - // Return TRUE if user is allowed to read message - - if (Msg->status == 'K' && IncludeKilled == 0) - return FALSE; - - if (SYSOP) - return TRUE; // Sysop can list or read anything - - if ((Msg->status != 'K') && (Msg->status != 'H')) - { - if (Msg->type == 'B' || Msg->type == 'T') return TRUE; - - if (Msg->type == 'P') - { - if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) - return TRUE; - } - } - - return FALSE; -} -/* -int GetUserMsgForwards(int m, char * Call, BOOL SYSOP) -{ - struct MsgInfo * Msg; - - // Get Next (usually backwards) message which should be shown to this user - // ie Not Deleted, and not Private unless to or from Call - - do - { - Msg=MsgHddrPtr[m]; - - if (Msg->status != 'K') - { - if (SYSOP) return m; // Sysop can list or read anything - - if (Msg->status != 'H') - { - if (Msg->type == 'B' || Msg->type == 'T') return m; - - if (Msg->type == 'P') - { - if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) - return m; - } - } - } - - m++; - - } while (m <= NumberofMessages); - - return 0; - -} - - -void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom) -{ - int m; - struct MsgInfo * Msg; - - for (m = Start; m >= End; m--) - { - Msg = GetMsgFromNumber(m); - - if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) - if (ListMessage(Msg, conn, Temp->SendFullFrom)) - return; // Hit page limit - - } -} - - -void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int End, int Start, BOOL SendFullFrom) -{ - int m; - struct MsgInfo * Msg; - - for (m = Start; m <= End; m++) - { - Msg = GetMsgFromNumber(m); - - if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) - if (ListMessage(Msg, conn, Temp->SendFullFrom)) - return; // Hit page limit - } -} -*/ - -void DoReadCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) -{ - int msgno=-1; - int i; - struct MsgInfo * Msg; - - - switch (toupper(Cmd[1])) - { - case 0: // Just R - - while (Arg1) - { - msgno = atoi(Arg1); - if (msgno > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return; - } - - ReadMessage(conn, user, msgno); - Arg1 = strtok_s(NULL, " \r", &Context); - } - - return; - - case 'M': // Read Mine (Unread Messages) - - if (toupper(Cmd[2]) == 'R') - { - for (i = 1; i <= NumberofMessages; i++) - { - Msg = MsgHddrPtr[i]; - - if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) - if (Msg->status == 'N') - ReadMessage(conn, user, Msg->number); - } - } - else - { - for (i = NumberofMessages; i > 0; i--) - { - Msg = MsgHddrPtr[i]; - - if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) - if (Msg->status == 'N') - ReadMessage(conn, user, Msg->number); - } - } - - return; - } - - nodeprintf(conn, "*** Error: Invalid Read option %c\r", Cmd[1]); - - return; -} - -int RemoveLF(char * Message, int len) -{ - // Remove lf chars and nulls - - char * ptr1, * ptr2; - - ptr1 = ptr2 = Message; - - while (len-- > 0) - { - while (*ptr1 == 0 && len) - { - ptr1++; - len--; - } - - *ptr2 = *ptr1; - - if (*ptr1 == '\r') - if (*(ptr1+1) == '\n') - { - ptr1++; - len--; - } - ptr1++; - ptr2++; - } - - return (int)(ptr2 - Message); -} - - - -int RemoveNulls(char * Message, int len) -{ - // Remove nulls - - char * ptr1, * ptr2; - - ptr1 = ptr2 = Message; - - while (len-- > 0) - { - while (*ptr1 == 0 && len) - { - ptr1++; - len--; - } - - *ptr2 = *ptr1; - - ptr1++; - ptr2++; - } - - return (int)(ptr2 - Message); -} - -void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) -{ - struct MsgInfo * Msg; - char * MsgBytes, * Save; - char FullTo[100]; - int Index = 0; - - Msg = GetMsgFromNumber(msgno); - - if (Msg == NULL) - { - nodeprintf(conn, "Message %d not found\r", msgno); - return; - } - - if (!CheckUserMsg(Msg, user->Call, conn->sysop, TRUE)) - { - nodeprintf(conn, "Message %d not for you\r", msgno); - return; - } - - if (_stricmp(Msg->to, "RMS") == 0) - sprintf(FullTo, "RMS:%s", Msg->via); - else - if (Msg->to[0] == 0) - sprintf(FullTo, "smtp:%s", Msg->via); - else - strcpy(FullTo, Msg->to); - - - nodeprintf(conn, "From: %s%s\rTo: %s\rType/Status: %c%c\rDate/Time: %s\rBid: %s\rTitle: %s\r\r", - Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); - - MsgBytes = Save = ReadMessageFile(msgno); - - if (Msg->type == 'P') - Index = PMSG; - else if (Msg->type == 'B') - Index = BMSG; - else if (Msg->type == 'T') - Index = TMSG; - - if (MsgBytes) - { - int Length = Msg->length; - - if (Msg->B2Flags & B2Msg) - { - char * ptr; - - // if message has attachments, display them if plain text - - if (Msg->B2Flags & Attachments) - { - char * FileName[100]; - int FileLen[100]; - int Files = 0; - int BodyLen, NewLen; - int i; - char *ptr2; - char Msg[512]; - int Len; - - ptr = MsgBytes; - - Len = sprintf(Msg, "Message has Attachments\r\r"); - QueueMsg(conn, Msg, Len); - - while(*ptr != 13) - { - ptr2 = strchr(ptr, 10); // Find CR - - if (memcmp(ptr, "Body: ", 6) == 0) - { - BodyLen = atoi(&ptr[6]); - } - - if (memcmp(ptr, "File: ", 6) == 0) - { - char * ptr1 = strchr(&ptr[6], ' '); // Find Space - - FileLen[Files] = atoi(&ptr[6]); - - FileName[Files++] = &ptr1[1]; - *(ptr2 - 1) = 0; - } - - ptr = ptr2; - ptr++; - } - - ptr += 2; // Over Blank Line and Separator - - NewLen = RemoveLF(ptr, BodyLen); - - QueueMsg(conn, ptr, NewLen); // Display Body - - ptr += BodyLen + 2; // to first file - - for (i = 0; i < Files; i++) - { - char Msg[512]; - int Len, n; - char * p = ptr; - char c; - - // Check if message is probably binary - - int BinCount = 0; - - NewLen = RemoveLF(ptr, FileLen[i]); // Removes LF agter CR but not on its own - - for (n = 0; n < NewLen; n++) - { - c = *p; - - if (c == 10) - *p = 13; - - if (c==0 || (c & 128)) - BinCount++; - - p++; - - } - - if (BinCount > NewLen/10) - { - // File is probably Binary - - Len = sprintf(Msg, "\rAttachment %s is a binary file\r", FileName[i]); - QueueMsg(conn, Msg, Len); - } - else - { - Len = sprintf(Msg, "\rAttachment %s\r\r", FileName[i]); - QueueMsg(conn, Msg, Len); - - user->Total.MsgsSent[Index] ++; - user->Total.BytesForwardedOut[Index] += NewLen; - - QueueMsg(conn, ptr, NewLen); - } - - ptr += FileLen[i]; - ptr +=2; // Over separator - } - goto sendEOM; - } - - // Remove B2 Headers (up to the File: Line) - - ptr = strstr(MsgBytes, "Body:"); - - if (ptr) - { - MsgBytes = ptr; - Length = (int)strlen(ptr); - } - } - - // Remove lf chars - - Length = RemoveLF(MsgBytes, Length); - - user->Total.MsgsSent[Index] ++; - user->Total.BytesForwardedOut[Index] += Length; - - QueueMsg(conn, MsgBytes, Length); - -sendEOM: - - free(Save); - - nodeprintf(conn, "\r\r[End of Message #%d from %s%s]\r", msgno, Msg->from, Msg->emailfrom); - - if ((_stricmp(Msg->to, user->Call) == 0) || ((conn->sysop) && (_stricmp(Msg->to, "SYSOP") == 0))) - { - if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) - { - if (Msg->status != 'Y') - { - Msg->status = 'Y'; - Msg->datechanged=time(NULL); - SaveMessageDatabase(); - } - } - } - } - else - { - nodeprintf(conn, "File for Message %d not found\r", msgno); - } -} - struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop) - { - int m=NumberofMessages; - - struct MsgInfo * Msg; - - do - { - m = GetUserMsg(m, Call, sysop); - - if (m == 0) - return NULL; - - Msg=MsgHddrPtr[m]; - - if (Msg->number == msgno) - return Msg; - - m--; - - } while (m> 0); - - return NULL; - -} - - -char * ReadInfoFile(char * File) -{ - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - char * MsgBytes; - struct stat STAT; - char * ptr1 = 0, * ptr2; - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/%s", BaseDir, File); - - if (stat(MsgFile, &STAT) == -1) - return NULL; - - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile == NULL) - return NULL; - - MsgBytes=malloc(FileSize+1); - - fread(MsgBytes, 1, FileSize, hFile); - - fclose(hFile); - - MsgBytes[FileSize] = 0; - - ptr1 = MsgBytes; - - // Replace LF or CRLF with CR - - // First remove cr from crlf - - while(ptr2 = strstr(ptr1, "\r\n")) - { - memmove(ptr2, ptr2 + 1, strlen(ptr2)); - } - - // Now replace lf with cr - - ptr1 = MsgBytes; - - while (*ptr1) - { - if (*ptr1 == '\n') - *(ptr1) = '\r'; - - ptr1++; - } - - return MsgBytes; -} - -char * FormatDateAndTime(time_t Datim, BOOL DateOnly) -{ - struct tm *tm; - static char Date[]="xx-xxx hh:mmZ"; - - tm = gmtime(&Datim); - - if (tm) - sprintf_s(Date, sizeof(Date), "%02d-%3s %02d:%02dZ", - tm->tm_mday, month[tm->tm_mon], tm->tm_hour, tm->tm_min); - - if (DateOnly) - { - Date[6]=0; - return Date; - } - - return Date; -} - -BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); - - -BOOL DoSendCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) -{ - // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY - - char * From = NULL; - char * BID = NULL; - char * ATBBS = NULL; - char seps[] = " \t\r"; - struct MsgInfo * OldMsg; - char OldTitle[62]; - char NewTitle[62]; - char To[100]= ""; - int msgno; - - if (Cmd[1] == 0) Cmd[1] ='P'; // Just S means SP - - switch (toupper(Cmd[1])) - { - case 'B': - - if (RefuseBulls) - { - nodeprintf(conn, "*** Error: This system doesn't allow sending Bulls\r"); - return FALSE; - } - - if (user->flags & F_NOBULLS) - { - nodeprintf(conn, "*** Error: You are not allowed to send Bulls\r"); - return FALSE; - } - - - case 'P': - case 'T': - - if (Arg1 == NULL) - { - nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); - return FALSE; - } - - strcpy(To, Arg1); - - if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) - return FALSE; - - return CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL); - - case 'R': - - if (Arg1 == NULL) - { - nodeprintf(conn, "*** Error: Message Number is missing\r"); - return FALSE; - } - - msgno = atoi(Arg1); - - if (msgno > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return FALSE; - } - - OldMsg = FindMessage(user->Call, msgno, conn->sysop); - - if (OldMsg == NULL) - { - nodeprintf(conn, "Message %d not found\r", msgno); - return FALSE; - } - - Arg1=&OldMsg->from[0]; - - strcpy(To, Arg1); - - if (_stricmp(Arg1, "SMTP:") == 0 || _stricmp(Arg1, "RMS:") == 0 || OldMsg->emailfrom) - { - // SMTP message. Need to get the real sender from the message - - sprintf(To, "%s%s", Arg1, OldMsg->emailfrom); - } - - if (!DecodeSendParams(conn, "", &From, To, &ATBBS, &BID)) - return FALSE; - - strcpy(OldTitle, OldMsg->title); - - if (strlen(OldTitle) > 57) OldTitle[57] = 0; - - strcpy(NewTitle, "Re:"); - strcat(NewTitle, OldTitle); - - return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); - - return TRUE; - - case 'C': - - if (Arg1 == NULL) - { - nodeprintf(conn, "*** Error: Message Number is missing\r"); - return FALSE; - } - - msgno = atoi(Arg1); - - if (msgno > 100000) - { - BBSputs(conn, "Message Number too high\r"); - return FALSE; - } - - Arg1 = strtok_s(NULL, seps, &Context); - - if (Arg1 == NULL) - { - nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); - return FALSE; - } - - strcpy(To, Arg1); - - if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) - return FALSE; - - OldMsg = FindMessage(user->Call, msgno, conn->sysop); - - if (OldMsg == NULL) - { - nodeprintf(conn, "Message %d not found\r", msgno); - return FALSE; - } - - strcpy(OldTitle, OldMsg->title); - - if (strlen(OldTitle) > 56) OldTitle[56] = 0; - - strcpy(NewTitle, "Fwd:"); - strcat(NewTitle, OldTitle); - - conn->CopyBuffer = ReadMessageFile(msgno); - - return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); - } - - - nodeprintf(conn, "*** Error: Invalid Send option %c\r", Cmd[1]); - - return FALSE; -} - -char * CheckToAddress(CIRCUIT * conn, char * Addr) -{ - // Check one element of Multiple Address - - if (conn == NULL || !(conn->BBSFlags & BBS)) - { - // if a normal user, check that TO and/or AT are known and warn if not. - - if (_stricmp(Addr, "SYSOP") == 0) - { - return _strdup(Addr); - } - - if (SendBBStoSYSOPCall) - if (_stricmp(Addr, BBSName) == 0) - return _strdup(SYSOPCall); - - - if (strchr(Addr, '@') == 0) - { - // No routing, if not a user and not known to forwarding or WP warn - - struct UserInfo * ToUser = LookupCall(Addr); - - if (ToUser) - { - // Local User. If Home BBS is specified, use it - - if (ToUser->HomeBBS[0]) - { - char * NewAddr = malloc(250); - if (conn) - nodeprintf(conn, "Address %s - @%s added from HomeBBS\r", Addr, ToUser->HomeBBS); - sprintf(NewAddr, "%s@%s", Addr, ToUser->HomeBBS); - return NewAddr; - } - } - else - { - WPRecP WP = LookupWP(Addr); - - if (WP) - { - char * NewAddr = malloc(250); - - if (conn) - nodeprintf(conn, "Address %s - @%s added from WP\r", Addr, WP->first_homebbs); - sprintf(NewAddr, "%s@%s", Addr, WP->first_homebbs); - return NewAddr; - } - } - } - } - - // Check SMTP and RMS Addresses - - if ((_memicmp(Addr, "rms:", 4) == 0) || (_memicmp(Addr, "rms/", 4) == 0)) - { - Addr[3] = ':'; // Replace RMS/ with RMS: - - if (conn && !FindRMS()) - { - nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); - return FALSE; - } - } - else if ((_memicmp(Addr, "smtp:", 5) == 0) || (_memicmp(Addr, "smtp/", 5) == 0)) - { - Addr[4] = ':'; // Replace smpt/ with smtp: - - if (ISP_Gateway_Enabled) - { - if (conn && (conn->UserPointer->flags & F_EMAIL) == 0) - { - nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); - return FALSE; - } - } - else - { - if (conn) - nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); - return FALSE; - } - } - - return _strdup(Addr); -} - - -char Winlink[] = "WINLINK.ORG"; - -BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char *To, char ** ATBBS, char ** BID) -{ - char * ptr; - char seps[] = " \t\r"; - WPRecP WP; - char * ToCopy = _strdup(To); - int Len; - - conn->ToCount = 0; - - // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY - - // Having trailing ; will mess up parsing multiple addresses, so remove. - - while (To[strlen(To) - 1] == ';') - To[strlen(To) - 1] = 0; - - if (strchr(Context, ';') || strchr(To, ';')) - { - // Multiple Addresses - put address list back together - - char * p; - - To[strlen(To)] = ' '; - Context = To; - - while (p = strchr(Context, ';')) - { - // Multiple Addressees - - To = strtok_s(NULL, ";", &Context); - Len = (int)strlen(To); - conn->To = realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); - if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) - conn->ToCount++; - } - - To = strtok_s(NULL, seps, &Context); - - Len = (int)strlen(To); - conn->To=realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); - if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) - conn->ToCount++; - } - else - { - // Single Call - - // accept CALL!CALL for source routed message - - if (strchr(To, '@') == 0 && strchr(To, '!')) // Bang route without @ - { - char * bang = strchr(To, '!'); - - memmove(bang + 1, bang, strlen(bang)); // Move !call down one - - *ATBBS = strlop(To, '!');; - } - - // Accept call@call (without spaces) - but check for smtp addresses - - if (_memicmp(To, "smtp:", 5) != 0 && _memicmp(To, "rms:", 4) != 0 && _memicmp(To, "rms/", 4) != 0) - { - ptr = strchr(To, '@'); - - if (ptr) - { - // If looks like a valid email address, treat as such - - int tolen; - *ATBBS = strlop(To, '@'); - - strlop(To, '-'); // Cant have SSID on BBS Name - - tolen = (int)strlen(To); - - if (tolen > 6 || !CheckifPacket(*ATBBS)) - { - // Probably Email address. Add smtp: or rms: - - if (FindRMS() || strchr(*ATBBS, '!')) // have RMS or source route - sprintf(To, "rms:%s", ToCopy); - else if (ISP_Gateway_Enabled) - sprintf(To, "smtp:%s", ToCopy); - else if (isAMPRMsg(ToCopy)) - sprintf(To, "rms:%s", ToCopy); - - } - } - } - } - - free(ToCopy); - - // Look for Optional fields; - - ptr = strtok_s(NULL, seps, &Context); - - while (ptr) - { - if (strcmp(ptr, "@") == 0) - { - *ATBBS = _strupr(strtok_s(NULL, seps, &Context)); - } - else if(strcmp(ptr, "<") == 0) - { - *From = strtok_s(NULL, seps, &Context); - } - else if (ptr[0] == '$') - *BID = &ptr[1]; - else - { - nodeprintf(conn, "*** Error: Invalid Format\r"); - return FALSE; - } - ptr = strtok_s(NULL, seps, &Context); - } - - // Only allow < from a BBS - - if (*From) - { - if (!(conn->BBSFlags & BBS)) - { - nodeprintf(conn, "*** < can only be used by a BBS\r"); - return FALSE; - } - } - - if (!*From) - *From = conn->UserPointer->Call; - - if (!(conn->BBSFlags & BBS)) - { - // if a normal user, check that TO and/or AT are known and warn if not. - - if (_stricmp(To, "SYSOP") == 0) - { - conn->LocalMsg = TRUE; - return TRUE; - } - - if (!*ATBBS && conn->ToCount == 0) - { - // No routing, if not a user and not known to forwarding or WP warn - - struct UserInfo * ToUser = LookupCall(To); - - if (ToUser) - { - // Local User. If Home BBS is specified, use it - - if (ToUser->flags & F_RMSREDIRECT) - { - // sent to Winlink - - *ATBBS = Winlink; - nodeprintf(conn, "Redirecting to winlink.org\r", *ATBBS); - } - else if (ToUser->HomeBBS[0]) - { - *ATBBS = ToUser->HomeBBS; - nodeprintf(conn, "Address @%s added from HomeBBS\r", *ATBBS); - } - else - { - conn->LocalMsg = TRUE; - } - } - else - { - conn->LocalMsg = FALSE; - WP = LookupWP(To); - - if (WP) - { - *ATBBS = WP->first_homebbs; - nodeprintf(conn, "Address @%s added from WP\r", *ATBBS); - } - } - } - } - return TRUE; -} - -BOOL CreateMessage(CIRCUIT * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title) -{ - struct MsgInfo * Msg, * TestMsg; - char * via = NULL; - char * FromHA; - - // Create a temp msg header entry - - if (conn->ToCount) - { - } - else - { - if (CheckRejFilters(From, ToCall, ATBBS, BID, MsgType)) - { - if ((conn->BBSFlags & BBS)) - { - nodeprintf(conn, "NO - REJECTED\r"); - if (conn->BBSFlags & OUTWARDCONNECT) - nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward - else - nodeprintf(conn, ">\r"); - } - else - nodeprintf(conn, "*** Error - Message Filters prevent sending this message\r"); - - return FALSE; - } - } - - Msg = malloc(sizeof (struct MsgInfo)); - - if (Msg == 0) - { - CriticalErrorHandler("malloc failed for new message header"); - return FALSE; - } - - memset(Msg, 0, sizeof (struct MsgInfo)); - - conn->TempMsg = Msg; - - Msg->type = MsgType; - - if (conn->UserPointer->flags & F_HOLDMAIL) - Msg->status = 'H'; - else - Msg->status = 'N'; - - Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); - - if (BID) - { - BIDRec * TempBID; - - // If P Message, dont immediately reject on a Duplicate BID. Check if we still have the message - // If we do, reject it. If not, accept it again. (do we need some loop protection ???) - - TempBID = LookupBID(BID); - - if (TempBID) - { - if (MsgType == 'B') - { - // Duplicate bid - - if ((conn->BBSFlags & BBS)) - { - nodeprintf(conn, "NO - BID\r"); - if (conn->BBSFlags & OUTWARDCONNECT) - nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward - else - nodeprintf(conn, ">\r"); - } - else - nodeprintf(conn, "*** Error- Duplicate BID\r"); - - return FALSE; - } - - TestMsg = GetMsgFromNumber(TempBID->u.msgno); - - // if the same TO we will assume the same message - - if (TestMsg && strcmp(TestMsg->to, ToCall) == 0) - { - // We have this message. If we have already forwarded it, we should accept it again - - if ((TestMsg->status == 'N') || (TestMsg->status == 'Y')|| (TestMsg->status == 'H')) - { - // Duplicate bid - - if ((conn->BBSFlags & BBS)) - { - nodeprintf(conn, "NO - BID\r"); - if (conn->BBSFlags & OUTWARDCONNECT) - nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward - else - nodeprintf(conn, ">\r"); - } - else - nodeprintf(conn, "*** Error- Duplicate BID\r"); - - return FALSE; - } - } - } - - if (strlen(BID) > 12) BID[12] = 0; - strcpy(Msg->bid, BID); - - // Save BID in temp list in case we are offered it again before completion - - TempBID = AllocateTempBIDRecord(); - strcpy(TempBID->BID, BID); - TempBID->u.conn = conn; - - } - - if (conn->ToCount) - { - } - else - { - if (_memicmp(ToCall, "rms:", 4) == 0) - { - if (!FindRMS()) - { - nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); - return FALSE; - } - - via=strlop(ToCall, ':'); - _strupr(ToCall); - } - else if (_memicmp(ToCall, "rms/", 4) == 0) - { - if (!FindRMS()) - { - nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); - return FALSE; - } - - via=strlop(ToCall, '/'); - _strupr(ToCall); - } - else if (_memicmp(ToCall, "smtp:", 5) == 0) - { - if (ISP_Gateway_Enabled) - { - if ((conn->UserPointer->flags & F_EMAIL) == 0) - { - nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); - return FALSE; - } - via=strlop(ToCall, ':'); - ToCall[0] = 0; - } - else - { - nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); - return FALSE; - } - } - else - { - _strupr(ToCall); - if (ATBBS) - via=_strupr(ATBBS); - } - - strlop(ToCall, '-'); // Remove any (illegal) ssid - if (strlen(ToCall) > 6) ToCall[6] = 0; - - strcpy(Msg->to, ToCall); - - if (SendBBStoSYSOPCall) - if (_stricmp(ToCall, BBSName) == 0) - strcpy(Msg->to, SYSOPCall); - - if (via) - { - if (strlen(via) > 40) via[40] = 0; - - strcpy(Msg->via, via); - } - - } // End of Multiple Dests - - // Look for HA in From (even if we shouldn't be getting it!) - - FromHA = strlop(From, '@'); - - - strlop(From, '-'); // Remove any (illegal) ssid - if (strlen(From) > 6) From[6] = 0; - strcpy(Msg->from, From); - - if (FromHA) - { - if (strlen(FromHA) > 39) FromHA[39] = 0; - Msg->emailfrom[0] = '@'; - strcpy(&Msg->emailfrom[1], _strupr(FromHA)); - } - - if (Title) // Only used by SR and SC - { - strcpy(Msg->title, Title); - conn->Flags |= GETTINGMESSAGE; - - // Create initial buffer of 10K. Expand if needed later - - conn->MailBuffer=malloc(10000); - conn->MailBufferSize=10000; - - nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); - return TRUE; - } - - if (conn->BBSFlags & FLARQMODE) - return TRUE; - - if (!(conn->BBSFlags & FBBCompressed)) - conn->Flags |= GETTINGTITLE; - - if (!(conn->BBSFlags & BBS)) - nodeprintf(conn, "Enter Title (only):\r"); - else - if (!(conn->BBSFlags & FBBForwarding)) - nodeprintf(conn, "OK\r"); - - return TRUE; -} - -VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int msglen) -{ - - conn->Flags &= ~GETTINGTITLE; - - if (msglen == 1) - { - nodeprintf(conn, "*** Message Cancelled\r"); - SendPrompt(conn, user); - return; - } - - if (msglen > 60) msglen = 60; - - Buffer[msglen-1] = 0; - - strcpy(conn->TempMsg->title, Buffer); - - // Create initial buffer of 10K. Expand if needed later - - conn->MailBuffer=malloc(10000); - conn->MailBufferSize=10000; - - if (conn->MailBuffer == NULL) - { - nodeprintf(conn, "Failed to create Message Buffer\r"); - return; - } - - conn->Flags |= GETTINGMESSAGE; - - if (!conn->BBSFlags & BBS) - nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); - -} - -VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int msglen) -{ - char * ptr2 = NULL; - - if (((msglen < 3) && (Buffer[0] == 0x1a)) || ((msglen == 4) && (_memicmp(Buffer, "/ex", 3) == 0))) - { - int Index = 0; - - if (conn->TempMsg->type == 'P') - Index = PMSG; - else if (conn->TempMsg->type == 'B') - Index = BMSG; - else if (conn->TempMsg->type == 'T') - Index = TMSG; - - conn->Flags &= ~GETTINGMESSAGE; - - user->Total.MsgsReceived[Index]++; - user->Total.BytesForwardedIn[Index] += conn->TempMsg->length; - - if (conn->ToCount) - { - // Multiple recipients - - struct MsgInfo * Msg = conn->TempMsg; - int i; - struct MsgInfo * SaveMsg = Msg; - char * SaveBody = conn->MailBuffer; - int SaveMsgLen = Msg->length; - BOOL SentToRMS = FALSE; - int ToLen = 0; - char * ToString = zalloc(conn->ToCount * 100); - - // If no BID provided, allocate one - - if (Msg->bid[0] == 0) - sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg + 1, BBSName); - - for (i = 0; i < conn->ToCount; i++) - { - char * Addr = conn->To[i]; - char * Via; - - if (_memicmp (Addr, "SMTP:", 5) == 0) - { - // For Email - - conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); - memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); - - conn->MailBuffer = malloc(SaveMsgLen + 10); - memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); - - Msg->to[0] = 0; - strcpy(Msg->via, &Addr[5]); - - CreateMessageFromBuffer(conn); - continue; - } - - if (_memicmp (Addr, "RMS:", 4) == 0) - { - // Add to B2 Message for RMS - - Addr+=4; - - Via = strlop(Addr, '@'); - - if (Via && _stricmp(Via, "winlink.org") == 0) - { - if (CheckifLocalRMSUser(Addr)) - { - // Local RMS - Leave Here - - Via = 0; // Drop Through - goto PktMsg; - } - else - { - ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); - continue; - } - } - - ToLen = sprintf(ToString, "%sTo: %s@%s\r\n", ToString, Addr, Via); - continue; - } - - _strupr(Addr); - - Via = strlop(Addr, '@'); - - if (Via && _stricmp(Via, "winlink.org") == 0) - { - if (CheckifLocalRMSUser(Addr)) - { - // Local RMS - Leave Here - - Via = 0; // Drop Through - } - else - { - ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); - - // Add to B2 Message for RMS - - continue; - } - } - - PktMsg: - - conn->LocalMsg = FALSE; - - // Normal BBS Message - - if (_stricmp(Addr, "SYSOP") == 0) - conn->LocalMsg = TRUE; - else - { - struct UserInfo * ToUser = LookupCall(Addr); - - if (ToUser) - conn->LocalMsg = TRUE; - } - - conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); - memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); - - conn->MailBuffer = malloc(SaveMsgLen + 10); - memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); - - strcpy(Msg->to, Addr); - - if (Via) - { - Msg->bid[0] = 0; // if we are forwarding it, we must change BID to be safe - strcpy(Msg->via, Via); - } - - CreateMessageFromBuffer(conn); - } - - if (ToLen) - { - char * B2Hddr = zalloc(ToLen + 1000); - int B2HddrLen; - char DateString[80]; - struct tm * tm; - time_t Date = time(NULL); - char Type[16] = "Private"; - - // Get Type - - if (conn->TempMsg->type == 'B') - strcpy(Type, "Bulletin"); - else if (conn->TempMsg->type == 'T') - strcpy(Type, "Traffic"); - - tm = gmtime(&Date); - - sprintf(DateString, "%04d/%02d/%02d %02d:%02d", - tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); - - conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); - memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); - - conn->MailBuffer = malloc(SaveMsgLen + 1000 + ToLen); - - Msg->B2Flags = B2Msg; - - B2HddrLen = sprintf(B2Hddr, - "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\n%sSubject: %s\r\nMbo: %s\r\nBody: %d\r\n\r\n", - SaveMsg->bid, DateString, Type, - SaveMsg->from, ToString, SaveMsg->title, BBSName, SaveMsgLen); - - memcpy(conn->MailBuffer, B2Hddr, B2HddrLen); - memcpy(&conn->MailBuffer[B2HddrLen], SaveBody, SaveMsgLen); - - Msg->length += B2HddrLen; - - strcpy(Msg->to, "RMS"); - - CreateMessageFromBuffer(conn); - - free(B2Hddr); - } - - free(SaveMsg); - free(SaveBody); - conn->MailBuffer = NULL; - conn->MailBufferSize=0; - - if (!(conn->BBSFlags & BBS)) - SendPrompt(conn, conn->UserPointer); - else - if (!(conn->BBSFlags & FBBForwarding)) - { - if (conn->BBSFlags & OUTWARDCONNECT) - BBSputs(conn, "F>\r"); // if Outward connect must be reverse forward - else - BBSputs(conn, ">\r"); - } - - /* - // From a client - Create one copy with all RMS recipients, and another for each packet recipient - - // Merge all RMS To: lines - - ToLen = 0; - ToString[0] = 0; - - for (i = 0; i < Recipients; i++) - { - if (LocalMsg[i]) - continue; // For a local RMS user - - if (_stricmp(Via[i], "WINLINK.ORG") == 0 || _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || - _stricmp(RecpTo[i], "RMS") == 0) - { - ToLen += strlen(HddrTo[i]); - strcat(ToString, HddrTo[i]); - } - } - - if (ToLen) - { - conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); - memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); - - conn->MailBuffer = malloc(SaveMsgLen + 1000); - memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); - - - memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); - memcpy(&conn->MailBuffer[B2To], ToString, ToLen); - - conn->TempMsg->length += ToLen; - - strcpy(Msg->to, "RMS"); - strcpy(Msg->via, "winlink.org"); - - // Must Change the BID - - Msg->bid[0] = 0; - - CreateMessageFromBuffer(conn); - } - - } - - free(ToString); - - for (i = 0; i < Recipients; i++) - { - // Only Process Non - RMS Dests or local RMS Users - - if (LocalMsg[i] == 0) - if (_stricmp (Via[i], "WINLINK.ORG") == 0 || - _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || - _stricmp(RecpTo[i], "RMS") == 0) - continue; - - conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); - memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); - - conn->MailBuffer = malloc(SaveMsgLen + 1000); - memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); - - // Add our To: - - ToLen = strlen(HddrTo[i]); - - if (_memicmp(HddrTo[i], "CC", 2) == 0) // Replace CC: with TO: - memcpy(HddrTo[i], "To", 2); - - memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); - memcpy(&conn->MailBuffer[B2To], HddrTo[i], ToLen); - - conn->TempMsg->length += ToLen; - - strcpy(Msg->to, RecpTo[i]); - strcpy(Msg->via, Via[i]); - - Msg->bid[0] = 0; - - CreateMessageFromBuffer(conn); - } - } // End not from RMS - - free(SaveMsg); - free(SaveBody); - conn->MailBuffer = NULL; - conn->MailBufferSize=0; - - SetupNextFBBMessage(conn); - return; - - } My__except_Routine("Process Multiple Destinations"); - - BBSputs(conn, "*** Program Error Processing Multiple Destinations\r"); - Flush(conn); - conn->CloseAfterFlush = 20; // 2 Secs - - return; -*/ - - conn->ToCount = 0; - - return; - } - - - CreateMessageFromBuffer(conn); - return; - - } - - Buffer[msglen++] = 0x0a; - - if ((conn->TempMsg->length + msglen) > conn->MailBufferSize) - { - conn->MailBufferSize += 10000; - conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); - - if (conn->MailBuffer == NULL) - { - nodeprintf(conn, "Failed to extend Message Buffer\r"); - - conn->Flags &= ~GETTINGMESSAGE; - return; - } - } - - memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, msglen); - - conn->TempMsg->length += msglen; -} - -VOID CreateMessageFromBuffer(CIRCUIT * conn) -{ - struct MsgInfo * Msg; - BIDRec * BIDRec; - char * ptr1, * ptr2 = NULL; - char * ptr3, * ptr4; - int FWDCount = 0; - char OldMess[] = "\r\n\r\nOriginal Message:\r\n\r\n"; - time_t Age; - int OurCount; - char * HoldReason = "User has Hold Messages flag set"; - struct UserInfo * user; - - -#ifndef LINBPQ - struct _EXCEPTION_POINTERS exinfo; -#endif - - // If doing SC, Append Old Message - - if (conn->CopyBuffer) - { - if ((conn->TempMsg->length + (int) strlen(conn->CopyBuffer) + 80 )> conn->MailBufferSize) - { - conn->MailBufferSize += (int)strlen(conn->CopyBuffer) + 80; - conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); - - if (conn->MailBuffer == NULL) - { - nodeprintf(conn, "Failed to extend Message Buffer\r"); - - conn->Flags &= ~GETTINGMESSAGE; - return; - } - } - - memcpy(&conn->MailBuffer[conn->TempMsg->length], OldMess, strlen(OldMess)); - - conn->TempMsg->length += (int)strlen(OldMess); - - memcpy(&conn->MailBuffer[conn->TempMsg->length], conn->CopyBuffer, strlen(conn->CopyBuffer)); - - conn->TempMsg->length += (int)strlen(conn->CopyBuffer); - - free(conn->CopyBuffer); - conn->CopyBuffer = NULL; - } - - // Allocate a message Record slot - - Msg = AllocateMsgRecord(); - memcpy(Msg, conn->TempMsg, sizeof(struct MsgInfo)); - - free(conn->TempMsg); - - // Set number here so they remain in sequence - - GetSemaphore(&MsgNoSemaphore, 0); - Msg->number = ++LatestMsg; - FreeSemaphore(&MsgNoSemaphore); - MsgnotoMsg[Msg->number] = Msg; - - if (Msg->status == 0) - Msg->status = 'N'; - - // Create BID if non supplied - - if (Msg->bid[0] == 0) - sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); - - // if message body had R: lines, get date created from last (not very accurate, but best we can do) - - // Also check if we have had message before to detect loops - - ptr1 = conn->MailBuffer; - OurCount = 0; - - // If it is a B2 Message, Must Skip B2 Header - - if (Msg->B2Flags & B2Msg) - { - ptr1 = strstr(ptr1, "\r\n\r\n"); - if (ptr1) - ptr1 += 4; - else - ptr1 = conn->MailBuffer; - } - -nextline: - - if (memcmp(ptr1, "R:", 2) == 0) - { - // Is if ours? - - // BPQ RLINE Format R:090920/1041Z 6542@N4JOA.#WPBFL.FL.USA.NOAM BPQ1.0.2 - - ptr3 = strchr(ptr1, '@'); - ptr4 = strchr(ptr1, '.'); - - if (ptr3 && ptr4 && (ptr4 > ptr3)) - { - if (memcmp(ptr3+1, BBSName, ptr4-ptr3-1) == 0) - OurCount++; - } - - GetWPBBSInfo(ptr1); // Create WP /I record from R: Line - - // see if another - - ptr2 = ptr1; // save - ptr1 = strchr(ptr1, '\r'); - if (ptr1 == 0) - { - Debugprintf("Corrupt Message %s from %s - truncated within R: line", Msg->bid, Msg->from); - return; - } - ptr1++; - if (*ptr1 == '\n') ptr1++; - - goto nextline; - } - - // ptr2 points to last R: line (if any) - - if (ptr2) - { - struct tm rtime; - time_t result; - - memset(&rtime, 0, sizeof(struct tm)); - - if (ptr2[10] == '/') - { - // Dodgy 4 char year - - sscanf(&ptr2[2], "%04d%02d%02d/%02d%02d", - &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); - rtime.tm_year -= 1900; - rtime.tm_mon--; - } - else if (ptr2[8] == '/') - { - sscanf(&ptr2[2], "%02d%02d%02d/%02d%02d", - &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); - - if (rtime.tm_year < 90) - rtime.tm_year += 100; // Range 1990-2089 - rtime.tm_mon--; - } - - // Otherwise leave date as zero, which should be rejected - - // result = _mkgmtime(&rtime); - - if ((result = mktime(&rtime)) != (time_t)-1 ) - { - result -= (time_t)_MYTIMEZONE; - - Msg->datecreated = result; - Age = (time(NULL) - result)/86400; - - if ( Age < -7) - { - Msg->status = 'H'; - HoldReason = "Suspect Date Sent"; - } - else if (Age > BidLifetime || Age > MaxAge) - { - Msg->status = 'H'; - HoldReason = "Message too old"; - - } - else - GetWPInfoFromRLine(Msg->from, ptr2, result); - } - else - { - // Can't decode R: Datestamp - - Msg->status = 'H'; - HoldReason = "Corrupt R: Line - can't determine age"; - } - - if (OurCount > 1) - { - // Message is looping - - Msg->status = 'H'; - HoldReason = "Message may be looping"; - - } - } - - if (strcmp(Msg->to, "WP") == 0) - { - // If Reject WP Bulls is set, Kill message here. - // It should only get here if B2 - otherwise it should be - // rejected earlier - - if (Msg->type == 'B' && FilterWPBulls) - Msg->status = 'K'; - - } - - conn->MailBuffer[Msg->length] = 0; - - if (CheckBadWords(Msg->title) || CheckBadWords(conn->MailBuffer)) - { - Msg->status = 'H'; - HoldReason = "Bad word in title or body"; - } - - if (CheckHoldFilters(Msg->from, Msg->to, Msg->via, Msg->bid)) - { - Msg->status = 'H'; - HoldReason = "Matched Hold Filters"; - } - - if (CheckValidCall(Msg->from) == 0) - { - Msg->status = 'H'; - HoldReason = "Probable Invalid From Call"; - } - - // Process any WP Messages - - if (strcmp(Msg->to, "WP") == 0) - { - if (Msg->status == 'N') - { - ProcessWPMsg(conn->MailBuffer, Msg->length, ptr2); - - if (Msg->type == 'P') // Kill any processed private WP messages. - { - char VIA[80]; - - strcpy(VIA, Msg->via); - strlop(VIA, '.'); - - if (strcmp(VIA, BBSName) == 0) - Msg->status = 'K'; - } - } - } - - CreateMessageFile(conn, Msg); - - BIDRec = AllocateBIDRecord(); - - strcpy(BIDRec->BID, Msg->bid); - BIDRec->mode = Msg->type; - BIDRec->u.msgno = LOWORD(Msg->number); - BIDRec->u.timestamp = LOWORD(time(NULL)/86400); - - if (Msg->length > MaxTXSize) - { - Msg->status = 'H'; - HoldReason = "Message too long"; - - if (!(conn->BBSFlags & BBS)) - nodeprintf(conn, "*** Warning Message length exceeds sysop-defined maximum of %d - Message will be held\r", MaxTXSize); - } - - // Check for message to internal server - - if (Msg->via[0] == 0 - || _stricmp(Msg->via, BBSName) == 0 // our BBS a - || _stricmp(Msg->via, AMPRDomain) == 0) // our AMPR Address - { - if (CheckforMessagetoServer(Msg)) - { - // Flag as killed and send prompt - - FlagAsKilled(Msg, TRUE); - - if (!(conn->BBSFlags & BBS)) - { - nodeprintf(conn, "Message %d to Server Processed and Killed.\r", Msg->number); - SendPrompt(conn, conn->UserPointer); - } - return; // no need to process further - } - } - - if (Msg->to[0]) - FWDCount = MatchMessagetoBBSList(Msg, conn); - else - { - // If addressed @winlink.org, and to a local user, Keep here. - - char * Call; - char * AT; - - // smtp or rms - don't warn no route - - FWDCount = 1; - - Call = _strupr(_strdup(Msg->via)); - AT = strlop(Call, '@'); - - if (AT && _stricmp(AT, "WINLINK.ORG") == 0) - { - struct UserInfo * user = LookupCall(Call); - - if (user) - { - if (user->flags & F_POLLRMS) - { - Logprintf(LOG_BBS, conn, '?', "SMTP Message @ winlink.org, but local RMS user - leave here"); - strcpy(Msg->to, Call); - strcpy(Msg->via, AT); - if (user->flags & F_BBS) // User is a BBS, so set FWD bit so he can get it - set_fwd_bit(Msg->fbbs, user->BBSNumber); - - } - } - } - free(Call); - } - - // Warn SYSOP if P or T forwarded in, and has nowhere to go - - if ((conn->BBSFlags & BBS) && Msg->type != 'B' && FWDCount == 0 && WarnNoRoute && - strcmp(Msg->to, "SYSOP") && strcmp(Msg->to, "WP")) - { - if (Msg->via[0]) - { - if (_stricmp(Msg->via, BBSName)) // Not for our BBS a - if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address - SendWarningToSYSOP(Msg); - } - else - { - // No via - is it for a local user? - - if (LookupCall(Msg->to) == 0) - SendWarningToSYSOP(Msg); - } - } - - if ((conn->BBSFlags & SYNCMODE) == 0) - { - if (!(conn->BBSFlags & BBS)) - { - nodeprintf(conn, "Message: %d Bid: %s Size: %d\r", Msg->number, Msg->bid, Msg->length); - - if (Msg->via[0]) - { - if (_stricmp(Msg->via, BBSName)) // Not for our BBS a - if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address - - if (FWDCount == 0 && Msg->to[0] != 0) // unless smtp msg - nodeprintf(conn, "@BBS specified, but no forwarding info is available - msg may not be delivered\r"); - } - else - { - if (FWDCount == 0 && conn->LocalMsg == 0 && Msg->type != 'B') - // Not Local and no forward route - nodeprintf(conn, "Message is not for a local user, and no forwarding info is available - msg may not be delivered\r"); - } - if (conn->ToCount == 0) - SendPrompt(conn, conn->UserPointer); - } - else - { - if (!(conn->BBSFlags & FBBForwarding)) - { - if (conn->ToCount == 0) - if (conn->BBSFlags & OUTWARDCONNECT) - nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward - else - nodeprintf(conn, ">\r"); - } - } - } - - if(Msg->to[0] == 0) - SMTPMsgCreated=TRUE; - - if (Msg->status != 'H' && Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) - Msg->status = '$'; // Has forwarding - - if (Msg->status == 'H') - { - int Length=0; - char * MailBuffer = malloc(100); - char Title[100]; - - Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); - sprintf(Title, "Message %d Held - %s", Msg->number, HoldReason); - SendMessageToSYSOP(Title, MailBuffer, Length); - } - - BuildNNTPList(Msg); // Build NNTP Groups list - - SaveMessageDatabase(); - SaveBIDDatabase(); - - // If Event Notifications enabled report a new message event - - if (reportNewMesageEvents) - { - char msg[200]; - - //12345 B 2053 TEST@ALL F6FBB 920325 This is the subject - - struct tm *tm = gmtime((time_t *)&Msg->datecreated); - - sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r", - Msg->number, Msg->type, Msg->length, Msg->to, - Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title); - -#ifdef WIN32 - if (pRunEventProgram) - pRunEventProgram("MailNewMsg.exe", msg); -#else - { - char prog[256]; - sprintf(prog, "%s/%s", BPQDirectory, "MailNewMsg"); - RunEventProgram(prog, msg); - } -#endif - } - - - if (EnableUI) -#ifdef LINBPQ - SendMsgUI(Msg); -#else - __try - { - SendMsgUI(Msg); - } - My__except_Routine("SendMsgUI"); -#endif - - user = LookupCall(Msg->to); - - if (user && (user->flags & F_APRSMFOR)) - { - char APRS[128]; - char Call[16]; - int SSID = user->flags >> 28; - - if (SSID) - sprintf(Call, "%s-%d", Msg->to, SSID); - else - strcpy(Call, Msg->to); - - sprintf(APRS, "New BBS Message %s From %s", Msg->title, Msg->from); - APISendAPRSMessage(APRS, Call); - } - - return; -} - -VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg) -{ - char MsgFile[MAX_PATH]; - FILE * hFile; - size_t WriteLen=0; - char Mess[255]; - int len; - BOOL AutoImport = FALSE; - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); - - // If title is "Batched messages for AutoImport from BBS xxxxxx and first line is S? and it is - // for this BBS, Import file and set message as Killed. May need to strip B2 Header and R: lines - - if (strcmp(Msg->to, BBSName) == 0 && strstr(Msg->title, "Batched messages for AutoImport from BBS ")) - { - UCHAR * ptr = conn->MailBuffer; - - // If it is a B2 Message, Must Skip B2 Header - - if (Msg->B2Flags & B2Msg) - { - ptr = strstr(ptr, "\r\n\r\n"); - if (ptr) - ptr += 4; - else - ptr = conn->MailBuffer; - } - - if (memcmp(ptr, "R:", 2) == 0) - { - ptr = strstr(ptr, "\r\n\r\n"); //And remove R: Lines - if (ptr) - ptr += 4; - } - - if (*(ptr) == 'S' && ptr[2] == ' ') - { - int HeaderLen = (int)(ptr - conn->MailBuffer); - Msg->length -= HeaderLen; - memmove(conn->MailBuffer, ptr, Msg->length); - Msg->status = 'K'; - AutoImport = TRUE; - } - } - - hFile = fopen(MsgFile, "wb"); - - if (hFile) - { - WriteLen = fwrite(conn->MailBuffer, 1, Msg->length, hFile); - fclose(hFile); - } - - if (AutoImport) - ImportMessages(NULL, MsgFile, TRUE); - - free(conn->MailBuffer); - conn->MailBufferSize=0; - conn->MailBuffer=0; - - if (WriteLen != Msg->length) - { - len = sprintf_s(Mess, sizeof(Mess), "Failed to create Message File\r"); - QueueMsg(conn, Mess, len); - Debugprintf(Mess); - } - return; -} - - - - -VOID SendUnbuffered(int stream, char * msg, int len) -{ -#ifndef LINBPQ - if (stream < 0) - WritetoConsoleWindow(stream, msg, len); - else -#endif - SendMsg(stream, msg, len); -} - -BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen); - -BOOL FindMessagestoForward (CIRCUIT * conn) -{ - struct UserInfo * user = conn->UserPointer; - -#ifndef LINBPQ - - struct _EXCEPTION_POINTERS exinfo; - - __try { -#endif - - // This is a hack so Winpack or WLE users can use forwarding - // protocols to get their messages without being defined as a BBS - - // !!IMPORTANT Getting this wrong can see message repeatedly proposed !! - // !! Anything sent using this must be killed if sent or rejected. - - // I'm not sure about this. I think I only need the PacLinkCalls - // if from RMS Express, and it always sends an FW; line - // Ah, no. What about WinPack ?? - // If from RMS Express must have Temp_B2 or BBS set or SID will - // be rejected. So maybe just Temp_B2 && BBS = 0?? - // No, someone may have Temp_B2 set and not be using WLE ?? So what ?? - - if ((user->flags & F_Temp_B2_BBS) && ((user->flags & F_BBS) == 0) || conn->RMSExpress || conn->PAT) - { - if (conn->PacLinkCalls == NULL) - { - // create a list with just the user call - - char * ptr1; - - conn->PacLinkCalls = zalloc(30); - - ptr1 = (char *)conn->PacLinkCalls; - ptr1 += 16; // Must be room for a null pointer on end (64 bit bug) - strcpy(ptr1, user->Call); - - conn->PacLinkCalls[0] = ptr1; - } - } - - if (conn->SendT && FindMessagestoForwardLoop(conn, 'T', conn->MaxTLen)) - { - conn->LastForwardType = 'T'; - return TRUE; - } - - if (conn->LastForwardType == 'T') - conn->NextMessagetoForward = FirstMessageIndextoForward; - - if (conn->SendP && FindMessagestoForwardLoop(conn, 'P', conn->MaxPLen)) - { - conn->LastForwardType = 'P'; - return TRUE; - } - - if (conn->LastForwardType == 'P') - conn->NextMessagetoForward = FirstMessageIndextoForward; - - if (conn->SendB && FindMessagestoForwardLoop(conn, 'B', conn->MaxBLen)) - { - conn->LastForwardType = 'B'; - return TRUE; - } - - conn->LastForwardType = 0; - return FALSE; -#ifndef LINBPQ - } My__except_Routine("FindMessagestoForward"); -#endif - return FALSE; - -} - - -BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen) -{ - // See if any messages are queued for this BBS - - int m; - struct MsgInfo * Msg; - struct UserInfo * user = conn->UserPointer; - struct FBBHeaderLine * FBBHeader; - BOOL Found = FALSE; - char RLine[100]; - int TotalSize = 0; - time_t NOW = time(NULL); - -// Debugprintf("FMTF entered Call %s Type %c Maxlen %d NextMsg = %d BBSNo = %d", -// conn->Callsign, Type, MaxLen, conn->NextMessagetoForward, user->BBSNumber); - - if (conn->PacLinkCalls || (conn->UserPointer->flags & F_NTSMPS)) // Looking for all messages, so reset - conn->NextMessagetoForward = 1; - - conn->FBBIndex = 0; - - for (m = conn->NextMessagetoForward; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - // If an NTS MPS, see if anything matches - - if (Type == 'T' && (conn->UserPointer->flags & F_NTSMPS)) - { - struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; - int depth; - - if (Msg->type == 'T' && Msg->status == 'N' && Msg->length <= MaxLen && ForwardingInfo) - { - depth = CheckBBSToForNTS(Msg, ForwardingInfo); - - if (depth > -1 && Msg->Locked == 0) - goto Forwardit; - - depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); - - if (depth && Msg->Locked == 0) - goto Forwardit; - - depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); - - if (depth > -1 && Msg->Locked == 0) - goto Forwardit; - } - } - - // If forwarding to Paclink or RMS Express, look for any message matching the - // requested call list with status 'N' (maybe should also be 'P' ??) - - if (conn->PacLinkCalls) - { - int index = 1; - - char * Call = conn->PacLinkCalls[0]; - - while (Call) - { - if (Msg->type == Type && Msg->status == 'N') - { -// Debugprintf("Comparing RMS Call %s %s", Msg->to, Call); - if (_stricmp(Msg->to, Call) == 0) - if (Msg->status == 'N' && Msg->type == Type && Msg->length <= MaxLen) - goto Forwardit; - else - Debugprintf("Call Match but Wrong Type/Len %c %d", Msg->type, Msg->length); - } - Call = conn->PacLinkCalls[index++]; - } -// continue; - } - - if (Msg->type == Type && Msg->length <= MaxLen && (Msg->status != 'H') - && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) - { - // Message to be sent - do a consistancy check (State, etc) - - Forwardit: - - if (Msg->Defered > 0) // = response received - { - Msg->Defered--; - Debugprintf("Message %d deferred", Msg->number); - continue; - } - - if ((Msg->from[0] == 0) || (Msg->to[0] == 0)) - { - int Length=0; - char * MailBuffer = malloc(100); - char Title[100]; - - Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); - sprintf(Title, "Message %d Held - %s", Msg->number, "Missing From: or To: field"); - SendMessageToSYSOP(Title, MailBuffer, Length); - - Msg->status = 'H'; - continue; - } - - conn->NextMessagetoForward = m + 1; // So we don't offer again if defered - - Msg->Locked = 1; // So other MPS can't pick it up - - // if FBB forwarding add to list, eise save pointer - - if (conn->BBSFlags & FBBForwarding) - { - struct tm *tm; - time_t temp; - - FBBHeader = &conn->FBBHeaders[conn->FBBIndex++]; - - FBBHeader->FwdMsg = Msg; - FBBHeader->MsgType = Msg->type; - FBBHeader->Size = Msg->length; - TotalSize += Msg->length; - strcpy(FBBHeader->From, Msg->from); - strcpy(FBBHeader->To, Msg->to); - strcpy(FBBHeader->ATBBS, Msg->via); - strcpy(FBBHeader->BID, Msg->bid); - - // Set up R:Line, so se can add its length to the sise - - memcpy(&temp, &Msg->datereceived, sizeof(time_t)); - tm = gmtime(&temp); - - FBBHeader->Size += sprintf_s(RLine, sizeof(RLine),"R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", - tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, - Msg->number, BBSName, HRoute, RlineVer); - - // If using B2 forwarding we need the message size and Compressed size for FC proposal - - if (conn->BBSFlags & FBBB2Mode) - { - if (CreateB2Message(conn, FBBHeader, RLine) == FALSE) - { - char * MailBuffer = malloc(100); - char Title[100]; - int Length; - - // Corrupt B2 Message - - Debugprintf("Corrupt B2 Message found - Message %d will be held", Msg->number); - Msg->status = 'H'; - SaveMessageDatabase(); - - conn->FBBIndex--; - TotalSize -= Msg->length; - memset(&conn->FBBHeaders[conn->FBBIndex], 0, sizeof(struct FBBHeaderLine)); - - Length = sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); - sprintf(Title, "Message %d Held - %s", Msg->number, "Corrupt B2 Message"); - SendMessageToSYSOP(Title, MailBuffer, Length); - - continue; - } - } - - if (conn->FBBIndex == 5 || TotalSize > user->ForwardingInfo->MaxFBBBlockSize) - return TRUE; // Got max number or too big - - Found = TRUE; // Remember we have some - } - else - { - conn->FwdMsg = Msg; - return TRUE; - } - } - } - - return Found; -} - -BOOL SeeifMessagestoForward (int BBSNumber, CIRCUIT * conn) -{ - // See if any messages are queued for this BBS - - // if Conn is not NULL, also check Msg Type - - int m; - struct MsgInfo * Msg; - - for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) - { - if (conn) - { - char Type = Msg->type; - - if ((conn->SendB && Type == 'B') || (conn->SendP && Type == 'P') || (conn->SendT && Type == 'T')) - { -// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); - return TRUE; - } - } - else - { -// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); - return TRUE; - } - } - } - - return FALSE; -} - -int CountMessagestoForward (struct UserInfo * user) -{ - // See if any messages are queued for this BBS - - int m, n=0; - struct MsgInfo * Msg; - int BBSNumber = user->BBSNumber; - int FirstMessage = FirstMessageIndextoForward; - - if ((user->flags & F_NTSMPS)) - FirstMessage = 1; - - for (m = FirstMessage; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) - { - n++; - continue; // So we dont count twice in Flag set and NTS MPS - } - - // if an NTS MPS, also check for any matches - - if (Msg->type == 'T' && (user->flags & F_NTSMPS)) - { - struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; - int depth; - - if (Msg->status == 'N' && ForwardingInfo) - { - depth = CheckBBSToForNTS(Msg, ForwardingInfo); - - if (depth > -1 && Msg->Locked == 0) - { - n++; - continue; - } - depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); - - if (depth && Msg->Locked == 0) - { - n++; - continue; - } - - depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); - - if (depth > -1 && Msg->Locked == 0) - { - n++; - continue; - } - } - } - } - - return n; -} - -int ListMessagestoForward(CIRCUIT * conn, struct UserInfo * user) -{ - // See if any messages are queued for this BBS - - int m, n=0; - struct MsgInfo * Msg; - int BBSNumber = user->BBSNumber; - int FirstMessage = FirstMessageIndextoForward; - - if ((user->flags & F_NTSMPS)) - FirstMessage = 1; - - for (m = FirstMessage; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) - { - nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); - continue; // So we dont count twice in Flag set and NTS MPS - } - - // if an NTS MPS, also check for any matches - - if (Msg->type == 'T' && (user->flags & F_NTSMPS)) - { - struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; - int depth; - - if (Msg->status == 'N' && ForwardingInfo) - { - depth = CheckBBSToForNTS(Msg, ForwardingInfo); - - if (depth > -1 && Msg->Locked == 0) - { - nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); - continue; - } - depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); - - if (depth && Msg->Locked == 0) - { - nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); - continue; - } - - depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); - - if (depth > -1 && Msg->Locked == 0) - { - nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); - continue; - } - } - } - } - - return n; -} - -VOID SendWarningToSYSOP(struct MsgInfo * Msg) -{ - int Length=0; - char * MailBuffer = malloc(100); - char Title[100]; - - Length += sprintf(MailBuffer, "Warning - Message %d has nowhere to go", Msg->number); - sprintf(Title, "Warning - Message %d has nowhere to go", Msg->number); - SendMessageToSYSOP(Title, MailBuffer, Length); -} - - - -VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length) -{ - struct MsgInfo * Msg = AllocateMsgRecord(); - BIDRec * BIDRec; - - char MsgFile[MAX_PATH]; - FILE * hFile; - size_t WriteLen=0; - - Msg->length = Length; - - GetSemaphore(&MsgNoSemaphore, 0); - Msg->number = ++LatestMsg; - MsgnotoMsg[Msg->number] = Msg; - - FreeSemaphore(&MsgNoSemaphore); - - strcpy(Msg->from, "SYSTEM"); - if (SendSYStoSYSOPCall) - strcpy(Msg->to, SYSOPCall); - else - strcpy(Msg->to, "SYSOP"); - - strcpy(Msg->title, Title); - - Msg->type = 'P'; - Msg->status = 'N'; - Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); - - sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); - - BIDRec = AllocateBIDRecord(); - strcpy(BIDRec->BID, Msg->bid); - BIDRec->mode = Msg->type; - BIDRec->u.msgno = LOWORD(Msg->number); - BIDRec->u.timestamp = LOWORD(time(NULL)/86400); - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); - - hFile = fopen(MsgFile, "wb"); - - if (hFile) - { - WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); - fclose(hFile); - } - - MatchMessagetoBBSList(Msg, NULL); - free(MailBuffer); -} - -VOID CheckBBSNumber(int i) -{ - // Make sure number is unique - - int Count = 0; - struct UserInfo * user; - - for (user = BBSChain; user; user = user->BBSNext) - { - if (user->BBSNumber == i) - { - Count++; - - if (Count > 1) - { - // Second with same number - Renumber this one - - user->BBSNumber = FindFreeBBSNumber(); - - if (user->BBSNumber == 0) - user->BBSNumber = NBBBS; // cant really do much else - - Logprintf(LOG_BBS, NULL, '?', "Duplicate BBS Number found. BBS %s Old BBSNumber %d New BBS Number %d", user->Call, i, user->BBSNumber); - - } - } - } -} - - -int FindFreeBBSNumber() -{ - // returns the lowest number not used by any bbs or message. - - struct MsgInfo * Msg; - struct UserInfo * user; - int i, m; - - for (i = 1; i<= NBBBS; i++) - { - for (user = BBSChain; user; user = user->BBSNext) - { - if (user->BBSNumber == i) - goto nexti; // In use - } - - // Not used by BBS - check messages - - for (m = 1; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - if (check_fwd_bit(Msg->fbbs, i)) - goto nexti; // In use - - if (check_fwd_bit(Msg->forw, i)) - goto nexti; // In use - } - - // Not in Use - - return i; - -nexti:; - - } - - return 0; // All used -} - -BOOL SetupNewBBS(struct UserInfo * user) -{ - user->BBSNumber = FindFreeBBSNumber(); - - if (user->BBSNumber == 0) - return FALSE; - - user->BBSNext = BBSChain; - BBSChain = user; - - SortBBSChain(); - - ReinitializeFWDStruct(user); - - return TRUE; -} - -VOID DeleteBBS(struct UserInfo * user) -{ - struct UserInfo * BBSRec, * PrevBBS = NULL; - -#ifndef LINBPQ - RemoveMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, MF_BYCOMMAND); -#endif - for (BBSRec = BBSChain; BBSRec; PrevBBS = BBSRec, BBSRec = BBSRec->BBSNext) - { - if (user == BBSRec) - { - if (PrevBBS == NULL) // First in chain; - { - BBSChain = BBSRec->BBSNext; - break; - } - PrevBBS->BBSNext = BBSRec->BBSNext; - break; - } - } -} - - -VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo); - -VOID SetupForwardingStruct(struct UserInfo * user) -{ - struct BBSForwardingInfo * ForwardingInfo; - - char Key[100] = "BBSForwarding."; - char Temp[100]; - - HKEY hKey=0; - char RegKey[100] = "SOFTWARE\\G8BPQ\\BPQ32\\BPQMailChat\\BBSForwarding\\"; - - int m; - struct MsgInfo * Msg; - - ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); - - if (UsingingRegConfig == 0) - { - // Config from file - - if (isdigit(user->Call[0]) || user->Call[0] == '_') - strcat(Key, "*"); - - strcat(Key, user->Call); - - group = config_lookup (&cfg, Key); - - if (group == NULL) // No info - return; - else - { - ForwardingInfo->TOCalls = GetMultiStringValue(group, "TOCalls"); - ForwardingInfo->ConnectScript = GetMultiStringValue(group, "ConnectScript"); - ForwardingInfo->ATCalls = GetMultiStringValue(group, "ATCalls"); - ForwardingInfo->Haddresses = GetMultiStringValue(group, "HRoutes"); - ForwardingInfo->HaddressesP = GetMultiStringValue(group, "HRoutesP"); - ForwardingInfo->FWDTimes = GetMultiStringValue(group, "FWDTimes"); - - ForwardingInfo->Enabled = GetIntValue(group, "Enabled"); - ForwardingInfo->ReverseFlag = GetIntValue(group, "RequestReverse"); - ForwardingInfo->AllowBlocked = GetIntValue(group, "AllowBlocked"); - ForwardingInfo->AllowCompressed = GetIntValue(group, "AllowCompressed"); - ForwardingInfo->AllowB1 = GetIntValue(group, "UseB1Protocol"); - ForwardingInfo->AllowB2 = GetIntValue(group, "UseB2Protocol"); - ForwardingInfo->SendCTRLZ = GetIntValue(group, "SendCTRLZ"); - - if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) - ForwardingInfo->AllowCompressed = TRUE; - - if (ForwardingInfo->AllowCompressed) - ForwardingInfo->AllowBlocked = TRUE; - - ForwardingInfo->PersonalOnly = GetIntValue(group, "FWDPersonalsOnly"); - ForwardingInfo->SendNew = GetIntValue(group, "FWDNewImmediately"); - ForwardingInfo->FwdInterval = GetIntValue(group, "FwdInterval"); - ForwardingInfo->RevFwdInterval = GetIntValue(group, "RevFWDInterval"); - ForwardingInfo->MaxFBBBlockSize = GetIntValue(group, "MaxFBBBlock"); - ForwardingInfo->ConTimeout = GetIntValue(group, "ConTimeout"); - - if (ForwardingInfo->MaxFBBBlockSize == 0) - ForwardingInfo->MaxFBBBlockSize = 10000; - - if (ForwardingInfo->FwdInterval == 0) - ForwardingInfo->FwdInterval = 3600; - - if (ForwardingInfo->ConTimeout == 0) - ForwardingInfo->ConTimeout = 120; - - GetStringValue(group, "BBSHA", Temp); - - if (Temp[0]) - ForwardingInfo->BBSHA = _strdup(Temp); - else - ForwardingInfo->BBSHA = _strdup(""); - } - } - else - { -#ifndef LINBPQ - - int retCode,Type,Vallen; - - strcat(RegKey, user->Call); - retCode = RegOpenKeyEx (REGTREE, RegKey, 0, KEY_QUERY_VALUE, &hKey); - - if (retCode != ERROR_SUCCESS) - return; - else - { - ForwardingInfo->ConnectScript = RegGetMultiStringValue(hKey, "Connect Script"); - ForwardingInfo->TOCalls = RegGetMultiStringValue(hKey, "TOCalls"); - ForwardingInfo->ATCalls = RegGetMultiStringValue(hKey, "ATCalls"); - ForwardingInfo->Haddresses = RegGetMultiStringValue(hKey, "HRoutes"); - ForwardingInfo->HaddressesP = RegGetMultiStringValue(hKey, "HRoutesP"); - ForwardingInfo->FWDTimes = RegGetMultiStringValue(hKey, "FWD Times"); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "Enabled", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->Enabled,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "RequestReverse", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->ReverseFlag,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "AllowCompressed", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowCompressed,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "Use B1 Protocol", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB1,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "Use B2 Protocol", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB2,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "SendCTRLZ", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendCTRLZ,(ULONG *)&Vallen); - - if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) - ForwardingInfo->AllowCompressed = TRUE; - - Vallen=4; - retCode += RegQueryValueEx(hKey, "FWD Personals Only", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->PersonalOnly,(ULONG *)&Vallen); - - Vallen=4; - retCode += RegQueryValueEx(hKey, "FWD New Immediately", 0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendNew,(ULONG *)&Vallen); - - Vallen=4; - RegQueryValueEx(hKey,"FWDInterval",0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->FwdInterval,(ULONG *)&Vallen); - - Vallen=4; - RegQueryValueEx(hKey,"RevFWDInterval",0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->RevFwdInterval,(ULONG *)&Vallen); - - RegQueryValueEx(hKey,"MaxFBBBlock",0, - (ULONG *)&Type,(UCHAR *)&ForwardingInfo->MaxFBBBlockSize,(ULONG *)&Vallen); - - if (ForwardingInfo->MaxFBBBlockSize == 0) - ForwardingInfo->MaxFBBBlockSize = 10000; - - if (ForwardingInfo->FwdInterval == 0) - ForwardingInfo->FwdInterval = 3600; - - Vallen=0; - retCode = RegQueryValueEx(hKey,"BBSHA",0 , (ULONG *)&Type,NULL, (ULONG *)&Vallen); - - if (retCode != 0) - { - // No Key - Get from WP?? - - WPRec * ptr = LookupWP(user->Call); - - if (ptr) - { - if (ptr->first_homebbs) - { - ForwardingInfo->BBSHA = _strdup(ptr->first_homebbs); - } - } - } - - if (Vallen) - { - ForwardingInfo->BBSHA = malloc(Vallen); - RegQueryValueEx(hKey, "BBSHA", 0, (ULONG *)&Type, ForwardingInfo->BBSHA,(ULONG *)&Vallen); - } - - RegCloseKey(hKey); - } -#endif - } - - // Convert FWD Times and H Addresses - - if (ForwardingInfo->FWDTimes) - SetupFwdTimes(ForwardingInfo); - - if (ForwardingInfo->Haddresses) - SetupHAddreses(ForwardingInfo); - - if (ForwardingInfo->HaddressesP) - SetupHAddresesP(ForwardingInfo); - - if (ForwardingInfo->BBSHA) - { - if (ForwardingInfo->BBSHA[0]) - SetupHAElements(ForwardingInfo); - else - { - free(ForwardingInfo->BBSHA); - ForwardingInfo->BBSHA = NULL; - } - } - - for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) - { - Msg=MsgHddrPtr[m]; - - // If any forward bits are set, increment count on BBS record. - - if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) - { - if (Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) - { - user->ForwardingInfo->MsgCount++; - } - } - } -} - -VOID * GetMultiStringValue(config_setting_t * group, char * ValueName) -{ - char * ptr1; - char * MultiString = NULL; - const char * ptr; - int Count = 0; - char ** Value; - config_setting_t *setting; - char * Save; - - Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values - Value[0] = NULL; - - setting = config_setting_get_member (group, ValueName); - - if (setting) - { - ptr = config_setting_get_string (setting); - - Save = _strdup(ptr); // DOnt want to change config string - ptr = Save; - - while (ptr && strlen(ptr)) - { - ptr1 = strchr(ptr, '|'); - - if (ptr1) - *(ptr1++) = 0; - - if (strlen(ptr)) // ignore null elements - { - Value = realloc(Value, (Count+2) * sizeof(void *)); - Value[Count++] = _strdup(ptr); - } - ptr = ptr1; - } - free(Save); - } - - Value[Count] = NULL; - return Value; -} - - -VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName) -{ -#ifdef LINBPQ - return NULL; -#else - int retCode,Type,Vallen; - char * MultiString = NULL; - int ptr, len; - int Count = 0; - char ** Value; - - Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values - - Value[0] = NULL; - - Vallen=0; - - - retCode = RegQueryValueEx(hKey, ValueName, 0, (ULONG *)&Type, NULL, (ULONG *)&Vallen); - - if ((retCode != 0) || (Vallen < 3)) // Two nulls means empty multistring - { - free(Value); - return FALSE; - } - - MultiString = malloc(Vallen); - - retCode = RegQueryValueEx(hKey, ValueName, 0, - (ULONG *)&Type,(UCHAR *)MultiString,(ULONG *)&Vallen); - - ptr=0; - - while (MultiString[ptr]) - { - len=strlen(&MultiString[ptr]); - - Value = realloc(Value, (Count+2) * sizeof(void *)); - Value[Count++] = _strdup(&MultiString[ptr]); - ptr+= (len + 1); - } - - Value[Count] = NULL; - - free(MultiString); - - return Value; -#endif -} - -VOID FreeForwardingStruct(struct UserInfo * user) -{ - struct BBSForwardingInfo * ForwardingInfo; - int i; - - - ForwardingInfo = user->ForwardingInfo; - - FreeList(ForwardingInfo->TOCalls); - FreeList(ForwardingInfo->ATCalls); - FreeList(ForwardingInfo->Haddresses); - FreeList(ForwardingInfo->HaddressesP); - - i=0; - if(ForwardingInfo->HADDRS) - { - while(ForwardingInfo->HADDRS[i]) - { - FreeList(ForwardingInfo->HADDRS[i]); - i++; - } - free(ForwardingInfo->HADDRS); - free(ForwardingInfo->HADDROffet); - } - - i=0; - if(ForwardingInfo->HADDRSP) - { - while(ForwardingInfo->HADDRSP[i]) - { - FreeList(ForwardingInfo->HADDRSP[i]); - i++; - } - free(ForwardingInfo->HADDRSP); - } - - FreeList(ForwardingInfo->ConnectScript); - FreeList(ForwardingInfo->FWDTimes); - if (ForwardingInfo->FWDBands) - { - i=0; - while(ForwardingInfo->FWDBands[i]) - { - free(ForwardingInfo->FWDBands[i]); - i++; - } - free(ForwardingInfo->FWDBands); - } - if (ForwardingInfo->BBSHAElements) - { - i=0; - while(ForwardingInfo->BBSHAElements[i]) - { - free(ForwardingInfo->BBSHAElements[i]); - i++; - } - free(ForwardingInfo->BBSHAElements); - } - free(ForwardingInfo->BBSHA); - -} - -VOID FreeList(char ** Hddr) -{ - VOID ** Save; - - if (Hddr) - { - Save = (void **)Hddr; - while(Hddr[0]) - { - free(Hddr[0]); - Hddr++; - } - free(Save); - } -} - - -VOID ReinitializeFWDStruct(struct UserInfo * user) -{ - if (user->ForwardingInfo) - { - FreeForwardingStruct(user); - free(user->ForwardingInfo); - } - - SetupForwardingStruct(user); - -} - -VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo) -{ - char ** Times = ForwardingInfo->FWDTimes; - int Start, End; - int Count = 0; - - ForwardingInfo->FWDBands = zalloc(sizeof(struct FWDBAND)); - - if (Times) - { - while(Times[0]) - { - ForwardingInfo->FWDBands = realloc(ForwardingInfo->FWDBands, (Count+2)* sizeof(struct FWDBAND)); - ForwardingInfo->FWDBands[Count] = zalloc(sizeof(struct FWDBAND)); - - Start = atoi(Times[0]); - End = atoi(&Times[0][5]); - - ForwardingInfo->FWDBands[Count]->FWDStartBand = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; - ForwardingInfo->FWDBands[Count]->FWDEndBand = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; - - Count++; - Times++; - } - ForwardingInfo->FWDBands[Count] = NULL; - } -} -void StartForwarding(int BBSNumber, char ** TempScript) -{ - struct UserInfo * user; - struct BBSForwardingInfo * ForwardingInfo ; - time_t NOW = time(NULL); - - - for (user = BBSChain; user; user = user->BBSNext) - { - // See if any messages are queued for this BBS - - ForwardingInfo = user->ForwardingInfo; - - if ((BBSNumber == 0) || (user->BBSNumber == BBSNumber)) - if (ForwardingInfo) - if (ForwardingInfo->Enabled || BBSNumber) // Menu Command overrides enable - if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) - if (BBSNumber || SeeifMessagestoForward(user->BBSNumber, NULL) || - (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) // Menu Command overrides Reverse - { - user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used - - // See if TempScript requested - - if (user->ForwardingInfo->TempConnectScript) - FreeList(user->ForwardingInfo->TempConnectScript); - - user->ForwardingInfo->TempConnectScript = TempScript; - - if (ConnecttoBBS(user)) - ForwardingInfo->Forwarding = TRUE; - } - } - - return; -} - -size_t fwritex(CIRCUIT * conn, void * _Str, size_t _Size, size_t _Count, FILE * _File) -{ - if (_File) - return fwrite(_Str, _Size, _Count, _File); - - // Appending to MailBuffer - - memcpy(&conn->MailBuffer[conn->InputLen], _Str, _Count); - conn->InputLen += (int)_Count; - - return _Count; -} - - -BOOL ForwardMessagestoFile(CIRCUIT * conn, char * FN) -{ - BOOL AddCRLF = FALSE; - BOOL AutoImport = FALSE; - FILE * Handle = NULL; - char * Context; - BOOL Email = FALSE; - time_t now = time(NULL); - char * param; - - FN = strtok_s(FN, " ,", &Context); - - param = strtok_s(NULL, " ,", &Context); - - if (param) - { - if (_stricmp(param, "ADDCRLF") == 0) - AddCRLF = TRUE; - - if (_stricmp(param, "AutoImport") == 0) - AutoImport = TRUE; - - param = strtok_s(NULL, " ,", &Context); - - if (param) - { - if (_stricmp(param, "ADDCRLF") == 0) - AddCRLF = TRUE; - - if (_stricmp(param, "AutoImport") == 0) - AutoImport = TRUE; - - } - } - // If FN is an email address, write to a temp file, and send via rms or emali gateway - - if (strchr(FN, '@') || _memicmp(FN, "RMS:", 4) == 0) - { - Email = TRUE; - AddCRLF =TRUE; - conn->MailBuffer=malloc(100000); - conn->MailBufferSize=100000; - conn->InputLen = 0; - } - else - { - Handle = fopen(FN, "ab"); - - if (Handle == NULL) - { - int err = GetLastError(); - Logprintf(LOG_BBS, conn, '!', "Failed to open Export File %s", FN); - return FALSE; - } - } - - while (FindMessagestoForward(conn)) - { - struct MsgInfo * Msg; - struct tm * tm; - time_t temp; - char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); - int MsgLen; - char * MsgPtr; - char Line[256]; - int len; - struct UserInfo * user = conn->UserPointer; - int Index = 0; - - Msg = conn->FwdMsg; - - if (Email) - if (conn->InputLen + Msg->length + 500 > conn->MailBufferSize) - break; - - if (Msg->type == 'P') - Index = PMSG; - else if (Msg->type == 'B') - Index = BMSG; - else if (Msg->type == 'T') - Index = TMSG; - - - if (Msg->via[0]) - len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, - Msg->via, Msg->from, Msg->bid); - else - len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); - - fwritex(conn, Line, 1, len, Handle); - - len = sprintf(Line, "%s\r\n", Msg->title); - fwritex(conn, Line, 1, len, Handle); - - if (MsgBytes == 0) - { - MsgBytes = _strdup("Message file not found\r\n"); - conn->FwdMsg->length = (int)strlen(MsgBytes); - } - - MsgPtr = MsgBytes; - MsgLen = conn->FwdMsg->length; - - // If a B2 Message, remove B2 Header - - if (conn->FwdMsg->B2Flags & B2Msg) - { - // Remove all B2 Headers, and all but the first part. - - MsgPtr = strstr(MsgBytes, "Body:"); - - if (MsgPtr) - { - MsgLen = atoi(&MsgPtr[5]); - MsgPtr = strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers - - if (MsgPtr) - MsgPtr +=4; - else - MsgPtr = MsgBytes; - - } - else - MsgPtr = MsgBytes; - } - - memcpy(&temp, &Msg->datereceived, sizeof(time_t)); - tm = gmtime(&temp); - - len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", - tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, - conn->FwdMsg->number, BBSName, HRoute, RlineVer); - - fwritex(conn, Line, 1, len, Handle); - - if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header - fwritex(conn, "\r\n", 1, 2, Handle); - - fwritex(conn, MsgPtr, 1, MsgLen, Handle); - - if (MsgPtr[MsgLen - 2] == '\r') - fwritex(conn, "/EX\r\n", 1, 5, Handle); - else - fwritex(conn, "\r\n/EX\r\n", 1, 7, Handle); - - if (AddCRLF) - fwritex(conn, "\r\n", 1, 2, Handle); - - free(MsgBytes); - - user->Total.MsgsSent[Index]++; - user->Total.BytesForwardedOut[Index] += MsgLen; - - Msg->datechanged = now; - - clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); - set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); - - // Only mark as forwarded if sent to all BBSs that should have it - - if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) - { - conn->FwdMsg->status = 'F'; // Mark as forwarded - conn->FwdMsg->datechanged=time(NULL); - } - - conn->UserPointer->ForwardingInfo->MsgCount--; - } - - if (Email) - { - struct MsgInfo * Msg; - BIDRec * BIDRec; - - if (conn->InputLen == 0) - { - free(conn->MailBuffer); - conn->MailBufferSize=0; - conn->MailBuffer=0; - - return TRUE; - } - - // Allocate a message Record slot - - Msg = AllocateMsgRecord(); - - // Set number here so they remain in sequence - - GetSemaphore(&MsgNoSemaphore, 0); - Msg->number = ++LatestMsg; - FreeSemaphore(&MsgNoSemaphore); - MsgnotoMsg[Msg->number] = Msg; - - Msg->type = 'P'; - Msg->status = 'N'; - Msg->datecreated = Msg->datechanged = Msg->datereceived = now; - - strcpy(Msg->from, BBSName); - - sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); - - if (AutoImport) - sprintf(Msg->title, "Batched messages for AutoImport from BBS %s", BBSName); - else - sprintf(Msg->title, "Batched messages from BBS %s", BBSName); - - Msg->length = conn->InputLen; - CreateMessageFile(conn, Msg); - - BIDRec = AllocateBIDRecord(); - - strcpy(BIDRec->BID, Msg->bid); - BIDRec->mode = Msg->type; - BIDRec->u.msgno = LOWORD(Msg->number); - BIDRec->u.timestamp = LOWORD(time(NULL)/86400); - - if (_memicmp(FN, "SMTP:", 5) == 0) - { - strcpy(Msg->via, &FN[5]); - SMTPMsgCreated=TRUE; - } - else - { - strcpy(Msg->to, "RMS"); - if (_memicmp(FN, "RMS:", 4) == 0) - strcpy(Msg->via, &FN[4]); - else - strcpy(Msg->via, FN); - } - - MatchMessagetoBBSList(Msg, conn); - - SaveMessageDatabase(); - SaveBIDDatabase(); - } - else - fclose(Handle); - - SaveMessageDatabase(); - return TRUE; -} - -BOOL ForwardMessagetoFile(struct MsgInfo * Msg, FILE * Handle) -{ - struct tm * tm; - time_t temp; - - char * MsgBytes = ReadMessageFile(Msg->number); - char * MsgPtr; - char Line[256]; - int len; - int MsgLen = Msg->length; - - if (Msg->via[0]) - len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, - Msg->via, Msg->from, Msg->bid); - else - len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); - - fwrite(Line, 1, len, Handle); - - len = sprintf(Line, "%s\r\n", Msg->title); - fwrite(Line, 1, len, Handle); - - if (MsgBytes == 0) - { - MsgBytes = _strdup("Message file not found\r\n"); - MsgLen = (int)strlen(MsgBytes); - } - - MsgPtr = MsgBytes; - - // If a B2 Message, remove B2 Header - - if (Msg->B2Flags & B2Msg) - { - // Remove all B2 Headers, and all but the first part. - - MsgPtr = strstr(MsgBytes, "Body:"); - - if (MsgPtr) - { - MsgLen = atoi(&MsgPtr[5]); - - MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers - - if (MsgPtr) - MsgPtr +=4; - else - MsgPtr = MsgBytes; - - } - else - MsgPtr = MsgBytes; - } - - memcpy(&temp, &Msg->datereceived, sizeof(time_t)); - tm = gmtime(&temp); - - len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", - tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, - Msg->number, BBSName, HRoute, RlineVer); - - fwrite(Line, 1, len, Handle); - - if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header - fwrite("\r\n", 1, 2, Handle); - - fwrite(MsgPtr, 1, MsgLen, Handle); - - if (MsgPtr[MsgLen - 2] == '\r') - fwrite("/EX\r\n", 1, 5, Handle); - else - fwrite("\r\n/EX\r\n", 1, 7, Handle); - - free(MsgBytes); - - return TRUE; - -} - -BOOL ConnecttoBBS (struct UserInfo * user) -{ - int n, p; - CIRCUIT * conn; - struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; - - for (n = NumberofStreams-1; n >= 0 ; n--) - { - conn = &Connections[n]; - - if (conn->Active == FALSE) - { - p = conn->BPQStream; - memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything - conn->BPQStream = p; - - // Can't set Active until Connected or Stuck Session detertor can clear session. - // But must set Active before Connected() runs or will appear is Incoming Connect. - // Connected() is semaphored, so get semaphore before ConnectUsingAppl - // Probably better to semaphore lost session code instead - - - strcpy(conn->Callsign, user->Call); - conn->BBSFlags |= (RunningConnectScript | OUTWARDCONNECT); - conn->UserPointer = user; - - Logprintf(LOG_BBS, conn, '|', "Connecting to BBS %s", user->Call); - - ForwardingInfo->MoreLines = TRUE; - - GetSemaphore(&ConSemaphore, 1); - conn->Active = TRUE; - ConnectUsingAppl(conn->BPQStream, BBSApplMask); - FreeSemaphore(&ConSemaphore); - - // If we are sending to a dump pms we may need to connect using the message sender's callsign. - // But we wont know until we run the connect script, which is a bit late to change call. Could add - // flag to forwarding config, but easier to look for SETCALLTOSENDER in the connect script. - - if (strstr(ForwardingInfo->ConnectScript[0], "SETCALLTOSENDER")) - { - conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; - conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; - - if (FindMessagestoForward(conn) && conn->FwdMsg) - { - // We have a message to send - - struct MsgInfo * Msg; - unsigned char AXCall[7]; - - Msg = conn->FwdMsg; - ConvToAX25(Msg->from, AXCall); - ChangeSessionCallsign(p, AXCall); - - conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER | NEWPACCOM; - conn->NextMessagetoForward = 0; // was set by FindMessages - } - conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; - } -#ifdef LINBPQ - { - BPQVECSTRUC * SESS; - SESS = &BPQHOSTVECTOR[conn->BPQStream - 1]; - - if (SESS->HOSTSESSION == NULL) - { - Logprintf(LOG_BBS, NULL, '|', "No L4 Sessions for connect to BBS %s", user->Call); - return FALSE; - } - - SESS->HOSTSESSION->Secure_Session = 1; - } -#endif - - strcpy(conn->Callsign, user->Call); - - // Connected Event will trigger connect to remote system - - RefreshMainWindow(); - - return TRUE; - } - } - - Logprintf(LOG_BBS, NULL, '|', "No Free Streams for connect to BBS %s", user->Call); - - return FALSE; - -} - -struct DelayParam -{ - struct UserInfo * User; - CIRCUIT * conn; - int Delay; -}; - -struct DelayParam DParam; // Not 100% safe, but near enough - -VOID ConnectDelayThread(struct DelayParam * DParam) -{ - struct UserInfo * User = DParam->User; - int Delay = DParam->Delay; - - User->ForwardingInfo->Forwarding = TRUE; // Minimize window for two connects - - Sleep(Delay); - - User->ForwardingInfo->Forwarding = TRUE; - ConnecttoBBS(User); - - return; -} - -VOID ConnectPauseThread(struct DelayParam * DParam) -{ - CIRCUIT * conn = DParam->conn; - int Delay = DParam->Delay; - char Msg[] = "Pause Ok\r "; - - Sleep(Delay); - - ProcessBBSConnectScript(conn, Msg, 9); - - return; -} - - -/* -BOOL ProcessBBSConnectScriptInner(CIRCUIT * conn, char * Buffer, int len); - - -BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) -{ - BOOL Ret; - GetSemaphore(&ScriptSEM); - Ret = ProcessBBSConnectScriptInner(conn, Buffer, len); - FreeSemaphore(&ScriptSEM); - - return Ret; -} -*/ - -BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) -{ - struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; - char ** Scripts; - char callsign[10]; - int port, sesstype, paclen, maxframe, l4window; - char * ptr, * ptr2; - - WriteLogLine(conn, '<',Buffer, len-1, LOG_BBS); - - Buffer[len]=0; - _strupr(Buffer); - - if (ForwardingInfo->TempConnectScript) - Scripts = ForwardingInfo->TempConnectScript; - else - Scripts = ForwardingInfo->ConnectScript; - - if (ForwardingInfo->ScriptIndex == -1) - { - // First Entry - if first line is TIMES, check and skip forward if necessary - - int n = 0; - int Start, End; - time_t now = time(NULL), StartSecs, EndSecs; - char * Line; - - if (Localtime) - now -= (time_t)_MYTIMEZONE; - - now %= 86400; - Line = Scripts[n]; - - if (_memicmp(Line, "TIMES", 5) == 0) - { - NextBand: - Start = atoi(&Line[6]); - End = atoi(&Line[11]); - - StartSecs = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; - EndSecs = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; - - if ((StartSecs <= now) && (EndSecs >= now)) - goto InBand; // In band - - // Look for next TIME - NextLine: - Line = Scripts[++n]; - - if (Line == NULL) - { - // No more lines - Disconnect - - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - if (_memicmp(Line, "TIMES", 5) != 0) - goto NextLine; - else - goto NextBand; -InBand: - ForwardingInfo->ScriptIndex = n; - } - - } - else - { - // Dont check first time through - - if (strcmp(Buffer, "*** CONNECTED ") != 0) - { - if (Scripts[ForwardingInfo->ScriptIndex] == NULL || - _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished - _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished - { - ForwardingInfo->MoreLines = FALSE; - } - if (!ForwardingInfo->MoreLines) - goto CheckForSID; - } - } - - if (strstr(Buffer, "BUSY") || strstr(Buffer, "FAILURE") || - (strstr(Buffer, "DOWNLINK") && strstr(Buffer, "ATTEMPTING") == 0) || - strstr(Buffer, "SORRY") || strstr(Buffer, "INVALID") || strstr(Buffer, "RETRIED") || - strstr(Buffer, "NO CONNECTION TO") || strstr(Buffer, "ERROR - ") || - strstr(Buffer, "UNABLE TO CONNECT") || strstr(Buffer, "DISCONNECTED") || - strstr(Buffer, "FAILED TO CONNECT") || strstr(Buffer, "REJECTED")) - { - // Connect Failed - - char * Cmd = Scripts[++ForwardingInfo->ScriptIndex]; - int Delay = 1000; - - // Look for an alternative connect block (Starting with ELSE) - - ElseLoop: - - // Skip any comments - - while (Cmd && ((strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#'))) - Cmd = Scripts[++ForwardingInfo->ScriptIndex]; - - // TIMES terminates a script - - if (Cmd == 0 || _memicmp(Cmd, "TIMES", 5) == 0) // Only Check until script is finished - { - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - if (_memicmp(Cmd, "ELSE", 4) != 0) - { - Cmd = Scripts[++ForwardingInfo->ScriptIndex]; - goto ElseLoop; - } - - if (_memicmp(&Cmd[5], "DELAY", 5) == 0) - Delay = atoi(&Cmd[10]) * 1000; - else - Delay = 1000; - - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - - DParam.Delay = Delay; - DParam.User = conn->UserPointer; - - _beginthread((void (*)(void *))ConnectDelayThread, 0, &DParam); - - return FALSE; - } - - // The pointer is only updated when we get the connect, so we can tell when the last line is acked - // The first entry is always from Connected event, so don't have to worry about testing entry -1 below - - - // NETROM to KA node returns - - //c 1 milsw - //WIRAC:N9PMO-2} Connected to MILSW - //###CONNECTED TO NODE MILSW(N9ZXS) CHANNEL A - //You have reached N9ZXS's KA-Node MILSW - //ENTER COMMAND: B,C,J,N, or Help ? - - //C KB9PRF-7 - //###LINK MADE - //###CONNECTED TO NODE KB9PRF-7(KB9PRF-4) CHANNEL A - - // Look for (Space)Connected so we aren't fooled by ###CONNECTED TO NODE, which is not - // an indication of a connect. - - if (strstr(Buffer, " CONNECTED") || strstr(Buffer, "PACLEN") || strstr(Buffer, "IDLETIME") || - strstr(Buffer, "OK") || strstr(Buffer, "###LINK MADE") || strstr(Buffer, "VIRTUAL CIRCUIT ESTABLISHED")) - { - // If connected to SYNC, save IP address and port - - char * Cmd; - - if (strstr(Buffer, "*** CONNECTED TO SYNC")) - { - char * IPAddr = &Buffer[22]; - char * Port = strlop(IPAddr, ':'); - - if (Port) - { - if (conn->SyncHost) - free(conn->SyncHost); - - conn->SyncHost = _strdup(IPAddr); - conn->SyncPort = atoi(Port); - } - } - - if (conn->SkipConn) - { - conn->SkipConn = FALSE; - return TRUE; - } - - LoopBack: - - Cmd = Scripts[++ForwardingInfo->ScriptIndex]; - - // Only Check until script is finished - - if (Cmd && (strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#')) - goto LoopBack; // Blank line - - if (Cmd && _memicmp(Cmd, "TIMES", 5) != 0 && _memicmp(Cmd, "ELSE", 4) != 0) // Only Check until script is finished - { - if (_memicmp(Cmd, "MSGTYPE", 7) == 0) - { - char * ptr; - - // Select Types to send. Only send types in param. Only reverse if R in param - - _strupr(Cmd); - - Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); - - conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; - - strcpy(conn->MSGTYPES, &Cmd[8]); - - if (strchr(&Cmd[8], 'R')) conn->DoReverse = TRUE; - - ptr = strchr(&Cmd[8], 'B'); - - if (ptr) - { - conn->SendB = TRUE; - conn->MaxBLen = atoi(++ptr); - if (conn->MaxBLen == 0) conn->MaxBLen = 99999999; - } - - ptr = strchr(&Cmd[8], 'T'); - - if (ptr) - { - conn->SendT = TRUE; - conn->MaxTLen = atoi(++ptr); - if (conn->MaxTLen == 0) conn->MaxTLen = 99999999; - } - ptr = strchr(&Cmd[8], 'P'); - - if (ptr) - { - conn->SendP = TRUE; - conn->MaxPLen = atoi(++ptr); - if (conn->MaxPLen == 0) conn->MaxPLen = 99999999; - } - - // If nothing to do, terminate script - - if (conn->DoReverse || SeeifMessagestoForward(conn->UserPointer->BBSNumber, conn)) - goto LoopBack; - - Logprintf(LOG_BBS, conn, '?', "Nothing to do - quitting"); - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - if (_memicmp(Cmd, "INTERLOCK ", 10) == 0) - { - // Used to limit connects on a port to 1 - - int Port; - char Option[80]; - - Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); - - sscanf(&Cmd[10], "%d %s", &Port, &Option[0]); - - if (CountConnectionsOnPort(Port)) - { - Logprintf(LOG_BBS, conn, '?', "Interlocked Port is busy - quitting"); - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - goto LoopBack; - } - - if (_memicmp(Cmd, "RADIO AUTH", 10) == 0) - { - // Generate a Password to enable RADIO commands on a remote node - char AuthCommand[80]; - - _strupr(Cmd); - strcpy(AuthCommand, Cmd); - - CreateOneTimePassword(&AuthCommand[11], &Cmd[11], 0); - - nodeprintf(conn, "%s\r", AuthCommand); - return TRUE; - } - - if (_memicmp(Cmd, "SKIPCON", 7) == 0) - { - // Remote Node sends Connected in CTEXT - we need to swallow it - - conn->SkipConn = TRUE; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "SendWL2KPM", 10) == 0|| _memicmp(Cmd, "SendWL2KFW", 10) == 0) - { - // Send ;FW: command - - conn->SendWL2KFW = TRUE; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "SKIPPROMPT", 10) == 0) - { - // Remote Node sends > at end of CTEXT - we need to swallow it - - conn->SkipPrompt++; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "TEXTFORWARDING", 10) == 0) - { - conn->BBSFlags |= TEXTFORWARDING; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "SETCALLTOSENDER", 15) == 0) - { - conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "RADIOONLY", 9) == 0) - { - conn->BBSFlags |= WINLINKRO; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "SYNC", 4) == 0) - { - conn->BBSFlags |= SYNCMODE; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "NEEDLF", 6) == 0) - { - conn->BBSFlags |= NEEDLF; - goto CheckForEnd; - } - - if (_memicmp(Cmd, "MCASTRX", 6) == 0) - { - conn->BBSFlags |= MCASTRX; - conn->MCastListenTime = atoi(&Cmd[7]) * 6; // Time to run session for *6 as value is mins put timer ticks 10 secs - - // send MCAST to Node - - nodeprintfEx(conn, "MCAST\r"); - return TRUE; - } - - if (_memicmp(Cmd, "FLARQ", 5) == 0) - { - conn->BBSFlags |= FLARQMAIL; - - CheckForEnd: - if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || - memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished - memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished - ForwardingInfo->MoreLines = FALSE; - - goto LoopBack; - } - if (_memicmp(Cmd, "PAUSE", 5) == 0) - { - // Pause script - - Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); - - DParam.Delay = atoi(&Cmd[6]) * 1000; - DParam.conn = conn; - - _beginthread((void (*)(void *))ConnectPauseThread, 0, &DParam); - - return TRUE; - } - - if (_memicmp(Cmd, "FILE", 4) == 0) - { - if (Cmd[4] == 0) - { - // Missing Filename - - Logprintf(LOG_BBS, conn, '!', "Export file name missing"); - } - else - ForwardMessagestoFile(conn, &Cmd[5]); - - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - if (_memicmp(Cmd, "SMTP", 4) == 0) - { - conn->NextMessagetoForward = FirstMessageIndextoForward; - conn->UserPointer->Total.ConnectsOut++; - - SendAMPRSMTP(conn); - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - - if (_memicmp(Cmd, "IMPORT", 6) == 0) - { - char * File, * Context; - int Num; - char * Temp = _strdup(Cmd); - - File = strtok_s(&Temp[6], " ", &Context); - - if (File && File[0]) - { - Num = ImportMessages(NULL, File, TRUE); - - Logprintf(LOG_BBS, NULL, '|', "Imported %d Message(s) from %s", Num, File); - - if (Context && _stricmp(Context, "delete") == 0) - DeleteFile(File); - } - free(Temp); - - if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || - memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished - memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished - { - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - goto LoopBack; - } - - // Anything else is sent to Node - - // Replace \ with # so can send commands starting with # - - if (Cmd[0] == '\\') - { - Cmd[0] = '#'; - nodeprintfEx(conn, "%s\r", Cmd); - Cmd[0] = '\\'; // Put \ back in script - } - else - nodeprintfEx(conn, "%s\r", Cmd); - - return TRUE; - } - - // End of script. - - ForwardingInfo->MoreLines = FALSE; - - if (conn->BBSFlags & MCASTRX) - { - // No session with Multicast, so no SID - - conn->BBSFlags &= ~RunningConnectScript; - return TRUE; - } - - if (conn->BBSFlags & FLARQMAIL) - { - // FLARQ doesnt send a prompt - Just send message(es) - - conn->UserPointer->Total.ConnectsOut++; - conn->BBSFlags &= ~RunningConnectScript; - ForwardingInfo->LastReverseForward = time(NULL); - - // Update Paclen - - GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); - - if (paclen > 0) - conn->paclen = paclen; - - SendARQMail(conn); - return TRUE; - } - - - return TRUE; - } - - ptr = strchr(Buffer, '}'); - - if (ptr && ForwardingInfo->MoreLines) // Beware it could be part of ctext - { - // Could be respsonse to Node Command - - ptr+=2; - - ptr2 = strchr(&ptr[0], ' '); - - if (ptr2) - { - if (_memicmp(ptr, Scripts[ForwardingInfo->ScriptIndex], ptr2-ptr) == 0) // Reply to last sscript command - { - if (Scripts[ForwardingInfo->ScriptIndex+1] && _memicmp(Scripts[ForwardingInfo->ScriptIndex+1], "else", 4) == 0) - { - // stray match or misconfigured - - return TRUE; - } - - ForwardingInfo->ScriptIndex++; - - if (Scripts[ForwardingInfo->ScriptIndex]) - if (_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) != 0) - nodeprintf(conn, "%s\r", Scripts[ForwardingInfo->ScriptIndex]); - - return TRUE; - } - } - } - - // Not Success or Fail. If last line is still outstanding, wait fot Response - // else look for SID or Prompt - - if (conn->SkipPrompt && Buffer[len-2] == '>') - { - conn->SkipPrompt--; - return TRUE; - } - - if (ForwardingInfo->MoreLines) - return TRUE; - - // No more steps, Look for SID or Prompt - -CheckForSID: - - if (strstr(Buffer, "POSYNCHELLO")) // RMS RELAY Sync process - { - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - conn->NextMessagetoForward = FirstMessageIndextoForward; - conn->UserPointer->Total.ConnectsOut++; - ForwardingInfo->LastReverseForward = time(NULL); - - ProcessLine(conn, 0, Buffer, len); - return FALSE; - } - - if (strstr(Buffer, "SORRY, NO")) // URONODE - { - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - if (memcmp(Buffer, ";PQ: ", 5) == 0) - { - // Secure CMS challenge - - int Len; - struct UserInfo * User = conn->UserPointer; - char * Pass = User->CMSPass; - int Response ; - char RespString[12]; - char ConnectingCall[10]; - -#ifdef LINBPQ - BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; -#else - BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; -#endif - - SESS += conn->BPQStream - 1; - - ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); - - strlop(ConnectingCall, ' '); - - if (Pass[0] == 0) - { - Pass = User->pass; // Old Way - if (Pass[0] == 0) - { - strlop(ConnectingCall, '-'); - User = LookupCall(ConnectingCall); - if (User) - Pass = User->CMSPass; - } - } - - // - - Response = GetCMSHash(&Buffer[5], Pass); - - sprintf(RespString, "%010d", Response); - - Len = sprintf(conn->SecureMsg, ";PR: %s\r", &RespString[2]); - - // Save challengs in case needed for FW lines - - strcpy(conn->PQChallenge, &Buffer[5]); - - return FALSE; - } - - - if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID - { - // Update PACLEN - - GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); - - if (paclen > 0) - conn->paclen = paclen; - - - Parse_SID(conn, &Buffer[1], len-4); - - if (conn->BBSFlags & FBBForwarding) - { - conn->FBBIndex = 0; // ready for first block; - memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); - conn->FBBChecksum = 0; - } - - return TRUE; - } - - if (memcmp(Buffer, "[PAKET ", 7) == 0) - { - conn->BBSFlags |= BBS; - conn->BBSFlags |= MBLFORWARDING; - } - - if (Buffer[len-2] == '>') - { - if (conn->SkipPrompt) - { - conn->SkipPrompt--; - return TRUE; - } - - conn->NextMessagetoForward = FirstMessageIndextoForward; - conn->UserPointer->Total.ConnectsOut++; - conn->BBSFlags &= ~RunningConnectScript; - ForwardingInfo->LastReverseForward = time(NULL); - - if (memcmp(Buffer, "[AEA PK", 7) == 0 || (conn->BBSFlags & TEXTFORWARDING)) - { - // PK232. Don't send a SID, and switch to Text Mode - - conn->BBSFlags |= (BBS | TEXTFORWARDING); - conn->Flags |= SENDTITLE; - - // Send Message. There is no mechanism for reverse forwarding - - if (FindMessagestoForward(conn) && conn->FwdMsg) - { - struct MsgInfo * Msg; - - // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ - - Msg = conn->FwdMsg; - - if ((conn->BBSFlags & SETCALLTOSENDER)) - nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, - (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); - else - nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, - (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, - Msg->from, Msg->bid); - } - else - { - conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered - Disconnect(conn->BPQStream); - return FALSE; - } - - return TRUE; - } - - if (strcmp(conn->Callsign, "RMS") == 0 || conn->SendWL2KFW) - { - // Build a ;FW: line with all calls with PollRMS Set - - // According to Lee if you use secure login the first - // must be the BBS call - // Actually I don't think we need the first, - // as that is implied - - // If a secure password is available send the new - // call|response format. - - // I think this should use the session callsign, which - // normally will be the BBS ApplCall, and not the BBS Name, - // but coudl be changed by *** LINKED - - int i, s; - char FWLine[10000] = ";FW: "; - struct UserInfo * user; - char RMSCall[20]; - char ConnectingCall[10]; - -#ifdef LINBPQ - BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; -#else - BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; -#endif - - SESS += conn->BPQStream - 1; - - ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); - strlop(ConnectingCall, ' '); - - strcat (FWLine, ConnectingCall); - - for (i = 0; i <= NumberofUsers; i++) - { - user = UserRecPtr[i]; - - if (user->flags & F_POLLRMS) - { - if (user->RMSSSIDBits == 0) user->RMSSSIDBits = 1; - - for (s = 0; s < 16; s++) - { - if (user->RMSSSIDBits & (1 << s)) - { - if (s) - sprintf(RMSCall, "%s-%d", user->Call, s); - else - sprintf(RMSCall, "%s", user->Call); - - // We added connectingcall at front - - if (strcmp(RMSCall, ConnectingCall) != 0) - { - strcat(FWLine, " "); - strcat(FWLine, RMSCall); - - if (user->CMSPass[0]) - { - int Response = GetCMSHash(conn->PQChallenge, user->CMSPass); - char RespString[12]; - - sprintf(RespString, "%010d", Response); - strcat(FWLine, "|"); - strcat(FWLine, &RespString[2]); - } - } - } - } - } - } - - strcat(FWLine, "\r"); - - nodeprintf(conn, FWLine); - } - - // Only declare B1 and B2 if other end did, and we are configued for it - - nodeprintfEx(conn, BBSSID, "BPQ-", - Ver[0], Ver[1], Ver[2], Ver[3], - (conn->BBSFlags & FBBCompressed) ? "B" : "", - (conn->BBSFlags & FBBB1Mode && !(conn->BBSFlags & FBBB2Mode)) ? "1" : "", - (conn->BBSFlags & FBBB2Mode) ? "2" : "", - (conn->BBSFlags & FBBForwarding) ? "F" : "", - (conn->BBSFlags & WINLINKRO) ? "" : "J"); - - if (conn->SecureMsg[0]) - { - struct UserInfo * user; - BBSputs(conn, conn->SecureMsg); - conn->SecureMsg[0] = 0; - - // Also send a Location Comment Line - - //; GM8BPQ-10 DE G8BPQ (IO92KX) - //; WL2K DE GM8BPQ () (PAT) - - user = LookupCall(BBSName); - - if (LOC && LOC[0]) - nodeprintf(conn, "; WL2K DE %s (%s)\r", BBSName, LOC); - } - - if (conn->BPQBBS && conn->MSGTYPES[0]) - - // Send a ; MSGTYPES to control what he sends us - - nodeprintf(conn, "; MSGTYPES %s\r", conn->MSGTYPES); - - if (conn->BBSFlags & FBBForwarding) - { - if (!FBBDoForward(conn)) // Send proposal if anthing to forward - { - if (conn->DoReverse) - FBBputs(conn, "FF\r"); - else - { - FBBputs(conn, "FQ\r"); - conn->CloseAfterFlush = 20; // 2 Secs - } - } - - return TRUE; - } - - return TRUE; - } - - return TRUE; -} - -VOID Parse_SID(CIRCUIT * conn, char * SID, int len) -{ - ChangeSessionIdletime(conn->BPQStream, BBSIDLETIME); // Default Idletime for BBS Sessions - - // scan backwards for first '-' - - if (strstr(SID, "BPQCHATSERVER")) - { - Disconnect(conn->BPQStream); - return; - } - - if (strstr(SID, "RMS Ex") || strstr(SID, "Winlink Ex")) - { - conn->RMSExpress = TRUE; - conn->Paclink = FALSE; - conn->PAT = FALSE; - - // Set new RMS Users as RMS User - - if (conn->NewUser) - conn->UserPointer->flags |= F_Temp_B2_BBS; - } - - if (stristr(SID, "PAT")) - { - // Set new PAT Users as RMS User - - conn->RMSExpress = FALSE; - conn->Paclink = FALSE; - conn->PAT = TRUE; - - if (conn->NewUser) - conn->UserPointer->flags |= F_Temp_B2_BBS; - } - if (strstr(SID, "Paclink")) - { - conn->RMSExpress = FALSE; - conn->Paclink = TRUE; - } - - if (strstr(SID, "WL2K-")) - { - conn->WL2K = TRUE; - conn->BBSFlags |= WINLINKRO; - } - - if (strstr(SID, "MFJ-")) - { - conn->BBSFlags |= MFJMODE; - } - - if (_memicmp(SID, "OpenBCM", 7) == 0) - { - // We should really only do this on Telnet Connections, as OpenBCM flag is used to remove relnet transparency - - - conn->OpenBCM = TRUE; - } - - if (_memicmp(SID, "PMS-3.2", 7) == 0) - { - // Paccom TNC that doesn't send newline prompt ater receiving subject - - conn->BBSFlags |= NEWPACCOM; - } - - // See if BPQ for selective forwarding - - if (strstr(SID, "BPQ")) - conn->BPQBBS = TRUE; - - while (len > 0) - { - switch (SID[len--]) - { - case '-': - - len=0; - break; - - case '$': - - conn->BBSFlags |= BBS | MBLFORWARDING; - conn->Paging = FALSE; - - break; - - case 'F': // FBB Blocked Forwarding - - // We now support blocked uncompressed. Not necessarily compatible with FBB - - if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) - { - // We need to allocate a forwarding structure - - conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); - conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; - conn->UserPointer->ForwardingInfo->AllowBlocked = TRUE; - conn->UserPointer->BBSNumber = NBBBS; - } - - if (conn->UserPointer->ForwardingInfo->AllowBlocked) - { - conn->BBSFlags |= FBBForwarding | BBS; - conn->BBSFlags &= ~MBLFORWARDING; - - conn->Paging = FALSE; - - if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) - { - // We need to allocate a forwarding structure - - conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); - conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; - conn->UserPointer->BBSNumber = NBBBS; - } - - // Allocate a Header Block - - conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); - } - break; - - case 'J': - - // Suspected to be associated with Winlink Radio Only - - conn->BBSFlags &= ~WINLINKRO; - break; - - case 'B': - - if (conn->UserPointer->ForwardingInfo->AllowCompressed) - { - conn->BBSFlags |= FBBCompressed; - conn->DontSaveRestartData = FALSE; // Allow restarts - - // Look for 1 or 2 or 12 as next 2 chars - - if (SID[len+2] == '1') - { - if (conn->UserPointer->ForwardingInfo->AllowB1 || - conn->UserPointer->ForwardingInfo->AllowB2) // B2 implies B1 - conn->BBSFlags |= FBBB1Mode; - - if (SID[len+3] == '2') - if (conn->UserPointer->ForwardingInfo->AllowB2) - conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) - - break; - } - - if (SID[len+2] == '2') - { - if (conn->UserPointer->ForwardingInfo->AllowB2) - conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) - - if (conn->UserPointer->ForwardingInfo->AllowB1) - conn->BBSFlags |= FBBB1Mode; // B2 should allow fallback to B1 (but RMS doesnt!) - - } - break; - } - - break; - } - } - - // Only allow blocked non-binary to other BPQ Nodes - - if ((conn->BBSFlags & FBBForwarding) && ((conn->BBSFlags & FBBCompressed) == 0) && (conn->BPQBBS == 0)) - { - // Switch back to MBL - - conn->BBSFlags |= MBLFORWARDING; - conn->BBSFlags &= ~FBBForwarding; // Turn off FBB Blocked - } - - return; -} - -VOID BBSSlowTimer() -{ - ConnectionInfo * conn; - int n; - - // Called every 10 seconds - - MCastTimer(); - - - for (n = 0; n < NumberofStreams; n++) - { - conn = &Connections[n]; - - if (conn->Active == TRUE) - { - // Check for stuck BBS sessions (BBS session but no Node Session) - - int state; - - GetSemaphore(&ConSemaphore, 1); - SessionStateNoAck(conn->BPQStream, &state); - FreeSemaphore(&ConSemaphore); - - if (state == 0) // No Node Session - { - // is it safe just to clear Active ?? - - conn->InputMode = 0; // So Disconnect wont save partial transfer - conn->BBSFlags = 0; - Disconnected (conn->BPQStream); - continue; - } - - if (conn->BBSFlags & MCASTRX) - MCastConTimer(conn); - - - // Check SIDTImers - used to detect failure to compete SID Handshake - - if (conn->SIDResponseTimer) - { - conn->SIDResponseTimer--; - if (conn->SIDResponseTimer == 0) - { - // Disconnect Session - - Disconnect(conn->BPQStream); - } - } - } - } - - // Flush logs - - for (n = 0; n < 4; n++) - { - if (LogHandle[n]) - { - time_t LT = time(NULL); - if ((LT - LastLogTime[n]) > 30) - { - LastLogTime[n] = LT; - fclose(LogHandle[n]); - LogHandle[n] = NULL; - } - } - } -} - - -VOID FWDTimerProc() -{ - struct UserInfo * user; - struct BBSForwardingInfo * ForwardingInfo ; - time_t NOW = time(NULL); - - for (user = BBSChain; user; user = user->BBSNext) - { - // See if any messages are queued for this BBS - - ForwardingInfo = user->ForwardingInfo; - ForwardingInfo->FwdTimer+=10; - - if (ForwardingInfo->FwdTimer >= ForwardingInfo->FwdInterval) - { - ForwardingInfo->FwdTimer=0; - - if (ForwardingInfo->FWDBands && ForwardingInfo->FWDBands[0]) - { - // Check Timebands - - struct FWDBAND ** Bands = ForwardingInfo->FWDBands; - int Count = 0; - time_t now = time(NULL); - - if (Localtime) - now -= (time_t)_MYTIMEZONE; - - now %= 86400; // Secs in day - - while(Bands[Count]) - { - if ((Bands[Count]->FWDStartBand < now) && (Bands[Count]->FWDEndBand >= now)) - goto FWD; // In band - - Count++; - } - continue; // Out of bands - } - FWD: - if (ForwardingInfo->Enabled) - { - if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) - { - //Temp Debug Code - -// Debugprintf("ReverseFlag = %d, Msgs to Forward Flag %d Msgs to Forward Count %d", -// ForwardingInfo->ReverseFlag, -// SeeifMessagestoForward(user->BBSNumber, NULL), -// CountMessagestoForward(user)); - - if (SeeifMessagestoForward(user->BBSNumber, NULL) || - (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) - - { - user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used - - - // remove any old TempScript - - if (user->ForwardingInfo->TempConnectScript) - { - FreeList(user->ForwardingInfo->TempConnectScript); - user->ForwardingInfo->TempConnectScript = NULL; - } - - if (ConnecttoBBS(user)) - ForwardingInfo->Forwarding = TRUE; - } - } - } - } - } -} - -VOID * _zalloc_dbg(size_t len, int type, char * file, int line) -{ - // ?? malloc and clear - - void * ptr; - -#ifdef WIN32 - ptr=_malloc_dbg(len, type, file, line); -#else - ptr = malloc(len); -#endif - if (ptr) - memset(ptr, 0, len); - - return ptr; -} - -struct MsgInfo * FindMessageByNumber(int msgno) - { - int m=NumberofMessages; - - struct MsgInfo * Msg; - - do - { - Msg=MsgHddrPtr[m]; - - if (Msg->number == msgno) - return Msg; - - if (Msg->number && Msg->number < msgno) // sometimes get zero msg number - return NULL; // Not found - - m--; - - } while (m > 0); - - return NULL; -} - -struct MsgInfo * FindMessageByBID(char * BID) -{ - int m = NumberofMessages; - - struct MsgInfo * Msg; - - while (m > 0) - { - Msg = MsgHddrPtr[m]; - - if (strcmp(Msg->bid, BID) == 0) - return Msg; - - m--; - } - - return NULL; -} - -VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len) -{ - unsigned char hash[50]; - unsigned char key[100]; - unsigned int i, j = 0, val1, val2; - unsigned char hostname[100]=""; - - gethostname(hostname, 100); - - strcpy(key, hostname); - strcat(key, ISPPOP3Name); - - md5(key, hash); - memcpy(&hash[16], hash, 16); // in case very long password - - // String is now encoded as hex pairs, but still need to decode old format - - for (i=0; i < len; i++) - { - if (Encrypt[i] < '0' || Encrypt[i] > 'F') - goto OldFormat; - } - - // Only '0' to 'F' - - for (i=0; i < len; i++) - { - val1 = Encrypt[i++]; - val1 -= '0'; - if (val1 > 9) - val1 -= 7; - - val2 = Encrypt[i]; - val2 -= '0'; - if (val2 > 9) - val2 -= 7; - - Pass[j] = (val1 << 4) | val2; - Pass[j] ^= hash[j]; - j++; - } - - return; - -OldFormat: - - for (i=0; i < len; i++) - { - Pass[i] = Encrypt[i] ^ hash[i]; - } - - return; -} - -int EncryptPass(char * Pass, char * Encrypt) -{ - unsigned char hash[50]; - unsigned char key[100]; - unsigned int i, val; - unsigned char hostname[100]; - unsigned char extendedpass[100]; - unsigned int passlen; - unsigned char * ptr; - - gethostname(hostname, 100); - - strcpy(key, hostname); - strcat(key, ISPPOP3Name); - - md5(key, hash); - memcpy(&hash[16], hash, 16); // in case very long password - - // if password is less than 16 chars, extend with zeros - - passlen=(int)strlen(Pass); - - strcpy(extendedpass, Pass); - - if (passlen < 16) - { - for (i=passlen+1; i <= 16; i++) - { - extendedpass[i] = 0; - } - - passlen = 16; - } - - ptr = Encrypt; - Encrypt[0] = 0; - - for (i=0; i < passlen; i++) - { - val = extendedpass[i] ^ hash[i]; - ptr += sprintf(ptr, "%02X", val); - } - - return passlen * 2; -} - - - -VOID SaveIntValue(config_setting_t * group, char * name, int value) -{ - config_setting_t *setting; - - setting = config_setting_add(group, name, CONFIG_TYPE_INT); - if(setting) - config_setting_set_int(setting, value); -} - -VOID SaveInt64Value(config_setting_t * group, char * name, long long value) -{ - config_setting_t *setting; - - setting = config_setting_add(group, name, CONFIG_TYPE_INT64); - if(setting) - config_setting_set_int64(setting, value); -} - -VOID SaveFloatValue(config_setting_t * group, char * name, double value) -{ - config_setting_t *setting; - - setting = config_setting_add(group, name, CONFIG_TYPE_FLOAT); - if (setting) - config_setting_set_float(setting, value); -} - -VOID SaveStringValue(config_setting_t * group, char * name, char * value) -{ - config_setting_t *setting; - - setting = config_setting_add(group, name, CONFIG_TYPE_STRING); - if (setting) - config_setting_set_string(setting, value); - -} - - -VOID SaveOverride(config_setting_t * group, char * name, struct Override ** values) -{ - config_setting_t *setting; - struct Override ** Calls; - char Multi[10000]; - char * ptr = &Multi[1]; - - *ptr = 0; - - if (values) - { - Calls = values; - - while(Calls[0]) - { - ptr += sprintf(ptr, "%s, %d|", Calls[0]->Call, Calls[0]->Days); - Calls++; - } - *(--ptr) = 0; - } - - setting = config_setting_add(group, name, CONFIG_TYPE_STRING); - if (setting) - config_setting_set_string(setting, &Multi[1]); - -} - - -VOID SaveMultiStringValue(config_setting_t * group, char * name, char ** values) -{ - config_setting_t *setting; - char ** Calls; - char Multi[100000]; - char * ptr = &Multi[1]; - - *ptr = 0; - - if (values) - { - Calls = values; - - while(Calls[0]) - { - strcpy(ptr, Calls[0]); - ptr += strlen(Calls[0]); - *(ptr++) = '|'; - Calls++; - } - *(--ptr) = 0; - } - - setting = config_setting_add(group, name, CONFIG_TYPE_STRING); - if (setting) - config_setting_set_string(setting, &Multi[1]); - -} - -int configSaved = 0; - -VOID SaveConfig(char * ConfigName) -{ - struct UserInfo * user; - struct BBSForwardingInfo * ForwardingInfo ; - config_setting_t *root, *group, *bbs; - int i; - char Size[80]; - struct BBSForwardingInfo DummyForwardingInfo; - char Line[1024]; - - if (configSaved == 0) - { - // only create backup once per run - - CopyConfigFile(ConfigName); - configSaved = 1; - } - - memset(&DummyForwardingInfo, 0, sizeof(struct BBSForwardingInfo)); - - // Get rid of old config before saving - - config_destroy(&cfg); - - memset((void *)&cfg, 0, sizeof(config_t)); - - config_init(&cfg); - - root = config_root_setting(&cfg); - - group = config_setting_add(root, "main", CONFIG_TYPE_GROUP); - - SaveIntValue(group, "Streams", MaxStreams); - SaveIntValue(group, "BBSApplNum", BBSApplNum); - SaveStringValue(group, "BBSName", BBSName); - SaveStringValue(group, "SYSOPCall", SYSOPCall); - SaveStringValue(group, "H-Route", HRoute); - SaveStringValue(group, "AMPRDomain", AMPRDomain); - SaveIntValue(group, "EnableUI", EnableUI); - SaveIntValue(group, "RefuseBulls", RefuseBulls); - SaveIntValue(group, "OnlyKnown", OnlyKnown); - SaveIntValue(group, "SendSYStoSYSOPCall", SendSYStoSYSOPCall); - SaveIntValue(group, "SendBBStoSYSOPCall", SendBBStoSYSOPCall); - SaveIntValue(group, "DontHoldNewUsers", DontHoldNewUsers); - SaveIntValue(group, "DefaultNoWINLINK", DefaultNoWINLINK); - SaveIntValue(group, "AllowAnon", AllowAnon); - SaveIntValue(group, "DontNeedHomeBBS", DontNeedHomeBBS); - SaveIntValue(group, "DontCheckFromCall", DontCheckFromCall); - SaveIntValue(group, "UserCantKillT", UserCantKillT); - - SaveIntValue(group, "ForwardToMe", ForwardToMe); - SaveIntValue(group, "SMTPPort", SMTPInPort); - SaveIntValue(group, "POP3Port", POP3InPort); - SaveIntValue(group, "NNTPPort", NNTPInPort); - SaveIntValue(group, "RemoteEmail", RemoteEmail); - SaveIntValue(group, "SendAMPRDirect", SendAMPRDirect); - - SaveIntValue(group, "MailForInterval", MailForInterval); - SaveStringValue(group, "MailForText", MailForText); - - EncryptedPassLen = EncryptPass(ISPAccountPass, EncryptedISPAccountPass); - - SaveIntValue(group, "AuthenticateSMTP", SMTPAuthNeeded); - - SaveIntValue(group, "MulticastRX", MulticastRX); - - SaveIntValue(group, "SMTPGatewayEnabled", ISP_Gateway_Enabled); - SaveIntValue(group, "ISPSMTPPort", ISPSMTPPort); - SaveIntValue(group, "ISPPOP3Port", ISPPOP3Port); - SaveIntValue(group, "POP3PollingInterval", ISPPOP3Interval); - - SaveStringValue(group, "MyDomain", MyDomain); - SaveStringValue(group, "ISPSMTPName", ISPSMTPName); - SaveStringValue(group, "ISPEHLOName", ISPEHLOName); - SaveStringValue(group, "ISPPOP3Name", ISPPOP3Name); - SaveStringValue(group, "ISPAccountName", ISPAccountName); - SaveStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); - - - // Save Window Sizes - -#ifndef LINBPQ - - if (ConsoleRect.right) - { - sprintf(Size,"%d,%d,%d,%d",ConsoleRect.left, ConsoleRect.right, - ConsoleRect.top, ConsoleRect.bottom); - - SaveStringValue(group, "ConsoleSize", Size); - } - - sprintf(Size,"%d,%d,%d,%d,%d",MonitorRect.left,MonitorRect.right,MonitorRect.top,MonitorRect.bottom, hMonitor ? 1 : 0); - SaveStringValue(group, "MonitorSize", Size); - - sprintf(Size,"%d,%d,%d,%d",MainRect.left,MainRect.right,MainRect.top,MainRect.bottom); - SaveStringValue(group, "WindowSize", Size); - - SaveIntValue(group, "Bells", Bells); - SaveIntValue(group, "FlashOnBell", FlashOnBell); - SaveIntValue(group, "StripLF", StripLF); - SaveIntValue(group, "WarnWrap", WarnWrap); - SaveIntValue(group, "WrapInput", WrapInput); - SaveIntValue(group, "FlashOnConnect", FlashOnConnect); - SaveIntValue(group, "CloseWindowOnBye", CloseWindowOnBye); - -#endif - - SaveIntValue(group, "Log_BBS", LogBBS); - SaveIntValue(group, "Log_TCP", LogTCP); - - sprintf(Size,"%d,%d,%d,%d", Ver[0], Ver[1], Ver[2], Ver[3]); - SaveStringValue(group, "Version", Size); - - // Save Welcome Messages and prompts - - SaveStringValue(group, "WelcomeMsg", WelcomeMsg); - SaveStringValue(group, "NewUserWelcomeMsg", NewWelcomeMsg); - SaveStringValue(group, "ExpertWelcomeMsg", ExpertWelcomeMsg); - - SaveStringValue(group, "Prompt", Prompt); - SaveStringValue(group, "NewUserPrompt", NewPrompt); - SaveStringValue(group, "ExpertPrompt", ExpertPrompt); - SaveStringValue(group, "SignoffMsg", SignoffMsg); - - SaveMultiStringValue(group, "RejFrom", RejFrom); - SaveMultiStringValue(group, "RejTo", RejTo); - SaveMultiStringValue(group, "RejAt", RejAt); - SaveMultiStringValue(group, "RejBID", RejBID); - - SaveMultiStringValue(group, "HoldFrom", HoldFrom); - SaveMultiStringValue(group, "HoldTo", HoldTo); - SaveMultiStringValue(group, "HoldAt", HoldAt); - SaveMultiStringValue(group, "HoldBID", HoldBID); - - SaveIntValue(group, "SendWP", SendWP); - SaveIntValue(group, "SendWPType", SendWPType); - SaveIntValue(group, "FilterWPBulls", FilterWPBulls); - SaveIntValue(group, "NoWPGuesses", NoWPGuesses); - - SaveStringValue(group, "SendWPTO", SendWPTO); - SaveStringValue(group, "SendWPVIA", SendWPVIA); - - SaveMultiStringValue(group, "SendWPAddrs", SendWPAddrs); - - // Save Forwarding Config - - // Interval and Max Sizes and Aliases are not user specific - - SaveIntValue(group, "MaxTXSize", MaxTXSize); - SaveIntValue(group, "MaxRXSize", MaxRXSize); - SaveIntValue(group, "ReaddressLocal", ReaddressLocal); - SaveIntValue(group, "ReaddressReceived", ReaddressReceived); - SaveIntValue(group, "WarnNoRoute", WarnNoRoute); - SaveIntValue(group, "Localtime", Localtime); - SaveIntValue(group, "SendPtoMultiple", SendPtoMultiple); - - SaveMultiStringValue(group, "FWDAliases", AliasText); - - bbs = config_setting_add(root, "BBSForwarding", CONFIG_TYPE_GROUP); - - for (i=1; i <= NumberofUsers; i++) - { - user = UserRecPtr[i]; - ForwardingInfo = user->ForwardingInfo; - - if (ForwardingInfo == NULL) - continue; - - if (memcmp(ForwardingInfo, &DummyForwardingInfo, sizeof(struct BBSForwardingInfo)) == 0) - continue; // Ignore empty records; - - if (isdigit(user->Call[0]) || user->Call[0] == '_') - { - char Key[20] = "*"; - strcat (Key, user->Call); - group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); - } - else - group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); - - SaveMultiStringValue(group, "TOCalls", ForwardingInfo->TOCalls); - SaveMultiStringValue(group, "ConnectScript", ForwardingInfo->ConnectScript); - SaveMultiStringValue(group, "ATCalls", ForwardingInfo->ATCalls); - SaveMultiStringValue(group, "HRoutes", ForwardingInfo->Haddresses); - SaveMultiStringValue(group, "HRoutesP", ForwardingInfo->HaddressesP); - SaveMultiStringValue(group, "FWDTimes", ForwardingInfo->FWDTimes); - - SaveIntValue(group, "Enabled", ForwardingInfo->Enabled); - SaveIntValue(group, "RequestReverse", ForwardingInfo->ReverseFlag); - SaveIntValue(group, "AllowBlocked", ForwardingInfo->AllowBlocked); - SaveIntValue(group, "AllowCompressed", ForwardingInfo->AllowCompressed); - SaveIntValue(group, "UseB1Protocol", ForwardingInfo->AllowB1); - SaveIntValue(group, "UseB2Protocol", ForwardingInfo->AllowB2); - SaveIntValue(group, "SendCTRLZ", ForwardingInfo->SendCTRLZ); - - SaveIntValue(group, "FWDPersonalsOnly", ForwardingInfo->PersonalOnly); - SaveIntValue(group, "FWDNewImmediately", ForwardingInfo->SendNew); - SaveIntValue(group, "FwdInterval", ForwardingInfo->FwdInterval); - SaveIntValue(group, "RevFWDInterval", ForwardingInfo->RevFwdInterval); - SaveIntValue(group, "MaxFBBBlock", ForwardingInfo->MaxFBBBlockSize); - SaveIntValue(group, "ConTimeout", ForwardingInfo->ConTimeout); - - SaveStringValue(group, "BBSHA", ForwardingInfo->BBSHA); - } - - - // Save Housekeeping config - - group = config_setting_add(root, "Housekeeping", CONFIG_TYPE_GROUP); - - SaveInt64Value(group, "LastHouseKeepingTime", LastHouseKeepingTime); - SaveInt64Value(group, "LastTrafficTime", LastTrafficTime); - SaveIntValue(group, "MaxMsgno", MaxMsgno); - SaveIntValue(group, "BidLifetime", BidLifetime); - SaveIntValue(group, "MaxAge", MaxAge); - SaveIntValue(group, "LogLifetime", LogAge); - SaveIntValue(group, "LogLifetime", LogAge); - SaveIntValue(group, "MaintInterval", MaintInterval); - SaveIntValue(group, "UserLifetime", UserLifetime); - SaveIntValue(group, "MaintTime", MaintTime); - SaveFloatValue(group, "PR", PR); - SaveFloatValue(group, "PUR", PUR); - SaveFloatValue(group, "PF", PF); - SaveFloatValue(group, "PNF", PNF); - SaveIntValue(group, "BF", BF); - SaveIntValue(group, "BNF", BNF); - SaveIntValue(group, "NTSD", NTSD); - SaveIntValue(group, "NTSF", NTSF); - SaveIntValue(group, "NTSU", NTSU); -// SaveIntValue(group, "AP", AP); -// SaveIntValue(group, "AB", AB); - SaveIntValue(group, "DeletetoRecycleBin", DeletetoRecycleBin); - SaveIntValue(group, "SuppressMaintEmail", SuppressMaintEmail); - SaveIntValue(group, "MaintSaveReg", SaveRegDuringMaint); - SaveIntValue(group, "OverrideUnsent", OverrideUnsent); - SaveIntValue(group, "SendNonDeliveryMsgs", SendNonDeliveryMsgs); - SaveIntValue(group, "GenerateTrafficReport", GenerateTrafficReport); - - SaveOverride(group, "LTFROM", LTFROM); - SaveOverride(group, "LTTO", LTTO); - SaveOverride(group, "LTAT", LTAT); - - // Save UI config - - for (i=1; i<=32; i++) - { - char Key[100]; - - sprintf(Key, "UIPort%d", i); - - group = config_setting_add(root, Key, CONFIG_TYPE_GROUP); - - if (group) - { - SaveIntValue(group, "Enabled", UIEnabled[i]); - SaveIntValue(group, "SendMF", UIMF[i]); - SaveIntValue(group, "SendHDDR", UIHDDR[i]); - SaveIntValue(group, "SendNull", UINull[i]); - - if (UIDigi[i]) - SaveStringValue(group, "Digis", UIDigi[i]); - } - } - - // Save User Config - - bbs = config_setting_add(root, "BBSUsers", CONFIG_TYPE_GROUP); - - for (i=1; i <= NumberofUsers; i++) - { - char stats[256], stats2[256]; - struct MsgStats * Stats; - char Key[20] = "*"; - - user = UserRecPtr[i]; - - if (isdigit(user->Call[0]) || user->Call[0] == '_') - { - strcat (Key, user->Call); -// group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); - } - else - { - strcpy(Key, user->Call); -// group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); - } - /* - SaveStringValue(group, "Name", user->Name); - SaveStringValue(group, "Address", user->Address); - SaveStringValue(group, "HomeBBS", user->HomeBBS); - SaveStringValue(group, "QRA", user->QRA); - SaveStringValue(group, "pass", user->pass); - SaveStringValue(group, "ZIP", user->ZIP); - SaveStringValue(group, "CMSPass", user->CMSPass); - - SaveIntValue(group, "lastmsg", user->lastmsg); - SaveIntValue(group, "flags", user->flags); - SaveIntValue(group, "PageLen", user->PageLen); - SaveIntValue(group, "BBSNumber", user->BBSNumber); - SaveIntValue(group, "RMSSSIDBits", user->RMSSSIDBits); - SaveIntValue(group, "WebSeqNo", user->WebSeqNo); - - SaveInt64Value(group, "TimeLastConnected", user->TimeLastConnected); -*/ - Stats = &user->Total; - -// sprintf(stats, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", - sprintf(stats, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", - Stats->ConnectsIn, Stats->ConnectsOut, - Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], - Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], - Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], - Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], - Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], - Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); - -// SaveStringValue(group, "Totsl", stats); - - Stats = &user->Last; - - sprintf(stats2, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", -// sprintf(stats2, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", - Stats->ConnectsIn, Stats->ConnectsOut, - Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], - Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], - Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], - Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], - Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], - Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); - -// SaveStringValue(group, "Last", stats2); - - sprintf(Line,"%s^%s^%s^%s^%s^%s^%s^%d^%d^%d^%d^%d^%d^%lld^%s^%s", - user->Name, user->Address, user->HomeBBS, user->QRA, user->pass, user->ZIP, user->CMSPass, - user->lastmsg, user->flags, user->PageLen, user->BBSNumber, user->RMSSSIDBits, user->WebSeqNo, - user->TimeLastConnected, stats, stats2); - - if (strlen(Line) < 10) - continue; - - SaveStringValue(bbs, Key, Line); - } - -/* - wp = config_setting_add(root, "WP", CONFIG_TYPE_GROUP); - - for (i = 0; i <= NumberofWPrecs; i++) - { - char WPString[1024]; - long long val1, val2; - - WP = WPRecPtr[i]; - val1 = WP->last_modif; - val2 = WP->last_seen; - - sprintf(Key, "R%d", i); - - sprintf(WPString, "%s|%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%ld|%ld", - &WP->callsign[0], &WP->name[0], WP->Type, WP->changed, WP->seen, &WP->first_homebbs[0], - &WP->secnd_homebbs[0], &WP->first_zip[0], &WP->secnd_zip[0], &WP->first_qth[0], &WP->secnd_qth[0], - val1, val2); - - SaveStringValue(wp, Key, WPString); - } - - // Save Message Headers - - msgs = config_setting_add(root, "MSGS", CONFIG_TYPE_GROUP); - - memset(MsgHddrPtr[0], 0, sizeof(struct MsgInfo)); - - MsgHddrPtr[0]->type = 'X'; - MsgHddrPtr[0]->status = '2'; - MsgHddrPtr[0]->number = 0; - MsgHddrPtr[0]->length = LatestMsg; - - - for (i = 0; i <= NumberofMessages; i++) - { - Msg = MsgHddrPtr[i]; - - for (n = 0; n < NBMASK; n++) - sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); - - n = 39; - while (n >=0 && HEXString1[n] == '0') - HEXString1[n--] = 0; - - for (n = 0; n < NBMASK; n++) - sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); - - n = 39; - while (n >= 0 && HEXString2[n] == '0') - HEXString2[n--] = 0; - - sprintf(Key, "R%d", Msg->number); - - n = sprintf(Line, "%c|%c|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, - Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], - &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, - &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); - - SaveStringValue(msgs, Key, Line); - } - - // Save Bids - - msgs = config_setting_add(root, "BIDS", CONFIG_TYPE_GROUP); - - for (i=1; i <= NumberofBIDs; i++) - { - sprintf(Key, "R%s", BIDRecPtr[i]->BID); - sprintf(Line, "%d|%d", BIDRecPtr[i]->mode, BIDRecPtr[i]->u.timestamp); - SaveStringValue(msgs, Key, Line); - } - -#ifdef LINBPQ - - if(! config_write_file(&cfg,"/dev/shm/linmail.cfg.temp" )) - { - print("Error while writing file.\n"); - config_destroy(&cfg); - return; - } - - CopyFile("/dev/shm/linmail.cfg.temp", ConfigName, FALSE); - -#else -*/ - if(! config_write_file(&cfg, ConfigName)) - { - fprintf(stderr, "Error while writing file.\n"); - config_destroy(&cfg); - return; - } - -//#endif - - config_destroy(&cfg); - -/* - -#ifndef LINBPQ - - // Save a copy with current Date/Time Stamp for debugging - - { - char Backup[MAX_PATH]; - time_t LT; - struct tm * tm; - - LT = time(NULL); - tm = gmtime(<); - - sprintf(Backup,"%s.%02d%02d%02d%02d%02d.save", ConfigName, tm->tm_year-100, tm->tm_mon+1, - tm->tm_mday, tm->tm_hour, tm->tm_min); - - CopyFile(ConfigName, Backup, FALSE); // Copy to .bak - } -#endif -*/ -} - -int GetIntValue(config_setting_t * group, char * name) -{ - config_setting_t *setting; - - setting = config_setting_get_member (group, name); - if (setting) - return config_setting_get_int (setting); - - return 0; -} - -long long GetInt64Value(config_setting_t * group, char * name) -{ - config_setting_t *setting; - - setting = config_setting_get_member (group, name); - if (setting) - return config_setting_get_int64 (setting); - - return 0; -} - -double GetFloatValue(config_setting_t * group, char * name) -{ - config_setting_t *setting; - - setting = config_setting_get_member (group, name); - - if (setting) - { - return config_setting_get_float (setting); - } - return 0; -} - -int GetIntValueWithDefault(config_setting_t * group, char * name, int Default) -{ - config_setting_t *setting; - - setting = config_setting_get_member (group, name); - if (setting) - return config_setting_get_int (setting); - - return Default; -} - - -BOOL GetStringValue(config_setting_t * group, char * name, char * value) -{ - const char * str; - config_setting_t *setting; - - setting = config_setting_get_member (group, name); - if (setting) - { - str = config_setting_get_string (setting); - strcpy(value, str); - return TRUE; - } - value[0] = 0; - return FALSE; -} - -BOOL GetConfig(char * ConfigName) -{ - int i; - char Size[80]; - config_setting_t *setting; - const char * ptr; - - config_init(&cfg); - - /* Read the file. If there is an error, report it and exit. */ - - if(! config_read_file(&cfg, ConfigName)) - { - char Msg[256]; - sprintf(Msg, "Config File Line %d - %s\n", - config_error_line(&cfg), config_error_text(&cfg)); -#ifdef WIN32 - MessageBox(NULL, Msg, "BPQMail", MB_ICONSTOP); -#else - printf("%s", Msg); -#endif - config_destroy(&cfg); - return(EXIT_FAILURE); - } - -#if LIBCONFIG_VER_MINOR > 5 - config_set_option(&cfg, CONFIG_OPTION_AUTOCONVERT, 1); -#else - config_set_auto_convert (&cfg, 1); -#endif - - group = config_lookup (&cfg, "main"); - - if (group == NULL) - return EXIT_FAILURE; - - SMTPInPort = GetIntValue(group, "SMTPPort"); - POP3InPort = GetIntValue(group, "POP3Port"); - NNTPInPort = GetIntValue(group, "NNTPPort"); - RemoteEmail = GetIntValue(group, "RemoteEmail"); - MaxStreams = GetIntValue(group, "Streams"); - BBSApplNum = GetIntValue(group, "BBSApplNum"); - EnableUI = GetIntValue(group, "EnableUI"); - MailForInterval = GetIntValue(group, "MailForInterval"); - RefuseBulls = GetIntValue(group, "RefuseBulls"); - OnlyKnown = GetIntValue(group, "OnlyKnown"); - SendSYStoSYSOPCall = GetIntValue(group, "SendSYStoSYSOPCall"); - SendBBStoSYSOPCall = GetIntValue(group, "SendBBStoSYSOPCall"); - DontHoldNewUsers = GetIntValue(group, "DontHoldNewUsers"); - DefaultNoWINLINK = GetIntValue(group, "DefaultNoWINLINK"); - ForwardToMe = GetIntValue(group, "ForwardToMe"); - AllowAnon = GetIntValue(group, "AllowAnon"); - UserCantKillT = GetIntValue(group, "UserCantKillT"); - - DontNeedHomeBBS = GetIntValue(group, "DontNeedHomeBBS"); - DontCheckFromCall = GetIntValue(group, "DontCheckFromCall"); - MaxTXSize = GetIntValue(group, "MaxTXSize"); - MaxRXSize = GetIntValue(group, "MaxRXSize"); - ReaddressLocal = GetIntValue(group, "ReaddressLocal"); - ReaddressReceived = GetIntValue(group, "ReaddressReceived"); - WarnNoRoute = GetIntValue(group, "WarnNoRoute"); - SendPtoMultiple = GetIntValue(group, "SendPtoMultiple"); - 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); - 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); - ISPSMTPPort = GetIntValue(group, "ISPSMTPPort"); - ISPPOP3Port = GetIntValue(group, "ISPPOP3Port"); - GetStringValue(group, "ISPAccountName", ISPAccountName); - GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); - GetStringValue(group, "ISPAccountName", ISPAccountName); - - sprintf(SignoffMsg, "73 de %s\r", BBSName); // Default - GetStringValue(group, "SignoffMsg", SignoffMsg); - - DecryptPass(EncryptedISPAccountPass, ISPAccountPass, (int)strlen(EncryptedISPAccountPass)); - - SMTPAuthNeeded = GetIntValue(group, "AuthenticateSMTP"); - LogBBS = GetIntValue(group, "Log_BBS"); - LogTCP = GetIntValue(group, "Log_TCP"); - - MulticastRX = GetIntValue(group, "MulticastRX"); - -#ifndef LINBPQ - - GetStringValue(group, "MonitorSize", Size); - sscanf(Size,"%d,%d,%d,%d,%d",&MonitorRect.left,&MonitorRect.right,&MonitorRect.top,&MonitorRect.bottom,&OpenMon); - - GetStringValue(group, "WindowSize", Size); - sscanf(Size,"%d,%d,%d,%d",&MainRect.left,&MainRect.right,&MainRect.top,&MainRect.bottom); - - Bells = GetIntValue(group, "Bells"); - - FlashOnBell = GetIntValue(group, "FlashOnBell"); - - StripLF = GetIntValue(group, "StripLF"); - CloseWindowOnBye = GetIntValue(group, "CloseWindowOnBye"); - WarnWrap = GetIntValue(group, "WarnWrap"); - WrapInput = GetIntValue(group, "WrapInput"); - FlashOnConnect = GetIntValue(group, "FlashOnConnect"); - - GetStringValue(group, "ConsoleSize", Size); - sscanf(Size,"%d,%d,%d,%d,%d", &ConsoleRect.left, &ConsoleRect.right, - &ConsoleRect.top, &ConsoleRect.bottom,&OpenConsole); - -#endif - - // Get Welcome Messages - - setting = config_setting_get_member (group, "WelcomeMsg"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - WelcomeMsg = _strdup(ptr); - } - else - WelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); - - - setting = config_setting_get_member (group, "NewUserWelcomeMsg"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - NewWelcomeMsg = _strdup(ptr); - } - else - NewWelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); - - - setting = config_setting_get_member (group, "ExpertWelcomeMsg"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - ExpertWelcomeMsg = _strdup(ptr); - } - else - ExpertWelcomeMsg = _strdup(""); - - // Get Prompts - - setting = config_setting_get_member (group, "Prompt"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - Prompt = _strdup(ptr); - } - else - { - Prompt = malloc(20); - sprintf(Prompt, "de %s>\r\n", BBSName); - } - - setting = config_setting_get_member (group, "NewUserPrompt"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - NewPrompt = _strdup(ptr); - } - else - { - NewPrompt = malloc(20); - sprintf(NewPrompt, "de %s>\r\n", BBSName); - } - - setting = config_setting_get_member (group, "ExpertPrompt"); - - if (setting && setting->value.sval[0]) - { - ptr = config_setting_get_string (setting); - ExpertPrompt = _strdup(ptr); - } - else - { - ExpertPrompt = malloc(20); - sprintf(ExpertPrompt, "de %s>\r\n", BBSName); - } - - TidyPrompts(); - - RejFrom = GetMultiStringValue(group, "RejFrom"); - RejTo = GetMultiStringValue(group, "RejTo"); - RejAt = GetMultiStringValue(group, "RejAt"); - RejBID = GetMultiStringValue(group, "RejBID"); - - HoldFrom = GetMultiStringValue(group, "HoldFrom"); - HoldTo = GetMultiStringValue(group, "HoldTo"); - HoldAt = GetMultiStringValue(group, "HoldAt"); - HoldBID = GetMultiStringValue(group, "HoldBID"); - - // Send WP Params - - SendWP = GetIntValue(group, "SendWP"); - SendWPType = GetIntValue(group, "SendWPType"); - - GetStringValue(group, "SendWPTO", SendWPTO); - GetStringValue(group, "SendWPVIA", SendWPVIA); - - SendWPAddrs = GetMultiStringValue(group, "SendWPAddrs"); - - FilterWPBulls = GetIntValue(group, "FilterWPBulls"); - NoWPGuesses = GetIntValue(group, "NoWPGuesses"); - - if (SendWPAddrs[0] == NULL && SendWPTO[0]) - { - // convert old format TO and VIA to entry in SendWPAddrs - - SendWPAddrs = realloc(SendWPAddrs, 8); // Add entry - - if (SendWPVIA[0]) - { - char WP[256]; - - sprintf(WP, "%s@%s", SendWPTO, SendWPVIA); - SendWPAddrs[0] = _strdup(WP); - } - else - SendWPAddrs[0] = _strdup(SendWPTO); - - - SendWPAddrs[1] = 0; - - SendWPTO[0] = 0; - SendWPVIA[0] = 0; - } - - GetStringValue(group, "Version", Size); - sscanf(Size,"%d,%d,%d,%d", &LastVer[0], &LastVer[1], &LastVer[2], &LastVer[3]); - - for (i=1; i<=32; i++) - { - char Key[100]; - - sprintf(Key, "UIPort%d", i); - - group = config_lookup (&cfg, Key); - - if (group) - { - UIEnabled[i] = GetIntValue(group, "Enabled"); - UIMF[i] = GetIntValueWithDefault(group, "SendMF", UIEnabled[i]); - UIHDDR[i] = GetIntValueWithDefault(group, "SendHDDR", UIEnabled[i]); - UINull[i] = GetIntValue(group, "SendNull"); - Size[0] = 0; - GetStringValue(group, "Digis", Size); - if (Size[0]) - UIDigi[i] = _strdup(Size); - } - } - - group = config_lookup (&cfg, "Housekeeping"); - - if (group) - { - LastHouseKeepingTime = GetIntValue(group, "LastHouseKeepingTime"); - LastTrafficTime = GetIntValue(group, "LastTrafficTime"); - MaxMsgno = GetIntValue(group, "MaxMsgno"); - LogAge = GetIntValue(group, "LogLifetime"); - BidLifetime = GetIntValue(group, "BidLifetime"); - MaxAge = GetIntValue(group, "MaxAge"); - if (MaxAge == 0) - MaxAge = 30; - UserLifetime = GetIntValue(group, "UserLifetime"); - MaintInterval = GetIntValue(group, "MaintInterval"); - - if (MaintInterval == 0) - MaintInterval = 24; - - MaintTime = GetIntValue(group, "MaintTime"); - - PR = GetFloatValue(group, "PR"); - PUR = GetFloatValue(group, "PUR"); - PF = GetFloatValue(group, "PF"); - PNF = GetFloatValue(group, "PNF"); - - BF = GetIntValue(group, "BF"); - BNF = GetIntValue(group, "BNF"); - NTSD = GetIntValue(group, "NTSD"); - NTSU = GetIntValue(group, "NTSU"); - NTSF = GetIntValue(group, "NTSF"); -// AP = GetIntValue(group, "AP"); -// AB = GetIntValue(group, "AB"); - DeletetoRecycleBin = GetIntValue(group, "DeletetoRecycleBin"); - SuppressMaintEmail = GetIntValue(group, "SuppressMaintEmail"); - SaveRegDuringMaint = GetIntValue(group, "MaintSaveReg"); - OverrideUnsent = GetIntValue(group, "OverrideUnsent"); - SendNonDeliveryMsgs = GetIntValue(group, "SendNonDeliveryMsgs"); - OverrideUnsent = GetIntValue(group, "OverrideUnsent"); - GenerateTrafficReport = GetIntValueWithDefault(group, "GenerateTrafficReport", 1); - - LTFROM = GetOverrides(group, "LTFROM"); - LTTO = GetOverrides(group, "LTTO"); - LTAT = GetOverrides(group, "LTAT"); - } - - return EXIT_SUCCESS; -} - - -int Connected(int Stream) -{ - int n, Mask; - CIRCUIT * conn; - struct UserInfo * user = NULL; - char callsign[10]; - int port, paclen, maxframe, l4window; - char ConnectedMsg[] = "*** CONNECTED "; - char Msg[100]; - char Title[100]; - int Freq = 0; - int Mode = 0; - BPQVECSTRUC * SESS; - TRANSPORTENTRY * Sess1 = NULL, * Sess2; - - for (n = 0; n < NumberofStreams; n++) - { - conn = &Connections[n]; - - if (Stream == conn->BPQStream) - { - if (conn->Active) - { - // Probably an outgoing connect - - ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions - conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; - conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; - conn->ErrorCount = 0; - - if (conn->BBSFlags & RunningConnectScript) - { - // BBS Outgoing Connect - - conn->paclen = 236; - - // Run first line of connect script - - ChangeSessionIdletime(Stream, BBSIDLETIME); // Default Idletime for BBS Sessions - ProcessBBSConnectScript(conn, ConnectedMsg, 15); - return 0; - } - } - - // Incoming Connect - - // Try to find port, freq, mode, etc - -#ifdef LINBPQ - SESS = &BPQHOSTVECTOR[0]; -#else - SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; -#endif - SESS +=(Stream - 1); - - if (SESS) - Sess1 = SESS->HOSTSESSION; - - if (Sess1) - { - Sess2 = Sess1->L4CROSSLINK; - - if (Sess2) - { - // See if L2 session - if so, get info from WL2K report line - - // if Session has report info, use it - - if (Sess2->Mode) - { - Freq = Sess2->Frequency; - Mode = Sess2->Mode; - } - else if (Sess2->L4CIRCUITTYPE & L2LINK) - { - LINKTABLE * LINK = Sess2->L4TARGET.LINK; - PORTCONTROLX * PORT = LINK->LINKPORT; - - Freq = PORT->WL2KInfo.Freq; - Mode = PORT->WL2KInfo.mode; - } - else - { - if (Sess2->RMSCall[0]) - { - Freq = Sess2->Frequency; - Mode = Sess2->Mode; - } - } - } - } - - memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything - conn->Active = TRUE; - conn->BPQStream = Stream; - ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions - - conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; - conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; - conn->ErrorCount = 0; - - conn->Secure_Session = GetConnectionInfo(Stream, callsign, - &port, &conn->SessType, &paclen, &maxframe, &l4window); - - strlop(callsign, ' '); // Remove trailing spaces - - if (strcmp(&callsign[strlen(callsign) - 2], "-T") == 0) - conn->RadioOnlyMode = 'T'; - else if (strcmp(&callsign[strlen(callsign) - 2], "-R") == 0) - conn->RadioOnlyMode = 'R'; - else - conn->RadioOnlyMode = 0; - - memcpy(conn->Callsign, callsign, 10); - - strlop(callsign, '-'); // Remove any SSID - - user = LookupCall(callsign); - - if (user == NULL) - { - int Length=0; - - if (OnlyKnown) - { - // Unknown users not allowed - - n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from unknown user %s Rejected", callsign); - WriteLogLine(conn, '|',Msg, n, LOG_BBS); - - Disconnect(Stream); - return 0; - } - - user = AllocateUserRecord(callsign); - user->Temp = zalloc(sizeof (struct TempUserInfo)); - - if (SendNewUserMessage) - { - char * MailBuffer = malloc(100); - Length += sprintf(MailBuffer, "New User %s Connected to Mailbox on Port %d Freq %d Mode %d\r\n", callsign, port, Freq, Mode); - - sprintf(Title, "New User %s", callsign); - - SendMessageToSYSOP(Title, MailBuffer, Length); - } - - if (user == NULL) return 0; // Cant happen?? - - if (!DontHoldNewUsers) - user->flags |= F_HOLDMAIL; - - if (DefaultNoWINLINK) - user->flags |= F_NOWINLINK; - - // Always set WLE User - can't see it doing any harm - - user->flags |= F_Temp_B2_BBS; - - conn->NewUser = TRUE; - } - - user->TimeLastConnected = time(NULL); - user->Total.ConnectsIn++; - - conn->UserPointer = user; - - conn->lastmsg = user->lastmsg; - - conn->NextMessagetoForward = FirstMessageIndextoForward; - - if (paclen == 0) - { - paclen = 236; - - if (conn->SessType & Sess_PACTOR) - paclen = 100; - } - - conn->paclen = paclen; - - // Set SYSOP flag if user is defined as SYSOP and Host Session - - if (((conn->SessType & Sess_BPQHOST) == Sess_BPQHOST) && (user->flags & F_SYSOP)) - conn->sysop = TRUE; - - if (conn->Secure_Session && (user->flags & F_SYSOP)) - conn->sysop = TRUE; - - Mask = 1 << (GetApplNum(Stream) - 1); - - if (user->flags & F_Excluded) - { - n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s Rejected by Exclude Flag", user->Call); - WriteLogLine(conn, '|',Msg, n, LOG_BBS); - Disconnect(Stream); - return 0; - } - - if (port) - n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s on Port %d Freq %d Mode %s", - user->Call, port, Freq, WL2KModes[Mode]); - else - n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s", user->Call); - - // Send SID and Prompt (Unless Sync) - - if (user->ForwardingInfo && user->ForwardingInfo->ConTimeout) - conn->SIDResponseTimer = user->ForwardingInfo->ConTimeout / 10; // 10 sec ticks - else - conn->SIDResponseTimer = 12; // Allow a couple of minutes for response to SID - - { - BOOL B1 = FALSE, B2 = FALSE, BIN = FALSE, BLOCKED = FALSE; - BOOL WL2KRO = FALSE; - - struct BBSForwardingInfo * ForwardingInfo; - - if (conn->RadioOnlyMode == 'R') - WL2KRO = 1; - - conn->PageLen = user->PageLen; - conn->Paging = (user->PageLen > 0); - - if (user->flags & F_Temp_B2_BBS) - { - // An RMS Express user that needs a temporary BBS struct - - if (user->ForwardingInfo == NULL) - { - // we now save the Forwarding info if BBS flag is cleared, - // so there may already be a ForwardingInfo - - user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); - } - - if (user->BBSNumber == 0) - user->BBSNumber = NBBBS; - - ForwardingInfo = user->ForwardingInfo; - - ForwardingInfo->AllowCompressed = TRUE; - B1 = ForwardingInfo->AllowB1 = FALSE; - B2 = ForwardingInfo->AllowB2 = TRUE; - BLOCKED = ForwardingInfo->AllowBlocked = TRUE; - } - - if (conn->NewUser) - { - BLOCKED = TRUE; - BIN = TRUE; - B2 = TRUE; - } - - if (user->ForwardingInfo) - { - BLOCKED = user->ForwardingInfo->AllowBlocked; - if (BLOCKED) - { - BIN = user->ForwardingInfo->AllowCompressed; - B1 = user->ForwardingInfo->AllowB1; - B2 = user->ForwardingInfo->AllowB2; - } - } - - WriteLogLine(conn, '|',Msg, n, LOG_BBS); - - if (conn->RadioOnlyMode) - nodeprintf(conn,";WL2K-Radio/Internet_Network\r"); - - if (!(conn->BBSFlags & SYNCMODE)) - { - - nodeprintf(conn, BBSSID, "BPQ-", - Ver[0], Ver[1], Ver[2], Ver[3], - BIN ? "B" : "", B1 ? "1" : "", B2 ? "2" : "", - BLOCKED ? "FW": "", WL2KRO ? "" : "J"); - - // if (user->flags & F_Temp_B2_BBS) - // nodeprintf(conn,";PQ: 66427529\r"); - - // nodeprintf(conn,"[WL2K-BPQ.1.0.4.39-B2FWIHJM$]\r"); - } - } - - if ((user->Name[0] == 0) & AllowAnon) - strcpy(user->Name, user->Call); - - if (!(conn->BBSFlags & SYNCMODE)) - { - if (user->Name[0] == 0) - { - conn->Flags |= GETTINGUSER; - BBSputs(conn, NewUserPrompt); - } - else - SendWelcomeMsg(Stream, conn, user); - } - else - { - // Seems to be a timing problem - see if this fixes it - - Sleep(500); - } - - RefreshMainWindow(); - - return 0; - } - } - - return 0; -} - -int Disconnected (int Stream) -{ - struct UserInfo * user = NULL; - CIRCUIT * conn; - int n; - char Msg[255]; - int len; - char DiscMsg[] = "DISCONNECTED "; - - for (n = 0; n <= NumberofStreams-1; n++) - { - conn=&Connections[n]; - - if (Stream == conn->BPQStream) - { - if (conn->Active == FALSE) - return 0; - - // if still running connect script, reenter it to see if - // there is an else - - if (conn->BBSFlags & RunningConnectScript) - { - // We need to see if we got as far as connnected, - // as if we have we need to reset the connect script - // over the ELSE - - struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; - char ** Scripts; - - if (ForwardingInfo->TempConnectScript) - Scripts = ForwardingInfo->TempConnectScript; - else - Scripts = ForwardingInfo->ConnectScript; - - // First see if any script left - - if (Scripts[ForwardingInfo->ScriptIndex]) - { - if (ForwardingInfo->MoreLines == FALSE) - { - // Have reached end of script, so need to set back over ELSE - - ForwardingInfo->ScriptIndex--; - ForwardingInfo->MoreLines = TRUE; - } - - // if (Scripts[ForwardingInfo->ScriptIndex] == NULL || - // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished - // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished - - - ProcessBBSConnectScript(conn, DiscMsg, 15); - return 0; - } - } - - // if sysop was chatting to user clear link -#ifndef LINBPQ - if (conn->BBSFlags & SYSOPCHAT) - { - SendUnbuffered(-1, "User has disconnected\n", 23); - BBSConsole.Console->SysopChatStream = 0; - } -#endif - ClearQueue(conn); - - if (conn->PacLinkCalls) - free(conn->PacLinkCalls); - - if (conn->InputBuffer) - { - free(conn->InputBuffer); - conn->InputBuffer = NULL; - conn->InputBufferLen = 0; - } - - if (conn->InputMode == 'B') - { - // Save partly received message for a restart - - if (conn->BBSFlags & FBBB1Mode) - if (conn->Paclink == 0) // Paclink doesn't do restarts - if (strcmp(conn->Callsign, "RMS") != 0) // Neither does RMS Packet. - if (conn->DontSaveRestartData == FALSE) - SaveFBBBinary(conn); - } - - conn->Active = FALSE; - - if (conn->FwdMsg) - conn->FwdMsg->Locked = 0; // Unlock - - RefreshMainWindow(); - - RemoveTempBIDS(conn); - - len=sprintf_s(Msg, sizeof(Msg), "%s Disconnected", conn->Callsign); - WriteLogLine(conn, '|',Msg, len, LOG_BBS); - - if (conn->FBBHeaders) - { - struct FBBHeaderLine * FBBHeader; - int n; - - for (n = 0; n < 5; n++) - { - FBBHeader = &conn->FBBHeaders[n]; - - if (FBBHeader->FwdMsg) - FBBHeader->FwdMsg->Locked = 0; // Unlock - - } - - free(conn->FBBHeaders); - conn->FBBHeaders = NULL; - } - - if (conn->UserPointer) - { - struct BBSForwardingInfo * FWDInfo = conn->UserPointer->ForwardingInfo; - - if (FWDInfo) - { - FWDInfo->Forwarding = FALSE; - -// if (FWDInfo->UserCall[0]) // Will be set if RMS -// { -// FindNextRMSUser(FWDInfo); -// } -// else - FWDInfo->FwdTimer = 0; - } - } - - conn->BBSFlags = 0; // Clear ARQ Mode - - return 0; - } - } - return 0; -} - -int DoReceivedData(int Stream) -{ - int count, InputLen; - size_t MsgLen; - int n; - CIRCUIT * conn; - struct UserInfo * user; - char * ptr, * ptr2; - char * Buffer; - - for (n = 0; n < NumberofStreams; n++) - { - conn = &Connections[n]; - - if (Stream == conn->BPQStream) - { - conn->SIDResponseTimer = 0; // Got a message, so cancel timeout. - - do - { - // May have several messages per packet, or message split over packets - - OuterLoop: - if (conn->InputLen + 1000 > conn->InputBufferLen ) // Shouldnt have lines longer than this in text mode - { - conn->InputBufferLen += 1000; - conn->InputBuffer = realloc(conn->InputBuffer, conn->InputBufferLen); - } - - GetMsg(Stream, &conn->InputBuffer[conn->InputLen], &InputLen, &count); - - if (InputLen == 0 && conn->InputMode != 'Y') - return 0; - - conn->InputLen += InputLen; - - if (conn->InputLen == 0) return 0; - - conn->Watchdog = 900; // 15 Minutes - - if (conn->InputMode == 'Y') // YAPP - { - if (ProcessYAPPMessage(conn)) // Returns TRUE if there could be more to process - goto OuterLoop; - - return 0; - } - - if (conn->InputMode == 'B') - { - // if in OpenBCM mode, remove FF transparency - - if (conn->OpenBCM) // Telnet, so escape any 0xFF - { - unsigned char * ptr1 = conn->InputBuffer; - unsigned char * ptr2; - int Len; - unsigned char c; - - // We can come through here again for the - // same data as we wait for a full packet - // So only check last InputLen bytes - - ptr1 += (conn->InputLen - InputLen); - ptr2 = ptr1; - Len = InputLen; - - while (Len--) - { - c = *(ptr1++); - - if (conn->InTelnetExcape) // Last char was ff - { - conn->InTelnetExcape = FALSE; - continue; - } - - *(ptr2++) = c; - - if (c == 0xff) // - conn->InTelnetExcape = TRUE; - } - - conn->InputLen = (int)(ptr2 - conn->InputBuffer); - } - - UnpackFBBBinary(conn); - goto OuterLoop; - } - else - { - - loop: - - if (conn->InputLen == 1 && conn->InputBuffer[0] == 0) // Single Null - { - conn->InputLen = 0; - return 0; - } - - user = conn->UserPointer; - - if (conn->BBSFlags & (MCASTRX | SYNCMODE)) - { - // MCAST and SYNCMODE deliver full packets - - if (conn->BBSFlags & RunningConnectScript) - ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); - else - ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); - - conn->InputLen=0; - continue; - } - - // This looks for CR, CRLF, LF or CR/Null and removes any LF or NULL, - // but this relies on both arriving in same packet. - // Need to check for LF and start of packet and ignore it - // But what if client is only using LF?? - // (WLE sends SID with CRLF, other packets with CR only) - - // We don't get here on the data part of a binary transfer, so - // don't need to worry about messing up binary data. - - ptr = memchr(conn->InputBuffer, '\r', conn->InputLen); - ptr2 = memchr(conn->InputBuffer, '\n', conn->InputLen); - - if (ptr) - conn->usingCR = 1; - - if ((ptr2 && ptr2 < ptr) || ptr == 0) // LF before CR, or no CR - ptr = ptr2; // Use LF - - if (ptr) // CR or LF in buffer - { - conn->lastLineEnd = *(ptr); - - *(ptr) = '\r'; // In case was LF - - ptr2 = &conn->InputBuffer[conn->InputLen]; - - if (++ptr == ptr2) - { - // Usual Case - single msg in buffer - - // if Length is 1 and Term is LF and normal line end is CR - // this is from a split CRLF - Ignore it - - if (conn->InputLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) - Debugprintf("BPQMail split Line End Detected"); - else - { - if (conn->BBSFlags & RunningConnectScript) - ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); - else - ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); - } - conn->InputLen=0; - } - else - { - // buffer contains more that 1 message - - MsgLen = conn->InputLen - (ptr2-ptr); - - Buffer = malloc(MsgLen + 100); - - memcpy(Buffer, conn->InputBuffer, MsgLen); - - // if Length is 1 and Term is LF and normal line end is CR - // this is from a split CRLF - Ignore it - - if (MsgLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) - Debugprintf("BPQMail split Line End Detected"); - else - { - if (conn->BBSFlags & RunningConnectScript) - ProcessBBSConnectScript(conn, Buffer, (int)MsgLen); - else - ProcessLine(conn, user, Buffer, (int)MsgLen); - } - free(Buffer); - - if (*ptr == 0 || *ptr == '\n') - { - /// CR LF or CR Null - - ptr++; - conn->InputLen--; - } - - memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen); - - conn->InputLen -= (int)MsgLen; - - goto loop; - - } - } - else - { - // Could be a YAPP Header - - - if (conn->InputLen == 2 && conn->InputBuffer[0] == ENQ && conn->InputBuffer[1] == 1) // YAPP Send_Init - { - UCHAR YAPPRR[2]; - YAPPRR[0] = ACK; - YAPPRR[1] = 1; - - conn->InputMode = 'Y'; - QueueMsg(conn, YAPPRR, 2); - - conn->InputLen = 0; - return 0; - } - } - } - - } while (count > 0); - - return 0; - } - } - - // Socket not found - - return 0; - -} -int DoBBSMonitorData(int Stream) -{ -// UCHAR Buffer[1000]; - UCHAR buff[500]; - - int len = 0,count=0; - int stamp; - - do - { - stamp=GetRaw(Stream, buff,&len,&count); - - if (len == 0) return 0; - - SeeifBBSUIFrame((struct _MESSAGEX *)buff, len); - } - - while (count > 0); - - - return 0; - -} - -VOID ProcessFLARQLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen) -{ - Buffer[MsgLen] = 0; - - if (MsgLen == 1 && Buffer[0] == 13) - return; - - if (strcmp(Buffer, "ARQ::ETX\r") == 0) - { - // Decode it. - - UCHAR * ptr1, * ptr2, * ptr3; - int len, linelen; - struct MsgInfo * Msg = conn->TempMsg; - time_t Date; - char FullTo[100]; - char FullFrom[100]; - char ** RecpTo = NULL; // May be several Recipients - char ** HddrTo = NULL; // May be several Recipients - char ** Via = NULL; // May be several Recipients - int LocalMsg[1000] ; // Set if Recipient is a local wl2k address - - int B2To; // Offset to To: fields in B2 header - int Recipients = 0; - int RMSMsgs = 0, BBSMsgs = 0; - -// Msg->B2Flags |= B2Msg; - - - ptr1 = conn->MailBuffer; - len = Msg->length; - ptr1[len] = 0; - - if (strstr(ptr1, "ARQ:ENCODING::")) - { - // a file, not a message. If is called "BBSPOLL" do a reverse forward else Ignore for now - - _strupr(conn->MailBuffer); - if (strstr(conn->MailBuffer, "BBSPOLL")) - { - SendARQMail(conn); - } - - free(conn->MailBuffer); - conn->MailBuffer = NULL; - conn->MailBufferSize = 0; - - return; - } - Loop: - ptr2 = strchr(ptr1, '\r'); - - linelen = (int)(ptr2 - ptr1); - - if (_memicmp(ptr1, "From:", 5) == 0 && linelen > 6) // Can have empty From: - { - char SaveFrom[100]; - char * FromHA; - - memcpy(FullFrom, ptr1, linelen); - FullFrom[linelen] = 0; - - // B2 From may now contain an @BBS - - strcpy(SaveFrom, FullFrom); - - FromHA = strlop(SaveFrom, '@'); - - if (strlen(SaveFrom) > 12) SaveFrom[12] = 0; - - strcpy(Msg->from, &SaveFrom[6]); - - if (FromHA) - { - if (strlen(FromHA) > 39) FromHA[39] = 0; - Msg->emailfrom[0] = '@'; - strcpy(&Msg->emailfrom[1], _strupr(FromHA)); - } - - // Remove any SSID - - ptr3 = strchr(Msg->from, '-'); - if (ptr3) *ptr3 = 0; - - } - else if (_memicmp(ptr1, "To:", 3) == 0 || _memicmp(ptr1, "cc:", 3) == 0) - { - HddrTo=realloc(HddrTo, (Recipients+1) * sizeof(void *)); - HddrTo[Recipients] = zalloc(100); - - memset(FullTo, 0, 99); - memcpy(FullTo, &ptr1[4], linelen-4); - memcpy(HddrTo[Recipients], ptr1, linelen+2); - LocalMsg[Recipients] = FALSE; - - _strupr(FullTo); - - B2To = (int)(ptr1 - conn->MailBuffer); - - if (_memicmp(FullTo, "RMS:", 4) == 0) - { - // remove RMS and add @winlink.org - - strcpy(FullTo, "RMS"); - strcpy(Msg->via, &FullTo[4]); - } - else - { - ptr3 = strchr(FullTo, '@'); - - if (ptr3) - { - *ptr3++ = 0; - strcpy(Msg->via, ptr3); - } - else - Msg->via[0] = 0; - } - - if (_memicmp(&ptr1[4], "SMTP:", 5) == 0) - { - // Airmail Sends MARS messages as SMTP - - if (CheckifPacket(Msg->via)) - { - // Packet Message - - memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); - _strupr(FullTo); - _strupr(Msg->via); - - // Update the saved to: line (remove the smtp:) - - strcpy(&HddrTo[Recipients][4], &HddrTo[Recipients][9]); - BBSMsgs++; - goto BBSMsg; - } - - // If a winlink.org address we need to convert to call - - if (_stricmp(Msg->via, "winlink.org") == 0) - { - memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); - _strupr(FullTo); - LocalMsg[Recipients] = CheckifLocalRMSUser(FullTo); - } - else - { - memcpy(Msg->via, &ptr1[9], linelen); - Msg->via[linelen - 9] = 0; - strcpy(FullTo,"RMS"); - } -// FullTo[0] = 0; - - BBSMsg: - _strupr(FullTo); - _strupr(Msg->via); - } - - if (memcmp(FullTo, "RMS:", 4) == 0) - { - // remove RMS and add @winlink.org - - memmove(FullTo, &FullTo[4], strlen(FullTo) - 3); - strcpy(Msg->via, "winlink.org"); - sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); - } - - if (strcmp(Msg->via, "RMS") == 0) - { - // replace RMS with @winlink.org - - strcpy(Msg->via, "winlink.org"); - sprintf(HddrTo[Recipients], "To: %s@winlink.org\r\n", FullTo); - } - - if (strlen(FullTo) > 6) - FullTo[6] = 0; - - strlop(FullTo, '-'); - - strcpy(Msg->to, FullTo); - - if (SendBBStoSYSOPCall) - if (_stricmp(FullTo, BBSName) == 0) - strcpy(Msg->to, SYSOPCall); - - if ((Msg->via[0] == 0 || strcmp(Msg->via, "BPQ") == 0 || strcmp(Msg->via, "BBS") == 0)) - { - // No routing - check @BBS and WP - - struct UserInfo * ToUser = LookupCall(FullTo); - - Msg->via[0] = 0; // In case BPQ and not found - - if (ToUser) - { - // Local User. If Home BBS is specified, use it - - if (ToUser->HomeBBS[0]) - { - strcpy(Msg->via, ToUser->HomeBBS); - } - } - else - { - WPRecP WP = LookupWP(FullTo); - - if (WP) - { - strcpy(Msg->via, WP->first_homebbs); - - } - } - - // Fix To: address in B2 Header - - if (Msg->via[0]) - sprintf(HddrTo[Recipients], "To: %s@%s\r\n", FullTo, Msg->via); - else - sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); - - } - - RecpTo=realloc(RecpTo, (Recipients+1) * sizeof(void *)); - RecpTo[Recipients] = zalloc(10); - - Via=realloc(Via, (Recipients+1) * sizeof(void *)); - Via[Recipients] = zalloc(50); - - strcpy(Via[Recipients], Msg->via); - strcpy(RecpTo[Recipients++], FullTo); - - // Remove the To: Line from the buffer - - } - else if (_memicmp(ptr1, "Type:", 4) == 0) - { - if (ptr1[6] == 'N') - Msg->type = 'T'; // NTS - else - Msg->type = ptr1[6]; - } - else if (_memicmp(ptr1, "Subject:", 8) == 0) - { - size_t Subjlen = ptr2 - &ptr1[9]; - if (Subjlen > 60) Subjlen = 60; - memcpy(Msg->title, &ptr1[9], Subjlen); - - goto ProcessBody; - } -// else if (_memicmp(ptr1, "Body:", 4) == 0) -// { -// MsgLen = atoi(&ptr1[5]); -// StartofMsg = ptr1; -// } - else if (_memicmp(ptr1, "File:", 5) == 0) - { - Msg->B2Flags |= Attachments; - } - else if (_memicmp(ptr1, "Date:", 5) == 0) - { - struct tm rtime; - char seps[] = " ,\t\r"; - - memset(&rtime, 0, sizeof(struct tm)); - - // Date: 2009/07/25 10:08 - - sscanf(&ptr1[5], "%04d/%02d/%02d %02d:%02d:%02d", - &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); - - sscanf(&ptr1[5], "%02d/%02d/%04d %02d:%02d:%02d", - &rtime.tm_mday, &rtime.tm_mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); - - rtime.tm_year -= 1900; - - Date = mktime(&rtime) - (time_t)_MYTIMEZONE; - - if (Date == (time_t)-1) - Date = time(NULL); - - Msg->datecreated = Date; - - } - - if (linelen) // Not Null line - { - ptr1 = ptr2 + 2; // Skip cr - goto Loop; - } - - - // Processed all headers -ProcessBody: - - ptr2 +=2; // skip crlf - - Msg->length = (int)(&conn->MailBuffer[Msg->length] - ptr2); - - memmove(conn->MailBuffer, ptr2, Msg->length); - - CreateMessageFromBuffer(conn); - - conn->BBSFlags = 0; // Clear ARQ Mode - return; - } - - // File away the data - - Buffer[MsgLen++] = 0x0a; // BBS Msgs stored with crlf - - if ((conn->TempMsg->length + MsgLen) > conn->MailBufferSize) - { - conn->MailBufferSize += 10000; - conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); - - if (conn->MailBuffer == NULL) - { - BBSputs(conn, "*** Failed to extend Message Buffer\r"); - conn->CloseAfterFlush = 20; // 2 Secs - - return; - } - } - - memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, MsgLen); - - conn->TempMsg->length += MsgLen; - - return; - - // Not sure what to do yet with files, but will process emails (using text style forwarding - -/* -ARQ:FILE::flarqmail-1.eml -ARQ:EMAIL:: -ARQ:SIZE::96 -ARQ::STX -//FLARQ COMPOSER -Date: 16/01/2014 22:26:06 -To: g8bpq -From: -Subject: test message - -Hello -Hello - -ARQ::ETX -*/ - - return; -} - -VOID ProcessTextFwdLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int len) -{ - Buffer[len] = 0; -// Debugprintf(Buffer); - - // With TNC2 body prompt is a single CR, so that shouldn't be ignored. - - // If thia causes problems with other TNC PMS implementations I'll have to revisit this - -// if (len == 1 && Buffer[0] == 13) -// return; - - if (conn->Flags & SENDTITLE) - { - // Waiting for Subject: prompt - - struct MsgInfo * Msg = conn->FwdMsg; - - nodeprintf(conn, "%s\r", Msg->title); - - conn->Flags &= ~SENDTITLE; - conn->Flags |= SENDBODY; - - // New Paccom PMS (V3.2) doesn't prompt for body so drop through and send it - if ((conn->BBSFlags & NEWPACCOM) == 0) - return; - - } - - if (conn->Flags & SENDBODY) - { - // Waiting for Enter Message Prompt - - struct tm * tm; - time_t temp; - - char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); - char * MsgPtr; - int MsgLen; - int Index = 0; - - if (MsgBytes == 0) - { - MsgBytes = _strdup("Message file not found\r"); - conn->FwdMsg->length = (int)strlen(MsgBytes); - } - - MsgPtr = MsgBytes; - MsgLen = conn->FwdMsg->length; - - // If a B2 Message, remove B2 Header - - if (conn->FwdMsg->B2Flags & B2Msg) - { - // Remove all B2 Headers, and all but the first part. - - MsgPtr = strstr(MsgBytes, "Body:"); - - if (MsgPtr) - { - MsgLen = atoi(&MsgPtr[5]); - MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers - - if (MsgPtr) - MsgPtr +=4; - else - MsgPtr = MsgBytes; - - } - else - MsgPtr = MsgBytes; - } - - memcpy(&temp, &conn->FwdMsg->datereceived, sizeof(time_t)); - tm = gmtime(&temp); - - nodeprintf(conn, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r", - tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, - conn->FwdMsg->number, BBSName, HRoute, RlineVer); - - if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header - BBSputs(conn, "\r"); - - MsgLen = RemoveLF(MsgPtr, MsgLen); - - QueueMsg(conn, MsgPtr, MsgLen); - - if (user->ForwardingInfo->SendCTRLZ) - nodeprintf(conn, "\r\x1a"); - else - nodeprintf(conn, "\r/ex\r"); - - free(MsgBytes); - - conn->FBBMsgsSent = TRUE; - - - if (conn->FwdMsg->type == 'P') - Index = PMSG; - else if (conn->FwdMsg->type == 'B') - Index = BMSG; - else if (conn->FwdMsg->type == 'T') - Index = TMSG; - - user->Total.MsgsSent[Index]++; - user->Total.BytesForwardedOut[Index] += MsgLen; - - conn->Flags &= ~SENDBODY; - conn->Flags |= WAITPROMPT; - - return; - } - - if (conn->Flags & WAITPROMPT) - { - if (Buffer[len-2] != '>') - return; - - conn->Flags &= ~WAITPROMPT; - - clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); - set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); - - // Only mark as forwarded if sent to all BBSs that should have it - - if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) - { - conn->FwdMsg->status = 'F'; // Mark as forwarded - conn->FwdMsg->datechanged=time(NULL); - } - - SaveMessageDatabase(); - - conn->UserPointer->ForwardingInfo->MsgCount--; - - // See if any more to forward - - if (FindMessagestoForward(conn) && conn->FwdMsg) - { - struct MsgInfo * Msg; - - // If we are using SETCALLTOSENDER make sure this message is from the same sender - -#ifdef LINBPQ - BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; -#else - BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; -#endif - unsigned char AXCall[7]; - - Msg = conn->FwdMsg; - ConvToAX25(Msg->from, AXCall); - if (memcmp(SESS[conn->BPQStream - 1].HOSTSESSION->L4USER, AXCall, 7) != 0) - { - Disconnect(conn->BPQStream); - return; - } - - // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ - - conn->Flags |= SENDTITLE; - - - if ((conn->BBSFlags & SETCALLTOSENDER)) - nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, - (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); - else - nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, - (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, - Msg->from, Msg->bid); - } - else - { - Disconnect(conn->BPQStream); - } - return; - } -} - - -#define N 2048 /* buffer size */ -#define F 60 /* lookahead buffer size */ -#define THRESHOLD 2 -#define NIL N /* leaf of tree */ - -extern UCHAR * infile; - -BOOL CheckforMIME(SocketConn * sockptr, char * Msg, char ** Body, int * MsgLen); - - -VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) -{ - char * Cmd, * Arg1; - char * Context; - char seps[] = " \t\r"; - int CmdLen; - - if (_memicmp(Buffer, "POSYNCLOGON", 11) == 0) - { - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - conn->BBSFlags |= SYNCMODE; - conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); - - Sleep(500); - - BBSputs(conn, "OK\r"); - Flush(conn); - return; - } - - if (_memicmp(Buffer, "POSYNCHELLO", 11) == 0) - { - // This is first message received after connecting to SYNC - // Save Callsign - - char Reply[32]; - conn->BBSFlags |= SYNCMODE; - conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); - - sprintf(Reply, "POSYNCLOGON %s\r", BBSName); - BBSputs(conn, Reply); - return; - } - - if (conn->BBSFlags & SYNCMODE) - { - ProcessSyncModeMessage(conn, user, Buffer, len); - return; - } - - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - // A few messages should be trapped here and result in an immediate disconnect, whatever mode I think the session is in (it could be wrong) - - // *** Protocol Error - // Already Connected - // Invalid Command - - if (_memicmp(Buffer, "Already Connected", 17) == 0 || - _memicmp(Buffer, "Invalid Command", 15) == 0 || - _memicmp(Buffer, "*** Protocol Error", 18) == 0) - { - conn->BBSFlags |= DISCONNECTING; - Disconnect(conn->BPQStream); - return; - } - - if (conn->BBSFlags & FBBForwarding) - { - ProcessFBBLine(conn, user, Buffer, len); - return; - } - - if (conn->BBSFlags & FLARQMODE) - { - ProcessFLARQLine(conn, user, Buffer, len); - return; - } - - if (conn->BBSFlags & MCASTRX) - { - ProcessMCASTLine(conn, user, Buffer, len); - return; - } - - - if (conn->BBSFlags & TEXTFORWARDING) - { - ProcessTextFwdLine(conn, user, Buffer, len); - return; - } - - // if chatting to sysop pass message to BBS console - - if (conn->BBSFlags & SYSOPCHAT) - { - SendUnbuffered(-1, Buffer,len); - return; - } - - if (conn->Flags & GETTINGMESSAGE) - { - ProcessMsgLine(conn, user, Buffer, len); - return; - } - if (conn->Flags & GETTINGTITLE) - { - ProcessMsgTitle(conn, user, Buffer, len); - return; - } - - if (conn->BBSFlags & MBLFORWARDING) - { - ProcessMBLLine(conn, user, Buffer, len); - return; - } - - if (conn->Flags & GETTINGUSER || conn->NewUser) // Could be new user but dont need name - { - if (memcmp(Buffer, ";FW:", 4) == 0 || Buffer[0] == '[') - { - struct BBSForwardingInfo * ForwardingInfo; - - conn->Flags &= ~GETTINGUSER; - - // New User is a BBS - create a temp struct for it - - if ((user->flags & (F_BBS | F_Temp_B2_BBS)) == 0) // It could already be a BBS without a user name - { - // Not defined as BBS - allocate and initialise forwarding structure - - user->flags |= F_Temp_B2_BBS; - - // An RMS Express user that needs a temporary BBS struct - - ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); - - ForwardingInfo->AllowCompressed = TRUE; - ForwardingInfo->AllowBlocked = TRUE; - conn->UserPointer->ForwardingInfo->AllowB2 = TRUE; - } - SaveUserDatabase(); - } - else - { - if (conn->Flags & GETTINGUSER) - { - conn->Flags &= ~GETTINGUSER; - if (len > 18) - len = 18; - - memcpy(user->Name, Buffer, len-1); - SendWelcomeMsg(conn->BPQStream, conn, user); - SaveUserDatabase(); - UpdateWPWithUserInfo(user); - return; - } - } - } - - // Process Command - - if (conn->Paging && (conn->LinesSent >= conn->PageLen)) - { - // Waiting for paging prompt - - if (len > 1) - { - if (_memicmp(Buffer, "Abort", 1) == 0) - { - ClearQueue(conn); - conn->LinesSent = 0; - - nodeprintf(conn, AbortedMsg); - - if (conn->UserPointer->Temp->ListSuspended) - nodeprintf(conn, "bort, , = Continue..>"); - - SendPrompt(conn, user); - return; - } - } - - conn->LinesSent = 0; - return; - } - - if (user->Temp->ListSuspended) - { - // Paging limit hit when listing. User may abort, continue, or read one or more messages - - ProcessSuspendedListCommand(conn, user, Buffer, len); - return; - } - if (len == 1) - { - SendPrompt(conn, user); - return; - } - - Buffer[len] = 0; - - if (strstr(Buffer, "ARQ:FILE:")) - { - // Message from FLARQ - - conn->BBSFlags |= FLARQMODE; - strcpy(conn->ARQFilename, &Buffer[10]); // Will need name when we decide what to do with files - - // Create a Temp Messge Stucture - - CreateMessage(conn, conn->Callsign, "", "", 'P', NULL, NULL); - - Buffer[len++] = 0x0a; // BBS Msgs stored with crlf - - if ((conn->TempMsg->length + len) > conn->MailBufferSize) - { - conn->MailBufferSize += 10000; - conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); - - if (conn->MailBuffer == NULL) - { - BBSputs(conn, "*** Failed to extend Message Buffer\r"); - conn->CloseAfterFlush = 20; // 2 Secs - - return; - } - } - - memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); - - conn->TempMsg->length += len; - - return; - } - if (Buffer[0] == ';') // WL2K Comment - { - if (memcmp(Buffer, ";FW:", 4) == 0) - { - // Paclink User Select (poll for list) - - char * ptr1,* ptr2, * ptr3; - int index=0; - - // Convert string to Multistring - - Buffer[len-1] = 0; - - conn->PacLinkCalls = zalloc(len*3); - - ptr1 = &Buffer[5]; - ptr2 = (char *)conn->PacLinkCalls; - ptr2 += (len * 2); - strcpy(ptr2, ptr1); - - while (ptr2) - { - ptr3 = strlop(ptr2, ' '); - - if (strlen(ptr2)) - conn->PacLinkCalls[index++] = ptr2; - - ptr2 = ptr3; - } - - return; - } - - if (memcmp(Buffer, ";FR:", 4) == 0) - { - // New Message from TriMode - Just igonre till I know what to do with it - - return; - } - - // Ignore other ';' message - - return; - } - - - - if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID - { - // If a BBS, set BBS Flag - - if (user->flags & ( F_BBS | F_Temp_B2_BBS)) - { - if (user->ForwardingInfo) - { - if (user->ForwardingInfo->Forwarding && ((conn->BBSFlags & OUTWARDCONNECT) == 0)) - { - BBSputs(conn, "Already Connected\r"); - Flush(conn); - Sleep(500); - Disconnect(conn->BPQStream); - return; - } - } - - if (user->ForwardingInfo) - { - user->ForwardingInfo->Forwarding = TRUE; - user->ForwardingInfo->FwdTimer = 0; // So we dont send to immediately - } - } - - if (user->flags & ( F_BBS | F_PMS | F_Temp_B2_BBS)) - { - Parse_SID(conn, &Buffer[1], len-4); - - if (conn->BBSFlags & FBBForwarding) - { - conn->FBBIndex = 0; // ready for first block; - conn->FBBChecksum = 0; - memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); - } - else - FBBputs(conn, ">\r"); - - } - - return; - } - - Cmd = strtok_s(Buffer, seps, &Context); - - if (Cmd == NULL) - { - if (!CheckForTooManyErrors(conn)) - BBSputs(conn, "Invalid Command\r"); - - SendPrompt(conn, user); - return; - } - - Arg1 = strtok_s(NULL, seps, &Context); - CmdLen = (int)strlen(Cmd); - - // Check List first. If any other, save last listed to user record. - - if (_memicmp(Cmd, "L", 1) == 0 && _memicmp(Cmd, "LISTFILES", 3) != 0) - { - DoListCommand(conn, user, Cmd, Arg1, FALSE, Context); - SendPrompt(conn, user); - return; - } - - if (conn->lastmsg > user->lastmsg) - { - user->lastmsg = conn->lastmsg; - SaveUserDatabase(); - } - - if (_stricmp(Cmd, "SHOWRMSPOLL") == 0) - { - DoShowRMSCmd(conn, user, Arg1, Context); - return; - } - - if (_stricmp(Cmd, "AUTH") == 0) - { - DoAuthCmd(conn, user, Arg1, Context); - return; - } - - if (_memicmp(Cmd, "Abort", 1) == 0) - { - ClearQueue(conn); - conn->LinesSent = 0; - - nodeprintf(conn, AbortedMsg); - - if (conn->UserPointer->Temp->ListSuspended) - nodeprintf(conn, "bort, , = Continue..>"); - - SendPrompt(conn, user); - return; - } - if (_memicmp(Cmd, "Bye", CmdLen) == 0 || _stricmp(Cmd, "ELSE") == 0) - { - ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); - Flush(conn); - Sleep(1000); - - if (conn->BPQStream > 0) - Disconnect(conn->BPQStream); -#ifndef LINBPQ - else - CloseConsole(conn->BPQStream); -#endif - return; - } - - if (_memicmp(Cmd, "Node", 4) == 0) - { - ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); - Flush(conn); - Sleep(1000); - - if (conn->BPQStream > 0) - ReturntoNode(conn->BPQStream); -#ifndef LINBPQ - else - CloseConsole(conn->BPQStream); -#endif - return; - } - - if (_memicmp(Cmd, "IDLETIME", 4) == 0) - { - DoSetIdleTime(conn, user, Arg1, Context); - return; - } - - if (_stricmp(Cmd, "SETNEXTMESSAGENUMBER") == 0) - { - DoSetMsgNo(conn, user, Arg1, Context); - return; - } - - if (strlen(Cmd) < 12 && _memicmp(Cmd, "D", 1) == 0) - { - DoDeliveredCommand(conn, user, Cmd, Arg1, Context); - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "K", 1) == 0) - { - DoKillCommand(conn, user, Cmd, Arg1, Context); - SendPrompt(conn, user); - return; - } - - - if (_memicmp(Cmd, "LISTFILES", 3) == 0 || _memicmp(Cmd, "FILES", 5) == 0) - { - ListFiles(conn, user, Arg1); - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "READFILE", 4) == 0) - { - ReadBBSFile(conn, user, Arg1); - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "REROUTEMSGS", 7) == 0) - { - if (conn->sysop == 0) - nodeprintf(conn, "Reroute Messages needs SYSOP status\r"); - else - { - ReRouteMessages(); - nodeprintf(conn, "Ok\r"); - } - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "YAPP", 4) == 0) - { - YAPPSendFile(conn, user, Arg1); - return; - } - - if (_memicmp(Cmd, "UH", 2) == 0 && conn->sysop) - { - DoUnholdCommand(conn, user, Cmd, Arg1, Context); - SendPrompt(conn, user); - return; - } - - if (_stricmp(Cmd, "IMPORT") == 0) - { - DoImportCmd(conn, user, Arg1, Context); - return; - } - - if (_stricmp(Cmd, "EXPORT") == 0) - { - DoExportCmd(conn, user, Arg1, Context); - return; - } - - if (_memicmp(Cmd, "I", 1) == 0) - { - char * Save; - char * MsgBytes; - - if (Arg1) - { - // User WP lookup - - DoWPLookup(conn, user, Cmd[1], Arg1); - SendPrompt(conn, user); - return; - } - - - MsgBytes = Save = ReadInfoFile("info.txt"); - if (MsgBytes) - { - int Length; - - // Remove lf chars - - Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); - - QueueMsg(conn, MsgBytes, Length); - free(Save); - } - else - BBSputs(conn, "SYSOP has not created an INFO file\r"); - - - SendPrompt(conn, user); - return; - } - - - if (_memicmp(Cmd, "Name", CmdLen) == 0) - { - if (Arg1) - { - if (strlen(Arg1) > 17) - Arg1[17] = 0; - - strcpy(user->Name, Arg1); - UpdateWPWithUserInfo(user); - - } - - SendWelcomeMsg(conn->BPQStream, conn, user); - SaveUserDatabase(); - - return; - } - - if (_memicmp(Cmd, "OP", 2) == 0) - { - int Lines; - - // Paging Control. Param is number of lines per page - - if (Arg1) - { - Lines = atoi(Arg1); - - if (Lines) // Sanity Check - { - if (Lines < 10) - { - nodeprintf(conn,"Page Length %d is too short\r", Lines); - SendPrompt(conn, user); - return; - } - } - - user->PageLen = Lines; - conn->PageLen = Lines; - conn->Paging = (Lines > 0); - SaveUserDatabase(); - } - - nodeprintf(conn,"Page Length is %d\r", user->PageLen); - SendPrompt(conn, user); - - return; - } - - if (_memicmp(Cmd, "QTH", CmdLen) == 0) - { - if (Arg1) - { - // QTH may contain spaces, so put back together, and just split at cr - - Arg1[strlen(Arg1)] = ' '; - strtok_s(Arg1, "\r", &Context); - - if (strlen(Arg1) > 60) - Arg1[60] = 0; - - strcpy(user->Address, Arg1); - UpdateWPWithUserInfo(user); - - } - - nodeprintf(conn,"QTH is %s\r", user->Address); - SendPrompt(conn, user); - - SaveUserDatabase(); - - return; - } - - if (_memicmp(Cmd, "ZIP", CmdLen) == 0) - { - if (Arg1) - { - if (strlen(Arg1) > 8) - Arg1[8] = 0; - - strcpy(user->ZIP, _strupr(Arg1)); - UpdateWPWithUserInfo(user); - } - - nodeprintf(conn,"ZIP is %s\r", user->ZIP); - SendPrompt(conn, user); - - SaveUserDatabase(); - - return; - } - - if (_memicmp(Cmd, "CMSPASS", 7) == 0) - { - if (Arg1 == 0) - { - nodeprintf(conn,"Must specify a password\r"); - } - else - { - if (strlen(Arg1) > 15) - Arg1[15] = 0; - - strcpy(user->CMSPass, Arg1); - nodeprintf(conn,"CMS Password Set\r"); - SaveUserDatabase(); - } - - SendPrompt(conn, user); - - return; - } - - if (_memicmp(Cmd, "PASS", CmdLen) == 0) - { - if (Arg1 == 0) - { - nodeprintf(conn,"Must specify a password\r"); - } - else - { - if (strlen(Arg1) > 12) - Arg1[12] = 0; - - strcpy(user->pass, Arg1); - nodeprintf(conn,"BBS Password Set\r"); - SaveUserDatabase(); - } - - SendPrompt(conn, user); - - return; - } - - - if (_memicmp(Cmd, "R", 1) == 0) - { - DoReadCommand(conn, user, Cmd, Arg1, Context); - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "S", 1) == 0) - { - if (!DoSendCommand(conn, user, Cmd, Arg1, Context)) - SendPrompt(conn, user); - return; - } - - if ((_memicmp(Cmd, "Help", CmdLen) == 0) || (_memicmp(Cmd, "?", 1) == 0)) - { - char * Save; - char * MsgBytes = Save = ReadInfoFile("help.txt"); - - if (MsgBytes) - { - int Length; - - // Remove lf chars - - Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); - - QueueMsg(conn, MsgBytes, Length); - free(Save); - } - else - { - BBSputs(conn, "A - Abort Output\r"); - BBSputs(conn, "B - Logoff\r"); - BBSputs(conn, "CMSPASS Password - Set CMS Password\r"); - BBSputs(conn, "D - Flag NTS Message(s) as Delivered - D num\r"); - BBSputs(conn, "HOMEBBS - Display or get HomeBBS\r"); - BBSputs(conn, "INFO - Display information about this BBS\r"); - BBSputs(conn, "I CALL - Lookup CALL in WP Allows *CALL CALL* *CALL* wildcards\r"); - BBSputs(conn, "I@ PARAM - Lookup @BBS in WP\r"); - BBSputs(conn, "IZ PARAM - Lookup Zip Codes in WP\r"); - BBSputs(conn, "IH PARAM - Lookup HA elements in WP - eg USA EU etc\r"); - - BBSputs(conn, "K - Kill Message(s) - K num, KM (Kill my read messages)\r"); - BBSputs(conn, "L - List Message(s) - \r"); - BBSputs(conn, " L = List New, LR = List New (Oldest first)\r"); - BBSputs(conn, " LM = List Mine, L> Call, L< Call, L@ = List to, from or at\r"); - BBSputs(conn, " LL num = List msg num, L num-num = List Range\r"); - BBSputs(conn, " LN LY LH LK LF L$ LD = List Message with corresponding Status\r"); - BBSputs(conn, " LB LP LT = List Mesaage with corresponding Type\r"); - BBSputs(conn, " LC = List TO fields of all active bulletins\r"); - BBSputs(conn, " You can combine most selections eg LMP, LMN LB< G8BPQ\r"); - BBSputs(conn, "LISTFILES or FILES - List files available for download\r"); - - BBSputs(conn, "N Name - Set Name\r"); - BBSputs(conn, "NODE - Return to Node\r"); - BBSputs(conn, "OP n - Set Page Length (Output will pause every n lines)\r"); - BBSputs(conn, "PASS Password - Set BBS Password\r"); - BBSputs(conn, "POLLRMS - Manage Polling for messages from RMS \r"); - BBSputs(conn, "Q QTH - Set QTH\r"); - BBSputs(conn, "R - Read Message(s) - R num \r"); - BBSputs(conn, " RM (Read new messages to me), RMR (RM oldest first)\r"); - BBSputs(conn, "READ Name - Read File\r"); - - BBSputs(conn, "S - Send Message - S or SP Send Personal, SB Send Bull, ST Send NTS,\r"); - BBSputs(conn, " SR Num - Send Reply, SC Num - Send Copy\r"); - BBSputs(conn, "X - Toggle Expert Mode\r"); - BBSputs(conn, "YAPP - Download file from BBS using YAPP protocol\r"); - if (conn->sysop) - { - BBSputs(conn, "DOHOUSEKEEPING - Run Housekeeping process\r"); - BBSputs(conn, "EU - Edit User Flags - Type EU for Help\r"); - BBSputs(conn, "EXPORT - Export messages to file - Type EXPORT for Help\r"); - BBSputs(conn, "FWD - Control Forwarding - Type FWD for Help\r"); - BBSputs(conn, "IMPORT - Import messages from file - Type IMPORT for Help\r"); - BBSputs(conn, "REROUTEMSGS - Rerun message routing process\r"); - BBSputs(conn, "SETNEXTMESSAGENUMBER - Sets next message number\r"); - BBSputs(conn, "SHOWRMSPOLL - Displays your RMS polling list\r"); - BBSputs(conn, "UH - Unhold Message(s) - UH ALL or UH num num num...\r"); - } - } - - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "Ver", CmdLen) == 0) - { - nodeprintf(conn, "BBS Version %s\rNode Version %s\r", VersionStringWithBuild, GetVersionString()); - - SendPrompt(conn, user); - return; - } - - if (_memicmp(Cmd, "HOMEBBS", CmdLen) == 0) - { - if (Arg1) - { - if (strlen(Arg1) > 40) Arg1[40] = 0; - - strcpy(user->HomeBBS, _strupr(Arg1)); - UpdateWPWithUserInfo(user); - - if (!strchr(Arg1, '.')) - BBSputs(conn, "Please enter HA with HomeBBS eg g8bpq.gbr.eu - this will help message routing\r"); - } - - nodeprintf(conn,"HomeBBS is %s\r", user->HomeBBS); - SendPrompt(conn, user); - - SaveUserDatabase(); - - return; - } - - if ((_memicmp(Cmd, "EDITUSER", 5) == 0) || (_memicmp(Cmd, "EU", 2) == 0)) - { - DoEditUserCmd(conn, user, Arg1, Context); - return; - } - - if (_stricmp(Cmd, "POLLRMS") == 0) - { - DoPollRMSCmd(conn, user, Arg1, Context); - return; - } - - if (_stricmp(Cmd, "DOHOUSEKEEPING") == 0) - { - DoHousekeepingCmd(conn, user, Arg1, Context); - return; - } - - - if (_stricmp(Cmd, "FWD") == 0) - { - DoFwdCmd(conn, user, Arg1, Context); - return; - } - - if (_memicmp(Cmd, "X", 1) == 0) - { - user->flags ^= F_Expert; - - if (user->flags & F_Expert) - BBSputs(conn, "Expert Mode\r"); - else - BBSputs(conn, "Expert Mode off\r"); - - SaveUserDatabase(); - SendPrompt(conn, user); - return; - } - - if (conn->Flags == 0) - { - if (!CheckForTooManyErrors(conn)) - BBSputs(conn, "Invalid Command\r"); - - SendPrompt(conn, user); - } - - // Send if possible - - Flush(conn); -} - -VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...) -{ - // seems to be printf to a socket - - char buff[600]; - va_list(arglist); - - va_start(arglist, format); - vsprintf(buff, format, arglist); - - BBSputs(conn, buff); -} - -// Code to delete obsolete files from Mail folder - -#ifdef WIN32 - -int DeleteRedundantMessages() -{ - WIN32_FIND_DATA ffd; - - char szDir[MAX_PATH]; - char File[MAX_PATH]; - HANDLE hFind = INVALID_HANDLE_VALUE; - int Msgno; - - // Prepare string for use with FindFile functions. First, copy the - // string to a buffer, then append '\*' to the directory name. - - strcpy(szDir, MailDir); - strcat(szDir, "\\*.mes"); - - - - // Find the first file in the directory. - - hFind = FindFirstFile(szDir, &ffd); - - if (INVALID_HANDLE_VALUE == hFind) - { - return 0; - } - - do - { - if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - OutputDebugString(ffd.cFileName); - } - else - { - Msgno = atoi(&ffd.cFileName[2]); - - if (MsgnotoMsg[Msgno] == 0) - { - sprintf(File, "%s/%s%c", MailDir, ffd.cFileName, 0); - Debugprintf("Tidy Mail - Delete %s\n", File); - -// if (DeletetoRecycleBin) - DeletetoRecycle(File); -// else -// DeleteFile(File); - } - } - } - while (FindNextFile(hFind, &ffd) != 0); - - FindClose(hFind); - return 0; -} - -#else - -#include - -int MsgFilter(const struct dirent * dir) -{ - return (strstr(dir->d_name, ".mes") != 0); -} - -int DeleteRedundantMessages() -{ - struct dirent **namelist; - int n; - struct stat STAT; - int Msgno = 0, res; - char File[100]; - - n = scandir("Mail", &namelist, MsgFilter, alphasort); - - if (n < 0) - perror("scandir"); - else - { - while(n--) - { - if (stat(namelist[n]->d_name, &STAT) == 0); - { - Msgno = atoi(&namelist[n]->d_name[2]); - - if (MsgnotoMsg[Msgno] == 0) - { - sprintf(File, "Mail/%s", namelist[n]->d_name); - printf("Deleting %s\n", File); - unlink(File); - } - } - free(namelist[n]); - } - free(namelist); - } - return 0; -} -#endif - -VOID TidyWelcomeMsg(char ** pPrompt) -{ - // Make sure Welcome Message doesn't ends with > - - char * Prompt = *pPrompt; - - int i = (int)strlen(Prompt) - 1; - - *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it - - Prompt = *pPrompt; - - while (Prompt[i] == 10 || Prompt[i] == 13) - { - Prompt[i--] = 0; - } - - while (i >= 0 && Prompt[i] == '>') - Prompt[i--] = 0; - - strcat(Prompt, "\r\n"); -} - -VOID TidyPrompt(char ** pPrompt) -{ - // Make sure prompt ends > CR LF - - char * Prompt = *pPrompt; - - int i = (int)strlen(Prompt) - 1; - - *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it - - Prompt = *pPrompt; - - while (Prompt[i] == 10 || Prompt[i] == 13) - { - Prompt[i--] = 0; - } - - if (Prompt[i] != '>') - strcat(Prompt, ">"); - - strcat(Prompt, "\r\n"); -} - -VOID TidyPrompts() -{ - TidyPrompt(&Prompt); - TidyPrompt(&NewPrompt); - TidyPrompt(&ExpertPrompt); -} - -BOOL SendARQMail(CIRCUIT * conn) -{ - conn->NextMessagetoForward = FirstMessageIndextoForward; - - // Send Message. There is no mechanism for reverse forwarding - - if (FindMessagestoForward(conn)) - { - struct MsgInfo * Msg; - char MsgHddr[512]; - int HddrLen; - char TimeString[64]; - char * WholeMessage; - - char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); - int MsgLen; - - if (MsgBytes == 0) - { - MsgBytes = _strdup("Message file not found\r"); - conn->FwdMsg->length = (int)strlen(MsgBytes); - } - - Msg = conn->FwdMsg; - WholeMessage = malloc(Msg->length + 512); - - FormatTime(TimeString, (time_t)Msg->datecreated); - -/* -ARQ:FILE::flarqmail-1.eml -ARQ:EMAIL:: -ARQ:SIZE::96 -ARQ::STX -//FLARQ COMPOSER -Date: 16/01/2014 22:26:06 -To: g8bpq -From: -Subject: test message - -Hello -Hello - -ARQ::ETX -*/ - Logprintf(LOG_BBS, conn, '>', "ARQ Send Msg %d From %s To %s", Msg->number, Msg->from, Msg->to); - - HddrLen = sprintf(MsgHddr, "Date: %s\nTo: %s\nFrom: %s\nSubject %s\n\n", - TimeString, Msg->to, Msg->from, Msg->title); - - MsgLen = sprintf(WholeMessage, "ARQ:FILE::Msg%s_%d\nARQ:EMAIL::\nARQ:SIZE::%d\nARQ::STX\n%s%s\nARQ::ETX\n", - BBSName, Msg->number, (int)(HddrLen + strlen(MsgBytes)), MsgHddr, MsgBytes); - - WholeMessage[MsgLen] = 0; - QueueMsg(conn,WholeMessage, MsgLen); - - free(WholeMessage); - free(MsgBytes); - - // FLARQ doesn't ACK the message, so set flag to look for all acked - - conn->BBSFlags |= ARQMAILACK; - conn->ARQClearCount = 10; // To make sure clear isn't reported too soon - - return TRUE; - } - - // Nothing to send - close - - Logprintf(LOG_BBS, conn, '>', "ARQ Send - Nothing to Send - Closing"); - - conn->CloseAfterFlush = 20; - return FALSE; -} - -char *stristr (char *ch1, char *ch2) -{ - char *chN1, *chN2; - char *chNdx; - char *chRet = NULL; - - chN1 = _strdup (ch1); - chN2 = _strdup (ch2); - if (chN1 && chN2) - { - chNdx = chN1; - while (*chNdx) - { - *chNdx = (char) tolower (*chNdx); - chNdx ++; - } - chNdx = chN2; - while (*chNdx) - { - *chNdx = (char) tolower (*chNdx); - chNdx ++; - } - - chNdx = strstr (chN1, chN2); - if (chNdx) - chRet = ch1 + (chNdx - chN1); - } - free (chN1); - free (chN2); - return chRet; -} - -#ifdef WIN32 - -void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) -{ - - WIN32_FIND_DATA ffd; - - char szDir[MAX_PATH]; - HANDLE hFind = INVALID_HANDLE_VALUE; - - // Prepare string for use with FindFile functions. First, copy the - // string to a buffer, then append '\*' to the directory name. - - strcpy(szDir, GetBPQDirectory()); - strcat(szDir, "\\BPQMailChat\\Files\\*.*"); - - // Find the first file in the directory. - - hFind = FindFirstFile(szDir, &ffd); - - if (INVALID_HANDLE_VALUE == hFind) - { - nodeprintf(conn, "No Files\r"); - return; - } - - // List all the files in the directory with some info about them. - - do - { - if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - {} - else - { - if (filename == NULL || stristr(ffd.cFileName, filename)) - nodeprintf(conn, "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); - } - } - while (FindNextFile(hFind, &ffd) != 0); - - FindClose(hFind); -} - -#else - -#include - -void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) -{ - struct dirent **namelist; - int n, i; - struct stat STAT; - time_t now = time(NULL); - int Age = 0, res; - char FN[256]; - - n = scandir("Files", &namelist, NULL, alphasort); - - if (n < 0) - perror("scandir"); - else - { - for (i = 0; i < n; i++) - { - sprintf(FN, "Files/%s", namelist[i]->d_name); - - if (filename == NULL || stristr(namelist[i]->d_name, filename)) - if (FN[6] != '.' && stat(FN, &STAT) == 0) - nodeprintf(conn, "%s %d\r", namelist[i]->d_name, STAT.st_size); - - free(namelist[i]); - } - free(namelist); - } - return; -} -#endif - -void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) -{ - char * MsgBytes; - - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - struct stat STAT; - - if (filename == NULL) - { - nodeprintf(conn, "Missing Filename\r"); - return; - } - - if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) - { - nodeprintf(conn, "Invalid filename\r"); - return; - } - - if (BaseDir[0]) - sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); - else - sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); - - if (stat(MsgFile, &STAT) != -1) - { - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile) - { - int Length; - - MsgBytes=malloc(FileSize+1); - fread(MsgBytes, 1, FileSize, hFile); - fclose(hFile); - - MsgBytes[FileSize]=0; - - // Remove lf chars - - Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); - - QueueMsg(conn, MsgBytes, Length); - free(MsgBytes); - - nodeprintf(conn, "\r\r[End of File %s]\r", filename); - return; - } - } - - nodeprintf(conn, "File %s not found\r", filename); -} - -VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) -{ - struct TempUserInfo * Temp = user->Temp; - - Buffer[len] = 0; - - // Command entered during listing pause. May be A R or C (or ) - - if (Buffer[0] == 'A' || Buffer[0] == 'a') - { - // Abort - - Temp->ListActive = Temp->ListSuspended = FALSE; - SendPrompt(conn, user); - return; - } - - if (_memicmp(Buffer, "R ", 2) == 0) - { - // Read Message(es) - - int msgno; - char * ptr; - char * Context; - - ptr = strtok_s(&Buffer[2], " ", &Context); - - while (ptr) - { - msgno = atoi(ptr); - ReadMessage(conn, user, msgno); - - ptr = strtok_s(NULL, " ", &Context); - } - - nodeprintf(conn, "bort, , = Continue..>"); - return; - } - - if (Buffer[0] == 'C' || Buffer[0] == 'c' || Buffer[0] == '\r' ) - { - // Resume Listing from where we left off - - DoListCommand(conn, user, Temp->LastListCommand, Temp->LastListParams, TRUE, ""); - SendPrompt(conn, user); - return; - } - - nodeprintf(conn, "bort, , = Continue..>"); - -} -/* -CreateMessageWithAttachments() -{ - int i; - char * ptr, * ptr2, * ptr3, * ptr4; - char Boundary[1000]; - BOOL Multipart = FALSE; - BOOL ALT = FALSE; - int Partlen; - char * Save; - BOOL Base64 = FALSE; - BOOL QuotedP = FALSE; - - char FileName[100][250] = {""}; - int FileLen[100]; - char * FileBody[100]; - char * MallocSave[100]; - UCHAR * NewMsg; - - int Files = 0; - - ptr = Msg; - - if ((sockptr->MailSize + 2000) > sockptr->MailBufferSize) - { - sockptr->MailBufferSize += 2000; - sockptr->MailBuffer = realloc(sockptr->MailBuffer, sockptr->MailBufferSize); - - if (sockptr->MailBuffer == NULL) - { - CriticalErrorHandler("Failed to extend Message Buffer"); - shutdown(sockptr->socket, 0); - return FALSE; - } - } - - - NewMsg = sockptr->MailBuffer + 1000; - - NewMsg += sprintf(NewMsg, "Body: %d\r\n", FileLen[0]); - - for (i = 1; i < Files; i++) - { - NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[i], FileName[i]); - } - - NewMsg += sprintf(NewMsg, "\r\n"); - - for (i = 0; i < Files; i++) - { - memcpy(NewMsg, FileBody[i], FileLen[i]); - NewMsg += FileLen[i]; - free(MallocSave[i]); - NewMsg += sprintf(NewMsg, "\r\n"); - } - - *MsgLen = NewMsg - (sockptr->MailBuffer + 1000); - *Body = sockptr->MailBuffer + 1000; - - return TRUE; // B2 Message -} - -*/ -VOID CreateUserReport() -{ - struct UserInfo * User; - int i; - char Line[200]; - int len; - char File[MAX_PATH]; - FILE * hFile; - - sprintf(File, "%s/UserList.csv", BaseDir); - - hFile = fopen(File, "wb"); - - if (hFile == NULL) - { - Debugprintf("Failed to create UserList.csv"); - return; - } - - for (i=1; i <= NumberofUsers; i++) - { - User = UserRecPtr[i]; - - len = sprintf(Line, "%s,%d,%s,%x,%s,\"%s\",%x,%s,%s,%s\r\n", - User->Call, - User->lastmsg, - FormatDateAndTime((time_t)User->TimeLastConnected, FALSE), - User->flags, - User->Name, - User->Address, - User->RMSSSIDBits, - User->HomeBBS, - User->QRA, - User->ZIP -// struct MsgStats Total; -// struct MsgStats Last; - ); - fwrite(Line, 1, len, hFile); - } - - fclose(hFile); -} - -BOOL ProcessYAPPMessage(CIRCUIT * conn) -{ - int Len = conn->InputLen; - UCHAR * Msg = conn->InputBuffer; - int pktLen = Msg[1]; - char Reply[2] = {ACK}; - int NameLen, SizeLen, OptLen; - char * ptr; - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - char Mess[255]; - int len; - char * FN = &Msg[2]; - - switch (Msg[0]) - { - case ENQ: // YAPP Send_Init - - // Shouldn't occur in session. Reset state - - Mess[0] = ACK; - Mess[1] = 1; - QueueMsg(conn, Mess, 2); - Flush(conn); - conn->InputLen = 0; - if (conn->MailBuffer) - { - free(conn->MailBuffer); - conn->MailBufferSize=0; - conn->MailBuffer=0; - } - return TRUE; - - case SOH: - - // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) - - // YAPPC has date/time in dos format - - if (Len < Msg[1] + 1) - return 0; - - NameLen = (int)strlen(FN); - strcpy(conn->ARQFilename, FN); - ptr = &Msg[3 + NameLen]; - SizeLen = (int)strlen(ptr); - FileSize = atoi(ptr); - - // Check file name for unsafe characters (.. / \) - - if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) - { - Mess[0] = NAK; - Mess[1] = 0; - QueueMsg(conn, Mess, 2); - Flush(conn); - len = sprintf_s(Mess, sizeof(Mess), "YAPP File Name %s invalid\r", FN); - QueueMsg(conn, Mess, len); - SendPrompt(conn, conn->UserPointer); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - - conn->InputLen = 0; - conn->InputMode = 0; - - return FALSE; - } - - OptLen = pktLen - (NameLen + SizeLen + 2); - - conn->YAPPDate = 0; - - if (OptLen >= 8) // We have a Date/Time for YAPPC - { - ptr = ptr + SizeLen + 1; - conn->YAPPDate = strtol(ptr, NULL, 16); - } - - // Check Size - - if (FileSize > MaxRXSize) - { - Mess[0] = NAK; - Mess[1] = sprintf(&Mess[2], "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); - QueueMsg(conn, Mess, Mess[1] + 2); - - Flush(conn); - - len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); - QueueMsg(conn, Mess, len); - SendPrompt(conn, conn->UserPointer); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - - conn->InputLen = 0; - conn->InputMode = 0; - - return FALSE; - } - - // Make sure file does not exist - - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); - - hFile = fopen(MsgFile, "rb"); - - if (hFile) - { - Mess[0] = NAK; - Mess[1] = sprintf(&Mess[2], "YAPP File %s already exists\r", conn->ARQFilename);; - QueueMsg(conn, Mess, Mess[1] + 2); - - Flush(conn); - - len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s already exists\r", conn->ARQFilename); - QueueMsg(conn, Mess, len); - SendPrompt(conn, conn->UserPointer); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - fclose(hFile); - - conn->InputLen = 0; - conn->InputMode = 0; - - return FALSE; - } - - - conn->MailBufferSize = FileSize; - conn->MailBuffer=malloc(FileSize); - conn->YAPPLen = 0; - - if (conn->YAPPDate) // If present use YAPPC - Reply[1] = ACK; //Receive_TPK - else - Reply[1] = 2; //Rcv_File - - QueueMsg(conn, Reply, 2); - - len = sprintf_s(Mess, sizeof(Mess), "YAPP upload to %s started", conn->ARQFilename); - WriteLogLine(conn, '!', Mess, len, LOG_BBS); - - conn->InputLen = 0; - return FALSE; - - case STX: - - // Data Packet - - // Check we have it all - - if (conn->YAPPDate) // If present use YAPPC so have checksum - { - if (pktLen > (Len - 3)) // -3 for header and checksum - return 0; // Wait for rest - } - else - { - if (pktLen > (Len - 2)) // -2 for header - return 0; // Wait for rest - } - - // Save data and remove from buffer - - // if YAPPC check checksum - - if (conn->YAPPDate) - { - UCHAR Sum = 0; - int i; - UCHAR * uptr = &Msg[2]; - - i = pktLen; - - while(i--) - Sum += *(uptr++); - - if (Sum != *uptr) - { - // Checksum Error - - Mess[0] = CAN; - Mess[1] = 0; - QueueMsg(conn, Mess, 2); - Flush(conn); - len = sprintf_s(Mess, sizeof(Mess), "YAPPC Checksum Error\r"); - QueueMsg(conn, Mess, len); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - conn->InputLen = 0; - conn->InputMode = 0; - return TRUE; - } - } - - if ((conn->YAPPLen) + pktLen > conn->MailBufferSize) - { - // Too Big ?? - - Mess[0] = CAN; - Mess[1] = 0; - QueueMsg(conn, Mess, 2); - Flush(conn); - len = sprintf_s(Mess, sizeof(Mess), "YAPP Too much data received\r"); - QueueMsg(conn, Mess, len); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - conn->InputLen = 0; - conn->InputMode = 0; - return TRUE; - } - - - memcpy(&conn->MailBuffer[conn->YAPPLen], &Msg[2], pktLen); - conn->YAPPLen += pktLen; - - if (conn->YAPPDate) - ++pktLen; // Add Checksum - - conn->InputLen -= (pktLen + 2); - memmove(conn->InputBuffer, &conn->InputBuffer[pktLen + 2], conn->InputLen); - - return TRUE; - - case ETX: - - // End Data - - - - if (conn->YAPPLen == conn->MailBufferSize) - { - // All received - - int ret; - DWORD Written = 0; - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); - -#ifdef WIN32 - hFile = CreateFile(MsgFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) - { - ret = WriteFile(hFile, conn->MailBuffer, conn->YAPPLen, &Written, NULL); - - if (conn->YAPPDate) - { - FILETIME FileTime; - struct tm TM; - struct timeval times[2]; - time_t TT; -/* - The MS-DOS date. The date is a packed value with the following format. - - cant use DosDateTimeToFileTime on Linux - - Bits Description - 0-4 Day of the month (1–31) - 5-8 Month (1 = January, 2 = February, and so on) - 9-15 Year offset from 1980 (add 1980 to get actual year) - wFatTime - The MS-DOS time. The time is a packed value with the following format. - Bits Description - 0-4 Second divided by 2 - 5-10 Minute (0–59) - 11-15 Hour (0–23 on a 24-hour clock) -*/ - memset(&TM, 0, sizeof(TM)); - - TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; - TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); - TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); - - TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); - TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; - TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; - - Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); - - TT = mktime(&TM); - times[0].tv_sec = times[1].tv_sec = - times[0].tv_usec = times[1].tv_usec = 0; - - DosDateTimeToFileTime((WORD)(conn->YAPPDate >> 16), (WORD)conn->YAPPDate & 0xFFFF, &FileTime); - ret = SetFileTime(hFile, &FileTime, &FileTime, &FileTime); - ret = GetLastError(); - - } - CloseHandle(hFile); - } -#else - - hFile = fopen(MsgFile, "wb"); - if (hFile) - { - Written = fwrite(conn->MailBuffer, 1, conn->YAPPLen, hFile); - fclose(hFile); - - if (conn->YAPPDate) - { - struct tm TM; - struct timeval times[2]; -/* - The MS-DOS date. The date is a packed value with the following format. - - cant use DosDateTimeToFileTime on Linux - - Bits Description - 0-4 Day of the month (1–31) - 5-8 Month (1 = January, 2 = February, and so on) - 9-15 Year offset from 1980 (add 1980 to get actual year) - wFatTime - The MS-DOS time. The time is a packed value with the following format. - Bits Description - 0-4 Second divided by 2 - 5-10 Minute (0–59) - 11-15 Hour (0–23 on a 24-hour clock) -*/ - memset(&TM, 0, sizeof(TM)); - - TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; - TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); - TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); - - TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); - TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; - TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; - - Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); - - times[0].tv_sec = times[1].tv_sec = mktime(&TM); - times[0].tv_usec = times[1].tv_usec = 0; - } - } -#endif - - free(conn->MailBuffer); - conn->MailBufferSize=0; - conn->MailBuffer=0; - - if (Written != conn->YAPPLen) - { - Mess[0] = CAN; - Mess[1] = 0; - QueueMsg(conn, Mess, 2); - Flush(conn); - len = sprintf_s(Mess, sizeof(Mess), "Failed to save YAPP File\r"); - QueueMsg(conn, Mess, len); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - conn->InputLen = 0; - conn->InputMode = 0; - } - } - - Reply[1] = 3; //Ack_EOF - QueueMsg(conn, Reply, 2); - Flush(conn); - conn->InputLen = 0; - - return TRUE; - - case EOT: - - // End Session - - Reply[1] = 4; // Ack_EOT - QueueMsg(conn, Reply, 2); - Flush(conn); - conn->InputLen = 0; - conn->InputMode = 0; - - len = sprintf_s(Mess, sizeof(Mess), "YAPP file %s received\r", conn->ARQFilename); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - QueueMsg(conn, Mess, len); - SendPrompt(conn, conn->UserPointer); - - return TRUE; - - case CAN: - - // Abort - - Mess[0] = ACK; - Mess[1] = 5; // CAN Ack - QueueMsg(conn, Mess, 2); - Flush(conn); - - if (conn->MailBuffer) - { - free(conn->MailBuffer); - conn->MailBufferSize=0; - conn->MailBuffer=0; - } - - // There may be a reason after the CAN - - len = Msg[1]; - - if (len) - { - char * errormsg = &Msg[2]; - errormsg[len] = 0; - nodeprintf(conn, "File Rejected - %s\r", errormsg); - } - else - - nodeprintf(conn, "File Rejected\r"); - - - len = sprintf_s(Mess, sizeof(Mess), "YAPP Transfer cancelled by Terminal\r"); - WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); - - conn->InputLen = 0; - conn->InputMode = 0; - conn->BBSFlags &= ~YAPPTX; - - return FALSE; - - case ACK: - - switch (Msg[1]) - { - case 1: // Rcv_Rdy - - // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) - - len = (int)strlen(conn->ARQFilename) + 3; - - strcpy(&Mess[2], conn->ARQFilename); - len += sprintf(&Mess[len], "%d", conn->MailBufferSize); - len++; // include null - Mess[0] = SOH; - Mess[1] = len - 2; - - QueueMsg(conn, Mess, len); - Flush(conn); - conn->InputLen = 0; - - return FALSE; - - case 2: - - // Start sending message - - YAPPSendData(conn); - conn->InputLen = 0; - return FALSE; - - case 3: - - // ACK EOF - Send EOT - - - Mess[0] = EOT; - Mess[1] = 1; - QueueMsg(conn, Mess, 2); - Flush(conn); - - conn->InputLen = 0; - return FALSE; - - case 4: - - // ACK EOT - - conn->InputMode = 0; - conn->BBSFlags &= ~YAPPTX; - - conn->InputLen = 0; - return FALSE; - - default: - conn->InputLen = 0; - return FALSE; - - - - } - - case NAK: - - // Either Reject or Restart - - // RE Resume NAK len R NULL (File size in ASCII) NULL - - if (conn->InputLen > 2 && Msg[2] == 'R' && Msg[3] == 0) - { - int posn = atoi(&Msg[4]); - - conn->YAPPLen += posn; - conn->MailBufferSize -= posn; - - YAPPSendData(conn); - conn->InputLen = 0; - return FALSE; - - } - - // There may be a reason after the ack - - len = Msg[1]; - - if (len) - { - char * errormsg = &Msg[2]; - errormsg[len] = 0; - nodeprintf(conn, "File Rejected - %s\r", errormsg); - } - else - - nodeprintf(conn, "File Rejected\r"); - - conn->InputMode = 0; - conn->BBSFlags &= ~YAPPTX; - conn->InputLen = 0; - SendPrompt(conn, conn->UserPointer); - return FALSE; - } - - nodeprintf(conn, "Unexpected message during YAPP Transfer. Transfer canncelled\r"); - - conn->InputMode = 0; - conn->BBSFlags &= ~YAPPTX; - conn->InputLen = 0; - SendPrompt(conn, conn->UserPointer); - - return FALSE; - -} - -void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) -{ - int FileSize; - char MsgFile[MAX_PATH]; - FILE * hFile; - struct stat STAT; - - if (filename == NULL) - { - nodeprintf(conn, "Filename missing\r"); - SendPrompt(conn, user); - return; - } - - if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) - { - nodeprintf(conn, "Invalid filename\r"); - SendPrompt(conn, user); - return; - } - - if (BaseDir[0]) - sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); - else - sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); - - if (stat(MsgFile, &STAT) != -1) - { - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile) - { - char Mess[255]; - strcpy(conn->ARQFilename, filename); - conn->MailBuffer = malloc(FileSize); - conn->MailBufferSize = FileSize; - conn->YAPPLen = 0; - fread(conn->MailBuffer, 1, FileSize, hFile); - fclose(hFile); - - Mess[0] = ENQ; - Mess[1] = 1; - - QueueMsg(conn, Mess, 2); - Flush(conn); - - conn->InputMode = 'Y'; - - return; - } - } - - nodeprintf(conn, "File %s not found\r", filename); - SendPrompt(conn, user); -} - -void YAPPSendData(ConnectionInfo * conn) -{ - char Mess[258]; - - conn->BBSFlags |= YAPPTX; - - while (TXCount(conn->BPQStream) < 15) - { - int Left = conn->MailBufferSize; - - if (Left == 0) - { - // Finished - send End Data - - Mess[0] = ETX; - Mess[1] = 1; - - QueueMsg(conn, Mess, 2); - Flush(conn); - - conn->BBSFlags &= ~YAPPTX; - break; - } - - if (Left > conn->paclen - 2) // 2 byte header - Left = conn->paclen -2; - - memcpy(&Mess[2], &conn->MailBuffer[conn->YAPPLen], Left); - Mess[0] = STX; - Mess[1] = Left; - - QueueMsg(conn, Mess, Left + 2); - Flush(conn); - - conn->YAPPLen += Left; - conn->MailBufferSize -= Left; - } -} - -char * AddUser(char * Call, char * password, BOOL BBSFlag) -{ - struct UserInfo * USER; - - strlop(Call, '-'); - - if (strlen(Call) > 6) - Call[6] = 0; - - _strupr(Call); - - if (Call[0] == 0 || LookupCall(Call)) - { - return("User already exists\r\n"); - } - - USER = AllocateUserRecord(Call); - USER->Temp = zalloc(sizeof (struct TempUserInfo)); - - if (strlen(password) > 12) - password[12] = 0; - - strcpy(USER->pass, password); - - if (BBSFlag) - { - if(SetupNewBBS(USER)) - USER->flags |= F_BBS; - else - printf("Cannot set user to be a BBS - you already have 160 BBS's defined\r\n"); - } - - SaveUserDatabase(); - UpdateWPWithUserInfo(USER); - - return("User added\r\n"); -} - -// Server Support Code - -// For the moment only internal REQDIR and REQFIL. - -// May add WPSERV and user implemented servers -/* -F6FBB BBS > - SP REQDIR @ F6ABJ.FRA.EU - Title of message : - YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU - Text of message : - /EX - - F6FBB BBS > - SP REQFIL @ F6ABJ.FRA.EU - Title of message : - DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU - Text of message : - /EX - - Note Text not used. - -*/ - -VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To); - -BOOL ProcessReqDir(struct MsgInfo * Msg) -{ - char * Buffer; - int Len = 0; - char * ptr; - - // Parse title - gives directory and return address - - // YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU - - // At the moment we don't allow subdirectories but no harm handling here - - char Pattern[64]; - char * Address; - char * filename = NULL; // ?? Pattern Match ?? - -#ifdef WIN32 - - WIN32_FIND_DATA ffd; - - char szDir[MAX_PATH]; - HANDLE hFind = INVALID_HANDLE_VALUE; - -#else - - #include - - struct dirent **namelist; - int n, i; - struct stat STAT; - int res; - char FN[256]; - -#endif - - strcpy(Pattern, Msg->title); - - ptr = strchr(Pattern, '@'); - - if (ptr == NULL) - - // if we don't have return address no point - // but could we default to sender?? - - return FALSE; - - *ptr++ = 0; // Terminate Path - - strlop(Pattern, ' '); - - while (*ptr == ' ') - ptr++; // accept with or without spaces round @ - - Address = ptr; - - ptr = Buffer = malloc(MaxTXSize); - -#ifdef WIN32 - - // Prepare string for use with FindFile functions. First, copy the - // string to a buffer, then append '\*' to the directory name. - - strcpy(szDir, GetBPQDirectory()); - strcat(szDir, "\\BPQMailChat\\Files\\"); - strcat(szDir, Pattern); - - // Find the first file in the directory. - - hFind = FindFirstFile(szDir, &ffd); - - if (INVALID_HANDLE_VALUE == hFind) - { - Len = sprintf(Buffer, "No Files\r"); - } - else - { - do - { - if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - {} - else - { - if (filename == NULL || stristr(ffd.cFileName, filename)) - Len += sprintf(&Buffer[Len], "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); - } - } - while (FindNextFile(hFind, &ffd) != 0); - - FindClose(hFind); - } - -#else - - n = scandir("Files", &namelist, NULL, alphasort); - - if (n < 0) - perror("scandir"); - else - { - for (i = 0; i < n; i++) - { - sprintf(FN, "Files/%s", namelist[i]->d_name); - - if (filename == NULL || stristr(namelist[i]->d_name, filename)) - if (FN[6] != '.' && stat(FN, &STAT) == 0) - Len += sprintf(&Buffer[Len], "%s %d\r", namelist[i]->d_name, STAT.st_size); - - free(namelist[i]); - } - free(namelist); - } - -#endif - - // Build Message - - SendServerReply("REQDIR Reply", Buffer, Len, _strupr(Address)); - return TRUE; -} - -/* - ' Augment Message ID with the Message Pickup Station we're directing this message to. - ' - Dim strAugmentedMessageID As String - If GetMidRMS(MessageId) <> "" Then - ' The MPS RMS is already set on the message ID - strAugmentedMessageID = MessageId - strMPS = GetMidRMS(MessageId) - ' "@R" at the end of the MID means route message only via radio - If GetMidForwarding(MessageId) = "" And (blnRadioOnly Or UploadThroughInternet()) Then - strAugmentedMessageID &= "@" & strHFOnlyFlag - End If - ElseIf strMPS <> "" Then - ' Add MPS to the message ID - strAugmentedMessageID = MessageId & "@" & strMPS - ' "@R" at the end of the MID means route message only via radio - If blnRadioOnly Or UploadThroughInternet() Then - strAugmentedMessageID &= "@" & strHFOnlyFlag - End If - Else - strAugmentedMessageID = MessageId - End If - -*/ - -void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) -{ - Buffer[len] = 0; - - if (conn->Flags & GETTINGSYNCMESSAGE) - { - // Data - - if ((conn->TempMsg->length + len) > conn->MailBufferSize) - { - conn->MailBufferSize += 10000; - conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); - - if (conn->MailBuffer == NULL) - { - BBSputs(conn, "*** Failed to extend Message Buffer\r"); - conn->CloseAfterFlush = 20; // 2 Secs - - return; - } - } - - memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); - - conn->TempMsg->length += len; - - if (conn->TempMsg->length >= conn->SyncCompressedLen) - { - // Complete - decompress it - - conn->BBSFlags |= FBBCompressed; - Decode(conn, 1); - - conn->Flags &= !GETTINGSYNCMESSAGE; - - BBSputs(conn, "OK\r"); - return; - } - return; - } - - if (conn->Flags & PROPOSINGSYNCMSG) - { - // Waiting for response to TR AddMessage - - if (strcmp(Buffer, "OK\r") == 0) - { - char Msg[256]; - int n; - - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - // Send the message, it has already been built - - conn->Flags &= !PROPOSINGSYNCMSG; - conn->Flags |= SENDINGSYNCMSG; - - n = sprintf_s(Msg, sizeof(Msg), "Sending SYNC message %s", conn->FwdMsg->bid); - WriteLogLine(conn, '|',Msg, n, LOG_BBS); - - QueueMsg(conn, conn->SyncMessage, conn->SyncCompressedLen); - return; - } - - if (strcmp(Buffer, "NO\r") == 0) - { - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - // Message Rejected - ? duplicate - - if (conn->FwdMsg) - { - // Zap the entry - - clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); - set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); - conn->UserPointer->ForwardingInfo->MsgCount--; - - // Only mark as forwarded if sent to all BBSs that should have it - - if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) - { - conn->FwdMsg->status = 'F'; // Mark as forwarded - conn->FwdMsg->datechanged=time(NULL); - } - - conn->FwdMsg->Locked = 0; // Unlock - } - } - - BBSputs(conn, "BYE\r"); - conn->CloseAfterFlush = 20; // 2 Secs - conn->Flags &= !PROPOSINGSYNCMSG; - conn->BBSFlags &= ~SYNCMODE; - return; - } - - if (conn->Flags & SENDINGSYNCMSG) - { - if (strcmp(Buffer, "OK\r") == 0) - { - // Message Sent - - conn->Flags &= !SENDINGSYNCMSG; - free(conn->SyncMessage); - - if (conn->FwdMsg) - { - char Msg[256]; - int n; - - n = sprintf_s(Msg, sizeof(Msg), "SYNC message %s Sent", conn->FwdMsg->bid); - WriteLogLine(conn, '|',Msg, n, LOG_BBS); - - clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); - set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); - conn->UserPointer->ForwardingInfo->MsgCount--; - - // Only mark as forwarded if sent to all BBSs that should have it - - if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) - { - conn->FwdMsg->status = 'F'; // Mark as forwarded - conn->FwdMsg->datechanged=time(NULL); - } - - conn->FwdMsg->Locked = 0; // Unlock - } - - // drop through to send any more - } - else - { - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - conn->Flags &= !SENDINGSYNCMSG; - free(conn->SyncMessage); - - BBSputs(conn, "BYE\r"); - conn->CloseAfterFlush = 20; // 2 Secs - conn->BBSFlags &= ~SYNCMODE; - - return; - } - } - - if (strcmp(Buffer, "OK\r") == 0) - { - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - // Send Message(?s) to RMS Relay SYNC - -/* -OK ->TR AddMessage_V5JLSGH591JR 786 1219 522 True -BYE*/ - if (FindMessagestoForward(conn) && conn->FwdMsg) - { - struct MsgInfo * Msg = conn->FwdMsg; - char Buffer[128]; - char * Message; - - Message = FormatSYNCMessage(conn, Msg); - - // Need to compress it - - conn->SyncMessage = malloc(conn->SyncXMLLen + conn->SyncMsgLen + 4096); - - conn->SyncCompressedLen = Encode(Message, conn->SyncMessage, conn->SyncXMLLen + conn->SyncMsgLen, 0, 1); - - sprintf(Buffer, "TR AddMessage_%s %d %d %d True\r", // The True on end indicates compressed - Msg->bid, conn->SyncCompressedLen, conn->SyncXMLLen, conn->SyncMsgLen); - - free(Message); - - conn->Flags |= PROPOSINGSYNCMSG; - - BBSputs(conn, Buffer); - return; - } - - - BBSputs(conn, "BYE\r"); - conn->CloseAfterFlush = 20; // 2 Secs - conn->BBSFlags &= ~SYNCMODE; - return; - } - - if (memcmp(Buffer, "TR ", 2) == 0) - { - // Messages have TR_COMMAND_BID Compressed Len XML Len Bosy Len - - char * Command; - char * BIDptr; - - BIDRec * BID; - char *ptr1, *ptr2, *context; - - // TR AddMessage_1145_G8BPQ 727 1202 440 True - - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - - Command = strtok_s(&Buffer[3], "_", &context); - BIDptr = strtok_s(NULL, " ", &context); - ptr2 = strtok_s(NULL, " ", &context); - conn->SyncCompressedLen = atoi(ptr2); - ptr2 = strtok_s(NULL, " ", &context); - conn->SyncXMLLen = atoi(ptr2); - ptr2 = strtok_s(NULL, " ", &context); - conn->SyncMsgLen = atoi(ptr2); - ptr2 = strtok_s(NULL, " ", &context); - - // If addmessage need to check bid doesn't exist - - if (strcmp(Command, "AddMessage") == 0) - { - strlop(BIDptr, '@'); // sometimes has @CALL@R - if (strlen(BIDptr) > 12) - BIDptr[12] = 0; - - BID = LookupBID(BIDptr); - - if (BID) - { - BBSputs(conn, "Rejected - Duplicate BID\r"); - return; - } - } - - conn->TempMsg = zalloc(sizeof(struct MsgInfo)); - - conn->Flags |= GETTINGSYNCMESSAGE; - - BBSputs(conn, "OK\r"); - return; - } - - if (memcmp(Buffer, "BYE\r", 4) == 0) - { - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - conn->CloseAfterFlush = 20; // 2 Secs - conn->BBSFlags &= ~SYNCMODE; - return; - } - - if (memcmp(Buffer, "BBS\r", 4) == 0) - { - // Out of Sync - - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - conn->BBSFlags &= ~SYNCMODE; - return; - } - - WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); - WriteLogLine(conn, '<', "Unexpected SYNC Message", 23, LOG_BBS); - - BBSputs(conn, "BYE\r"); - conn->CloseAfterFlush = 20; // 2 Secs - conn->BBSFlags &= ~SYNCMODE; - return; -} -BOOL ProcessReqFile(struct MsgInfo * Msg) -{ - char FN[128]; - char * Buffer; - int Len = 0; - char * ptr; - struct stat STAT; - char MsgFile[MAX_PATH]; - FILE * hFile; - int FileSize; - char * MsgBytes; - - // Parse title - gives file and return address - - // DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU - - // At the moment we don't allow subdirectories but no harm handling here - - char * Address; - char * filename = NULL; // ?? Pattern Match ?? - - strcpy(FN, Msg->title); - - ptr = strchr(FN, '@'); - - if (ptr == NULL) - - // if we don't have return address no point - // but could we default to sender?? - - return FALSE; - - *ptr++ = 0; // Terminate Path - - strlop(FN, ' '); - - while (*ptr == ' ') - ptr++; // accept with or without spaces round @ - - Address = ptr; - - ptr = Buffer = malloc(MaxTXSize + 1); // Allow terminating Null - - // Build Message - - if (FN == NULL) - { - Len = sprintf(Buffer, "Missing Filename\r"); - } - else if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) - { - Len = sprintf(Buffer,"Invalid filename %s\r", FN); - } - else - { - if (BaseDir[0]) - sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, FN); - else - sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", FN); - - if (stat(MsgFile, &STAT) != -1) - { - FileSize = STAT.st_size; - - hFile = fopen(MsgFile, "rb"); - - if (hFile) - { - int Length; - - if (FileSize > MaxTXSize) - FileSize = MaxTXSize; // Truncate to max size - - MsgBytes=malloc(FileSize+1); - fread(MsgBytes, 1, FileSize, hFile); - fclose(hFile); - - MsgBytes[FileSize]=0; - - // Remove lf chars - - Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); - - Len = sprintf(Buffer, "%s", MsgBytes); - free(MsgBytes); - } - } - else - Len = sprintf(Buffer, "File %s not found\r", FN); - } - - SendServerReply("REQFIL Reply", Buffer, Len, _strupr(Address)); - return TRUE; -} - -BOOL CheckforMessagetoServer(struct MsgInfo * Msg) -{ - if (_stricmp(Msg->to, "REQDIR") == 0) - return ProcessReqDir(Msg); - - if (_stricmp(Msg->to, "REQFIL") == 0) - return ProcessReqFile(Msg); - - return FALSE; -} - -VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To) -{ - struct MsgInfo * Msg = AllocateMsgRecord(); - BIDRec * BIDRec; - char * Via; - char MsgFile[MAX_PATH]; - FILE * hFile; - size_t WriteLen=0; - - Msg->length = Length; - - GetSemaphore(&MsgNoSemaphore, 0); - Msg->number = ++LatestMsg; - MsgnotoMsg[Msg->number] = Msg; - - FreeSemaphore(&MsgNoSemaphore); - - strcpy(Msg->from, BBSName); - Via = strlop(To, '@'); - - if (Via) - strcpy(Msg->via, Via); - - strcpy(Msg->to, To); - strcpy(Msg->title, Title); - - Msg->type = 'P'; - Msg->status = 'N'; - Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); - - sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); - - BIDRec = AllocateBIDRecord(); - strcpy(BIDRec->BID, Msg->bid); - BIDRec->mode = Msg->type; - BIDRec->u.msgno = LOWORD(Msg->number); - BIDRec->u.timestamp = LOWORD(time(NULL)/86400); - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); - - hFile = fopen(MsgFile, "wb"); - - if (hFile) - { - WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); - fclose(hFile); - } - - MatchMessagetoBBSList(Msg, NULL); - free(MailBuffer); -} - -void SendRequestSync(CIRCUIT * conn) -{ - // Only need XML Header - - char * Buffer = malloc(4096); - int Len = 0; - - struct tm *tm; - char Date[32]; - char MsgTime[32]; - time_t Time = time(NULL); - - char * Encoded; - - tm = gmtime(&Time); - - sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - - sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); - - Len += sprintf(&Buffer[Len], "\r\n"); - - Len += sprintf(&Buffer[Len], "\r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " request_sync\r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", Date); - Len += sprintf(&Buffer[Len], " %s\r\n", BBSName); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " BBSName\r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", conn->SyncHost); - Len += sprintf(&Buffer[Len], " %d\r\n", conn->SyncPort); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], "\r\n"); - -/* - - - - request_sync - 20230205100652 - GI8BPQ - - - GI8BPQ - - 127.0.0.1 - 8780 - - - -*/ - - // Need to compress it - - conn->SyncXMLLen = Len; - conn->SyncMsgLen = 0; - - conn->SyncMessage = malloc(conn->SyncXMLLen + 4096); - - conn->SyncCompressedLen = Encode(Buffer, conn->SyncMessage, conn->SyncXMLLen, 0, 1); - - sprintf(Buffer, "TR RequestSync_%s_%d %d %d 0 True\r", // The True on end indicates compressed - 50, conn->SyncCompressedLen, conn->SyncXMLLen); - - free(Buffer); - - conn->Flags |= REQUESTINGSYNC; - - BBSputs(conn, Buffer); - return; -} - - -void ProcessSyncXML(CIRCUIT * conn, char * XML) -{ - // Process XML from RMS Relay Sync - - // All seem to start - - // - // - // - // - - char * Type = strstr(XML, ""); - - if (Type == NULL) - return; - - Type += strlen(""); - - if (memcmp(Type, "rms_location", 12) == 0) - { - return; - } - - - if (memcmp(Type, "request_sync", 12) == 0) - { - char * Call; - struct UserInfo * BBSREC; - - // This isn't requesting a poll, it is asking to be added as a sync partner - - Call = strstr(Type, ""); - - if (Call == NULL) - return; - - Call += 10; - strlop(Call, '<'); - BBSREC = FindBBS(Call); - - if (BBSREC == NULL) - return; - - if (BBSREC->ForwardingInfo->Forwarding == 0) - StartForwarding(BBSREC->BBSNumber, NULL); - - return; - } - - if (memcmp(Type, "remove_message", 14) == 0) - { - char * MID = strstr(Type, ""); - struct MsgInfo * Msg; - - if (MID == NULL) - return; - - MID += 11; - strlop(MID, '<'); - - strlop(MID, '@'); // sometimes has @CALL@R - if (strlen(MID) > 12) - MID[12] = 0; - - Msg = FindMessageByBID(MID); - - if (Msg == NULL) - return; - - Logprintf(LOG_BBS, conn, '|', "Killing Msg %d %s", Msg->number, Msg->bid); - - FlagAsKilled(Msg, TRUE); - return; - } - - if (memcmp(Type, "delivered", 9) == 0) - { - char * MID = strstr(Type, ""); - struct MsgInfo * Msg; - - if (MID == NULL) - return; - - MID += 11; - strlop(MID, '<'); - - strlop(MID, '@'); // sometimes has @CALL@R - if (strlen(MID) > 12) - MID[12] = 0; - - Msg = FindMessageByBID(MID); - - if (Msg == NULL) - return; - - Logprintf(LOG_BBS, conn, '|', "Message Msg %d %s Delivered", Msg->number, Msg->bid); - return; - } - - Debugprintf(Type); - return; - -/* - - - - request_sync - 20230205100652 - GI8BPQ - - - GI8BPQ - - 127.0.0.1 - 8780 - - - -} - - - - delivered - 20230205093113 - G8BPQ - - - 10845_GM8BPB - G8BPQ - G8BPQ - 3 - - - - Public Enum MessageDeliveryMethod - ' - ' Method used to deliver a message. None if the message hasn't been delivered. - ' - Unspecified = -1 - None = 0 - Telnet = 1 - CMS = 2 - Radio = 3 - Email = 4 -End Enum -*/ -} - -int ReformatSyncMessage(CIRCUIT * conn) -{ - // Message has been decompressed - reformat to look like a WLE message - - char * MsgBit; - char *ptr1, *ptr2; - int linelen; - char FullFrom[80]; - char FullTo[80]; - char BID[80]; - time_t Date; - char Mon[80]; - char Subject[80]; - int i = 0; - char * Boundary; - char * Input; - char * via = NULL; - char * NewMsg = conn->MailBuffer; - char * SaveMsg = NewMsg; - char DateString[80]; - struct tm * tm; - char Type[16] = "Private"; - char * part[100] = {""}; - char * partname[100]; - int partLen[100]; - char xml[4096]; - - // Message has an XML header then the message - - // The XML may have control info, so examine it. - - /* - Date: Mon, 25 Oct 2021 10:22:00 -0000 - From: GM8BPQ - Subject: Test - To: 2E1BGT - Message-ID: ALYJQJRXVQAO - X-Source: GM8BPQ - X-Relay: G8BPQ - MIME-Version: 1.0 - MIME-Version: 1.0 - Content-Type: multipart/mixed; boundary="boundaryBSoxlw==" - - --boundaryBSoxlw== - Content-Type: text/plain; charset="iso-8859-1" - Content-Transfer-Encoding: quoted-printable - - Hello Hello - - --boundaryBSoxlw==-- - */ - - // I think the best way is to reformat as if from Winlink Express, then pass - //through the normal B2 code. - -// WriteLogLine(conn, '<', conn->MailBuffer, conn->TempMsg->length, LOG_BBS); - - // display the message for testing - - conn->MailBuffer[conn->TempMsg->length] = 0; - -// OutputDebugString(conn->MailBuffer); - memcpy(xml, conn->MailBuffer, conn->SyncXMLLen); - xml[conn->SyncXMLLen] = 0; - - if (conn->SyncMsgLen == 0) - { - // No message, Just xml. Looks like a status report - - ProcessSyncXML(conn, xml); - return 0; - } - - MsgBit = &conn->MailBuffer[conn->SyncXMLLen]; - conn->TempMsg->length -= conn->SyncXMLLen; - - ptr1 = MsgBit; - -Loop: - - ptr2 = strchr(ptr1, '\r'); - - linelen = (int)(ptr2 - ptr1); - - if (_memicmp(ptr1, "From:", 5) == 0) - { - memcpy(FullFrom, &ptr1[6], linelen - 6); - FullFrom[linelen - 6] = 0; - } - - if (_memicmp(ptr1, "To:", 3) == 0) - { - memcpy(FullTo, &ptr1[4], linelen - 4); - FullTo[linelen - 4] = 0; - } - - else if (_memicmp(ptr1, "Subject:", 8) == 0) - { - memcpy(Subject, &ptr1[9], linelen - 9); - Subject[linelen - 9] = 0; - } - - else if (_memicmp(ptr1, "Message-ID", 10) == 0) - { - memcpy(BID, &ptr1[12], linelen - 12); - BID[linelen - 12] = 0; - } - - else if (_memicmp(ptr1, "Date:", 5) == 0) - { - struct tm rtime; - char seps[] = " ,\t\r"; - - memset(&rtime, 0, sizeof(struct tm)); - - // Date: Mon, 25 Oct 2021 10:22:00 -0000 - - sscanf(&ptr1[11], "%02d %s %04d %02d:%02d:%02d", - &rtime.tm_mday, &Mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); - - rtime.tm_year -= 1900; - - for (i = 0; i < 12; i++) - { - if (strcmp(Mon, month[i]) == 0) - break; - } - - rtime.tm_mon = i; - - Date = mktime(&rtime) - (time_t)_MYTIMEZONE; - - if (Date == (time_t)-1) - Date = time(NULL); - - } - - if (linelen) // Not Null line - { - ptr1 = ptr2 + 2; // Skip crlf - goto Loop; - } - - // Unpack Body - seems to be multipart even if only one - - // Can't we just send the whole body through ?? - // No, Attachment format is different - - // Mbo: GM8BPQ - // Body: 17 - // File: 1471 leadercoeffs.txt - - Input = MsgBit; - Boundary = initMultipartUnpack(&Input); - - i = 0; - - if (Boundary) - { - // input should be start of part - - // Find End of part - ie -- Boundary + CRLF or -- - - char * ptr, * saveptr; - char * Msgptr; - size_t BLen = strlen(Boundary); - size_t Partlen; - - saveptr = Msgptr = ptr = Input; - - while(ptr) // Just in case we run off end - { - if (*ptr == '-' && *(ptr+1) == '-') - { - if (memcmp(&ptr[2], Boundary, BLen) == 0) - { - // Found Boundary - - char * p1, *p2, *ptr3, *ptr4; - int llen; - int Base64 = 0; - int QuotedP = 0; - char * BoundaryStart = ptr; - - Partlen = ptr - Msgptr; - - ptr += (BLen + 2); // End of Boundary - - if (*ptr == '-') // Terminating Boundary - Input = NULL; - else - Input = ptr + 2; - - // Will check for quoted printable - - p1 = Msgptr; -Loop2: - p2 = strchr(p1, '\r'); - llen = (int)(p2 - p1); - - if (llen) - { - - if (_memicmp(p1, "Content-Transfer-Encoding:", 26) == 0) - { - if (_memicmp(&p1[27], "base64", 6) == 0) - Base64 = TRUE; - else if (_memicmp(&p1[27], "quoted", 6) == 0) - QuotedP = TRUE; - } - else if (_memicmp(p1, "Content-Disposition: ", 21) == 0) - { - ptr3 = strstr(&p1[21], "name"); - - if (ptr3) - { - ptr3 += 5; - if (*ptr3 == '"') ptr3++; - ptr4 = strchr(ptr3, '"'); - if (ptr4) *ptr4 = 0; - - partname[i] = ptr3; - } - } - - if (llen) // Not Null line - { - p1 = p2 + 2; // Skip crlf - goto Loop2; - } - } - - part[i] = strstr(p2, "\r\n"); // Over separator - - if (part[i]) - { - part[i] += 2; - partLen[i] = BoundaryStart - part[i] - 2; - if (QuotedP) - partLen[i] = decode_quoted_printable(part[i], partLen[i]); - else if (Base64) - { - int Len = partLen[i], NewLen; - char * ptr = part[i]; - char * ptr2 = part[i]; - - // WLE sends base64 with embedded crlf, so remove them - - while (Len-- > 0) - { - if ((*ptr) != 10 && (*ptr) != 13) - *(ptr2++) = *(ptr++); - else - ptr ++; - } - - Len = ptr2 - part[i]; - ptr = part[i]; - ptr2 = part[i]; - - while (Len > 0) - { - decodeblock(ptr, ptr2); - ptr += 4; - ptr2 += 3; - Len -= 4; - } - - NewLen = (int)(ptr2 - part[i]); - - if (*(ptr-1) == '=') - NewLen--; - - if (*(ptr-2) == '=') - NewLen--; - - partLen[i] = NewLen; - } - } - Msgptr = ptr = Input; - i++; - continue; } - - // See if more parts - } - ptr++; - } - ptr++; - } - - - // Build the message - - tm = gmtime(&Date); - - sprintf(DateString, "%04d/%02d/%02d %02d:%02d", - tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); - - NewMsg += sprintf(NewMsg, - "MID: %s\r\n" - "Date: %s\r\n" - "Type: %s\r\n" - "From: %s\r\n", - BID, DateString, Type, FullFrom); - -// if (ToCalls) -// { -// int i; - -// for (i = 0; i < Calls; i++) -// NewMsg += sprintf(NewMsg, "To: %s\r\n", ToCalls[i]); - -// } -// else - { - NewMsg += sprintf(NewMsg, "To: %s\r\n", - FullTo); - } -// if (WebMail->CC && WebMail->CC[0]) -// NewMsg += sprintf(NewMsg, "CC: %s\r\n", WebMail->CC); - - NewMsg += sprintf(NewMsg, - "Subject: %s\r\n" - "Mbo: %s\r\n", - Subject, BBSName); - - // Write the Body: line and any File Lines - - NewMsg += sprintf(NewMsg, "Body: %d\r\n", partLen[0]); - - i = 1; - - while (part[i]) - { - NewMsg += sprintf(NewMsg, "File: %d %s\r\n", - partLen[i], partname[i]); - - i++; - } - - NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line to end header - - // Now add parts - - i = 0; - - while (part[i]) - { - memmove(NewMsg, part[i], partLen[i]); - NewMsg += partLen[i]; - i++; - NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line between attachments - } - - conn->TempMsg->length = NewMsg - SaveMsg; - conn->TempMsg->datereceived = conn->TempMsg->datechanged = time(NULL); - conn->TempMsg->datecreated = Date; - strcpy(conn->TempMsg->bid, BID); - - if (strlen(Subject) > 60) - Subject[60] = 0; - - strcpy(conn->TempMsg->title, Subject); - - return TRUE; -} - -char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg) -{ - // First an XML Header - - char * Buffer = malloc(4096 + Msg->length); - int Len = 0; - - struct tm *tm; - char Date[32]; - char MsgTime[32]; - char Separator[33]=""; - time_t Time = time(NULL); - char * MailBuffer; - int BodyLen; - char * Encoded; - - // Get the message - may need length in header - - MailBuffer = ReadMessageFile(Msg->number); - - BodyLen = Msg->length; - - // Remove any B2 Header - - if (Msg->B2Flags & B2Msg) - { - // Remove B2 Headers (up to the File: Line) - - char * ptr; - ptr = strstr(MailBuffer, "Body:"); - if (ptr) - { - BodyLen = atoi(ptr + 5); - ptr = strstr(ptr, "\r\n\r\n"); - } - if (ptr) - { - memcpy(MailBuffer, ptr + 4, BodyLen); - MailBuffer[BodyLen] = 0; - } - } - - // encode body as quoted printable; - - Encoded = malloc(Msg->length * 3); - - BodyLen = encode_quoted_printable(MailBuffer, Encoded, BodyLen); - - // Create multipart Boundary - - CreateOneTimePassword(&Separator[0], "Key", 0); - CreateOneTimePassword(&Separator[16], "Key", 1); - - - tm = gmtime(&Time); - - sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - - tm = gmtime((time_t *)&Msg->datecreated); - - sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); - - Len += sprintf(&Buffer[Len], "\r\n"); - - Len += sprintf(&Buffer[Len], "\r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " add_message\r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", Date); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); - Len += sprintf(&Buffer[Len], " \r\n", MsgTime); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); - Len += sprintf(&Buffer[Len], " 2\r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", (Msg->B2Flags & Attachments) ? "true" : "false"); - Len += sprintf(&Buffer[Len], " %d\r\n", BodyLen); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->title); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); - Len += sprintf(&Buffer[Len], " 450443\r\n"); - Len += sprintf(&Buffer[Len], " %s\r\n", Msg->to); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " 0\r\n"); - Len += sprintf(&Buffer[Len], " False\r\n"); - Len += sprintf(&Buffer[Len], " False\r\n"); - Len += sprintf(&Buffer[Len], " False\r\n"); - Len += sprintf(&Buffer[Len], " False\r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " True\r\n"); - Len += sprintf(&Buffer[Len], " False\r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], " \r\n"); - Len += sprintf(&Buffer[Len], "\r\n"); - -// Debugprintf(Buffer); - - conn->SyncXMLLen = Len; - - Len += sprintf(&Buffer[Len], "Date: Sat, 04 Feb 2023 11:19:00 +0000\r\n"); - Len += sprintf(&Buffer[Len], "From: %s\r\n", Msg->from); - Len += sprintf(&Buffer[Len], "Subject: %s\r\n", Msg->title); - Len += sprintf(&Buffer[Len], "To: %s\r\n", Msg->to); - Len += sprintf(&Buffer[Len], "Message-ID: %s\r\n", Msg->bid); -// Len += sprintf(&Buffer[Len], "X-Source: G8BPQ\r\n"); -// Len += sprintf(&Buffer[Len], "X-Location: 52.979167N, 1.125000W (GRID SQUARE)\r\n"); -// Len += sprintf(&Buffer[Len], "X-RMS-Originator: G8BPQ\r\n"); -// Len += sprintf(&Buffer[Len], "X-RMS-Path: G8BPQ@2023-02-04-11:19:29\r\n"); - Len += sprintf(&Buffer[Len], "X-Relay: %s\r\n", BBSName); - - Len += sprintf(&Buffer[Len], "MIME-Version: 1.0\r\n"); - Len += sprintf(&Buffer[Len], "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", Separator); - - Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before separator - Len += sprintf(&Buffer[Len], "--%s\r\n", Separator); - Len += sprintf(&Buffer[Len], "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n"); - Len += sprintf(&Buffer[Len], "Content-Transfer-Encoding: quoted-printable\r\n"); - Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before body - - Len += sprintf(&Buffer[Len], "%s\r\n", Encoded); - Len += sprintf(&Buffer[Len], "--%s--\r\n", Separator); - - conn->SyncMsgLen = Len - conn->SyncXMLLen; - - free(Encoded); - free(MailBuffer); - - return Buffer; -} - -int encode_quoted_printable(char *s, char * out, int Len) -{ - int n = 0; - char * start = out; - - while(Len--) - { - if (n >= 73 && *s != 10 && *s != 13) - {strcpy(out, "=\r\n"); n = 0; out +=3;} - if (*s == 10 || *s == 13) {putchar(*s); n = 0;} - else if (*s<32 || *s==61 || *s>126) - out += sprintf(out, "=%02x", (unsigned char)*s); - else if (*s != 32 || (*(s+1) != 10 && *(s+1) != 13)) - {*(out++) = *s; n++;} - else n += printf("=20"); - - s++; - } - *out = 0; - - return out - start; -} - -int decode_quoted_printable(char *ptr, int len) -{ - // overwrite input with decoded version - - char * ptr2 = ptr; - char * End = ptr + len; - char * Start = ptr; - - while (ptr < End) - { - if ((*ptr) == '=') - { - char c = *(++ptr); - char d; - - c = c - 48; - if (c < 0) - { - // = CRLF as a soft break - - ptr += 2; - continue; - } - if (c > 9) c -= 7; - d = *(++ptr); - d = d - 48; - if (d > 9) d -= 7; - - *(ptr2) = c << 4 | d; - ptr2++; - ptr++; - } - else - *ptr2++ = *ptr++; - } - return ptr2 - Start; -} +/* +Copyright 2001-2018 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + +// Mail and Chat Server for BPQ32 Packet Switch +// +// Utility Routines + +#include "bpqmail.h" +#ifdef WIN32 +#include "Winspool.h" +#else +#include +#endif + + +BOOL Bells; +BOOL FlashOnBell; // Flash instead of Beep +BOOL StripLF; + +BOOL WarnWrap; +BOOL FlashOnConnect; +BOOL WrapInput; +BOOL CloseWindowOnBye; + +RECT ConsoleRect; + +BOOL OpenConsole; +BOOL OpenMon; + +int reportNewMesageEvents = 0; + + +extern struct ConsoleInfo BBSConsole; + +extern char LOC[7]; + +//#define BBSIDLETIME 120 +//#define USERIDLETIME 300 + + +#define BBSIDLETIME 900 +#define USERIDLETIME 900 + +#ifdef LINBPQ +extern BPQVECSTRUC ** BPQHOSTVECPTR; +UCHAR * GetLogDirectory(); +DllExport int APIENTRY SessionStateNoAck(int stream, int * state); +int RefreshWebMailIndex(); +#else +__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; +typedef char * (WINAPI FAR *FARPROCZ)(); +typedef int (WINAPI FAR *FARPROCX)(); +FARPROCZ pGetLOC; +FARPROCX pRefreshWebMailIndex; + +#endif + +Dll BOOL APIENTRY APISendAPRSMessage(char * Text, char * ToCall); +VOID APIENTRY md5 (char *arg, unsigned char * checksum); +int APIENTRY GetRaw(int stream, char * msg, int * len, int * count); +void GetSemaphore(struct SEM * Semaphore, int ID); +void FreeSemaphore(struct SEM * Semaphore); +int EncryptPass(char * Pass, char * Encrypt); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); +void DeletetoRecycle(char * FN); +VOID DoImportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoExportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID TidyPrompts(); +char * ReadMessageFileEx(struct MsgInfo * MsgRec); +char * APIENTRY GetBPQDirectory(); +BOOL SendARQMail(CIRCUIT * conn); +int APIENTRY ChangeSessionIdletime(int Stream, int idletime); +int APIENTRY GetApplNum(int Stream); +VOID FormatTime(char * Time, time_t cTime); +BOOL CheckifPacket(char * Via); +char * APIENTRY GetVersionString(); +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename); +void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); +int GetCMSHash(char * Challenge, char * Password); +BOOL SendAMPRSMTP(CIRCUIT * conn); +VOID ProcessMCASTLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen); +VOID MCastTimer(); +VOID MCastConTimer(ConnectionInfo * conn); +int FindFreeBBSNumber(); +VOID DoSetMsgNo(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +BOOL ProcessYAPPMessage(CIRCUIT * conn); +void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); +void YAPPSendData(ConnectionInfo * conn); +VOID CheckBBSNumber(int i); +struct UserInfo * FindAMPR(); +VOID SaveInt64Value(config_setting_t * group, char * name, long long value); +VOID SaveIntValue(config_setting_t * group, char * name, int value); +VOID SaveStringValue(config_setting_t * group, char * name, char * value); +char *stristr (char *ch1, char *ch2); +BOOL CheckforMessagetoServer(struct MsgInfo * Msg); +void DoHousekeepingCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled); +void ListCategories(ConnectionInfo * conn); +void RebuildNNTPList(); +long long GetInt64Value(config_setting_t * group, char * name); +void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +int ReformatSyncMessage(CIRCUIT * conn); +char * initMultipartUnpack(char ** Input); +char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg); +int decode_quoted_printable(char *ptr, int len); +void decodeblock( unsigned char in[4], unsigned char out[3]); +int encode_quoted_printable(char *s, char * out, int Len); +int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress); +int APIENTRY ChangeSessionCallsign(int Stream, unsigned char * AXCall); + +config_t cfg; +config_setting_t * group; + +extern ULONG BBSApplMask; + +//static int SEMCLASHES = 0; + +char SecureMsg[80] = ""; // CMS Secure Signon Response + +int NumberofStreams; + +extern char VersionStringWithBuild[50]; + +#define MaxSockets 64 + +extern struct SEM OutputSEM; + +extern ConnectionInfo Connections[MaxSockets+1]; + +extern struct UserInfo ** UserRecPtr; +extern int NumberofUsers; + +extern struct UserInfo * BBSChain; // Chain of users that are BBSes + +extern struct MsgInfo ** MsgHddrPtr; +extern int NumberofMessages; + +extern int FirstMessageIndextoForward; // Lowest Message wirh a forward bit set - limits search + +extern char UserDatabaseName[MAX_PATH]; +extern char UserDatabasePath[MAX_PATH]; + +extern char MsgDatabasePath[MAX_PATH]; +extern char MsgDatabaseName[MAX_PATH]; + +extern char BIDDatabasePath[MAX_PATH]; +extern char BIDDatabaseName[MAX_PATH]; + +extern char WPDatabasePath[MAX_PATH]; +extern char WPDatabaseName[MAX_PATH]; + +extern char BadWordsPath[MAX_PATH]; +extern char BadWordsName[MAX_PATH]; + +extern char BaseDir[MAX_PATH]; +extern char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% +extern char ProperBaseDir[MAX_PATH]; // BPQ Directory/BPQMailChat + + +extern char MailDir[MAX_PATH]; + +extern BIDRec ** BIDRecPtr; +extern int NumberofBIDs; + +extern BIDRec ** TempBIDRecPtr; +extern int NumberofTempBIDs; + +extern WPRec ** WPRecPtr; +extern int NumberofWPrecs; + +extern char ** BadWords; +extern int NumberofBadWords; +extern char * BadFile; + +extern int LatestMsg; +extern struct SEM MsgNoSemaphore; // For locking updates to LatestMsg +extern int HighestBBSNumber; + +extern int MaxMsgno; +extern int BidLifetime; +extern int MaxAge; +extern int MaintInterval; +extern int MaintTime; + +extern int ProgramErrors; + +extern BOOL MonBBS; +extern BOOL MonCHAT; +extern BOOL MonTCP; + +BOOL SendNewUserMessage = TRUE; +BOOL AllowAnon = FALSE; +BOOL UserCantKillT = FALSE; + +typedef int (WINAPI FAR *FARPROCX)(); +FARPROCX pRunEventProgram; + +int RunEventProgram(char * Program, char * Param); + + +extern BOOL EventsEnabled; + +#define BPQHOSTSTREAMS 64 + +// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 + +extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; + +#ifdef LINBPQ +extern BPQVECSTRUC ** BPQHOSTVECPTR; +extern char WL2KModes [54][18]; +#else +__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; + + +char WL2KModes [54][18] = { + "Packet 1200", "Packet 2400", "Packet 4800", "Packet 9600", "Packet 19200", "Packet 38400", "High Speed Packet", "", "", "", "", + "", "Pactor 1", "", "", "Pactor 2", "", "Pactor 3", "", "", "Pactor 4", // 10 - 20 + "Winmor 500", "Winmor 1600", "", "", "", "", "", "", "", // 21 - 29 + "Robust Packet", "", "", "", "", "", "", "", "", "", // 30 - 39 + "ARDOP 200", "ARDOP 500", "ARDOP 1000", "ARDOP 2000", "ARDOP 2000 FM", "", "", "", "", "", // 40 - 49 + "VARA", "VARA FM", "VARA FM WIDE", "VARA 500"}; +#endif + + + + + +FILE * LogHandle[4] = {NULL, NULL, NULL, NULL}; + +time_t LastLogTime[4] = {0, 0, 0, 0}; + +char FilesNames[4][100] = {"", "", "", ""}; + +char * Logs[4] = {"BBS", "CHAT", "TCP", "DEBUG"}; + + +BOOL OpenLogfile(int Flags) +{ + UCHAR FN[MAX_PATH]; + time_t LT; + struct tm * tm; + + LT = time(NULL); + tm = gmtime(<); + + sprintf(FN,"%s/logs/log_%02d%02d%02d_%s.txt", GetLogDirectory(), tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Logs[Flags]); + + LogHandle[Flags] = fopen(FN, "ab"); + +#ifndef WIN32 + + if (strcmp(FN, &FilesNames[Flags][0])) + { + UCHAR SYMLINK[MAX_PATH]; + + sprintf(SYMLINK,"%s/logLatest_%s.txt", GetBPQDirectory(), Logs[Flags]); + unlink(SYMLINK); + strcpy(&FilesNames[Flags][0], FN); + symlink(FN, SYMLINK); + } + +#endif + + return (LogHandle[Flags] != NULL); +} + +typedef int (WINAPI FAR *FARPROCX)(); + +extern FARPROCX pDllBPQTRACE; + +struct SEM LogSEM = {0, 0}; + +void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags) +{ + char CRLF[2] = {0x0d,0x0a}; + struct tm * tm; + char Stamp[20]; + time_t LT; +// struct _EXCEPTION_POINTERS exinfo; + + // Write to Node BPQTRACE system + + if ((Flags == LOG_BBS || Flags == LOG_DEBUG_X) && MsgLen < 250) + { + MESSAGE Monframe; + memset(&Monframe, 0, sizeof(Monframe)); + + Monframe.PORT = 64; + Monframe.LENGTH = 12 + MsgLen; + Monframe.DEST[0] = 1; // Plain Text Monitor + + memcpy(&Monframe.DEST[1], Msg, MsgLen); + Monframe.DEST[1 + MsgLen] = 0; + + time(&Monframe.Timestamp); +#ifdef LINBPQ + GetSemaphore(&Semaphore, 88); + BPQTRACE(&Monframe, FALSE); + FreeSemaphore(&Semaphore); +#else + if (pDllBPQTRACE) + pDllBPQTRACE(&Monframe, FALSE); +#endif + } +#ifndef LINBPQ + __try + { +#endif + + + +#ifndef LINBPQ + + if (hMonitor) + { + if (Flags == LOG_TCP && MonTCP) + { + WritetoMonitorWindow((char *)&Flag, 1); + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_CHAT && MonCHAT) + { + WritetoMonitorWindow((char *)&Flag, 1); + + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + WritetoMonitorWindow(call, 10); + } + else + WritetoMonitorWindow(" ", 10); + + WritetoMonitorWindow(Msg, MsgLen); + if (Msg[MsgLen-1] != '\r') + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_BBS && MonBBS) + { + WritetoMonitorWindow((char *)&Flag, 1); + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + WritetoMonitorWindow(call, 10); + } + else + WritetoMonitorWindow(" ", 10); + + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_DEBUG_X) + { + WritetoMonitorWindow((char *)&Flag, 1); + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + } +#endif + + if (Flags == LOG_TCP && !LogTCP) + return; + if (Flags == LOG_BBS && !LogBBS) + return; + if (Flags == LOG_CHAT && !LogCHAT) + return; + + GetSemaphore(&LogSEM, 0); + + if (LogHandle[Flags] == NULL) + OpenLogfile(Flags); + + if (LogHandle[Flags] == NULL) + { + FreeSemaphore(&LogSEM); + return; + } + LT = time(NULL); + tm = gmtime(<); + + sprintf(Stamp,"%02d%02d%02d %02d:%02d:%02d %c", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, Flag); + + fwrite(Stamp, 1, strlen(Stamp), LogHandle[Flags]); + + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + fwrite(call, 1, 10, LogHandle[Flags]); + } + else + fwrite(" ", 1, 10, LogHandle[Flags]); + + fwrite(Msg, 1, MsgLen, LogHandle[Flags]); + + if (Flags == LOG_CHAT && Msg[MsgLen-1] == '\r') + fwrite(&CRLF[1], 1, 1, LogHandle[Flags]); + else + fwrite(CRLF, 1, 2, LogHandle[Flags]); + + // Don't close/reopen logs every time + +// if ((LT - LastLogTime[Flags]) > 60) + { + LastLogTime[Flags] = LT; + fclose(LogHandle[Flags]); + LogHandle[Flags] = NULL; + } + FreeSemaphore(&LogSEM); + +#ifndef LINBPQ + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +#endif +} + +int CriticalErrorHandler(char * error) +{ + Debugprintf("Critical Error %s", error); + ProgramErrors = 25; + CheckProgramErrors(); // Force close + return 0; +} + +BOOL CheckForTooManyErrors(ConnectionInfo * conn) +{ + conn->ErrorCount++; + + if (conn->ErrorCount > 4) + { + BBSputs(conn, "Too many errors - closing\r"); + conn->CloseAfterFlush = 20; + return TRUE; + } + return FALSE; +} + + + + + +VOID __cdecl Debugprintf(const char * format, ...) +{ + char Mess[16384]; + va_list(arglist); + int Len; + + va_start(arglist, format); + Len = vsprintf(Mess, format, arglist); +#ifndef LINBPQ + WriteLogLine(NULL, '!',Mess, Len, LOG_DEBUG_X); +#endif + // #ifdef _DEBUG + strcat(Mess, "\r\n"); + OutputDebugString(Mess); + +// #endif + return; +} + +VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...) +{ + char Mess[1000]; + va_list(arglist);int Len; + + va_start(arglist, format); + Len = vsprintf(Mess, format, arglist); + WriteLogLine(conn, InOut, Mess, Len, LogMode); + + return; +} + +struct MsgInfo * GetMsgFromNumber(int msgno) +{ + if (msgno < 1 || msgno > 999999) + return NULL; + + return MsgnotoMsg[msgno]; +} + +struct UserInfo * AllocateUserRecord(char * Call) +{ + struct UserInfo * User = zalloc(sizeof (struct UserInfo)); + + strcpy(User->Call, Call); + User->Length = sizeof (struct UserInfo); + + GetSemaphore(&AllocSemaphore, 0); + + UserRecPtr=realloc(UserRecPtr,(++NumberofUsers+1) * sizeof(void *)); + UserRecPtr[NumberofUsers]= User; + + FreeSemaphore(&AllocSemaphore); + + return User; +} + +struct MsgInfo * AllocateMsgRecord() +{ + struct MsgInfo * Msg = zalloc(sizeof (struct MsgInfo)); + + GetSemaphore(&AllocSemaphore, 0); + + MsgHddrPtr=realloc(MsgHddrPtr,(++NumberofMessages+1) * sizeof(void *)); + MsgHddrPtr[NumberofMessages] = Msg; + + FreeSemaphore(&AllocSemaphore); + + return Msg; +} + +BIDRec * AllocateBIDRecord() +{ + BIDRec * BID = zalloc(sizeof (BIDRec)); + + GetSemaphore(&AllocSemaphore, 0); + + BIDRecPtr = realloc(BIDRecPtr,(++NumberofBIDs+1) * sizeof(void *)); + BIDRecPtr[NumberofBIDs] = BID; + + FreeSemaphore(&AllocSemaphore); + + return BID; +} + +BIDRec * AllocateTempBIDRecord() +{ + BIDRec * BID = zalloc(sizeof (BIDRec)); + + GetSemaphore(&AllocSemaphore, 0); + + TempBIDRecPtr=realloc(TempBIDRecPtr,(++NumberofTempBIDs+1) * sizeof(void *)); + TempBIDRecPtr[NumberofTempBIDs] = BID; + + FreeSemaphore(&AllocSemaphore); + + return BID; +} + +struct UserInfo * LookupCall(char * Call) +{ + struct UserInfo * ptr = NULL; + int i; + + for (i=1; i <= NumberofUsers; i++) + { + ptr = UserRecPtr[i]; + + if (_stricmp(ptr->Call, Call) == 0) return ptr; + + } + + return NULL; +} + +int GetNetInt(char * Line) +{ + char temp[1024]; + char * ptr = strlop(Line, ','); + int n = atoi(Line); + if (ptr == NULL) + Line[0] = 0; + else + { + strcpy(temp, ptr); + strcpy(Line, temp); + } + return n; +} + +VOID GetUserDatabase() +{ + struct UserInfo UserRec; + + FILE * Handle; + size_t ReadLen; + struct UserInfo * user; + time_t UserLimit = time(NULL) - (UserLifetime * 86400); // Oldest user to keep + int i; + + // See if user config is in main config + + group = config_lookup (&cfg, "BBSUsers"); + + if (group) + { + // We have User config in the main config file. so use that + + int index = 0; + char * stats; + struct MsgStats * Stats; + char * ptr, * ptr2; + + config_setting_t * entry = config_setting_get_elem (group, index++); + + // Initialise a new File + + UserRecPtr = malloc(sizeof(void *)); + UserRecPtr[0] = malloc(sizeof (struct UserInfo)); + memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); + UserRecPtr[0]->Length = sizeof (struct UserInfo); + + NumberofUsers = 0; + + while (entry) + { + char call[16]; + + // entry->name is call, will have * in front if a call stating woth number + + if (entry->name[0] == '*') + strcpy(call, &entry->name[1]); + else + strcpy(call, entry->name); + + user = AllocateUserRecord(call); + + ptr = entry->value.sval; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->Name, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->Address, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->HomeBBS, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->QRA, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->pass, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->ZIP, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->CMSPass, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->lastmsg = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->flags = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->PageLen = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->BBSNumber = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->RMSSSIDBits = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->WebSeqNo = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->TimeLastConnected = atol(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) Stats = &user->Total; + stats = ptr; + + if (Stats == NULL) + { + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + + Stats->ConnectsIn = GetNetInt(stats); + Stats->ConnectsOut = GetNetInt(stats); + Stats->MsgsReceived[0] = GetNetInt(stats); + Stats->MsgsReceived[1] = GetNetInt(stats); + Stats->MsgsReceived[2] = GetNetInt(stats); + Stats->MsgsReceived[3] = GetNetInt(stats); + Stats->MsgsSent[0] = GetNetInt(stats); + Stats->MsgsSent[1] = GetNetInt(stats); + Stats->MsgsSent[2] = GetNetInt(stats); + Stats->MsgsSent[3] = GetNetInt(stats); + Stats->MsgsRejectedIn[0] = GetNetInt(stats); + Stats->MsgsRejectedIn[1] = GetNetInt(stats); + Stats->MsgsRejectedIn[2] = GetNetInt(stats); + Stats->MsgsRejectedIn[3] = GetNetInt(stats); + Stats->MsgsRejectedOut[0] = GetNetInt(stats); + Stats->MsgsRejectedOut[1] = GetNetInt(stats); + Stats->MsgsRejectedOut[2] = GetNetInt(stats); + Stats->MsgsRejectedOut[3] = GetNetInt(stats); + Stats->BytesForwardedIn[0] = GetNetInt(stats); + Stats->BytesForwardedIn[1] = GetNetInt(stats); + Stats->BytesForwardedIn[2] = GetNetInt(stats); + Stats->BytesForwardedIn[3] = GetNetInt(stats); + Stats->BytesForwardedOut[0] = GetNetInt(stats); + Stats->BytesForwardedOut[1] = GetNetInt(stats); + Stats->BytesForwardedOut[2] = GetNetInt(stats); + Stats->BytesForwardedOut[3] = GetNetInt(stats); + + Stats = &user->Last; + stats = ptr2; + + if (Stats == NULL) + { + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + + Stats->ConnectsIn = GetNetInt(stats); + Stats->ConnectsOut = GetNetInt(stats); + Stats->MsgsReceived[0] = GetNetInt(stats); + Stats->MsgsReceived[1] = GetNetInt(stats); + Stats->MsgsReceived[2] = GetNetInt(stats); + Stats->MsgsReceived[3] = GetNetInt(stats); + Stats->MsgsSent[0] = GetNetInt(stats); + Stats->MsgsSent[1] = GetNetInt(stats); + Stats->MsgsSent[2] = GetNetInt(stats); + Stats->MsgsSent[3] = GetNetInt(stats); + Stats->MsgsRejectedIn[0] = GetNetInt(stats); + Stats->MsgsRejectedIn[1] = GetNetInt(stats); + Stats->MsgsRejectedIn[2] = GetNetInt(stats); + Stats->MsgsRejectedIn[3] = GetNetInt(stats); + Stats->MsgsRejectedOut[0] = GetNetInt(stats); + Stats->MsgsRejectedOut[1] = GetNetInt(stats); + Stats->MsgsRejectedOut[2] = GetNetInt(stats); + Stats->MsgsRejectedOut[3] = GetNetInt(stats); + Stats->BytesForwardedIn[0] = GetNetInt(stats); + Stats->BytesForwardedIn[1] = GetNetInt(stats); + Stats->BytesForwardedIn[2] = GetNetInt(stats); + Stats->BytesForwardedIn[3] = GetNetInt(stats); + Stats->BytesForwardedOut[0] = GetNetInt(stats); + Stats->BytesForwardedOut[1] = GetNetInt(stats); + Stats->BytesForwardedOut[2] = GetNetInt(stats); + Stats->BytesForwardedOut[3] = GetNetInt(stats); + + + if ((user->flags & F_BBS) == 0) // Not BBS - Check Age + { + if (UserLifetime && user->TimeLastConnected) // Dont delete manually added Users that havent yet connected + { + if (user->TimeLastConnected < UserLimit) + { + // Too Old - ignore + + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + } + } + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (user->lastmsg < 0 || user->lastmsg > LatestMsg) + user->lastmsg = LatestMsg; + + + entry = config_setting_get_elem (group, index++); + } + } + else + { + Handle = fopen(UserDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); + UserRecPtr[0]->Length = sizeof (struct UserInfo); + + NumberofUsers = 0; + + return; + } + + + // Get First Record + + ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&UserRec, 0, sizeof (struct UserInfo)); + UserRec.Length = sizeof (struct UserInfo); + } + else + { + // See if format has changed + + if (UserRec.Length == 0) + { + // Old format without a Length field + + struct OldUserInfo * OldRec = (struct OldUserInfo *)&UserRec; + int Users = OldRec->ConnectsIn; // User Count in control record + char Backup1[MAX_PATH]; + + // Create a backup in case reversion is needed and Reposition to first User record + + fclose(Handle); + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".oldformat"); + + CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak + + Handle = fopen(UserDatabasePath, "rb"); + + ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); // Skip Control Record + + // Set up control record + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); + UserRecPtr[0]->Length = sizeof (UserRec); + + NumberofUsers = 0; + +OldNext: + + ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); + + if (ReadLen > 0) + { + if (OldRec->Call[0] < '0') + goto OldNext; // Blank record + + user = AllocateUserRecord(OldRec->Call); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + // Copy info from Old record + + user->lastmsg = OldRec->lastmsg; + user->Total.ConnectsIn = OldRec->ConnectsIn; + user->TimeLastConnected = OldRec->TimeLastConnected; + user->flags = OldRec->flags; + user->PageLen = OldRec->PageLen; + user->BBSNumber = OldRec->BBSNumber; + memcpy(user->Name, OldRec->Name, 18); + memcpy(user->Address, OldRec->Address, 61); + user->Total.MsgsReceived[0] = OldRec->MsgsReceived; + user->Total.MsgsSent[0] = OldRec->MsgsSent; + user->Total.MsgsRejectedIn[0] = OldRec->MsgsRejectedIn; // Messages we reject + user->Total.MsgsRejectedOut[0] = OldRec->MsgsRejectedOut; // Messages Rejectd by other end + user->Total.BytesForwardedIn[0] = OldRec->BytesForwardedIn; + user->Total.BytesForwardedOut[0] = OldRec->BytesForwardedOut; + user->Total.ConnectsOut = OldRec->ConnectsOut; // Forwarding Connects Out + user->RMSSSIDBits = OldRec->RMSSSIDBits; // SSID's to poll in RMS + memcpy(user->HomeBBS, OldRec->HomeBBS, 41); + memcpy(user->QRA, OldRec->QRA, 7); + memcpy(user->pass, OldRec->pass, 13); + memcpy(user->ZIP, OldRec->ZIP, 9); + + // Read any forwarding info, even if not a BBS. + // This allows a BBS to be temporarily set as a + // normal user without loosing forwarding info + + SetupForwardingStruct(user); + + if (user->flags & F_BBS) + { + // Defined as BBS - allocate and initialise forwarding structure + + // Add to BBS Chain; + + user->BBSNext = BBSChain; + BBSChain = user; + + // Save Highest BBS Number + + if (user->BBSNumber > HighestBBSNumber) HighestBBSNumber = user->BBSNumber; + } + goto OldNext; + } + + SortBBSChain(); + fclose(Handle); + + return; + } + } + + // Set up control record + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); + UserRecPtr[0]->Length = sizeof (UserRec); + + NumberofUsers = 0; + +Next: + + ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); + + if (ReadLen > 0) + { + if (UserRec.Call[0] < '0') + goto Next; // Blank record + + if (UserRec.TimeLastConnected == 0) + UserRec.TimeLastConnected = UserRec.xTimeLastConnected; + + if ((UserRec.flags & F_BBS) == 0) // Not BBS - Check Age + if (UserLifetime) // if limit set + if (UserRec.TimeLastConnected) // Dont delete manually added Users that havent yet connected + if (UserRec.TimeLastConnected < UserLimit) + goto Next; // Too Old - ignore + + user = AllocateUserRecord(UserRec.Call); + memcpy(user, &UserRec, sizeof (UserRec)); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + user->ForwardingInfo = NULL; // In case left behind on crash + user->BBSNext = NULL; + user->POP3Locked = FALSE; + + if (user->lastmsg < 0 || user->lastmsg > LatestMsg) + user->lastmsg = LatestMsg; + + goto Next; + } + fclose(Handle); + } + + // Setting up BBS struct has been moved until all user record + // have been read so we can fix corrupt BBSNUmber + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + // Read any forwarding info, even if not a BBS. + // This allows a BBS to be temporarily set as a + // normal user without loosing forwarding info + + SetupForwardingStruct(user); + + if (user->flags & F_BBS) + { + // Add to BBS Chain; + + if (user->BBSNumber == NBBBS) // Fix corrupt records + { + user->BBSNumber = FindFreeBBSNumber(); + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; // cant really do much else + } + + user->BBSNext = BBSChain; + BBSChain = user; + +// Logprintf(LOG_BBS, NULL, '?', "BBS %s BBSNumber %d", user->Call, user->BBSNumber); + + // Save Highest BBS Number + + if (user->BBSNumber > HighestBBSNumber) + HighestBBSNumber = user->BBSNumber; + } + } + + // Check for dulicate BBS numbers + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_BBS) + { + if (user->BBSNumber == 0) + user->BBSNumber = FindFreeBBSNumber(); + + CheckBBSNumber(user->BBSNumber); + } + } + + SortBBSChain(); +} + +VOID CopyUserDatabase() +{ + return; // User config now in main config file +/* + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + + // Keep 4 Generations + + strcpy(Backup2, UserDatabasePath); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, UserDatabasePath); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); //Move .bak to .bak.1 + + CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak +*/ +} + +VOID CopyConfigFile(char * ConfigName) +{ + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + + // Keep 4 Generations + + strcpy(Backup2, ConfigName); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, ConfigName); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, ConfigName); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, ConfigName); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); // Move .bak to .bak.1 + + CopyFile(ConfigName, Backup1, FALSE); // Copy to .bak +} + + + +VOID SaveUserDatabase() +{ + SaveConfig(ConfigName); // User config is now in main config file + GetConfig(ConfigName); + +/* + FILE * Handle; + size_t WriteLen; + int i; + + Handle = fopen(UserDatabasePath, "wb"); + + UserRecPtr[0]->Total.ConnectsIn = NumberofUsers; + + for (i=0; i <= NumberofUsers; i++) + { + WriteLen = fwrite(UserRecPtr[i], 1, (int)sizeof (struct UserInfo), Handle); + } + + fclose(Handle); +*/ + return; +} + +VOID GetMessageDatabase() +{ + struct MsgInfo MsgRec; + FILE * Handle; + size_t ReadLen; + struct MsgInfo * Msg; + char * MsgBytes; + int FileRecsize = sizeof(struct MsgInfo); // May be changed if reformating + BOOL Reformatting = FALSE; + char HEX[3] = ""; + int n; + + // See if Message Database is in main config + + group = config_lookup (&cfg, "MSGS"); + +// group = 0; + + if (group) + { + // We have User config in the main config file. so use that + + int index = 0; + char * ptr, * ptr2; + config_setting_t * entry = config_setting_get_elem (group, index++); + + // Initialise a new File + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); + NumberofMessages = 0; + MsgHddrPtr[0]->status = 2; + + if (entry) + { + // First Record has current message number + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + ptr2 = strlop(ptr2, '|'); + if (ptr2) + LatestMsg = atoi(ptr2); + } + + entry = config_setting_get_elem (group, index++); + + while (entry) + { + // entry->name is MsgNo with 'R' in front + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + + memset(&MsgRec, 0, sizeof(struct MsgInfo)); + + MsgRec.number = atoi(&entry->name[1]); + MsgRec.type = ptr[0]; + + ptr = ptr2; + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + ptr2 = strlop(ptr, '|'); + MsgRec.status = ptr[0]; + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.length = atoi(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datereceived = atol(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.bbsfrom, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.via, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.from, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.to, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.bid, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.B2Flags = atoi(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datecreated = atol(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datechanged = atol(ptr); + + ptr = ptr2; + if (ptr) ptr2 = strlop(ptr, '|'); + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + if (ptr[0]) + { + char String[50] = "00000000000000000000"; + String[20] = 0; + memcpy(String, ptr, strlen(ptr)); + for (n = 0; n < NBMASK; n++) + { + memcpy(HEX, &String[n * 2], 2); + MsgRec.fbbs[n] = (UCHAR)strtol(HEX, 0, 16); + } + } + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + if (ptr[0]) + { + char String[50] = "00000000000000000000"; + String[20] = 0; + memcpy(String, ptr, strlen(ptr)); + for (n = 0; n < NBMASK; n++) + { + memcpy(HEX, &String[n * 2], 2); + MsgRec.forw[n] = (UCHAR)strtol(HEX, 0, 16); + } + } + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.emailfrom, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.UTF8 = atoi(ptr); + + ptr = ptr2; + + if (ptr) + { + strcpy(MsgRec.title, ptr); + + MsgBytes = ReadMessageFileEx(&MsgRec); + + if (MsgBytes) + { + free(MsgBytes); + Msg = AllocateMsgRecord(); + memcpy(Msg, &MsgRec, sizeof (MsgRec)); + + MsgnotoMsg[Msg->number] = Msg; + + // Fix Corrupted NTS Messages + + if (Msg->type == 'N') + Msg->type = 'T'; + + // Look for corrupt FROM address (ending in @) + + strlop(Msg->from, '@'); + + BuildNNTPList(Msg); // Build NNTP Groups list + + // If any forward bits are set, increment count on corresponding BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + } + } + } + entry = config_setting_get_elem (group, index++); + } + + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + + return; + } + + Handle = fopen(MsgDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); + NumberofMessages = 0; + MsgHddrPtr[0]->status = 2; + + return; + } + + // Get First Record + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&MsgRec, 0, sizeof (MsgRec)); + MsgRec.status = 2; + } + + // Set up control record + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= malloc(sizeof (MsgRec)); + memcpy(MsgHddrPtr[0], &MsgRec, sizeof (MsgRec)); + + LatestMsg=MsgHddrPtr[0]->length; + + NumberofMessages = 0; + + if (MsgRec.status == 1) // Used as file format version + // 0 = original, 1 = Extra email from addr, 2 = More BBS's. + { + char Backup1[MAX_PATH]; + + // Create a backup in case reversion is needed and Reposition to first User record + + fclose(Handle); + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".oldformat"); + + CopyFile(MsgDatabasePath, Backup1, FALSE); // Copy to .oldformat + + Handle = fopen(MsgDatabasePath, "rb"); + + FileRecsize = sizeof(struct OldMsgInfo); + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + MsgHddrPtr[0]->status = 2; + } + +Next: + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + if (ReadLen > 0) + { + // Validate Header + + if (FileRecsize == sizeof(struct MsgInfo)) + { + if (MsgRec.type == 0 || MsgRec.number == 0) + goto Next; + + MsgBytes = ReadMessageFileEx(&MsgRec); + + if (MsgBytes) + { + // MsgRec.length = strlen(MsgBytes); + free(MsgBytes); + } + else + goto Next; + + Msg = AllocateMsgRecord(); + + memcpy(Msg, &MsgRec, +sizeof (MsgRec)); + } + else + { + // Resizing - record from file is an OldRecInfo + + struct OldMsgInfo * OldMessage = (struct OldMsgInfo *) &MsgRec; + + if (OldMessage->type == 0) + goto Next; + + if (OldMessage->number > 99999 || OldMessage->number < 1) + goto Next; + + Msg = AllocateMsgRecord(); + + + Msg->B2Flags = OldMessage->B2Flags; + memcpy(Msg->bbsfrom, OldMessage->bbsfrom, 7); + memcpy(Msg->bid, OldMessage->bid, 13); + Msg->datechanged = OldMessage->datechanged; + Msg->datecreated = OldMessage->datecreated; + Msg->datereceived = OldMessage->datereceived; + memcpy(Msg->emailfrom, OldMessage->emailfrom, 41); + memcpy(Msg->fbbs , OldMessage->fbbs, 10); + memcpy(Msg->forw , OldMessage->forw, 10); + memcpy(Msg->from, OldMessage->from, 7); + Msg->length = OldMessage->length; + Msg->nntpnum = OldMessage->nntpnum; + Msg->number = OldMessage->number; + Msg->status = OldMessage->status; + memcpy(Msg->title, OldMessage->title, 61); + memcpy(Msg->to, OldMessage->to, 7); + Msg->type = OldMessage->type; + memcpy(Msg->via, OldMessage->via, 41); + } + + MsgnotoMsg[Msg->number] = Msg; + + // Fix Corrupted NTS Messages + + if (Msg->type == 'N') + Msg->type = 'T'; + + // Look for corrupt FROM address (ending in @) + + strlop(Msg->from, '@'); + + // Move Dates if first run with new format + + if (Msg->datecreated == 0) + Msg->datecreated = Msg->xdatecreated; + + if (Msg->datereceived == 0) + Msg->datereceived = Msg->xdatereceived; + + if (Msg->datechanged == 0) + Msg->datechanged = Msg->xdatechanged; + + BuildNNTPList(Msg); // Build NNTP Groups list + + Msg->Locked = 0; // In case left locked + Msg->Defered = 0; // In case left set. + + // If any forward bits are set, increment count on corresponding BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + } + + goto Next; + } + + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + + fclose(Handle); +} + +VOID CopyMessageDatabase() +{ + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + +// return; + + // Keep 4 Generations + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); //Move .bak to .bak.1 + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak"); + + CopyFile(MsgDatabasePath, Backup2, FALSE); // Copy to .bak + +} + +VOID SaveMessageDatabase() +{ + FILE * Handle; + size_t WriteLen; + int i; + char Key[16]; + struct MsgInfo *Msg; +// char CfgName[MAX_PATH]; + char HEXString1[64]; + char HEXString2[64]; + int n; +// char * CfgBuffer; + char Cfg[1024]; +// int CfgLen = 0; +// FILE * hFile; + +// SaveConfig(ConfigName); // Message Headers now in main config +// return; + +#ifdef LINBPQ + RefreshWebMailIndex(); +#else + if (pRefreshWebMailIndex) + pRefreshWebMailIndex(); +#endif + + Handle = fopen(MsgDatabasePath, "wb"); + + if (Handle == NULL) + { + CriticalErrorHandler("Failed to open message database"); + return; + } + + MsgHddrPtr[0]->status = 2; + MsgHddrPtr[0]->number = NumberofMessages; + MsgHddrPtr[0]->length = LatestMsg; + + for (i=0; i <= NumberofMessages; i++) + { + WriteLen = fwrite(MsgHddrPtr[i], 1, sizeof (struct MsgInfo), Handle); + + if (WriteLen != sizeof(struct MsgInfo)) + { + CriticalErrorHandler("Failed to write message database record"); + return; + } + } + + if (fclose(Handle) != 0) + CriticalErrorHandler("Failed to close message database"); + + for (i = 1; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); + + n = 39; + while (n >=0 && HEXString1[n] == '0') + HEXString1[n--] = 0; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); + + n = 39; + while (n >= 0 && HEXString2[n] == '0') + HEXString2[n--] = 0; + + sprintf(Key, "R%d:\r\n", i); + + n = sprintf(Cfg, "%c|%c|%d|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, + Msg->number, Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], + &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, + &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); + } + + return; +} + +VOID GetBIDDatabase() +{ + BIDRec BIDRec; + FILE * Handle; + size_t ReadLen; + BIDRecP BID; + int index = 0; + char * ptr, * ptr2; + + // If BID info is in main config file, use it + + group = config_lookup (&cfg, "BIDS"); + + if (group) + { + config_setting_t * entry = config_setting_get_elem (group, index++); + + BIDRecPtr=malloc(sizeof(void *)); + BIDRecPtr[0]= malloc(sizeof (BIDRec)); + memset(BIDRecPtr[0], 0, sizeof (BIDRec)); + NumberofBIDs = 0; + + while (entry) + { + // entry->name is Bid with 'R' in front + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + + if (ptr && ptr2) + { + BID = AllocateBIDRecord(); + strcpy(BID->BID, &entry->name[1]); + BID->mode = atoi(ptr); + BID->u.timestamp = atoi(ptr2); + + if (BID->u.timestamp == 0) + BID->u.timestamp = LOWORD(time(NULL)/86400); + + } + entry = config_setting_get_elem (group, index++); + } + return; + } + + Handle = fopen(BIDDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + BIDRecPtr=malloc(sizeof(void *)); + BIDRecPtr[0]= malloc(sizeof (BIDRec)); + memset(BIDRecPtr[0], 0, sizeof (BIDRec)); + NumberofBIDs = 0; + + return; + } + + + // Get First Record + + ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&BIDRec, 0, sizeof (BIDRec)); + } + + // Set up control record + + BIDRecPtr = malloc(sizeof(void *)); + BIDRecPtr[0] = malloc(sizeof (BIDRec)); + memcpy(BIDRecPtr[0], &BIDRec, sizeof (BIDRec)); + + NumberofBIDs = 0; + +Next: + + ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); + + if (ReadLen > 0) + { + BID = AllocateBIDRecord(); + memcpy(BID, &BIDRec, sizeof (BIDRec)); + + if (BID->u.timestamp == 0) + BID->u.timestamp = LOWORD(time(NULL)/86400); + + goto Next; + } + + fclose(Handle); +} + +VOID CopyBIDDatabase() +{ + char Backup[MAX_PATH]; + +// return; + + + strcpy(Backup, BIDDatabasePath); + strcat(Backup, ".bak"); + + CopyFile(BIDDatabasePath, Backup, FALSE); +} + +VOID SaveBIDDatabase() +{ + FILE * Handle; + size_t WriteLen; + int i; + +// return; // Bids are now in main config and are saved when message is saved + + Handle = fopen(BIDDatabasePath, "wb"); + + BIDRecPtr[0]->u.msgno = NumberofBIDs; // First Record has file size + + for (i=0; i <= NumberofBIDs; i++) + { + WriteLen = fwrite(BIDRecPtr[i], 1, sizeof (BIDRec), Handle); + } + + fclose(Handle); + + return; +} + +BIDRec * LookupBID(char * BID) +{ + BIDRec * ptr = NULL; + int i; + + for (i=1; i <= NumberofBIDs; i++) + { + ptr = BIDRecPtr[i]; + + if (_stricmp(ptr->BID, BID) == 0) + return ptr; + } + + return NULL; +} + +BIDRec * LookupTempBID(char * BID) +{ + BIDRec * ptr = NULL; + int i; + + for (i=1; i <= NumberofTempBIDs; i++) + { + ptr = TempBIDRecPtr[i]; + + if (_stricmp(ptr->BID, BID) == 0) return ptr; + } + + return NULL; +} + +VOID RemoveTempBIDS(CIRCUIT * conn) +{ + // Remove any Temp BID records for conn. Called when connection closes - Msgs will be complete or failed + + if (NumberofTempBIDs == 0) + return; + else + { + BIDRec * ptr = NULL; + BIDRec ** NewTempBIDRecPtr = zalloc((NumberofTempBIDs+1) * sizeof(void *)); + int i = 0, n; + + GetSemaphore(&AllocSemaphore, 0); + + for (n = 1; n <= NumberofTempBIDs; n++) + { + ptr = TempBIDRecPtr[n]; + + if (ptr) + { + if (ptr->u.conn == conn) + // Remove this entry + free(ptr); + else + NewTempBIDRecPtr[++i] = ptr; + } + } + + NumberofTempBIDs = i; + + free(TempBIDRecPtr); + + TempBIDRecPtr = NewTempBIDRecPtr; + FreeSemaphore(&AllocSemaphore); + } + +} + +VOID GetBadWordFile() +{ + FILE * Handle; + DWORD FileSize; + char * ptr1, * ptr2; + struct stat STAT; + + if (stat(BadWordsPath, &STAT) == -1) + return; + + FileSize = STAT.st_size; + + Handle = fopen(BadWordsPath, "rb"); + + if (Handle == NULL) + return; + + // Release old info in case a re-read + + if (BadWords) free(BadWords); + if (BadFile) free(BadFile); + + BadWords = NULL; + BadFile = NULL; + NumberofBadWords = 0; + + BadFile = malloc(FileSize+1); + + fread(BadFile, 1, FileSize, Handle); + + fclose(Handle); + + BadFile[FileSize]=0; + + _strlwr(BadFile); // Compares are case-insensitive + + ptr1 = BadFile; + + while (ptr1) + { + if (*ptr1 == '\n') ptr1++; + + ptr2 = strtok_s(NULL, "\r\n", &ptr1); + if (ptr2) + { + if (*ptr2 != '#') + { + BadWords = realloc(BadWords,(++NumberofBadWords+1) * sizeof(void *)); + BadWords[NumberofBadWords] = ptr2; + } + } + else + break; + } +} + +BOOL CheckBadWord(char * Word, char * Msg) +{ + char * ptr1 = Msg, * ptr2; + size_t len = strlen(Word); + + while (*ptr1) // Stop at end + { + ptr2 = strstr(ptr1, Word); + + if (ptr2 == NULL) + return FALSE; // OK + + // Only bad if it ia not part of a longer word + + if ((ptr2 == Msg) || !(isalpha(*(ptr2 - 1)))) // No alpha before + if (!(isalpha(*(ptr2 + len)))) // No alpha after + return TRUE; // Bad word + + // Keep searching + + ptr1 = ptr2 + len; + } + + return FALSE; // OK +} + +BOOL CheckBadWords(char * Msg) +{ + char * dupMsg = _strlwr(_strdup(Msg)); + int i; + + for (i = 1; i <= NumberofBadWords; i++) + { + if (CheckBadWord(BadWords[i], dupMsg)) + { + free(dupMsg); + return TRUE; // Bad + } + } + + free(dupMsg); + return FALSE; // OK + +} + +VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user) +{ + if (user->flags & F_Expert) + ExpandAndSendMessage(conn, ExpertWelcomeMsg, LOG_BBS); + else if (conn->NewUser) + ExpandAndSendMessage(conn, NewWelcomeMsg, LOG_BBS); + else + ExpandAndSendMessage(conn, WelcomeMsg, LOG_BBS); + + if (user->HomeBBS[0] == 0 && !DontNeedHomeBBS) + BBSputs(conn, "Please enter your Home BBS using the Home command.\rYou may also enter your QTH and ZIP/Postcode using qth and zip commands.\r"); + +// if (user->flags & F_Temp_B2_BBS) +// nodeprintf(conn, "%s CMS >\r", BBSName); +// else + SendPrompt(conn, user); +} + +VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user) +{ + if (user->Temp->ListSuspended) + return; // Dont send prompt if pausing a listing + + if (user->flags & F_Expert) + ExpandAndSendMessage(conn, ExpertPrompt, LOG_BBS); + else if (conn->NewUser) + ExpandAndSendMessage(conn, NewPrompt, LOG_BBS); + else + ExpandAndSendMessage(conn, Prompt, LOG_BBS); + +// if (user->flags & F_Expert) +// nodeprintf(conn, "%s\r", ExpertPrompt); +// else if (conn->NewUser) +// nodeprintf(conn, "%s\r", NewPrompt); +// else +// nodeprintf(conn, "%s\r", Prompt); +} + + + +VOID * _zalloc(size_t len) +{ + // ?? malloc and clear + + void * ptr; + + ptr=malloc(len); + memset(ptr, 0, len); + + return ptr; +} + +BOOL isAMPRMsg(char * Addr) +{ + // See if message is addressed to ampr.org and is either + // for us or we have SendAMPRDirect (ie don't need RMS or SMTP to send it) + + size_t toLen = strlen(Addr); + + if (_memicmp(&Addr[toLen - 8], "ampr.org", 8) == 0) + { + // message is for ampr.org + + char toCall[48]; + char * via; + + strcpy(toCall, _strupr(Addr)); + + via = strlop(toCall, '@'); + + if (_stricmp(via, AMPRDomain) == 0) + { + // message is for us. + + return TRUE; + } + + if (SendAMPRDirect) + { + // We want to send ampr mail direct to host. Queue to BBS AMPR + + if (FindAMPR()) + { + // We have bbs AMPR + + return TRUE; + } + } + } + return FALSE; +} + +struct UserInfo * FindAMPR() +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, "AMPR") == 0) + return bbs; + } + + return NULL; +} + +struct UserInfo * FindRMS() +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, "RMS") == 0) + return bbs; + } + + return NULL; +} + +struct UserInfo * FindBBS(char * Name) +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, Name) == 0) + return bbs; + } + + return NULL; +} + +int CountConnectionsOnPort(int CheckPort) +{ + int n, Count = 0; + CIRCUIT * conn; + int port, sesstype, paclen, maxframe, l4window; + char callsign[11]; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active) + { + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + if (port == CheckPort) + Count++; + } + } + + return Count; +} + + +BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type) +{ + char ** Calls; + + if (Type == 'B' && FilterWPBulls && _stricmp(To, "WP") == 0) + return TRUE; + + if (RejFrom && From) + { + Calls = RejFrom; + + while(Calls[0]) + { + if (_stricmp(Calls[0], From) == 0) + return TRUE; + + Calls++; + } + } + + if (RejTo && To) + { + Calls = RejTo; + + while(Calls[0]) + { + if (_stricmp(Calls[0], To) == 0) + return TRUE; + + Calls++; + } + } + + if (RejAt && ATBBS) + { + Calls = RejAt; + + while(Calls[0]) + { + if (_stricmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + + if (RejBID && BID) + { + Calls = RejBID; + + while(Calls[0]) + { + if (Calls[0][0] == '*') + { + if (stristr(BID, &Calls[0][1])) + return TRUE; + } + else + { + if (_stricmp(BID, Calls[0]) == 0) + return TRUE; + } + + Calls++; + } + } + return FALSE; // Ok to accept +} + +BOOL CheckValidCall(char * From) +{ + unsigned int i; + + if (DontCheckFromCall) + return TRUE; + + if (strcmp(From, "SYSOP") == 0 || strcmp(From, "SYSTEM") == 0 || + strcmp(From, "IMPORT") == 0 || strcmp(From, "SMTP:") == 0 || strcmp(From, "RMS:") == 0) + return TRUE; + + for (i = 1; i < strlen(From); i++) // skip first which may also be digit + { + if (isdigit(From[i])) + { + // Has a digit. Check Last is not digit + + if (isalpha(From[strlen(From) - 1])) + return TRUE; + } + } + + // No digit, return false + + return FALSE; +} + +BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID) +{ + char ** Calls; + + if (HoldFrom && From) + { + Calls = HoldFrom; + + while(Calls[0]) + { + if (_stricmp(Calls[0], From) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldTo && To) + { + Calls = HoldTo; + + while(Calls[0]) + { + if (_stricmp(Calls[0], To) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldAt && ATBBS) + { + Calls = HoldAt; + + while(Calls[0]) + { + if (_stricmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldBID && BID) + { + Calls = HoldBID; + + while(Calls[0]) + { + if (Calls[0][0] == '*') + { + if (stristr(BID, &Calls[0][1])) + return TRUE; + } + else + { + if (_stricmp(BID, Calls[0]) == 0) + return TRUE; + } + + Calls++; + } + } + return FALSE; // Ok to accept +} + +BOOL CheckifLocalRMSUser(char * FullTo) +{ + struct UserInfo * user = LookupCall(FullTo); + + if (user) + if (user->flags & F_POLLRMS) + return TRUE; + + return FALSE; + +} + + + +int check_fwd_bit(char *mask, int bbsnumber) +{ + if (bbsnumber) + return (mask[(bbsnumber - 1) / 8] & (1 << ((bbsnumber - 1) % 8))); + else + return 0; +} + + +void set_fwd_bit(char *mask, int bbsnumber) +{ + if (bbsnumber) + mask[(bbsnumber - 1) / 8] |= (1 << ((bbsnumber - 1) % 8)); +} + + +void clear_fwd_bit (char *mask, int bbsnumber) +{ + if (bbsnumber) + mask[(bbsnumber - 1) / 8] &= (~(1 << ((bbsnumber - 1) % 8))); +} + +VOID BBSputs(CIRCUIT * conn, char * buf) +{ + // Sends to user and logs + + WriteLogLine(conn, '>',buf, (int)strlen(buf) -1, LOG_BBS); + + QueueMsg(conn, buf, (int)strlen(buf)); +} + +VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...) +{ + char Mess[1000]; + int len; + va_list(arglist); + + + va_start(arglist, format); + len = vsprintf(Mess, format, arglist); + + QueueMsg(conn, Mess, len); + + WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); + + return; +} + +// nodeprintfEx add a LF if NEEFLF is set + +VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...) +{ + char Mess[1000]; + int len; + va_list(arglist); + + + va_start(arglist, format); + len = vsprintf(Mess, format, arglist); + + QueueMsg(conn, Mess, len); + + WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); + + if (conn->BBSFlags & NEEDLF) + QueueMsg(conn, "\r", 1); + + return; +} + + +int compare( const void *arg1, const void *arg2 ); + +VOID SortBBSChain() +{ + struct UserInfo * user; + struct UserInfo * users[161]; + int i = 0, n; + + // Get array of addresses + + for (user = BBSChain; user; user = user->BBSNext) + { + users[i++] = user; + if (i > 160) break; + } + + qsort((void *)users, i, sizeof(void *), compare ); + + BBSChain = NULL; + + // Rechain (backwards, as entries ate put on front of chain) + + for (n = i-1; n >= 0; n--) + { + users[n]->BBSNext = BBSChain; + BBSChain = users[n]; + } +} + +int compare(const void *arg1, const void *arg2) +{ + // Compare Calls. Fortunately call is at start of stuct + + return _stricmp(*(char**)arg1 , *(char**)arg2); +} + +int CountMessagesTo(struct UserInfo * user, int * Unread) +{ + int i, Msgs = 0; + UCHAR * Call = user->Call; + + *Unread = 0; + + for (i = NumberofMessages; i > 0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (_stricmp(MsgHddrPtr[i]->to, Call) == 0) + { + Msgs++; + if (MsgHddrPtr[i]->status == 'N') + *Unread = *Unread + 1; + } + } + return(Msgs); +} + + + +// Custimised message handling routines. +/* + Variables - a subset of those used by FBB + + $C : Number of the next message. + $I : First name of the connected user. + $L : Number of the latest message. + $N : Number of active messages + $U : Callsign of the connected user. + $W : Inserts a carriage return. + $Z : Last message read by the user (L command). + %X : Number of messages for the user. + %x : Number of new messages for the user. +*/ + +VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG) +{ + char NewMessage[10000]; + char * OldP = Msg; + char * NewP = NewMessage; + char * ptr, * pptr; + size_t len; + char Dollar[] = "$"; + char CR[] = "\r"; + char num[20]; + int Msgs = 0, Unread = 0; + + ptr = strchr(OldP, '$'); + + while (ptr) + { + len = ptr - OldP; // Chars before $ + memcpy(NewP, OldP, len); + NewP += len; + + switch (*++ptr) + { + case 'I': // First name of the connected user. + + pptr = conn->UserPointer->Name; + break; + + case 'L': // Number of the latest message. + + sprintf(num, "%d", LatestMsg); + pptr = num; + break; + + case 'N': // Number of active messages. + + sprintf(num, "%d", NumberofMessages); + pptr = num; + break; + + case 'U': // Callsign of the connected user. + + pptr = conn->UserPointer->Call; + break; + + case 'W': // Inserts a carriage return. + + pptr = CR; + break; + + case 'Z': // Last message read by the user (L command). + + sprintf(num, "%d", conn->UserPointer->lastmsg); + pptr = num; + break; + + case 'X': // Number of messages for the user. + + Msgs = CountMessagesTo(conn->UserPointer, &Unread); + sprintf(num, "%d", Msgs); + pptr = num; + break; + + case 'x': // Number of new messages for the user. + + Msgs = CountMessagesTo(conn->UserPointer, &Unread); + sprintf(num, "%d", Unread); + pptr = num; + break; + + case 'F': // Number of new messages to forward to this BBS. + + Msgs = CountMessagestoForward(conn->UserPointer); + sprintf(num, "%d", Msgs); + pptr = num; + break; + + default: + + pptr = Dollar; // Just Copy $ + } + + len = strlen(pptr); + memcpy(NewP, pptr, len); + NewP += len; + + OldP = ++ptr; + ptr = strchr(OldP, '$'); + } + + strcpy(NewP, OldP); + + len = RemoveLF(NewMessage, (int)strlen(NewMessage)); + + WriteLogLine(conn, '>', NewMessage, (int)len, LOG); + QueueMsg(conn, NewMessage, (int)len); +} + +BOOL isdigits(char * string) +{ + // Returns TRUE id sting is decimal digits + + size_t i, n = strlen(string); + + for (i = 0; i < n; i++) + { + if (isdigit(string[i]) == FALSE) return FALSE; + } + return TRUE; +} + +BOOL wildcardcompare(char * Target, char * Match) +{ + // Do a compare with string *string string* *string* + + // Strings should all be UC + + char Pattern[100]; + char * firststar; + + strcpy(Pattern, Match); + firststar = strchr(Pattern,'*'); + + if (firststar) + { + size_t Len = strlen(Pattern); + + if (Pattern[0] == '*' && Pattern[Len - 1] == '*') // * at start and end + { + Pattern[Len - 1] = 0; + return !(strstr(Target, &Pattern[1]) == NULL); + } + if (Pattern[0] == '*') // * at start + { + // Compare the last len - 1 chars of Target + + size_t Targlen = strlen(Target); + size_t Comparelen = Targlen - (Len - 1); + + if (Len == 1) // Just * + return TRUE; + + if (Comparelen < 0) // Too Short + return FALSE; + + return (memcmp(&Target[Comparelen], &Pattern[1], Len - 1) == 0); + } + + // Must be * at end - compare first Len-1 char + + return (memcmp(Target, Pattern, Len - 1) == 0); + } + + // No WildCards - straight strcmp + return (strcmp(Target, Pattern) == 0); +} + +#ifndef LINBPQ + +PrintMessage(HDC hDC, struct MsgInfo * Msg); + +PrintMessages(HWND hDlg, int Count, int * Indexes) +{ + int i, CurrentMsgIndex; + char MsgnoText[10]; + int Msgno; + struct MsgInfo * Msg; + int Len = MAX_PATH; + BOOL hResult; + PRINTDLG pdx = {0}; + HDC hDC; + +// CHOOSEFONT cf; + LOGFONT lf; + HFONT hFont; + + + // Initialize the PRINTDLG structure. + + pdx.lStructSize = sizeof(PRINTDLG); + pdx.hwndOwner = hWnd; + pdx.hDevMode = NULL; + pdx.hDevNames = NULL; + pdx.hDC = NULL; + pdx.Flags = PD_RETURNDC | PD_COLLATE; + pdx.nMinPage = 1; + pdx.nMaxPage = 1000; + pdx.nCopies = 1; + pdx.hInstance = 0; + pdx.lpPrintTemplateName = NULL; + + // Invoke the Print property sheet. + + hResult = PrintDlg(&pdx); + + memset(&lf, 0, sizeof(LOGFONT)); + + /* + + // Initialize members of the CHOOSEFONT structure. + + cf.lStructSize = sizeof(CHOOSEFONT); + cf.hwndOwner = (HWND)NULL; + cf.hDC = pdx.hDC; + cf.lpLogFont = &lf; + cf.iPointSize = 0; + cf.Flags = CF_PRINTERFONTS | CF_FIXEDPITCHONLY; + cf.rgbColors = RGB(0,0,0); + cf.lCustData = 0L; + cf.lpfnHook = (LPCFHOOKPROC)NULL; + cf.lpTemplateName = (LPSTR)NULL; + cf.hInstance = (HINSTANCE) NULL; + cf.lpszStyle = (LPSTR)NULL; + cf.nFontType = PRINTER_FONTTYPE; + cf.nSizeMin = 0; + cf.nSizeMax = 0; + + // Display the CHOOSEFONT common-dialog box. + + ChooseFont(&cf); + + // Create a logical font based on the user's + // selection and return a handle identifying + // that font. +*/ + + lf.lfHeight = -56; + lf.lfWeight = 600; + lf.lfOutPrecision = 3; + lf.lfClipPrecision = 2; + lf.lfQuality = 1; + lf.lfPitchAndFamily = '1'; + strcpy (lf.lfFaceName, "Courier New"); + + hFont = CreateFontIndirect(&lf); + + if (hResult) + { + // User clicked the Print button, so use the DC and other information returned in the + // PRINTDLG structure to print the document. + + DOCINFO pdi; + + pdi.cbSize = sizeof(DOCINFO); + pdi.lpszDocName = "BBS Message Print"; + pdi.lpszOutput = NULL; + pdi.lpszDatatype = "RAW"; + pdi.fwType = 0; + + hDC = pdx.hDC; + + SelectObject(hDC, hFont); + + StartDoc(hDC, &pdi); + StartPage(hDC); + + for (i = 0; i < Count; i++) + { + SendDlgItemMessage(hDlg, 0, LB_GETTEXT, Indexes[i], (LPARAM)(LPCTSTR)&MsgnoText); + + Msgno = atoi(MsgnoText); + + for (CurrentMsgIndex = 1; CurrentMsgIndex <= NumberofMessages; CurrentMsgIndex++) + { + Msg = MsgHddrPtr[CurrentMsgIndex]; + + if (Msg->number == Msgno) + { + PrintMessage(hDC, Msg); + break; + } + } + } + + EndDoc(hDC); + } + + if (pdx.hDevMode != NULL) + GlobalFree(pdx.hDevMode); + if (pdx.hDevNames != NULL) + GlobalFree(pdx.hDevNames); + + if (pdx.hDC != NULL) + DeleteDC(pdx.hDC); + + return 0; +} + +PrintMessage(HDC hDC, struct MsgInfo * Msg) +{ + int Len = MAX_PATH; + char * MsgBytes; + char * Save; + int Msglen; + + StartPage(hDC); + + Save = MsgBytes = ReadMessageFile(Msg->number); + + Msglen = Msg->length; + + if (MsgBytes) + { + char Hddr[1000]; + char FullTo[100]; + int HRes, VRes; + char * ptr1, * ptr2; + int LineLen; + + RECT Rect; + + if (_stricmp(Msg->to, "RMS") == 0) + sprintf(FullTo, "RMS:%s", Msg->via); + else + if (Msg->to[0] == 0) + sprintf(FullTo, "smtp:%s", Msg->via); + else + strcpy(FullTo, Msg->to); + + + sprintf(Hddr, "From: %s%s\r\nTo: %s\r\nType/Status: %c%c\r\nDate/Time: %s\r\nBid: %s\r\nTitle: %s\r\n\r\n", + Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); + + + if (Msg->B2Flags & B2Msg) + { + // Remove B2 Headers (up to the File: Line) + + char * ptr; + ptr = strstr(MsgBytes, "Body:"); + if (ptr) + { + Msglen = atoi(ptr + 5); + ptr = strstr(ptr, "\r\n\r\n"); + } + if (ptr) + MsgBytes = ptr + 4; + } + + HRes = GetDeviceCaps(hDC, HORZRES) - 50; + VRes = GetDeviceCaps(hDC, VERTRES) - 50; + + Rect.top = 50; + Rect.left = 50; + Rect.right = HRes; + Rect.bottom = VRes; + + DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_CALCRECT | DT_WORDBREAK); + DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_WORDBREAK); + + // process message a line at a time. When page is full, output a page break + + ptr1 = MsgBytes; + ptr2 = ptr1; + + while (Msglen-- > 0) + { + if (*ptr1++ == '\r') + { + // Output this line + + // First check if it will fit + + Rect.top = Rect.bottom; + Rect.right = HRes; + Rect.bottom = VRes; + + LineLen = ptr1 - ptr2 - 1; + + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); + + if (Rect.bottom >= VRes) + { + EndPage(hDC); + StartPage(hDC); + + Rect.top = 50; + Rect.bottom = VRes; + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); + } + + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_WORDBREAK); + + if (*(ptr1) == '\n') + { + ptr1++; + Msglen--; + } + + ptr2 = ptr1; + } + } + + free(Save); + + EndPage(hDC); + + } + return 0; +} + +#endif + + +int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup) +{ + char FileName[MAX_PATH] = "Messages.in"; + int Files = 0; + int WriteLen=0; + FILE *in; + CIRCUIT dummyconn; + struct UserInfo User; + int Index = 0; + + char Buffer[100000]; + char *buf = Buffer; + + if (FN[0]) // Name supplled + strcpy(FileName, FN); + + else + { +#ifndef LINBPQ + OPENFILENAME Ofn; + + memset(&Ofn, 0, sizeof(Ofn)); + + Ofn.lStructSize = sizeof(OPENFILENAME); + Ofn.hInstance = hInst; + Ofn.hwndOwner = MainWnd; + Ofn.lpstrFilter = NULL; + Ofn.lpstrFile= FileName; + Ofn.nMaxFile = sizeof(FileName)/ sizeof(*FileName); + Ofn.lpstrFileTitle = NULL; + Ofn.nMaxFileTitle = 0; + Ofn.lpstrInitialDir = BaseDir; + Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + Ofn.lpstrTitle = NULL;//; + + if (!GetOpenFileName(&Ofn)) + return 0; +#endif + } + + in = fopen(FileName, "rb"); + + if (!(in)) + { + char msg[500]; + sprintf_s(msg, sizeof(msg), "Failed to open %s", FileName); + if (conn) + nodeprintf(conn, "%s\r", msg); +#ifdef WIN32 + else + if (Nopopup == FALSE) + MessageBox(NULL, msg, "BPQMailChat", MB_OK); +#endif + return 0; + } + + memset(&dummyconn, 0, sizeof(CIRCUIT)); + memset(&User, 0, sizeof(struct UserInfo)); + + if (conn == 0) + { + conn = &dummyconn; + + dummyconn.UserPointer = &User; // Was SYSOPCall, but I think that is wrong. + strcpy(User.Call, "IMPORT"); + User.flags |= F_EMAIL; + dummyconn.sysop = TRUE; + dummyconn.BBSFlags = BBS; + + strcpy(dummyconn.Callsign, "IMPORT"); + } + + while(fgets(Buffer, 99999, in)) + { + // First line should start SP/SB ?ST? + + char * From = NULL; + char * BID = NULL; + char * ATBBS = NULL; + char seps[] = " \t\r"; + struct MsgInfo * Msg; + char To[100]= ""; + int msglen; + char * Context; + char * Arg1, * Cmd; + +NextMessage: + + From = NULL; + BID = NULL; + ATBBS = NULL; + To[0]= 0; + + Sleep(100); + + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + + if (Buffer[0] == 0) //Blank Line + continue; + + WriteLogLine(conn, '>', Buffer, (int)strlen(Buffer), LOG_BBS); + + if (dummyconn.sysop == 0) + { + nodeprintf(conn, "%s\r", Buffer); + Flush(conn); + } + + Cmd = strtok_s(Buffer, seps, &Context); + + if (Cmd == NULL) + { + fclose(in); + return Files; + } + + Arg1 = strtok_s(NULL, seps, &Context); + + if (Arg1 == NULL) + { + if (dummyconn.sysop) + Debugprintf("Bad Import Line %s", Buffer); + else + nodeprintf(conn, "Bad Import Line %s\r", Buffer); + + fclose(in); + return Files; + } + + strcpy(To, Arg1); + + if (DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + { + if (CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL)) + { + Msg = conn->TempMsg; + + // SP is Ok, read message; + + ClearQueue(conn); + + fgets(Buffer, 99999, in); + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + if (strlen(Buffer) > 60) + Buffer[60] = 0; + + strcpy(Msg->title, Buffer); + + // Read the lines + + conn->Flags |= GETTINGMESSAGE; + + Buffer[0] = 0; + + fgets(Buffer, 99999, in); + + while ((conn->Flags & GETTINGMESSAGE) && Buffer[0]) + { + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + msglen = (int)strlen(Buffer); + Buffer[msglen++] = 13; + ProcessMsgLine(conn, conn->UserPointer,Buffer, msglen); + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + } + + // Message completed (or off end of file) + + Files ++; + + ClearQueue(conn); + + if (Buffer[0]) + goto NextMessage; // We have read the SP/SB line; + else + { + fclose(in); + return Files; + } + } + else + { + // Create failed + + Flush(conn); + } + } + + // Search for next message + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + + while (Buffer[0]) + { + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + + if (_stricmp(Buffer, "/EX") == 0) + { + // Found end + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + + if (dummyconn.sysop) + ClearQueue(conn); + else + Flush(conn); + + if (Buffer[0]) + goto NextMessage; // We have read the SP/SB line; + } + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + } + } + + fclose(in); + + if (dummyconn.sysop) + ClearQueue(conn); + else + Flush(conn); + + return Files; +} +char * ReadMessageFileEx(struct MsgInfo * MsgRec) +{ + // Sets Message Size from File Size + + int msgno = MsgRec->number; + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes=malloc(FileSize+1); + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize]=0; + MsgRec->length = FileSize; + + return MsgBytes; +} + +char * ReadMessageFile(int msgno) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes = malloc(FileSize + 100); // A bit of space for alias substitution on B2 + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize]=0; + + return MsgBytes; +} + + +int QueueMsg(ConnectionInfo * conn, char * msg, int len) +{ + // Add Message to queue for this connection + + // UCHAR * OutputQueue; // Messages to user + // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. + + // Create or extend buffer + + GetSemaphore(&OutputSEM, 0); + + conn->OutputQueue=realloc(conn->OutputQueue, conn->OutputQueueLength + len); + + if (conn->OutputQueue == NULL) + { + // relloc failed - should never happen, but clean up + + CriticalErrorHandler("realloc failed to expand output queue"); + FreeSemaphore(&OutputSEM); + return 0; + } + + memcpy(&conn->OutputQueue[conn->OutputQueueLength], msg, len); + conn->OutputQueueLength += len; + FreeSemaphore(&OutputSEM); + + return len; +} + +void TrytoSend() +{ + // call Flush on any connected streams with queued data + + ConnectionInfo * conn; + struct ConsoleInfo * Cons; + + int n; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active == TRUE) + { + Flush(conn); + + // if an FLARQ mail has been sent see if queues have cleared + + if (conn->BBSFlags & YAPPTX) + { + YAPPSendData(conn); + } + else if (conn->OutputQueue == NULL && (conn->BBSFlags & ARQMAILACK)) + { + int n = TXCount(conn->BPQStream); // All Sent and Acked? + + if (n == 0) + { + struct MsgInfo * Msg = conn->FwdMsg; + + conn->ARQClearCount--; + + if (conn->ARQClearCount <= 0) + { + Logprintf(LOG_BBS, conn, '>', "ARQ Send Complete"); + + // Mark mail as sent, and look for more + + clear_fwd_bit(Msg->fbbs, conn->UserPointer->BBSNumber); + set_fwd_bit(Msg->forw, conn->UserPointer->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(Msg->fbbs, zeros, NBMASK) == 0) + { + Msg->status = 'F'; // Mark as forwarded + Msg->datechanged=time(NULL); + } + + conn->BBSFlags &= ~ARQMAILACK; + conn->UserPointer->ForwardingInfo->MsgCount--; + + SaveMessageDatabase(); + SendARQMail(conn); // See if any more - close if not + } + } + else + conn->ARQClearCount = 10; + } + } + } +#ifndef LINBPQ + for (Cons = ConsHeader[0]; Cons; Cons = Cons->next) + { + if (Cons->Console) + Flush(Cons->Console); + } +#endif +} + + +void Flush(CIRCUIT * conn) +{ + int tosend, len, sent; + + // Try to send data to user. May be stopped by user paging or node flow control + + // UCHAR * OutputQueue; // Messages to user + // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. + + // BOOL Paging; // Set if user wants paging + // int LinesSent; // Count when paging + // int PageLen; // Lines per page + + + if (conn->OutputQueue == NULL) + { + // Nothing to send. If Close after Flush is set, disconnect + + if (conn->CloseAfterFlush) + { + conn->CloseAfterFlush--; + + if (conn->CloseAfterFlush) + return; + + Disconnect(conn->BPQStream); + conn->ErrorCount = 0; + } + + return; // Nothing to send + } + tosend = conn->OutputQueueLength - conn->OutputGetPointer; + + sent=0; + + while (tosend > 0) + { + if (TXCount(conn->BPQStream) > 15) + return; // Busy + + if (conn->BBSFlags & SYSOPCHAT) // Suspend queued output while sysop chatting + return; + + if (conn->Paging && (conn->LinesSent >= conn->PageLen)) + return; + + if (tosend <= conn->paclen) + len=tosend; + else + len=conn->paclen; + + GetSemaphore(&OutputSEM, 0); + + if (conn->Paging) + { + // look for CR chars in message to send. Increment LinesSent, and stop if at limit + + UCHAR * ptr1 = &conn->OutputQueue[conn->OutputGetPointer]; + UCHAR * ptr2; + int lenleft = len; + + ptr2 = memchr(ptr1, 0x0d, len); + + while (ptr2) + { + conn->LinesSent++; + ptr2++; + lenleft = len - (int)(ptr2 - ptr1); + + if (conn->LinesSent >= conn->PageLen) + { + len = (int)(ptr2 - &conn->OutputQueue[conn->OutputGetPointer]); + + SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); + conn->OutputGetPointer+=len; + tosend-=len; + SendUnbuffered(conn->BPQStream, "bort, Continue..>", 25); + FreeSemaphore(&OutputSEM); + return; + + } + ptr2 = memchr(ptr2, 0x0d, lenleft); + } + } + + SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); + + conn->OutputGetPointer+=len; + + FreeSemaphore(&OutputSEM); + + tosend-=len; + sent++; + + if (sent > 15) + return; + } + + // All Sent. Free buffers and reset pointers + + conn->LinesSent = 0; + + ClearQueue(conn); +} + +VOID ClearQueue(ConnectionInfo * conn) +{ + if (conn->OutputQueue == NULL) + return; + + GetSemaphore(&OutputSEM, 0); + + free(conn->OutputQueue); + + conn->OutputQueue=NULL; + conn->OutputGetPointer=0; + conn->OutputQueueLength=0; + + FreeSemaphore(&OutputSEM); +} + + + +VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB) +{ + struct UserInfo * user; + + Msg->status='K'; + Msg->datechanged=time(NULL); + + // Remove any forwarding references + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + for (user = BBSChain; user; user = user->BBSNext) + { + if (check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + user->ForwardingInfo->MsgCount--; + clear_fwd_bit(Msg->fbbs, user->BBSNumber); + } + } + } + if (SaveDB) + SaveMessageDatabase(); + RebuildNNTPList(); +} + +void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + struct MsgInfo * Msg; + + while (Arg1) + { + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + goto Next; + } + + if (Msg->type != 'T') + { + nodeprintf(conn, "Message %d not an NTS Message\r", msgno); + goto Next; + } + + if (Msg->status == 'N') + nodeprintf(conn, "Warning - Message has status N\r"); + + Msg->status = 'D'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + + nodeprintf(conn, "Message #%d Flagged as Delivered\r", msgno); + Next: + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; +} + +void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + // Param is either ALL or a list of numbers + + if (Arg1 == NULL) + { + nodeprintf(conn, "No message number\r"); + return; + } + + if (_stricmp(Arg1, "ALL") == 0) + { + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if (Msg->status == 'H') + { + if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + else + Msg->status = 'N'; + + nodeprintf(conn, "Message #%d Unheld\r", Msg->number); + } + } + return; + } + + while (Arg1) + { + msgno = atoi(Arg1); + Msg = GetMsgFromNumber(msgno); + + if (Msg) + { + if (Msg->status == 'H') + { + if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + else + Msg->status = 'N'; + + nodeprintf(conn, "Message #%d Unheld\r", msgno); + } + else + { + nodeprintf(conn, "Message #%d was not held\r", msgno); + } + } + else + nodeprintf(conn, "Message #%d not found\r", msgno); + + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; +} + +void DoKillCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + switch (toupper(Cmd[1])) + { + + case 0: // Just K + + while (Arg1) + { + msgno = atoi(Arg1); + KillMessage(conn, user, msgno); + + Arg1 = strtok_s(NULL, " \r", &Context); + } + + SaveMessageDatabase(); + return; + + case 'M': // Kill Mine + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + { + if (Msg->type == 'P' && Msg->status == 'Y') + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", Msg->number); + } + } + } + + SaveMessageDatabase(); + return; + + case 'H': // Kill Held + + if (conn->sysop) + { + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if (Msg->status == 'H') + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", Msg->number); + } + } + } + SaveMessageDatabase(); + return; + + case '>': // K> - Kill to + + if (conn->sysop) + { + if (Arg1) + if (KillMessagesTo(conn, user, Arg1) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + } + + case '<': + + if (conn->sysop) + { + if (Arg1) + if (KillMessagesFrom(conn, user, Arg1) == 0); + BBSputs(conn, "No Messages found\r"); + + return; + } + } + + nodeprintf(conn, "*** Error: Invalid Kill option %c\r", Cmd[1]); + + return; + +} + +int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call) +{ + int i, Msgs = 0; + struct MsgInfo * Msg; + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + if (Msg->status != 'K' && _stricmp(Msg->to, Call) == 0) + { + Msgs++; + KillMessage(conn, user, MsgHddrPtr[i]->number); + } + } + + SaveMessageDatabase(); + return(Msgs); +} + +int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call) +{ + int i, Msgs = 0; + struct MsgInfo * Msg; + + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + if (Msg->status != 'K' && _stricmp(Msg->from, Call) == 0) + { + Msgs++; + KillMessage(conn, user, MsgHddrPtr[i]->number); + } + } + + SaveMessageDatabase(); + return(Msgs); +} + +BOOL OkToKillMessage(BOOL SYSOP, char * Call, struct MsgInfo * Msg) +{ + if (SYSOP || (Msg->type == 'T' && UserCantKillT == FALSE)) + return TRUE; + + if (Msg->type == 'P') + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return TRUE; + + if (Msg->type == 'B') + if (_stricmp(Msg->from, Call) == 0) + return TRUE; + + return FALSE; +} + +void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) +{ + struct MsgInfo * Msg; + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL || Msg->status == 'K') + { + nodeprintf(conn, "Message %d not found\r", msgno); + return; + } + + if (OkToKillMessage(conn->sysop, user->Call, Msg)) + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", msgno); + } + else + nodeprintf(conn, "Not your message\r"); +} + + +BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp) +{ + char FullFrom[80]; + char FullTo[80]; + + strcpy(FullFrom, Msg->from); + + if ((_stricmp(Msg->from, "RMS:") == 0) || (_stricmp(Msg->from, "SMTP:") == 0) || + Temp->SendFullFrom || (_stricmp(Msg->emailfrom, "@winlink.org") == 0)) + strcat(FullFrom, Msg->emailfrom); + + if (_stricmp(Msg->to, "RMS") == 0) + { + sprintf(FullTo, "RMS:%s", Msg->via); + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); + } + else + + if (Msg->to[0] == 0 && Msg->via[0] != 0) + { + sprintf(FullTo, "smtp:%s", Msg->via); + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); + } + + else + if (Msg->via[0] != 0) + { + char Via[80]; + strcpy(Via, Msg->via); + strlop(Via, '.'); // Only show first part of via + nodeprintf(conn, "%-6d %s %c%c %5d %-7s@%-6s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, Via, FullFrom, Msg->title); + } + else + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, FullFrom, Msg->title); + + // if paging, stop two before page lengh. This lets us send the continue prompt, save status + // and exit without triggering the system paging code. We can then read a message then resume listing + + if (Temp->ListActive && conn->Paging) + { + Temp->LinesSent++; + + if ((Temp->LinesSent + 1) >= conn->PageLen) + { + nodeprintf(conn, "bort, , = Continue..>"); + Temp->LastListedInPagedMode = Msg->number; + Temp->ListSuspended = TRUE; + return TRUE; + } + } + + return FALSE; +} + +void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context) +{ + struct TempUserInfo * Temp = user->Temp; + struct MsgInfo * Msg; + + // Allow compound selection, eg LTN or LFP + + // types P N T + // Options LL LR L< L> L@ LM LC (L* used internally for just L, ie List New + // Status N Y H F K D + + // Allowing options in any order complicates paging. May be best to parse options once and restore if paging. + + Temp->ListActive = TRUE; + Temp->LinesSent = 0; + + if (Resuming) + { + // Entered after a paging pause. Selection fields are already set up + + // We have reentered list command after a pause. The next message to list is in Temp->LastListedInPagedMode + +// Start = Temp->LastListedInPagedMode; + Temp->ListSuspended = FALSE; + } + else + { + Temp->ListRangeEnd = LatestMsg; + Temp->ListRangeStart = 1; + Temp->LLCount = 0; + Temp->SendFullFrom = 0; + Temp->ListType = 0; + Temp->ListStatus = 0; + Temp->ListSelector = 0; + Temp->UpdateLatest = 0; + Temp->LastListParams[0] = 0; + Temp->IncludeKilled = 1; // SYSOP include Killed except LM + + //Analyse L params. + + _strupr(Cmd); + + if (strcmp(Cmd, "LC") == 0) // List Bull Categories + { + ListCategories(conn); + return; + } + + // if command is just L or LR start from last listed + + if (Arg1 == NULL) + { + if (strcmp(Cmd, "L") == 0 || strcmp(Cmd, "LR") == 0) + { + if (LatestMsg == conn->lastmsg) + { + BBSputs(conn, "No New Messages\r"); + return; + } + + Temp->UpdateLatest = 1; + Temp->ListRangeStart = conn->lastmsg; + } + } + + if (strchr(Cmd, 'V')) // Verbose + Temp->SendFullFrom = 'V'; + + if (strchr(Cmd, 'R')) + Temp->ListDirn = 'R'; + else + Temp->ListDirn = '*'; // Default newest first + + Cmd++; // skip L + + if (strchr(Cmd, 'T')) + Temp->ListType = 'T'; + else if (strchr(Cmd, 'P')) + Temp->ListType = 'P'; + else if (strchr(Cmd, 'B')) + Temp->ListType = 'B'; + + if (strchr(Cmd, 'N')) + Temp->ListStatus = 'N'; + else if (strchr(Cmd, 'Y')) + Temp->ListStatus = 'Y'; + else if (strchr(Cmd, 'F')) + Temp->ListStatus = 'F'; + else if (strchr(Cmd, '$')) + Temp->ListStatus = '$'; + else if (strchr(Cmd, 'H')) + Temp->ListStatus = 'H'; + else if (strchr(Cmd, 'K')) + Temp->ListStatus = 'K'; + else if (strchr(Cmd, 'D')) + Temp->ListStatus = 'D'; + + // H or K only by Sysop + + switch (Temp->ListStatus) + { + case 'K': + case 'H': // List Status + + if (conn->sysop) + break; + + BBSputs(conn, "LH or LK can only be used by SYSOP\r"); + return; + } + + if (strchr(Cmd, '<')) + Temp->ListSelector = '<'; + else if (strchr(Cmd, '>')) + Temp->ListSelector = '>'; + else if (strchr(Cmd, '@')) + Temp->ListSelector = '@'; + else if (strchr(Cmd, 'M')) + { + Temp->ListSelector = 'M'; + Temp->IncludeKilled = FALSE; + } + + // Param could be single number, number range or call + + if (Arg1) + { + if (strchr(Cmd, 'L')) // List Last + { + // Param is number + + if (Arg1) + Temp->LLCount = atoi(Arg1); + } + else + { + // Range nnn-nnn or single value or callsign + + char * Arg2, * Arg3, * Range; + char seps[] = " \t\r"; + UINT From=LatestMsg, To=0; + + Arg2 = strtok_s(NULL, seps, &Context); + Arg3 = strtok_s(NULL, seps, &Context); + + if (Temp->ListSelector && Temp->ListSelector != 'M') + { + // < > or @ - first param is callsign + + strcpy(Temp->LastListParams, Arg1); + + // Just possible number range + + Arg1 = Arg2; + Arg2 = Arg3; + Arg3 = strtok_s(NULL, seps, &Context); + } + + if (Arg1) + { + Range = strchr(Arg1, '-'); + + // A number could be a Numeric Bull Dest (eg 44) + // I think this can only resaonably be > + + if (isdigits(Arg1)) + To = From = atoi(Arg1); + + if (Arg2) + From = atoi(Arg2); + else + { + if (Range) + { + Arg3 = strlop(Arg1, '-'); + + To = atoi(Arg1); + + if (Arg3 && Arg3[0]) + From = atoi(Arg3); + else + From = LatestMsg; + } + } + if (From > 100000 || To > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + Temp->ListRangeStart = To; + Temp->ListRangeEnd = From; + } + } + } + } + + // Run through all messages (either forwards or backwards) and list any that match all selection criteria + + while (1) + { + if (Temp->ListDirn == 'R') + Msg = GetMsgFromNumber(Temp->ListRangeStart); + else + Msg = GetMsgFromNumber(Temp->ListRangeEnd); + + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop, Temp->IncludeKilled)) // Check if user is allowed to list this message + { + // Check filters + + if (Temp->ListStatus && Temp->ListStatus != Msg->status) + goto skip; + + if (Temp->ListType && Temp->ListType != Msg->type) + goto skip; + + if (Temp->ListSelector == '<') + if (_stricmp(Msg->from, Temp->LastListParams) != 0) + goto skip; + + if (Temp->ListSelector == '>') + if (_stricmp(Msg->to, Temp->LastListParams) != 0) + goto skip; + + if (Temp->ListSelector == '@') + if (_memicmp(Msg->via, Temp->LastListParams, strlen(Temp->LastListParams)) != 0 && + (_stricmp(Temp->LastListParams, "SMTP:") != 0 || Msg->to[0] != 0)) + goto skip; + + if (Temp->ListSelector == 'M') + if (_stricmp(Msg->to, user->Call) != 0 && + (_stricmp(Msg->to, "SYSOP") != 0 || ((user->flags & F_SYSOP_IN_LM) == 0))) + + goto skip; + + if (ListMessage(Msg, conn, Temp)) + { + if (Temp->ListDirn == 'R') + Temp->ListRangeStart++; + else + Temp->ListRangeEnd--; + + return; // Hit page limit + } + + if (Temp->LLCount) + { + Temp->LLCount--; + if (Temp->LLCount == 0) + return; // LL count reached + } +skip:; + } + + if (Temp->ListRangeStart == Temp->ListRangeEnd) + { + // if using L or LR (list new) update last listed field + + if (Temp->UpdateLatest) + conn->lastmsg = LatestMsg; + + return; + } + + if (Temp->ListDirn == 'R') + Temp->ListRangeStart++; + else + Temp->ListRangeEnd--; + + if (Temp->ListRangeStart > 100000 || Temp->ListRangeEnd < 0) // Loop protection! + return; + + } + +/* + + switch (Cmd[0]) + { + + case '*': // Just L + case 'R': // LR = List Reverse + + if (Arg1) + { + // Range nnn-nnn or single value + + char * Arg2, * Arg3; + char * Context; + char seps[] = " -\t\r"; + UINT From=LatestMsg, To=0; + char * Range = strchr(Arg1, '-'); + + Arg2 = strtok_s(Arg1, seps, &Context); + Arg3 = strtok_s(NULL, seps, &Context); + + if (Arg2) + To = From = atoi(Arg2); + + if (Arg3) + From = atoi(Arg3); + else + if (Range) + From = LatestMsg; + + if (From > 100000 || To > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + if (Cmd[1] == 'R') + { + if (Start) + To = Start + 1; + + ListMessagesInRangeForwards(conn, user, user->Call, From, To, Temp->SendFullFrom); + } + else + { + if (Start) + From = Start - 1; + + ListMessagesInRange(conn, user, user->Call, From, To, Temp->SendFullFrom); + } + } + else + + if (LatestMsg == conn->lastmsg) + BBSputs(conn, "No New Messages\r"); + else if (Cmd[1] == 'R') + ListMessagesInRangeForwards(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); + else + ListMessagesInRange(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); + + conn->lastmsg = LatestMsg; + + return; + + + case 'L': // List Last + + if (Arg1) + { + int i = atoi(Arg1); + int m = NumberofMessages; + + if (Resuming) + i = Temp->LLCount; + else + Temp->LLCount = i; + + for (; i>0 && m != 0; i--) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + i++; + continue; + } + + Temp->LLCount--; + + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + m--; + } + } + } + return; + + case 'M': // LM - List Mine + + if (ListMessagesTo(conn, user, user->Call, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + return; + + case '>': // L> - List to + + if (Arg1) + if (ListMessagesTo(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + + return; + + case '<': + + if (Arg1) + if (ListMessagesFrom(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + + case '@': + + if (Arg1) + if (ListMessagesAT(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + + case 'N': + case 'Y': + case 'F': + case '$': + case 'D': // Delivered NTS Traffic can be listed by anyone + { + int m = NumberofMessages; + + while (m > 0) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + continue; + } + + if (Temp->ListType) + { + if (MsgHddrPtr[m]->status == Cmd[1] && MsgHddrPtr[m]->type == Temp->ListType) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + } + else + { + if (MsgHddrPtr[m]->status == toupper(Cmd[1])) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + } + m--; + } + } + } + return; + + case 'K': + case 'H': // List Status + + if (conn->sysop) + { + int i, Msgs = Start; + + for (i=NumberofMessages; i>0; i--) + { + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (MsgHddrPtr[i]->status == toupper(Cmd[1])) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, SendFullFrom)) + return; // Hit page limit + + } + } + + if (Msgs == 0) + BBSputs(conn, "No Messages found\r"); + } + else + BBSputs(conn, "LH or LK can only be used by SYSOP\r"); + + return; + + case 'C': + { + struct NNTPRec * ptr = FirstNNTPRec; + char Cat[100]; + char NextCat[100]; + int Line = 0; + int Count; + + while (ptr) + { + // if the next name is the same, combine counts + + strcpy(Cat, ptr->NewsGroup); + strlop(Cat, '.'); + Count = ptr->Count; + Catloop: + if (ptr->Next) + { + strcpy(NextCat, ptr->Next->NewsGroup); + strlop(NextCat, '.'); + if (strcmp(Cat, NextCat) == 0) + { + ptr = ptr->Next; + Count += ptr->Count; + goto Catloop; + } + } + + nodeprintf(conn, "%-6s %-3d", Cat, Count); + Line += 10; + if (Line > 80) + { + Line = 0; + nodeprintf(conn, "\r"); + } + + ptr = ptr->Next; + } + + if (Line) + nodeprintf(conn, "\r\r"); + else + nodeprintf(conn, "\r"); + + return; + } + } + + // Could be P B or T if specified without a status + + switch (Temp->ListType) + { + case 'P': + case 'B': + case 'T': // NTS Traffic can be listed by anyone + { + int m = NumberofMessages; + + while (m > 0) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + continue; + } + + if (MsgHddrPtr[m]->type == Temp->ListType) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + m--; + } + } + + return; + } + } + +*/ + nodeprintf(conn, "*** Error: Invalid List option %c\r", Cmd[1]); + +} + +void ListCategories(ConnectionInfo * conn) +{ + // list bull categories + struct NNTPRec * ptr = FirstNNTPRec; + char Cat[100]; + char NextCat[100]; + int Line = 0; + int Count; + + while (ptr) + { + // if the next name is the same, combine counts + + strcpy(Cat, ptr->NewsGroup); + strlop(Cat, '.'); + Count = ptr->Count; +Catloop: + if (ptr->Next) + { + strcpy(NextCat, ptr->Next->NewsGroup); + strlop(NextCat, '.'); + if (strcmp(Cat, NextCat) == 0) + { + ptr = ptr->Next; + Count += ptr->Count; + goto Catloop; + } + } + + nodeprintf(conn, "%-6s %-3d", Cat, Count); + Line += 10; + if (Line > 80) + { + Line = 0; + nodeprintf(conn, "\r"); + } + + ptr = ptr->Next; + } + + if (Line) + nodeprintf(conn, "\r\r"); + else + nodeprintf(conn, "\r"); + + return; +} + +/* +int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) +{ + int i, Msgs = Start; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if ((_stricmp(MsgHddrPtr[i]->to, Call) == 0) || + ((conn->sysop) && _stricmp(Call, SYSOPCall) == 0 && + _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0 && (user->flags & F_SYSOP_IN_LM))) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + break; // Hit page limit + } + } + + return(Msgs); +} + +int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) +{ + int i, Msgs = 0; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (_stricmp(MsgHddrPtr[i]->from, Call) == 0) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + return Msgs; // Hit page limit + + } + } + + return(Msgs); +} + +int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom,int Start) +{ + int i, Msgs = 0; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (_memicmp(MsgHddrPtr[i]->via, Call, strlen(Call)) == 0 || + (_stricmp(Call, "SMTP:") == 0 && MsgHddrPtr[i]->to[0] == 0)) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + break; // Hit page limit + } + } + + return(Msgs); +} +*/ +int GetUserMsg(int m, char * Call, BOOL SYSOP) +{ + struct MsgInfo * Msg; + + // Get Next (usually backwards) message which should be shown to this user + // ie Not Deleted, and not Private unless to or from Call + + do + { + Msg=MsgHddrPtr[m]; + + if (SYSOP) return m; // Sysop can list or read anything + + if (Msg->status != 'K') + { + + if (Msg->status != 'H') + { + if (Msg->type == 'B' || Msg->type == 'T') return m; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return m; + } + } + } + + m--; + + } while (m> 0); + + return 0; +} + + +BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled) +{ + // Return TRUE if user is allowed to read message + + if (Msg->status == 'K' && IncludeKilled == 0) + return FALSE; + + if (SYSOP) + return TRUE; // Sysop can list or read anything + + if ((Msg->status != 'K') && (Msg->status != 'H')) + { + if (Msg->type == 'B' || Msg->type == 'T') return TRUE; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return TRUE; + } + } + + return FALSE; +} +/* +int GetUserMsgForwards(int m, char * Call, BOOL SYSOP) +{ + struct MsgInfo * Msg; + + // Get Next (usually backwards) message which should be shown to this user + // ie Not Deleted, and not Private unless to or from Call + + do + { + Msg=MsgHddrPtr[m]; + + if (Msg->status != 'K') + { + if (SYSOP) return m; // Sysop can list or read anything + + if (Msg->status != 'H') + { + if (Msg->type == 'B' || Msg->type == 'T') return m; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return m; + } + } + } + + m++; + + } while (m <= NumberofMessages); + + return 0; + +} + + +void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom) +{ + int m; + struct MsgInfo * Msg; + + for (m = Start; m >= End; m--) + { + Msg = GetMsgFromNumber(m); + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) + if (ListMessage(Msg, conn, Temp->SendFullFrom)) + return; // Hit page limit + + } +} + + +void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int End, int Start, BOOL SendFullFrom) +{ + int m; + struct MsgInfo * Msg; + + for (m = Start; m <= End; m++) + { + Msg = GetMsgFromNumber(m); + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) + if (ListMessage(Msg, conn, Temp->SendFullFrom)) + return; // Hit page limit + } +} +*/ + +void DoReadCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + + switch (toupper(Cmd[1])) + { + case 0: // Just R + + while (Arg1) + { + msgno = atoi(Arg1); + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + ReadMessage(conn, user, msgno); + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; + + case 'M': // Read Mine (Unread Messages) + + if (toupper(Cmd[2]) == 'R') + { + for (i = 1; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + if (Msg->status == 'N') + ReadMessage(conn, user, Msg->number); + } + } + else + { + for (i = NumberofMessages; i > 0; i--) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + if (Msg->status == 'N') + ReadMessage(conn, user, Msg->number); + } + } + + return; + } + + nodeprintf(conn, "*** Error: Invalid Read option %c\r", Cmd[1]); + + return; +} + +int RemoveLF(char * Message, int len) +{ + // Remove lf chars and nulls + + char * ptr1, * ptr2; + + ptr1 = ptr2 = Message; + + while (len-- > 0) + { + while (*ptr1 == 0 && len) + { + ptr1++; + len--; + } + + *ptr2 = *ptr1; + + if (*ptr1 == '\r') + if (*(ptr1+1) == '\n') + { + ptr1++; + len--; + } + ptr1++; + ptr2++; + } + + return (int)(ptr2 - Message); +} + + + +int RemoveNulls(char * Message, int len) +{ + // Remove nulls + + char * ptr1, * ptr2; + + ptr1 = ptr2 = Message; + + while (len-- > 0) + { + while (*ptr1 == 0 && len) + { + ptr1++; + len--; + } + + *ptr2 = *ptr1; + + ptr1++; + ptr2++; + } + + return (int)(ptr2 - Message); +} + +void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) +{ + struct MsgInfo * Msg; + char * MsgBytes, * Save; + char FullTo[100]; + int Index = 0; + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return; + } + + if (!CheckUserMsg(Msg, user->Call, conn->sysop, TRUE)) + { + nodeprintf(conn, "Message %d not for you\r", msgno); + return; + } + + if (_stricmp(Msg->to, "RMS") == 0) + sprintf(FullTo, "RMS:%s", Msg->via); + else + if (Msg->to[0] == 0) + sprintf(FullTo, "smtp:%s", Msg->via); + else + strcpy(FullTo, Msg->to); + + + nodeprintf(conn, "From: %s%s\rTo: %s\rType/Status: %c%c\rDate/Time: %s\rBid: %s\rTitle: %s\r\r", + Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); + + MsgBytes = Save = ReadMessageFile(msgno); + + if (Msg->type == 'P') + Index = PMSG; + else if (Msg->type == 'B') + Index = BMSG; + else if (Msg->type == 'T') + Index = TMSG; + + if (MsgBytes) + { + int Length = Msg->length; + + if (Msg->B2Flags & B2Msg) + { + char * ptr; + + // if message has attachments, display them if plain text + + if (Msg->B2Flags & Attachments) + { + char * FileName[100]; + int FileLen[100]; + int Files = 0; + int BodyLen, NewLen; + int i; + char *ptr2; + char Msg[512]; + int Len; + + ptr = MsgBytes; + + Len = sprintf(Msg, "Message has Attachments\r\r"); + QueueMsg(conn, Msg, Len); + + while(*ptr != 13) + { + ptr2 = strchr(ptr, 10); // Find CR + + if (memcmp(ptr, "Body: ", 6) == 0) + { + BodyLen = atoi(&ptr[6]); + } + + if (memcmp(ptr, "File: ", 6) == 0) + { + char * ptr1 = strchr(&ptr[6], ' '); // Find Space + + FileLen[Files] = atoi(&ptr[6]); + + FileName[Files++] = &ptr1[1]; + *(ptr2 - 1) = 0; + } + + ptr = ptr2; + ptr++; + } + + ptr += 2; // Over Blank Line and Separator + + NewLen = RemoveLF(ptr, BodyLen); + + QueueMsg(conn, ptr, NewLen); // Display Body + + ptr += BodyLen + 2; // to first file + + for (i = 0; i < Files; i++) + { + char Msg[512]; + int Len, n; + char * p = ptr; + char c; + + // Check if message is probably binary + + int BinCount = 0; + + NewLen = RemoveLF(ptr, FileLen[i]); // Removes LF agter CR but not on its own + + for (n = 0; n < NewLen; n++) + { + c = *p; + + if (c == 10) + *p = 13; + + if (c==0 || (c & 128)) + BinCount++; + + p++; + + } + + if (BinCount > NewLen/10) + { + // File is probably Binary + + Len = sprintf(Msg, "\rAttachment %s is a binary file\r", FileName[i]); + QueueMsg(conn, Msg, Len); + } + else + { + Len = sprintf(Msg, "\rAttachment %s\r\r", FileName[i]); + QueueMsg(conn, Msg, Len); + + user->Total.MsgsSent[Index] ++; + user->Total.BytesForwardedOut[Index] += NewLen; + + QueueMsg(conn, ptr, NewLen); + } + + ptr += FileLen[i]; + ptr +=2; // Over separator + } + goto sendEOM; + } + + // Remove B2 Headers (up to the File: Line) + + ptr = strstr(MsgBytes, "Body:"); + + if (ptr) + { + MsgBytes = ptr; + Length = (int)strlen(ptr); + } + } + + // Remove lf chars + + Length = RemoveLF(MsgBytes, Length); + + user->Total.MsgsSent[Index] ++; + user->Total.BytesForwardedOut[Index] += Length; + + QueueMsg(conn, MsgBytes, Length); + +sendEOM: + + free(Save); + + nodeprintf(conn, "\r\r[End of Message #%d from %s%s]\r", msgno, Msg->from, Msg->emailfrom); + + if ((_stricmp(Msg->to, user->Call) == 0) || ((conn->sysop) && (_stricmp(Msg->to, "SYSOP") == 0))) + { + if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) + { + if (Msg->status != 'Y') + { + Msg->status = 'Y'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + } + } + } + } + else + { + nodeprintf(conn, "File for Message %d not found\r", msgno); + } +} + struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop) + { + int m=NumberofMessages; + + struct MsgInfo * Msg; + + do + { + m = GetUserMsg(m, Call, sysop); + + if (m == 0) + return NULL; + + Msg=MsgHddrPtr[m]; + + if (Msg->number == msgno) + return Msg; + + m--; + + } while (m> 0); + + return NULL; + +} + + +char * ReadInfoFile(char * File) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + char * ptr1 = 0, * ptr2; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%s", BaseDir, File); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes=malloc(FileSize+1); + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize] = 0; + + ptr1 = MsgBytes; + + // Replace LF or CRLF with CR + + // First remove cr from crlf + + while(ptr2 = strstr(ptr1, "\r\n")) + { + memmove(ptr2, ptr2 + 1, strlen(ptr2)); + } + + // Now replace lf with cr + + ptr1 = MsgBytes; + + while (*ptr1) + { + if (*ptr1 == '\n') + *(ptr1) = '\r'; + + ptr1++; + } + + return MsgBytes; +} + +char * FormatDateAndTime(time_t Datim, BOOL DateOnly) +{ + struct tm *tm; + static char Date[]="xx-xxx hh:mmZ"; + + tm = gmtime(&Datim); + + if (tm) + sprintf_s(Date, sizeof(Date), "%02d-%3s %02d:%02dZ", + tm->tm_mday, month[tm->tm_mon], tm->tm_hour, tm->tm_min); + + if (DateOnly) + { + Date[6]=0; + return Date; + } + + return Date; +} + +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); + + +BOOL DoSendCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY + + char * From = NULL; + char * BID = NULL; + char * ATBBS = NULL; + char seps[] = " \t\r"; + struct MsgInfo * OldMsg; + char OldTitle[62]; + char NewTitle[62]; + char To[100]= ""; + int msgno; + + if (Cmd[1] == 0) Cmd[1] ='P'; // Just S means SP + + switch (toupper(Cmd[1])) + { + case 'B': + + if (RefuseBulls) + { + nodeprintf(conn, "*** Error: This system doesn't allow sending Bulls\r"); + return FALSE; + } + + if (user->flags & F_NOBULLS) + { + nodeprintf(conn, "*** Error: You are not allowed to send Bulls\r"); + return FALSE; + } + + + case 'P': + case 'T': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); + return FALSE; + } + + strcpy(To, Arg1); + + if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + return FALSE; + + return CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL); + + case 'R': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: Message Number is missing\r"); + return FALSE; + } + + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return FALSE; + } + + OldMsg = FindMessage(user->Call, msgno, conn->sysop); + + if (OldMsg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return FALSE; + } + + Arg1=&OldMsg->from[0]; + + strcpy(To, Arg1); + + if (_stricmp(Arg1, "SMTP:") == 0 || _stricmp(Arg1, "RMS:") == 0 || OldMsg->emailfrom) + { + // SMTP message. Need to get the real sender from the message + + sprintf(To, "%s%s", Arg1, OldMsg->emailfrom); + } + + if (!DecodeSendParams(conn, "", &From, To, &ATBBS, &BID)) + return FALSE; + + strcpy(OldTitle, OldMsg->title); + + if (strlen(OldTitle) > 57) OldTitle[57] = 0; + + strcpy(NewTitle, "Re:"); + strcat(NewTitle, OldTitle); + + return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); + + return TRUE; + + case 'C': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: Message Number is missing\r"); + return FALSE; + } + + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return FALSE; + } + + Arg1 = strtok_s(NULL, seps, &Context); + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); + return FALSE; + } + + strcpy(To, Arg1); + + if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + return FALSE; + + OldMsg = FindMessage(user->Call, msgno, conn->sysop); + + if (OldMsg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return FALSE; + } + + strcpy(OldTitle, OldMsg->title); + + if (strlen(OldTitle) > 56) OldTitle[56] = 0; + + strcpy(NewTitle, "Fwd:"); + strcat(NewTitle, OldTitle); + + conn->CopyBuffer = ReadMessageFile(msgno); + + return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); + } + + + nodeprintf(conn, "*** Error: Invalid Send option %c\r", Cmd[1]); + + return FALSE; +} + +char * CheckToAddress(CIRCUIT * conn, char * Addr) +{ + // Check one element of Multiple Address + + if (conn == NULL || !(conn->BBSFlags & BBS)) + { + // if a normal user, check that TO and/or AT are known and warn if not. + + if (_stricmp(Addr, "SYSOP") == 0) + { + return _strdup(Addr); + } + + if (SendBBStoSYSOPCall) + if (_stricmp(Addr, BBSName) == 0) + return _strdup(SYSOPCall); + + + if (strchr(Addr, '@') == 0) + { + // No routing, if not a user and not known to forwarding or WP warn + + struct UserInfo * ToUser = LookupCall(Addr); + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->HomeBBS[0]) + { + char * NewAddr = malloc(250); + if (conn) + nodeprintf(conn, "Address %s - @%s added from HomeBBS\r", Addr, ToUser->HomeBBS); + sprintf(NewAddr, "%s@%s", Addr, ToUser->HomeBBS); + return NewAddr; + } + } + else + { + WPRecP WP = LookupWP(Addr); + + if (WP) + { + char * NewAddr = malloc(250); + + if (conn) + nodeprintf(conn, "Address %s - @%s added from WP\r", Addr, WP->first_homebbs); + sprintf(NewAddr, "%s@%s", Addr, WP->first_homebbs); + return NewAddr; + } + } + } + } + + // Check SMTP and RMS Addresses + + if ((_memicmp(Addr, "rms:", 4) == 0) || (_memicmp(Addr, "rms/", 4) == 0)) + { + Addr[3] = ':'; // Replace RMS/ with RMS: + + if (conn && !FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + } + else if ((_memicmp(Addr, "smtp:", 5) == 0) || (_memicmp(Addr, "smtp/", 5) == 0)) + { + Addr[4] = ':'; // Replace smpt/ with smtp: + + if (ISP_Gateway_Enabled) + { + if (conn && (conn->UserPointer->flags & F_EMAIL) == 0) + { + nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); + return FALSE; + } + } + else + { + if (conn) + nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); + return FALSE; + } + } + + return _strdup(Addr); +} + + +char Winlink[] = "WINLINK.ORG"; + +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char *To, char ** ATBBS, char ** BID) +{ + char * ptr; + char seps[] = " \t\r"; + WPRecP WP; + char * ToCopy = _strdup(To); + int Len; + + conn->ToCount = 0; + + // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY + + // Having trailing ; will mess up parsing multiple addresses, so remove. + + while (To[strlen(To) - 1] == ';') + To[strlen(To) - 1] = 0; + + if (strchr(Context, ';') || strchr(To, ';')) + { + // Multiple Addresses - put address list back together + + char * p; + + To[strlen(To)] = ' '; + Context = To; + + while (p = strchr(Context, ';')) + { + // Multiple Addressees + + To = strtok_s(NULL, ";", &Context); + Len = (int)strlen(To); + conn->To = realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); + if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) + conn->ToCount++; + } + + To = strtok_s(NULL, seps, &Context); + + Len = (int)strlen(To); + conn->To=realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); + if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) + conn->ToCount++; + } + else + { + // Single Call + + // accept CALL!CALL for source routed message + + if (strchr(To, '@') == 0 && strchr(To, '!')) // Bang route without @ + { + char * bang = strchr(To, '!'); + + memmove(bang + 1, bang, strlen(bang)); // Move !call down one + + *ATBBS = strlop(To, '!');; + } + + // Accept call@call (without spaces) - but check for smtp addresses + + if (_memicmp(To, "smtp:", 5) != 0 && _memicmp(To, "rms:", 4) != 0 && _memicmp(To, "rms/", 4) != 0) + { + ptr = strchr(To, '@'); + + if (ptr) + { + // If looks like a valid email address, treat as such + + int tolen; + *ATBBS = strlop(To, '@'); + + strlop(To, '-'); // Cant have SSID on BBS Name + + tolen = (int)strlen(To); + + if (tolen > 6 || !CheckifPacket(*ATBBS)) + { + // Probably Email address. Add smtp: or rms: + + if (FindRMS() || strchr(*ATBBS, '!')) // have RMS or source route + sprintf(To, "rms:%s", ToCopy); + else if (ISP_Gateway_Enabled) + sprintf(To, "smtp:%s", ToCopy); + else if (isAMPRMsg(ToCopy)) + sprintf(To, "rms:%s", ToCopy); + + } + } + } + } + + free(ToCopy); + + // Look for Optional fields; + + ptr = strtok_s(NULL, seps, &Context); + + while (ptr) + { + if (strcmp(ptr, "@") == 0) + { + *ATBBS = _strupr(strtok_s(NULL, seps, &Context)); + } + else if(strcmp(ptr, "<") == 0) + { + *From = strtok_s(NULL, seps, &Context); + } + else if (ptr[0] == '$') + *BID = &ptr[1]; + else + { + nodeprintf(conn, "*** Error: Invalid Format\r"); + return FALSE; + } + ptr = strtok_s(NULL, seps, &Context); + } + + // Only allow < from a BBS + + if (*From) + { + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "*** < can only be used by a BBS\r"); + return FALSE; + } + } + + if (!*From) + *From = conn->UserPointer->Call; + + if (!(conn->BBSFlags & BBS)) + { + // if a normal user, check that TO and/or AT are known and warn if not. + + if (_stricmp(To, "SYSOP") == 0) + { + conn->LocalMsg = TRUE; + return TRUE; + } + + if (!*ATBBS && conn->ToCount == 0) + { + // No routing, if not a user and not known to forwarding or WP warn + + struct UserInfo * ToUser = LookupCall(To); + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->flags & F_RMSREDIRECT) + { + // sent to Winlink + + *ATBBS = Winlink; + nodeprintf(conn, "Redirecting to winlink.org\r", *ATBBS); + } + else if (ToUser->HomeBBS[0]) + { + *ATBBS = ToUser->HomeBBS; + nodeprintf(conn, "Address @%s added from HomeBBS\r", *ATBBS); + } + else + { + conn->LocalMsg = TRUE; + } + } + else + { + conn->LocalMsg = FALSE; + WP = LookupWP(To); + + if (WP) + { + *ATBBS = WP->first_homebbs; + nodeprintf(conn, "Address @%s added from WP\r", *ATBBS); + } + } + } + } + return TRUE; +} + +BOOL CreateMessage(CIRCUIT * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title) +{ + struct MsgInfo * Msg, * TestMsg; + char * via = NULL; + char * FromHA; + + // Create a temp msg header entry + + if (conn->ToCount) + { + } + else + { + if (CheckRejFilters(From, ToCall, ATBBS, BID, MsgType)) + { + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - REJECTED\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error - Message Filters prevent sending this message\r"); + + return FALSE; + } + } + + Msg = malloc(sizeof (struct MsgInfo)); + + if (Msg == 0) + { + CriticalErrorHandler("malloc failed for new message header"); + return FALSE; + } + + memset(Msg, 0, sizeof (struct MsgInfo)); + + conn->TempMsg = Msg; + + Msg->type = MsgType; + + if (conn->UserPointer->flags & F_HOLDMAIL) + Msg->status = 'H'; + else + Msg->status = 'N'; + + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + if (BID) + { + BIDRec * TempBID; + + // If P Message, dont immediately reject on a Duplicate BID. Check if we still have the message + // If we do, reject it. If not, accept it again. (do we need some loop protection ???) + + TempBID = LookupBID(BID); + + if (TempBID) + { + if (MsgType == 'B') + { + // Duplicate bid + + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - BID\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error- Duplicate BID\r"); + + return FALSE; + } + + TestMsg = GetMsgFromNumber(TempBID->u.msgno); + + // if the same TO we will assume the same message + + if (TestMsg && strcmp(TestMsg->to, ToCall) == 0) + { + // We have this message. If we have already forwarded it, we should accept it again + + if ((TestMsg->status == 'N') || (TestMsg->status == 'Y')|| (TestMsg->status == 'H')) + { + // Duplicate bid + + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - BID\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error- Duplicate BID\r"); + + return FALSE; + } + } + } + + if (strlen(BID) > 12) BID[12] = 0; + strcpy(Msg->bid, BID); + + // Save BID in temp list in case we are offered it again before completion + + TempBID = AllocateTempBIDRecord(); + strcpy(TempBID->BID, BID); + TempBID->u.conn = conn; + + } + + if (conn->ToCount) + { + } + else + { + if (_memicmp(ToCall, "rms:", 4) == 0) + { + if (!FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + + via=strlop(ToCall, ':'); + _strupr(ToCall); + } + else if (_memicmp(ToCall, "rms/", 4) == 0) + { + if (!FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + + via=strlop(ToCall, '/'); + _strupr(ToCall); + } + else if (_memicmp(ToCall, "smtp:", 5) == 0) + { + if (ISP_Gateway_Enabled) + { + if ((conn->UserPointer->flags & F_EMAIL) == 0) + { + nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); + return FALSE; + } + via=strlop(ToCall, ':'); + ToCall[0] = 0; + } + else + { + nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); + return FALSE; + } + } + else + { + _strupr(ToCall); + if (ATBBS) + via=_strupr(ATBBS); + } + + strlop(ToCall, '-'); // Remove any (illegal) ssid + if (strlen(ToCall) > 6) ToCall[6] = 0; + + strcpy(Msg->to, ToCall); + + if (SendBBStoSYSOPCall) + if (_stricmp(ToCall, BBSName) == 0) + strcpy(Msg->to, SYSOPCall); + + if (via) + { + if (strlen(via) > 40) via[40] = 0; + + strcpy(Msg->via, via); + } + + } // End of Multiple Dests + + // Look for HA in From (even if we shouldn't be getting it!) + + FromHA = strlop(From, '@'); + + + strlop(From, '-'); // Remove any (illegal) ssid + if (strlen(From) > 6) From[6] = 0; + strcpy(Msg->from, From); + + if (FromHA) + { + if (strlen(FromHA) > 39) FromHA[39] = 0; + Msg->emailfrom[0] = '@'; + strcpy(&Msg->emailfrom[1], _strupr(FromHA)); + } + + if (Title) // Only used by SR and SC + { + strcpy(Msg->title, Title); + conn->Flags |= GETTINGMESSAGE; + + // Create initial buffer of 10K. Expand if needed later + + conn->MailBuffer=malloc(10000); + conn->MailBufferSize=10000; + + nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); + return TRUE; + } + + if (conn->BBSFlags & FLARQMODE) + return TRUE; + + if (!(conn->BBSFlags & FBBCompressed)) + conn->Flags |= GETTINGTITLE; + + if (!(conn->BBSFlags & BBS)) + nodeprintf(conn, "Enter Title (only):\r"); + else + if (!(conn->BBSFlags & FBBForwarding)) + nodeprintf(conn, "OK\r"); + + return TRUE; +} + +VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int msglen) +{ + + conn->Flags &= ~GETTINGTITLE; + + if (msglen == 1) + { + nodeprintf(conn, "*** Message Cancelled\r"); + SendPrompt(conn, user); + return; + } + + if (msglen > 60) msglen = 60; + + Buffer[msglen-1] = 0; + + strcpy(conn->TempMsg->title, Buffer); + + // Create initial buffer of 10K. Expand if needed later + + conn->MailBuffer=malloc(10000); + conn->MailBufferSize=10000; + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to create Message Buffer\r"); + return; + } + + conn->Flags |= GETTINGMESSAGE; + + if (!conn->BBSFlags & BBS) + nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); + +} + +VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int msglen) +{ + char * ptr2 = NULL; + + if (((msglen < 3) && (Buffer[0] == 0x1a)) || ((msglen == 4) && (_memicmp(Buffer, "/ex", 3) == 0))) + { + int Index = 0; + + if (conn->TempMsg->type == 'P') + Index = PMSG; + else if (conn->TempMsg->type == 'B') + Index = BMSG; + else if (conn->TempMsg->type == 'T') + Index = TMSG; + + conn->Flags &= ~GETTINGMESSAGE; + + user->Total.MsgsReceived[Index]++; + user->Total.BytesForwardedIn[Index] += conn->TempMsg->length; + + if (conn->ToCount) + { + // Multiple recipients + + struct MsgInfo * Msg = conn->TempMsg; + int i; + struct MsgInfo * SaveMsg = Msg; + char * SaveBody = conn->MailBuffer; + int SaveMsgLen = Msg->length; + BOOL SentToRMS = FALSE; + int ToLen = 0; + char * ToString = zalloc(conn->ToCount * 100); + + // If no BID provided, allocate one + + if (Msg->bid[0] == 0) + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg + 1, BBSName); + + for (i = 0; i < conn->ToCount; i++) + { + char * Addr = conn->To[i]; + char * Via; + + if (_memicmp (Addr, "SMTP:", 5) == 0) + { + // For Email + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 10); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + Msg->to[0] = 0; + strcpy(Msg->via, &Addr[5]); + + CreateMessageFromBuffer(conn); + continue; + } + + if (_memicmp (Addr, "RMS:", 4) == 0) + { + // Add to B2 Message for RMS + + Addr+=4; + + Via = strlop(Addr, '@'); + + if (Via && _stricmp(Via, "winlink.org") == 0) + { + if (CheckifLocalRMSUser(Addr)) + { + // Local RMS - Leave Here + + Via = 0; // Drop Through + goto PktMsg; + } + else + { + ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); + continue; + } + } + + ToLen = sprintf(ToString, "%sTo: %s@%s\r\n", ToString, Addr, Via); + continue; + } + + _strupr(Addr); + + Via = strlop(Addr, '@'); + + if (Via && _stricmp(Via, "winlink.org") == 0) + { + if (CheckifLocalRMSUser(Addr)) + { + // Local RMS - Leave Here + + Via = 0; // Drop Through + } + else + { + ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); + + // Add to B2 Message for RMS + + continue; + } + } + + PktMsg: + + conn->LocalMsg = FALSE; + + // Normal BBS Message + + if (_stricmp(Addr, "SYSOP") == 0) + conn->LocalMsg = TRUE; + else + { + struct UserInfo * ToUser = LookupCall(Addr); + + if (ToUser) + conn->LocalMsg = TRUE; + } + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 10); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + strcpy(Msg->to, Addr); + + if (Via) + { + Msg->bid[0] = 0; // if we are forwarding it, we must change BID to be safe + strcpy(Msg->via, Via); + } + + CreateMessageFromBuffer(conn); + } + + if (ToLen) + { + char * B2Hddr = zalloc(ToLen + 1000); + int B2HddrLen; + char DateString[80]; + struct tm * tm; + time_t Date = time(NULL); + char Type[16] = "Private"; + + // Get Type + + if (conn->TempMsg->type == 'B') + strcpy(Type, "Bulletin"); + else if (conn->TempMsg->type == 'T') + strcpy(Type, "Traffic"); + + tm = gmtime(&Date); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000 + ToLen); + + Msg->B2Flags = B2Msg; + + B2HddrLen = sprintf(B2Hddr, + "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\n%sSubject: %s\r\nMbo: %s\r\nBody: %d\r\n\r\n", + SaveMsg->bid, DateString, Type, + SaveMsg->from, ToString, SaveMsg->title, BBSName, SaveMsgLen); + + memcpy(conn->MailBuffer, B2Hddr, B2HddrLen); + memcpy(&conn->MailBuffer[B2HddrLen], SaveBody, SaveMsgLen); + + Msg->length += B2HddrLen; + + strcpy(Msg->to, "RMS"); + + CreateMessageFromBuffer(conn); + + free(B2Hddr); + } + + free(SaveMsg); + free(SaveBody); + conn->MailBuffer = NULL; + conn->MailBufferSize=0; + + if (!(conn->BBSFlags & BBS)) + SendPrompt(conn, conn->UserPointer); + else + if (!(conn->BBSFlags & FBBForwarding)) + { + if (conn->BBSFlags & OUTWARDCONNECT) + BBSputs(conn, "F>\r"); // if Outward connect must be reverse forward + else + BBSputs(conn, ">\r"); + } + + /* + // From a client - Create one copy with all RMS recipients, and another for each packet recipient + + // Merge all RMS To: lines + + ToLen = 0; + ToString[0] = 0; + + for (i = 0; i < Recipients; i++) + { + if (LocalMsg[i]) + continue; // For a local RMS user + + if (_stricmp(Via[i], "WINLINK.ORG") == 0 || _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || + _stricmp(RecpTo[i], "RMS") == 0) + { + ToLen += strlen(HddrTo[i]); + strcat(ToString, HddrTo[i]); + } + } + + if (ToLen) + { + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + + memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); + memcpy(&conn->MailBuffer[B2To], ToString, ToLen); + + conn->TempMsg->length += ToLen; + + strcpy(Msg->to, "RMS"); + strcpy(Msg->via, "winlink.org"); + + // Must Change the BID + + Msg->bid[0] = 0; + + CreateMessageFromBuffer(conn); + } + + } + + free(ToString); + + for (i = 0; i < Recipients; i++) + { + // Only Process Non - RMS Dests or local RMS Users + + if (LocalMsg[i] == 0) + if (_stricmp (Via[i], "WINLINK.ORG") == 0 || + _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || + _stricmp(RecpTo[i], "RMS") == 0) + continue; + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + // Add our To: + + ToLen = strlen(HddrTo[i]); + + if (_memicmp(HddrTo[i], "CC", 2) == 0) // Replace CC: with TO: + memcpy(HddrTo[i], "To", 2); + + memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); + memcpy(&conn->MailBuffer[B2To], HddrTo[i], ToLen); + + conn->TempMsg->length += ToLen; + + strcpy(Msg->to, RecpTo[i]); + strcpy(Msg->via, Via[i]); + + Msg->bid[0] = 0; + + CreateMessageFromBuffer(conn); + } + } // End not from RMS + + free(SaveMsg); + free(SaveBody); + conn->MailBuffer = NULL; + conn->MailBufferSize=0; + + SetupNextFBBMessage(conn); + return; + + } My__except_Routine("Process Multiple Destinations"); + + BBSputs(conn, "*** Program Error Processing Multiple Destinations\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + + return; +*/ + + conn->ToCount = 0; + + return; + } + + + CreateMessageFromBuffer(conn); + return; + + } + + Buffer[msglen++] = 0x0a; + + if ((conn->TempMsg->length + msglen) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to extend Message Buffer\r"); + + conn->Flags &= ~GETTINGMESSAGE; + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, msglen); + + conn->TempMsg->length += msglen; +} + +VOID CreateMessageFromBuffer(CIRCUIT * conn) +{ + struct MsgInfo * Msg; + BIDRec * BIDRec; + char * ptr1, * ptr2 = NULL; + char * ptr3, * ptr4; + int FWDCount = 0; + char OldMess[] = "\r\n\r\nOriginal Message:\r\n\r\n"; + time_t Age; + int OurCount; + char * HoldReason = "User has Hold Messages flag set"; + struct UserInfo * user; + + +#ifndef LINBPQ + struct _EXCEPTION_POINTERS exinfo; +#endif + + // If doing SC, Append Old Message + + if (conn->CopyBuffer) + { + if ((conn->TempMsg->length + (int) strlen(conn->CopyBuffer) + 80 )> conn->MailBufferSize) + { + conn->MailBufferSize += (int)strlen(conn->CopyBuffer) + 80; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to extend Message Buffer\r"); + + conn->Flags &= ~GETTINGMESSAGE; + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], OldMess, strlen(OldMess)); + + conn->TempMsg->length += (int)strlen(OldMess); + + memcpy(&conn->MailBuffer[conn->TempMsg->length], conn->CopyBuffer, strlen(conn->CopyBuffer)); + + conn->TempMsg->length += (int)strlen(conn->CopyBuffer); + + free(conn->CopyBuffer); + conn->CopyBuffer = NULL; + } + + // Allocate a message Record slot + + Msg = AllocateMsgRecord(); + memcpy(Msg, conn->TempMsg, sizeof(struct MsgInfo)); + + free(conn->TempMsg); + + // Set number here so they remain in sequence + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + FreeSemaphore(&MsgNoSemaphore); + MsgnotoMsg[Msg->number] = Msg; + + if (Msg->status == 0) + Msg->status = 'N'; + + // Create BID if non supplied + + if (Msg->bid[0] == 0) + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + // if message body had R: lines, get date created from last (not very accurate, but best we can do) + + // Also check if we have had message before to detect loops + + ptr1 = conn->MailBuffer; + OurCount = 0; + + // If it is a B2 Message, Must Skip B2 Header + + if (Msg->B2Flags & B2Msg) + { + ptr1 = strstr(ptr1, "\r\n\r\n"); + if (ptr1) + ptr1 += 4; + else + ptr1 = conn->MailBuffer; + } + +nextline: + + if (memcmp(ptr1, "R:", 2) == 0) + { + // Is if ours? + + // BPQ RLINE Format R:090920/1041Z 6542@N4JOA.#WPBFL.FL.USA.NOAM BPQ1.0.2 + + ptr3 = strchr(ptr1, '@'); + ptr4 = strchr(ptr1, '.'); + + if (ptr3 && ptr4 && (ptr4 > ptr3)) + { + if (memcmp(ptr3+1, BBSName, ptr4-ptr3-1) == 0) + OurCount++; + } + + GetWPBBSInfo(ptr1); // Create WP /I record from R: Line + + // see if another + + ptr2 = ptr1; // save + ptr1 = strchr(ptr1, '\r'); + if (ptr1 == 0) + { + Debugprintf("Corrupt Message %s from %s - truncated within R: line", Msg->bid, Msg->from); + return; + } + ptr1++; + if (*ptr1 == '\n') ptr1++; + + goto nextline; + } + + // ptr2 points to last R: line (if any) + + if (ptr2) + { + struct tm rtime; + time_t result; + + memset(&rtime, 0, sizeof(struct tm)); + + if (ptr2[10] == '/') + { + // Dodgy 4 char year + + sscanf(&ptr2[2], "%04d%02d%02d/%02d%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); + rtime.tm_year -= 1900; + rtime.tm_mon--; + } + else if (ptr2[8] == '/') + { + sscanf(&ptr2[2], "%02d%02d%02d/%02d%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); + + if (rtime.tm_year < 90) + rtime.tm_year += 100; // Range 1990-2089 + rtime.tm_mon--; + } + + // Otherwise leave date as zero, which should be rejected + + // result = _mkgmtime(&rtime); + + if ((result = mktime(&rtime)) != (time_t)-1 ) + { + result -= (time_t)_MYTIMEZONE; + + Msg->datecreated = result; + Age = (time(NULL) - result)/86400; + + if ( Age < -7) + { + Msg->status = 'H'; + HoldReason = "Suspect Date Sent"; + } + else if (Age > BidLifetime || Age > MaxAge) + { + Msg->status = 'H'; + HoldReason = "Message too old"; + + } + else + GetWPInfoFromRLine(Msg->from, ptr2, result); + } + else + { + // Can't decode R: Datestamp + + Msg->status = 'H'; + HoldReason = "Corrupt R: Line - can't determine age"; + } + + if (OurCount > 1) + { + // Message is looping + + Msg->status = 'H'; + HoldReason = "Message may be looping"; + + } + } + + if (strcmp(Msg->to, "WP") == 0) + { + // If Reject WP Bulls is set, Kill message here. + // It should only get here if B2 - otherwise it should be + // rejected earlier + + if (Msg->type == 'B' && FilterWPBulls) + Msg->status = 'K'; + + } + + conn->MailBuffer[Msg->length] = 0; + + if (CheckBadWords(Msg->title) || CheckBadWords(conn->MailBuffer)) + { + Msg->status = 'H'; + HoldReason = "Bad word in title or body"; + } + + if (CheckHoldFilters(Msg->from, Msg->to, Msg->via, Msg->bid)) + { + Msg->status = 'H'; + HoldReason = "Matched Hold Filters"; + } + + if (CheckValidCall(Msg->from) == 0) + { + Msg->status = 'H'; + HoldReason = "Probable Invalid From Call"; + } + + // Process any WP Messages + + if (strcmp(Msg->to, "WP") == 0) + { + if (Msg->status == 'N') + { + ProcessWPMsg(conn->MailBuffer, Msg->length, ptr2); + + if (Msg->type == 'P') // Kill any processed private WP messages. + { + char VIA[80]; + + strcpy(VIA, Msg->via); + strlop(VIA, '.'); + + if (strcmp(VIA, BBSName) == 0) + Msg->status = 'K'; + } + } + } + + CreateMessageFile(conn, Msg); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + if (Msg->length > MaxTXSize) + { + Msg->status = 'H'; + HoldReason = "Message too long"; + + if (!(conn->BBSFlags & BBS)) + nodeprintf(conn, "*** Warning Message length exceeds sysop-defined maximum of %d - Message will be held\r", MaxTXSize); + } + + // Check for message to internal server + + if (Msg->via[0] == 0 + || _stricmp(Msg->via, BBSName) == 0 // our BBS a + || _stricmp(Msg->via, AMPRDomain) == 0) // our AMPR Address + { + if (CheckforMessagetoServer(Msg)) + { + // Flag as killed and send prompt + + FlagAsKilled(Msg, TRUE); + + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "Message %d to Server Processed and Killed.\r", Msg->number); + SendPrompt(conn, conn->UserPointer); + } + return; // no need to process further + } + } + + if (Msg->to[0]) + FWDCount = MatchMessagetoBBSList(Msg, conn); + else + { + // If addressed @winlink.org, and to a local user, Keep here. + + char * Call; + char * AT; + + // smtp or rms - don't warn no route + + FWDCount = 1; + + Call = _strupr(_strdup(Msg->via)); + AT = strlop(Call, '@'); + + if (AT && _stricmp(AT, "WINLINK.ORG") == 0) + { + struct UserInfo * user = LookupCall(Call); + + if (user) + { + if (user->flags & F_POLLRMS) + { + Logprintf(LOG_BBS, conn, '?', "SMTP Message @ winlink.org, but local RMS user - leave here"); + strcpy(Msg->to, Call); + strcpy(Msg->via, AT); + if (user->flags & F_BBS) // User is a BBS, so set FWD bit so he can get it + set_fwd_bit(Msg->fbbs, user->BBSNumber); + + } + } + } + free(Call); + } + + // Warn SYSOP if P or T forwarded in, and has nowhere to go + + if ((conn->BBSFlags & BBS) && Msg->type != 'B' && FWDCount == 0 && WarnNoRoute && + strcmp(Msg->to, "SYSOP") && strcmp(Msg->to, "WP")) + { + if (Msg->via[0]) + { + if (_stricmp(Msg->via, BBSName)) // Not for our BBS a + if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address + SendWarningToSYSOP(Msg); + } + else + { + // No via - is it for a local user? + + if (LookupCall(Msg->to) == 0) + SendWarningToSYSOP(Msg); + } + } + + if ((conn->BBSFlags & SYNCMODE) == 0) + { + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "Message: %d Bid: %s Size: %d\r", Msg->number, Msg->bid, Msg->length); + + if (Msg->via[0]) + { + if (_stricmp(Msg->via, BBSName)) // Not for our BBS a + if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address + + if (FWDCount == 0 && Msg->to[0] != 0) // unless smtp msg + nodeprintf(conn, "@BBS specified, but no forwarding info is available - msg may not be delivered\r"); + } + else + { + if (FWDCount == 0 && conn->LocalMsg == 0 && Msg->type != 'B') + // Not Local and no forward route + nodeprintf(conn, "Message is not for a local user, and no forwarding info is available - msg may not be delivered\r"); + } + if (conn->ToCount == 0) + SendPrompt(conn, conn->UserPointer); + } + else + { + if (!(conn->BBSFlags & FBBForwarding)) + { + if (conn->ToCount == 0) + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + } + } + + if(Msg->to[0] == 0) + SMTPMsgCreated=TRUE; + + if (Msg->status != 'H' && Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + + if (Msg->status == 'H') + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, HoldReason); + SendMessageToSYSOP(Title, MailBuffer, Length); + } + + BuildNNTPList(Msg); // Build NNTP Groups list + + SaveMessageDatabase(); + SaveBIDDatabase(); + + // If Event Notifications enabled report a new message event + + if (reportNewMesageEvents) + { + char msg[200]; + + //12345 B 2053 TEST@ALL F6FBB 920325 This is the subject + + struct tm *tm = gmtime((time_t *)&Msg->datecreated); + + sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r", + Msg->number, Msg->type, Msg->length, Msg->to, + Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title); + +#ifdef WIN32 + if (pRunEventProgram) + pRunEventProgram("MailNewMsg.exe", msg); +#else + { + char prog[256]; + sprintf(prog, "%s/%s", BPQDirectory, "MailNewMsg"); + RunEventProgram(prog, msg); + } +#endif + } + + + if (EnableUI) +#ifdef LINBPQ + SendMsgUI(Msg); +#else + __try + { + SendMsgUI(Msg); + } + My__except_Routine("SendMsgUI"); +#endif + + user = LookupCall(Msg->to); + + if (user && (user->flags & F_APRSMFOR)) + { + char APRS[128]; + char Call[16]; + int SSID = user->flags >> 28; + + if (SSID) + sprintf(Call, "%s-%d", Msg->to, SSID); + else + strcpy(Call, Msg->to); + + sprintf(APRS, "New BBS Message %s From %s", Msg->title, Msg->from); + APISendAPRSMessage(APRS, Call); + } + + return; +} + +VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg) +{ + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + char Mess[255]; + int len; + BOOL AutoImport = FALSE; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + // If title is "Batched messages for AutoImport from BBS xxxxxx and first line is S? and it is + // for this BBS, Import file and set message as Killed. May need to strip B2 Header and R: lines + + if (strcmp(Msg->to, BBSName) == 0 && strstr(Msg->title, "Batched messages for AutoImport from BBS ")) + { + UCHAR * ptr = conn->MailBuffer; + + // If it is a B2 Message, Must Skip B2 Header + + if (Msg->B2Flags & B2Msg) + { + ptr = strstr(ptr, "\r\n\r\n"); + if (ptr) + ptr += 4; + else + ptr = conn->MailBuffer; + } + + if (memcmp(ptr, "R:", 2) == 0) + { + ptr = strstr(ptr, "\r\n\r\n"); //And remove R: Lines + if (ptr) + ptr += 4; + } + + if (*(ptr) == 'S' && ptr[2] == ' ') + { + int HeaderLen = (int)(ptr - conn->MailBuffer); + Msg->length -= HeaderLen; + memmove(conn->MailBuffer, ptr, Msg->length); + Msg->status = 'K'; + AutoImport = TRUE; + } + } + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(conn->MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + if (AutoImport) + ImportMessages(NULL, MsgFile, TRUE); + + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + if (WriteLen != Msg->length) + { + len = sprintf_s(Mess, sizeof(Mess), "Failed to create Message File\r"); + QueueMsg(conn, Mess, len); + Debugprintf(Mess); + } + return; +} + + + + +VOID SendUnbuffered(int stream, char * msg, int len) +{ +#ifndef LINBPQ + if (stream < 0) + WritetoConsoleWindow(stream, msg, len); + else +#endif + SendMsg(stream, msg, len); +} + +BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen); + +BOOL FindMessagestoForward (CIRCUIT * conn) +{ + struct UserInfo * user = conn->UserPointer; + +#ifndef LINBPQ + + struct _EXCEPTION_POINTERS exinfo; + + __try { +#endif + + // This is a hack so Winpack or WLE users can use forwarding + // protocols to get their messages without being defined as a BBS + + // !!IMPORTANT Getting this wrong can see message repeatedly proposed !! + // !! Anything sent using this must be killed if sent or rejected. + + // I'm not sure about this. I think I only need the PacLinkCalls + // if from RMS Express, and it always sends an FW; line + // Ah, no. What about WinPack ?? + // If from RMS Express must have Temp_B2 or BBS set or SID will + // be rejected. So maybe just Temp_B2 && BBS = 0?? + // No, someone may have Temp_B2 set and not be using WLE ?? So what ?? + + if ((user->flags & F_Temp_B2_BBS) && ((user->flags & F_BBS) == 0) || conn->RMSExpress || conn->PAT) + { + if (conn->PacLinkCalls == NULL) + { + // create a list with just the user call + + char * ptr1; + + conn->PacLinkCalls = zalloc(30); + + ptr1 = (char *)conn->PacLinkCalls; + ptr1 += 16; // Must be room for a null pointer on end (64 bit bug) + strcpy(ptr1, user->Call); + + conn->PacLinkCalls[0] = ptr1; + } + } + + if (conn->SendT && FindMessagestoForwardLoop(conn, 'T', conn->MaxTLen)) + { + conn->LastForwardType = 'T'; + return TRUE; + } + + if (conn->LastForwardType == 'T') + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (conn->SendP && FindMessagestoForwardLoop(conn, 'P', conn->MaxPLen)) + { + conn->LastForwardType = 'P'; + return TRUE; + } + + if (conn->LastForwardType == 'P') + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (conn->SendB && FindMessagestoForwardLoop(conn, 'B', conn->MaxBLen)) + { + conn->LastForwardType = 'B'; + return TRUE; + } + + conn->LastForwardType = 0; + return FALSE; +#ifndef LINBPQ + } My__except_Routine("FindMessagestoForward"); +#endif + return FALSE; + +} + + +BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen) +{ + // See if any messages are queued for this BBS + + int m; + struct MsgInfo * Msg; + struct UserInfo * user = conn->UserPointer; + struct FBBHeaderLine * FBBHeader; + BOOL Found = FALSE; + char RLine[100]; + int TotalSize = 0; + time_t NOW = time(NULL); + +// Debugprintf("FMTF entered Call %s Type %c Maxlen %d NextMsg = %d BBSNo = %d", +// conn->Callsign, Type, MaxLen, conn->NextMessagetoForward, user->BBSNumber); + + if (conn->PacLinkCalls || (conn->UserPointer->flags & F_NTSMPS)) // Looking for all messages, so reset + conn->NextMessagetoForward = 1; + + conn->FBBIndex = 0; + + for (m = conn->NextMessagetoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + // If an NTS MPS, see if anything matches + + if (Type == 'T' && (conn->UserPointer->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + int depth; + + if (Msg->type == 'T' && Msg->status == 'N' && Msg->length <= MaxLen && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + goto Forwardit; + + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + goto Forwardit; + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + goto Forwardit; + } + } + + // If forwarding to Paclink or RMS Express, look for any message matching the + // requested call list with status 'N' (maybe should also be 'P' ??) + + if (conn->PacLinkCalls) + { + int index = 1; + + char * Call = conn->PacLinkCalls[0]; + + while (Call) + { + if (Msg->type == Type && Msg->status == 'N') + { +// Debugprintf("Comparing RMS Call %s %s", Msg->to, Call); + if (_stricmp(Msg->to, Call) == 0) + if (Msg->status == 'N' && Msg->type == Type && Msg->length <= MaxLen) + goto Forwardit; + else + Debugprintf("Call Match but Wrong Type/Len %c %d", Msg->type, Msg->length); + } + Call = conn->PacLinkCalls[index++]; + } +// continue; + } + + if (Msg->type == Type && Msg->length <= MaxLen && (Msg->status != 'H') + && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + // Message to be sent - do a consistancy check (State, etc) + + Forwardit: + + if (Msg->Defered > 0) // = response received + { + Msg->Defered--; + Debugprintf("Message %d deferred", Msg->number); + continue; + } + + if ((Msg->from[0] == 0) || (Msg->to[0] == 0)) + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, "Missing From: or To: field"); + SendMessageToSYSOP(Title, MailBuffer, Length); + + Msg->status = 'H'; + continue; + } + + conn->NextMessagetoForward = m + 1; // So we don't offer again if defered + + Msg->Locked = 1; // So other MPS can't pick it up + + // if FBB forwarding add to list, eise save pointer + + if (conn->BBSFlags & FBBForwarding) + { + struct tm *tm; + time_t temp; + + FBBHeader = &conn->FBBHeaders[conn->FBBIndex++]; + + FBBHeader->FwdMsg = Msg; + FBBHeader->MsgType = Msg->type; + FBBHeader->Size = Msg->length; + TotalSize += Msg->length; + strcpy(FBBHeader->From, Msg->from); + strcpy(FBBHeader->To, Msg->to); + strcpy(FBBHeader->ATBBS, Msg->via); + strcpy(FBBHeader->BID, Msg->bid); + + // Set up R:Line, so se can add its length to the sise + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + FBBHeader->Size += sprintf_s(RLine, sizeof(RLine),"R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + Msg->number, BBSName, HRoute, RlineVer); + + // If using B2 forwarding we need the message size and Compressed size for FC proposal + + if (conn->BBSFlags & FBBB2Mode) + { + if (CreateB2Message(conn, FBBHeader, RLine) == FALSE) + { + char * MailBuffer = malloc(100); + char Title[100]; + int Length; + + // Corrupt B2 Message + + Debugprintf("Corrupt B2 Message found - Message %d will be held", Msg->number); + Msg->status = 'H'; + SaveMessageDatabase(); + + conn->FBBIndex--; + TotalSize -= Msg->length; + memset(&conn->FBBHeaders[conn->FBBIndex], 0, sizeof(struct FBBHeaderLine)); + + Length = sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, "Corrupt B2 Message"); + SendMessageToSYSOP(Title, MailBuffer, Length); + + continue; + } + } + + if (conn->FBBIndex == 5 || TotalSize > user->ForwardingInfo->MaxFBBBlockSize) + return TRUE; // Got max number or too big + + Found = TRUE; // Remember we have some + } + else + { + conn->FwdMsg = Msg; + return TRUE; + } + } + } + + return Found; +} + +BOOL SeeifMessagestoForward (int BBSNumber, CIRCUIT * conn) +{ + // See if any messages are queued for this BBS + + // if Conn is not NULL, also check Msg Type + + int m; + struct MsgInfo * Msg; + + for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + if (conn) + { + char Type = Msg->type; + + if ((conn->SendB && Type == 'B') || (conn->SendP && Type == 'P') || (conn->SendT && Type == 'T')) + { +// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); + return TRUE; + } + } + else + { +// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); + return TRUE; + } + } + } + + return FALSE; +} + +int CountMessagestoForward (struct UserInfo * user) +{ + // See if any messages are queued for this BBS + + int m, n=0; + struct MsgInfo * Msg; + int BBSNumber = user->BBSNumber; + int FirstMessage = FirstMessageIndextoForward; + + if ((user->flags & F_NTSMPS)) + FirstMessage = 1; + + for (m = FirstMessage; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + n++; + continue; // So we dont count twice in Flag set and NTS MPS + } + + // if an NTS MPS, also check for any matches + + if (Msg->type == 'T' && (user->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + int depth; + + if (Msg->status == 'N' && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + { + n++; + continue; + } + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + { + n++; + continue; + } + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + { + n++; + continue; + } + } + } + } + + return n; +} + +int ListMessagestoForward(CIRCUIT * conn, struct UserInfo * user) +{ + // See if any messages are queued for this BBS + + int m, n=0; + struct MsgInfo * Msg; + int BBSNumber = user->BBSNumber; + int FirstMessage = FirstMessageIndextoForward; + + if ((user->flags & F_NTSMPS)) + FirstMessage = 1; + + for (m = FirstMessage; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; // So we dont count twice in Flag set and NTS MPS + } + + // if an NTS MPS, also check for any matches + + if (Msg->type == 'T' && (user->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + int depth; + + if (Msg->status == 'N' && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + } + } + } + + return n; +} + +VOID SendWarningToSYSOP(struct MsgInfo * Msg) +{ + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Warning - Message %d has nowhere to go", Msg->number); + sprintf(Title, "Warning - Message %d has nowhere to go", Msg->number); + SendMessageToSYSOP(Title, MailBuffer, Length); +} + + + +VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length) +{ + struct MsgInfo * Msg = AllocateMsgRecord(); + BIDRec * BIDRec; + + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + + Msg->length = Length; + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + FreeSemaphore(&MsgNoSemaphore); + + strcpy(Msg->from, "SYSTEM"); + if (SendSYStoSYSOPCall) + strcpy(Msg->to, SYSOPCall); + else + strcpy(Msg->to, "SYSOP"); + + strcpy(Msg->title, Title); + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + BIDRec = AllocateBIDRecord(); + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + MatchMessagetoBBSList(Msg, NULL); + free(MailBuffer); +} + +VOID CheckBBSNumber(int i) +{ + // Make sure number is unique + + int Count = 0; + struct UserInfo * user; + + for (user = BBSChain; user; user = user->BBSNext) + { + if (user->BBSNumber == i) + { + Count++; + + if (Count > 1) + { + // Second with same number - Renumber this one + + user->BBSNumber = FindFreeBBSNumber(); + + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; // cant really do much else + + Logprintf(LOG_BBS, NULL, '?', "Duplicate BBS Number found. BBS %s Old BBSNumber %d New BBS Number %d", user->Call, i, user->BBSNumber); + + } + } + } +} + + +int FindFreeBBSNumber() +{ + // returns the lowest number not used by any bbs or message. + + struct MsgInfo * Msg; + struct UserInfo * user; + int i, m; + + for (i = 1; i<= NBBBS; i++) + { + for (user = BBSChain; user; user = user->BBSNext) + { + if (user->BBSNumber == i) + goto nexti; // In use + } + + // Not used by BBS - check messages + + for (m = 1; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if (check_fwd_bit(Msg->fbbs, i)) + goto nexti; // In use + + if (check_fwd_bit(Msg->forw, i)) + goto nexti; // In use + } + + // Not in Use + + return i; + +nexti:; + + } + + return 0; // All used +} + +BOOL SetupNewBBS(struct UserInfo * user) +{ + user->BBSNumber = FindFreeBBSNumber(); + + if (user->BBSNumber == 0) + return FALSE; + + user->BBSNext = BBSChain; + BBSChain = user; + + SortBBSChain(); + + ReinitializeFWDStruct(user); + + return TRUE; +} + +VOID DeleteBBS(struct UserInfo * user) +{ + struct UserInfo * BBSRec, * PrevBBS = NULL; + +#ifndef LINBPQ + RemoveMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, MF_BYCOMMAND); +#endif + for (BBSRec = BBSChain; BBSRec; PrevBBS = BBSRec, BBSRec = BBSRec->BBSNext) + { + if (user == BBSRec) + { + if (PrevBBS == NULL) // First in chain; + { + BBSChain = BBSRec->BBSNext; + break; + } + PrevBBS->BBSNext = BBSRec->BBSNext; + break; + } + } +} + + +VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo); + +VOID SetupForwardingStruct(struct UserInfo * user) +{ + struct BBSForwardingInfo * ForwardingInfo; + + char Key[100] = "BBSForwarding."; + char Temp[100]; + + HKEY hKey=0; + char RegKey[100] = "SOFTWARE\\G8BPQ\\BPQ32\\BPQMailChat\\BBSForwarding\\"; + + int m; + struct MsgInfo * Msg; + + ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + + if (UsingingRegConfig == 0) + { + // Config from file + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + strcat(Key, "*"); + + strcat(Key, user->Call); + + group = config_lookup (&cfg, Key); + + if (group == NULL) // No info + return; + else + { + ForwardingInfo->TOCalls = GetMultiStringValue(group, "TOCalls"); + ForwardingInfo->ConnectScript = GetMultiStringValue(group, "ConnectScript"); + ForwardingInfo->ATCalls = GetMultiStringValue(group, "ATCalls"); + ForwardingInfo->Haddresses = GetMultiStringValue(group, "HRoutes"); + ForwardingInfo->HaddressesP = GetMultiStringValue(group, "HRoutesP"); + ForwardingInfo->FWDTimes = GetMultiStringValue(group, "FWDTimes"); + + ForwardingInfo->Enabled = GetIntValue(group, "Enabled"); + ForwardingInfo->ReverseFlag = GetIntValue(group, "RequestReverse"); + ForwardingInfo->AllowBlocked = GetIntValue(group, "AllowBlocked"); + ForwardingInfo->AllowCompressed = GetIntValue(group, "AllowCompressed"); + ForwardingInfo->AllowB1 = GetIntValue(group, "UseB1Protocol"); + ForwardingInfo->AllowB2 = GetIntValue(group, "UseB2Protocol"); + ForwardingInfo->SendCTRLZ = GetIntValue(group, "SendCTRLZ"); + + if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) + ForwardingInfo->AllowCompressed = TRUE; + + if (ForwardingInfo->AllowCompressed) + ForwardingInfo->AllowBlocked = TRUE; + + ForwardingInfo->PersonalOnly = GetIntValue(group, "FWDPersonalsOnly"); + ForwardingInfo->SendNew = GetIntValue(group, "FWDNewImmediately"); + ForwardingInfo->FwdInterval = GetIntValue(group, "FwdInterval"); + ForwardingInfo->RevFwdInterval = GetIntValue(group, "RevFWDInterval"); + ForwardingInfo->MaxFBBBlockSize = GetIntValue(group, "MaxFBBBlock"); + ForwardingInfo->ConTimeout = GetIntValue(group, "ConTimeout"); + + if (ForwardingInfo->MaxFBBBlockSize == 0) + ForwardingInfo->MaxFBBBlockSize = 10000; + + if (ForwardingInfo->FwdInterval == 0) + ForwardingInfo->FwdInterval = 3600; + + if (ForwardingInfo->ConTimeout == 0) + ForwardingInfo->ConTimeout = 120; + + GetStringValue(group, "BBSHA", Temp); + + if (Temp[0]) + ForwardingInfo->BBSHA = _strdup(Temp); + else + ForwardingInfo->BBSHA = _strdup(""); + } + } + else + { +#ifndef LINBPQ + + int retCode,Type,Vallen; + + strcat(RegKey, user->Call); + retCode = RegOpenKeyEx (REGTREE, RegKey, 0, KEY_QUERY_VALUE, &hKey); + + if (retCode != ERROR_SUCCESS) + return; + else + { + ForwardingInfo->ConnectScript = RegGetMultiStringValue(hKey, "Connect Script"); + ForwardingInfo->TOCalls = RegGetMultiStringValue(hKey, "TOCalls"); + ForwardingInfo->ATCalls = RegGetMultiStringValue(hKey, "ATCalls"); + ForwardingInfo->Haddresses = RegGetMultiStringValue(hKey, "HRoutes"); + ForwardingInfo->HaddressesP = RegGetMultiStringValue(hKey, "HRoutesP"); + ForwardingInfo->FWDTimes = RegGetMultiStringValue(hKey, "FWD Times"); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Enabled", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->Enabled,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "RequestReverse", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->ReverseFlag,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "AllowCompressed", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowCompressed,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Use B1 Protocol", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB1,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Use B2 Protocol", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB2,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "SendCTRLZ", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendCTRLZ,(ULONG *)&Vallen); + + if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) + ForwardingInfo->AllowCompressed = TRUE; + + Vallen=4; + retCode += RegQueryValueEx(hKey, "FWD Personals Only", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->PersonalOnly,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "FWD New Immediately", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendNew,(ULONG *)&Vallen); + + Vallen=4; + RegQueryValueEx(hKey,"FWDInterval",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->FwdInterval,(ULONG *)&Vallen); + + Vallen=4; + RegQueryValueEx(hKey,"RevFWDInterval",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->RevFwdInterval,(ULONG *)&Vallen); + + RegQueryValueEx(hKey,"MaxFBBBlock",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->MaxFBBBlockSize,(ULONG *)&Vallen); + + if (ForwardingInfo->MaxFBBBlockSize == 0) + ForwardingInfo->MaxFBBBlockSize = 10000; + + if (ForwardingInfo->FwdInterval == 0) + ForwardingInfo->FwdInterval = 3600; + + Vallen=0; + retCode = RegQueryValueEx(hKey,"BBSHA",0 , (ULONG *)&Type,NULL, (ULONG *)&Vallen); + + if (retCode != 0) + { + // No Key - Get from WP?? + + WPRec * ptr = LookupWP(user->Call); + + if (ptr) + { + if (ptr->first_homebbs) + { + ForwardingInfo->BBSHA = _strdup(ptr->first_homebbs); + } + } + } + + if (Vallen) + { + ForwardingInfo->BBSHA = malloc(Vallen); + RegQueryValueEx(hKey, "BBSHA", 0, (ULONG *)&Type, ForwardingInfo->BBSHA,(ULONG *)&Vallen); + } + + RegCloseKey(hKey); + } +#endif + } + + // Convert FWD Times and H Addresses + + if (ForwardingInfo->FWDTimes) + SetupFwdTimes(ForwardingInfo); + + if (ForwardingInfo->Haddresses) + SetupHAddreses(ForwardingInfo); + + if (ForwardingInfo->HaddressesP) + SetupHAddresesP(ForwardingInfo); + + if (ForwardingInfo->BBSHA) + { + if (ForwardingInfo->BBSHA[0]) + SetupHAElements(ForwardingInfo); + else + { + free(ForwardingInfo->BBSHA); + ForwardingInfo->BBSHA = NULL; + } + } + + for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + // If any forward bits are set, increment count on BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + user->ForwardingInfo->MsgCount++; + } + } + } +} + +VOID * GetMultiStringValue(config_setting_t * group, char * ValueName) +{ + char * ptr1; + char * MultiString = NULL; + const char * ptr; + int Count = 0; + char ** Value; + config_setting_t *setting; + char * Save; + + Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values + Value[0] = NULL; + + setting = config_setting_get_member (group, ValueName); + + if (setting) + { + ptr = config_setting_get_string (setting); + + Save = _strdup(ptr); // DOnt want to change config string + ptr = Save; + + while (ptr && strlen(ptr)) + { + ptr1 = strchr(ptr, '|'); + + if (ptr1) + *(ptr1++) = 0; + + if (strlen(ptr)) // ignore null elements + { + Value = realloc(Value, (Count+2) * sizeof(void *)); + Value[Count++] = _strdup(ptr); + } + ptr = ptr1; + } + free(Save); + } + + Value[Count] = NULL; + return Value; +} + + +VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName) +{ +#ifdef LINBPQ + return NULL; +#else + int retCode,Type,Vallen; + char * MultiString = NULL; + int ptr, len; + int Count = 0; + char ** Value; + + Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values + + Value[0] = NULL; + + Vallen=0; + + + retCode = RegQueryValueEx(hKey, ValueName, 0, (ULONG *)&Type, NULL, (ULONG *)&Vallen); + + if ((retCode != 0) || (Vallen < 3)) // Two nulls means empty multistring + { + free(Value); + return FALSE; + } + + MultiString = malloc(Vallen); + + retCode = RegQueryValueEx(hKey, ValueName, 0, + (ULONG *)&Type,(UCHAR *)MultiString,(ULONG *)&Vallen); + + ptr=0; + + while (MultiString[ptr]) + { + len=strlen(&MultiString[ptr]); + + Value = realloc(Value, (Count+2) * sizeof(void *)); + Value[Count++] = _strdup(&MultiString[ptr]); + ptr+= (len + 1); + } + + Value[Count] = NULL; + + free(MultiString); + + return Value; +#endif +} + +VOID FreeForwardingStruct(struct UserInfo * user) +{ + struct BBSForwardingInfo * ForwardingInfo; + int i; + + + ForwardingInfo = user->ForwardingInfo; + + FreeList(ForwardingInfo->TOCalls); + FreeList(ForwardingInfo->ATCalls); + FreeList(ForwardingInfo->Haddresses); + FreeList(ForwardingInfo->HaddressesP); + + i=0; + if(ForwardingInfo->HADDRS) + { + while(ForwardingInfo->HADDRS[i]) + { + FreeList(ForwardingInfo->HADDRS[i]); + i++; + } + free(ForwardingInfo->HADDRS); + free(ForwardingInfo->HADDROffet); + } + + i=0; + if(ForwardingInfo->HADDRSP) + { + while(ForwardingInfo->HADDRSP[i]) + { + FreeList(ForwardingInfo->HADDRSP[i]); + i++; + } + free(ForwardingInfo->HADDRSP); + } + + FreeList(ForwardingInfo->ConnectScript); + FreeList(ForwardingInfo->FWDTimes); + if (ForwardingInfo->FWDBands) + { + i=0; + while(ForwardingInfo->FWDBands[i]) + { + free(ForwardingInfo->FWDBands[i]); + i++; + } + free(ForwardingInfo->FWDBands); + } + if (ForwardingInfo->BBSHAElements) + { + i=0; + while(ForwardingInfo->BBSHAElements[i]) + { + free(ForwardingInfo->BBSHAElements[i]); + i++; + } + free(ForwardingInfo->BBSHAElements); + } + free(ForwardingInfo->BBSHA); + +} + +VOID FreeList(char ** Hddr) +{ + VOID ** Save; + + if (Hddr) + { + Save = (void **)Hddr; + while(Hddr[0]) + { + free(Hddr[0]); + Hddr++; + } + free(Save); + } +} + + +VOID ReinitializeFWDStruct(struct UserInfo * user) +{ + if (user->ForwardingInfo) + { + FreeForwardingStruct(user); + free(user->ForwardingInfo); + } + + SetupForwardingStruct(user); + +} + +VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo) +{ + char ** Times = ForwardingInfo->FWDTimes; + int Start, End; + int Count = 0; + + ForwardingInfo->FWDBands = zalloc(sizeof(struct FWDBAND)); + + if (Times) + { + while(Times[0]) + { + ForwardingInfo->FWDBands = realloc(ForwardingInfo->FWDBands, (Count+2)* sizeof(struct FWDBAND)); + ForwardingInfo->FWDBands[Count] = zalloc(sizeof(struct FWDBAND)); + + Start = atoi(Times[0]); + End = atoi(&Times[0][5]); + + ForwardingInfo->FWDBands[Count]->FWDStartBand = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; + ForwardingInfo->FWDBands[Count]->FWDEndBand = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; + + Count++; + Times++; + } + ForwardingInfo->FWDBands[Count] = NULL; + } +} +void StartForwarding(int BBSNumber, char ** TempScript) +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + time_t NOW = time(NULL); + + + for (user = BBSChain; user; user = user->BBSNext) + { + // See if any messages are queued for this BBS + + ForwardingInfo = user->ForwardingInfo; + + if ((BBSNumber == 0) || (user->BBSNumber == BBSNumber)) + if (ForwardingInfo) + if (ForwardingInfo->Enabled || BBSNumber) // Menu Command overrides enable + if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) + if (BBSNumber || SeeifMessagestoForward(user->BBSNumber, NULL) || + (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) // Menu Command overrides Reverse + { + user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used + + // See if TempScript requested + + if (user->ForwardingInfo->TempConnectScript) + FreeList(user->ForwardingInfo->TempConnectScript); + + user->ForwardingInfo->TempConnectScript = TempScript; + + if (ConnecttoBBS(user)) + ForwardingInfo->Forwarding = TRUE; + } + } + + return; +} + +size_t fwritex(CIRCUIT * conn, void * _Str, size_t _Size, size_t _Count, FILE * _File) +{ + if (_File) + return fwrite(_Str, _Size, _Count, _File); + + // Appending to MailBuffer + + memcpy(&conn->MailBuffer[conn->InputLen], _Str, _Count); + conn->InputLen += (int)_Count; + + return _Count; +} + + +BOOL ForwardMessagestoFile(CIRCUIT * conn, char * FN) +{ + BOOL AddCRLF = FALSE; + BOOL AutoImport = FALSE; + FILE * Handle = NULL; + char * Context; + BOOL Email = FALSE; + time_t now = time(NULL); + char * param; + + FN = strtok_s(FN, " ,", &Context); + + param = strtok_s(NULL, " ,", &Context); + + if (param) + { + if (_stricmp(param, "ADDCRLF") == 0) + AddCRLF = TRUE; + + if (_stricmp(param, "AutoImport") == 0) + AutoImport = TRUE; + + param = strtok_s(NULL, " ,", &Context); + + if (param) + { + if (_stricmp(param, "ADDCRLF") == 0) + AddCRLF = TRUE; + + if (_stricmp(param, "AutoImport") == 0) + AutoImport = TRUE; + + } + } + // If FN is an email address, write to a temp file, and send via rms or emali gateway + + if (strchr(FN, '@') || _memicmp(FN, "RMS:", 4) == 0) + { + Email = TRUE; + AddCRLF =TRUE; + conn->MailBuffer=malloc(100000); + conn->MailBufferSize=100000; + conn->InputLen = 0; + } + else + { + Handle = fopen(FN, "ab"); + + if (Handle == NULL) + { + int err = GetLastError(); + Logprintf(LOG_BBS, conn, '!', "Failed to open Export File %s", FN); + return FALSE; + } + } + + while (FindMessagestoForward(conn)) + { + struct MsgInfo * Msg; + struct tm * tm; + time_t temp; + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + int MsgLen; + char * MsgPtr; + char Line[256]; + int len; + struct UserInfo * user = conn->UserPointer; + int Index = 0; + + Msg = conn->FwdMsg; + + if (Email) + if (conn->InputLen + Msg->length + 500 > conn->MailBufferSize) + break; + + if (Msg->type == 'P') + Index = PMSG; + else if (Msg->type == 'B') + Index = BMSG; + else if (Msg->type == 'T') + Index = TMSG; + + + if (Msg->via[0]) + len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, + Msg->via, Msg->from, Msg->bid); + else + len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); + + fwritex(conn, Line, 1, len, Handle); + + len = sprintf(Line, "%s\r\n", Msg->title); + fwritex(conn, Line, 1, len, Handle); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + MsgLen = conn->FwdMsg->length; + + // If a B2 Message, remove B2 Header + + if (conn->FwdMsg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + MsgPtr = strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + conn->FwdMsg->number, BBSName, HRoute, RlineVer); + + fwritex(conn, Line, 1, len, Handle); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + fwritex(conn, "\r\n", 1, 2, Handle); + + fwritex(conn, MsgPtr, 1, MsgLen, Handle); + + if (MsgPtr[MsgLen - 2] == '\r') + fwritex(conn, "/EX\r\n", 1, 5, Handle); + else + fwritex(conn, "\r\n/EX\r\n", 1, 7, Handle); + + if (AddCRLF) + fwritex(conn, "\r\n", 1, 2, Handle); + + free(MsgBytes); + + user->Total.MsgsSent[Index]++; + user->Total.BytesForwardedOut[Index] += MsgLen; + + Msg->datechanged = now; + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->UserPointer->ForwardingInfo->MsgCount--; + } + + if (Email) + { + struct MsgInfo * Msg; + BIDRec * BIDRec; + + if (conn->InputLen == 0) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + return TRUE; + } + + // Allocate a message Record slot + + Msg = AllocateMsgRecord(); + + // Set number here so they remain in sequence + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + FreeSemaphore(&MsgNoSemaphore); + MsgnotoMsg[Msg->number] = Msg; + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datecreated = Msg->datechanged = Msg->datereceived = now; + + strcpy(Msg->from, BBSName); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + if (AutoImport) + sprintf(Msg->title, "Batched messages for AutoImport from BBS %s", BBSName); + else + sprintf(Msg->title, "Batched messages from BBS %s", BBSName); + + Msg->length = conn->InputLen; + CreateMessageFile(conn, Msg); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + if (_memicmp(FN, "SMTP:", 5) == 0) + { + strcpy(Msg->via, &FN[5]); + SMTPMsgCreated=TRUE; + } + else + { + strcpy(Msg->to, "RMS"); + if (_memicmp(FN, "RMS:", 4) == 0) + strcpy(Msg->via, &FN[4]); + else + strcpy(Msg->via, FN); + } + + MatchMessagetoBBSList(Msg, conn); + + SaveMessageDatabase(); + SaveBIDDatabase(); + } + else + fclose(Handle); + + SaveMessageDatabase(); + return TRUE; +} + +BOOL ForwardMessagetoFile(struct MsgInfo * Msg, FILE * Handle) +{ + struct tm * tm; + time_t temp; + + char * MsgBytes = ReadMessageFile(Msg->number); + char * MsgPtr; + char Line[256]; + int len; + int MsgLen = Msg->length; + + if (Msg->via[0]) + len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, + Msg->via, Msg->from, Msg->bid); + else + len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); + + fwrite(Line, 1, len, Handle); + + len = sprintf(Line, "%s\r\n", Msg->title); + fwrite(Line, 1, len, Handle); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + MsgLen = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + + // If a B2 Message, remove B2 Header + + if (Msg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + + MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + Msg->number, BBSName, HRoute, RlineVer); + + fwrite(Line, 1, len, Handle); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + fwrite("\r\n", 1, 2, Handle); + + fwrite(MsgPtr, 1, MsgLen, Handle); + + if (MsgPtr[MsgLen - 2] == '\r') + fwrite("/EX\r\n", 1, 5, Handle); + else + fwrite("\r\n/EX\r\n", 1, 7, Handle); + + free(MsgBytes); + + return TRUE; + +} + +BOOL ConnecttoBBS (struct UserInfo * user) +{ + int n, p; + CIRCUIT * conn; + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + + for (n = NumberofStreams-1; n >= 0 ; n--) + { + conn = &Connections[n]; + + if (conn->Active == FALSE) + { + p = conn->BPQStream; + memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything + conn->BPQStream = p; + + // Can't set Active until Connected or Stuck Session detertor can clear session. + // But must set Active before Connected() runs or will appear is Incoming Connect. + // Connected() is semaphored, so get semaphore before ConnectUsingAppl + // Probably better to semaphore lost session code instead + + + strcpy(conn->Callsign, user->Call); + conn->BBSFlags |= (RunningConnectScript | OUTWARDCONNECT); + conn->UserPointer = user; + + Logprintf(LOG_BBS, conn, '|', "Connecting to BBS %s", user->Call); + + ForwardingInfo->MoreLines = TRUE; + + GetSemaphore(&ConSemaphore, 1); + conn->Active = TRUE; + ConnectUsingAppl(conn->BPQStream, BBSApplMask); + FreeSemaphore(&ConSemaphore); + + // If we are sending to a dump pms we may need to connect using the message sender's callsign. + // But we wont know until we run the connect script, which is a bit late to change call. Could add + // flag to forwarding config, but easier to look for SETCALLTOSENDER in the connect script. + + if (strstr(ForwardingInfo->ConnectScript[0], "SETCALLTOSENDER")) + { + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + // We have a message to send + + struct MsgInfo * Msg; + unsigned char AXCall[7]; + + Msg = conn->FwdMsg; + ConvToAX25(Msg->from, AXCall); + ChangeSessionCallsign(p, AXCall); + + conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER | NEWPACCOM; + conn->NextMessagetoForward = 0; // was set by FindMessages + } + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; + } +#ifdef LINBPQ + { + BPQVECSTRUC * SESS; + SESS = &BPQHOSTVECTOR[conn->BPQStream - 1]; + + if (SESS->HOSTSESSION == NULL) + { + Logprintf(LOG_BBS, NULL, '|', "No L4 Sessions for connect to BBS %s", user->Call); + return FALSE; + } + + SESS->HOSTSESSION->Secure_Session = 1; + } +#endif + + strcpy(conn->Callsign, user->Call); + + // Connected Event will trigger connect to remote system + + RefreshMainWindow(); + + return TRUE; + } + } + + Logprintf(LOG_BBS, NULL, '|', "No Free Streams for connect to BBS %s", user->Call); + + return FALSE; + +} + +struct DelayParam +{ + struct UserInfo * User; + CIRCUIT * conn; + int Delay; +}; + +struct DelayParam DParam; // Not 100% safe, but near enough + +VOID ConnectDelayThread(struct DelayParam * DParam) +{ + struct UserInfo * User = DParam->User; + int Delay = DParam->Delay; + + User->ForwardingInfo->Forwarding = TRUE; // Minimize window for two connects + + Sleep(Delay); + + User->ForwardingInfo->Forwarding = TRUE; + ConnecttoBBS(User); + + return; +} + +VOID ConnectPauseThread(struct DelayParam * DParam) +{ + CIRCUIT * conn = DParam->conn; + int Delay = DParam->Delay; + char Msg[] = "Pause Ok\r "; + + Sleep(Delay); + + ProcessBBSConnectScript(conn, Msg, 9); + + return; +} + + +/* +BOOL ProcessBBSConnectScriptInner(CIRCUIT * conn, char * Buffer, int len); + + +BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) +{ + BOOL Ret; + GetSemaphore(&ScriptSEM); + Ret = ProcessBBSConnectScriptInner(conn, Buffer, len); + FreeSemaphore(&ScriptSEM); + + return Ret; +} +*/ + +BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) +{ + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + char ** Scripts; + char callsign[10]; + int port, sesstype, paclen, maxframe, l4window; + char * ptr, * ptr2; + + WriteLogLine(conn, '<',Buffer, len-1, LOG_BBS); + + Buffer[len]=0; + _strupr(Buffer); + + if (ForwardingInfo->TempConnectScript) + Scripts = ForwardingInfo->TempConnectScript; + else + Scripts = ForwardingInfo->ConnectScript; + + if (ForwardingInfo->ScriptIndex == -1) + { + // First Entry - if first line is TIMES, check and skip forward if necessary + + int n = 0; + int Start, End; + time_t now = time(NULL), StartSecs, EndSecs; + char * Line; + + if (Localtime) + now -= (time_t)_MYTIMEZONE; + + now %= 86400; + Line = Scripts[n]; + + if (_memicmp(Line, "TIMES", 5) == 0) + { + NextBand: + Start = atoi(&Line[6]); + End = atoi(&Line[11]); + + StartSecs = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; + EndSecs = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; + + if ((StartSecs <= now) && (EndSecs >= now)) + goto InBand; // In band + + // Look for next TIME + NextLine: + Line = Scripts[++n]; + + if (Line == NULL) + { + // No more lines - Disconnect + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Line, "TIMES", 5) != 0) + goto NextLine; + else + goto NextBand; +InBand: + ForwardingInfo->ScriptIndex = n; + } + + } + else + { + // Dont check first time through + + if (strcmp(Buffer, "*** CONNECTED ") != 0) + { + if (Scripts[ForwardingInfo->ScriptIndex] == NULL || + _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished + _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished + { + ForwardingInfo->MoreLines = FALSE; + } + if (!ForwardingInfo->MoreLines) + goto CheckForSID; + } + } + + if (strstr(Buffer, "BUSY") || strstr(Buffer, "FAILURE") || + (strstr(Buffer, "DOWNLINK") && strstr(Buffer, "ATTEMPTING") == 0) || + strstr(Buffer, "SORRY") || strstr(Buffer, "INVALID") || strstr(Buffer, "RETRIED") || + strstr(Buffer, "NO CONNECTION TO") || strstr(Buffer, "ERROR - ") || + strstr(Buffer, "UNABLE TO CONNECT") || strstr(Buffer, "DISCONNECTED") || + strstr(Buffer, "FAILED TO CONNECT") || strstr(Buffer, "REJECTED")) + { + // Connect Failed + + char * Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + int Delay = 1000; + + // Look for an alternative connect block (Starting with ELSE) + + ElseLoop: + + // Skip any comments + + while (Cmd && ((strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#'))) + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + + // TIMES terminates a script + + if (Cmd == 0 || _memicmp(Cmd, "TIMES", 5) == 0) // Only Check until script is finished + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "ELSE", 4) != 0) + { + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + goto ElseLoop; + } + + if (_memicmp(&Cmd[5], "DELAY", 5) == 0) + Delay = atoi(&Cmd[10]) * 1000; + else + Delay = 1000; + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + + DParam.Delay = Delay; + DParam.User = conn->UserPointer; + + _beginthread((void (*)(void *))ConnectDelayThread, 0, &DParam); + + return FALSE; + } + + // The pointer is only updated when we get the connect, so we can tell when the last line is acked + // The first entry is always from Connected event, so don't have to worry about testing entry -1 below + + + // NETROM to KA node returns + + //c 1 milsw + //WIRAC:N9PMO-2} Connected to MILSW + //###CONNECTED TO NODE MILSW(N9ZXS) CHANNEL A + //You have reached N9ZXS's KA-Node MILSW + //ENTER COMMAND: B,C,J,N, or Help ? + + //C KB9PRF-7 + //###LINK MADE + //###CONNECTED TO NODE KB9PRF-7(KB9PRF-4) CHANNEL A + + // Look for (Space)Connected so we aren't fooled by ###CONNECTED TO NODE, which is not + // an indication of a connect. + + if (strstr(Buffer, " CONNECTED") || strstr(Buffer, "PACLEN") || strstr(Buffer, "IDLETIME") || + strstr(Buffer, "OK") || strstr(Buffer, "###LINK MADE") || strstr(Buffer, "VIRTUAL CIRCUIT ESTABLISHED")) + { + // If connected to SYNC, save IP address and port + + char * Cmd; + + if (strstr(Buffer, "*** CONNECTED TO SYNC")) + { + char * IPAddr = &Buffer[22]; + char * Port = strlop(IPAddr, ':'); + + if (Port) + { + if (conn->SyncHost) + free(conn->SyncHost); + + conn->SyncHost = _strdup(IPAddr); + conn->SyncPort = atoi(Port); + } + } + + if (conn->SkipConn) + { + conn->SkipConn = FALSE; + return TRUE; + } + + LoopBack: + + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + + // Only Check until script is finished + + if (Cmd && (strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#')) + goto LoopBack; // Blank line + + if (Cmd && _memicmp(Cmd, "TIMES", 5) != 0 && _memicmp(Cmd, "ELSE", 4) != 0) // Only Check until script is finished + { + if (_memicmp(Cmd, "MSGTYPE", 7) == 0) + { + char * ptr; + + // Select Types to send. Only send types in param. Only reverse if R in param + + _strupr(Cmd); + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; + + strcpy(conn->MSGTYPES, &Cmd[8]); + + if (strchr(&Cmd[8], 'R')) conn->DoReverse = TRUE; + + ptr = strchr(&Cmd[8], 'B'); + + if (ptr) + { + conn->SendB = TRUE; + conn->MaxBLen = atoi(++ptr); + if (conn->MaxBLen == 0) conn->MaxBLen = 99999999; + } + + ptr = strchr(&Cmd[8], 'T'); + + if (ptr) + { + conn->SendT = TRUE; + conn->MaxTLen = atoi(++ptr); + if (conn->MaxTLen == 0) conn->MaxTLen = 99999999; + } + ptr = strchr(&Cmd[8], 'P'); + + if (ptr) + { + conn->SendP = TRUE; + conn->MaxPLen = atoi(++ptr); + if (conn->MaxPLen == 0) conn->MaxPLen = 99999999; + } + + // If nothing to do, terminate script + + if (conn->DoReverse || SeeifMessagestoForward(conn->UserPointer->BBSNumber, conn)) + goto LoopBack; + + Logprintf(LOG_BBS, conn, '?', "Nothing to do - quitting"); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "INTERLOCK ", 10) == 0) + { + // Used to limit connects on a port to 1 + + int Port; + char Option[80]; + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + sscanf(&Cmd[10], "%d %s", &Port, &Option[0]); + + if (CountConnectionsOnPort(Port)) + { + Logprintf(LOG_BBS, conn, '?', "Interlocked Port is busy - quitting"); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + goto LoopBack; + } + + if (_memicmp(Cmd, "RADIO AUTH", 10) == 0) + { + // Generate a Password to enable RADIO commands on a remote node + char AuthCommand[80]; + + _strupr(Cmd); + strcpy(AuthCommand, Cmd); + + CreateOneTimePassword(&AuthCommand[11], &Cmd[11], 0); + + nodeprintf(conn, "%s\r", AuthCommand); + return TRUE; + } + + if (_memicmp(Cmd, "SKIPCON", 7) == 0) + { + // Remote Node sends Connected in CTEXT - we need to swallow it + + conn->SkipConn = TRUE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SendWL2KPM", 10) == 0|| _memicmp(Cmd, "SendWL2KFW", 10) == 0) + { + // Send ;FW: command + + conn->SendWL2KFW = TRUE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SKIPPROMPT", 10) == 0) + { + // Remote Node sends > at end of CTEXT - we need to swallow it + + conn->SkipPrompt++; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "TEXTFORWARDING", 10) == 0) + { + conn->BBSFlags |= TEXTFORWARDING; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SETCALLTOSENDER", 15) == 0) + { + conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "RADIOONLY", 9) == 0) + { + conn->BBSFlags |= WINLINKRO; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SYNC", 4) == 0) + { + conn->BBSFlags |= SYNCMODE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "NEEDLF", 6) == 0) + { + conn->BBSFlags |= NEEDLF; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "MCASTRX", 6) == 0) + { + conn->BBSFlags |= MCASTRX; + conn->MCastListenTime = atoi(&Cmd[7]) * 6; // Time to run session for *6 as value is mins put timer ticks 10 secs + + // send MCAST to Node + + nodeprintfEx(conn, "MCAST\r"); + return TRUE; + } + + if (_memicmp(Cmd, "FLARQ", 5) == 0) + { + conn->BBSFlags |= FLARQMAIL; + + CheckForEnd: + if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || + memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished + memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished + ForwardingInfo->MoreLines = FALSE; + + goto LoopBack; + } + if (_memicmp(Cmd, "PAUSE", 5) == 0) + { + // Pause script + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + DParam.Delay = atoi(&Cmd[6]) * 1000; + DParam.conn = conn; + + _beginthread((void (*)(void *))ConnectPauseThread, 0, &DParam); + + return TRUE; + } + + if (_memicmp(Cmd, "FILE", 4) == 0) + { + if (Cmd[4] == 0) + { + // Missing Filename + + Logprintf(LOG_BBS, conn, '!', "Export file name missing"); + } + else + ForwardMessagestoFile(conn, &Cmd[5]); + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "SMTP", 4) == 0) + { + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + + SendAMPRSMTP(conn); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + + if (_memicmp(Cmd, "IMPORT", 6) == 0) + { + char * File, * Context; + int Num; + char * Temp = _strdup(Cmd); + + File = strtok_s(&Temp[6], " ", &Context); + + if (File && File[0]) + { + Num = ImportMessages(NULL, File, TRUE); + + Logprintf(LOG_BBS, NULL, '|', "Imported %d Message(s) from %s", Num, File); + + if (Context && _stricmp(Context, "delete") == 0) + DeleteFile(File); + } + free(Temp); + + if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || + memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished + memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + goto LoopBack; + } + + // Anything else is sent to Node + + // Replace \ with # so can send commands starting with # + + if (Cmd[0] == '\\') + { + Cmd[0] = '#'; + nodeprintfEx(conn, "%s\r", Cmd); + Cmd[0] = '\\'; // Put \ back in script + } + else + nodeprintfEx(conn, "%s\r", Cmd); + + return TRUE; + } + + // End of script. + + ForwardingInfo->MoreLines = FALSE; + + if (conn->BBSFlags & MCASTRX) + { + // No session with Multicast, so no SID + + conn->BBSFlags &= ~RunningConnectScript; + return TRUE; + } + + if (conn->BBSFlags & FLARQMAIL) + { + // FLARQ doesnt send a prompt - Just send message(es) + + conn->UserPointer->Total.ConnectsOut++; + conn->BBSFlags &= ~RunningConnectScript; + ForwardingInfo->LastReverseForward = time(NULL); + + // Update Paclen + + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + + if (paclen > 0) + conn->paclen = paclen; + + SendARQMail(conn); + return TRUE; + } + + + return TRUE; + } + + ptr = strchr(Buffer, '}'); + + if (ptr && ForwardingInfo->MoreLines) // Beware it could be part of ctext + { + // Could be respsonse to Node Command + + ptr+=2; + + ptr2 = strchr(&ptr[0], ' '); + + if (ptr2) + { + if (_memicmp(ptr, Scripts[ForwardingInfo->ScriptIndex], ptr2-ptr) == 0) // Reply to last sscript command + { + if (Scripts[ForwardingInfo->ScriptIndex+1] && _memicmp(Scripts[ForwardingInfo->ScriptIndex+1], "else", 4) == 0) + { + // stray match or misconfigured + + return TRUE; + } + + ForwardingInfo->ScriptIndex++; + + if (Scripts[ForwardingInfo->ScriptIndex]) + if (_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) != 0) + nodeprintf(conn, "%s\r", Scripts[ForwardingInfo->ScriptIndex]); + + return TRUE; + } + } + } + + // Not Success or Fail. If last line is still outstanding, wait fot Response + // else look for SID or Prompt + + if (conn->SkipPrompt && Buffer[len-2] == '>') + { + conn->SkipPrompt--; + return TRUE; + } + + if (ForwardingInfo->MoreLines) + return TRUE; + + // No more steps, Look for SID or Prompt + +CheckForSID: + + if (strstr(Buffer, "POSYNCHELLO")) // RMS RELAY Sync process + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + ForwardingInfo->LastReverseForward = time(NULL); + + ProcessLine(conn, 0, Buffer, len); + return FALSE; + } + + if (strstr(Buffer, "SORRY, NO")) // URONODE + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (memcmp(Buffer, ";PQ: ", 5) == 0) + { + // Secure CMS challenge + + int Len; + struct UserInfo * User = conn->UserPointer; + char * Pass = User->CMSPass; + int Response ; + char RespString[12]; + char ConnectingCall[10]; + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + + SESS += conn->BPQStream - 1; + + ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); + + strlop(ConnectingCall, ' '); + + if (Pass[0] == 0) + { + Pass = User->pass; // Old Way + if (Pass[0] == 0) + { + strlop(ConnectingCall, '-'); + User = LookupCall(ConnectingCall); + if (User) + Pass = User->CMSPass; + } + } + + // + + Response = GetCMSHash(&Buffer[5], Pass); + + sprintf(RespString, "%010d", Response); + + Len = sprintf(conn->SecureMsg, ";PR: %s\r", &RespString[2]); + + // Save challengs in case needed for FW lines + + strcpy(conn->PQChallenge, &Buffer[5]); + + return FALSE; + } + + + if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID + { + // Update PACLEN + + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + + if (paclen > 0) + conn->paclen = paclen; + + + Parse_SID(conn, &Buffer[1], len-4); + + if (conn->BBSFlags & FBBForwarding) + { + conn->FBBIndex = 0; // ready for first block; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + conn->FBBChecksum = 0; + } + + return TRUE; + } + + if (memcmp(Buffer, "[PAKET ", 7) == 0) + { + conn->BBSFlags |= BBS; + conn->BBSFlags |= MBLFORWARDING; + } + + if (Buffer[len-2] == '>') + { + if (conn->SkipPrompt) + { + conn->SkipPrompt--; + return TRUE; + } + + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + conn->BBSFlags &= ~RunningConnectScript; + ForwardingInfo->LastReverseForward = time(NULL); + + if (memcmp(Buffer, "[AEA PK", 7) == 0 || (conn->BBSFlags & TEXTFORWARDING)) + { + // PK232. Don't send a SID, and switch to Text Mode + + conn->BBSFlags |= (BBS | TEXTFORWARDING); + conn->Flags |= SENDTITLE; + + // Send Message. There is no mechanism for reverse forwarding + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg; + + // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ + + Msg = conn->FwdMsg; + + if ((conn->BBSFlags & SETCALLTOSENDER)) + nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); + else + nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, + Msg->from, Msg->bid); + } + else + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + return TRUE; + } + + if (strcmp(conn->Callsign, "RMS") == 0 || conn->SendWL2KFW) + { + // Build a ;FW: line with all calls with PollRMS Set + + // According to Lee if you use secure login the first + // must be the BBS call + // Actually I don't think we need the first, + // as that is implied + + // If a secure password is available send the new + // call|response format. + + // I think this should use the session callsign, which + // normally will be the BBS ApplCall, and not the BBS Name, + // but coudl be changed by *** LINKED + + int i, s; + char FWLine[10000] = ";FW: "; + struct UserInfo * user; + char RMSCall[20]; + char ConnectingCall[10]; + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + + SESS += conn->BPQStream - 1; + + ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); + strlop(ConnectingCall, ' '); + + strcat (FWLine, ConnectingCall); + + for (i = 0; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_POLLRMS) + { + if (user->RMSSSIDBits == 0) user->RMSSSIDBits = 1; + + for (s = 0; s < 16; s++) + { + if (user->RMSSSIDBits & (1 << s)) + { + if (s) + sprintf(RMSCall, "%s-%d", user->Call, s); + else + sprintf(RMSCall, "%s", user->Call); + + // We added connectingcall at front + + if (strcmp(RMSCall, ConnectingCall) != 0) + { + strcat(FWLine, " "); + strcat(FWLine, RMSCall); + + if (user->CMSPass[0]) + { + int Response = GetCMSHash(conn->PQChallenge, user->CMSPass); + char RespString[12]; + + sprintf(RespString, "%010d", Response); + strcat(FWLine, "|"); + strcat(FWLine, &RespString[2]); + } + } + } + } + } + } + + strcat(FWLine, "\r"); + + nodeprintf(conn, FWLine); + } + + // Only declare B1 and B2 if other end did, and we are configued for it + + nodeprintfEx(conn, BBSSID, "BPQ-", + Ver[0], Ver[1], Ver[2], Ver[3], + (conn->BBSFlags & FBBCompressed) ? "B" : "", + (conn->BBSFlags & FBBB1Mode && !(conn->BBSFlags & FBBB2Mode)) ? "1" : "", + (conn->BBSFlags & FBBB2Mode) ? "2" : "", + (conn->BBSFlags & FBBForwarding) ? "F" : "", + (conn->BBSFlags & WINLINKRO) ? "" : "J"); + + if (conn->SecureMsg[0]) + { + struct UserInfo * user; + BBSputs(conn, conn->SecureMsg); + conn->SecureMsg[0] = 0; + + // Also send a Location Comment Line + + //; GM8BPQ-10 DE G8BPQ (IO92KX) + //; WL2K DE GM8BPQ () (PAT) + + user = LookupCall(BBSName); + + if (LOC && LOC[0]) + nodeprintf(conn, "; WL2K DE %s (%s)\r", BBSName, LOC); + } + + if (conn->BPQBBS && conn->MSGTYPES[0]) + + // Send a ; MSGTYPES to control what he sends us + + nodeprintf(conn, "; MSGTYPES %s\r", conn->MSGTYPES); + + if (conn->BBSFlags & FBBForwarding) + { + if (!FBBDoForward(conn)) // Send proposal if anthing to forward + { + if (conn->DoReverse) + FBBputs(conn, "FF\r"); + else + { + FBBputs(conn, "FQ\r"); + conn->CloseAfterFlush = 20; // 2 Secs + } + } + + return TRUE; + } + + return TRUE; + } + + return TRUE; +} + +VOID Parse_SID(CIRCUIT * conn, char * SID, int len) +{ + ChangeSessionIdletime(conn->BPQStream, BBSIDLETIME); // Default Idletime for BBS Sessions + + // scan backwards for first '-' + + if (strstr(SID, "BPQCHATSERVER")) + { + Disconnect(conn->BPQStream); + return; + } + + if (strstr(SID, "RMS Ex") || strstr(SID, "Winlink Ex")) + { + conn->RMSExpress = TRUE; + conn->Paclink = FALSE; + conn->PAT = FALSE; + + // Set new RMS Users as RMS User + + if (conn->NewUser) + conn->UserPointer->flags |= F_Temp_B2_BBS; + } + + if (stristr(SID, "PAT")) + { + // Set new PAT Users as RMS User + + conn->RMSExpress = FALSE; + conn->Paclink = FALSE; + conn->PAT = TRUE; + + if (conn->NewUser) + conn->UserPointer->flags |= F_Temp_B2_BBS; + } + if (strstr(SID, "Paclink")) + { + conn->RMSExpress = FALSE; + conn->Paclink = TRUE; + } + + if (strstr(SID, "WL2K-")) + { + conn->WL2K = TRUE; + conn->BBSFlags |= WINLINKRO; + } + + if (strstr(SID, "MFJ-")) + { + conn->BBSFlags |= MFJMODE; + } + + if (_memicmp(SID, "OpenBCM", 7) == 0) + { + // We should really only do this on Telnet Connections, as OpenBCM flag is used to remove relnet transparency + + + conn->OpenBCM = TRUE; + } + + if (_memicmp(SID, "PMS-3.2", 7) == 0) + { + // Paccom TNC that doesn't send newline prompt ater receiving subject + + conn->BBSFlags |= NEWPACCOM; + } + + // See if BPQ for selective forwarding + + if (strstr(SID, "BPQ")) + conn->BPQBBS = TRUE; + + while (len > 0) + { + switch (SID[len--]) + { + case '-': + + len=0; + break; + + case '$': + + conn->BBSFlags |= BBS | MBLFORWARDING; + conn->Paging = FALSE; + + break; + + case 'F': // FBB Blocked Forwarding + + // We now support blocked uncompressed. Not necessarily compatible with FBB + + if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) + { + // We need to allocate a forwarding structure + + conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; + conn->UserPointer->ForwardingInfo->AllowBlocked = TRUE; + conn->UserPointer->BBSNumber = NBBBS; + } + + if (conn->UserPointer->ForwardingInfo->AllowBlocked) + { + conn->BBSFlags |= FBBForwarding | BBS; + conn->BBSFlags &= ~MBLFORWARDING; + + conn->Paging = FALSE; + + if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) + { + // We need to allocate a forwarding structure + + conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; + conn->UserPointer->BBSNumber = NBBBS; + } + + // Allocate a Header Block + + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + } + break; + + case 'J': + + // Suspected to be associated with Winlink Radio Only + + conn->BBSFlags &= ~WINLINKRO; + break; + + case 'B': + + if (conn->UserPointer->ForwardingInfo->AllowCompressed) + { + conn->BBSFlags |= FBBCompressed; + conn->DontSaveRestartData = FALSE; // Allow restarts + + // Look for 1 or 2 or 12 as next 2 chars + + if (SID[len+2] == '1') + { + if (conn->UserPointer->ForwardingInfo->AllowB1 || + conn->UserPointer->ForwardingInfo->AllowB2) // B2 implies B1 + conn->BBSFlags |= FBBB1Mode; + + if (SID[len+3] == '2') + if (conn->UserPointer->ForwardingInfo->AllowB2) + conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) + + break; + } + + if (SID[len+2] == '2') + { + if (conn->UserPointer->ForwardingInfo->AllowB2) + conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) + + if (conn->UserPointer->ForwardingInfo->AllowB1) + conn->BBSFlags |= FBBB1Mode; // B2 should allow fallback to B1 (but RMS doesnt!) + + } + break; + } + + break; + } + } + + // Only allow blocked non-binary to other BPQ Nodes + + if ((conn->BBSFlags & FBBForwarding) && ((conn->BBSFlags & FBBCompressed) == 0) && (conn->BPQBBS == 0)) + { + // Switch back to MBL + + conn->BBSFlags |= MBLFORWARDING; + conn->BBSFlags &= ~FBBForwarding; // Turn off FBB Blocked + } + + return; +} + +VOID BBSSlowTimer() +{ + ConnectionInfo * conn; + int n; + + // Called every 10 seconds + + MCastTimer(); + + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active == TRUE) + { + // Check for stuck BBS sessions (BBS session but no Node Session) + + int state; + + GetSemaphore(&ConSemaphore, 1); + SessionStateNoAck(conn->BPQStream, &state); + FreeSemaphore(&ConSemaphore); + + if (state == 0) // No Node Session + { + // is it safe just to clear Active ?? + + conn->InputMode = 0; // So Disconnect wont save partial transfer + conn->BBSFlags = 0; + Disconnected (conn->BPQStream); + continue; + } + + if (conn->BBSFlags & MCASTRX) + MCastConTimer(conn); + + + // Check SIDTImers - used to detect failure to compete SID Handshake + + if (conn->SIDResponseTimer) + { + conn->SIDResponseTimer--; + if (conn->SIDResponseTimer == 0) + { + // Disconnect Session + + Disconnect(conn->BPQStream); + } + } + } + } + + // Flush logs + + for (n = 0; n < 4; n++) + { + if (LogHandle[n]) + { + time_t LT = time(NULL); + if ((LT - LastLogTime[n]) > 30) + { + LastLogTime[n] = LT; + fclose(LogHandle[n]); + LogHandle[n] = NULL; + } + } + } +} + + +VOID FWDTimerProc() +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + time_t NOW = time(NULL); + + for (user = BBSChain; user; user = user->BBSNext) + { + // See if any messages are queued for this BBS + + ForwardingInfo = user->ForwardingInfo; + ForwardingInfo->FwdTimer+=10; + + if (ForwardingInfo->FwdTimer >= ForwardingInfo->FwdInterval) + { + ForwardingInfo->FwdTimer=0; + + if (ForwardingInfo->FWDBands && ForwardingInfo->FWDBands[0]) + { + // Check Timebands + + struct FWDBAND ** Bands = ForwardingInfo->FWDBands; + int Count = 0; + time_t now = time(NULL); + + if (Localtime) + now -= (time_t)_MYTIMEZONE; + + now %= 86400; // Secs in day + + while(Bands[Count]) + { + if ((Bands[Count]->FWDStartBand < now) && (Bands[Count]->FWDEndBand >= now)) + goto FWD; // In band + + Count++; + } + continue; // Out of bands + } + FWD: + if (ForwardingInfo->Enabled) + { + if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) + { + //Temp Debug Code + +// Debugprintf("ReverseFlag = %d, Msgs to Forward Flag %d Msgs to Forward Count %d", +// ForwardingInfo->ReverseFlag, +// SeeifMessagestoForward(user->BBSNumber, NULL), +// CountMessagestoForward(user)); + + if (SeeifMessagestoForward(user->BBSNumber, NULL) || + (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) + + { + user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used + + + // remove any old TempScript + + if (user->ForwardingInfo->TempConnectScript) + { + FreeList(user->ForwardingInfo->TempConnectScript); + user->ForwardingInfo->TempConnectScript = NULL; + } + + if (ConnecttoBBS(user)) + ForwardingInfo->Forwarding = TRUE; + } + } + } + } + } +} + +VOID * _zalloc_dbg(size_t len, int type, char * file, int line) +{ + // ?? malloc and clear + + void * ptr; + +#ifdef WIN32 + ptr=_malloc_dbg(len, type, file, line); +#else + ptr = malloc(len); +#endif + if (ptr) + memset(ptr, 0, len); + + return ptr; +} + +struct MsgInfo * FindMessageByNumber(int msgno) + { + int m=NumberofMessages; + + struct MsgInfo * Msg; + + do + { + Msg=MsgHddrPtr[m]; + + if (Msg->number == msgno) + return Msg; + + if (Msg->number && Msg->number < msgno) // sometimes get zero msg number + return NULL; // Not found + + m--; + + } while (m > 0); + + return NULL; +} + +struct MsgInfo * FindMessageByBID(char * BID) +{ + int m = NumberofMessages; + + struct MsgInfo * Msg; + + while (m > 0) + { + Msg = MsgHddrPtr[m]; + + if (strcmp(Msg->bid, BID) == 0) + return Msg; + + m--; + } + + return NULL; +} + +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len) +{ + unsigned char hash[50]; + unsigned char key[100]; + unsigned int i, j = 0, val1, val2; + unsigned char hostname[100]=""; + + gethostname(hostname, 100); + + strcpy(key, hostname); + strcat(key, ISPPOP3Name); + + md5(key, hash); + memcpy(&hash[16], hash, 16); // in case very long password + + // String is now encoded as hex pairs, but still need to decode old format + + for (i=0; i < len; i++) + { + if (Encrypt[i] < '0' || Encrypt[i] > 'F') + goto OldFormat; + } + + // Only '0' to 'F' + + for (i=0; i < len; i++) + { + val1 = Encrypt[i++]; + val1 -= '0'; + if (val1 > 9) + val1 -= 7; + + val2 = Encrypt[i]; + val2 -= '0'; + if (val2 > 9) + val2 -= 7; + + Pass[j] = (val1 << 4) | val2; + Pass[j] ^= hash[j]; + j++; + } + + return; + +OldFormat: + + for (i=0; i < len; i++) + { + Pass[i] = Encrypt[i] ^ hash[i]; + } + + return; +} + +int EncryptPass(char * Pass, char * Encrypt) +{ + unsigned char hash[50]; + unsigned char key[100]; + unsigned int i, val; + unsigned char hostname[100]; + unsigned char extendedpass[100]; + unsigned int passlen; + unsigned char * ptr; + + gethostname(hostname, 100); + + strcpy(key, hostname); + strcat(key, ISPPOP3Name); + + md5(key, hash); + memcpy(&hash[16], hash, 16); // in case very long password + + // if password is less than 16 chars, extend with zeros + + passlen=(int)strlen(Pass); + + strcpy(extendedpass, Pass); + + if (passlen < 16) + { + for (i=passlen+1; i <= 16; i++) + { + extendedpass[i] = 0; + } + + passlen = 16; + } + + ptr = Encrypt; + Encrypt[0] = 0; + + for (i=0; i < passlen; i++) + { + val = extendedpass[i] ^ hash[i]; + ptr += sprintf(ptr, "%02X", val); + } + + return passlen * 2; +} + + + +VOID SaveIntValue(config_setting_t * group, char * name, int value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_INT); + if(setting) + config_setting_set_int(setting, value); +} + +VOID SaveInt64Value(config_setting_t * group, char * name, long long value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_INT64); + if(setting) + config_setting_set_int64(setting, value); +} + +VOID SaveFloatValue(config_setting_t * group, char * name, double value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_FLOAT); + if (setting) + config_setting_set_float(setting, value); +} + +VOID SaveStringValue(config_setting_t * group, char * name, char * value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, value); + +} + + +VOID SaveOverride(config_setting_t * group, char * name, struct Override ** values) +{ + config_setting_t *setting; + struct Override ** Calls; + char Multi[10000]; + char * ptr = &Multi[1]; + + *ptr = 0; + + if (values) + { + Calls = values; + + while(Calls[0]) + { + ptr += sprintf(ptr, "%s, %d|", Calls[0]->Call, Calls[0]->Days); + Calls++; + } + *(--ptr) = 0; + } + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, &Multi[1]); + +} + + +VOID SaveMultiStringValue(config_setting_t * group, char * name, char ** values) +{ + config_setting_t *setting; + char ** Calls; + char Multi[100000]; + char * ptr = &Multi[1]; + + *ptr = 0; + + if (values) + { + Calls = values; + + while(Calls[0]) + { + strcpy(ptr, Calls[0]); + ptr += strlen(Calls[0]); + *(ptr++) = '|'; + Calls++; + } + *(--ptr) = 0; + } + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, &Multi[1]); + +} + +int configSaved = 0; + +VOID SaveConfig(char * ConfigName) +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + config_setting_t *root, *group, *bbs; + int i; + char Size[80]; + struct BBSForwardingInfo DummyForwardingInfo; + char Line[1024]; + + if (configSaved == 0) + { + // only create backup once per run + + CopyConfigFile(ConfigName); + configSaved = 1; + } + + memset(&DummyForwardingInfo, 0, sizeof(struct BBSForwardingInfo)); + + // Get rid of old config before saving + + config_destroy(&cfg); + + memset((void *)&cfg, 0, sizeof(config_t)); + + config_init(&cfg); + + root = config_root_setting(&cfg); + + group = config_setting_add(root, "main", CONFIG_TYPE_GROUP); + + SaveIntValue(group, "Streams", MaxStreams); + SaveIntValue(group, "BBSApplNum", BBSApplNum); + SaveStringValue(group, "BBSName", BBSName); + SaveStringValue(group, "SYSOPCall", SYSOPCall); + SaveStringValue(group, "H-Route", HRoute); + SaveStringValue(group, "AMPRDomain", AMPRDomain); + SaveIntValue(group, "EnableUI", EnableUI); + SaveIntValue(group, "RefuseBulls", RefuseBulls); + SaveIntValue(group, "OnlyKnown", OnlyKnown); + SaveIntValue(group, "SendSYStoSYSOPCall", SendSYStoSYSOPCall); + SaveIntValue(group, "SendBBStoSYSOPCall", SendBBStoSYSOPCall); + SaveIntValue(group, "DontHoldNewUsers", DontHoldNewUsers); + SaveIntValue(group, "DefaultNoWINLINK", DefaultNoWINLINK); + SaveIntValue(group, "AllowAnon", AllowAnon); + SaveIntValue(group, "DontNeedHomeBBS", DontNeedHomeBBS); + SaveIntValue(group, "DontCheckFromCall", DontCheckFromCall); + SaveIntValue(group, "UserCantKillT", UserCantKillT); + + SaveIntValue(group, "ForwardToMe", ForwardToMe); + SaveIntValue(group, "SMTPPort", SMTPInPort); + SaveIntValue(group, "POP3Port", POP3InPort); + SaveIntValue(group, "NNTPPort", NNTPInPort); + SaveIntValue(group, "RemoteEmail", RemoteEmail); + SaveIntValue(group, "SendAMPRDirect", SendAMPRDirect); + + SaveIntValue(group, "MailForInterval", MailForInterval); + SaveStringValue(group, "MailForText", MailForText); + + EncryptedPassLen = EncryptPass(ISPAccountPass, EncryptedISPAccountPass); + + SaveIntValue(group, "AuthenticateSMTP", SMTPAuthNeeded); + + SaveIntValue(group, "MulticastRX", MulticastRX); + + SaveIntValue(group, "SMTPGatewayEnabled", ISP_Gateway_Enabled); + SaveIntValue(group, "ISPSMTPPort", ISPSMTPPort); + SaveIntValue(group, "ISPPOP3Port", ISPPOP3Port); + SaveIntValue(group, "POP3PollingInterval", ISPPOP3Interval); + + SaveStringValue(group, "MyDomain", MyDomain); + SaveStringValue(group, "ISPSMTPName", ISPSMTPName); + SaveStringValue(group, "ISPEHLOName", ISPEHLOName); + SaveStringValue(group, "ISPPOP3Name", ISPPOP3Name); + SaveStringValue(group, "ISPAccountName", ISPAccountName); + SaveStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); + + + // Save Window Sizes + +#ifndef LINBPQ + + if (ConsoleRect.right) + { + sprintf(Size,"%d,%d,%d,%d",ConsoleRect.left, ConsoleRect.right, + ConsoleRect.top, ConsoleRect.bottom); + + SaveStringValue(group, "ConsoleSize", Size); + } + + sprintf(Size,"%d,%d,%d,%d,%d",MonitorRect.left,MonitorRect.right,MonitorRect.top,MonitorRect.bottom, hMonitor ? 1 : 0); + SaveStringValue(group, "MonitorSize", Size); + + sprintf(Size,"%d,%d,%d,%d",MainRect.left,MainRect.right,MainRect.top,MainRect.bottom); + SaveStringValue(group, "WindowSize", Size); + + SaveIntValue(group, "Bells", Bells); + SaveIntValue(group, "FlashOnBell", FlashOnBell); + SaveIntValue(group, "StripLF", StripLF); + SaveIntValue(group, "WarnWrap", WarnWrap); + SaveIntValue(group, "WrapInput", WrapInput); + SaveIntValue(group, "FlashOnConnect", FlashOnConnect); + SaveIntValue(group, "CloseWindowOnBye", CloseWindowOnBye); + +#endif + + SaveIntValue(group, "Log_BBS", LogBBS); + SaveIntValue(group, "Log_TCP", LogTCP); + + sprintf(Size,"%d,%d,%d,%d", Ver[0], Ver[1], Ver[2], Ver[3]); + SaveStringValue(group, "Version", Size); + + // Save Welcome Messages and prompts + + SaveStringValue(group, "WelcomeMsg", WelcomeMsg); + SaveStringValue(group, "NewUserWelcomeMsg", NewWelcomeMsg); + SaveStringValue(group, "ExpertWelcomeMsg", ExpertWelcomeMsg); + + SaveStringValue(group, "Prompt", Prompt); + SaveStringValue(group, "NewUserPrompt", NewPrompt); + SaveStringValue(group, "ExpertPrompt", ExpertPrompt); + SaveStringValue(group, "SignoffMsg", SignoffMsg); + + SaveMultiStringValue(group, "RejFrom", RejFrom); + SaveMultiStringValue(group, "RejTo", RejTo); + SaveMultiStringValue(group, "RejAt", RejAt); + SaveMultiStringValue(group, "RejBID", RejBID); + + SaveMultiStringValue(group, "HoldFrom", HoldFrom); + SaveMultiStringValue(group, "HoldTo", HoldTo); + SaveMultiStringValue(group, "HoldAt", HoldAt); + SaveMultiStringValue(group, "HoldBID", HoldBID); + + SaveIntValue(group, "SendWP", SendWP); + SaveIntValue(group, "SendWPType", SendWPType); + SaveIntValue(group, "FilterWPBulls", FilterWPBulls); + SaveIntValue(group, "NoWPGuesses", NoWPGuesses); + + SaveStringValue(group, "SendWPTO", SendWPTO); + SaveStringValue(group, "SendWPVIA", SendWPVIA); + + SaveMultiStringValue(group, "SendWPAddrs", SendWPAddrs); + + // Save Forwarding Config + + // Interval and Max Sizes and Aliases are not user specific + + SaveIntValue(group, "MaxTXSize", MaxTXSize); + SaveIntValue(group, "MaxRXSize", MaxRXSize); + SaveIntValue(group, "ReaddressLocal", ReaddressLocal); + SaveIntValue(group, "ReaddressReceived", ReaddressReceived); + SaveIntValue(group, "WarnNoRoute", WarnNoRoute); + SaveIntValue(group, "Localtime", Localtime); + SaveIntValue(group, "SendPtoMultiple", SendPtoMultiple); + + SaveMultiStringValue(group, "FWDAliases", AliasText); + + bbs = config_setting_add(root, "BBSForwarding", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + ForwardingInfo = user->ForwardingInfo; + + if (ForwardingInfo == NULL) + continue; + + if (memcmp(ForwardingInfo, &DummyForwardingInfo, sizeof(struct BBSForwardingInfo)) == 0) + continue; // Ignore empty records; + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + { + char Key[20] = "*"; + strcat (Key, user->Call); + group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); + } + else + group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); + + SaveMultiStringValue(group, "TOCalls", ForwardingInfo->TOCalls); + SaveMultiStringValue(group, "ConnectScript", ForwardingInfo->ConnectScript); + SaveMultiStringValue(group, "ATCalls", ForwardingInfo->ATCalls); + SaveMultiStringValue(group, "HRoutes", ForwardingInfo->Haddresses); + SaveMultiStringValue(group, "HRoutesP", ForwardingInfo->HaddressesP); + SaveMultiStringValue(group, "FWDTimes", ForwardingInfo->FWDTimes); + + SaveIntValue(group, "Enabled", ForwardingInfo->Enabled); + SaveIntValue(group, "RequestReverse", ForwardingInfo->ReverseFlag); + SaveIntValue(group, "AllowBlocked", ForwardingInfo->AllowBlocked); + SaveIntValue(group, "AllowCompressed", ForwardingInfo->AllowCompressed); + SaveIntValue(group, "UseB1Protocol", ForwardingInfo->AllowB1); + SaveIntValue(group, "UseB2Protocol", ForwardingInfo->AllowB2); + SaveIntValue(group, "SendCTRLZ", ForwardingInfo->SendCTRLZ); + + SaveIntValue(group, "FWDPersonalsOnly", ForwardingInfo->PersonalOnly); + SaveIntValue(group, "FWDNewImmediately", ForwardingInfo->SendNew); + SaveIntValue(group, "FwdInterval", ForwardingInfo->FwdInterval); + SaveIntValue(group, "RevFWDInterval", ForwardingInfo->RevFwdInterval); + SaveIntValue(group, "MaxFBBBlock", ForwardingInfo->MaxFBBBlockSize); + SaveIntValue(group, "ConTimeout", ForwardingInfo->ConTimeout); + + SaveStringValue(group, "BBSHA", ForwardingInfo->BBSHA); + } + + + // Save Housekeeping config + + group = config_setting_add(root, "Housekeeping", CONFIG_TYPE_GROUP); + + SaveInt64Value(group, "LastHouseKeepingTime", LastHouseKeepingTime); + SaveInt64Value(group, "LastTrafficTime", LastTrafficTime); + SaveIntValue(group, "MaxMsgno", MaxMsgno); + SaveIntValue(group, "BidLifetime", BidLifetime); + SaveIntValue(group, "MaxAge", MaxAge); + SaveIntValue(group, "LogLifetime", LogAge); + SaveIntValue(group, "LogLifetime", LogAge); + SaveIntValue(group, "MaintInterval", MaintInterval); + SaveIntValue(group, "UserLifetime", UserLifetime); + SaveIntValue(group, "MaintTime", MaintTime); + SaveFloatValue(group, "PR", PR); + SaveFloatValue(group, "PUR", PUR); + SaveFloatValue(group, "PF", PF); + SaveFloatValue(group, "PNF", PNF); + SaveIntValue(group, "BF", BF); + SaveIntValue(group, "BNF", BNF); + SaveIntValue(group, "NTSD", NTSD); + SaveIntValue(group, "NTSF", NTSF); + SaveIntValue(group, "NTSU", NTSU); +// SaveIntValue(group, "AP", AP); +// SaveIntValue(group, "AB", AB); + SaveIntValue(group, "DeletetoRecycleBin", DeletetoRecycleBin); + SaveIntValue(group, "SuppressMaintEmail", SuppressMaintEmail); + SaveIntValue(group, "MaintSaveReg", SaveRegDuringMaint); + SaveIntValue(group, "OverrideUnsent", OverrideUnsent); + SaveIntValue(group, "SendNonDeliveryMsgs", SendNonDeliveryMsgs); + SaveIntValue(group, "GenerateTrafficReport", GenerateTrafficReport); + + SaveOverride(group, "LTFROM", LTFROM); + SaveOverride(group, "LTTO", LTTO); + SaveOverride(group, "LTAT", LTAT); + + // Save UI config + + for (i=1; i<=32; i++) + { + char Key[100]; + + sprintf(Key, "UIPort%d", i); + + group = config_setting_add(root, Key, CONFIG_TYPE_GROUP); + + if (group) + { + SaveIntValue(group, "Enabled", UIEnabled[i]); + SaveIntValue(group, "SendMF", UIMF[i]); + SaveIntValue(group, "SendHDDR", UIHDDR[i]); + SaveIntValue(group, "SendNull", UINull[i]); + + if (UIDigi[i]) + SaveStringValue(group, "Digis", UIDigi[i]); + } + } + + // Save User Config + + bbs = config_setting_add(root, "BBSUsers", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofUsers; i++) + { + char stats[256], stats2[256]; + struct MsgStats * Stats; + char Key[20] = "*"; + + user = UserRecPtr[i]; + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + { + strcat (Key, user->Call); +// group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); + } + else + { + strcpy(Key, user->Call); +// group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); + } + /* + SaveStringValue(group, "Name", user->Name); + SaveStringValue(group, "Address", user->Address); + SaveStringValue(group, "HomeBBS", user->HomeBBS); + SaveStringValue(group, "QRA", user->QRA); + SaveStringValue(group, "pass", user->pass); + SaveStringValue(group, "ZIP", user->ZIP); + SaveStringValue(group, "CMSPass", user->CMSPass); + + SaveIntValue(group, "lastmsg", user->lastmsg); + SaveIntValue(group, "flags", user->flags); + SaveIntValue(group, "PageLen", user->PageLen); + SaveIntValue(group, "BBSNumber", user->BBSNumber); + SaveIntValue(group, "RMSSSIDBits", user->RMSSSIDBits); + SaveIntValue(group, "WebSeqNo", user->WebSeqNo); + + SaveInt64Value(group, "TimeLastConnected", user->TimeLastConnected); +*/ + Stats = &user->Total; + +// sprintf(stats, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + sprintf(stats, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", + Stats->ConnectsIn, Stats->ConnectsOut, + Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], + Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], + Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], + Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], + Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], + Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); + +// SaveStringValue(group, "Totsl", stats); + + Stats = &user->Last; + + sprintf(stats2, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", +// sprintf(stats2, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + Stats->ConnectsIn, Stats->ConnectsOut, + Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], + Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], + Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], + Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], + Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], + Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); + +// SaveStringValue(group, "Last", stats2); + + sprintf(Line,"%s^%s^%s^%s^%s^%s^%s^%d^%d^%d^%d^%d^%d^%lld^%s^%s", + user->Name, user->Address, user->HomeBBS, user->QRA, user->pass, user->ZIP, user->CMSPass, + user->lastmsg, user->flags, user->PageLen, user->BBSNumber, user->RMSSSIDBits, user->WebSeqNo, + user->TimeLastConnected, stats, stats2); + + if (strlen(Line) < 10) + continue; + + SaveStringValue(bbs, Key, Line); + } + +/* + wp = config_setting_add(root, "WP", CONFIG_TYPE_GROUP); + + for (i = 0; i <= NumberofWPrecs; i++) + { + char WPString[1024]; + long long val1, val2; + + WP = WPRecPtr[i]; + val1 = WP->last_modif; + val2 = WP->last_seen; + + sprintf(Key, "R%d", i); + + sprintf(WPString, "%s|%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%ld|%ld", + &WP->callsign[0], &WP->name[0], WP->Type, WP->changed, WP->seen, &WP->first_homebbs[0], + &WP->secnd_homebbs[0], &WP->first_zip[0], &WP->secnd_zip[0], &WP->first_qth[0], &WP->secnd_qth[0], + val1, val2); + + SaveStringValue(wp, Key, WPString); + } + + // Save Message Headers + + msgs = config_setting_add(root, "MSGS", CONFIG_TYPE_GROUP); + + memset(MsgHddrPtr[0], 0, sizeof(struct MsgInfo)); + + MsgHddrPtr[0]->type = 'X'; + MsgHddrPtr[0]->status = '2'; + MsgHddrPtr[0]->number = 0; + MsgHddrPtr[0]->length = LatestMsg; + + + for (i = 0; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); + + n = 39; + while (n >=0 && HEXString1[n] == '0') + HEXString1[n--] = 0; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); + + n = 39; + while (n >= 0 && HEXString2[n] == '0') + HEXString2[n--] = 0; + + sprintf(Key, "R%d", Msg->number); + + n = sprintf(Line, "%c|%c|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, + Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], + &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, + &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); + + SaveStringValue(msgs, Key, Line); + } + + // Save Bids + + msgs = config_setting_add(root, "BIDS", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofBIDs; i++) + { + sprintf(Key, "R%s", BIDRecPtr[i]->BID); + sprintf(Line, "%d|%d", BIDRecPtr[i]->mode, BIDRecPtr[i]->u.timestamp); + SaveStringValue(msgs, Key, Line); + } + +#ifdef LINBPQ + + if(! config_write_file(&cfg,"/dev/shm/linmail.cfg.temp" )) + { + print("Error while writing file.\n"); + config_destroy(&cfg); + return; + } + + CopyFile("/dev/shm/linmail.cfg.temp", ConfigName, FALSE); + +#else +*/ + if(! config_write_file(&cfg, ConfigName)) + { + fprintf(stderr, "Error while writing file.\n"); + config_destroy(&cfg); + return; + } + +//#endif + + config_destroy(&cfg); + +/* + +#ifndef LINBPQ + + // Save a copy with current Date/Time Stamp for debugging + + { + char Backup[MAX_PATH]; + time_t LT; + struct tm * tm; + + LT = time(NULL); + tm = gmtime(<); + + sprintf(Backup,"%s.%02d%02d%02d%02d%02d.save", ConfigName, tm->tm_year-100, tm->tm_mon+1, + tm->tm_mday, tm->tm_hour, tm->tm_min); + + CopyFile(ConfigName, Backup, FALSE); // Copy to .bak + } +#endif +*/ +} + +int GetIntValue(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int (setting); + + return 0; +} + +long long GetInt64Value(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int64 (setting); + + return 0; +} + +double GetFloatValue(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + + if (setting) + { + return config_setting_get_float (setting); + } + return 0; +} + +int GetIntValueWithDefault(config_setting_t * group, char * name, int Default) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int (setting); + + return Default; +} + + +BOOL GetStringValue(config_setting_t * group, char * name, char * value) +{ + const char * str; + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + { + str = config_setting_get_string (setting); + strcpy(value, str); + return TRUE; + } + value[0] = 0; + return FALSE; +} + +BOOL GetConfig(char * ConfigName) +{ + int i; + char Size[80]; + config_setting_t *setting; + const char * ptr; + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + + if(! config_read_file(&cfg, ConfigName)) + { + char Msg[256]; + sprintf(Msg, "Config File Line %d - %s\n", + config_error_line(&cfg), config_error_text(&cfg)); +#ifdef WIN32 + MessageBox(NULL, Msg, "BPQMail", MB_ICONSTOP); +#else + printf("%s", Msg); +#endif + config_destroy(&cfg); + return(EXIT_FAILURE); + } + +#if LIBCONFIG_VER_MINOR > 5 + config_set_option(&cfg, CONFIG_OPTION_AUTOCONVERT, 1); +#else + config_set_auto_convert (&cfg, 1); +#endif + + group = config_lookup (&cfg, "main"); + + if (group == NULL) + return EXIT_FAILURE; + + SMTPInPort = GetIntValue(group, "SMTPPort"); + POP3InPort = GetIntValue(group, "POP3Port"); + NNTPInPort = GetIntValue(group, "NNTPPort"); + RemoteEmail = GetIntValue(group, "RemoteEmail"); + MaxStreams = GetIntValue(group, "Streams"); + BBSApplNum = GetIntValue(group, "BBSApplNum"); + EnableUI = GetIntValue(group, "EnableUI"); + MailForInterval = GetIntValue(group, "MailForInterval"); + RefuseBulls = GetIntValue(group, "RefuseBulls"); + OnlyKnown = GetIntValue(group, "OnlyKnown"); + SendSYStoSYSOPCall = GetIntValue(group, "SendSYStoSYSOPCall"); + SendBBStoSYSOPCall = GetIntValue(group, "SendBBStoSYSOPCall"); + DontHoldNewUsers = GetIntValue(group, "DontHoldNewUsers"); + DefaultNoWINLINK = GetIntValue(group, "DefaultNoWINLINK"); + ForwardToMe = GetIntValue(group, "ForwardToMe"); + AllowAnon = GetIntValue(group, "AllowAnon"); + UserCantKillT = GetIntValue(group, "UserCantKillT"); + + DontNeedHomeBBS = GetIntValue(group, "DontNeedHomeBBS"); + DontCheckFromCall = GetIntValue(group, "DontCheckFromCall"); + MaxTXSize = GetIntValue(group, "MaxTXSize"); + MaxRXSize = GetIntValue(group, "MaxRXSize"); + ReaddressLocal = GetIntValue(group, "ReaddressLocal"); + ReaddressReceived = GetIntValue(group, "ReaddressReceived"); + WarnNoRoute = GetIntValue(group, "WarnNoRoute"); + SendPtoMultiple = GetIntValue(group, "SendPtoMultiple"); + 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); + 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); + ISPSMTPPort = GetIntValue(group, "ISPSMTPPort"); + ISPPOP3Port = GetIntValue(group, "ISPPOP3Port"); + GetStringValue(group, "ISPAccountName", ISPAccountName); + GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); + GetStringValue(group, "ISPAccountName", ISPAccountName); + + sprintf(SignoffMsg, "73 de %s\r", BBSName); // Default + GetStringValue(group, "SignoffMsg", SignoffMsg); + + DecryptPass(EncryptedISPAccountPass, ISPAccountPass, (int)strlen(EncryptedISPAccountPass)); + + SMTPAuthNeeded = GetIntValue(group, "AuthenticateSMTP"); + LogBBS = GetIntValue(group, "Log_BBS"); + LogTCP = GetIntValue(group, "Log_TCP"); + + MulticastRX = GetIntValue(group, "MulticastRX"); + +#ifndef LINBPQ + + GetStringValue(group, "MonitorSize", Size); + sscanf(Size,"%d,%d,%d,%d,%d",&MonitorRect.left,&MonitorRect.right,&MonitorRect.top,&MonitorRect.bottom,&OpenMon); + + GetStringValue(group, "WindowSize", Size); + sscanf(Size,"%d,%d,%d,%d",&MainRect.left,&MainRect.right,&MainRect.top,&MainRect.bottom); + + Bells = GetIntValue(group, "Bells"); + + FlashOnBell = GetIntValue(group, "FlashOnBell"); + + StripLF = GetIntValue(group, "StripLF"); + CloseWindowOnBye = GetIntValue(group, "CloseWindowOnBye"); + WarnWrap = GetIntValue(group, "WarnWrap"); + WrapInput = GetIntValue(group, "WrapInput"); + FlashOnConnect = GetIntValue(group, "FlashOnConnect"); + + GetStringValue(group, "ConsoleSize", Size); + sscanf(Size,"%d,%d,%d,%d,%d", &ConsoleRect.left, &ConsoleRect.right, + &ConsoleRect.top, &ConsoleRect.bottom,&OpenConsole); + +#endif + + // Get Welcome Messages + + setting = config_setting_get_member (group, "WelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + WelcomeMsg = _strdup(ptr); + } + else + WelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); + + + setting = config_setting_get_member (group, "NewUserWelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + NewWelcomeMsg = _strdup(ptr); + } + else + NewWelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); + + + setting = config_setting_get_member (group, "ExpertWelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + ExpertWelcomeMsg = _strdup(ptr); + } + else + ExpertWelcomeMsg = _strdup(""); + + // Get Prompts + + setting = config_setting_get_member (group, "Prompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + Prompt = _strdup(ptr); + } + else + { + Prompt = malloc(20); + sprintf(Prompt, "de %s>\r\n", BBSName); + } + + setting = config_setting_get_member (group, "NewUserPrompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + NewPrompt = _strdup(ptr); + } + else + { + NewPrompt = malloc(20); + sprintf(NewPrompt, "de %s>\r\n", BBSName); + } + + setting = config_setting_get_member (group, "ExpertPrompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + ExpertPrompt = _strdup(ptr); + } + else + { + ExpertPrompt = malloc(20); + sprintf(ExpertPrompt, "de %s>\r\n", BBSName); + } + + TidyPrompts(); + + RejFrom = GetMultiStringValue(group, "RejFrom"); + RejTo = GetMultiStringValue(group, "RejTo"); + RejAt = GetMultiStringValue(group, "RejAt"); + RejBID = GetMultiStringValue(group, "RejBID"); + + HoldFrom = GetMultiStringValue(group, "HoldFrom"); + HoldTo = GetMultiStringValue(group, "HoldTo"); + HoldAt = GetMultiStringValue(group, "HoldAt"); + HoldBID = GetMultiStringValue(group, "HoldBID"); + + // Send WP Params + + SendWP = GetIntValue(group, "SendWP"); + SendWPType = GetIntValue(group, "SendWPType"); + + GetStringValue(group, "SendWPTO", SendWPTO); + GetStringValue(group, "SendWPVIA", SendWPVIA); + + SendWPAddrs = GetMultiStringValue(group, "SendWPAddrs"); + + FilterWPBulls = GetIntValue(group, "FilterWPBulls"); + NoWPGuesses = GetIntValue(group, "NoWPGuesses"); + + if (SendWPAddrs[0] == NULL && SendWPTO[0]) + { + // convert old format TO and VIA to entry in SendWPAddrs + + SendWPAddrs = realloc(SendWPAddrs, 8); // Add entry + + if (SendWPVIA[0]) + { + char WP[256]; + + sprintf(WP, "%s@%s", SendWPTO, SendWPVIA); + SendWPAddrs[0] = _strdup(WP); + } + else + SendWPAddrs[0] = _strdup(SendWPTO); + + + SendWPAddrs[1] = 0; + + SendWPTO[0] = 0; + SendWPVIA[0] = 0; + } + + GetStringValue(group, "Version", Size); + sscanf(Size,"%d,%d,%d,%d", &LastVer[0], &LastVer[1], &LastVer[2], &LastVer[3]); + + for (i=1; i<=32; i++) + { + char Key[100]; + + sprintf(Key, "UIPort%d", i); + + group = config_lookup (&cfg, Key); + + if (group) + { + UIEnabled[i] = GetIntValue(group, "Enabled"); + UIMF[i] = GetIntValueWithDefault(group, "SendMF", UIEnabled[i]); + UIHDDR[i] = GetIntValueWithDefault(group, "SendHDDR", UIEnabled[i]); + UINull[i] = GetIntValue(group, "SendNull"); + Size[0] = 0; + GetStringValue(group, "Digis", Size); + if (Size[0]) + UIDigi[i] = _strdup(Size); + } + } + + group = config_lookup (&cfg, "Housekeeping"); + + if (group) + { + LastHouseKeepingTime = GetIntValue(group, "LastHouseKeepingTime"); + LastTrafficTime = GetIntValue(group, "LastTrafficTime"); + MaxMsgno = GetIntValue(group, "MaxMsgno"); + LogAge = GetIntValue(group, "LogLifetime"); + BidLifetime = GetIntValue(group, "BidLifetime"); + MaxAge = GetIntValue(group, "MaxAge"); + if (MaxAge == 0) + MaxAge = 30; + UserLifetime = GetIntValue(group, "UserLifetime"); + MaintInterval = GetIntValue(group, "MaintInterval"); + + if (MaintInterval == 0) + MaintInterval = 24; + + MaintTime = GetIntValue(group, "MaintTime"); + + PR = GetFloatValue(group, "PR"); + PUR = GetFloatValue(group, "PUR"); + PF = GetFloatValue(group, "PF"); + PNF = GetFloatValue(group, "PNF"); + + BF = GetIntValue(group, "BF"); + BNF = GetIntValue(group, "BNF"); + NTSD = GetIntValue(group, "NTSD"); + NTSU = GetIntValue(group, "NTSU"); + NTSF = GetIntValue(group, "NTSF"); +// AP = GetIntValue(group, "AP"); +// AB = GetIntValue(group, "AB"); + DeletetoRecycleBin = GetIntValue(group, "DeletetoRecycleBin"); + SuppressMaintEmail = GetIntValue(group, "SuppressMaintEmail"); + SaveRegDuringMaint = GetIntValue(group, "MaintSaveReg"); + OverrideUnsent = GetIntValue(group, "OverrideUnsent"); + SendNonDeliveryMsgs = GetIntValue(group, "SendNonDeliveryMsgs"); + OverrideUnsent = GetIntValue(group, "OverrideUnsent"); + GenerateTrafficReport = GetIntValueWithDefault(group, "GenerateTrafficReport", 1); + + LTFROM = GetOverrides(group, "LTFROM"); + LTTO = GetOverrides(group, "LTTO"); + LTAT = GetOverrides(group, "LTAT"); + } + + return EXIT_SUCCESS; +} + + +int Connected(int Stream) +{ + int n, Mask; + CIRCUIT * conn; + struct UserInfo * user = NULL; + char callsign[10]; + int port, paclen, maxframe, l4window; + char ConnectedMsg[] = "*** CONNECTED "; + char Msg[100]; + char Title[100]; + int Freq = 0; + int Mode = 0; + BPQVECSTRUC * SESS; + TRANSPORTENTRY * Sess1 = NULL, * Sess2; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (Stream == conn->BPQStream) + { + if (conn->Active) + { + // Probably an outgoing connect + + ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + conn->ErrorCount = 0; + + if (conn->BBSFlags & RunningConnectScript) + { + // BBS Outgoing Connect + + conn->paclen = 236; + + // Run first line of connect script + + ChangeSessionIdletime(Stream, BBSIDLETIME); // Default Idletime for BBS Sessions + ProcessBBSConnectScript(conn, ConnectedMsg, 15); + return 0; + } + } + + // Incoming Connect + + // Try to find port, freq, mode, etc + +#ifdef LINBPQ + SESS = &BPQHOSTVECTOR[0]; +#else + SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + SESS +=(Stream - 1); + + if (SESS) + Sess1 = SESS->HOSTSESSION; + + if (Sess1) + { + Sess2 = Sess1->L4CROSSLINK; + + if (Sess2) + { + // See if L2 session - if so, get info from WL2K report line + + // if Session has report info, use it + + if (Sess2->Mode) + { + Freq = Sess2->Frequency; + Mode = Sess2->Mode; + } + else if (Sess2->L4CIRCUITTYPE & L2LINK) + { + LINKTABLE * LINK = Sess2->L4TARGET.LINK; + PORTCONTROLX * PORT = LINK->LINKPORT; + + Freq = PORT->WL2KInfo.Freq; + Mode = PORT->WL2KInfo.mode; + } + else + { + if (Sess2->RMSCall[0]) + { + Freq = Sess2->Frequency; + Mode = Sess2->Mode; + } + } + } + } + + memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything + conn->Active = TRUE; + conn->BPQStream = Stream; + ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions + + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + conn->ErrorCount = 0; + + conn->Secure_Session = GetConnectionInfo(Stream, callsign, + &port, &conn->SessType, &paclen, &maxframe, &l4window); + + strlop(callsign, ' '); // Remove trailing spaces + + if (strcmp(&callsign[strlen(callsign) - 2], "-T") == 0) + conn->RadioOnlyMode = 'T'; + else if (strcmp(&callsign[strlen(callsign) - 2], "-R") == 0) + conn->RadioOnlyMode = 'R'; + else + conn->RadioOnlyMode = 0; + + memcpy(conn->Callsign, callsign, 10); + + strlop(callsign, '-'); // Remove any SSID + + user = LookupCall(callsign); + + if (user == NULL) + { + int Length=0; + + if (OnlyKnown) + { + // Unknown users not allowed + + n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from unknown user %s Rejected", callsign); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + Disconnect(Stream); + return 0; + } + + user = AllocateUserRecord(callsign); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (SendNewUserMessage) + { + char * MailBuffer = malloc(100); + Length += sprintf(MailBuffer, "New User %s Connected to Mailbox on Port %d Freq %d Mode %d\r\n", callsign, port, Freq, Mode); + + sprintf(Title, "New User %s", callsign); + + SendMessageToSYSOP(Title, MailBuffer, Length); + } + + if (user == NULL) return 0; // Cant happen?? + + if (!DontHoldNewUsers) + user->flags |= F_HOLDMAIL; + + if (DefaultNoWINLINK) + user->flags |= F_NOWINLINK; + + // Always set WLE User - can't see it doing any harm + + user->flags |= F_Temp_B2_BBS; + + conn->NewUser = TRUE; + } + + user->TimeLastConnected = time(NULL); + user->Total.ConnectsIn++; + + conn->UserPointer = user; + + conn->lastmsg = user->lastmsg; + + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (paclen == 0) + { + paclen = 236; + + if (conn->SessType & Sess_PACTOR) + paclen = 100; + } + + conn->paclen = paclen; + + // Set SYSOP flag if user is defined as SYSOP and Host Session + + if (((conn->SessType & Sess_BPQHOST) == Sess_BPQHOST) && (user->flags & F_SYSOP)) + conn->sysop = TRUE; + + if (conn->Secure_Session && (user->flags & F_SYSOP)) + conn->sysop = TRUE; + + Mask = 1 << (GetApplNum(Stream) - 1); + + if (user->flags & F_Excluded) + { + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s Rejected by Exclude Flag", user->Call); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + Disconnect(Stream); + return 0; + } + + if (port) + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s on Port %d Freq %d Mode %s", + user->Call, port, Freq, WL2KModes[Mode]); + else + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s", user->Call); + + // Send SID and Prompt (Unless Sync) + + if (user->ForwardingInfo && user->ForwardingInfo->ConTimeout) + conn->SIDResponseTimer = user->ForwardingInfo->ConTimeout / 10; // 10 sec ticks + else + conn->SIDResponseTimer = 12; // Allow a couple of minutes for response to SID + + { + BOOL B1 = FALSE, B2 = FALSE, BIN = FALSE, BLOCKED = FALSE; + BOOL WL2KRO = FALSE; + + struct BBSForwardingInfo * ForwardingInfo; + + if (conn->RadioOnlyMode == 'R') + WL2KRO = 1; + + conn->PageLen = user->PageLen; + conn->Paging = (user->PageLen > 0); + + if (user->flags & F_Temp_B2_BBS) + { + // An RMS Express user that needs a temporary BBS struct + + if (user->ForwardingInfo == NULL) + { + // we now save the Forwarding info if BBS flag is cleared, + // so there may already be a ForwardingInfo + + user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + } + + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; + + ForwardingInfo = user->ForwardingInfo; + + ForwardingInfo->AllowCompressed = TRUE; + B1 = ForwardingInfo->AllowB1 = FALSE; + B2 = ForwardingInfo->AllowB2 = TRUE; + BLOCKED = ForwardingInfo->AllowBlocked = TRUE; + } + + if (conn->NewUser) + { + BLOCKED = TRUE; + BIN = TRUE; + B2 = TRUE; + } + + if (user->ForwardingInfo) + { + BLOCKED = user->ForwardingInfo->AllowBlocked; + if (BLOCKED) + { + BIN = user->ForwardingInfo->AllowCompressed; + B1 = user->ForwardingInfo->AllowB1; + B2 = user->ForwardingInfo->AllowB2; + } + } + + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + if (conn->RadioOnlyMode) + nodeprintf(conn,";WL2K-Radio/Internet_Network\r"); + + if (!(conn->BBSFlags & SYNCMODE)) + { + + nodeprintf(conn, BBSSID, "BPQ-", + Ver[0], Ver[1], Ver[2], Ver[3], + BIN ? "B" : "", B1 ? "1" : "", B2 ? "2" : "", + BLOCKED ? "FW": "", WL2KRO ? "" : "J"); + + // if (user->flags & F_Temp_B2_BBS) + // nodeprintf(conn,";PQ: 66427529\r"); + + // nodeprintf(conn,"[WL2K-BPQ.1.0.4.39-B2FWIHJM$]\r"); + } + } + + if ((user->Name[0] == 0) & AllowAnon) + strcpy(user->Name, user->Call); + + if (!(conn->BBSFlags & SYNCMODE)) + { + if (user->Name[0] == 0) + { + conn->Flags |= GETTINGUSER; + BBSputs(conn, NewUserPrompt); + } + else + SendWelcomeMsg(Stream, conn, user); + } + else + { + // Seems to be a timing problem - see if this fixes it + + Sleep(500); + } + + RefreshMainWindow(); + + return 0; + } + } + + return 0; +} + +int Disconnected (int Stream) +{ + struct UserInfo * user = NULL; + CIRCUIT * conn; + int n; + char Msg[255]; + int len; + char DiscMsg[] = "DISCONNECTED "; + + for (n = 0; n <= NumberofStreams-1; n++) + { + conn=&Connections[n]; + + if (Stream == conn->BPQStream) + { + if (conn->Active == FALSE) + return 0; + + // if still running connect script, reenter it to see if + // there is an else + + if (conn->BBSFlags & RunningConnectScript) + { + // We need to see if we got as far as connnected, + // as if we have we need to reset the connect script + // over the ELSE + + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + char ** Scripts; + + if (ForwardingInfo->TempConnectScript) + Scripts = ForwardingInfo->TempConnectScript; + else + Scripts = ForwardingInfo->ConnectScript; + + // First see if any script left + + if (Scripts[ForwardingInfo->ScriptIndex]) + { + if (ForwardingInfo->MoreLines == FALSE) + { + // Have reached end of script, so need to set back over ELSE + + ForwardingInfo->ScriptIndex--; + ForwardingInfo->MoreLines = TRUE; + } + + // if (Scripts[ForwardingInfo->ScriptIndex] == NULL || + // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished + // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished + + + ProcessBBSConnectScript(conn, DiscMsg, 15); + return 0; + } + } + + // if sysop was chatting to user clear link +#ifndef LINBPQ + if (conn->BBSFlags & SYSOPCHAT) + { + SendUnbuffered(-1, "User has disconnected\n", 23); + BBSConsole.Console->SysopChatStream = 0; + } +#endif + ClearQueue(conn); + + if (conn->PacLinkCalls) + free(conn->PacLinkCalls); + + if (conn->InputBuffer) + { + free(conn->InputBuffer); + conn->InputBuffer = NULL; + conn->InputBufferLen = 0; + } + + if (conn->InputMode == 'B') + { + // Save partly received message for a restart + + if (conn->BBSFlags & FBBB1Mode) + if (conn->Paclink == 0) // Paclink doesn't do restarts + if (strcmp(conn->Callsign, "RMS") != 0) // Neither does RMS Packet. + if (conn->DontSaveRestartData == FALSE) + SaveFBBBinary(conn); + } + + conn->Active = FALSE; + + if (conn->FwdMsg) + conn->FwdMsg->Locked = 0; // Unlock + + RefreshMainWindow(); + + RemoveTempBIDS(conn); + + len=sprintf_s(Msg, sizeof(Msg), "%s Disconnected", conn->Callsign); + WriteLogLine(conn, '|',Msg, len, LOG_BBS); + + if (conn->FBBHeaders) + { + struct FBBHeaderLine * FBBHeader; + int n; + + for (n = 0; n < 5; n++) + { + FBBHeader = &conn->FBBHeaders[n]; + + if (FBBHeader->FwdMsg) + FBBHeader->FwdMsg->Locked = 0; // Unlock + + } + + free(conn->FBBHeaders); + conn->FBBHeaders = NULL; + } + + if (conn->UserPointer) + { + struct BBSForwardingInfo * FWDInfo = conn->UserPointer->ForwardingInfo; + + if (FWDInfo) + { + FWDInfo->Forwarding = FALSE; + +// if (FWDInfo->UserCall[0]) // Will be set if RMS +// { +// FindNextRMSUser(FWDInfo); +// } +// else + FWDInfo->FwdTimer = 0; + } + } + + conn->BBSFlags = 0; // Clear ARQ Mode + + return 0; + } + } + return 0; +} + +int DoReceivedData(int Stream) +{ + int count, InputLen; + size_t MsgLen; + int n; + CIRCUIT * conn; + struct UserInfo * user; + char * ptr, * ptr2; + char * Buffer; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (Stream == conn->BPQStream) + { + conn->SIDResponseTimer = 0; // Got a message, so cancel timeout. + + do + { + // May have several messages per packet, or message split over packets + + OuterLoop: + if (conn->InputLen + 1000 > conn->InputBufferLen ) // Shouldnt have lines longer than this in text mode + { + conn->InputBufferLen += 1000; + conn->InputBuffer = realloc(conn->InputBuffer, conn->InputBufferLen); + } + + GetMsg(Stream, &conn->InputBuffer[conn->InputLen], &InputLen, &count); + + if (InputLen == 0 && conn->InputMode != 'Y') + return 0; + + conn->InputLen += InputLen; + + if (conn->InputLen == 0) return 0; + + conn->Watchdog = 900; // 15 Minutes + + if (conn->InputMode == 'Y') // YAPP + { + if (ProcessYAPPMessage(conn)) // Returns TRUE if there could be more to process + goto OuterLoop; + + return 0; + } + + if (conn->InputMode == 'B') + { + // if in OpenBCM mode, remove FF transparency + + if (conn->OpenBCM) // Telnet, so escape any 0xFF + { + unsigned char * ptr1 = conn->InputBuffer; + unsigned char * ptr2; + int Len; + unsigned char c; + + // We can come through here again for the + // same data as we wait for a full packet + // So only check last InputLen bytes + + ptr1 += (conn->InputLen - InputLen); + ptr2 = ptr1; + Len = InputLen; + + while (Len--) + { + c = *(ptr1++); + + if (conn->InTelnetExcape) // Last char was ff + { + conn->InTelnetExcape = FALSE; + continue; + } + + *(ptr2++) = c; + + if (c == 0xff) // + conn->InTelnetExcape = TRUE; + } + + conn->InputLen = (int)(ptr2 - conn->InputBuffer); + } + + UnpackFBBBinary(conn); + goto OuterLoop; + } + else + { + + loop: + + if (conn->InputLen == 1 && conn->InputBuffer[0] == 0) // Single Null + { + conn->InputLen = 0; + return 0; + } + + user = conn->UserPointer; + + if (conn->BBSFlags & (MCASTRX | SYNCMODE)) + { + // MCAST and SYNCMODE deliver full packets + + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); + else + ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); + + conn->InputLen=0; + continue; + } + + // This looks for CR, CRLF, LF or CR/Null and removes any LF or NULL, + // but this relies on both arriving in same packet. + // Need to check for LF and start of packet and ignore it + // But what if client is only using LF?? + // (WLE sends SID with CRLF, other packets with CR only) + + // We don't get here on the data part of a binary transfer, so + // don't need to worry about messing up binary data. + + ptr = memchr(conn->InputBuffer, '\r', conn->InputLen); + ptr2 = memchr(conn->InputBuffer, '\n', conn->InputLen); + + if (ptr) + conn->usingCR = 1; + + if ((ptr2 && ptr2 < ptr) || ptr == 0) // LF before CR, or no CR + ptr = ptr2; // Use LF + + if (ptr) // CR or LF in buffer + { + conn->lastLineEnd = *(ptr); + + *(ptr) = '\r'; // In case was LF + + ptr2 = &conn->InputBuffer[conn->InputLen]; + + if (++ptr == ptr2) + { + // Usual Case - single msg in buffer + + // if Length is 1 and Term is LF and normal line end is CR + // this is from a split CRLF - Ignore it + + if (conn->InputLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) + Debugprintf("BPQMail split Line End Detected"); + else + { + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); + else + ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); + } + conn->InputLen=0; + } + else + { + // buffer contains more that 1 message + + MsgLen = conn->InputLen - (ptr2-ptr); + + Buffer = malloc(MsgLen + 100); + + memcpy(Buffer, conn->InputBuffer, MsgLen); + + // if Length is 1 and Term is LF and normal line end is CR + // this is from a split CRLF - Ignore it + + if (MsgLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) + Debugprintf("BPQMail split Line End Detected"); + else + { + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, Buffer, (int)MsgLen); + else + ProcessLine(conn, user, Buffer, (int)MsgLen); + } + free(Buffer); + + if (*ptr == 0 || *ptr == '\n') + { + /// CR LF or CR Null + + ptr++; + conn->InputLen--; + } + + memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen); + + conn->InputLen -= (int)MsgLen; + + goto loop; + + } + } + else + { + // Could be a YAPP Header + + + if (conn->InputLen == 2 && conn->InputBuffer[0] == ENQ && conn->InputBuffer[1] == 1) // YAPP Send_Init + { + UCHAR YAPPRR[2]; + YAPPRR[0] = ACK; + YAPPRR[1] = 1; + + conn->InputMode = 'Y'; + QueueMsg(conn, YAPPRR, 2); + + conn->InputLen = 0; + return 0; + } + } + } + + } while (count > 0); + + return 0; + } + } + + // Socket not found + + return 0; + +} +int DoBBSMonitorData(int Stream) +{ +// UCHAR Buffer[1000]; + UCHAR buff[500]; + + int len = 0,count=0; + int stamp; + + do + { + stamp=GetRaw(Stream, buff,&len,&count); + + if (len == 0) return 0; + + SeeifBBSUIFrame((struct _MESSAGEX *)buff, len); + } + + while (count > 0); + + + return 0; + +} + +VOID ProcessFLARQLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen) +{ + Buffer[MsgLen] = 0; + + if (MsgLen == 1 && Buffer[0] == 13) + return; + + if (strcmp(Buffer, "ARQ::ETX\r") == 0) + { + // Decode it. + + UCHAR * ptr1, * ptr2, * ptr3; + int len, linelen; + struct MsgInfo * Msg = conn->TempMsg; + time_t Date; + char FullTo[100]; + char FullFrom[100]; + char ** RecpTo = NULL; // May be several Recipients + char ** HddrTo = NULL; // May be several Recipients + char ** Via = NULL; // May be several Recipients + int LocalMsg[1000] ; // Set if Recipient is a local wl2k address + + int B2To; // Offset to To: fields in B2 header + int Recipients = 0; + int RMSMsgs = 0, BBSMsgs = 0; + +// Msg->B2Flags |= B2Msg; + + + ptr1 = conn->MailBuffer; + len = Msg->length; + ptr1[len] = 0; + + if (strstr(ptr1, "ARQ:ENCODING::")) + { + // a file, not a message. If is called "BBSPOLL" do a reverse forward else Ignore for now + + _strupr(conn->MailBuffer); + if (strstr(conn->MailBuffer, "BBSPOLL")) + { + SendARQMail(conn); + } + + free(conn->MailBuffer); + conn->MailBuffer = NULL; + conn->MailBufferSize = 0; + + return; + } + Loop: + ptr2 = strchr(ptr1, '\r'); + + linelen = (int)(ptr2 - ptr1); + + if (_memicmp(ptr1, "From:", 5) == 0 && linelen > 6) // Can have empty From: + { + char SaveFrom[100]; + char * FromHA; + + memcpy(FullFrom, ptr1, linelen); + FullFrom[linelen] = 0; + + // B2 From may now contain an @BBS + + strcpy(SaveFrom, FullFrom); + + FromHA = strlop(SaveFrom, '@'); + + if (strlen(SaveFrom) > 12) SaveFrom[12] = 0; + + strcpy(Msg->from, &SaveFrom[6]); + + if (FromHA) + { + if (strlen(FromHA) > 39) FromHA[39] = 0; + Msg->emailfrom[0] = '@'; + strcpy(&Msg->emailfrom[1], _strupr(FromHA)); + } + + // Remove any SSID + + ptr3 = strchr(Msg->from, '-'); + if (ptr3) *ptr3 = 0; + + } + else if (_memicmp(ptr1, "To:", 3) == 0 || _memicmp(ptr1, "cc:", 3) == 0) + { + HddrTo=realloc(HddrTo, (Recipients+1) * sizeof(void *)); + HddrTo[Recipients] = zalloc(100); + + memset(FullTo, 0, 99); + memcpy(FullTo, &ptr1[4], linelen-4); + memcpy(HddrTo[Recipients], ptr1, linelen+2); + LocalMsg[Recipients] = FALSE; + + _strupr(FullTo); + + B2To = (int)(ptr1 - conn->MailBuffer); + + if (_memicmp(FullTo, "RMS:", 4) == 0) + { + // remove RMS and add @winlink.org + + strcpy(FullTo, "RMS"); + strcpy(Msg->via, &FullTo[4]); + } + else + { + ptr3 = strchr(FullTo, '@'); + + if (ptr3) + { + *ptr3++ = 0; + strcpy(Msg->via, ptr3); + } + else + Msg->via[0] = 0; + } + + if (_memicmp(&ptr1[4], "SMTP:", 5) == 0) + { + // Airmail Sends MARS messages as SMTP + + if (CheckifPacket(Msg->via)) + { + // Packet Message + + memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); + _strupr(FullTo); + _strupr(Msg->via); + + // Update the saved to: line (remove the smtp:) + + strcpy(&HddrTo[Recipients][4], &HddrTo[Recipients][9]); + BBSMsgs++; + goto BBSMsg; + } + + // If a winlink.org address we need to convert to call + + if (_stricmp(Msg->via, "winlink.org") == 0) + { + memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); + _strupr(FullTo); + LocalMsg[Recipients] = CheckifLocalRMSUser(FullTo); + } + else + { + memcpy(Msg->via, &ptr1[9], linelen); + Msg->via[linelen - 9] = 0; + strcpy(FullTo,"RMS"); + } +// FullTo[0] = 0; + + BBSMsg: + _strupr(FullTo); + _strupr(Msg->via); + } + + if (memcmp(FullTo, "RMS:", 4) == 0) + { + // remove RMS and add @winlink.org + + memmove(FullTo, &FullTo[4], strlen(FullTo) - 3); + strcpy(Msg->via, "winlink.org"); + sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); + } + + if (strcmp(Msg->via, "RMS") == 0) + { + // replace RMS with @winlink.org + + strcpy(Msg->via, "winlink.org"); + sprintf(HddrTo[Recipients], "To: %s@winlink.org\r\n", FullTo); + } + + if (strlen(FullTo) > 6) + FullTo[6] = 0; + + strlop(FullTo, '-'); + + strcpy(Msg->to, FullTo); + + if (SendBBStoSYSOPCall) + if (_stricmp(FullTo, BBSName) == 0) + strcpy(Msg->to, SYSOPCall); + + if ((Msg->via[0] == 0 || strcmp(Msg->via, "BPQ") == 0 || strcmp(Msg->via, "BBS") == 0)) + { + // No routing - check @BBS and WP + + struct UserInfo * ToUser = LookupCall(FullTo); + + Msg->via[0] = 0; // In case BPQ and not found + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->HomeBBS[0]) + { + strcpy(Msg->via, ToUser->HomeBBS); + } + } + else + { + WPRecP WP = LookupWP(FullTo); + + if (WP) + { + strcpy(Msg->via, WP->first_homebbs); + + } + } + + // Fix To: address in B2 Header + + if (Msg->via[0]) + sprintf(HddrTo[Recipients], "To: %s@%s\r\n", FullTo, Msg->via); + else + sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); + + } + + RecpTo=realloc(RecpTo, (Recipients+1) * sizeof(void *)); + RecpTo[Recipients] = zalloc(10); + + Via=realloc(Via, (Recipients+1) * sizeof(void *)); + Via[Recipients] = zalloc(50); + + strcpy(Via[Recipients], Msg->via); + strcpy(RecpTo[Recipients++], FullTo); + + // Remove the To: Line from the buffer + + } + else if (_memicmp(ptr1, "Type:", 4) == 0) + { + if (ptr1[6] == 'N') + Msg->type = 'T'; // NTS + else + Msg->type = ptr1[6]; + } + else if (_memicmp(ptr1, "Subject:", 8) == 0) + { + size_t Subjlen = ptr2 - &ptr1[9]; + if (Subjlen > 60) Subjlen = 60; + memcpy(Msg->title, &ptr1[9], Subjlen); + + goto ProcessBody; + } +// else if (_memicmp(ptr1, "Body:", 4) == 0) +// { +// MsgLen = atoi(&ptr1[5]); +// StartofMsg = ptr1; +// } + else if (_memicmp(ptr1, "File:", 5) == 0) + { + Msg->B2Flags |= Attachments; + } + else if (_memicmp(ptr1, "Date:", 5) == 0) + { + struct tm rtime; + char seps[] = " ,\t\r"; + + memset(&rtime, 0, sizeof(struct tm)); + + // Date: 2009/07/25 10:08 + + sscanf(&ptr1[5], "%04d/%02d/%02d %02d:%02d:%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + sscanf(&ptr1[5], "%02d/%02d/%04d %02d:%02d:%02d", + &rtime.tm_mday, &rtime.tm_mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + rtime.tm_year -= 1900; + + Date = mktime(&rtime) - (time_t)_MYTIMEZONE; + + if (Date == (time_t)-1) + Date = time(NULL); + + Msg->datecreated = Date; + + } + + if (linelen) // Not Null line + { + ptr1 = ptr2 + 2; // Skip cr + goto Loop; + } + + + // Processed all headers +ProcessBody: + + ptr2 +=2; // skip crlf + + Msg->length = (int)(&conn->MailBuffer[Msg->length] - ptr2); + + memmove(conn->MailBuffer, ptr2, Msg->length); + + CreateMessageFromBuffer(conn); + + conn->BBSFlags = 0; // Clear ARQ Mode + return; + } + + // File away the data + + Buffer[MsgLen++] = 0x0a; // BBS Msgs stored with crlf + + if ((conn->TempMsg->length + MsgLen) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, MsgLen); + + conn->TempMsg->length += MsgLen; + + return; + + // Not sure what to do yet with files, but will process emails (using text style forwarding + +/* +ARQ:FILE::flarqmail-1.eml +ARQ:EMAIL:: +ARQ:SIZE::96 +ARQ::STX +//FLARQ COMPOSER +Date: 16/01/2014 22:26:06 +To: g8bpq +From: +Subject: test message + +Hello +Hello + +ARQ::ETX +*/ + + return; +} + +VOID ProcessTextFwdLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int len) +{ + Buffer[len] = 0; +// Debugprintf(Buffer); + + // With TNC2 body prompt is a single CR, so that shouldn't be ignored. + + // If thia causes problems with other TNC PMS implementations I'll have to revisit this + +// if (len == 1 && Buffer[0] == 13) +// return; + + if (conn->Flags & SENDTITLE) + { + // Waiting for Subject: prompt + + struct MsgInfo * Msg = conn->FwdMsg; + + nodeprintf(conn, "%s\r", Msg->title); + + conn->Flags &= ~SENDTITLE; + conn->Flags |= SENDBODY; + + // New Paccom PMS (V3.2) doesn't prompt for body so drop through and send it + if ((conn->BBSFlags & NEWPACCOM) == 0) + return; + + } + + if (conn->Flags & SENDBODY) + { + // Waiting for Enter Message Prompt + + struct tm * tm; + time_t temp; + + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + char * MsgPtr; + int MsgLen; + int Index = 0; + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + MsgLen = conn->FwdMsg->length; + + // If a B2 Message, remove B2 Header + + if (conn->FwdMsg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &conn->FwdMsg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + nodeprintf(conn, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + conn->FwdMsg->number, BBSName, HRoute, RlineVer); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + BBSputs(conn, "\r"); + + MsgLen = RemoveLF(MsgPtr, MsgLen); + + QueueMsg(conn, MsgPtr, MsgLen); + + if (user->ForwardingInfo->SendCTRLZ) + nodeprintf(conn, "\r\x1a"); + else + nodeprintf(conn, "\r/ex\r"); + + free(MsgBytes); + + conn->FBBMsgsSent = TRUE; + + + if (conn->FwdMsg->type == 'P') + Index = PMSG; + else if (conn->FwdMsg->type == 'B') + Index = BMSG; + else if (conn->FwdMsg->type == 'T') + Index = TMSG; + + user->Total.MsgsSent[Index]++; + user->Total.BytesForwardedOut[Index] += MsgLen; + + conn->Flags &= ~SENDBODY; + conn->Flags |= WAITPROMPT; + + return; + } + + if (conn->Flags & WAITPROMPT) + { + if (Buffer[len-2] != '>') + return; + + conn->Flags &= ~WAITPROMPT; + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + SaveMessageDatabase(); + + conn->UserPointer->ForwardingInfo->MsgCount--; + + // See if any more to forward + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg; + + // If we are using SETCALLTOSENDER make sure this message is from the same sender + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + unsigned char AXCall[7]; + + Msg = conn->FwdMsg; + ConvToAX25(Msg->from, AXCall); + if (memcmp(SESS[conn->BPQStream - 1].HOSTSESSION->L4USER, AXCall, 7) != 0) + { + Disconnect(conn->BPQStream); + return; + } + + // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ + + conn->Flags |= SENDTITLE; + + + if ((conn->BBSFlags & SETCALLTOSENDER)) + nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); + else + nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, + Msg->from, Msg->bid); + } + else + { + Disconnect(conn->BPQStream); + } + return; + } +} + + +#define N 2048 /* buffer size */ +#define F 60 /* lookahead buffer size */ +#define THRESHOLD 2 +#define NIL N /* leaf of tree */ + +extern UCHAR * infile; + +BOOL CheckforMIME(SocketConn * sockptr, char * Msg, char ** Body, int * MsgLen); + + +VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + char * Cmd, * Arg1; + char * Context; + char seps[] = " \t\r"; + int CmdLen; + + if (_memicmp(Buffer, "POSYNCLOGON", 11) == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->BBSFlags |= SYNCMODE; + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + + Sleep(500); + + BBSputs(conn, "OK\r"); + Flush(conn); + return; + } + + if (_memicmp(Buffer, "POSYNCHELLO", 11) == 0) + { + // This is first message received after connecting to SYNC + // Save Callsign + + char Reply[32]; + conn->BBSFlags |= SYNCMODE; + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + + sprintf(Reply, "POSYNCLOGON %s\r", BBSName); + BBSputs(conn, Reply); + return; + } + + if (conn->BBSFlags & SYNCMODE) + { + ProcessSyncModeMessage(conn, user, Buffer, len); + return; + } + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // A few messages should be trapped here and result in an immediate disconnect, whatever mode I think the session is in (it could be wrong) + + // *** Protocol Error + // Already Connected + // Invalid Command + + if (_memicmp(Buffer, "Already Connected", 17) == 0 || + _memicmp(Buffer, "Invalid Command", 15) == 0 || + _memicmp(Buffer, "*** Protocol Error", 18) == 0) + { + conn->BBSFlags |= DISCONNECTING; + Disconnect(conn->BPQStream); + return; + } + + if (conn->BBSFlags & FBBForwarding) + { + ProcessFBBLine(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & FLARQMODE) + { + ProcessFLARQLine(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & MCASTRX) + { + ProcessMCASTLine(conn, user, Buffer, len); + return; + } + + + if (conn->BBSFlags & TEXTFORWARDING) + { + ProcessTextFwdLine(conn, user, Buffer, len); + return; + } + + // if chatting to sysop pass message to BBS console + + if (conn->BBSFlags & SYSOPCHAT) + { + SendUnbuffered(-1, Buffer,len); + return; + } + + if (conn->Flags & GETTINGMESSAGE) + { + ProcessMsgLine(conn, user, Buffer, len); + return; + } + if (conn->Flags & GETTINGTITLE) + { + ProcessMsgTitle(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & MBLFORWARDING) + { + ProcessMBLLine(conn, user, Buffer, len); + return; + } + + if (conn->Flags & GETTINGUSER || conn->NewUser) // Could be new user but dont need name + { + if (memcmp(Buffer, ";FW:", 4) == 0 || Buffer[0] == '[') + { + struct BBSForwardingInfo * ForwardingInfo; + + conn->Flags &= ~GETTINGUSER; + + // New User is a BBS - create a temp struct for it + + if ((user->flags & (F_BBS | F_Temp_B2_BBS)) == 0) // It could already be a BBS without a user name + { + // Not defined as BBS - allocate and initialise forwarding structure + + user->flags |= F_Temp_B2_BBS; + + // An RMS Express user that needs a temporary BBS struct + + ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + + ForwardingInfo->AllowCompressed = TRUE; + ForwardingInfo->AllowBlocked = TRUE; + conn->UserPointer->ForwardingInfo->AllowB2 = TRUE; + } + SaveUserDatabase(); + } + else + { + if (conn->Flags & GETTINGUSER) + { + conn->Flags &= ~GETTINGUSER; + if (len > 18) + len = 18; + + memcpy(user->Name, Buffer, len-1); + SendWelcomeMsg(conn->BPQStream, conn, user); + SaveUserDatabase(); + UpdateWPWithUserInfo(user); + return; + } + } + } + + // Process Command + + if (conn->Paging && (conn->LinesSent >= conn->PageLen)) + { + // Waiting for paging prompt + + if (len > 1) + { + if (_memicmp(Buffer, "Abort", 1) == 0) + { + ClearQueue(conn); + conn->LinesSent = 0; + + nodeprintf(conn, AbortedMsg); + + if (conn->UserPointer->Temp->ListSuspended) + nodeprintf(conn, "bort, , = Continue..>"); + + SendPrompt(conn, user); + return; + } + } + + conn->LinesSent = 0; + return; + } + + if (user->Temp->ListSuspended) + { + // Paging limit hit when listing. User may abort, continue, or read one or more messages + + ProcessSuspendedListCommand(conn, user, Buffer, len); + return; + } + if (len == 1) + { + SendPrompt(conn, user); + return; + } + + Buffer[len] = 0; + + if (strstr(Buffer, "ARQ:FILE:")) + { + // Message from FLARQ + + conn->BBSFlags |= FLARQMODE; + strcpy(conn->ARQFilename, &Buffer[10]); // Will need name when we decide what to do with files + + // Create a Temp Messge Stucture + + CreateMessage(conn, conn->Callsign, "", "", 'P', NULL, NULL); + + Buffer[len++] = 0x0a; // BBS Msgs stored with crlf + + if ((conn->TempMsg->length + len) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); + + conn->TempMsg->length += len; + + return; + } + if (Buffer[0] == ';') // WL2K Comment + { + if (memcmp(Buffer, ";FW:", 4) == 0) + { + // Paclink User Select (poll for list) + + char * ptr1,* ptr2, * ptr3; + int index=0; + + // Convert string to Multistring + + Buffer[len-1] = 0; + + conn->PacLinkCalls = zalloc(len*3); + + ptr1 = &Buffer[5]; + ptr2 = (char *)conn->PacLinkCalls; + ptr2 += (len * 2); + strcpy(ptr2, ptr1); + + while (ptr2) + { + ptr3 = strlop(ptr2, ' '); + + if (strlen(ptr2)) + conn->PacLinkCalls[index++] = ptr2; + + ptr2 = ptr3; + } + + return; + } + + if (memcmp(Buffer, ";FR:", 4) == 0) + { + // New Message from TriMode - Just igonre till I know what to do with it + + return; + } + + // Ignore other ';' message + + return; + } + + + + if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID + { + // If a BBS, set BBS Flag + + if (user->flags & ( F_BBS | F_Temp_B2_BBS)) + { + if (user->ForwardingInfo) + { + if (user->ForwardingInfo->Forwarding && ((conn->BBSFlags & OUTWARDCONNECT) == 0)) + { + BBSputs(conn, "Already Connected\r"); + Flush(conn); + Sleep(500); + Disconnect(conn->BPQStream); + return; + } + } + + if (user->ForwardingInfo) + { + user->ForwardingInfo->Forwarding = TRUE; + user->ForwardingInfo->FwdTimer = 0; // So we dont send to immediately + } + } + + if (user->flags & ( F_BBS | F_PMS | F_Temp_B2_BBS)) + { + Parse_SID(conn, &Buffer[1], len-4); + + if (conn->BBSFlags & FBBForwarding) + { + conn->FBBIndex = 0; // ready for first block; + conn->FBBChecksum = 0; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + } + else + FBBputs(conn, ">\r"); + + } + + return; + } + + Cmd = strtok_s(Buffer, seps, &Context); + + if (Cmd == NULL) + { + if (!CheckForTooManyErrors(conn)) + BBSputs(conn, "Invalid Command\r"); + + SendPrompt(conn, user); + return; + } + + Arg1 = strtok_s(NULL, seps, &Context); + CmdLen = (int)strlen(Cmd); + + // Check List first. If any other, save last listed to user record. + + if (_memicmp(Cmd, "L", 1) == 0 && _memicmp(Cmd, "LISTFILES", 3) != 0) + { + DoListCommand(conn, user, Cmd, Arg1, FALSE, Context); + SendPrompt(conn, user); + return; + } + + if (conn->lastmsg > user->lastmsg) + { + user->lastmsg = conn->lastmsg; + SaveUserDatabase(); + } + + if (_stricmp(Cmd, "SHOWRMSPOLL") == 0) + { + DoShowRMSCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "AUTH") == 0) + { + DoAuthCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "Abort", 1) == 0) + { + ClearQueue(conn); + conn->LinesSent = 0; + + nodeprintf(conn, AbortedMsg); + + if (conn->UserPointer->Temp->ListSuspended) + nodeprintf(conn, "bort, , = Continue..>"); + + SendPrompt(conn, user); + return; + } + if (_memicmp(Cmd, "Bye", CmdLen) == 0 || _stricmp(Cmd, "ELSE") == 0) + { + ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); + Flush(conn); + Sleep(1000); + + if (conn->BPQStream > 0) + Disconnect(conn->BPQStream); +#ifndef LINBPQ + else + CloseConsole(conn->BPQStream); +#endif + return; + } + + if (_memicmp(Cmd, "Node", 4) == 0) + { + ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); + Flush(conn); + Sleep(1000); + + if (conn->BPQStream > 0) + ReturntoNode(conn->BPQStream); +#ifndef LINBPQ + else + CloseConsole(conn->BPQStream); +#endif + return; + } + + if (_memicmp(Cmd, "IDLETIME", 4) == 0) + { + DoSetIdleTime(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "SETNEXTMESSAGENUMBER") == 0) + { + DoSetMsgNo(conn, user, Arg1, Context); + return; + } + + if (strlen(Cmd) < 12 && _memicmp(Cmd, "D", 1) == 0) + { + DoDeliveredCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "K", 1) == 0) + { + DoKillCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + + if (_memicmp(Cmd, "LISTFILES", 3) == 0 || _memicmp(Cmd, "FILES", 5) == 0) + { + ListFiles(conn, user, Arg1); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "READFILE", 4) == 0) + { + ReadBBSFile(conn, user, Arg1); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "REROUTEMSGS", 7) == 0) + { + if (conn->sysop == 0) + nodeprintf(conn, "Reroute Messages needs SYSOP status\r"); + else + { + ReRouteMessages(); + nodeprintf(conn, "Ok\r"); + } + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "YAPP", 4) == 0) + { + YAPPSendFile(conn, user, Arg1); + return; + } + + if (_memicmp(Cmd, "UH", 2) == 0 && conn->sysop) + { + DoUnholdCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_stricmp(Cmd, "IMPORT") == 0) + { + DoImportCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "EXPORT") == 0) + { + DoExportCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "I", 1) == 0) + { + char * Save; + char * MsgBytes; + + if (Arg1) + { + // User WP lookup + + DoWPLookup(conn, user, Cmd[1], Arg1); + SendPrompt(conn, user); + return; + } + + + MsgBytes = Save = ReadInfoFile("info.txt"); + if (MsgBytes) + { + int Length; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(Save); + } + else + BBSputs(conn, "SYSOP has not created an INFO file\r"); + + + SendPrompt(conn, user); + return; + } + + + if (_memicmp(Cmd, "Name", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 17) + Arg1[17] = 0; + + strcpy(user->Name, Arg1); + UpdateWPWithUserInfo(user); + + } + + SendWelcomeMsg(conn->BPQStream, conn, user); + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "OP", 2) == 0) + { + int Lines; + + // Paging Control. Param is number of lines per page + + if (Arg1) + { + Lines = atoi(Arg1); + + if (Lines) // Sanity Check + { + if (Lines < 10) + { + nodeprintf(conn,"Page Length %d is too short\r", Lines); + SendPrompt(conn, user); + return; + } + } + + user->PageLen = Lines; + conn->PageLen = Lines; + conn->Paging = (Lines > 0); + SaveUserDatabase(); + } + + nodeprintf(conn,"Page Length is %d\r", user->PageLen); + SendPrompt(conn, user); + + return; + } + + if (_memicmp(Cmd, "QTH", CmdLen) == 0) + { + if (Arg1) + { + // QTH may contain spaces, so put back together, and just split at cr + + Arg1[strlen(Arg1)] = ' '; + strtok_s(Arg1, "\r", &Context); + + if (strlen(Arg1) > 60) + Arg1[60] = 0; + + strcpy(user->Address, Arg1); + UpdateWPWithUserInfo(user); + + } + + nodeprintf(conn,"QTH is %s\r", user->Address); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "ZIP", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 8) + Arg1[8] = 0; + + strcpy(user->ZIP, _strupr(Arg1)); + UpdateWPWithUserInfo(user); + } + + nodeprintf(conn,"ZIP is %s\r", user->ZIP); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "CMSPASS", 7) == 0) + { + if (Arg1 == 0) + { + nodeprintf(conn,"Must specify a password\r"); + } + else + { + if (strlen(Arg1) > 15) + Arg1[15] = 0; + + strcpy(user->CMSPass, Arg1); + nodeprintf(conn,"CMS Password Set\r"); + SaveUserDatabase(); + } + + SendPrompt(conn, user); + + return; + } + + if (_memicmp(Cmd, "PASS", CmdLen) == 0) + { + if (Arg1 == 0) + { + nodeprintf(conn,"Must specify a password\r"); + } + else + { + if (strlen(Arg1) > 12) + Arg1[12] = 0; + + strcpy(user->pass, Arg1); + nodeprintf(conn,"BBS Password Set\r"); + SaveUserDatabase(); + } + + SendPrompt(conn, user); + + return; + } + + + if (_memicmp(Cmd, "R", 1) == 0) + { + DoReadCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "S", 1) == 0) + { + if (!DoSendCommand(conn, user, Cmd, Arg1, Context)) + SendPrompt(conn, user); + return; + } + + if ((_memicmp(Cmd, "Help", CmdLen) == 0) || (_memicmp(Cmd, "?", 1) == 0)) + { + char * Save; + char * MsgBytes = Save = ReadInfoFile("help.txt"); + + if (MsgBytes) + { + int Length; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(Save); + } + else + { + BBSputs(conn, "A - Abort Output\r"); + BBSputs(conn, "B - Logoff\r"); + BBSputs(conn, "CMSPASS Password - Set CMS Password\r"); + BBSputs(conn, "D - Flag NTS Message(s) as Delivered - D num\r"); + BBSputs(conn, "HOMEBBS - Display or get HomeBBS\r"); + BBSputs(conn, "INFO - Display information about this BBS\r"); + BBSputs(conn, "I CALL - Lookup CALL in WP Allows *CALL CALL* *CALL* wildcards\r"); + BBSputs(conn, "I@ PARAM - Lookup @BBS in WP\r"); + BBSputs(conn, "IZ PARAM - Lookup Zip Codes in WP\r"); + BBSputs(conn, "IH PARAM - Lookup HA elements in WP - eg USA EU etc\r"); + + BBSputs(conn, "K - Kill Message(s) - K num, KM (Kill my read messages)\r"); + BBSputs(conn, "L - List Message(s) - \r"); + BBSputs(conn, " L = List New, LR = List New (Oldest first)\r"); + BBSputs(conn, " LM = List Mine, L> Call, L< Call, L@ = List to, from or at\r"); + BBSputs(conn, " LL num = List msg num, L num-num = List Range\r"); + BBSputs(conn, " LN LY LH LK LF L$ LD = List Message with corresponding Status\r"); + BBSputs(conn, " LB LP LT = List Mesaage with corresponding Type\r"); + BBSputs(conn, " LC = List TO fields of all active bulletins\r"); + BBSputs(conn, " You can combine most selections eg LMP, LMN LB< G8BPQ\r"); + BBSputs(conn, "LISTFILES or FILES - List files available for download\r"); + + BBSputs(conn, "N Name - Set Name\r"); + BBSputs(conn, "NODE - Return to Node\r"); + BBSputs(conn, "OP n - Set Page Length (Output will pause every n lines)\r"); + BBSputs(conn, "PASS Password - Set BBS Password\r"); + BBSputs(conn, "POLLRMS - Manage Polling for messages from RMS \r"); + BBSputs(conn, "Q QTH - Set QTH\r"); + BBSputs(conn, "R - Read Message(s) - R num \r"); + BBSputs(conn, " RM (Read new messages to me), RMR (RM oldest first)\r"); + BBSputs(conn, "READ Name - Read File\r"); + + BBSputs(conn, "S - Send Message - S or SP Send Personal, SB Send Bull, ST Send NTS,\r"); + BBSputs(conn, " SR Num - Send Reply, SC Num - Send Copy\r"); + BBSputs(conn, "X - Toggle Expert Mode\r"); + BBSputs(conn, "YAPP - Download file from BBS using YAPP protocol\r"); + if (conn->sysop) + { + BBSputs(conn, "DOHOUSEKEEPING - Run Housekeeping process\r"); + BBSputs(conn, "EU - Edit User Flags - Type EU for Help\r"); + BBSputs(conn, "EXPORT - Export messages to file - Type EXPORT for Help\r"); + BBSputs(conn, "FWD - Control Forwarding - Type FWD for Help\r"); + BBSputs(conn, "IMPORT - Import messages from file - Type IMPORT for Help\r"); + BBSputs(conn, "REROUTEMSGS - Rerun message routing process\r"); + BBSputs(conn, "SETNEXTMESSAGENUMBER - Sets next message number\r"); + BBSputs(conn, "SHOWRMSPOLL - Displays your RMS polling list\r"); + BBSputs(conn, "UH - Unhold Message(s) - UH ALL or UH num num num...\r"); + } + } + + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "Ver", CmdLen) == 0) + { + nodeprintf(conn, "BBS Version %s\rNode Version %s\r", VersionStringWithBuild, GetVersionString()); + + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "HOMEBBS", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 40) Arg1[40] = 0; + + strcpy(user->HomeBBS, _strupr(Arg1)); + UpdateWPWithUserInfo(user); + + if (!strchr(Arg1, '.')) + BBSputs(conn, "Please enter HA with HomeBBS eg g8bpq.gbr.eu - this will help message routing\r"); + } + + nodeprintf(conn,"HomeBBS is %s\r", user->HomeBBS); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if ((_memicmp(Cmd, "EDITUSER", 5) == 0) || (_memicmp(Cmd, "EU", 2) == 0)) + { + DoEditUserCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "POLLRMS") == 0) + { + DoPollRMSCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "DOHOUSEKEEPING") == 0) + { + DoHousekeepingCmd(conn, user, Arg1, Context); + return; + } + + + if (_stricmp(Cmd, "FWD") == 0) + { + DoFwdCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "X", 1) == 0) + { + user->flags ^= F_Expert; + + if (user->flags & F_Expert) + BBSputs(conn, "Expert Mode\r"); + else + BBSputs(conn, "Expert Mode off\r"); + + SaveUserDatabase(); + SendPrompt(conn, user); + return; + } + + if (conn->Flags == 0) + { + if (!CheckForTooManyErrors(conn)) + BBSputs(conn, "Invalid Command\r"); + + SendPrompt(conn, user); + } + + // Send if possible + + Flush(conn); +} + +VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...) +{ + // seems to be printf to a socket + + char buff[600]; + va_list(arglist); + + va_start(arglist, format); + vsprintf(buff, format, arglist); + + BBSputs(conn, buff); +} + +// Code to delete obsolete files from Mail folder + +#ifdef WIN32 + +int DeleteRedundantMessages() +{ + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + char File[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + int Msgno; + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, MailDir); + strcat(szDir, "\\*.mes"); + + + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + return 0; + } + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + OutputDebugString(ffd.cFileName); + } + else + { + Msgno = atoi(&ffd.cFileName[2]); + + if (MsgnotoMsg[Msgno] == 0) + { + sprintf(File, "%s/%s%c", MailDir, ffd.cFileName, 0); + Debugprintf("Tidy Mail - Delete %s\n", File); + +// if (DeletetoRecycleBin) + DeletetoRecycle(File); +// else +// DeleteFile(File); + } + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + return 0; +} + +#else + +#include + +int MsgFilter(const struct dirent * dir) +{ + return (strstr(dir->d_name, ".mes") != 0); +} + +int DeleteRedundantMessages() +{ + struct dirent **namelist; + int n; + struct stat STAT; + int Msgno = 0, res; + char File[100]; + + n = scandir("Mail", &namelist, MsgFilter, alphasort); + + if (n < 0) + perror("scandir"); + else + { + while(n--) + { + if (stat(namelist[n]->d_name, &STAT) == 0); + { + Msgno = atoi(&namelist[n]->d_name[2]); + + if (MsgnotoMsg[Msgno] == 0) + { + sprintf(File, "Mail/%s", namelist[n]->d_name); + printf("Deleting %s\n", File); + unlink(File); + } + } + free(namelist[n]); + } + free(namelist); + } + return 0; +} +#endif + +VOID TidyWelcomeMsg(char ** pPrompt) +{ + // Make sure Welcome Message doesn't ends with > + + char * Prompt = *pPrompt; + + int i = (int)strlen(Prompt) - 1; + + *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it + + Prompt = *pPrompt; + + while (Prompt[i] == 10 || Prompt[i] == 13) + { + Prompt[i--] = 0; + } + + while (i >= 0 && Prompt[i] == '>') + Prompt[i--] = 0; + + strcat(Prompt, "\r\n"); +} + +VOID TidyPrompt(char ** pPrompt) +{ + // Make sure prompt ends > CR LF + + char * Prompt = *pPrompt; + + int i = (int)strlen(Prompt) - 1; + + *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it + + Prompt = *pPrompt; + + while (Prompt[i] == 10 || Prompt[i] == 13) + { + Prompt[i--] = 0; + } + + if (Prompt[i] != '>') + strcat(Prompt, ">"); + + strcat(Prompt, "\r\n"); +} + +VOID TidyPrompts() +{ + TidyPrompt(&Prompt); + TidyPrompt(&NewPrompt); + TidyPrompt(&ExpertPrompt); +} + +BOOL SendARQMail(CIRCUIT * conn) +{ + conn->NextMessagetoForward = FirstMessageIndextoForward; + + // Send Message. There is no mechanism for reverse forwarding + + if (FindMessagestoForward(conn)) + { + struct MsgInfo * Msg; + char MsgHddr[512]; + int HddrLen; + char TimeString[64]; + char * WholeMessage; + + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + int MsgLen; + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + Msg = conn->FwdMsg; + WholeMessage = malloc(Msg->length + 512); + + FormatTime(TimeString, (time_t)Msg->datecreated); + +/* +ARQ:FILE::flarqmail-1.eml +ARQ:EMAIL:: +ARQ:SIZE::96 +ARQ::STX +//FLARQ COMPOSER +Date: 16/01/2014 22:26:06 +To: g8bpq +From: +Subject: test message + +Hello +Hello + +ARQ::ETX +*/ + Logprintf(LOG_BBS, conn, '>', "ARQ Send Msg %d From %s To %s", Msg->number, Msg->from, Msg->to); + + HddrLen = sprintf(MsgHddr, "Date: %s\nTo: %s\nFrom: %s\nSubject %s\n\n", + TimeString, Msg->to, Msg->from, Msg->title); + + MsgLen = sprintf(WholeMessage, "ARQ:FILE::Msg%s_%d\nARQ:EMAIL::\nARQ:SIZE::%d\nARQ::STX\n%s%s\nARQ::ETX\n", + BBSName, Msg->number, (int)(HddrLen + strlen(MsgBytes)), MsgHddr, MsgBytes); + + WholeMessage[MsgLen] = 0; + QueueMsg(conn,WholeMessage, MsgLen); + + free(WholeMessage); + free(MsgBytes); + + // FLARQ doesn't ACK the message, so set flag to look for all acked + + conn->BBSFlags |= ARQMAILACK; + conn->ARQClearCount = 10; // To make sure clear isn't reported too soon + + return TRUE; + } + + // Nothing to send - close + + Logprintf(LOG_BBS, conn, '>', "ARQ Send - Nothing to Send - Closing"); + + conn->CloseAfterFlush = 20; + return FALSE; +} + +char *stristr (char *ch1, char *ch2) +{ + char *chN1, *chN2; + char *chNdx; + char *chRet = NULL; + + chN1 = _strdup (ch1); + chN2 = _strdup (ch2); + if (chN1 && chN2) + { + chNdx = chN1; + while (*chNdx) + { + *chNdx = (char) tolower (*chNdx); + chNdx ++; + } + chNdx = chN2; + while (*chNdx) + { + *chNdx = (char) tolower (*chNdx); + chNdx ++; + } + + chNdx = strstr (chN1, chN2); + if (chNdx) + chRet = ch1 + (chNdx - chN1); + } + free (chN1); + free (chN2); + return chRet; +} + +#ifdef WIN32 + +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, GetBPQDirectory()); + strcat(szDir, "\\BPQMailChat\\Files\\*.*"); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + nodeprintf(conn, "No Files\r"); + return; + } + + // List all the files in the directory with some info about them. + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + {} + else + { + if (filename == NULL || stristr(ffd.cFileName, filename)) + nodeprintf(conn, "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); +} + +#else + +#include + +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + struct dirent **namelist; + int n, i; + struct stat STAT; + time_t now = time(NULL); + int Age = 0, res; + char FN[256]; + + n = scandir("Files", &namelist, NULL, alphasort); + + if (n < 0) + perror("scandir"); + else + { + for (i = 0; i < n; i++) + { + sprintf(FN, "Files/%s", namelist[i]->d_name); + + if (filename == NULL || stristr(namelist[i]->d_name, filename)) + if (FN[6] != '.' && stat(FN, &STAT) == 0) + nodeprintf(conn, "%s %d\r", namelist[i]->d_name, STAT.st_size); + + free(namelist[i]); + } + free(namelist); + } + return; +} +#endif + +void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + char * MsgBytes; + + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + struct stat STAT; + + if (filename == NULL) + { + nodeprintf(conn, "Missing Filename\r"); + return; + } + + if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) + { + nodeprintf(conn, "Invalid filename\r"); + return; + } + + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + int Length; + + MsgBytes=malloc(FileSize+1); + fread(MsgBytes, 1, FileSize, hFile); + fclose(hFile); + + MsgBytes[FileSize]=0; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(MsgBytes); + + nodeprintf(conn, "\r\r[End of File %s]\r", filename); + return; + } + } + + nodeprintf(conn, "File %s not found\r", filename); +} + +VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + struct TempUserInfo * Temp = user->Temp; + + Buffer[len] = 0; + + // Command entered during listing pause. May be A R or C (or ) + + if (Buffer[0] == 'A' || Buffer[0] == 'a') + { + // Abort + + Temp->ListActive = Temp->ListSuspended = FALSE; + SendPrompt(conn, user); + return; + } + + if (_memicmp(Buffer, "R ", 2) == 0) + { + // Read Message(es) + + int msgno; + char * ptr; + char * Context; + + ptr = strtok_s(&Buffer[2], " ", &Context); + + while (ptr) + { + msgno = atoi(ptr); + ReadMessage(conn, user, msgno); + + ptr = strtok_s(NULL, " ", &Context); + } + + nodeprintf(conn, "bort, , = Continue..>"); + return; + } + + if (Buffer[0] == 'C' || Buffer[0] == 'c' || Buffer[0] == '\r' ) + { + // Resume Listing from where we left off + + DoListCommand(conn, user, Temp->LastListCommand, Temp->LastListParams, TRUE, ""); + SendPrompt(conn, user); + return; + } + + nodeprintf(conn, "bort, , = Continue..>"); + +} +/* +CreateMessageWithAttachments() +{ + int i; + char * ptr, * ptr2, * ptr3, * ptr4; + char Boundary[1000]; + BOOL Multipart = FALSE; + BOOL ALT = FALSE; + int Partlen; + char * Save; + BOOL Base64 = FALSE; + BOOL QuotedP = FALSE; + + char FileName[100][250] = {""}; + int FileLen[100]; + char * FileBody[100]; + char * MallocSave[100]; + UCHAR * NewMsg; + + int Files = 0; + + ptr = Msg; + + if ((sockptr->MailSize + 2000) > sockptr->MailBufferSize) + { + sockptr->MailBufferSize += 2000; + sockptr->MailBuffer = realloc(sockptr->MailBuffer, sockptr->MailBufferSize); + + if (sockptr->MailBuffer == NULL) + { + CriticalErrorHandler("Failed to extend Message Buffer"); + shutdown(sockptr->socket, 0); + return FALSE; + } + } + + + NewMsg = sockptr->MailBuffer + 1000; + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", FileLen[0]); + + for (i = 1; i < Files; i++) + { + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[i], FileName[i]); + } + + NewMsg += sprintf(NewMsg, "\r\n"); + + for (i = 0; i < Files; i++) + { + memcpy(NewMsg, FileBody[i], FileLen[i]); + NewMsg += FileLen[i]; + free(MallocSave[i]); + NewMsg += sprintf(NewMsg, "\r\n"); + } + + *MsgLen = NewMsg - (sockptr->MailBuffer + 1000); + *Body = sockptr->MailBuffer + 1000; + + return TRUE; // B2 Message +} + +*/ +VOID CreateUserReport() +{ + struct UserInfo * User; + int i; + char Line[200]; + int len; + char File[MAX_PATH]; + FILE * hFile; + + sprintf(File, "%s/UserList.csv", BaseDir); + + hFile = fopen(File, "wb"); + + if (hFile == NULL) + { + Debugprintf("Failed to create UserList.csv"); + return; + } + + for (i=1; i <= NumberofUsers; i++) + { + User = UserRecPtr[i]; + + len = sprintf(Line, "%s,%d,%s,%x,%s,\"%s\",%x,%s,%s,%s\r\n", + User->Call, + User->lastmsg, + FormatDateAndTime((time_t)User->TimeLastConnected, FALSE), + User->flags, + User->Name, + User->Address, + User->RMSSSIDBits, + User->HomeBBS, + User->QRA, + User->ZIP +// struct MsgStats Total; +// struct MsgStats Last; + ); + fwrite(Line, 1, len, hFile); + } + + fclose(hFile); +} + +BOOL ProcessYAPPMessage(CIRCUIT * conn) +{ + int Len = conn->InputLen; + UCHAR * Msg = conn->InputBuffer; + int pktLen = Msg[1]; + char Reply[2] = {ACK}; + int NameLen, SizeLen, OptLen; + char * ptr; + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char Mess[255]; + int len; + char * FN = &Msg[2]; + + switch (Msg[0]) + { + case ENQ: // YAPP Send_Init + + // Shouldn't occur in session. Reset state + + Mess[0] = ACK; + Mess[1] = 1; + QueueMsg(conn, Mess, 2); + Flush(conn); + conn->InputLen = 0; + if (conn->MailBuffer) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + } + return TRUE; + + case SOH: + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + // YAPPC has date/time in dos format + + if (Len < Msg[1] + 1) + return 0; + + NameLen = (int)strlen(FN); + strcpy(conn->ARQFilename, FN); + ptr = &Msg[3 + NameLen]; + SizeLen = (int)strlen(ptr); + FileSize = atoi(ptr); + + // Check file name for unsafe characters (.. / \) + + if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) + { + Mess[0] = NAK; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPP File Name %s invalid\r", FN); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + OptLen = pktLen - (NameLen + SizeLen + 2); + + conn->YAPPDate = 0; + + if (OptLen >= 8) // We have a Date/Time for YAPPC + { + ptr = ptr + SizeLen + 1; + conn->YAPPDate = strtol(ptr, NULL, 16); + } + + // Check Size + + if (FileSize > MaxRXSize) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); + QueueMsg(conn, Mess, Mess[1] + 2); + + Flush(conn); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + // Make sure file does not exist + + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "YAPP File %s already exists\r", conn->ARQFilename);; + QueueMsg(conn, Mess, Mess[1] + 2); + + Flush(conn); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s already exists\r", conn->ARQFilename); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + fclose(hFile); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + + conn->MailBufferSize = FileSize; + conn->MailBuffer=malloc(FileSize); + conn->YAPPLen = 0; + + if (conn->YAPPDate) // If present use YAPPC + Reply[1] = ACK; //Receive_TPK + else + Reply[1] = 2; //Rcv_File + + QueueMsg(conn, Reply, 2); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP upload to %s started", conn->ARQFilename); + WriteLogLine(conn, '!', Mess, len, LOG_BBS); + + conn->InputLen = 0; + return FALSE; + + case STX: + + // Data Packet + + // Check we have it all + + if (conn->YAPPDate) // If present use YAPPC so have checksum + { + if (pktLen > (Len - 3)) // -3 for header and checksum + return 0; // Wait for rest + } + else + { + if (pktLen > (Len - 2)) // -2 for header + return 0; // Wait for rest + } + + // Save data and remove from buffer + + // if YAPPC check checksum + + if (conn->YAPPDate) + { + UCHAR Sum = 0; + int i; + UCHAR * uptr = &Msg[2]; + + i = pktLen; + + while(i--) + Sum += *(uptr++); + + if (Sum != *uptr) + { + // Checksum Error + + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPPC Checksum Error\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + return TRUE; + } + } + + if ((conn->YAPPLen) + pktLen > conn->MailBufferSize) + { + // Too Big ?? + + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPP Too much data received\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + return TRUE; + } + + + memcpy(&conn->MailBuffer[conn->YAPPLen], &Msg[2], pktLen); + conn->YAPPLen += pktLen; + + if (conn->YAPPDate) + ++pktLen; // Add Checksum + + conn->InputLen -= (pktLen + 2); + memmove(conn->InputBuffer, &conn->InputBuffer[pktLen + 2], conn->InputLen); + + return TRUE; + + case ETX: + + // End Data + + + + if (conn->YAPPLen == conn->MailBufferSize) + { + // All received + + int ret; + DWORD Written = 0; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); + +#ifdef WIN32 + hFile = CreateFile(MsgFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + ret = WriteFile(hFile, conn->MailBuffer, conn->YAPPLen, &Written, NULL); + + if (conn->YAPPDate) + { + FILETIME FileTime; + struct tm TM; + struct timeval times[2]; + time_t TT; +/* + The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) +*/ + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; + TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; + + Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); + + TT = mktime(&TM); + times[0].tv_sec = times[1].tv_sec = + times[0].tv_usec = times[1].tv_usec = 0; + + DosDateTimeToFileTime((WORD)(conn->YAPPDate >> 16), (WORD)conn->YAPPDate & 0xFFFF, &FileTime); + ret = SetFileTime(hFile, &FileTime, &FileTime, &FileTime); + ret = GetLastError(); + + } + CloseHandle(hFile); + } +#else + + hFile = fopen(MsgFile, "wb"); + if (hFile) + { + Written = fwrite(conn->MailBuffer, 1, conn->YAPPLen, hFile); + fclose(hFile); + + if (conn->YAPPDate) + { + struct tm TM; + struct timeval times[2]; +/* + The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) +*/ + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; + TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; + + Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); + + times[0].tv_sec = times[1].tv_sec = mktime(&TM); + times[0].tv_usec = times[1].tv_usec = 0; + } + } +#endif + + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + if (Written != conn->YAPPLen) + { + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "Failed to save YAPP File\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + } + } + + Reply[1] = 3; //Ack_EOF + QueueMsg(conn, Reply, 2); + Flush(conn); + conn->InputLen = 0; + + return TRUE; + + case EOT: + + // End Session + + Reply[1] = 4; // Ack_EOT + QueueMsg(conn, Reply, 2); + Flush(conn); + conn->InputLen = 0; + conn->InputMode = 0; + + len = sprintf_s(Mess, sizeof(Mess), "YAPP file %s received\r", conn->ARQFilename); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + + return TRUE; + + case CAN: + + // Abort + + Mess[0] = ACK; + Mess[1] = 5; // CAN Ack + QueueMsg(conn, Mess, 2); + Flush(conn); + + if (conn->MailBuffer) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + } + + // There may be a reason after the CAN + + len = Msg[1]; + + if (len) + { + char * errormsg = &Msg[2]; + errormsg[len] = 0; + nodeprintf(conn, "File Rejected - %s\r", errormsg); + } + else + + nodeprintf(conn, "File Rejected\r"); + + + len = sprintf_s(Mess, sizeof(Mess), "YAPP Transfer cancelled by Terminal\r"); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + + return FALSE; + + case ACK: + + switch (Msg[1]) + { + case 1: // Rcv_Rdy + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + len = (int)strlen(conn->ARQFilename) + 3; + + strcpy(&Mess[2], conn->ARQFilename); + len += sprintf(&Mess[len], "%d", conn->MailBufferSize); + len++; // include null + Mess[0] = SOH; + Mess[1] = len - 2; + + QueueMsg(conn, Mess, len); + Flush(conn); + conn->InputLen = 0; + + return FALSE; + + case 2: + + // Start sending message + + YAPPSendData(conn); + conn->InputLen = 0; + return FALSE; + + case 3: + + // ACK EOF - Send EOT + + + Mess[0] = EOT; + Mess[1] = 1; + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->InputLen = 0; + return FALSE; + + case 4: + + // ACK EOT + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + + conn->InputLen = 0; + return FALSE; + + default: + conn->InputLen = 0; + return FALSE; + + + + } + + case NAK: + + // Either Reject or Restart + + // RE Resume NAK len R NULL (File size in ASCII) NULL + + if (conn->InputLen > 2 && Msg[2] == 'R' && Msg[3] == 0) + { + int posn = atoi(&Msg[4]); + + conn->YAPPLen += posn; + conn->MailBufferSize -= posn; + + YAPPSendData(conn); + conn->InputLen = 0; + return FALSE; + + } + + // There may be a reason after the ack + + len = Msg[1]; + + if (len) + { + char * errormsg = &Msg[2]; + errormsg[len] = 0; + nodeprintf(conn, "File Rejected - %s\r", errormsg); + } + else + + nodeprintf(conn, "File Rejected\r"); + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + conn->InputLen = 0; + SendPrompt(conn, conn->UserPointer); + return FALSE; + } + + nodeprintf(conn, "Unexpected message during YAPP Transfer. Transfer canncelled\r"); + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + conn->InputLen = 0; + SendPrompt(conn, conn->UserPointer); + + return FALSE; + +} + +void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + struct stat STAT; + + if (filename == NULL) + { + nodeprintf(conn, "Filename missing\r"); + SendPrompt(conn, user); + return; + } + + if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) + { + nodeprintf(conn, "Invalid filename\r"); + SendPrompt(conn, user); + return; + } + + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + char Mess[255]; + strcpy(conn->ARQFilename, filename); + conn->MailBuffer = malloc(FileSize); + conn->MailBufferSize = FileSize; + conn->YAPPLen = 0; + fread(conn->MailBuffer, 1, FileSize, hFile); + fclose(hFile); + + Mess[0] = ENQ; + Mess[1] = 1; + + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->InputMode = 'Y'; + + return; + } + } + + nodeprintf(conn, "File %s not found\r", filename); + SendPrompt(conn, user); +} + +void YAPPSendData(ConnectionInfo * conn) +{ + char Mess[258]; + + conn->BBSFlags |= YAPPTX; + + while (TXCount(conn->BPQStream) < 15) + { + int Left = conn->MailBufferSize; + + if (Left == 0) + { + // Finished - send End Data + + Mess[0] = ETX; + Mess[1] = 1; + + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->BBSFlags &= ~YAPPTX; + break; + } + + if (Left > conn->paclen - 2) // 2 byte header + Left = conn->paclen -2; + + memcpy(&Mess[2], &conn->MailBuffer[conn->YAPPLen], Left); + Mess[0] = STX; + Mess[1] = Left; + + QueueMsg(conn, Mess, Left + 2); + Flush(conn); + + conn->YAPPLen += Left; + conn->MailBufferSize -= Left; + } +} + +char * AddUser(char * Call, char * password, BOOL BBSFlag) +{ + struct UserInfo * USER; + + strlop(Call, '-'); + + if (strlen(Call) > 6) + Call[6] = 0; + + _strupr(Call); + + if (Call[0] == 0 || LookupCall(Call)) + { + return("User already exists\r\n"); + } + + USER = AllocateUserRecord(Call); + USER->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (strlen(password) > 12) + password[12] = 0; + + strcpy(USER->pass, password); + + if (BBSFlag) + { + if(SetupNewBBS(USER)) + USER->flags |= F_BBS; + else + printf("Cannot set user to be a BBS - you already have 160 BBS's defined\r\n"); + } + + SaveUserDatabase(); + UpdateWPWithUserInfo(USER); + + return("User added\r\n"); +} + +// Server Support Code + +// For the moment only internal REQDIR and REQFIL. + +// May add WPSERV and user implemented servers +/* +F6FBB BBS > + SP REQDIR @ F6ABJ.FRA.EU + Title of message : + YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU + Text of message : + /EX + + F6FBB BBS > + SP REQFIL @ F6ABJ.FRA.EU + Title of message : + DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU + Text of message : + /EX + + Note Text not used. + +*/ + +VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To); + +BOOL ProcessReqDir(struct MsgInfo * Msg) +{ + char * Buffer; + int Len = 0; + char * ptr; + + // Parse title - gives directory and return address + + // YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU + + // At the moment we don't allow subdirectories but no harm handling here + + char Pattern[64]; + char * Address; + char * filename = NULL; // ?? Pattern Match ?? + +#ifdef WIN32 + + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + +#else + + #include + + struct dirent **namelist; + int n, i; + struct stat STAT; + int res; + char FN[256]; + +#endif + + strcpy(Pattern, Msg->title); + + ptr = strchr(Pattern, '@'); + + if (ptr == NULL) + + // if we don't have return address no point + // but could we default to sender?? + + return FALSE; + + *ptr++ = 0; // Terminate Path + + strlop(Pattern, ' '); + + while (*ptr == ' ') + ptr++; // accept with or without spaces round @ + + Address = ptr; + + ptr = Buffer = malloc(MaxTXSize); + +#ifdef WIN32 + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, GetBPQDirectory()); + strcat(szDir, "\\BPQMailChat\\Files\\"); + strcat(szDir, Pattern); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + Len = sprintf(Buffer, "No Files\r"); + } + else + { + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + {} + else + { + if (filename == NULL || stristr(ffd.cFileName, filename)) + Len += sprintf(&Buffer[Len], "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + } + +#else + + n = scandir("Files", &namelist, NULL, alphasort); + + if (n < 0) + perror("scandir"); + else + { + for (i = 0; i < n; i++) + { + sprintf(FN, "Files/%s", namelist[i]->d_name); + + if (filename == NULL || stristr(namelist[i]->d_name, filename)) + if (FN[6] != '.' && stat(FN, &STAT) == 0) + Len += sprintf(&Buffer[Len], "%s %d\r", namelist[i]->d_name, STAT.st_size); + + free(namelist[i]); + } + free(namelist); + } + +#endif + + // Build Message + + SendServerReply("REQDIR Reply", Buffer, Len, _strupr(Address)); + return TRUE; +} + +/* + ' Augment Message ID with the Message Pickup Station we're directing this message to. + ' + Dim strAugmentedMessageID As String + If GetMidRMS(MessageId) <> "" Then + ' The MPS RMS is already set on the message ID + strAugmentedMessageID = MessageId + strMPS = GetMidRMS(MessageId) + ' "@R" at the end of the MID means route message only via radio + If GetMidForwarding(MessageId) = "" And (blnRadioOnly Or UploadThroughInternet()) Then + strAugmentedMessageID &= "@" & strHFOnlyFlag + End If + ElseIf strMPS <> "" Then + ' Add MPS to the message ID + strAugmentedMessageID = MessageId & "@" & strMPS + ' "@R" at the end of the MID means route message only via radio + If blnRadioOnly Or UploadThroughInternet() Then + strAugmentedMessageID &= "@" & strHFOnlyFlag + End If + Else + strAugmentedMessageID = MessageId + End If + +*/ + +void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + Buffer[len] = 0; + + if (conn->Flags & GETTINGSYNCMESSAGE) + { + // Data + + if ((conn->TempMsg->length + len) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); + + conn->TempMsg->length += len; + + if (conn->TempMsg->length >= conn->SyncCompressedLen) + { + // Complete - decompress it + + conn->BBSFlags |= FBBCompressed; + Decode(conn, 1); + + conn->Flags &= !GETTINGSYNCMESSAGE; + + BBSputs(conn, "OK\r"); + return; + } + return; + } + + if (conn->Flags & PROPOSINGSYNCMSG) + { + // Waiting for response to TR AddMessage + + if (strcmp(Buffer, "OK\r") == 0) + { + char Msg[256]; + int n; + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Send the message, it has already been built + + conn->Flags &= !PROPOSINGSYNCMSG; + conn->Flags |= SENDINGSYNCMSG; + + n = sprintf_s(Msg, sizeof(Msg), "Sending SYNC message %s", conn->FwdMsg->bid); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + QueueMsg(conn, conn->SyncMessage, conn->SyncCompressedLen); + return; + } + + if (strcmp(Buffer, "NO\r") == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Message Rejected - ? duplicate + + if (conn->FwdMsg) + { + // Zap the entry + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + conn->UserPointer->ForwardingInfo->MsgCount--; + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->FwdMsg->Locked = 0; // Unlock + } + } + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->Flags &= !PROPOSINGSYNCMSG; + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (conn->Flags & SENDINGSYNCMSG) + { + if (strcmp(Buffer, "OK\r") == 0) + { + // Message Sent + + conn->Flags &= !SENDINGSYNCMSG; + free(conn->SyncMessage); + + if (conn->FwdMsg) + { + char Msg[256]; + int n; + + n = sprintf_s(Msg, sizeof(Msg), "SYNC message %s Sent", conn->FwdMsg->bid); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + conn->UserPointer->ForwardingInfo->MsgCount--; + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->FwdMsg->Locked = 0; // Unlock + } + + // drop through to send any more + } + else + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + conn->Flags &= !SENDINGSYNCMSG; + free(conn->SyncMessage); + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + + return; + } + } + + if (strcmp(Buffer, "OK\r") == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Send Message(?s) to RMS Relay SYNC + +/* +OK +>TR AddMessage_V5JLSGH591JR 786 1219 522 True +BYE*/ + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg = conn->FwdMsg; + char Buffer[128]; + char * Message; + + Message = FormatSYNCMessage(conn, Msg); + + // Need to compress it + + conn->SyncMessage = malloc(conn->SyncXMLLen + conn->SyncMsgLen + 4096); + + conn->SyncCompressedLen = Encode(Message, conn->SyncMessage, conn->SyncXMLLen + conn->SyncMsgLen, 0, 1); + + sprintf(Buffer, "TR AddMessage_%s %d %d %d True\r", // The True on end indicates compressed + Msg->bid, conn->SyncCompressedLen, conn->SyncXMLLen, conn->SyncMsgLen); + + free(Message); + + conn->Flags |= PROPOSINGSYNCMSG; + + BBSputs(conn, Buffer); + return; + } + + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (memcmp(Buffer, "TR ", 2) == 0) + { + // Messages have TR_COMMAND_BID Compressed Len XML Len Bosy Len + + char * Command; + char * BIDptr; + + BIDRec * BID; + char *ptr1, *ptr2, *context; + + // TR AddMessage_1145_G8BPQ 727 1202 440 True + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + Command = strtok_s(&Buffer[3], "_", &context); + BIDptr = strtok_s(NULL, " ", &context); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncCompressedLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncXMLLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncMsgLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + + // If addmessage need to check bid doesn't exist + + if (strcmp(Command, "AddMessage") == 0) + { + strlop(BIDptr, '@'); // sometimes has @CALL@R + if (strlen(BIDptr) > 12) + BIDptr[12] = 0; + + BID = LookupBID(BIDptr); + + if (BID) + { + BBSputs(conn, "Rejected - Duplicate BID\r"); + return; + } + } + + conn->TempMsg = zalloc(sizeof(struct MsgInfo)); + + conn->Flags |= GETTINGSYNCMESSAGE; + + BBSputs(conn, "OK\r"); + return; + } + + if (memcmp(Buffer, "BYE\r", 4) == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (memcmp(Buffer, "BBS\r", 4) == 0) + { + // Out of Sync + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->BBSFlags &= ~SYNCMODE; + return; + } + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + WriteLogLine(conn, '<', "Unexpected SYNC Message", 23, LOG_BBS); + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; +} +BOOL ProcessReqFile(struct MsgInfo * Msg) +{ + char FN[128]; + char * Buffer; + int Len = 0; + char * ptr; + struct stat STAT; + char MsgFile[MAX_PATH]; + FILE * hFile; + int FileSize; + char * MsgBytes; + + // Parse title - gives file and return address + + // DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU + + // At the moment we don't allow subdirectories but no harm handling here + + char * Address; + char * filename = NULL; // ?? Pattern Match ?? + + strcpy(FN, Msg->title); + + ptr = strchr(FN, '@'); + + if (ptr == NULL) + + // if we don't have return address no point + // but could we default to sender?? + + return FALSE; + + *ptr++ = 0; // Terminate Path + + strlop(FN, ' '); + + while (*ptr == ' ') + ptr++; // accept with or without spaces round @ + + Address = ptr; + + ptr = Buffer = malloc(MaxTXSize + 1); // Allow terminating Null + + // Build Message + + if (FN == NULL) + { + Len = sprintf(Buffer, "Missing Filename\r"); + } + else if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) + { + Len = sprintf(Buffer,"Invalid filename %s\r", FN); + } + else + { + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, FN); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", FN); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + int Length; + + if (FileSize > MaxTXSize) + FileSize = MaxTXSize; // Truncate to max size + + MsgBytes=malloc(FileSize+1); + fread(MsgBytes, 1, FileSize, hFile); + fclose(hFile); + + MsgBytes[FileSize]=0; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + Len = sprintf(Buffer, "%s", MsgBytes); + free(MsgBytes); + } + } + else + Len = sprintf(Buffer, "File %s not found\r", FN); + } + + SendServerReply("REQFIL Reply", Buffer, Len, _strupr(Address)); + return TRUE; +} + +BOOL CheckforMessagetoServer(struct MsgInfo * Msg) +{ + if (_stricmp(Msg->to, "REQDIR") == 0) + return ProcessReqDir(Msg); + + if (_stricmp(Msg->to, "REQFIL") == 0) + return ProcessReqFile(Msg); + + return FALSE; +} + +VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To) +{ + struct MsgInfo * Msg = AllocateMsgRecord(); + BIDRec * BIDRec; + char * Via; + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + + Msg->length = Length; + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + FreeSemaphore(&MsgNoSemaphore); + + strcpy(Msg->from, BBSName); + Via = strlop(To, '@'); + + if (Via) + strcpy(Msg->via, Via); + + strcpy(Msg->to, To); + strcpy(Msg->title, Title); + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + BIDRec = AllocateBIDRecord(); + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + MatchMessagetoBBSList(Msg, NULL); + free(MailBuffer); +} + +void SendRequestSync(CIRCUIT * conn) +{ + // Only need XML Header + + char * Buffer = malloc(4096); + int Len = 0; + + struct tm *tm; + char Date[32]; + char MsgTime[32]; + time_t Time = time(NULL); + + char * Encoded; + + tm = gmtime(&Time); + + sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + Len += sprintf(&Buffer[Len], "\r\n"); + + Len += sprintf(&Buffer[Len], "\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " request_sync\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Date); + Len += sprintf(&Buffer[Len], " %s\r\n", BBSName); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " BBSName\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", conn->SyncHost); + Len += sprintf(&Buffer[Len], " %d\r\n", conn->SyncPort); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); + +/* + + + + request_sync + 20230205100652 + GI8BPQ + + + GI8BPQ + + 127.0.0.1 + 8780 + + + +*/ + + // Need to compress it + + conn->SyncXMLLen = Len; + conn->SyncMsgLen = 0; + + conn->SyncMessage = malloc(conn->SyncXMLLen + 4096); + + conn->SyncCompressedLen = Encode(Buffer, conn->SyncMessage, conn->SyncXMLLen, 0, 1); + + sprintf(Buffer, "TR RequestSync_%s_%d %d %d 0 True\r", // The True on end indicates compressed + 50, conn->SyncCompressedLen, conn->SyncXMLLen); + + free(Buffer); + + conn->Flags |= REQUESTINGSYNC; + + BBSputs(conn, Buffer); + return; +} + + +void ProcessSyncXML(CIRCUIT * conn, char * XML) +{ + // Process XML from RMS Relay Sync + + // All seem to start + + // + // + // + // + + char * Type = strstr(XML, ""); + + if (Type == NULL) + return; + + Type += strlen(""); + + if (memcmp(Type, "rms_location", 12) == 0) + { + return; + } + + + if (memcmp(Type, "request_sync", 12) == 0) + { + char * Call; + struct UserInfo * BBSREC; + + // This isn't requesting a poll, it is asking to be added as a sync partner + + Call = strstr(Type, ""); + + if (Call == NULL) + return; + + Call += 10; + strlop(Call, '<'); + BBSREC = FindBBS(Call); + + if (BBSREC == NULL) + return; + + if (BBSREC->ForwardingInfo->Forwarding == 0) + StartForwarding(BBSREC->BBSNumber, NULL); + + return; + } + + if (memcmp(Type, "remove_message", 14) == 0) + { + char * MID = strstr(Type, ""); + struct MsgInfo * Msg; + + if (MID == NULL) + return; + + MID += 11; + strlop(MID, '<'); + + strlop(MID, '@'); // sometimes has @CALL@R + if (strlen(MID) > 12) + MID[12] = 0; + + Msg = FindMessageByBID(MID); + + if (Msg == NULL) + return; + + Logprintf(LOG_BBS, conn, '|', "Killing Msg %d %s", Msg->number, Msg->bid); + + FlagAsKilled(Msg, TRUE); + return; + } + + if (memcmp(Type, "delivered", 9) == 0) + { + char * MID = strstr(Type, ""); + struct MsgInfo * Msg; + + if (MID == NULL) + return; + + MID += 11; + strlop(MID, '<'); + + strlop(MID, '@'); // sometimes has @CALL@R + if (strlen(MID) > 12) + MID[12] = 0; + + Msg = FindMessageByBID(MID); + + if (Msg == NULL) + return; + + Logprintf(LOG_BBS, conn, '|', "Message Msg %d %s Delivered", Msg->number, Msg->bid); + return; + } + + Debugprintf(Type); + return; + +/* + + + + request_sync + 20230205100652 + GI8BPQ + + + GI8BPQ + + 127.0.0.1 + 8780 + + + +} + + + + delivered + 20230205093113 + G8BPQ + + + 10845_GM8BPB + G8BPQ + G8BPQ + 3 + + + + Public Enum MessageDeliveryMethod + ' + ' Method used to deliver a message. None if the message hasn't been delivered. + ' + Unspecified = -1 + None = 0 + Telnet = 1 + CMS = 2 + Radio = 3 + Email = 4 +End Enum +*/ +} + +int ReformatSyncMessage(CIRCUIT * conn) +{ + // Message has been decompressed - reformat to look like a WLE message + + char * MsgBit; + char *ptr1, *ptr2; + int linelen; + char FullFrom[80]; + char FullTo[80]; + char BID[80]; + time_t Date; + char Mon[80]; + char Subject[80]; + int i = 0; + char * Boundary; + char * Input; + char * via = NULL; + char * NewMsg = conn->MailBuffer; + char * SaveMsg = NewMsg; + char DateString[80]; + struct tm * tm; + char Type[16] = "Private"; + char * part[100] = {""}; + char * partname[100]; + int partLen[100]; + char xml[4096]; + + // Message has an XML header then the message + + // The XML may have control info, so examine it. + + /* + Date: Mon, 25 Oct 2021 10:22:00 -0000 + From: GM8BPQ + Subject: Test + To: 2E1BGT + Message-ID: ALYJQJRXVQAO + X-Source: GM8BPQ + X-Relay: G8BPQ + MIME-Version: 1.0 + MIME-Version: 1.0 + Content-Type: multipart/mixed; boundary="boundaryBSoxlw==" + + --boundaryBSoxlw== + Content-Type: text/plain; charset="iso-8859-1" + Content-Transfer-Encoding: quoted-printable + + Hello Hello + + --boundaryBSoxlw==-- + */ + + // I think the best way is to reformat as if from Winlink Express, then pass + //through the normal B2 code. + +// WriteLogLine(conn, '<', conn->MailBuffer, conn->TempMsg->length, LOG_BBS); + + // display the message for testing + + conn->MailBuffer[conn->TempMsg->length] = 0; + +// OutputDebugString(conn->MailBuffer); + memcpy(xml, conn->MailBuffer, conn->SyncXMLLen); + xml[conn->SyncXMLLen] = 0; + + if (conn->SyncMsgLen == 0) + { + // No message, Just xml. Looks like a status report + + ProcessSyncXML(conn, xml); + return 0; + } + + MsgBit = &conn->MailBuffer[conn->SyncXMLLen]; + conn->TempMsg->length -= conn->SyncXMLLen; + + ptr1 = MsgBit; + +Loop: + + ptr2 = strchr(ptr1, '\r'); + + linelen = (int)(ptr2 - ptr1); + + if (_memicmp(ptr1, "From:", 5) == 0) + { + memcpy(FullFrom, &ptr1[6], linelen - 6); + FullFrom[linelen - 6] = 0; + } + + if (_memicmp(ptr1, "To:", 3) == 0) + { + memcpy(FullTo, &ptr1[4], linelen - 4); + FullTo[linelen - 4] = 0; + } + + else if (_memicmp(ptr1, "Subject:", 8) == 0) + { + memcpy(Subject, &ptr1[9], linelen - 9); + Subject[linelen - 9] = 0; + } + + else if (_memicmp(ptr1, "Message-ID", 10) == 0) + { + memcpy(BID, &ptr1[12], linelen - 12); + BID[linelen - 12] = 0; + } + + else if (_memicmp(ptr1, "Date:", 5) == 0) + { + struct tm rtime; + char seps[] = " ,\t\r"; + + memset(&rtime, 0, sizeof(struct tm)); + + // Date: Mon, 25 Oct 2021 10:22:00 -0000 + + sscanf(&ptr1[11], "%02d %s %04d %02d:%02d:%02d", + &rtime.tm_mday, &Mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + rtime.tm_year -= 1900; + + for (i = 0; i < 12; i++) + { + if (strcmp(Mon, month[i]) == 0) + break; + } + + rtime.tm_mon = i; + + Date = mktime(&rtime) - (time_t)_MYTIMEZONE; + + if (Date == (time_t)-1) + Date = time(NULL); + + } + + if (linelen) // Not Null line + { + ptr1 = ptr2 + 2; // Skip crlf + goto Loop; + } + + // Unpack Body - seems to be multipart even if only one + + // Can't we just send the whole body through ?? + // No, Attachment format is different + + // Mbo: GM8BPQ + // Body: 17 + // File: 1471 leadercoeffs.txt + + Input = MsgBit; + Boundary = initMultipartUnpack(&Input); + + i = 0; + + if (Boundary) + { + // input should be start of part + + // Find End of part - ie -- Boundary + CRLF or -- + + char * ptr, * saveptr; + char * Msgptr; + size_t BLen = strlen(Boundary); + size_t Partlen; + + saveptr = Msgptr = ptr = Input; + + while(ptr) // Just in case we run off end + { + if (*ptr == '-' && *(ptr+1) == '-') + { + if (memcmp(&ptr[2], Boundary, BLen) == 0) + { + // Found Boundary + + char * p1, *p2, *ptr3, *ptr4; + int llen; + int Base64 = 0; + int QuotedP = 0; + char * BoundaryStart = ptr; + + Partlen = ptr - Msgptr; + + ptr += (BLen + 2); // End of Boundary + + if (*ptr == '-') // Terminating Boundary + Input = NULL; + else + Input = ptr + 2; + + // Will check for quoted printable + + p1 = Msgptr; +Loop2: + p2 = strchr(p1, '\r'); + llen = (int)(p2 - p1); + + if (llen) + { + + if (_memicmp(p1, "Content-Transfer-Encoding:", 26) == 0) + { + if (_memicmp(&p1[27], "base64", 6) == 0) + Base64 = TRUE; + else if (_memicmp(&p1[27], "quoted", 6) == 0) + QuotedP = TRUE; + } + else if (_memicmp(p1, "Content-Disposition: ", 21) == 0) + { + ptr3 = strstr(&p1[21], "name"); + + if (ptr3) + { + ptr3 += 5; + if (*ptr3 == '"') ptr3++; + ptr4 = strchr(ptr3, '"'); + if (ptr4) *ptr4 = 0; + + partname[i] = ptr3; + } + } + + if (llen) // Not Null line + { + p1 = p2 + 2; // Skip crlf + goto Loop2; + } + } + + part[i] = strstr(p2, "\r\n"); // Over separator + + if (part[i]) + { + part[i] += 2; + partLen[i] = BoundaryStart - part[i] - 2; + if (QuotedP) + partLen[i] = decode_quoted_printable(part[i], partLen[i]); + else if (Base64) + { + int Len = partLen[i], NewLen; + char * ptr = part[i]; + char * ptr2 = part[i]; + + // WLE sends base64 with embedded crlf, so remove them + + while (Len-- > 0) + { + if ((*ptr) != 10 && (*ptr) != 13) + *(ptr2++) = *(ptr++); + else + ptr ++; + } + + Len = ptr2 - part[i]; + ptr = part[i]; + ptr2 = part[i]; + + while (Len > 0) + { + decodeblock(ptr, ptr2); + ptr += 4; + ptr2 += 3; + Len -= 4; + } + + NewLen = (int)(ptr2 - part[i]); + + if (*(ptr-1) == '=') + NewLen--; + + if (*(ptr-2) == '=') + NewLen--; + + partLen[i] = NewLen; + } + } + Msgptr = ptr = Input; + i++; + continue; } + + // See if more parts + } + ptr++; + } + ptr++; + } + + + // Build the message + + tm = gmtime(&Date); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + NewMsg += sprintf(NewMsg, + "MID: %s\r\n" + "Date: %s\r\n" + "Type: %s\r\n" + "From: %s\r\n", + BID, DateString, Type, FullFrom); + +// if (ToCalls) +// { +// int i; + +// for (i = 0; i < Calls; i++) +// NewMsg += sprintf(NewMsg, "To: %s\r\n", ToCalls[i]); + +// } +// else + { + NewMsg += sprintf(NewMsg, "To: %s\r\n", + FullTo); + } +// if (WebMail->CC && WebMail->CC[0]) +// NewMsg += sprintf(NewMsg, "CC: %s\r\n", WebMail->CC); + + NewMsg += sprintf(NewMsg, + "Subject: %s\r\n" + "Mbo: %s\r\n", + Subject, BBSName); + + // Write the Body: line and any File Lines + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", partLen[0]); + + i = 1; + + while (part[i]) + { + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", + partLen[i], partname[i]); + + i++; + } + + NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line to end header + + // Now add parts + + i = 0; + + while (part[i]) + { + memmove(NewMsg, part[i], partLen[i]); + NewMsg += partLen[i]; + i++; + NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line between attachments + } + + conn->TempMsg->length = NewMsg - SaveMsg; + conn->TempMsg->datereceived = conn->TempMsg->datechanged = time(NULL); + conn->TempMsg->datecreated = Date; + strcpy(conn->TempMsg->bid, BID); + + if (strlen(Subject) > 60) + Subject[60] = 0; + + strcpy(conn->TempMsg->title, Subject); + + return TRUE; +} + +char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg) +{ + // First an XML Header + + char * Buffer = malloc(4096 + Msg->length); + int Len = 0; + + struct tm *tm; + char Date[32]; + char MsgTime[32]; + char Separator[33]=""; + time_t Time = time(NULL); + char * MailBuffer; + int BodyLen; + char * Encoded; + + // Get the message - may need length in header + + MailBuffer = ReadMessageFile(Msg->number); + + BodyLen = Msg->length; + + // Remove any B2 Header + + if (Msg->B2Flags & B2Msg) + { + // Remove B2 Headers (up to the File: Line) + + char * ptr; + ptr = strstr(MailBuffer, "Body:"); + if (ptr) + { + BodyLen = atoi(ptr + 5); + ptr = strstr(ptr, "\r\n\r\n"); + } + if (ptr) + { + memcpy(MailBuffer, ptr + 4, BodyLen); + MailBuffer[BodyLen] = 0; + } + } + + // encode body as quoted printable; + + Encoded = malloc(Msg->length * 3); + + BodyLen = encode_quoted_printable(MailBuffer, Encoded, BodyLen); + + // Create multipart Boundary + + CreateOneTimePassword(&Separator[0], "Key", 0); + CreateOneTimePassword(&Separator[16], "Key", 1); + + + tm = gmtime(&Time); + + sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + tm = gmtime((time_t *)&Msg->datecreated); + + sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + Len += sprintf(&Buffer[Len], "\r\n"); + + Len += sprintf(&Buffer[Len], "\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " add_message\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Date); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); + Len += sprintf(&Buffer[Len], " \r\n", MsgTime); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " 2\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", (Msg->B2Flags & Attachments) ? "true" : "false"); + Len += sprintf(&Buffer[Len], " %d\r\n", BodyLen); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->title); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); + Len += sprintf(&Buffer[Len], " 450443\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->to); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " 0\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " True\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); + +// Debugprintf(Buffer); + + conn->SyncXMLLen = Len; + + Len += sprintf(&Buffer[Len], "Date: Sat, 04 Feb 2023 11:19:00 +0000\r\n"); + Len += sprintf(&Buffer[Len], "From: %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], "Subject: %s\r\n", Msg->title); + Len += sprintf(&Buffer[Len], "To: %s\r\n", Msg->to); + Len += sprintf(&Buffer[Len], "Message-ID: %s\r\n", Msg->bid); +// Len += sprintf(&Buffer[Len], "X-Source: G8BPQ\r\n"); +// Len += sprintf(&Buffer[Len], "X-Location: 52.979167N, 1.125000W (GRID SQUARE)\r\n"); +// Len += sprintf(&Buffer[Len], "X-RMS-Originator: G8BPQ\r\n"); +// Len += sprintf(&Buffer[Len], "X-RMS-Path: G8BPQ@2023-02-04-11:19:29\r\n"); + Len += sprintf(&Buffer[Len], "X-Relay: %s\r\n", BBSName); + + Len += sprintf(&Buffer[Len], "MIME-Version: 1.0\r\n"); + Len += sprintf(&Buffer[Len], "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", Separator); + + Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before separator + Len += sprintf(&Buffer[Len], "--%s\r\n", Separator); + Len += sprintf(&Buffer[Len], "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n"); + Len += sprintf(&Buffer[Len], "Content-Transfer-Encoding: quoted-printable\r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before body + + Len += sprintf(&Buffer[Len], "%s\r\n", Encoded); + Len += sprintf(&Buffer[Len], "--%s--\r\n", Separator); + + conn->SyncMsgLen = Len - conn->SyncXMLLen; + + free(Encoded); + free(MailBuffer); + + return Buffer; +} + +int encode_quoted_printable(char *s, char * out, int Len) +{ + int n = 0; + char * start = out; + + while(Len--) + { + if (n >= 73 && *s != 10 && *s != 13) + {strcpy(out, "=\r\n"); n = 0; out +=3;} + if (*s == 10 || *s == 13) {putchar(*s); n = 0;} + else if (*s<32 || *s==61 || *s>126) + out += sprintf(out, "=%02x", (unsigned char)*s); + else if (*s != 32 || (*(s+1) != 10 && *(s+1) != 13)) + {*(out++) = *s; n++;} + else n += printf("=20"); + + s++; + } + *out = 0; + + return out - start; +} + +int decode_quoted_printable(char *ptr, int len) +{ + // overwrite input with decoded version + + char * ptr2 = ptr; + char * End = ptr + len; + char * Start = ptr; + + while (ptr < End) + { + if ((*ptr) == '=') + { + char c = *(++ptr); + char d; + + c = c - 48; + if (c < 0) + { + // = CRLF as a soft break + + ptr += 2; + continue; + } + if (c > 9) c -= 7; + d = *(++ptr); + d = d - 48; + if (d > 9) d -= 7; + + *(ptr2) = c << 4 | d; + ptr2++; + ptr++; + } + else + *ptr2++ = *ptr++; + } + return ptr2 - Start; +} diff --git a/BPQMail.c b/BPQMail.c index 82e58bc..ab8c86c 100644 --- a/BPQMail.c +++ b/BPQMail.c @@ -1110,10 +1110,12 @@ // Fix Webmail auto-refresh when page exceeds 64K bytes (54) // Fix Webmail send when using both headers/footers and attachmonts (55) // Fix R: line corruption on some 64 bit builds -// Dont drop empty lines inm TEXTFORWARDING (61) +// Dont drop empty lines in TEXTFORWARDING (61) // Dont wait for body prompt for TEXTFORWARDING for SID [PMS-3.2-C$] (62) // Add forwarding mode SETCALLTOSENDER for PMS Systems that don't accept < in SP (63) // QtTerm Monitoring fixed for 63 port version of BPQ (69) +// Fix to UI system to support up to 63 ports (79) +// Fix recently introduced crash when "Don't allow new users" is set (81) #include "bpqmail.h" @@ -1711,7 +1713,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, } /* ---------- TAJ --PG Server------*/ - if ( user->Temp->RUNPGPARAMS ) { + if (user->Temp && user->Temp->RUNPGPARAMS ) { printf("Also freeing RUNPGARGS\n"); free(user->Temp->RUNPGPARAMS); diff --git a/BPQMail.c.bak b/BPQMail.c.bak index ff79254..a4eecaf 100644 --- a/BPQMail.c.bak +++ b/BPQMail.c.bak @@ -1,3651 +1,3651 @@ -// Mail and Chat Server for BPQ32 Packet Switch -// -// - -// Version 1.0.0.17re - -// Split Messasge, User and BBS Editing from Main Config. -// Add word wrap to Console input and output -// Flash Console on chat user connect -// Fix processing Name response in chat mode -// Fix processing of *RTL from station not defined as a Chat Node -// Fix overlength lines ln List responses -// Housekeeping expires BIDs -// Killing a message removes it from the forwarding counts - -// Version 1.0.0.18 - -// Save User Database when name is entered or updated so it is not lost on a crash -// Fix Protocol Error in Compressed Forwarding when switching direction -// Add Housekeeping results dialog. - -// Version 1.0.0.19 - -// Allow PACLEN in forward scripts. -// Store and forward messages with CRLF as line ends -// Send Disconnect after FQ ( for LinFBB) -// "Last Listed" is saved if MailChat is closed without closing Console -// Maximum acceptable message length can be specified (in Forwarding Config) - -// Version 1.0.0.20 - -// Fix error in saving forwarding config (introduced in .19) -// Limit size of FBB forwarding block. -// Clear old connection (instead of new) if duplicate connect on Chat Node-Node link -// Send FA for Compressed Mail (was sending FB for both Compressed and Uncompressed) - -// Version 1.0.0.21 - -// Fix Connect Script Processing (wasn't waiting for CONNECTED from last step) -// Implement Defer -// Fix MBL-style forwarding -// Fix Add User (Params were not saved) -// Add SC (Send Copy) Command -// Accept call@bbs as well as call @ bbs - -// Version 1.0.0.22 - -// Implement RB RP LN LR LF LN L$ Commands. -// Implement QTH and ZIP Commands. -// Entering an empty Title cancels the message. -// Uses HomeBBS field to set @ field for local users. -// Creates basic WP Database. -// Uses WP to lookup @ field for non-local calls. -// Console "Actions" Menu renamed "Options". -// Excluded flag is actioned. -// Asks user to set HomeBBS if not already set. -// Fix "Shrinking Message" problem, where message got shorter each time it was read Initroduced in .19). -// Flash Server window when anyone connects to chat (If Console Option "Flash on Chat User Connect" set). - -// Version 1.0.0.23 - -// Fix R: line scan bug - -// Version 1.0.0.24 - -// Fix closing console window on 'B'. -// Fix Message Creation time. -// Enable Delete function in WP edit dialog - -// Version 1.0.0.25 - -// Implement K< and K> commands -// Experimental support for B1 and B2 forwarding -// Experimental UI System -// Fix extracting QTH from WP updates - -// Version 1.0.0.26 - -// Add YN etc responses for FBB B1/B2 - -// Version 1.0.0.27 - -// Fix crash if NULL received as start of a packet. -// Add Save WP command -// Make B2 flag BBS-specific. -// Implement B2 Send - -// Version 1.0.0.28 - -// Fix parsing of smtp to addresses - eg smtp:john.wiseman@cantab.net -// Flag messages as Held if smtp server rejects from or to addresses -// Fix Kill to (K> Call) -// Edit Message dialog shows latest first -// Add chat debug window to try to track down occasional chat connection problems - -// Version 1.0.0.29 - -// Add loads of try/excspt - -// Version 1.0.0.30 - -// Writes Debug output to LOG_DEBUG_X and Monitor Window - -// Version 1.0.0.32 - -// Allow use of GoogleMail for ISP functions -// Accept SYSOP as alias for SYSOPCall - ie user can do SP SYSOP, and it will appear in sysop's LM, RM, etc -// Email Housekeeping Results to SYSOP - -// Version 1.0.0.33 - -// Housekeeping now runs at Maintenance Time. Maintenance Interval removed. -// Allow multiple numbers on R and K commands -// Fix L command with single number -// Log if Forward count is out of step with messages to forward. -// UI Processing improved and F< command implemented - -// Version 1.0.0.34 - -// Semaphore Chat Messages -// Display Semaphore Clashes -// More Program Error Traps -// Kill Messages more than BIDLifetime old - -// Version 1.0.0.35 - -// Test for Mike - Remove B1 check from Parse_SID - -// Version 1.0.0.36 - -// Fix calculation of Housekeeping Time. -// Set dialog box background explicitly. -// Remove tray entry for chat debug window. -// Add date to log file name. -// Add Actions Menu option to disable logging. -// Fix size of main window when it changes between versions. - -// Version 1.0.0.37 - -// Implement Paging. -// Fix L< command (was giving no messages). -// Implement LR LR mmm-nnn LR nnn- (and L nnn-) -// KM should no longer kill SYSOP bulls. -// ISP interfaces allows SMTP Auth to be configured -// SMTP Client would fail to send any more messages if a connection failed - -// Version 1.0.0.38 - -// Don't include killed messages in L commands (except LK!) -// Implement l@ -// Add forwarding timebands -// Allow resizing of main window. -// Add Ver command. - -// Version 1.0.1.1 - -// First Public Beta - -// Fix part line handling in Console -// Maintenance deletes old log files. -// Add option to delete files to the recycle bin. - -// Version 1.0.2.1 - -// Allow all Node SYSOP commands in connect scripts. -// Implement FBB B1 Protocol with Resume -// Make FBB Max Block size settable for each BBS. -// Add extra logging when Chat Sessions refused. -// Fix Crash on invalid housekeeping override. -// Add Hold Messages option. -// Trap CRT Errors -// Sort Actions/Start Forwarding List - -// Version 1.0.2.2 - -// Fill in gaps in BBS Number sequence -// Fix PE if ctext contains } -// Run Houskeeping at startup if previous Housekeeping was missed - -// Version 1.0.2.3 - -// Add configured nodes to /p listing - -// Version 1.0.2.4 - -// Fix RMS (it wanted B2 not B12) -// Send messages if available after rejecting all proposals -// Dont try to send msg back to originator. - -// Version 1.0.2.5 - -// Fix timeband processing when none specified. -// Improved Chat Help display. -// Add helpful responses to /n /q and /t - -// Version 1.0.2.6 - -// Kill Personal WP messages after processing -// Make sure a node doesnt try to "join" or "leave" a node as a user. -// More tracing to try to track down lost topic links. -// Add command recall to Console -// Show users in new topic when changing topic -// Add Send From Clipboard" Action - -// Version 1.0.2.7 - -// Hold messages from the future, or with invalid dates. -// Add KH (kill held) command. -// Send Message to SYSOP when a new user connects. - -// Version 1.0.2.8 - -// Don't reject personal message on Dup BID unless we already have an unforwarded copy. -// Hold Looping messages. -// Warn SYSOP of held messages. - -// Version 1.0.2.9 - -// Close connecton on receipt of *** DONE (MBL style forwarding). -// Improved validation in link_drop (Chat Node) -// Change to welcome prompt and Msg Header for Outpost. -// Fix Connect Script processing for KA Nodes - -// Version 1.0.3.1 - -// Fix incorrect sending of NO - BID. -// Fix problems caused by a user being connected to more than one chat node. -// Show idle time on Chat /u display. -// Rewrite forwarding by HA. -// Add "Bad Words" Test. -// Add reason for holding to SYSOP "Message Held" Message. -// Make topics case-insensitive. -// Allow SR for smtp mail. -// Try to fix some user's "Add User" problem. - - -// Version 1.0.3.2 - -// Fix program error when prcessing - response in FBB forwarding. -// Fix code to flag messages as sent. - - -// Version 1.0.3.3 - -// Attempt to fix message loop on topic_change -// Fix loop if compressed size is greater than 32K when receiving with B1 protocol. -// Fix selection of B1 - -// Version 1.0.3.4 - -// Add "KISS ONLY" Flag to R: Lines (Needs Node Version 4.10.12 (4.10l) or above) -// Add Basic NNTP Interface -// Fix possible loop in lzhuf encode - -// Version 1.0.3.5 - -// Fix forwarding of Held Messages -// More attempts to fix Chat crashes. -// Limit join/leave problem with mismatched nodes. -// Add Chat Node Monitoring System. -// Change order of elements in nntp addresses (now to.at, was at.to) - -// Version 1.0.3.6 - -// Restart and Exit if too many errors -// Fix forwarding of killed messages. -// Fix Forwarding to PaKet. -// Fix problem if BBS signon contains words from the "Fail" list - -// Version 1.0.3.7 - -// re-fix loop if compressed size is greater than 32K - reintroduced in 1.0.3.4 -// Add last message to edit users -// Change Console and Monitor Buffer sizes -// Don't flag msg as 'Y' on read if it was Held or Killed - -// Version 1.0.3.8 - -// Don't connect if all messages for a BBS are held. -// Hold message if From or To are missing. -// Fix parsing of /n and /q commands -// fix possible loop on changing name or qth - -// Version 1.0.3.9 - -// More Chat fixes and monitoring -// Added additional console for chat - -// Version 1.0.3.10 - -// Fix for corruption of CIrcuit-Node chain. - -// Version 1.0.3.11 - -// Fix flow control for SMTP and NNTP - -// Version 1.0.3.12 - -// Fix crash in SendChatStatus if no Chat Links Defined. -// Disable Chat Mode if there is no ApplCall for ChatApplNum, -// Add Edit Message to Manage Messages Dialog -// NNTP needs authentication - - -// Version 1.0.3.13 - -// Fix Chat ApplCall warning when ChatAppl = 0 -// Add NNTP NEWGROUPS Command -// Fix MBL Forwarding (remove extra > prompt after SP) - -// Version 1.0.3.14 - -// Fix topic switch code. -// Send SYSOP messages on POP3 interface if User SYSOP flag is set. -// NNTP only needs Authentication for posting, not reading. - -// Version 1.0.3.15 - -// Fix reset of First to Forward after househeeping - -// Version 1.0.3.16 - -// Fix check of HA for terminating WW -// MBL Mode remove extra > prompts -// Fix program error if WP record has unexpected format -// Connect Script changes for WINMOR -// Fix typo in unconfigured node has connected message - -// Version 1.0.3.17 - -// Fix forwarding of Personals - -// Version 1.0.3.18 - -// Fix detection of misconfigured nodes to work with new nodes. -// Limit connection attempt rate when a chat node is unavailable. -// Fix Program Error on long input lines (> ~250 chars). - -// Version 1.0.3.19 - -// Fix Restart of B2 mode transfers. -// Fix error if other end offers B1 and you are configured for B2 only. - - -// Version 1.0.3.20 - -// Fix Paging in Chat Mode. -// Report Node Versions. - -// Version 1.0.3.21 - -// Check node is not already known when processing OK -// Add option to suppress emailing of housekeeping results - -// Version 1.0.3.22 - -// Correct Version processing when user connects via the network -// Add time controlled forwarding scripts - -// Version 1.0.3.23 - -// Changes to RMS forwarding - -// Version 1.0.3.24 - -// Fix RMS: from SMTP interface -// Accept RMS/ instead of RMS: for Thunderbird - -// Version 1.0.3.25 - -// Accept smtp: addresses from smtp client, and route to ISP gateway. -// Set FROM address of messages from RMS that are delivered to smtp client so a reply will go back via RMS. - -// Version 1.0.3.26 - -// Improve display of rms and smtp messages in message lists and message display. - -// Version 1.0.3.27 - -// Correct code that prevents mail being retured to originating BBS. -// Tidy stuck Nodes and Topics when all links close -// Fix B2 handling of @ to TO Address. - -// Version 1.0.3.28 - -// Ensure user Record for the BBS Call has BBS bit set. -// Don't send messages addressed @winlink.org if addressee is a local user with Poll RMS set. -// Add user configurable welcome messages. - -// Version 1.0.3.29 - -// Add AUTH feature to Rig Control - -// Version 1.0.3.30 - -// Process Paclink Header (;FW:) - -// Version 1.0.3.31 - -// Process Messages with attachments. -// Add inactivity timeout to Chat Console sessions. - -// Version 1.0.3.32 - -// Fix for Paclink > BBS Addresses - -// Version 1.0.3.33 - -// Fix multiple transfers per session for B2. -// Kill messages eent to paclink. -// Add option to forward messages on arrival. - -// Version 1.0.3.34 - -// Fix bbs addresses to winlink. -// Fix adding @winlink.org to imcoming paclink msgs - -// Version 1.0.3.35 - -// Fix bbs addresses to winlink. (Again) - -// Version 1.0.3.36 - -// Restart changes for RMS/paclink - -// Version 1.0.3.37 - -// Fix for RMS Express forwarding - -// Version 1.0.3.38 - -// Fixes for smtp and lower case packet addresses from Airmail -// Fix missing > afer NO - Bid in MBL mode - -// Version 1.0.3.39 - -// Use ;FW: for RMS polling. - -// Version 1.0.3.40 - -// Add ELSE Option to connect scripts. - -// Version 1.0.3.41 - -// Improved handling of Multiple Addresses -// Add user colours to chat. - -// Version 1.0.3.42 - -// Poll multiple SSID's for RMS -// Colour support for BPQTEerminal -// New /C chat command to toggle colour on or off. - -// Version 1.0.3.43 - -// Add SKIPPROMPT command to forward scripts - -// Version 1.0.4.1 - -// Non - Beta Release -// Fix possible crash/corruption with long B2 messages - -// Version 1.0.4.2 - -// Add @winlink.org to the B2 From addresss if it is just a callsign -// Route Flood Bulls on TO as well as @ - -// Version 1.0.4.3 - -// Handle Packet Addresses from RMS Express -// Fix for Housekeeping B$ messages - -// Version 1.0.4.4 - -// Remove B2 header and all but the Body part from messages forwared using MBL -// Fix handling of ;FW: from RMS Express - -// Version 1.0.4.5 - -// Disable Paging on forwarding sessions. -// Kill Msgs sent to RMS Exxpress -// Add Name to Chat *** Joined msg - -// Version 1.0.4.6 - -// Pass smtp:winlink.org messages from Airmail to local user check -// Only apply local user check to RMS: messages @winlink.org -// Check locally input smtp: messages for local winlink.org users -// Provide facility to allow only one connect on a port - -// Version 1.0.4.8 - -// Only reset last listed on L or LR commands. - -// Version 1.0.4.9 - -// Fix error in handling smtp: messages to winlink.org addresses from Airmail - -// Version 1.0.4.10 - -// Fix Badwords processing -// Add Connect Script PAUSE command - -// Version 1.0.4.11 - -// Suppress display and listing of held messages -// Add option to exclude SYSOP messages from LM, KM, etc -// Fix crash whan receiving messages with long lines via plain text forwarding - -// Version 1.0.4.12 Jul 2010 - -// Route P messages on AT -// Allow Applications above 8 - -// Version 1.0.4.13 Aug 2010 - -// Fix TidyString for addresses of form John Wiseman -// Add Try/Except around socket routines - -// Version 1.0.4.14 Aug 2010 - -// Trap "Error - TNC Not Ready" in forward script response -// Fix restart after program error -// Add INFO command -// Add SYSOP-configurable HELP Text. - -// Version 1.0.4.15 Aug 2010 - -// Semaphore Connect/Disconnect -// Semaphore RemoveTempBIDS - -// Version 1.0.4.16 Aug 2010 - -// Remove prompt after receiving unrecognised line in MBL mode. (for MSYS) - -// Version 1.0.4.17 Aug 2010 - -// Fix receiving multiple messages in FBB Uncompressed Mode -// Try to trap phantom chat node connections -// Add delay to close - - -// Version 1.0.4.18 Aug 2010 - -// Add "Send SYSTEM messages to SYSOP Call" Option -// set fwd bit on local winlink.org msgs if user is a BBS -// add winlink.org to from address of messages from WL2K that don't already have an @ - -// Version 1.0.4.19 Sept 2010 - -// Build a B2 From: address if possible, so RMS Express can reply to packet messages. -// Fix handling of addresses from WL2K with SSID's -// L@ now only matches up to length of input string. -// Remove "Type H for help" from login prompt. - -// Version 1.0.4.20 Sept 2010 - -// Process FBB 'E' response -// Handle FROM addresses with an @BBS -// Fix FROM addresses with @ on end. -// Extend delay before close after sending FQ on winmor/pactor sessions. - -// Version 1.0.4.21 Sept 2010 - -// Fix handling B2 From: with an HA -// Add "Expert User" welcome message. - -// Version 1.0.4.22 Sept 2010 - -// Version 1.0.4.23 Oct 2010 - -// Add Dup message supression -// Dont change B2 from if going to RMS - -// Version 1.0.4.24 Oct 2010 - -// Add "Save Registry Config" command -// Add forwarding on wildcarded TO for NTS -// Add option to force text mode forwarding -// Define new users as a temporaty BBS if SID received in reply to Name prompt -// Reduce delay before sending close after sending FQ on pactor sessions -// Fix processing of MIME boundary from GMail - -// Send /ex instead of ctrl/z for text mode forwarding -// Send [WL2K-BPQ... SID if user flagged as RMS Express -// Fix Chat Map reporting when more than one AXIP port -// Add Message State D for NTS Messages -// Forward messages in priority order - T, P, B -// Add Reject and Hold Filters -// Fix holding messages to local RMS users when received as part of a multiple addressee message - -// Version 1.0.4.25 Nov 2010 - -// Renumbered for release -// Add option to save Registry Config during Housekeeping - -// Version 1.0.4.26 Nov 2010 - -// Fix F> loop when doing MBL forwarding between BPQ BBSes -// Allow multiple To: addresses, separated by ; -// Allow Houskeeping Lifetime Overrides to apply to Unsent Messages. -// Set Unforwarded Bulls to status '$' -// Accept MARS and USA as continent codes for MARS Packet Addresses -// Add option to send Non-delivery notifications. - -// Version 1.0.4.27 Dec 2010 - -// Add MSGTYPES fwd file option - -// Version 1.0.4.28 Dec 2010 - -// Renumbered to for release - -// Version 1.0.4.30 Dec 2010 - -// Fix rescan requeuing where bull was rejected by a BBS -// Fiz flagging bulls received by NNTP with $ if they need to be forwarded. -// Add Chat Keepalive option. -// Fix bug in non-delivery notification. - -// Version 1.0.4.32 Jan 2011 - -// Allow "Send from Clipboard" to send to rms: or smtp: -// Allow messages received via SMTP to be bulls (TO preceeded by bull/) or NTS (to nnnnn@NTSXX or nnnnn@NTSXX.NTS) -// Fix corruption of messages converted to B2 if body contains binary data -// Fix occasional program error when forwarding B2 messages -// Limit FBB protocol data blocks to 250 to try to fix restart problem. -// Add F2 to F5 to open windows. - -// Version 1.0.4.33 Jan 2011 - -// Fix holding old bulls with forwarding info. - -// Version 1.0.4.33 Jan 2011 - -// Prevent transfer restarting after a program error. -// Allow Housekeeping to kill held messages. - -// Version 1.0.4.35 Jan 2011 - -// Add Size limits for P and T messages to MSGTYPES command -// Fix Error in MBL processing when blank lines received (introduced in .33) -// Trap possible PE in Send_MON_Datagram -// Don't use paging on chat sessions - -// Version 1.0.4.36 Jan 2011 - -// Fix error after handling first FBB block. -// Add $X and $x welcome message options. - -// Version 1.0.4.37 Jan 2011 - -// Change L command not to list the last message if no new ones are available -// Add LC I I@ IH IZ commands -// Add option to send warning to sysop if forwarded P or T message has nowhere to go -// Fixes for Winpack Compressed Download -// Fix Houskeeping when "Apply Overrides to Unsent Bulls" is set. -// Add console copy/paste. -// Add "No Bulls" Option. -// Add "Mail For" Beacon. -// Tidied up Tab order in config dialogs to help text-to-speech programs. -// Limit MaxMsgno to 99000. - -// Version 1.0.4.38 Feb 2011 - -// Renumbered for release - -// Version 1.0.4.40 April 2011 - -// Add POLLRMS command - -// Changes for Vista/Win7 (registry key change) -// Workaround for changes to RMS Express -// Fix AUTH bug in SMTP server -// Add filter to Edit Messages dialog - -// Version 1.0.4.41 April 2011 - -// Extend B2 proposals to other BPQMail systems so Reject Filter will work. -// Add Edit User Command -// Use internal Registry Save routine instead of Regedit -// Fix Start Forward/All -// Allow Winpack Compressed Upload/Download if PMS flag set (as well as BBS flag) -// Add FWD SYSOP command -// Fix security on POLLRMS command -// Add AUTH command -// Leave selection in same place after Delete User -// Combine SMTP server messages to multiple WL2K addresses into one message to WL2k -// Add option to show name as well as call on Chat messages -// Fix program error if you try to define more than 80 BBS's - -// Version 1.0.4.45 October 2011 - -// Changes to program error reporting. -// BBS "Returh to Node" command added -// Move config to "Standard" location (BPQ Directory/BPQMailChat) . -// Fix crash if "Edit Message" clicked with no message selected. - -// Version 1.0.4.46 October 2011 - -// Fix BaseDir test when BaseDir ends with \ or / -// Fix long BaseDir values (>50 chars) - -// Version 1.4.47.1 January 2012 - -// Call CloseBPQ32 on exit -// Add option to flash window instead of sounding bell on Chat Connects -// Add ShowRMS SYSOP command -// Update WP with I records from R: lines -// Send WP Updates -// Fix Paclen on Pactor-like sessions -// Fix SID and Prompt when RMS Express User is set -// Try to stop loop in Program Error/Restarting code -// Trap "UNABLE TO CONNECT" response in connect script -// Add facility to print messages or save them to a text file - -// Version 1.4.48.1 January 2012 - -// Add Send Message (as well as Send from Clipboard) -// Fix Email From: Address when forwaring using B2 -// Send WP from BBSCALL not SYSOPCALL -// Send Chat Map reports via BPQ32.dll - - -// Version 1.4.49.1 February 2012 - - -// Fix Setting Paclink mode on SNOS connects -// Remove creation of debugging file for each message -// Add Message Export and Import functions -// All printing of more than one message at a time -// Add command to toggle "Expert" status - -// Version 1.4.50.1 February 2012 - -// Fix forwarding to RMS Express users -// Route messages received via B2 to an Internet email address to RMS -// Add Reverse Poll interval -// Add full FROM address to POP3 messages -// Include HOMEBBS command in Help - - -// Version 1.4.51.1 June 2012 - -// Allow bulls to be sent from RMS Express. -// Handle BASE64 and Quoted-printable encoding of single part messages -// Work round for RMS Express "All proposals rejected" Bug. - -// Version 1.4.52.1 August 2012 - -// Fix size limit on B2 To List when sending to multiple dests -// Fix initialisation of DIRMES.SYS control record -// Allow use of Tracker and UZ7HO ports for UI messages - -// Version 1.4.53.1 September 2012 - -// Fix crash if R: line with out a CR found. - -// Version 1.4.54.1 ?? 2012 - -// Add configurable prompts -// Fix KISS-Only Test -// Send EHLO instead of HELO when Authentication is needed on SMTP session -// Add option to use local tome for bbs forwarding config -// Allow comment lines (; or @) or single space in fwd scripts -// Fix loss of forwarding info if SAVE is clicked before selecting a call - -// Version 1.4.55.1 June 2013 - -// Add option to remove users that have not connected for a long time. -// Add l@ smtp: -// Fix From: sent to POP3 Client when meaages is from RMS -// Display Email From on Manage Messages - -// Version 1.4.56.1 July 2013 - -// Add timeout -// Verify prompts -// Add IDLETIME command - - - -// Version 1.4.57.1 - -// Change default IDLETIME -// Fix display of BBS's in Web "Manage Messages" -// Add separate househeeping lifetines for T messages -// Don't change flag on forwarded or delivered messages if they sre subsequently read -// Speed up processing, mainly to stop RMS Express timing out when connecting via Telnet -// Don't append winlink.org to RMS Express or Paclink addresses if RMS is not configured -// Fix receiving NTS messages via B2 -// Add option to send "Mail For", but not FBB Headers -// Fix corruption caused with Subject longer than 60 bytes reveived from Winlink systems -// Fix Endian bug in FBB Compression code - - -// Version 1.4.58.1 - -// Change control of appending winlink.org to RMS Express or Paclink addresses to a user flag -// Lookup HomeBBS and WP for calls without a via received from RMS Express or Paclink -// Treat call@bpq as request to look up address in Home BBS/WP for messages received from RMS Express or Paclink -// Collect stats by message type -// Fix Non-Delivery notifications to SMTP messages -// Add Message Type Stats to BBS Trafic Report -// Add "Batch forward to email" -// Add EXPORT command -// Allow more BBS records -// Allow lower case connect scripts -// Fix POP3 LIST command -// Fix MIME Multipart Alternate with first part Base64 or Quoted Printable encoding -// Fix duplicates of SP SYSOP@WW Messages -// Add command line option (tidymail) to delete redundant Mail files -// Add command line option (nohomebbs) to suppress HomeBBS prompt - -// 59 April 2014 - -// Add FLARQ Mail Mode -// Fix possible crash saving restart data -// Add script command ADDLF for connect scripts over Telnet -// Add recogniton of URONODE connected message -// Add option to stop Name prompt -// Add new RMS Express users with "RMS Express User" flag set -// Validate HTML Pages -// Add NTS swap file -// Add basic File list and read functions -// Fix Traffic report - -// 60 - -// Fix security hole in readfile - -// 61 August 2014 -// Set Messages to NTS:nnnnn@NTSXX to type 'T' and remove NTS -// Dont treat "Attempting downlink" as a failure -// Add option to read messages during a list -// Fix crash during message renumber on MAC -// Timeout response to SID to try to avoid hang on an incomplete connection. -// Save config in file instead of registry -// Fix Manage Messages "EXPORT" option and check filename on EXPORT command -// Fix reverse forward prompt in MBL mode. -// Fix From address in POP3 messages where path is @winlink.org -// Fix possible program error in T message procesing -// Add MaxAge param (for incoming Bulls) - - -//62 November 2014 -// Add ZIP and Permit Bulls flag to Manage Users -// Allow users to kill their own B and anyone to kill T messages -// Improve saving of "Last Listed" -// Fix LL when paging -// Send Date received in R: Line (should fix B2 message restarts) -// Fix occasional crash in terminal part line processing -// Add "SKIPCON" forwarding command to handle nodes that include "Connected" in their CTEXT -// Fix possible retry loop when message is deferred (FBB '=' response); -// Don't remove Attachments from received bulls. - -//63 Feb 2015 - -// Fix creating Bulls from RMS Express messages. -// Fix PE if message with no To: received. -// Fix setting "RMS Express User" flag on new connects from RMS Express -// Fix deleting 'T' messages downloaded by RMS Express -// Include MPS messages in count of messages to forward. -// Add new Welcome Message variable $F for messages to forward -// Fix setting Type in B2 header when usong NTS: or BULL: -// Remove trailing spaces from BID when Creating Message from Clipboard. -// Improved handling of FBB B1/B2 Restarts. - -//64 September 2015 - -// Fix Message Type in msgs from RMS Express to Internet -// Reopen Monitor window if open when program list closed -// Only apply NTS alias file to NTS Messages -// Fix failure to store some encrypted ISP passwords -// Allow EDITUSER to change "RMS Express User" flag -// Fix reporting of Config File errors -// Fix Finding MPS Messages (First to Forward was being used incorrectly) -// Add "Save Attachment" to Web Mgmt Interface -// Support Secure Signon on Forwarding sessions to CMS -// Save Forwarding config when BBS flag on user is cleared -// Pass internally generated SYSOP messages through routing process -// Add POP3 TOP command. -// Don't set 'T' messages to 'Y' when read. -// Add optional temporary connect script on "FWD NOW" command -// Add automatic import facility -// Accept RMS mail to BBS Call even if "Poll RMS" not set. - -// 65 November 2015 - -// Fix loading Housekeeping value for forwarded bulls. -// Fix re-using Fwd script override in timer driven forwarding. -// Add ampr.org handling -// Add "Dont forward" match on TO address for NTS -// Allow listing a combinatiom of state and type, such as LNT or LPF -// Fix handling ISP messages from gmail without a '+' -// Add basic WebMail support - -// 66 - -// Autoimport messages as Dummy Call, not SYSOP Call -// Add "My Messages" display option to WebMail -// Create .csv extract of User List during hourekeeping. -// Fix processing of NTS Alising of @ Addresses -// Don't reroute Delivered NTS Messages -// Add option to stop users killing T messages -// Add multicast Receive -// Fix initialising new message database format field -// Fix "Forward Messages to BBS Call" option. -// Add Filter WP Bulls option and allow multiple WP "TO" addresses -// Fix deleting P WP messages for other stations -// Fix saving blank lines in forwarding config -// Fix paging on L@ and l< -// Fix removing DELETE from IMPORT XXX DELETE and allow multiple IMPORT lines in script -// Run DeleteRedundantMessages before renumbering messages -// Connect script now tries ELSE lines if prompt not received from remote BBS -// Send connecting call instead of BBS Name when connecting to CMS server. -// Add BID filter to Manage Messages -// Fix handling of over long suject lines in IMPORT -// Allow comments before ELSE in connect script -// Add Copy and Clear to Multicast Window -// Fix possible duplicate messages with MBL forwarding -// Set "Permit EMail" on IMPORT dummy User. -// Fix repeated running of housekeeping if clock is stepped forward. -// Fix corruption of CMS Pass field by Web interface -// Kill B2 WP bulls if FilterWPBulls set -// Include Message Type in BPQ B2 proposal extensions - -// 6.0.14.1 July 2017 - -// Fix corruption of BBSNumber if RMS Ex User and BBS both checked -// Tread B messages without an AT as Flood. -// Make sure Message headers are always saved to disk when a message status changes -// Reject message instead of failing session if TO address too long in FBB forwarding -// Fix error when FBB restart data exactly fills a packet. -// Fix possible generation of msg number zero in send nondlivery notification -// Fix problem with Web "Manage Messages" when stray message number zero appears -// Fix Crash in AMPR forward when host missing from VIA -// Fix possible addition of an spurious password entry to the ;FW: line when connecting to CMS -// Fix test for Status "D" in forward check. -// Don't cancel AUTH on SMTP RSET -// Fix "nowhere to go" message on some messages sent to smtp addresses -// Add @ from Home BBS or WP is not spcified in "Send from Clipboard" - -// 6.0.15.1 Feb 2018 - -// Fix PE if Filename missing from FILE connect script command -// Suppress reporting errors after receiving FQ -// Fix problem caused by trailing spaces on callsign in WP database -// Support mixed case WINLINK Passwords - -// 6.0.16.1 March 2018 - -// Make sure messages sent to WL2K don;'t have @ on from: address -// If message to saildocs add R: line as an X header instead of to body -// Close session if more than 4 Invalid Commmad responses sent -// Report TOP in POP3 CAPA list. Allows POP3 to work with Windows Mail client - -// 6.0.17.1 November 2018 - -// Add source routing using ! eg sp g8bpq@winlink.org!gm8bpq to send via RMS on gm8bpq -// Accept an internet email address without rms: or smtp: -// Fix "Forward messages for BBS Call" when TO isn't BBS Call -// Accept NNTP commands in either case -// Add NNTP BODY command -// Timeout POP or SMTP TCP connections that are open too long -// Add YAPP support -// Fix connect script when Node CTEXT contains "} BBS " -// Fix handling null H Route -// Detect and correct duplicate BBS Numbers -// Fix problem if BBS requests FBB blocked forwarding without compression (ie SID of F without B) -// Fix crash if YAPP entered without filenmame and send BBS prompt after YAPP error messages -// Add support for Winlink HTML Forms to WebMail interface -// Update B2 header when using NTS alias file with B2 messages - -// 6.0.18.1 January 2019 - -// Ensure callsigns in WP database are upper case. -// Various fixes for Webmail -// Fix sending direct to ampr.org addresses -// Use SYSOP Call as default for Webmail if set -// Preparations for 64 bit version - - -// 6.0.19.1 September 2019 - -// Trap missing HTML reply Template or HTML files -// Fix case problems in HTML Templates -// Fix setting To call on reply to HTML messages -// More preparations for 64 bit including saving WP info as a text file. -// Set "RMS Express User" when a new user connects using PAT -// Increace maximum length on Forwarding Alias string in Web interface -// Expand multiaddress messages from Winlink Express if "Don't add @Winlink.org" set or no RMS BBS -// Fix program error if READ used without a filename -// Trap reject messages from Winlink CMS -// Fix "delete to recycle bin" on Linux -// Handle Radio Only Messages (-T or -R suffix on calling station) -// Fix program error on saving empty Alias list on Web Forwarding page -// Add REQDIR and REQFIL -// Experimental Blocked Uncompressed forwarding -// Security fix for YAPP -// Fix WebMail Cancel Send Message -// Fix processing Hold Message response from Winlink Express - -// 6.0.20.1 April 2020 - -// Improvments to YAPP -// Add Copy forwarding config -// Add Next and Previous buttons to Webmail message read screen -// Move HTML templates from HTMLPages to inline code. -// Fix Paclen on YAPP send -// Fix bug in handling "RMS Express User" -// Fix WINPACK compressed forwarding -// Add option to send P messages to more than one BBS -// Add "Default to Don't Add WINLINK.ORG" Config option -// Re-read Badwords.sys during Housekeeping -// Add BID Hold and Reject Filters -// On SMTP Send try HELO if EHLO rejected -// Allow SID response timeout to be configured per BBS -// Fix sending bulls with PAT -// Set "Forward Messages to BBS Call" when routing Bulls on TO -// Add option to send Mail For Message to APRS -// Fix WP update -// Fix Holding messages from Webmail Interface -// Add RMR command -// Add REROUTEMSGS BBS SYSOP command -// Disable null passwords and check Exclude flag in Webmail Signin -// Add basic Webmail logging - -// 6.0.21.1 December 2020 - -// Remove nulls from displayed messages. -// Fix Holding messages from SMTP and POP3 Interfaces -// Various fixes for handling messages to/from Internet email addresses -// Fix saving Email From field in Manage Messages -// Fix sending WL2K traffic reports via TriMode. -// Fix removing successive CR from Webmail Message display -// Fix Wildcarded @ forwarding -// Fix message type when receiving NTS Msgs form Airmail -// Fix address on SERVICE messages from Winlink -// Add multiple TO processing to Webmail non-template messages -// Don't backup config file if reading it fails -// Include Port and Freq on Connected log record -// Make sure welcome mesages don't end in > -// Allow flagging unread T messages as Delivered -// Replace \ with # in forward script so commands starting with # can be sent -// Fix forwarding NTS on TO field -// Fix possible crash in text mode forwarding -// Allow decimals of days in P message lifetimes and allow Houskeeping interval to be configured -// Add DOHOUSEKEEPING sysop command -// Add MARS continent code -// Try to trap 'zombie' BBS Sessions -// On Linux if "Delete to Recycle Bin" is set move deleted messages and logs to directory Deleted under current directory. -// Fix corruption of message length when reading R2 message via Read command -// Fix paging on List command and add new combinations of List options -// Fix NNTP list and LC command when bulls are killed - -// 6.0.22.1 August 2021 - -// Fix flagging messages with attachments as read. -// Fix possible corruption of WP database and subsequent crash on reloading. -// Fix format of Web Manage Messages display -// Include SETNEXTMESSAGENUMBER in SYSOP Help Message -// Fix occasional "Incoming Connect from SWITCH" -// Fix L> with numeric dests -// Improved diagnostic for MailTCP select() error. -// Clear "RMS Express User" if user is changed to a BBS -// Fix saving Window positions on exit -// Fix parsing ReplyTemplate name in Webmail -// Handle multiple addressees for WebMail Forms messages to packet stations -// Add option to allow only known users to connect -// Add basic callsign validation to From address -// Add option to forward a user's messages to Winlink -// Move User config to main config file. -// Update message status whne reading a Forms Webmail message -// Speed up killing multiple messages -// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command - -// 6.0.23.1 June 2022 - -// Fix crash when ; added to call in send commands -// Allow smtp/ override for messages from RMS Express to send via ISP gateway -// Send Internet email from RMS Express to ISP Gateway if enabled and RMS BBS not configured -// Recompiled for Web Interface changes in Node -// Add RMS Relay SYNC Mode (.17) -// Add Protocol changes for Relay RO forwarding -// Add SendWL2KPM command to connect script to allow users other than RMS to send ;FW: string to RMS Relay -// Fix B2 Header Date in Webmail message with sttachments. -// Fix bug when using YAPP with VARA (.27) -// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command -// Add mechsnism to send bbs log records to qttermtcp. (32) -// Add MFJ forwarding Mode (No @BBS on send) -// Fix handling CR/LF split over packet boundaries -// Add Header and Footers for Webmail Send (42) -// Fix Maintenance Interval in LinBPQ (53) -// Add RMS: to valid from addresses (.56) -// Fix Web management on Android deviced (.58) -// Disconnect immediately if "Invalid Command" "*** Protocol Error" or "Already Connected" received (.70) -// Check Badword and Reject filters before processing WP Messages - -// 6.0.24.1 ?? 2022 - -// Fix ' in Webmail subject (8) -// Change web buttons to white on black when pressed (10) -// Add auto-refresh option to Webmail index page (25) -// Fix displaying help and info files with crlf line endings on Linux (28) -// Improve validation of extended FC message (32) -// Improve WP check for SYSTEM as a callsign (33) -// Improvements to RMS Relay SYNC mode (47) -// Fix BID Hold and Reject filters -// Fix Webmail auto-refresh when page exceeds 64K bytes (54) -// Fix Webmail send when using both headers/footers and attachmonts (55) -// Fix R: line corruption on some 64 bit builds -// Dont drop empty lines inm TEXTFORWARDING (61) -// Dont wait for body prompt for TEXTFORWARDING for SID [PMS-3.2-C$] (62) -// Add forwarding mode SETCALLTOSENDER for PMS Systems that don't accept < in SP (63) -// QtTerm Monitoring fixed for 63 port version of BPQ (69) - - -#include "bpqmail.h" -#include "winstdint.h" -#define MAIL -#include "Versions.h" - -#include "GetVersion.h" - -#define MAX_LOADSTRING 100 - -typedef int (WINAPI FAR *FARPROCX)(); -typedef int (WINAPI FAR *FARPROCZ)(); - -FARPROCX pDllBPQTRACE; -FARPROCZ pGetLOC; -FARPROCX pRefreshWebMailIndex; -FARPROCX pRunEventProgram; - -BOOL WINE = FALSE; - -INT_PTR CALLBACK UserEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -INT_PTR CALLBACK MsgEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -INT_PTR CALLBACK FwdEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -INT_PTR CALLBACK WPEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); - -VOID SetupNTSAliases(char * FN); - -HKEY REGTREE = HKEY_LOCAL_MACHINE; // Default -char * REGTREETEXT = "HKEY_LOCAL_MACHINE"; - -// Global Variables: -HINSTANCE hInst; // current instance -TCHAR szTitle[MAX_LOADSTRING]; // The title bar text -TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name - -extern int LastVer[4]; // In case we need to do somthing the first time a version is run - -UINT BPQMsg; - -HWND MainWnd; -HWND hWndSess; -RECT MainRect; -HMENU hActionMenu; -static HMENU hMenu; -HMENU hDisMenu; // Disconnect Menu Handle -HMENU hFWDMenu; // Forward Menu Handle - -int SessX, SessY, SessWidth; // Params for Session Window - -char szBuff[80]; - -#define MaxSockets 64 - -int _MYTIMEZONE = 0; - -ConnectionInfo Connections[MaxSockets+1]; - -//struct SEM AllocSemaphore = {0, 0}; -//struct SEM ConSemaphore = {0, 0}; -//struct SEM OutputSEM = {0, 0}; - -//struct UserInfo ** UserRecPtr=NULL; -//int NumberofUsers=0; - -//struct UserInfo * BBSChain = NULL; // Chain of users that are BBSes - -//struct MsgInfo ** MsgHddrPtr=NULL; -//int NumberofMessages=0; - -//int FirstMessageIndextoForward=0; // Lowest Message wirh a forward bit set - limits search - -//BIDRec ** BIDRecPtr=NULL; -//int NumberofBIDs=0; - -extern BIDRec ** TempBIDRecPtr; -//int NumberofTempBIDs=0; - -//WPRec ** WPRecPtr=NULL; -//int NumberofWPrecs=0; - -extern char ** BadWords; -//int NumberofBadWords=0; -extern char * BadFile; - -//int LatestMsg = 0; -//struct SEM MsgNoSemaphore = {0, 0}; // For locking updates to LatestMsg -//int HighestBBSNumber = 0; - -//int MaxMsgno = 60000; -//int BidLifetime = 60; -//int MaintInterval = 24; -//int MaintTime = 0; -//int UserLifetime = 0; - - -BOOL cfgMinToTray; - -BOOL DisconnectOnClose; - -extern char PasswordMsg[100]; - -char cfgHOSTPROMPT[100]; - -char cfgCTEXT[100]; - -char cfgLOCALECHO[100]; - -char AttemptsMsg[]; -char disMsg[]; - -char LoginMsg[]; - -char BlankCall[]; - - -ULONG BBSApplMask; -ULONG ChatApplMask; - -int BBSApplNum; - -//int StartStream=0; -int NumberofStreams; -int MaxStreams; - -extern char BBSSID[]; -extern char ChatSID[]; - -extern char NewUserPrompt[100]; - -extern char * WelcomeMsg; -extern char * NewWelcomeMsg; -extern char * ExpertWelcomeMsg; - -extern char * Prompt; -extern char * NewPrompt; -extern char * ExpertPrompt; - -extern BOOL DontNeedHomeBBS; - -char BBSName[100]; -char MailForText[100]; - -char SignoffMsg[100]; - -char AbortedMsg[100]; - -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]; - -char NTSAliasesPath[MAX_PATH]; -extern char NTSAliasesName[MAX_PATH]; - -char BaseDir[MAX_PATH]; -char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% - -char MailDir[MAX_PATH]; - -char RlineVer[50]; - -extern BOOL KISSOnly; - -extern BOOL OpenMon; - -extern struct ALIAS ** NTSAliases; - -extern int EnableUI; -extern int RefuseBulls; -extern int SendSYStoSYSOPCall; -extern int SendBBStoSYSOPCall; -extern int DontHoldNewUsers; -extern int ForwardToMe; - -extern int MailForInterval; - -char zeros[NBMASK]; // For forward bitmask tests - -time_t MaintClock; // Time to run housekeeping - -struct MsgInfo * MsgnotoMsg[100000]; // Message Number to Message Slot List. - -// Filter Params - -char ** RejFrom; // Reject on FROM Call -char ** RejTo; // Reject on TO Call -char ** RejAt; // Reject on AT Call -char ** RejBID; // Reject on BID - -char ** HoldFrom; // Hold on FROM Call -char ** HoldTo; // Hold on TO Call -char ** HoldAt; // Hold on AT Call -char ** HoldBID; // Hold on BID - - -// Send WP Params - -BOOL SendWP; -char SendWPVIA[81]; -char SendWPTO[11]; -int SendWPType; - - -int ProgramErrors = 0; - -UCHAR BPQDirectory[260] = ""; - - -// Forward declarations of functions included in this code module: -ATOM MyRegisterClass(HINSTANCE hInstance); -ATOM RegisterMainWindowClass(HINSTANCE hInstance); -BOOL InitInstance(HINSTANCE, int); -LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); -INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -INT_PTR CALLBACK ChatMapDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); - -unsigned long _beginthread( void( *start_address )(VOID * DParam), - unsigned stack_size, VOID * DParam); - -VOID SendMailForThread(VOID * Param); -BOOL CreatePipeThread(); -int DeleteRedundantMessages(); -VOID BBSSlowTimer(); -VOID CopyConfigFile(char * ConfigName); -BOOL CreateMulticastConsole(); -char * CheckToAddress(CIRCUIT * conn, char * Addr); -BOOL CheckifPacket(char * Via); -int GetHTMLForms(); - -struct _EXCEPTION_POINTERS exinfox; - -CONTEXT ContextRecord; -EXCEPTION_RECORD ExceptionRecord; - -DWORD Stack[16]; - -BOOL Restarting = FALSE; - -Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg) -{ - unsigned int SPPtr; - unsigned int SPVal; - - memcpy(&ContextRecord, exinfo->ContextRecord, sizeof(ContextRecord)); - memcpy(&ExceptionRecord, exinfo->ExceptionRecord, sizeof(ExceptionRecord)); - - SPPtr = ContextRecord.Esp; - - Debugprintf("BPQMail *** Program Error %x at %x in %s", - ExceptionRecord.ExceptionCode, ExceptionRecord.ExceptionAddress, Msg); - - - __asm{ - - mov eax, SPPtr - mov SPVal,eax - lea edi,Stack - mov esi,eax - mov ecx,64 - rep movsb - - } - - Debugprintf("EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x ESP %x", - ContextRecord.Eax, ContextRecord.Ebx, ContextRecord.Ecx, - ContextRecord.Edx, ContextRecord.Esi, ContextRecord.Edi, SPVal); - - Debugprintf("Stack:"); - - Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", - SPVal, Stack[0], Stack[1], Stack[2], Stack[3], Stack[4], Stack[5], Stack[6], Stack[7]); - - Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", - SPVal+32, Stack[8], Stack[9], Stack[10], Stack[11], Stack[12], Stack[13], Stack[14], Stack[15]); - -} - - - -void myInvalidParameterHandler(const wchar_t* expression, - const wchar_t* function, - const wchar_t* file, - unsigned int line, - uintptr_t pReserved) -{ - Logprintf(LOG_DEBUG_X, NULL, '!', "*** Error **** C Run Time Invalid Parameter Handler Called"); - - if (expression && function && file) - { - Logprintf(LOG_DEBUG_X, NULL, '!', "Expression = %S", expression); - Logprintf(LOG_DEBUG_X, NULL, '!', "Function %S", function); - Logprintf(LOG_DEBUG_X, NULL, '!', "File %S Line %d", file, line); - } -} - -// If program gets too many program errors, it will restart itself and shut down - -VOID CheckProgramErrors() -{ - STARTUPINFO SInfo; // pointer to STARTUPINFO - PROCESS_INFORMATION PInfo; // pointer to PROCESS_INFORMATION - char ProgName[256]; - - 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"); - - if (cfgMinToTray) - { - DeleteTrayMenuItem(MainWnd); - if (ConsHeader[0]->hConsole) - DeleteTrayMenuItem(ConsHeader[0]->hConsole); - if (ConsHeader[1]->hConsole) - DeleteTrayMenuItem(ConsHeader[1]->hConsole); - if (hMonitor) - DeleteTrayMenuItem(hMonitor); - } - - 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); - } -} - - -VOID WriteMiniDump() -{ -#ifdef WIN32 - - HANDLE hFile; - BOOL ret; - char FN[256]; - - sprintf(FN, "%s/Logs/MiniDump%x.dmp", GetBPQDirectory(), time(NULL)); - - 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 -} - - -void GetSemaphore(struct SEM * Semaphore, int ID) -{ - // - // Wait for it to be free - // -#ifdef WIN32 - if (Semaphore->Flag != 0) - { - Semaphore->Clashes++; - } -loop1: - - while (Semaphore->Flag != 0) - { - Sleep(10); - } - - // - // try to get semaphore - // - - _asm{ - - mov eax,1 - mov ebx, Semaphore - xchg [ebx],eax // this instruction is locked - - cmp eax,0 - jne loop1 // someone else got it - try again -; -; ok, weve got the semaphore -; - } -#else - - while (Semaphore->Flag) - usleep(10000); - - Semaphore->Flag = 1; - -#endif - return; -} - -void FreeSemaphore(struct SEM * Semaphore) -{ - Semaphore->Flag = 0; - - return; -} - -char * CmdLine; - -extern int configSaved; - -int APIENTRY WinMain(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - LPTSTR lpCmdLine, - int nCmdShow) -{ - MSG msg; - HACCEL hAccelTable; - int BPQStream, n; - struct UserInfo * user; - struct _EXCEPTION_POINTERS exinfo; - _invalid_parameter_handler oldHandler, newHandler; - char Msg[100]; - int i = 60; - struct NNTPRec * NNTPREC; - struct NNTPRec * SaveNNTPREC; - - CmdLine = _strdup(lpCmdLine); - _strlwr(CmdLine); - - if (_stricmp(lpCmdLine, "Wait") == 0) // If AutoRestart then Delay 60 Secs - { - hWnd = CreateWindow("STATIC", "Mail Restarting after Failure - Please Wait", 0, - CW_USEDEFAULT, 100, 550, 70, - NULL, NULL, hInstance, NULL); - - ShowWindow(hWnd, nCmdShow); - - while (i-- > 0) - { - sprintf(Msg, "Mail Restarting after Failure - Please Wait %d secs.", i); - SetWindowText(hWnd, Msg); - - Sleep(1000); - } - - DestroyWindow(hWnd); - } - - __try { - - // Trap CRT Errors - - newHandler = myInvalidParameterHandler; - oldHandler = _set_invalid_parameter_handler(newHandler); - - // Initialize global strings - LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); - LoadString(hInstance, IDC_BPQMailChat, szWindowClass, MAX_LOADSTRING); - MyRegisterClass(hInstance); - - // Perform application initialization: - - if (!InitInstance (hInstance, nCmdShow)) - { - return FALSE; - } - - hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BPQMailChat)); - - // Main message loop: - - Logprintf(LOG_DEBUG_X, NULL, '!', "Program Starting"); - Logprintf(LOG_BBS, NULL, '!', "BPQMail Starting"); - Debugprintf("BPQMail Starting"); - - if (pDllBPQTRACE == 0) - Logprintf(LOG_BBS, NULL, '!', "Remote Monitor Log not available - update BPQ32.dll to enable"); - - - } My__except_Routine("Init"); - - while (GetMessage(&msg, NULL, 0, 0)) - { - __try - { - if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - #define EXCEPTMSG "GetMessageLoop" - #include "StdExcept.c" - - CheckProgramErrors(); - } - } - - __try - { - for (n = 0; n < NumberofStreams; n++) - { - BPQStream=Connections[n].BPQStream; - - if (BPQStream) - { - SetAppl(BPQStream, 0, 0); - Disconnect(BPQStream); - DeallocateStream(BPQStream); - } - } - - - hWnd = CreateWindow("STATIC", "Mail Closing - Please Wait", 0, - 150, 200, 350, 40, NULL, NULL, hInstance, NULL); - - ShowWindow(hWnd, nCmdShow); - - Sleep(1000); // A bit of time for links to close - - DestroyWindow(hWnd); - - if (ConsHeader[0]->hConsole) - DestroyWindow(ConsHeader[0]->hConsole); - if (ConsHeader[1]->hConsole) - DestroyWindow(ConsHeader[1]->hConsole); - if (hMonitor) - { - DestroyWindow(hMonitor); - hMonitor = (HWND)1; // For status Save - } - - -// SaveUserDatabase(); - SaveMessageDatabase(); - SaveBIDDatabase(); - - configSaved = 1; - SaveConfig(ConfigName); - - if (cfgMinToTray) - { - DeleteTrayMenuItem(MainWnd); - if (ConsHeader[0]->hConsole) - DeleteTrayMenuItem(ConsHeader[0]->hConsole); - if (ConsHeader[1]->hConsole) - DeleteTrayMenuItem(ConsHeader[1]->hConsole); - if (hMonitor) - DeleteTrayMenuItem(hMonitor); - } - - // Free all allocated memory - - for (n = 0; n <= NumberofUsers; n++) - { - user = UserRecPtr[n]; - - if (user->ForwardingInfo) - { - FreeForwardingStruct(user); - free(user->ForwardingInfo); - } - - free(user->Temp); - - free(user); - } - - free(UserRecPtr); - - for (n = 0; n <= NumberofMessages; n++) - free(MsgHddrPtr[n]); - - free(MsgHddrPtr); - - for (n = 0; n <= NumberofWPrecs; n++) - free(WPRecPtr[n]); - - free(WPRecPtr); - - for (n = 0; n <= NumberofBIDs; n++) - free(BIDRecPtr[n]); - - free(BIDRecPtr); - - if (TempBIDRecPtr) - free(TempBIDRecPtr); - - NNTPREC = FirstNNTPRec; - - while (NNTPREC) - { - SaveNNTPREC = NNTPREC->Next; - free(NNTPREC); - NNTPREC = SaveNNTPREC; - } - - if (BadWords) free(BadWords); - if (BadFile) free(BadFile); - - n = 0; - - if (Aliases) - { - while(Aliases[n]) - { - free(Aliases[n]->Dest); - free(Aliases[n]); - n++; - } - - free(Aliases); - FreeList(AliasText); - } - - n = 0; - - if (NTSAliases) - { - while(NTSAliases[n]) - { - free(NTSAliases[n]->Dest); - free(NTSAliases[n]); - n++; - } - - free(NTSAliases); - } - - FreeOverrides(); - - FreeList(RejFrom); - FreeList(RejTo); - FreeList(RejAt); - FreeList(RejBID); - FreeList(HoldFrom); - FreeList(HoldTo); - FreeList(HoldAt); - FreeList(HoldBID); - FreeList(SendWPAddrs); - - Free_UI(); - - for (n=1; n<20; n++) - { - if (MyElements[n]) free(MyElements[n]); - } - - free(WelcomeMsg); - free(NewWelcomeMsg); - free(ExpertWelcomeMsg); - - free(Prompt); - free(NewPrompt); - free(ExpertPrompt); - - FreeWebMailMallocs(); - - free(CmdLine); - - _CrtDumpMemoryLeaks(); - - } - My__except_Routine("Close Processing"); - - CloseBPQ32(); // Close Ext Drivers if last bpq32 process - - return (int) msg.wParam; -} - - - -// -// FUNCTION: MyRegisterClass() -// -// PURPOSE: Registers the window class. -// -// COMMENTS: -// -// This function and its usage are only necessary if you want this code -// to be compatible with Win32 systems prior to the 'RegisterClassEx' -// function that was added to Windows 95. It is important to call this function -// so that the application will get 'well formed' small icons associated -// with it. -// -// -#define BGCOLOUR RGB(236,233,216) -//#define BGCOLOUR RGB(245,245,245) - -HBRUSH bgBrush; - -ATOM MyRegisterClass(HINSTANCE hInstance) -{ - WNDCLASSEX wcex; - - bgBrush = CreateSolidBrush(BGCOLOUR); - - wcex.cbSize = sizeof(WNDCLASSEX); - - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.lpfnWndProc = WndProc; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = DLGWINDOWEXTRA; - wcex.hInstance = hInstance; - wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(BPQICON)); - wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = bgBrush; - wcex.lpszMenuName = MAKEINTRESOURCE(IDC_BPQMailChat); - wcex.lpszClassName = szWindowClass; - wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(BPQICON)); - - return RegisterClassEx(&wcex); -} - - -// -// FUNCTION: InitInstance(HINSTANCE, int) -// -// PURPOSE: Saves instance handle and creates main window -// -// COMMENTS: -// -// In this function, we save the instance handle in a global variable and -// create and display the main program window. -// - -HWND hWnd; - -int AXIPPort = 0; - -char LOC[7] = ""; - -BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) -{ - char Title[80]; - WSADATA WsaData; - HMENU hTopMenu; // handle of menu - HKEY hKey=0; - int retCode; - RECT InitRect; - RECT SessRect; - struct _EXCEPTION_POINTERS exinfo; - - HMODULE ExtDriver = LoadLibrary("bpq32.dll"); - - if (ExtDriver) - { - pDllBPQTRACE = GetProcAddress(ExtDriver,"_DllBPQTRACE@8"); - pGetLOC = GetProcAddress(ExtDriver,"_GetLOC@0"); - pRefreshWebMailIndex = GetProcAddress(ExtDriver,"_RefreshWebMailIndex@0"); - pRunEventProgram = GetProcAddress(ExtDriver,"_RunEventProgram@8"); - - if (pGetLOC) - { - char * pLOC = (char *)pGetLOC(); - memcpy(LOC, pLOC, 6); - } - } - - // See if running under WINE - - retCode = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "SOFTWARE\\Wine", 0, KEY_QUERY_VALUE, &hKey); - - if (retCode == ERROR_SUCCESS) - { - RegCloseKey(hKey); - WINE =TRUE; - Debugprintf("Running under WINE"); - } - - - REGTREE = GetRegistryKey(); - REGTREETEXT = GetRegistryKeyText(); - - Sleep(1000); - - { - int n; - struct _EXTPORTDATA * PORTVEC; - - KISSOnly = TRUE; - - for (n=1; n <= GetNumberofPorts(); n++) - { - PORTVEC = (struct _EXTPORTDATA * )GetPortTableEntryFromSlot(n); - - if (PORTVEC->PORTCONTROL.PORTTYPE == 16) // EXTERNAL - { - if (_memicmp(PORTVEC->PORT_DLL_NAME, "TELNET", 6) == 0) - KISSOnly = FALSE; - - if (PORTVEC->PORTCONTROL.PROTOCOL != 10) // Pactor/WINMOR - KISSOnly = FALSE; - - if (AXIPPort == 0) - { - if (_memicmp(PORTVEC->PORT_DLL_NAME, "BPQAXIP", 7) == 0) - { - AXIPPort = PORTVEC->PORTCONTROL.PORTNUMBER; - KISSOnly = FALSE; - } - } - } - } - } - - hInst = hInstance; - - hWnd=CreateDialog(hInst,szWindowClass,0,NULL); - - if (!hWnd) - { - return FALSE; - } - - MainWnd = hWnd; - - GetVersionInfo(NULL); - - sprintf(Title,"G8BPQ Mail Server Version %s", VersionString); - - sprintf(RlineVer, "BPQ%s%d.%d.%d", (KISSOnly) ? "K" : "", Ver[0], Ver[1], Ver[2]); - - SetWindowText(hWnd,Title); - - hWndSess = GetDlgItem(hWnd, 100); - - GetWindowRect(hWnd, &InitRect); - GetWindowRect(hWndSess, &SessRect); - - SessX = SessRect.left - InitRect.left ; - SessY = SessRect.top -InitRect.top; - SessWidth = SessRect.right - SessRect.left; - - // Get handles for updating menu items - - hTopMenu=GetMenu(MainWnd); - hActionMenu=GetSubMenu(hTopMenu,0); - - hFWDMenu=GetSubMenu(hActionMenu,0); - hMenu=GetSubMenu(hActionMenu,1); - hDisMenu=GetSubMenu(hActionMenu,2); - - CheckTimer(); - - cfgMinToTray = GetMinimizetoTrayFlag(); - - if ((nCmdShow == SW_SHOWMINIMIZED) || (nCmdShow == SW_SHOWMINNOACTIVE)) - if (cfgMinToTray) - { - ShowWindow(hWnd, SW_HIDE); - } - else - { - ShowWindow(hWnd, nCmdShow); - } - else - ShowWindow(hWnd, nCmdShow); - - UpdateWindow(hWnd); - - WSAStartup(MAKEWORD(2, 0), &WsaData); - - __try { - - return Initialise(); - - }My__except_Routine("Initialise"); - - return FALSE; -} - -// -// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) -// -// PURPOSE: Processes messages for the main window. -// -// - - -LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - int wmId, wmEvent; - PAINTSTRUCT ps; - HDC hdc; - int state,change; - ConnectionInfo * conn; - struct _EXCEPTION_POINTERS exinfo; - - - if (message == BPQMsg) - { - if (lParam & BPQMonitorAvail) - { - __try - { - DoBBSMonitorData(wParam); - } - My__except_Routine("DoMonitorData"); - - return 0; - - } - if (lParam & BPQDataAvail) - { - // Dont trap error at this level - let Node error handler pick it up -// __try -// { - DoReceivedData(wParam); -// } -// My__except_Routine("DoReceivedData") - return 0; - } - if (lParam & BPQStateChange) - { - // Get current Session State. Any state changed is ACK'ed - // automatically. See BPQHOST functions 4 and 5. - - __try - { - SessionState(wParam, &state, &change); - - if (change == 1) - { - if (state == 1) // Connected - { - GetSemaphore(&ConSemaphore, 0); - __try {Connected(wParam);} - My__except_Routine("Connected"); - FreeSemaphore(&ConSemaphore); - } - else - { - GetSemaphore(&ConSemaphore, 0); - __try{Disconnected(wParam);} - My__except_Routine("Disconnected"); - FreeSemaphore(&ConSemaphore); - } - } - } - My__except_Routine("DoStateChange"); - - } - - return 0; - } - - - switch (message) - { - - case WM_KEYUP: - - switch (wParam) - { - case VK_F2: - CreateConsole(-1); - return 0; - - case VK_F3: - CreateMulticastConsole(); - return 0; - - case VK_F4: - CreateMonitor(); - return 0; - - case VK_TAB: - return TRUE; - - break; - - - - } - return 0; - - case WM_TIMER: - - if (wParam == 1) // Slow = 10 secs - { - __try - { - time_t NOW = time(NULL); - struct tm * tm; - RefreshMainWindow(); - CheckTimer(); - TCPTimer(); - BBSSlowTimer(); - FWDTimerProc(); - if (MaintClock < NOW) - { - while (MaintClock < NOW) // in case large time step - MaintClock += MaintInterval * 3600; - - Debugprintf("|Enter HouseKeeping"); - DoHouseKeeping(FALSE); - } - tm = gmtime(&NOW); - - if (tm->tm_wday == 0) // Sunday - { - if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) - { - CreateBBSTrafficReport(); - LastTrafficTime = NOW; - } - } - } - My__except_Routine("Slow Timer"); - } - else - __try - { - TrytoSend(); - TCPFastTimer(); - } - My__except_Routine("TrytoSend"); - - return (0); - - - 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_INITMENUPOPUP: - - if (wParam == (WPARAM)hActionMenu) - { - if (IsClipboardFormatAvailable(CF_TEXT)) - EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_ENABLED); - else - EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_GRAYED ); - - return TRUE; - } - - if (wParam == (WPARAM)hFWDMenu) - { - // Set up Forward Menu - - struct UserInfo * user; - char MenuLine[30]; - - for (user = BBSChain; user; user = user->BBSNext) - { - sprintf(MenuLine, "%s %d Msgs", user->Call, CountMessagestoForward(user)); - - if (ModifyMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, - MF_BYCOMMAND | MF_STRING, IDM_FORWARD_ALL + user->BBSNumber, MenuLine) == 0) - - AppendMenu(hFWDMenu, MF_STRING,IDM_FORWARD_ALL + user->BBSNumber, MenuLine); - } - return TRUE; - } - - if (wParam == (WPARAM)hDisMenu) - { - // Set up Disconnect Menu - - CIRCUIT * conn; - char MenuLine[30]; - int n; - - for (n = 0; n <= NumberofStreams-1; n++) - { - conn=&Connections[n]; - - RemoveMenu(hDisMenu, IDM_DISCONNECT + n, MF_BYCOMMAND); - - if (conn->Active) - { - sprintf_s(MenuLine, 30, "%d %s", conn->BPQStream, conn->Callsign); - AppendMenu(hDisMenu, MF_STRING, IDM_DISCONNECT + n, MenuLine); - } - } - return TRUE; - } - break; - - - case WM_COMMAND: - wmId = LOWORD(wParam); - wmEvent = HIWORD(wParam); - // Parse the menu selections: - - if (wmEvent == LBN_DBLCLK) - - break; - - if (wmId >= IDM_DISCONNECT && wmId < IDM_DISCONNECT+MaxSockets+1) - { - // disconnect user - - conn=&Connections[wmId-IDM_DISCONNECT]; - - if (conn->Active) - { - Disconnect(conn->BPQStream); - } - } - - if (wmId >= IDM_FORWARD_ALL && wmId < IDM_FORWARD_ALL + 100) - { - StartForwarding(wmId - IDM_FORWARD_ALL, NULL); - return 0; - } - - switch (wmId) - { - case IDM_LOGBBS: - - ToggleParam(hMenu, hWnd, &LogBBS, IDM_LOGBBS); - break; - - case IDM_LOGCHAT: - - ToggleParam(hMenu, hWnd, &LogCHAT, IDM_LOGCHAT); - break; - - case IDM_LOGTCP: - - ToggleParam(hMenu, hWnd, &LogTCP, IDM_LOGTCP); - break; - - case IDM_HOUSEKEEPING: - - DoHouseKeeping(TRUE); - - break; - - case IDM_CONSOLE: - - CreateConsole(-1); - break; - - case IDM_MCMONITOR: - - CreateMulticastConsole(); - break; - - case IDM_MONITOR: - - CreateMonitor(); - break; - - case RESCANMSGS: - - ReRouteMessages(); - break; - - case IDM_IMPORT: - - ImportMessages(NULL, "", FALSE); - break; - - case IDM_ABOUT: - DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); - break; - - case ID_HELP_ONLINEHELP: - - ShellExecute(hWnd,"open", - "http://www.cantab.net/users/john.wiseman/Documents/MailServer.html", - "", NULL, SW_SHOWNORMAL); - - break; - - case IDM_CONFIG: - DialogBox(hInst, MAKEINTRESOURCE(IDD_CONFIG), hWnd, ConfigWndProc); - break; - - case IDM_USERS: - DialogBox(hInst, MAKEINTRESOURCE(IDD_USEREDIT), hWnd, UserEditDialogProc); - break; - - case IDM_FWD: - DialogBox(hInst, MAKEINTRESOURCE(IDD_FORWARDING), hWnd, FwdEditDialogProc); - break; - - case IDM_MESSAGES: - DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGEDIT), hWnd, MsgEditDialogProc); - break; - - case IDM_WP: - DialogBox(hInst, MAKEINTRESOURCE(IDD_EDITWP), hWnd, WPEditDialogProc); - break; - - case ID_ACTIONS_SENDMSGFROMCLIPBOARD: - DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, ClpMsgDialogProc); - break; - - case ID_ACTIONS_SENDMESSAGE: - DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, SendMsgDialogProc); - break; - - case ID_MULTICAST: - - MulticastRX = !MulticastRX; - CheckMenuItem(hActionMenu, ID_MULTICAST, (MulticastRX) ? MF_CHECKED : MF_UNCHECKED); - break; - - case IDM_EXIT: - DestroyWindow(hWnd); - break; - - - - default: - return DefWindowProc(hWnd, message, wParam, lParam); - } - break; - - case WM_SIZE: - - if (wParam == SIZE_MINIMIZED) - if (cfgMinToTray) - return ShowWindow(hWnd, SW_HIDE); - - return (0); - - - case WM_SIZING: - { - LPRECT lprc = (LPRECT) lParam; - int Height = lprc->bottom-lprc->top; - int Width = lprc->right-lprc->left; - - MoveWindow(hWndSess, 0, 30, SessWidth, Height - 100, TRUE); - - return TRUE; - } - - - case WM_PAINT: - hdc = BeginPaint(hWnd, &ps); - // TODO: Add any drawing code here... - EndPaint(hWnd, &ps); - break; - - case WM_DESTROY: - - GetWindowRect(MainWnd, &MainRect); // For save soutine - if (ConsHeader[0]->hConsole) - GetWindowRect(ConsHeader[0]->hConsole, &ConsHeader[0]->ConsoleRect); // For save soutine - if (ConsHeader[1]->hConsole) - GetWindowRect(ConsHeader[1]->hConsole, &ConsHeader[1]->ConsoleRect); // For save soutine - if (hMonitor) - GetWindowRect(hMonitor, &MonitorRect); // For save soutine - - KillTimer(hWnd,1); - KillTimer(hWnd,2); - PostQuitMessage(0); - break; - - default: - return DefWindowProc(hWnd, message, wParam, lParam); - } - return 0; -} - -INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch (message) - { - case WM_INITDIALOG: - - SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "B"); - SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "P"); - SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "T"); - - SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_SETCURSEL, 0, 0); - - return TRUE; - - case WM_SIZING: - { - HWND hWndEdit = GetDlgItem(hDlg, IDC_EDIT1); - - LPRECT lprc = (LPRECT) lParam; - int Height = lprc->bottom-lprc->top; - int Width = lprc->right-lprc->left; - - MoveWindow(hWndEdit, 5, 90, Width-20, Height - 140, TRUE); - - return TRUE; - } - - case WM_COMMAND: - - if (LOWORD(wParam) == IDSEND) - { - char status [3]; - struct MsgInfo * Msg; - char * via = NULL; - char BID[13]; - char FileList[32768]; - BIDRec * BIDRec; - int MsgLen; - char * MailBuffer; - char MsgFile[MAX_PATH]; - HANDLE hFile = INVALID_HANDLE_VALUE; - int WriteLen=0; - char HDest[61]; - char Destcopy[61]; - char * Vptr; - char * FileName[100]; - int FileLen[100]; - char * FileBody[100]; - int n, Files = 0; - int TotalFileSize = 0; - char * NewMsg; - - GetDlgItemText(hDlg, IDC_MSGTO, HDest, 60); - strcpy(Destcopy, HDest); - - GetDlgItemText(hDlg, IDC_MSGBID, BID, 13); - strlop(BID, ' '); - - GetDlgItemText(hDlg, IDC_ATTACHMENTS, FileList, 32767); - - // if there are attachments, check that they can be opened ane read - - n = 0; - - if (FileList[0]) - { - FILE * Handle; - struct stat STAT; - char * ptr1 = FileList, * ptr2; - - while(ptr1 && ptr1[0]) - { - ptr2 = strchr(ptr1, ';'); - - if (ptr2) - *(ptr2++) = 0; - - FileName[n++] = ptr1; - - ptr1 = ptr2; - } - - FileName[n] = 0; - - // read the files - - Files = n; - n = 0; - - while (FileName[n]) - { - if (stat(FileName[n], &STAT) == -1) - { - char ErrorMessage[512]; - sprintf(ErrorMessage,"Can't find file %s", FileName[n]); - MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); - return TRUE; - } - - FileLen[n] = STAT.st_size; - - Handle = fopen(FileName[n], "rb"); - - if (Handle == NULL) - { - char ErrorMessage[512]; - sprintf(ErrorMessage,"Can't open file %s", FileName[n]); - MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); - return TRUE; - } - - FileBody[n] = malloc(FileLen[n]+1); - - fread(FileBody[n], 1, FileLen[n], Handle); - - fclose(Handle); - - TotalFileSize += FileLen[n]; - n++; - } - } - - if (strlen(HDest) == 0) - { - MessageBox(NULL, "To: Call Missing!", "BPQMail", MB_ICONERROR); - return TRUE; - } - - if (strlen(BID)) - { - if (LookupBID(BID)) - { - // Duplicate bid - - MessageBox(NULL, "Duplicate BID", "BPQMail", MB_ICONERROR); - return TRUE; - } - } - - Msg = AllocateMsgRecord(); - - // Set number here so they remain in sequence - - Msg->number = ++LatestMsg; - MsgnotoMsg[Msg->number] = Msg; - - strcpy(Msg->from, SYSOPCall); - - Vptr = strlop(Destcopy, '@'); - - if (Vptr == 0 && strchr(Destcopy, '!')) // Bang route without @ - { - Vptr = strchr(Destcopy, '!'); - strcpy(Msg->via, Vptr); - strlop(Destcopy, '!'); - - if (strlen(Destcopy) > 6) - memcpy(Msg->to, Destcopy, 6); - else - strcpy(Msg->to, Destcopy); - goto gotAddr; - } - - if (strlen(Destcopy) > 6) - memcpy(Msg->to, Destcopy, 6); - else - strcpy(Msg->to, Destcopy); - - _strupr(Msg->to); - - if (_memicmp(HDest, "rms:", 4) == 0 || _memicmp(HDest, "rms/", 4) == 0) - { - Vptr = HDest; - memmove(HDest, &HDest[4], strlen(HDest)); - strcpy(Msg->to, "RMS"); - - } - else if (_memicmp(HDest, "smtp:", 5) == 0) - { - if (ISP_Gateway_Enabled) - { - Vptr = HDest; - memmove(HDest, &HDest[5], strlen(HDest)); - Msg->to[0] = 0; - } - } - else if (Vptr) - { - // If looks like a valid email address, treat as such - - int tolen = (Vptr - Destcopy) - 1; - - if (tolen > 6 || !CheckifPacket(Vptr)) - { - // Assume Email address - - Vptr = HDest; - - if (FindRMS() || strchr(Vptr, '!')) // have RMS or source route - strcpy(Msg->to, "RMS"); - else if (ISP_Gateway_Enabled) - Msg->to[0] = 0; - else - { - MessageBox(NULL, "Sending to Internet Email not available", "BPQMail", MB_ICONERROR); - return TRUE; - } - } - } - if (Vptr) - { - if (strlen(Vptr) > 40) - Vptr[40] = 0; - - strcpy(Msg->via, Vptr); - } -gotAddr: - GetDlgItemText(hDlg, IDC_MSGTITLE, Msg->title, 61); - GetDlgItemText(hDlg, IDC_MSGTYPE, status, 2); - Msg->type = status[0]; - Msg->status = 'N'; - - if (strlen(BID) == 0) - sprintf_s(BID, sizeof(BID), "%d_%s", LatestMsg, BBSName); - - strcpy(Msg->bid, BID); - - Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); - - BIDRec = AllocateBIDRecord(); - - strcpy(BIDRec->BID, Msg->bid); - BIDRec->mode = Msg->type; - BIDRec->u.msgno = LOWORD(Msg->number); - BIDRec->u.timestamp = LOWORD(time(NULL)/86400); - - MsgLen = SendDlgItemMessage(hDlg, IDC_EDIT1, WM_GETTEXTLENGTH, 0 ,0); - - MailBuffer = malloc(MsgLen + TotalFileSize + 2000); // Allow for a B2 Header if attachments - - if (Files) - { - char DateString[80]; - struct tm * tm; - - char Type[16] = "Private"; - - // Get Type - - if (Msg->type == 'B') - strcpy(Type, "Bulletin"); - else if (Msg->type == 'T') - strcpy(Type, "Traffic"); - - // Create a B2 Message - - // B2 Header - - NewMsg = MailBuffer + 1000; - - tm = gmtime((time_t *)&Msg->datecreated); - - sprintf(DateString, "%04d/%02d/%02d %02d:%02d", - tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); - - // Remove last Source Route - - if (strchr(HDest, '!')) - { - char * bang = HDest + strlen(HDest); - - while (*(--bang) != '!'); // Find last ! - - *(bang) = 0; // remove it; - } - - NewMsg += sprintf(NewMsg, - "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\nTo: %s\r\nSubject: %s\r\nMbo: %s\r\n", - Msg->bid, DateString, Type, Msg->from, HDest, Msg->title, BBSName); - - - NewMsg += sprintf(NewMsg, "Body: %d\r\n", MsgLen); - - for (n = 0; n < Files; n++) - { - char * p = FileName[n], * q; - - // Remove any path - - q = strchr(p, '\\'); - - while (q) - { - if (q) - *q++ = 0; - p = q; - q = strchr(p, '\\'); - } - - NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[n], p); - } - - NewMsg += sprintf(NewMsg, "\r\n"); - GetDlgItemText(hDlg, IDC_EDIT1, NewMsg, MsgLen+1); - NewMsg += MsgLen; - NewMsg += sprintf(NewMsg, "\r\n"); - - for (n = 0; n < Files; n++) - { - memcpy(NewMsg, FileBody[n], FileLen[n]); - NewMsg += FileLen[n]; - free(FileBody[n]); - NewMsg += sprintf(NewMsg, "\r\n"); - } - - Msg->length = NewMsg - (MailBuffer + 1000); - NewMsg = MailBuffer + 1000; - Msg->B2Flags = B2Msg | Attachments; - } - - else - { - GetDlgItemText(hDlg, IDC_EDIT1, MailBuffer, MsgLen+1); - Msg->length = MsgLen; - NewMsg = MailBuffer; - } - - sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); - - hFile = CreateFile(MsgFile, - GENERIC_WRITE, - FILE_SHARE_READ, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (hFile != INVALID_HANDLE_VALUE) - { - WriteFile(hFile, NewMsg, Msg->length, &WriteLen, NULL); - CloseHandle(hFile); - } - - free(MailBuffer); - - MatchMessagetoBBSList(Msg, 0); - - BuildNNTPList(Msg); // Build NNTP Groups list - - SaveMessageDatabase(); - SaveBIDDatabase(); - - EndDialog(hDlg, LOWORD(wParam)); - - return TRUE; - } - - - if (LOWORD(wParam) == IDSelectFiles) - { - char FileNames[2048]; - char FullFileNames[32768]; - OPENFILENAME Ofn; - int err; - - FileNames[0] = 0; - - memset(&Ofn, 0, sizeof(Ofn)); - - Ofn.lStructSize = sizeof(OPENFILENAME); - Ofn.hInstance = hInst; - Ofn.hwndOwner = hDlg; - Ofn.lpstrFilter = NULL; - Ofn.lpstrFile= FileNames; - Ofn.nMaxFile = 2048; - Ofn.lpstrFileTitle = NULL; - Ofn.nMaxFileTitle = 0; - Ofn.lpstrInitialDir = (LPSTR)NULL; - Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; - Ofn.lpstrTitle = NULL;//; - - if (GetOpenFileName(&Ofn)) - { - // if one is selected, a single string is returned, if more than one, a single - // path, followed by all the strings, duuble null terminated. - - char * Names[101]; // Allow up to 100 names - int n = 0; - char * ptr = FileNames; - - while (*ptr) - { - Names[n++] = ptr; - ptr += strlen(ptr); - ptr++; - } - - GetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames, 32768); - - if (strlen(FullFileNames)) - strcat(FullFileNames, ";"); - - if (n == 1) - { - // Single Select - - strcat(FullFileNames, FileNames); - } - else - { - int i = 1; - - while(i < n) - { - strcat(FullFileNames, Names[0]); - strcat(FullFileNames, "\\"); - strcat(FullFileNames, Names[i]); - i++; - if (i < n) - strcat(FullFileNames, ";"); - } - } - SetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames); - } - else - err = GetLastError(); - return (INT_PTR)TRUE; - } - - - if (LOWORD(wParam) == IDCANCEL) - { - EndDialog(hDlg, LOWORD(wParam)); - return (INT_PTR)TRUE; - } - - return (INT_PTR)TRUE; - - break; - } - return (INT_PTR)FALSE; -} - -INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - HGLOBAL hglb; - LPTSTR lptstr; - - switch (message) - { - case WM_INITDIALOG: - - SetWindowText(hDlg, "Send Message from Clipboard"); - - if (!IsClipboardFormatAvailable(CF_TEXT)) - break; - - if (!OpenClipboard(hDlg)) - break; - - hglb = GetClipboardData(CF_TEXT); - - if (hglb != NULL) - { - lptstr = GlobalLock(hglb); - - if (lptstr != NULL) - { - SetDlgItemText(hDlg, IDC_EDIT1, lptstr); - GlobalUnlock(hglb); - } - } - CloseClipboard(); - } - - return SendMsgDialogProc(hDlg, message, wParam, lParam); - -} - -// Message handler for about box. -INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - UNREFERENCED_PARAMETER(lParam); - switch (message) - { - case WM_INITDIALOG: - return (INT_PTR)TRUE; - - case WM_COMMAND: - if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) - { - EndDialog(hDlg, LOWORD(wParam)); - return (INT_PTR)TRUE; - } - return (INT_PTR)TRUE; - - break; - } - return (INT_PTR)FALSE; -} - -SMTPMsgs = 0; - -int RefreshMainWindow() -{ - char msg[80]; - CIRCUIT * conn; - int i,n, SYSOPMsgs = 0, HeldMsgs = 0; - time_t now; - struct tm * tm; - char tim[20]; - - SendDlgItemMessage(MainWnd,100,LB_RESETCONTENT,0,0); - - SMTPMsgs = 0; - - for (n = 0; n < NumberofStreams; n++) - { - conn=&Connections[n]; - - if (!conn->Active) - { - strcpy(msg,"Idle"); - } - else - { - { - if (conn->UserPointer == 0) - strcpy(msg,"Logging in"); - else - { - i=sprintf_s(msg, sizeof(msg), "%-10s %-10s %2d %-10s%5d", - conn->UserPointer->Name, conn->UserPointer->Call, conn->BPQStream, - "BBS", conn->OutputQueueLength - conn->OutputGetPointer); - } - } - } - SendDlgItemMessage(MainWnd,100,LB_ADDSTRING,0,(LPARAM)msg); - } - - SetDlgItemInt(hWnd, IDC_MSGS, NumberofMessages, FALSE); - - n = 0; - - for (i=1; i <= NumberofMessages; i++) - { - if (MsgHddrPtr[i]->status == 'N') - { - if (_stricmp(MsgHddrPtr[i]->to, SYSOPCall) == 0 || _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0) - SYSOPMsgs++; - else - if (MsgHddrPtr[i]->to[0] == 0) - SMTPMsgs++; - } - else - { - if (MsgHddrPtr[i]->status == 'H') - HeldMsgs++; - } - } - - SetDlgItemInt(hWnd, IDC_SYSOPMSGS, SYSOPMsgs, FALSE); - SetDlgItemInt(hWnd, IDC_HELD, HeldMsgs, FALSE); - SetDlgItemInt(hWnd, IDC_SMTP, SMTPMsgs, FALSE); - - SetDlgItemInt(hWnd, IDC_MSGSEM, MsgNoSemaphore.Clashes, FALSE); - SetDlgItemInt(hWnd, IDC_ALLOCSEM, AllocSemaphore.Clashes, FALSE); - SetDlgItemInt(hWnd, IDC_CONSEM, ConSemaphore.Clashes, FALSE); - - now = time(NULL); - - tm = gmtime(&now); - sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); - SetDlgItemText(hWnd, IDC_UTC, tim); - - tm = localtime(&now); - sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); - SetDlgItemText(hWnd, IDC_LOCAL, tim); - - - return 0; -} - -#define MAX_PENDING_CONNECTS 4 - -#define VERSION_MAJOR 2 -#define VERSION_MINOR 0 - -SOCKADDR_IN local_sin; /* Local socket - internet style */ - -PSOCKADDR_IN psin; - -SOCKET sock; - - - -BOOL Initialise() -{ - int i, len; - ConnectionInfo * conn; - struct UserInfo * user = NULL; - HKEY hKey=0; - char * ptr1; - int Attrs, ret; - char msg[500]; - TIME_ZONE_INFORMATION TimeZoneInformation; - struct stat STAT; - - GetTimeZoneInformation(&TimeZoneInformation); - - _tzset(); - _MYTIMEZONE = timezone; - _MYTIMEZONE = TimeZoneInformation.Bias * 60; - - // Register message for posting by BPQDLL - - BPQMsg = RegisterWindowMessage(BPQWinMsg); - - // See if we need to warn of possible problem with BaseDir moved by installer - - strcpy(BPQDirectory, GetBPQDirectory()); - - sprintf(BaseDir, "%s/BPQMailChat", BPQDirectory); - - len = strlen(BaseDir); - ptr1 = BaseDir; - - while (*ptr1) - { - if (*(ptr1) == '/') *(ptr1) = '\\'; - ptr1++; - } - - // Make Sure BASEDIR Exists - - Attrs = GetFileAttributes(BaseDir); - - if (Attrs == -1) - { - sprintf_s(msg, sizeof(msg), "Base Directory %s not found - should it be created?", BaseDir); - ret = MessageBox(NULL, msg, "BPQMail", MB_YESNO); - - if (ret == IDYES) - { - ret = CreateDirectory(BaseDir, NULL); - if (ret == 0) - { - MessageBox(NULL, "Failed to created Base Directory - exiting", "BPQMail", MB_ICONSTOP); - return FALSE; - } - } - else - { - MessageBox(NULL, "Can't Continue without a Base Directory - exiting", "BPQMailChat", MB_ICONSTOP); - return FALSE; - } - } - else - { - if (!(Attrs & FILE_ATTRIBUTE_DIRECTORY)) - { - sprintf_s(msg, sizeof(msg), "Base Directory %s is a file not a directory - exiting", BaseDir); - ret = MessageBox(NULL, msg, "BPQMail", MB_ICONSTOP); - - return FALSE; - } - } - - initUTF8(); - - // 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"); - - CreateDirectory(MailDir, NULL); // Just in case - - strcpy(ConfigName, BaseDir); - strcat(ConfigName, "\\"); - strcat(ConfigName, "BPQMail.cfg"); - - UsingingRegConfig = FALSE; - - // if config file exists use it else try to get from Registry - - if (stat(ConfigName, &STAT) == -1) - { - UsingingRegConfig = TRUE; - - if (GetConfigFromRegistry()) - { - SaveConfig(ConfigName); - } - else - { - int retCode; - - strcpy(BBSName, GetNodeCall()); - strlop(BBSName, '-'); - strlop(BBSName, ' '); - - sprintf(msg, "No configuration found - Dummy Config created"); - - retCode = MessageBox(NULL, msg, "BPQMailChat", MB_OKCANCEL); - - if (retCode == IDCANCEL) - return FALSE; - - SaveConfig(ConfigName); - } - } - - if (GetConfig(ConfigName) == EXIT_FAILURE) - { - ret = MessageBox(NULL, - "BBS Config File seems corrupt - check before continuing", "BPQMail", MB_ICONSTOP); - return FALSE; - } - - // Got a Config File - - if (MainRect.right < 100 || MainRect.bottom < 100) - { - GetWindowRect(MainWnd, &MainRect); - } - - MoveWindow(MainWnd, MainRect.left, MainRect.top, MainRect.right-MainRect.left, MainRect.bottom-MainRect.top, TRUE); - - if (OpenMon) - CreateMonitor(); - - BBSApplMask = 1<<(BBSApplNum-1); - - ShowWindow(GetDlgItem(MainWnd, 901), SW_HIDE); - ShowWindow(GetDlgItem(MainWnd, 902), SW_HIDE); - ShowWindow(GetDlgItem(MainWnd, 903), SW_HIDE); - - // Make backup copies of Databases - - CopyBIDDatabase(); - CopyMessageDatabase(); - CopyUserDatabase(); - CopyWPDatabase(); - - SetupMyHA(); - SetupFwdAliases(); - SetupNTSAliases(NTSAliasesPath); - - GetWPDatabase(); - GetMessageDatabase(); - GetUserDatabase(); - GetBIDDatabase(); - GetBadWordFile(); - GetHTMLForms(); - - UsingingRegConfig = FALSE; - - // Make sure SYSOPCALL is set - - if (SYSOPCall[0] == 0) - strcpy(SYSOPCall, BBSName); - - // 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(); - } - - // Allocate Streams - - 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 | ChatApplMask); - Disconnect(conn->BPQStream); - } - - InitialiseTCP(); - - InitialiseNNTP(); - - SetupListenSet(); // Master set of listening sockets - - if (BBSApplNum) - { - SetupUIInterface(); - if (MailForInterval) - _beginthread(SendMailForThread, 0, 0); - } - - if (cfgMinToTray) - { - AddTrayMenuItem(MainWnd, "Mail Server"); - } - - SetTimer(hWnd,1,10000,NULL); // Slow Timer (10 Secs) - SetTimer(hWnd,2,100,NULL); // Send to Node and TCP Poll (100 ms) - - // 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 = _mkgmtime(tm); - - 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); - } - } - } - - if (strstr(CmdLine, "tidymail")) - DeleteRedundantMessages(); - - if (strstr(CmdLine, "nohomebbs")) - DontNeedHomeBBS = TRUE; - - if (strstr(CmdLine, "DontCheckFromCall")) - DontCheckFromCall = TRUE; - - CheckMenuItem(hMenu,IDM_LOGBBS, (LogBBS) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(hMenu,IDM_LOGTCP, (LogTCP) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(hMenu,IDM_LOGCHAT, (LogCHAT) ? MF_CHECKED : MF_UNCHECKED); - - RefreshMainWindow(); - -// CreateWPReport(); - - CreatePipeThread(); - - return TRUE; -} - -int ConnectState(Stream) -{ - int state; - - SessionStateNoAck(Stream, &state); - return state; -} -UCHAR * EncodeCall(UCHAR * Call) -{ - static char axcall[10]; - - ConvToAX25(Call, axcall); - return &axcall[0]; - -} - -/* -VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo) -{ - struct UserInfo * user; - - int i = FWDInfo->UserIndex; - - if (i == -1) - { - FWDInfo->UserIndex = FWDInfo->UserCall[0] = 0; // Not scanning users - } - - for (i++; i <= NumberofUsers; i++) - { - user = UserRecPtr[i]; - - if (user->flags & F_POLLRMS) - { - FWDInfo->UserIndex = i; - strcpy(FWDInfo->UserCall, user->Call); - FWDInfo->FwdTimer = FWDInfo->FwdInterval - 20; - return ; - } - } - - // Finished Scan - - FWDInfo->UserIndex = FWDInfo->FwdTimer = FWDInfo->UserCall[0] = 0; -} -*/ - -#ifndef NEWROUTING - -VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo) -{ -} -VOID SetupMyHA() -{ -} -VOID SetupFwdAliases() -{ -} - -int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn) -{ - struct UserInfo * bbs; - struct BBSForwardingInfo * ForwardingInfo; - char ATBBS[41]; - char * HRoute; - int Count =0; - - strcpy(ATBBS, Msg->via); - HRoute = strlop(ATBBS, '.'); - - if (Msg->type == 'P') - { - // P messages are only sent to one BBS, but check the TO and AT of all BBSs before routing on HA - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - ForwardingInfo = bbs->ForwardingInfo; - - if (CheckBBSToList(Msg, bbs, ForwardingInfo)) - { - if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! - { - if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back - { - set_fwd_bit(Msg->fbbs, bbs->BBSNumber); - ForwardingInfo->MsgCount++; - } - } - return 1; - } - } - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - ForwardingInfo = bbs->ForwardingInfo; - - if (CheckBBSAtList(Msg, ForwardingInfo, ATBBS)) - { - if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! - { - if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back - { - set_fwd_bit(Msg->fbbs, bbs->BBSNumber); - ForwardingInfo->MsgCount++; - } - } - return 1; - } - } - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - ForwardingInfo = bbs->ForwardingInfo; - - if (CheckBBSHList(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) - { - if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! - { - if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back - { - set_fwd_bit(Msg->fbbs, bbs->BBSNumber); - ForwardingInfo->MsgCount++; - } - } - return 1; - } - } - - return FALSE; - } - - // Bulls go to all matching BBSs, so the order of checking doesn't matter - - for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) - { - ForwardingInfo = bbs->ForwardingInfo; - - if (CheckABBS(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) - { - if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! - { - if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back - { - set_fwd_bit(Msg->fbbs, bbs->BBSNumber); - ForwardingInfo->MsgCount++; - } - } - Count++; - } - } - - return Count; -} -BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) -{ - char ** Calls; - char ** HRoutes; - int i, j; - - if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS - return TRUE; - - // Check TO distributions - - if (ForwardingInfo->TOCalls) - { - Calls = ForwardingInfo->TOCalls; - - while(Calls[0]) - { - if (strcmp(Calls[0], Msg->to) == 0) - return TRUE; - - Calls++; - } - } - - // Check AT distributions - - if (ForwardingInfo->ATCalls) - { - Calls = ForwardingInfo->ATCalls; - - while(Calls[0]) - { - if (strcmp(Calls[0], ATBBS) == 0) - return TRUE; - - Calls++; - } - } - if ((HRoute) && (ForwardingInfo->Haddresses)) - { - // Match on Routes - - HRoutes = ForwardingInfo->Haddresses; - - while(HRoutes[0]) - { - i = strlen(HRoutes[0]) - 1; - j = strlen(HRoute) - 1; - - while ((i >= 0) && (j >= 0)) // Until one string rus out - { - if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards - goto next; - } - - return TRUE; - next: - HRoutes++; - } - } - - - return FALSE; - -} - -BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo) -{ - char ** Calls; - - // Check TO distributions - - if (ForwardingInfo->TOCalls) - { - Calls = ForwardingInfo->TOCalls; - - while(Calls[0]) - { - if (strcmp(Calls[0], Msg->to) == 0) - return TRUE; - - Calls++; - } - } - return FALSE; -} - -BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS) -{ - char ** Calls; - - // Check AT distributions - - if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS - return TRUE; - - if (ForwardingInfo->ATCalls) - { - Calls = ForwardingInfo->ATCalls; - - while(Calls[0]) - { - if (strcmp(Calls[0], ATBBS) == 0) - return TRUE; - - Calls++; - } - } - return FALSE; -} - -BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) -{ - char ** HRoutes; - int i, j; - - if ((HRoute) && (ForwardingInfo->Haddresses)) - { - // Match on Routes - - HRoutes = ForwardingInfo->Haddresses; - - while(HRoutes[0]) - { - i = strlen(HRoutes[0]) - 1; - j = strlen(HRoute) - 1; - - while ((i >= 0) && (j >= 0)) // Until one string rus out - { - if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards - goto next; - } - - return TRUE; - next: - HRoutes++; - } - } - return FALSE; -} - -#endif - -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; -} +// Mail and Chat Server for BPQ32 Packet Switch +// +// + +// Version 1.0.0.17re + +// Split Messasge, User and BBS Editing from Main Config. +// Add word wrap to Console input and output +// Flash Console on chat user connect +// Fix processing Name response in chat mode +// Fix processing of *RTL from station not defined as a Chat Node +// Fix overlength lines ln List responses +// Housekeeping expires BIDs +// Killing a message removes it from the forwarding counts + +// Version 1.0.0.18 + +// Save User Database when name is entered or updated so it is not lost on a crash +// Fix Protocol Error in Compressed Forwarding when switching direction +// Add Housekeeping results dialog. + +// Version 1.0.0.19 + +// Allow PACLEN in forward scripts. +// Store and forward messages with CRLF as line ends +// Send Disconnect after FQ ( for LinFBB) +// "Last Listed" is saved if MailChat is closed without closing Console +// Maximum acceptable message length can be specified (in Forwarding Config) + +// Version 1.0.0.20 + +// Fix error in saving forwarding config (introduced in .19) +// Limit size of FBB forwarding block. +// Clear old connection (instead of new) if duplicate connect on Chat Node-Node link +// Send FA for Compressed Mail (was sending FB for both Compressed and Uncompressed) + +// Version 1.0.0.21 + +// Fix Connect Script Processing (wasn't waiting for CONNECTED from last step) +// Implement Defer +// Fix MBL-style forwarding +// Fix Add User (Params were not saved) +// Add SC (Send Copy) Command +// Accept call@bbs as well as call @ bbs + +// Version 1.0.0.22 + +// Implement RB RP LN LR LF LN L$ Commands. +// Implement QTH and ZIP Commands. +// Entering an empty Title cancels the message. +// Uses HomeBBS field to set @ field for local users. +// Creates basic WP Database. +// Uses WP to lookup @ field for non-local calls. +// Console "Actions" Menu renamed "Options". +// Excluded flag is actioned. +// Asks user to set HomeBBS if not already set. +// Fix "Shrinking Message" problem, where message got shorter each time it was read Initroduced in .19). +// Flash Server window when anyone connects to chat (If Console Option "Flash on Chat User Connect" set). + +// Version 1.0.0.23 + +// Fix R: line scan bug + +// Version 1.0.0.24 + +// Fix closing console window on 'B'. +// Fix Message Creation time. +// Enable Delete function in WP edit dialog + +// Version 1.0.0.25 + +// Implement K< and K> commands +// Experimental support for B1 and B2 forwarding +// Experimental UI System +// Fix extracting QTH from WP updates + +// Version 1.0.0.26 + +// Add YN etc responses for FBB B1/B2 + +// Version 1.0.0.27 + +// Fix crash if NULL received as start of a packet. +// Add Save WP command +// Make B2 flag BBS-specific. +// Implement B2 Send + +// Version 1.0.0.28 + +// Fix parsing of smtp to addresses - eg smtp:john.wiseman@cantab.net +// Flag messages as Held if smtp server rejects from or to addresses +// Fix Kill to (K> Call) +// Edit Message dialog shows latest first +// Add chat debug window to try to track down occasional chat connection problems + +// Version 1.0.0.29 + +// Add loads of try/excspt + +// Version 1.0.0.30 + +// Writes Debug output to LOG_DEBUG_X and Monitor Window + +// Version 1.0.0.32 + +// Allow use of GoogleMail for ISP functions +// Accept SYSOP as alias for SYSOPCall - ie user can do SP SYSOP, and it will appear in sysop's LM, RM, etc +// Email Housekeeping Results to SYSOP + +// Version 1.0.0.33 + +// Housekeeping now runs at Maintenance Time. Maintenance Interval removed. +// Allow multiple numbers on R and K commands +// Fix L command with single number +// Log if Forward count is out of step with messages to forward. +// UI Processing improved and F< command implemented + +// Version 1.0.0.34 + +// Semaphore Chat Messages +// Display Semaphore Clashes +// More Program Error Traps +// Kill Messages more than BIDLifetime old + +// Version 1.0.0.35 + +// Test for Mike - Remove B1 check from Parse_SID + +// Version 1.0.0.36 + +// Fix calculation of Housekeeping Time. +// Set dialog box background explicitly. +// Remove tray entry for chat debug window. +// Add date to log file name. +// Add Actions Menu option to disable logging. +// Fix size of main window when it changes between versions. + +// Version 1.0.0.37 + +// Implement Paging. +// Fix L< command (was giving no messages). +// Implement LR LR mmm-nnn LR nnn- (and L nnn-) +// KM should no longer kill SYSOP bulls. +// ISP interfaces allows SMTP Auth to be configured +// SMTP Client would fail to send any more messages if a connection failed + +// Version 1.0.0.38 + +// Don't include killed messages in L commands (except LK!) +// Implement l@ +// Add forwarding timebands +// Allow resizing of main window. +// Add Ver command. + +// Version 1.0.1.1 + +// First Public Beta + +// Fix part line handling in Console +// Maintenance deletes old log files. +// Add option to delete files to the recycle bin. + +// Version 1.0.2.1 + +// Allow all Node SYSOP commands in connect scripts. +// Implement FBB B1 Protocol with Resume +// Make FBB Max Block size settable for each BBS. +// Add extra logging when Chat Sessions refused. +// Fix Crash on invalid housekeeping override. +// Add Hold Messages option. +// Trap CRT Errors +// Sort Actions/Start Forwarding List + +// Version 1.0.2.2 + +// Fill in gaps in BBS Number sequence +// Fix PE if ctext contains } +// Run Houskeeping at startup if previous Housekeeping was missed + +// Version 1.0.2.3 + +// Add configured nodes to /p listing + +// Version 1.0.2.4 + +// Fix RMS (it wanted B2 not B12) +// Send messages if available after rejecting all proposals +// Dont try to send msg back to originator. + +// Version 1.0.2.5 + +// Fix timeband processing when none specified. +// Improved Chat Help display. +// Add helpful responses to /n /q and /t + +// Version 1.0.2.6 + +// Kill Personal WP messages after processing +// Make sure a node doesnt try to "join" or "leave" a node as a user. +// More tracing to try to track down lost topic links. +// Add command recall to Console +// Show users in new topic when changing topic +// Add Send From Clipboard" Action + +// Version 1.0.2.7 + +// Hold messages from the future, or with invalid dates. +// Add KH (kill held) command. +// Send Message to SYSOP when a new user connects. + +// Version 1.0.2.8 + +// Don't reject personal message on Dup BID unless we already have an unforwarded copy. +// Hold Looping messages. +// Warn SYSOP of held messages. + +// Version 1.0.2.9 + +// Close connecton on receipt of *** DONE (MBL style forwarding). +// Improved validation in link_drop (Chat Node) +// Change to welcome prompt and Msg Header for Outpost. +// Fix Connect Script processing for KA Nodes + +// Version 1.0.3.1 + +// Fix incorrect sending of NO - BID. +// Fix problems caused by a user being connected to more than one chat node. +// Show idle time on Chat /u display. +// Rewrite forwarding by HA. +// Add "Bad Words" Test. +// Add reason for holding to SYSOP "Message Held" Message. +// Make topics case-insensitive. +// Allow SR for smtp mail. +// Try to fix some user's "Add User" problem. + + +// Version 1.0.3.2 + +// Fix program error when prcessing - response in FBB forwarding. +// Fix code to flag messages as sent. + + +// Version 1.0.3.3 + +// Attempt to fix message loop on topic_change +// Fix loop if compressed size is greater than 32K when receiving with B1 protocol. +// Fix selection of B1 + +// Version 1.0.3.4 + +// Add "KISS ONLY" Flag to R: Lines (Needs Node Version 4.10.12 (4.10l) or above) +// Add Basic NNTP Interface +// Fix possible loop in lzhuf encode + +// Version 1.0.3.5 + +// Fix forwarding of Held Messages +// More attempts to fix Chat crashes. +// Limit join/leave problem with mismatched nodes. +// Add Chat Node Monitoring System. +// Change order of elements in nntp addresses (now to.at, was at.to) + +// Version 1.0.3.6 + +// Restart and Exit if too many errors +// Fix forwarding of killed messages. +// Fix Forwarding to PaKet. +// Fix problem if BBS signon contains words from the "Fail" list + +// Version 1.0.3.7 + +// re-fix loop if compressed size is greater than 32K - reintroduced in 1.0.3.4 +// Add last message to edit users +// Change Console and Monitor Buffer sizes +// Don't flag msg as 'Y' on read if it was Held or Killed + +// Version 1.0.3.8 + +// Don't connect if all messages for a BBS are held. +// Hold message if From or To are missing. +// Fix parsing of /n and /q commands +// fix possible loop on changing name or qth + +// Version 1.0.3.9 + +// More Chat fixes and monitoring +// Added additional console for chat + +// Version 1.0.3.10 + +// Fix for corruption of CIrcuit-Node chain. + +// Version 1.0.3.11 + +// Fix flow control for SMTP and NNTP + +// Version 1.0.3.12 + +// Fix crash in SendChatStatus if no Chat Links Defined. +// Disable Chat Mode if there is no ApplCall for ChatApplNum, +// Add Edit Message to Manage Messages Dialog +// NNTP needs authentication + + +// Version 1.0.3.13 + +// Fix Chat ApplCall warning when ChatAppl = 0 +// Add NNTP NEWGROUPS Command +// Fix MBL Forwarding (remove extra > prompt after SP) + +// Version 1.0.3.14 + +// Fix topic switch code. +// Send SYSOP messages on POP3 interface if User SYSOP flag is set. +// NNTP only needs Authentication for posting, not reading. + +// Version 1.0.3.15 + +// Fix reset of First to Forward after househeeping + +// Version 1.0.3.16 + +// Fix check of HA for terminating WW +// MBL Mode remove extra > prompts +// Fix program error if WP record has unexpected format +// Connect Script changes for WINMOR +// Fix typo in unconfigured node has connected message + +// Version 1.0.3.17 + +// Fix forwarding of Personals + +// Version 1.0.3.18 + +// Fix detection of misconfigured nodes to work with new nodes. +// Limit connection attempt rate when a chat node is unavailable. +// Fix Program Error on long input lines (> ~250 chars). + +// Version 1.0.3.19 + +// Fix Restart of B2 mode transfers. +// Fix error if other end offers B1 and you are configured for B2 only. + + +// Version 1.0.3.20 + +// Fix Paging in Chat Mode. +// Report Node Versions. + +// Version 1.0.3.21 + +// Check node is not already known when processing OK +// Add option to suppress emailing of housekeeping results + +// Version 1.0.3.22 + +// Correct Version processing when user connects via the network +// Add time controlled forwarding scripts + +// Version 1.0.3.23 + +// Changes to RMS forwarding + +// Version 1.0.3.24 + +// Fix RMS: from SMTP interface +// Accept RMS/ instead of RMS: for Thunderbird + +// Version 1.0.3.25 + +// Accept smtp: addresses from smtp client, and route to ISP gateway. +// Set FROM address of messages from RMS that are delivered to smtp client so a reply will go back via RMS. + +// Version 1.0.3.26 + +// Improve display of rms and smtp messages in message lists and message display. + +// Version 1.0.3.27 + +// Correct code that prevents mail being retured to originating BBS. +// Tidy stuck Nodes and Topics when all links close +// Fix B2 handling of @ to TO Address. + +// Version 1.0.3.28 + +// Ensure user Record for the BBS Call has BBS bit set. +// Don't send messages addressed @winlink.org if addressee is a local user with Poll RMS set. +// Add user configurable welcome messages. + +// Version 1.0.3.29 + +// Add AUTH feature to Rig Control + +// Version 1.0.3.30 + +// Process Paclink Header (;FW:) + +// Version 1.0.3.31 + +// Process Messages with attachments. +// Add inactivity timeout to Chat Console sessions. + +// Version 1.0.3.32 + +// Fix for Paclink > BBS Addresses + +// Version 1.0.3.33 + +// Fix multiple transfers per session for B2. +// Kill messages eent to paclink. +// Add option to forward messages on arrival. + +// Version 1.0.3.34 + +// Fix bbs addresses to winlink. +// Fix adding @winlink.org to imcoming paclink msgs + +// Version 1.0.3.35 + +// Fix bbs addresses to winlink. (Again) + +// Version 1.0.3.36 + +// Restart changes for RMS/paclink + +// Version 1.0.3.37 + +// Fix for RMS Express forwarding + +// Version 1.0.3.38 + +// Fixes for smtp and lower case packet addresses from Airmail +// Fix missing > afer NO - Bid in MBL mode + +// Version 1.0.3.39 + +// Use ;FW: for RMS polling. + +// Version 1.0.3.40 + +// Add ELSE Option to connect scripts. + +// Version 1.0.3.41 + +// Improved handling of Multiple Addresses +// Add user colours to chat. + +// Version 1.0.3.42 + +// Poll multiple SSID's for RMS +// Colour support for BPQTEerminal +// New /C chat command to toggle colour on or off. + +// Version 1.0.3.43 + +// Add SKIPPROMPT command to forward scripts + +// Version 1.0.4.1 + +// Non - Beta Release +// Fix possible crash/corruption with long B2 messages + +// Version 1.0.4.2 + +// Add @winlink.org to the B2 From addresss if it is just a callsign +// Route Flood Bulls on TO as well as @ + +// Version 1.0.4.3 + +// Handle Packet Addresses from RMS Express +// Fix for Housekeeping B$ messages + +// Version 1.0.4.4 + +// Remove B2 header and all but the Body part from messages forwared using MBL +// Fix handling of ;FW: from RMS Express + +// Version 1.0.4.5 + +// Disable Paging on forwarding sessions. +// Kill Msgs sent to RMS Exxpress +// Add Name to Chat *** Joined msg + +// Version 1.0.4.6 + +// Pass smtp:winlink.org messages from Airmail to local user check +// Only apply local user check to RMS: messages @winlink.org +// Check locally input smtp: messages for local winlink.org users +// Provide facility to allow only one connect on a port + +// Version 1.0.4.8 + +// Only reset last listed on L or LR commands. + +// Version 1.0.4.9 + +// Fix error in handling smtp: messages to winlink.org addresses from Airmail + +// Version 1.0.4.10 + +// Fix Badwords processing +// Add Connect Script PAUSE command + +// Version 1.0.4.11 + +// Suppress display and listing of held messages +// Add option to exclude SYSOP messages from LM, KM, etc +// Fix crash whan receiving messages with long lines via plain text forwarding + +// Version 1.0.4.12 Jul 2010 + +// Route P messages on AT +// Allow Applications above 8 + +// Version 1.0.4.13 Aug 2010 + +// Fix TidyString for addresses of form John Wiseman +// Add Try/Except around socket routines + +// Version 1.0.4.14 Aug 2010 + +// Trap "Error - TNC Not Ready" in forward script response +// Fix restart after program error +// Add INFO command +// Add SYSOP-configurable HELP Text. + +// Version 1.0.4.15 Aug 2010 + +// Semaphore Connect/Disconnect +// Semaphore RemoveTempBIDS + +// Version 1.0.4.16 Aug 2010 + +// Remove prompt after receiving unrecognised line in MBL mode. (for MSYS) + +// Version 1.0.4.17 Aug 2010 + +// Fix receiving multiple messages in FBB Uncompressed Mode +// Try to trap phantom chat node connections +// Add delay to close + + +// Version 1.0.4.18 Aug 2010 + +// Add "Send SYSTEM messages to SYSOP Call" Option +// set fwd bit on local winlink.org msgs if user is a BBS +// add winlink.org to from address of messages from WL2K that don't already have an @ + +// Version 1.0.4.19 Sept 2010 + +// Build a B2 From: address if possible, so RMS Express can reply to packet messages. +// Fix handling of addresses from WL2K with SSID's +// L@ now only matches up to length of input string. +// Remove "Type H for help" from login prompt. + +// Version 1.0.4.20 Sept 2010 + +// Process FBB 'E' response +// Handle FROM addresses with an @BBS +// Fix FROM addresses with @ on end. +// Extend delay before close after sending FQ on winmor/pactor sessions. + +// Version 1.0.4.21 Sept 2010 + +// Fix handling B2 From: with an HA +// Add "Expert User" welcome message. + +// Version 1.0.4.22 Sept 2010 + +// Version 1.0.4.23 Oct 2010 + +// Add Dup message supression +// Dont change B2 from if going to RMS + +// Version 1.0.4.24 Oct 2010 + +// Add "Save Registry Config" command +// Add forwarding on wildcarded TO for NTS +// Add option to force text mode forwarding +// Define new users as a temporaty BBS if SID received in reply to Name prompt +// Reduce delay before sending close after sending FQ on pactor sessions +// Fix processing of MIME boundary from GMail + +// Send /ex instead of ctrl/z for text mode forwarding +// Send [WL2K-BPQ... SID if user flagged as RMS Express +// Fix Chat Map reporting when more than one AXIP port +// Add Message State D for NTS Messages +// Forward messages in priority order - T, P, B +// Add Reject and Hold Filters +// Fix holding messages to local RMS users when received as part of a multiple addressee message + +// Version 1.0.4.25 Nov 2010 + +// Renumbered for release +// Add option to save Registry Config during Housekeeping + +// Version 1.0.4.26 Nov 2010 + +// Fix F> loop when doing MBL forwarding between BPQ BBSes +// Allow multiple To: addresses, separated by ; +// Allow Houskeeping Lifetime Overrides to apply to Unsent Messages. +// Set Unforwarded Bulls to status '$' +// Accept MARS and USA as continent codes for MARS Packet Addresses +// Add option to send Non-delivery notifications. + +// Version 1.0.4.27 Dec 2010 + +// Add MSGTYPES fwd file option + +// Version 1.0.4.28 Dec 2010 + +// Renumbered to for release + +// Version 1.0.4.30 Dec 2010 + +// Fix rescan requeuing where bull was rejected by a BBS +// Fiz flagging bulls received by NNTP with $ if they need to be forwarded. +// Add Chat Keepalive option. +// Fix bug in non-delivery notification. + +// Version 1.0.4.32 Jan 2011 + +// Allow "Send from Clipboard" to send to rms: or smtp: +// Allow messages received via SMTP to be bulls (TO preceeded by bull/) or NTS (to nnnnn@NTSXX or nnnnn@NTSXX.NTS) +// Fix corruption of messages converted to B2 if body contains binary data +// Fix occasional program error when forwarding B2 messages +// Limit FBB protocol data blocks to 250 to try to fix restart problem. +// Add F2 to F5 to open windows. + +// Version 1.0.4.33 Jan 2011 + +// Fix holding old bulls with forwarding info. + +// Version 1.0.4.33 Jan 2011 + +// Prevent transfer restarting after a program error. +// Allow Housekeeping to kill held messages. + +// Version 1.0.4.35 Jan 2011 + +// Add Size limits for P and T messages to MSGTYPES command +// Fix Error in MBL processing when blank lines received (introduced in .33) +// Trap possible PE in Send_MON_Datagram +// Don't use paging on chat sessions + +// Version 1.0.4.36 Jan 2011 + +// Fix error after handling first FBB block. +// Add $X and $x welcome message options. + +// Version 1.0.4.37 Jan 2011 + +// Change L command not to list the last message if no new ones are available +// Add LC I I@ IH IZ commands +// Add option to send warning to sysop if forwarded P or T message has nowhere to go +// Fixes for Winpack Compressed Download +// Fix Houskeeping when "Apply Overrides to Unsent Bulls" is set. +// Add console copy/paste. +// Add "No Bulls" Option. +// Add "Mail For" Beacon. +// Tidied up Tab order in config dialogs to help text-to-speech programs. +// Limit MaxMsgno to 99000. + +// Version 1.0.4.38 Feb 2011 + +// Renumbered for release + +// Version 1.0.4.40 April 2011 + +// Add POLLRMS command + +// Changes for Vista/Win7 (registry key change) +// Workaround for changes to RMS Express +// Fix AUTH bug in SMTP server +// Add filter to Edit Messages dialog + +// Version 1.0.4.41 April 2011 + +// Extend B2 proposals to other BPQMail systems so Reject Filter will work. +// Add Edit User Command +// Use internal Registry Save routine instead of Regedit +// Fix Start Forward/All +// Allow Winpack Compressed Upload/Download if PMS flag set (as well as BBS flag) +// Add FWD SYSOP command +// Fix security on POLLRMS command +// Add AUTH command +// Leave selection in same place after Delete User +// Combine SMTP server messages to multiple WL2K addresses into one message to WL2k +// Add option to show name as well as call on Chat messages +// Fix program error if you try to define more than 80 BBS's + +// Version 1.0.4.45 October 2011 + +// Changes to program error reporting. +// BBS "Returh to Node" command added +// Move config to "Standard" location (BPQ Directory/BPQMailChat) . +// Fix crash if "Edit Message" clicked with no message selected. + +// Version 1.0.4.46 October 2011 + +// Fix BaseDir test when BaseDir ends with \ or / +// Fix long BaseDir values (>50 chars) + +// Version 1.4.47.1 January 2012 + +// Call CloseBPQ32 on exit +// Add option to flash window instead of sounding bell on Chat Connects +// Add ShowRMS SYSOP command +// Update WP with I records from R: lines +// Send WP Updates +// Fix Paclen on Pactor-like sessions +// Fix SID and Prompt when RMS Express User is set +// Try to stop loop in Program Error/Restarting code +// Trap "UNABLE TO CONNECT" response in connect script +// Add facility to print messages or save them to a text file + +// Version 1.4.48.1 January 2012 + +// Add Send Message (as well as Send from Clipboard) +// Fix Email From: Address when forwaring using B2 +// Send WP from BBSCALL not SYSOPCALL +// Send Chat Map reports via BPQ32.dll + + +// Version 1.4.49.1 February 2012 + + +// Fix Setting Paclink mode on SNOS connects +// Remove creation of debugging file for each message +// Add Message Export and Import functions +// All printing of more than one message at a time +// Add command to toggle "Expert" status + +// Version 1.4.50.1 February 2012 + +// Fix forwarding to RMS Express users +// Route messages received via B2 to an Internet email address to RMS +// Add Reverse Poll interval +// Add full FROM address to POP3 messages +// Include HOMEBBS command in Help + + +// Version 1.4.51.1 June 2012 + +// Allow bulls to be sent from RMS Express. +// Handle BASE64 and Quoted-printable encoding of single part messages +// Work round for RMS Express "All proposals rejected" Bug. + +// Version 1.4.52.1 August 2012 + +// Fix size limit on B2 To List when sending to multiple dests +// Fix initialisation of DIRMES.SYS control record +// Allow use of Tracker and UZ7HO ports for UI messages + +// Version 1.4.53.1 September 2012 + +// Fix crash if R: line with out a CR found. + +// Version 1.4.54.1 ?? 2012 + +// Add configurable prompts +// Fix KISS-Only Test +// Send EHLO instead of HELO when Authentication is needed on SMTP session +// Add option to use local tome for bbs forwarding config +// Allow comment lines (; or @) or single space in fwd scripts +// Fix loss of forwarding info if SAVE is clicked before selecting a call + +// Version 1.4.55.1 June 2013 + +// Add option to remove users that have not connected for a long time. +// Add l@ smtp: +// Fix From: sent to POP3 Client when meaages is from RMS +// Display Email From on Manage Messages + +// Version 1.4.56.1 July 2013 + +// Add timeout +// Verify prompts +// Add IDLETIME command + + + +// Version 1.4.57.1 + +// Change default IDLETIME +// Fix display of BBS's in Web "Manage Messages" +// Add separate househeeping lifetines for T messages +// Don't change flag on forwarded or delivered messages if they sre subsequently read +// Speed up processing, mainly to stop RMS Express timing out when connecting via Telnet +// Don't append winlink.org to RMS Express or Paclink addresses if RMS is not configured +// Fix receiving NTS messages via B2 +// Add option to send "Mail For", but not FBB Headers +// Fix corruption caused with Subject longer than 60 bytes reveived from Winlink systems +// Fix Endian bug in FBB Compression code + + +// Version 1.4.58.1 + +// Change control of appending winlink.org to RMS Express or Paclink addresses to a user flag +// Lookup HomeBBS and WP for calls without a via received from RMS Express or Paclink +// Treat call@bpq as request to look up address in Home BBS/WP for messages received from RMS Express or Paclink +// Collect stats by message type +// Fix Non-Delivery notifications to SMTP messages +// Add Message Type Stats to BBS Trafic Report +// Add "Batch forward to email" +// Add EXPORT command +// Allow more BBS records +// Allow lower case connect scripts +// Fix POP3 LIST command +// Fix MIME Multipart Alternate with first part Base64 or Quoted Printable encoding +// Fix duplicates of SP SYSOP@WW Messages +// Add command line option (tidymail) to delete redundant Mail files +// Add command line option (nohomebbs) to suppress HomeBBS prompt + +// 59 April 2014 + +// Add FLARQ Mail Mode +// Fix possible crash saving restart data +// Add script command ADDLF for connect scripts over Telnet +// Add recogniton of URONODE connected message +// Add option to stop Name prompt +// Add new RMS Express users with "RMS Express User" flag set +// Validate HTML Pages +// Add NTS swap file +// Add basic File list and read functions +// Fix Traffic report + +// 60 + +// Fix security hole in readfile + +// 61 August 2014 +// Set Messages to NTS:nnnnn@NTSXX to type 'T' and remove NTS +// Dont treat "Attempting downlink" as a failure +// Add option to read messages during a list +// Fix crash during message renumber on MAC +// Timeout response to SID to try to avoid hang on an incomplete connection. +// Save config in file instead of registry +// Fix Manage Messages "EXPORT" option and check filename on EXPORT command +// Fix reverse forward prompt in MBL mode. +// Fix From address in POP3 messages where path is @winlink.org +// Fix possible program error in T message procesing +// Add MaxAge param (for incoming Bulls) + + +//62 November 2014 +// Add ZIP and Permit Bulls flag to Manage Users +// Allow users to kill their own B and anyone to kill T messages +// Improve saving of "Last Listed" +// Fix LL when paging +// Send Date received in R: Line (should fix B2 message restarts) +// Fix occasional crash in terminal part line processing +// Add "SKIPCON" forwarding command to handle nodes that include "Connected" in their CTEXT +// Fix possible retry loop when message is deferred (FBB '=' response); +// Don't remove Attachments from received bulls. + +//63 Feb 2015 + +// Fix creating Bulls from RMS Express messages. +// Fix PE if message with no To: received. +// Fix setting "RMS Express User" flag on new connects from RMS Express +// Fix deleting 'T' messages downloaded by RMS Express +// Include MPS messages in count of messages to forward. +// Add new Welcome Message variable $F for messages to forward +// Fix setting Type in B2 header when usong NTS: or BULL: +// Remove trailing spaces from BID when Creating Message from Clipboard. +// Improved handling of FBB B1/B2 Restarts. + +//64 September 2015 + +// Fix Message Type in msgs from RMS Express to Internet +// Reopen Monitor window if open when program list closed +// Only apply NTS alias file to NTS Messages +// Fix failure to store some encrypted ISP passwords +// Allow EDITUSER to change "RMS Express User" flag +// Fix reporting of Config File errors +// Fix Finding MPS Messages (First to Forward was being used incorrectly) +// Add "Save Attachment" to Web Mgmt Interface +// Support Secure Signon on Forwarding sessions to CMS +// Save Forwarding config when BBS flag on user is cleared +// Pass internally generated SYSOP messages through routing process +// Add POP3 TOP command. +// Don't set 'T' messages to 'Y' when read. +// Add optional temporary connect script on "FWD NOW" command +// Add automatic import facility +// Accept RMS mail to BBS Call even if "Poll RMS" not set. + +// 65 November 2015 + +// Fix loading Housekeeping value for forwarded bulls. +// Fix re-using Fwd script override in timer driven forwarding. +// Add ampr.org handling +// Add "Dont forward" match on TO address for NTS +// Allow listing a combinatiom of state and type, such as LNT or LPF +// Fix handling ISP messages from gmail without a '+' +// Add basic WebMail support + +// 66 + +// Autoimport messages as Dummy Call, not SYSOP Call +// Add "My Messages" display option to WebMail +// Create .csv extract of User List during hourekeeping. +// Fix processing of NTS Alising of @ Addresses +// Don't reroute Delivered NTS Messages +// Add option to stop users killing T messages +// Add multicast Receive +// Fix initialising new message database format field +// Fix "Forward Messages to BBS Call" option. +// Add Filter WP Bulls option and allow multiple WP "TO" addresses +// Fix deleting P WP messages for other stations +// Fix saving blank lines in forwarding config +// Fix paging on L@ and l< +// Fix removing DELETE from IMPORT XXX DELETE and allow multiple IMPORT lines in script +// Run DeleteRedundantMessages before renumbering messages +// Connect script now tries ELSE lines if prompt not received from remote BBS +// Send connecting call instead of BBS Name when connecting to CMS server. +// Add BID filter to Manage Messages +// Fix handling of over long suject lines in IMPORT +// Allow comments before ELSE in connect script +// Add Copy and Clear to Multicast Window +// Fix possible duplicate messages with MBL forwarding +// Set "Permit EMail" on IMPORT dummy User. +// Fix repeated running of housekeeping if clock is stepped forward. +// Fix corruption of CMS Pass field by Web interface +// Kill B2 WP bulls if FilterWPBulls set +// Include Message Type in BPQ B2 proposal extensions + +// 6.0.14.1 July 2017 + +// Fix corruption of BBSNumber if RMS Ex User and BBS both checked +// Tread B messages without an AT as Flood. +// Make sure Message headers are always saved to disk when a message status changes +// Reject message instead of failing session if TO address too long in FBB forwarding +// Fix error when FBB restart data exactly fills a packet. +// Fix possible generation of msg number zero in send nondlivery notification +// Fix problem with Web "Manage Messages" when stray message number zero appears +// Fix Crash in AMPR forward when host missing from VIA +// Fix possible addition of an spurious password entry to the ;FW: line when connecting to CMS +// Fix test for Status "D" in forward check. +// Don't cancel AUTH on SMTP RSET +// Fix "nowhere to go" message on some messages sent to smtp addresses +// Add @ from Home BBS or WP is not spcified in "Send from Clipboard" + +// 6.0.15.1 Feb 2018 + +// Fix PE if Filename missing from FILE connect script command +// Suppress reporting errors after receiving FQ +// Fix problem caused by trailing spaces on callsign in WP database +// Support mixed case WINLINK Passwords + +// 6.0.16.1 March 2018 + +// Make sure messages sent to WL2K don;'t have @ on from: address +// If message to saildocs add R: line as an X header instead of to body +// Close session if more than 4 Invalid Commmad responses sent +// Report TOP in POP3 CAPA list. Allows POP3 to work with Windows Mail client + +// 6.0.17.1 November 2018 + +// Add source routing using ! eg sp g8bpq@winlink.org!gm8bpq to send via RMS on gm8bpq +// Accept an internet email address without rms: or smtp: +// Fix "Forward messages for BBS Call" when TO isn't BBS Call +// Accept NNTP commands in either case +// Add NNTP BODY command +// Timeout POP or SMTP TCP connections that are open too long +// Add YAPP support +// Fix connect script when Node CTEXT contains "} BBS " +// Fix handling null H Route +// Detect and correct duplicate BBS Numbers +// Fix problem if BBS requests FBB blocked forwarding without compression (ie SID of F without B) +// Fix crash if YAPP entered without filenmame and send BBS prompt after YAPP error messages +// Add support for Winlink HTML Forms to WebMail interface +// Update B2 header when using NTS alias file with B2 messages + +// 6.0.18.1 January 2019 + +// Ensure callsigns in WP database are upper case. +// Various fixes for Webmail +// Fix sending direct to ampr.org addresses +// Use SYSOP Call as default for Webmail if set +// Preparations for 64 bit version + + +// 6.0.19.1 September 2019 + +// Trap missing HTML reply Template or HTML files +// Fix case problems in HTML Templates +// Fix setting To call on reply to HTML messages +// More preparations for 64 bit including saving WP info as a text file. +// Set "RMS Express User" when a new user connects using PAT +// Increace maximum length on Forwarding Alias string in Web interface +// Expand multiaddress messages from Winlink Express if "Don't add @Winlink.org" set or no RMS BBS +// Fix program error if READ used without a filename +// Trap reject messages from Winlink CMS +// Fix "delete to recycle bin" on Linux +// Handle Radio Only Messages (-T or -R suffix on calling station) +// Fix program error on saving empty Alias list on Web Forwarding page +// Add REQDIR and REQFIL +// Experimental Blocked Uncompressed forwarding +// Security fix for YAPP +// Fix WebMail Cancel Send Message +// Fix processing Hold Message response from Winlink Express + +// 6.0.20.1 April 2020 + +// Improvments to YAPP +// Add Copy forwarding config +// Add Next and Previous buttons to Webmail message read screen +// Move HTML templates from HTMLPages to inline code. +// Fix Paclen on YAPP send +// Fix bug in handling "RMS Express User" +// Fix WINPACK compressed forwarding +// Add option to send P messages to more than one BBS +// Add "Default to Don't Add WINLINK.ORG" Config option +// Re-read Badwords.sys during Housekeeping +// Add BID Hold and Reject Filters +// On SMTP Send try HELO if EHLO rejected +// Allow SID response timeout to be configured per BBS +// Fix sending bulls with PAT +// Set "Forward Messages to BBS Call" when routing Bulls on TO +// Add option to send Mail For Message to APRS +// Fix WP update +// Fix Holding messages from Webmail Interface +// Add RMR command +// Add REROUTEMSGS BBS SYSOP command +// Disable null passwords and check Exclude flag in Webmail Signin +// Add basic Webmail logging + +// 6.0.21.1 December 2020 + +// Remove nulls from displayed messages. +// Fix Holding messages from SMTP and POP3 Interfaces +// Various fixes for handling messages to/from Internet email addresses +// Fix saving Email From field in Manage Messages +// Fix sending WL2K traffic reports via TriMode. +// Fix removing successive CR from Webmail Message display +// Fix Wildcarded @ forwarding +// Fix message type when receiving NTS Msgs form Airmail +// Fix address on SERVICE messages from Winlink +// Add multiple TO processing to Webmail non-template messages +// Don't backup config file if reading it fails +// Include Port and Freq on Connected log record +// Make sure welcome mesages don't end in > +// Allow flagging unread T messages as Delivered +// Replace \ with # in forward script so commands starting with # can be sent +// Fix forwarding NTS on TO field +// Fix possible crash in text mode forwarding +// Allow decimals of days in P message lifetimes and allow Houskeeping interval to be configured +// Add DOHOUSEKEEPING sysop command +// Add MARS continent code +// Try to trap 'zombie' BBS Sessions +// On Linux if "Delete to Recycle Bin" is set move deleted messages and logs to directory Deleted under current directory. +// Fix corruption of message length when reading R2 message via Read command +// Fix paging on List command and add new combinations of List options +// Fix NNTP list and LC command when bulls are killed + +// 6.0.22.1 August 2021 + +// Fix flagging messages with attachments as read. +// Fix possible corruption of WP database and subsequent crash on reloading. +// Fix format of Web Manage Messages display +// Include SETNEXTMESSAGENUMBER in SYSOP Help Message +// Fix occasional "Incoming Connect from SWITCH" +// Fix L> with numeric dests +// Improved diagnostic for MailTCP select() error. +// Clear "RMS Express User" if user is changed to a BBS +// Fix saving Window positions on exit +// Fix parsing ReplyTemplate name in Webmail +// Handle multiple addressees for WebMail Forms messages to packet stations +// Add option to allow only known users to connect +// Add basic callsign validation to From address +// Add option to forward a user's messages to Winlink +// Move User config to main config file. +// Update message status whne reading a Forms Webmail message +// Speed up killing multiple messages +// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command + +// 6.0.23.1 June 2022 + +// Fix crash when ; added to call in send commands +// Allow smtp/ override for messages from RMS Express to send via ISP gateway +// Send Internet email from RMS Express to ISP Gateway if enabled and RMS BBS not configured +// Recompiled for Web Interface changes in Node +// Add RMS Relay SYNC Mode (.17) +// Add Protocol changes for Relay RO forwarding +// Add SendWL2KPM command to connect script to allow users other than RMS to send ;FW: string to RMS Relay +// Fix B2 Header Date in Webmail message with sttachments. +// Fix bug when using YAPP with VARA (.27) +// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command +// Add mechsnism to send bbs log records to qttermtcp. (32) +// Add MFJ forwarding Mode (No @BBS on send) +// Fix handling CR/LF split over packet boundaries +// Add Header and Footers for Webmail Send (42) +// Fix Maintenance Interval in LinBPQ (53) +// Add RMS: to valid from addresses (.56) +// Fix Web management on Android deviced (.58) +// Disconnect immediately if "Invalid Command" "*** Protocol Error" or "Already Connected" received (.70) +// Check Badword and Reject filters before processing WP Messages + +// 6.0.24.1 ?? 2022 + +// Fix ' in Webmail subject (8) +// Change web buttons to white on black when pressed (10) +// Add auto-refresh option to Webmail index page (25) +// Fix displaying help and info files with crlf line endings on Linux (28) +// Improve validation of extended FC message (32) +// Improve WP check for SYSTEM as a callsign (33) +// Improvements to RMS Relay SYNC mode (47) +// Fix BID Hold and Reject filters +// Fix Webmail auto-refresh when page exceeds 64K bytes (54) +// Fix Webmail send when using both headers/footers and attachmonts (55) +// Fix R: line corruption on some 64 bit builds +// Dont drop empty lines inm TEXTFORWARDING (61) +// Dont wait for body prompt for TEXTFORWARDING for SID [PMS-3.2-C$] (62) +// Add forwarding mode SETCALLTOSENDER for PMS Systems that don't accept < in SP (63) +// QtTerm Monitoring fixed for 63 port version of BPQ (69) + + +#include "bpqmail.h" +#include "winstdint.h" +#define MAIL +#include "Versions.h" + +#include "GetVersion.h" + +#define MAX_LOADSTRING 100 + +typedef int (WINAPI FAR *FARPROCX)(); +typedef int (WINAPI FAR *FARPROCZ)(); + +FARPROCX pDllBPQTRACE; +FARPROCZ pGetLOC; +FARPROCX pRefreshWebMailIndex; +FARPROCX pRunEventProgram; + +BOOL WINE = FALSE; + +INT_PTR CALLBACK UserEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK MsgEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK FwdEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK WPEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +VOID SetupNTSAliases(char * FN); + +HKEY REGTREE = HKEY_LOCAL_MACHINE; // Default +char * REGTREETEXT = "HKEY_LOCAL_MACHINE"; + +// Global Variables: +HINSTANCE hInst; // current instance +TCHAR szTitle[MAX_LOADSTRING]; // The title bar text +TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name + +extern int LastVer[4]; // In case we need to do somthing the first time a version is run + +UINT BPQMsg; + +HWND MainWnd; +HWND hWndSess; +RECT MainRect; +HMENU hActionMenu; +static HMENU hMenu; +HMENU hDisMenu; // Disconnect Menu Handle +HMENU hFWDMenu; // Forward Menu Handle + +int SessX, SessY, SessWidth; // Params for Session Window + +char szBuff[80]; + +#define MaxSockets 64 + +int _MYTIMEZONE = 0; + +ConnectionInfo Connections[MaxSockets+1]; + +//struct SEM AllocSemaphore = {0, 0}; +//struct SEM ConSemaphore = {0, 0}; +//struct SEM OutputSEM = {0, 0}; + +//struct UserInfo ** UserRecPtr=NULL; +//int NumberofUsers=0; + +//struct UserInfo * BBSChain = NULL; // Chain of users that are BBSes + +//struct MsgInfo ** MsgHddrPtr=NULL; +//int NumberofMessages=0; + +//int FirstMessageIndextoForward=0; // Lowest Message wirh a forward bit set - limits search + +//BIDRec ** BIDRecPtr=NULL; +//int NumberofBIDs=0; + +extern BIDRec ** TempBIDRecPtr; +//int NumberofTempBIDs=0; + +//WPRec ** WPRecPtr=NULL; +//int NumberofWPrecs=0; + +extern char ** BadWords; +//int NumberofBadWords=0; +extern char * BadFile; + +//int LatestMsg = 0; +//struct SEM MsgNoSemaphore = {0, 0}; // For locking updates to LatestMsg +//int HighestBBSNumber = 0; + +//int MaxMsgno = 60000; +//int BidLifetime = 60; +//int MaintInterval = 24; +//int MaintTime = 0; +//int UserLifetime = 0; + + +BOOL cfgMinToTray; + +BOOL DisconnectOnClose; + +extern char PasswordMsg[100]; + +char cfgHOSTPROMPT[100]; + +char cfgCTEXT[100]; + +char cfgLOCALECHO[100]; + +char AttemptsMsg[]; +char disMsg[]; + +char LoginMsg[]; + +char BlankCall[]; + + +ULONG BBSApplMask; +ULONG ChatApplMask; + +int BBSApplNum; + +//int StartStream=0; +int NumberofStreams; +int MaxStreams; + +extern char BBSSID[]; +extern char ChatSID[]; + +extern char NewUserPrompt[100]; + +extern char * WelcomeMsg; +extern char * NewWelcomeMsg; +extern char * ExpertWelcomeMsg; + +extern char * Prompt; +extern char * NewPrompt; +extern char * ExpertPrompt; + +extern BOOL DontNeedHomeBBS; + +char BBSName[100]; +char MailForText[100]; + +char SignoffMsg[100]; + +char AbortedMsg[100]; + +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]; + +char NTSAliasesPath[MAX_PATH]; +extern char NTSAliasesName[MAX_PATH]; + +char BaseDir[MAX_PATH]; +char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% + +char MailDir[MAX_PATH]; + +char RlineVer[50]; + +extern BOOL KISSOnly; + +extern BOOL OpenMon; + +extern struct ALIAS ** NTSAliases; + +extern int EnableUI; +extern int RefuseBulls; +extern int SendSYStoSYSOPCall; +extern int SendBBStoSYSOPCall; +extern int DontHoldNewUsers; +extern int ForwardToMe; + +extern int MailForInterval; + +char zeros[NBMASK]; // For forward bitmask tests + +time_t MaintClock; // Time to run housekeeping + +struct MsgInfo * MsgnotoMsg[100000]; // Message Number to Message Slot List. + +// Filter Params + +char ** RejFrom; // Reject on FROM Call +char ** RejTo; // Reject on TO Call +char ** RejAt; // Reject on AT Call +char ** RejBID; // Reject on BID + +char ** HoldFrom; // Hold on FROM Call +char ** HoldTo; // Hold on TO Call +char ** HoldAt; // Hold on AT Call +char ** HoldBID; // Hold on BID + + +// Send WP Params + +BOOL SendWP; +char SendWPVIA[81]; +char SendWPTO[11]; +int SendWPType; + + +int ProgramErrors = 0; + +UCHAR BPQDirectory[260] = ""; + + +// Forward declarations of functions included in this code module: +ATOM MyRegisterClass(HINSTANCE hInstance); +ATOM RegisterMainWindowClass(HINSTANCE hInstance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK ChatMapDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +unsigned long _beginthread( void( *start_address )(VOID * DParam), + unsigned stack_size, VOID * DParam); + +VOID SendMailForThread(VOID * Param); +BOOL CreatePipeThread(); +int DeleteRedundantMessages(); +VOID BBSSlowTimer(); +VOID CopyConfigFile(char * ConfigName); +BOOL CreateMulticastConsole(); +char * CheckToAddress(CIRCUIT * conn, char * Addr); +BOOL CheckifPacket(char * Via); +int GetHTMLForms(); + +struct _EXCEPTION_POINTERS exinfox; + +CONTEXT ContextRecord; +EXCEPTION_RECORD ExceptionRecord; + +DWORD Stack[16]; + +BOOL Restarting = FALSE; + +Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg) +{ + unsigned int SPPtr; + unsigned int SPVal; + + memcpy(&ContextRecord, exinfo->ContextRecord, sizeof(ContextRecord)); + memcpy(&ExceptionRecord, exinfo->ExceptionRecord, sizeof(ExceptionRecord)); + + SPPtr = ContextRecord.Esp; + + Debugprintf("BPQMail *** Program Error %x at %x in %s", + ExceptionRecord.ExceptionCode, ExceptionRecord.ExceptionAddress, Msg); + + + __asm{ + + mov eax, SPPtr + mov SPVal,eax + lea edi,Stack + mov esi,eax + mov ecx,64 + rep movsb + + } + + Debugprintf("EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x ESP %x", + ContextRecord.Eax, ContextRecord.Ebx, ContextRecord.Ecx, + ContextRecord.Edx, ContextRecord.Esi, ContextRecord.Edi, SPVal); + + Debugprintf("Stack:"); + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + SPVal, Stack[0], Stack[1], Stack[2], Stack[3], Stack[4], Stack[5], Stack[6], Stack[7]); + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + SPVal+32, Stack[8], Stack[9], Stack[10], Stack[11], Stack[12], Stack[13], Stack[14], Stack[15]); + +} + + + +void myInvalidParameterHandler(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) +{ + Logprintf(LOG_DEBUG_X, NULL, '!', "*** Error **** C Run Time Invalid Parameter Handler Called"); + + if (expression && function && file) + { + Logprintf(LOG_DEBUG_X, NULL, '!', "Expression = %S", expression); + Logprintf(LOG_DEBUG_X, NULL, '!', "Function %S", function); + Logprintf(LOG_DEBUG_X, NULL, '!', "File %S Line %d", file, line); + } +} + +// If program gets too many program errors, it will restart itself and shut down + +VOID CheckProgramErrors() +{ + STARTUPINFO SInfo; // pointer to STARTUPINFO + PROCESS_INFORMATION PInfo; // pointer to PROCESS_INFORMATION + char ProgName[256]; + + 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"); + + if (cfgMinToTray) + { + DeleteTrayMenuItem(MainWnd); + if (ConsHeader[0]->hConsole) + DeleteTrayMenuItem(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DeleteTrayMenuItem(ConsHeader[1]->hConsole); + if (hMonitor) + DeleteTrayMenuItem(hMonitor); + } + + 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); + } +} + + +VOID WriteMiniDump() +{ +#ifdef WIN32 + + HANDLE hFile; + BOOL ret; + char FN[256]; + + sprintf(FN, "%s/Logs/MiniDump%x.dmp", GetBPQDirectory(), time(NULL)); + + 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 +} + + +void GetSemaphore(struct SEM * Semaphore, int ID) +{ + // + // Wait for it to be free + // +#ifdef WIN32 + if (Semaphore->Flag != 0) + { + Semaphore->Clashes++; + } +loop1: + + while (Semaphore->Flag != 0) + { + Sleep(10); + } + + // + // try to get semaphore + // + + _asm{ + + mov eax,1 + mov ebx, Semaphore + xchg [ebx],eax // this instruction is locked + + cmp eax,0 + jne loop1 // someone else got it - try again +; +; ok, weve got the semaphore +; + } +#else + + while (Semaphore->Flag) + usleep(10000); + + Semaphore->Flag = 1; + +#endif + return; +} + +void FreeSemaphore(struct SEM * Semaphore) +{ + Semaphore->Flag = 0; + + return; +} + +char * CmdLine; + +extern int configSaved; + +int APIENTRY WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, + int nCmdShow) +{ + MSG msg; + HACCEL hAccelTable; + int BPQStream, n; + struct UserInfo * user; + struct _EXCEPTION_POINTERS exinfo; + _invalid_parameter_handler oldHandler, newHandler; + char Msg[100]; + int i = 60; + struct NNTPRec * NNTPREC; + struct NNTPRec * SaveNNTPREC; + + CmdLine = _strdup(lpCmdLine); + _strlwr(CmdLine); + + if (_stricmp(lpCmdLine, "Wait") == 0) // If AutoRestart then Delay 60 Secs + { + hWnd = CreateWindow("STATIC", "Mail Restarting after Failure - Please Wait", 0, + CW_USEDEFAULT, 100, 550, 70, + NULL, NULL, hInstance, NULL); + + ShowWindow(hWnd, nCmdShow); + + while (i-- > 0) + { + sprintf(Msg, "Mail Restarting after Failure - Please Wait %d secs.", i); + SetWindowText(hWnd, Msg); + + Sleep(1000); + } + + DestroyWindow(hWnd); + } + + __try { + + // Trap CRT Errors + + newHandler = myInvalidParameterHandler; + oldHandler = _set_invalid_parameter_handler(newHandler); + + // Initialize global strings + LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadString(hInstance, IDC_BPQMailChat, szWindowClass, MAX_LOADSTRING); + MyRegisterClass(hInstance); + + // Perform application initialization: + + if (!InitInstance (hInstance, nCmdShow)) + { + return FALSE; + } + + hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BPQMailChat)); + + // Main message loop: + + Logprintf(LOG_DEBUG_X, NULL, '!', "Program Starting"); + Logprintf(LOG_BBS, NULL, '!', "BPQMail Starting"); + Debugprintf("BPQMail Starting"); + + if (pDllBPQTRACE == 0) + Logprintf(LOG_BBS, NULL, '!', "Remote Monitor Log not available - update BPQ32.dll to enable"); + + + } My__except_Routine("Init"); + + while (GetMessage(&msg, NULL, 0, 0)) + { + __try + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + #define EXCEPTMSG "GetMessageLoop" + #include "StdExcept.c" + + CheckProgramErrors(); + } + } + + __try + { + for (n = 0; n < NumberofStreams; n++) + { + BPQStream=Connections[n].BPQStream; + + if (BPQStream) + { + SetAppl(BPQStream, 0, 0); + Disconnect(BPQStream); + DeallocateStream(BPQStream); + } + } + + + hWnd = CreateWindow("STATIC", "Mail Closing - Please Wait", 0, + 150, 200, 350, 40, NULL, NULL, hInstance, NULL); + + ShowWindow(hWnd, nCmdShow); + + Sleep(1000); // A bit of time for links to close + + DestroyWindow(hWnd); + + if (ConsHeader[0]->hConsole) + DestroyWindow(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DestroyWindow(ConsHeader[1]->hConsole); + if (hMonitor) + { + DestroyWindow(hMonitor); + hMonitor = (HWND)1; // For status Save + } + + +// SaveUserDatabase(); + SaveMessageDatabase(); + SaveBIDDatabase(); + + configSaved = 1; + SaveConfig(ConfigName); + + if (cfgMinToTray) + { + DeleteTrayMenuItem(MainWnd); + if (ConsHeader[0]->hConsole) + DeleteTrayMenuItem(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DeleteTrayMenuItem(ConsHeader[1]->hConsole); + if (hMonitor) + DeleteTrayMenuItem(hMonitor); + } + + // Free all allocated memory + + for (n = 0; n <= NumberofUsers; n++) + { + user = UserRecPtr[n]; + + if (user->ForwardingInfo) + { + FreeForwardingStruct(user); + free(user->ForwardingInfo); + } + + free(user->Temp); + + free(user); + } + + free(UserRecPtr); + + for (n = 0; n <= NumberofMessages; n++) + free(MsgHddrPtr[n]); + + free(MsgHddrPtr); + + for (n = 0; n <= NumberofWPrecs; n++) + free(WPRecPtr[n]); + + free(WPRecPtr); + + for (n = 0; n <= NumberofBIDs; n++) + free(BIDRecPtr[n]); + + free(BIDRecPtr); + + if (TempBIDRecPtr) + free(TempBIDRecPtr); + + NNTPREC = FirstNNTPRec; + + while (NNTPREC) + { + SaveNNTPREC = NNTPREC->Next; + free(NNTPREC); + NNTPREC = SaveNNTPREC; + } + + if (BadWords) free(BadWords); + if (BadFile) free(BadFile); + + n = 0; + + if (Aliases) + { + while(Aliases[n]) + { + free(Aliases[n]->Dest); + free(Aliases[n]); + n++; + } + + free(Aliases); + FreeList(AliasText); + } + + n = 0; + + if (NTSAliases) + { + while(NTSAliases[n]) + { + free(NTSAliases[n]->Dest); + free(NTSAliases[n]); + n++; + } + + free(NTSAliases); + } + + FreeOverrides(); + + FreeList(RejFrom); + FreeList(RejTo); + FreeList(RejAt); + FreeList(RejBID); + FreeList(HoldFrom); + FreeList(HoldTo); + FreeList(HoldAt); + FreeList(HoldBID); + FreeList(SendWPAddrs); + + Free_UI(); + + for (n=1; n<20; n++) + { + if (MyElements[n]) free(MyElements[n]); + } + + free(WelcomeMsg); + free(NewWelcomeMsg); + free(ExpertWelcomeMsg); + + free(Prompt); + free(NewPrompt); + free(ExpertPrompt); + + FreeWebMailMallocs(); + + free(CmdLine); + + _CrtDumpMemoryLeaks(); + + } + My__except_Routine("Close Processing"); + + CloseBPQ32(); // Close Ext Drivers if last bpq32 process + + return (int) msg.wParam; +} + + + +// +// FUNCTION: MyRegisterClass() +// +// PURPOSE: Registers the window class. +// +// COMMENTS: +// +// This function and its usage are only necessary if you want this code +// to be compatible with Win32 systems prior to the 'RegisterClassEx' +// function that was added to Windows 95. It is important to call this function +// so that the application will get 'well formed' small icons associated +// with it. +// +// +#define BGCOLOUR RGB(236,233,216) +//#define BGCOLOUR RGB(245,245,245) + +HBRUSH bgBrush; + +ATOM MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + + bgBrush = CreateSolidBrush(BGCOLOUR); + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = DLGWINDOWEXTRA; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(BPQICON)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = bgBrush; + wcex.lpszMenuName = MAKEINTRESOURCE(IDC_BPQMailChat); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(BPQICON)); + + return RegisterClassEx(&wcex); +} + + +// +// FUNCTION: InitInstance(HINSTANCE, int) +// +// PURPOSE: Saves instance handle and creates main window +// +// COMMENTS: +// +// In this function, we save the instance handle in a global variable and +// create and display the main program window. +// + +HWND hWnd; + +int AXIPPort = 0; + +char LOC[7] = ""; + +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + char Title[80]; + WSADATA WsaData; + HMENU hTopMenu; // handle of menu + HKEY hKey=0; + int retCode; + RECT InitRect; + RECT SessRect; + struct _EXCEPTION_POINTERS exinfo; + + HMODULE ExtDriver = LoadLibrary("bpq32.dll"); + + if (ExtDriver) + { + pDllBPQTRACE = GetProcAddress(ExtDriver,"_DllBPQTRACE@8"); + pGetLOC = GetProcAddress(ExtDriver,"_GetLOC@0"); + pRefreshWebMailIndex = GetProcAddress(ExtDriver,"_RefreshWebMailIndex@0"); + pRunEventProgram = GetProcAddress(ExtDriver,"_RunEventProgram@8"); + + if (pGetLOC) + { + char * pLOC = (char *)pGetLOC(); + memcpy(LOC, pLOC, 6); + } + } + + // See if running under WINE + + retCode = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "SOFTWARE\\Wine", 0, KEY_QUERY_VALUE, &hKey); + + if (retCode == ERROR_SUCCESS) + { + RegCloseKey(hKey); + WINE =TRUE; + Debugprintf("Running under WINE"); + } + + + REGTREE = GetRegistryKey(); + REGTREETEXT = GetRegistryKeyText(); + + Sleep(1000); + + { + int n; + struct _EXTPORTDATA * PORTVEC; + + KISSOnly = TRUE; + + for (n=1; n <= GetNumberofPorts(); n++) + { + PORTVEC = (struct _EXTPORTDATA * )GetPortTableEntryFromSlot(n); + + if (PORTVEC->PORTCONTROL.PORTTYPE == 16) // EXTERNAL + { + if (_memicmp(PORTVEC->PORT_DLL_NAME, "TELNET", 6) == 0) + KISSOnly = FALSE; + + if (PORTVEC->PORTCONTROL.PROTOCOL != 10) // Pactor/WINMOR + KISSOnly = FALSE; + + if (AXIPPort == 0) + { + if (_memicmp(PORTVEC->PORT_DLL_NAME, "BPQAXIP", 7) == 0) + { + AXIPPort = PORTVEC->PORTCONTROL.PORTNUMBER; + KISSOnly = FALSE; + } + } + } + } + } + + hInst = hInstance; + + hWnd=CreateDialog(hInst,szWindowClass,0,NULL); + + if (!hWnd) + { + return FALSE; + } + + MainWnd = hWnd; + + GetVersionInfo(NULL); + + sprintf(Title,"G8BPQ Mail Server Version %s", VersionString); + + sprintf(RlineVer, "BPQ%s%d.%d.%d", (KISSOnly) ? "K" : "", Ver[0], Ver[1], Ver[2]); + + SetWindowText(hWnd,Title); + + hWndSess = GetDlgItem(hWnd, 100); + + GetWindowRect(hWnd, &InitRect); + GetWindowRect(hWndSess, &SessRect); + + SessX = SessRect.left - InitRect.left ; + SessY = SessRect.top -InitRect.top; + SessWidth = SessRect.right - SessRect.left; + + // Get handles for updating menu items + + hTopMenu=GetMenu(MainWnd); + hActionMenu=GetSubMenu(hTopMenu,0); + + hFWDMenu=GetSubMenu(hActionMenu,0); + hMenu=GetSubMenu(hActionMenu,1); + hDisMenu=GetSubMenu(hActionMenu,2); + + CheckTimer(); + + cfgMinToTray = GetMinimizetoTrayFlag(); + + if ((nCmdShow == SW_SHOWMINIMIZED) || (nCmdShow == SW_SHOWMINNOACTIVE)) + if (cfgMinToTray) + { + ShowWindow(hWnd, SW_HIDE); + } + else + { + ShowWindow(hWnd, nCmdShow); + } + else + ShowWindow(hWnd, nCmdShow); + + UpdateWindow(hWnd); + + WSAStartup(MAKEWORD(2, 0), &WsaData); + + __try { + + return Initialise(); + + }My__except_Routine("Initialise"); + + return FALSE; +} + +// +// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) +// +// PURPOSE: Processes messages for the main window. +// +// + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + PAINTSTRUCT ps; + HDC hdc; + int state,change; + ConnectionInfo * conn; + struct _EXCEPTION_POINTERS exinfo; + + + if (message == BPQMsg) + { + if (lParam & BPQMonitorAvail) + { + __try + { + DoBBSMonitorData(wParam); + } + My__except_Routine("DoMonitorData"); + + return 0; + + } + if (lParam & BPQDataAvail) + { + // Dont trap error at this level - let Node error handler pick it up +// __try +// { + DoReceivedData(wParam); +// } +// My__except_Routine("DoReceivedData") + return 0; + } + if (lParam & BPQStateChange) + { + // Get current Session State. Any state changed is ACK'ed + // automatically. See BPQHOST functions 4 and 5. + + __try + { + SessionState(wParam, &state, &change); + + if (change == 1) + { + if (state == 1) // Connected + { + GetSemaphore(&ConSemaphore, 0); + __try {Connected(wParam);} + My__except_Routine("Connected"); + FreeSemaphore(&ConSemaphore); + } + else + { + GetSemaphore(&ConSemaphore, 0); + __try{Disconnected(wParam);} + My__except_Routine("Disconnected"); + FreeSemaphore(&ConSemaphore); + } + } + } + My__except_Routine("DoStateChange"); + + } + + return 0; + } + + + switch (message) + { + + case WM_KEYUP: + + switch (wParam) + { + case VK_F2: + CreateConsole(-1); + return 0; + + case VK_F3: + CreateMulticastConsole(); + return 0; + + case VK_F4: + CreateMonitor(); + return 0; + + case VK_TAB: + return TRUE; + + break; + + + + } + return 0; + + case WM_TIMER: + + if (wParam == 1) // Slow = 10 secs + { + __try + { + time_t NOW = time(NULL); + struct tm * tm; + RefreshMainWindow(); + CheckTimer(); + TCPTimer(); + BBSSlowTimer(); + FWDTimerProc(); + if (MaintClock < NOW) + { + while (MaintClock < NOW) // in case large time step + MaintClock += MaintInterval * 3600; + + Debugprintf("|Enter HouseKeeping"); + DoHouseKeeping(FALSE); + } + tm = gmtime(&NOW); + + if (tm->tm_wday == 0) // Sunday + { + if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) + { + CreateBBSTrafficReport(); + LastTrafficTime = NOW; + } + } + } + My__except_Routine("Slow Timer"); + } + else + __try + { + TrytoSend(); + TCPFastTimer(); + } + My__except_Routine("TrytoSend"); + + return (0); + + + 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_INITMENUPOPUP: + + if (wParam == (WPARAM)hActionMenu) + { + if (IsClipboardFormatAvailable(CF_TEXT)) + EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_ENABLED); + else + EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_GRAYED ); + + return TRUE; + } + + if (wParam == (WPARAM)hFWDMenu) + { + // Set up Forward Menu + + struct UserInfo * user; + char MenuLine[30]; + + for (user = BBSChain; user; user = user->BBSNext) + { + sprintf(MenuLine, "%s %d Msgs", user->Call, CountMessagestoForward(user)); + + if (ModifyMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, + MF_BYCOMMAND | MF_STRING, IDM_FORWARD_ALL + user->BBSNumber, MenuLine) == 0) + + AppendMenu(hFWDMenu, MF_STRING,IDM_FORWARD_ALL + user->BBSNumber, MenuLine); + } + return TRUE; + } + + if (wParam == (WPARAM)hDisMenu) + { + // Set up Disconnect Menu + + CIRCUIT * conn; + char MenuLine[30]; + int n; + + for (n = 0; n <= NumberofStreams-1; n++) + { + conn=&Connections[n]; + + RemoveMenu(hDisMenu, IDM_DISCONNECT + n, MF_BYCOMMAND); + + if (conn->Active) + { + sprintf_s(MenuLine, 30, "%d %s", conn->BPQStream, conn->Callsign); + AppendMenu(hDisMenu, MF_STRING, IDM_DISCONNECT + n, MenuLine); + } + } + return TRUE; + } + break; + + + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + // Parse the menu selections: + + if (wmEvent == LBN_DBLCLK) + + break; + + if (wmId >= IDM_DISCONNECT && wmId < IDM_DISCONNECT+MaxSockets+1) + { + // disconnect user + + conn=&Connections[wmId-IDM_DISCONNECT]; + + if (conn->Active) + { + Disconnect(conn->BPQStream); + } + } + + if (wmId >= IDM_FORWARD_ALL && wmId < IDM_FORWARD_ALL + 100) + { + StartForwarding(wmId - IDM_FORWARD_ALL, NULL); + return 0; + } + + switch (wmId) + { + case IDM_LOGBBS: + + ToggleParam(hMenu, hWnd, &LogBBS, IDM_LOGBBS); + break; + + case IDM_LOGCHAT: + + ToggleParam(hMenu, hWnd, &LogCHAT, IDM_LOGCHAT); + break; + + case IDM_LOGTCP: + + ToggleParam(hMenu, hWnd, &LogTCP, IDM_LOGTCP); + break; + + case IDM_HOUSEKEEPING: + + DoHouseKeeping(TRUE); + + break; + + case IDM_CONSOLE: + + CreateConsole(-1); + break; + + case IDM_MCMONITOR: + + CreateMulticastConsole(); + break; + + case IDM_MONITOR: + + CreateMonitor(); + break; + + case RESCANMSGS: + + ReRouteMessages(); + break; + + case IDM_IMPORT: + + ImportMessages(NULL, "", FALSE); + break; + + case IDM_ABOUT: + DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); + break; + + case ID_HELP_ONLINEHELP: + + ShellExecute(hWnd,"open", + "http://www.cantab.net/users/john.wiseman/Documents/MailServer.html", + "", NULL, SW_SHOWNORMAL); + + break; + + case IDM_CONFIG: + DialogBox(hInst, MAKEINTRESOURCE(IDD_CONFIG), hWnd, ConfigWndProc); + break; + + case IDM_USERS: + DialogBox(hInst, MAKEINTRESOURCE(IDD_USEREDIT), hWnd, UserEditDialogProc); + break; + + case IDM_FWD: + DialogBox(hInst, MAKEINTRESOURCE(IDD_FORWARDING), hWnd, FwdEditDialogProc); + break; + + case IDM_MESSAGES: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGEDIT), hWnd, MsgEditDialogProc); + break; + + case IDM_WP: + DialogBox(hInst, MAKEINTRESOURCE(IDD_EDITWP), hWnd, WPEditDialogProc); + break; + + case ID_ACTIONS_SENDMSGFROMCLIPBOARD: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, ClpMsgDialogProc); + break; + + case ID_ACTIONS_SENDMESSAGE: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, SendMsgDialogProc); + break; + + case ID_MULTICAST: + + MulticastRX = !MulticastRX; + CheckMenuItem(hActionMenu, ID_MULTICAST, (MulticastRX) ? MF_CHECKED : MF_UNCHECKED); + break; + + case IDM_EXIT: + DestroyWindow(hWnd); + break; + + + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + break; + + case WM_SIZE: + + if (wParam == SIZE_MINIMIZED) + if (cfgMinToTray) + return ShowWindow(hWnd, SW_HIDE); + + return (0); + + + case WM_SIZING: + { + LPRECT lprc = (LPRECT) lParam; + int Height = lprc->bottom-lprc->top; + int Width = lprc->right-lprc->left; + + MoveWindow(hWndSess, 0, 30, SessWidth, Height - 100, TRUE); + + return TRUE; + } + + + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + // TODO: Add any drawing code here... + EndPaint(hWnd, &ps); + break; + + case WM_DESTROY: + + GetWindowRect(MainWnd, &MainRect); // For save soutine + if (ConsHeader[0]->hConsole) + GetWindowRect(ConsHeader[0]->hConsole, &ConsHeader[0]->ConsoleRect); // For save soutine + if (ConsHeader[1]->hConsole) + GetWindowRect(ConsHeader[1]->hConsole, &ConsHeader[1]->ConsoleRect); // For save soutine + if (hMonitor) + GetWindowRect(hMonitor, &MonitorRect); // For save soutine + + KillTimer(hWnd,1); + KillTimer(hWnd,2); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "B"); + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "P"); + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "T"); + + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_SETCURSEL, 0, 0); + + return TRUE; + + case WM_SIZING: + { + HWND hWndEdit = GetDlgItem(hDlg, IDC_EDIT1); + + LPRECT lprc = (LPRECT) lParam; + int Height = lprc->bottom-lprc->top; + int Width = lprc->right-lprc->left; + + MoveWindow(hWndEdit, 5, 90, Width-20, Height - 140, TRUE); + + return TRUE; + } + + case WM_COMMAND: + + if (LOWORD(wParam) == IDSEND) + { + char status [3]; + struct MsgInfo * Msg; + char * via = NULL; + char BID[13]; + char FileList[32768]; + BIDRec * BIDRec; + int MsgLen; + char * MailBuffer; + char MsgFile[MAX_PATH]; + HANDLE hFile = INVALID_HANDLE_VALUE; + int WriteLen=0; + char HDest[61]; + char Destcopy[61]; + char * Vptr; + char * FileName[100]; + int FileLen[100]; + char * FileBody[100]; + int n, Files = 0; + int TotalFileSize = 0; + char * NewMsg; + + GetDlgItemText(hDlg, IDC_MSGTO, HDest, 60); + strcpy(Destcopy, HDest); + + GetDlgItemText(hDlg, IDC_MSGBID, BID, 13); + strlop(BID, ' '); + + GetDlgItemText(hDlg, IDC_ATTACHMENTS, FileList, 32767); + + // if there are attachments, check that they can be opened ane read + + n = 0; + + if (FileList[0]) + { + FILE * Handle; + struct stat STAT; + char * ptr1 = FileList, * ptr2; + + while(ptr1 && ptr1[0]) + { + ptr2 = strchr(ptr1, ';'); + + if (ptr2) + *(ptr2++) = 0; + + FileName[n++] = ptr1; + + ptr1 = ptr2; + } + + FileName[n] = 0; + + // read the files + + Files = n; + n = 0; + + while (FileName[n]) + { + if (stat(FileName[n], &STAT) == -1) + { + char ErrorMessage[512]; + sprintf(ErrorMessage,"Can't find file %s", FileName[n]); + MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); + return TRUE; + } + + FileLen[n] = STAT.st_size; + + Handle = fopen(FileName[n], "rb"); + + if (Handle == NULL) + { + char ErrorMessage[512]; + sprintf(ErrorMessage,"Can't open file %s", FileName[n]); + MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); + return TRUE; + } + + FileBody[n] = malloc(FileLen[n]+1); + + fread(FileBody[n], 1, FileLen[n], Handle); + + fclose(Handle); + + TotalFileSize += FileLen[n]; + n++; + } + } + + if (strlen(HDest) == 0) + { + MessageBox(NULL, "To: Call Missing!", "BPQMail", MB_ICONERROR); + return TRUE; + } + + if (strlen(BID)) + { + if (LookupBID(BID)) + { + // Duplicate bid + + MessageBox(NULL, "Duplicate BID", "BPQMail", MB_ICONERROR); + return TRUE; + } + } + + Msg = AllocateMsgRecord(); + + // Set number here so they remain in sequence + + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + strcpy(Msg->from, SYSOPCall); + + Vptr = strlop(Destcopy, '@'); + + if (Vptr == 0 && strchr(Destcopy, '!')) // Bang route without @ + { + Vptr = strchr(Destcopy, '!'); + strcpy(Msg->via, Vptr); + strlop(Destcopy, '!'); + + if (strlen(Destcopy) > 6) + memcpy(Msg->to, Destcopy, 6); + else + strcpy(Msg->to, Destcopy); + goto gotAddr; + } + + if (strlen(Destcopy) > 6) + memcpy(Msg->to, Destcopy, 6); + else + strcpy(Msg->to, Destcopy); + + _strupr(Msg->to); + + if (_memicmp(HDest, "rms:", 4) == 0 || _memicmp(HDest, "rms/", 4) == 0) + { + Vptr = HDest; + memmove(HDest, &HDest[4], strlen(HDest)); + strcpy(Msg->to, "RMS"); + + } + else if (_memicmp(HDest, "smtp:", 5) == 0) + { + if (ISP_Gateway_Enabled) + { + Vptr = HDest; + memmove(HDest, &HDest[5], strlen(HDest)); + Msg->to[0] = 0; + } + } + else if (Vptr) + { + // If looks like a valid email address, treat as such + + int tolen = (Vptr - Destcopy) - 1; + + if (tolen > 6 || !CheckifPacket(Vptr)) + { + // Assume Email address + + Vptr = HDest; + + if (FindRMS() || strchr(Vptr, '!')) // have RMS or source route + strcpy(Msg->to, "RMS"); + else if (ISP_Gateway_Enabled) + Msg->to[0] = 0; + else + { + MessageBox(NULL, "Sending to Internet Email not available", "BPQMail", MB_ICONERROR); + return TRUE; + } + } + } + if (Vptr) + { + if (strlen(Vptr) > 40) + Vptr[40] = 0; + + strcpy(Msg->via, Vptr); + } +gotAddr: + GetDlgItemText(hDlg, IDC_MSGTITLE, Msg->title, 61); + GetDlgItemText(hDlg, IDC_MSGTYPE, status, 2); + Msg->type = status[0]; + Msg->status = 'N'; + + if (strlen(BID) == 0) + sprintf_s(BID, sizeof(BID), "%d_%s", LatestMsg, BBSName); + + strcpy(Msg->bid, BID); + + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + MsgLen = SendDlgItemMessage(hDlg, IDC_EDIT1, WM_GETTEXTLENGTH, 0 ,0); + + MailBuffer = malloc(MsgLen + TotalFileSize + 2000); // Allow for a B2 Header if attachments + + if (Files) + { + char DateString[80]; + struct tm * tm; + + char Type[16] = "Private"; + + // Get Type + + if (Msg->type == 'B') + strcpy(Type, "Bulletin"); + else if (Msg->type == 'T') + strcpy(Type, "Traffic"); + + // Create a B2 Message + + // B2 Header + + NewMsg = MailBuffer + 1000; + + tm = gmtime((time_t *)&Msg->datecreated); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + // Remove last Source Route + + if (strchr(HDest, '!')) + { + char * bang = HDest + strlen(HDest); + + while (*(--bang) != '!'); // Find last ! + + *(bang) = 0; // remove it; + } + + NewMsg += sprintf(NewMsg, + "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\nTo: %s\r\nSubject: %s\r\nMbo: %s\r\n", + Msg->bid, DateString, Type, Msg->from, HDest, Msg->title, BBSName); + + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", MsgLen); + + for (n = 0; n < Files; n++) + { + char * p = FileName[n], * q; + + // Remove any path + + q = strchr(p, '\\'); + + while (q) + { + if (q) + *q++ = 0; + p = q; + q = strchr(p, '\\'); + } + + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[n], p); + } + + NewMsg += sprintf(NewMsg, "\r\n"); + GetDlgItemText(hDlg, IDC_EDIT1, NewMsg, MsgLen+1); + NewMsg += MsgLen; + NewMsg += sprintf(NewMsg, "\r\n"); + + for (n = 0; n < Files; n++) + { + memcpy(NewMsg, FileBody[n], FileLen[n]); + NewMsg += FileLen[n]; + free(FileBody[n]); + NewMsg += sprintf(NewMsg, "\r\n"); + } + + Msg->length = NewMsg - (MailBuffer + 1000); + NewMsg = MailBuffer + 1000; + Msg->B2Flags = B2Msg | Attachments; + } + + else + { + GetDlgItemText(hDlg, IDC_EDIT1, MailBuffer, MsgLen+1); + Msg->length = MsgLen; + NewMsg = MailBuffer; + } + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = CreateFile(MsgFile, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + WriteFile(hFile, NewMsg, Msg->length, &WriteLen, NULL); + CloseHandle(hFile); + } + + free(MailBuffer); + + MatchMessagetoBBSList(Msg, 0); + + BuildNNTPList(Msg); // Build NNTP Groups list + + SaveMessageDatabase(); + SaveBIDDatabase(); + + EndDialog(hDlg, LOWORD(wParam)); + + return TRUE; + } + + + if (LOWORD(wParam) == IDSelectFiles) + { + char FileNames[2048]; + char FullFileNames[32768]; + OPENFILENAME Ofn; + int err; + + FileNames[0] = 0; + + memset(&Ofn, 0, sizeof(Ofn)); + + Ofn.lStructSize = sizeof(OPENFILENAME); + Ofn.hInstance = hInst; + Ofn.hwndOwner = hDlg; + Ofn.lpstrFilter = NULL; + Ofn.lpstrFile= FileNames; + Ofn.nMaxFile = 2048; + Ofn.lpstrFileTitle = NULL; + Ofn.nMaxFileTitle = 0; + Ofn.lpstrInitialDir = (LPSTR)NULL; + Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; + Ofn.lpstrTitle = NULL;//; + + if (GetOpenFileName(&Ofn)) + { + // if one is selected, a single string is returned, if more than one, a single + // path, followed by all the strings, duuble null terminated. + + char * Names[101]; // Allow up to 100 names + int n = 0; + char * ptr = FileNames; + + while (*ptr) + { + Names[n++] = ptr; + ptr += strlen(ptr); + ptr++; + } + + GetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames, 32768); + + if (strlen(FullFileNames)) + strcat(FullFileNames, ";"); + + if (n == 1) + { + // Single Select + + strcat(FullFileNames, FileNames); + } + else + { + int i = 1; + + while(i < n) + { + strcat(FullFileNames, Names[0]); + strcat(FullFileNames, "\\"); + strcat(FullFileNames, Names[i]); + i++; + if (i < n) + strcat(FullFileNames, ";"); + } + } + SetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames); + } + else + err = GetLastError(); + return (INT_PTR)TRUE; + } + + + if (LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + + return (INT_PTR)TRUE; + + break; + } + return (INT_PTR)FALSE; +} + +INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + HGLOBAL hglb; + LPTSTR lptstr; + + switch (message) + { + case WM_INITDIALOG: + + SetWindowText(hDlg, "Send Message from Clipboard"); + + if (!IsClipboardFormatAvailable(CF_TEXT)) + break; + + if (!OpenClipboard(hDlg)) + break; + + hglb = GetClipboardData(CF_TEXT); + + if (hglb != NULL) + { + lptstr = GlobalLock(hglb); + + if (lptstr != NULL) + { + SetDlgItemText(hDlg, IDC_EDIT1, lptstr); + GlobalUnlock(hglb); + } + } + CloseClipboard(); + } + + return SendMsgDialogProc(hDlg, message, wParam, lParam); + +} + +// Message handler for about box. +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + return (INT_PTR)TRUE; + + break; + } + return (INT_PTR)FALSE; +} + +SMTPMsgs = 0; + +int RefreshMainWindow() +{ + char msg[80]; + CIRCUIT * conn; + int i,n, SYSOPMsgs = 0, HeldMsgs = 0; + time_t now; + struct tm * tm; + char tim[20]; + + SendDlgItemMessage(MainWnd,100,LB_RESETCONTENT,0,0); + + SMTPMsgs = 0; + + for (n = 0; n < NumberofStreams; n++) + { + conn=&Connections[n]; + + if (!conn->Active) + { + strcpy(msg,"Idle"); + } + else + { + { + if (conn->UserPointer == 0) + strcpy(msg,"Logging in"); + else + { + i=sprintf_s(msg, sizeof(msg), "%-10s %-10s %2d %-10s%5d", + conn->UserPointer->Name, conn->UserPointer->Call, conn->BPQStream, + "BBS", conn->OutputQueueLength - conn->OutputGetPointer); + } + } + } + SendDlgItemMessage(MainWnd,100,LB_ADDSTRING,0,(LPARAM)msg); + } + + SetDlgItemInt(hWnd, IDC_MSGS, NumberofMessages, FALSE); + + n = 0; + + for (i=1; i <= NumberofMessages; i++) + { + if (MsgHddrPtr[i]->status == 'N') + { + if (_stricmp(MsgHddrPtr[i]->to, SYSOPCall) == 0 || _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0) + SYSOPMsgs++; + else + if (MsgHddrPtr[i]->to[0] == 0) + SMTPMsgs++; + } + else + { + if (MsgHddrPtr[i]->status == 'H') + HeldMsgs++; + } + } + + SetDlgItemInt(hWnd, IDC_SYSOPMSGS, SYSOPMsgs, FALSE); + SetDlgItemInt(hWnd, IDC_HELD, HeldMsgs, FALSE); + SetDlgItemInt(hWnd, IDC_SMTP, SMTPMsgs, FALSE); + + SetDlgItemInt(hWnd, IDC_MSGSEM, MsgNoSemaphore.Clashes, FALSE); + SetDlgItemInt(hWnd, IDC_ALLOCSEM, AllocSemaphore.Clashes, FALSE); + SetDlgItemInt(hWnd, IDC_CONSEM, ConSemaphore.Clashes, FALSE); + + now = time(NULL); + + tm = gmtime(&now); + sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); + SetDlgItemText(hWnd, IDC_UTC, tim); + + tm = localtime(&now); + sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); + SetDlgItemText(hWnd, IDC_LOCAL, tim); + + + return 0; +} + +#define MAX_PENDING_CONNECTS 4 + +#define VERSION_MAJOR 2 +#define VERSION_MINOR 0 + +SOCKADDR_IN local_sin; /* Local socket - internet style */ + +PSOCKADDR_IN psin; + +SOCKET sock; + + + +BOOL Initialise() +{ + int i, len; + ConnectionInfo * conn; + struct UserInfo * user = NULL; + HKEY hKey=0; + char * ptr1; + int Attrs, ret; + char msg[500]; + TIME_ZONE_INFORMATION TimeZoneInformation; + struct stat STAT; + + GetTimeZoneInformation(&TimeZoneInformation); + + _tzset(); + _MYTIMEZONE = timezone; + _MYTIMEZONE = TimeZoneInformation.Bias * 60; + + // Register message for posting by BPQDLL + + BPQMsg = RegisterWindowMessage(BPQWinMsg); + + // See if we need to warn of possible problem with BaseDir moved by installer + + strcpy(BPQDirectory, GetBPQDirectory()); + + sprintf(BaseDir, "%s/BPQMailChat", BPQDirectory); + + len = strlen(BaseDir); + ptr1 = BaseDir; + + while (*ptr1) + { + if (*(ptr1) == '/') *(ptr1) = '\\'; + ptr1++; + } + + // Make Sure BASEDIR Exists + + Attrs = GetFileAttributes(BaseDir); + + if (Attrs == -1) + { + sprintf_s(msg, sizeof(msg), "Base Directory %s not found - should it be created?", BaseDir); + ret = MessageBox(NULL, msg, "BPQMail", MB_YESNO); + + if (ret == IDYES) + { + ret = CreateDirectory(BaseDir, NULL); + if (ret == 0) + { + MessageBox(NULL, "Failed to created Base Directory - exiting", "BPQMail", MB_ICONSTOP); + return FALSE; + } + } + else + { + MessageBox(NULL, "Can't Continue without a Base Directory - exiting", "BPQMailChat", MB_ICONSTOP); + return FALSE; + } + } + else + { + if (!(Attrs & FILE_ATTRIBUTE_DIRECTORY)) + { + sprintf_s(msg, sizeof(msg), "Base Directory %s is a file not a directory - exiting", BaseDir); + ret = MessageBox(NULL, msg, "BPQMail", MB_ICONSTOP); + + return FALSE; + } + } + + initUTF8(); + + // 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"); + + CreateDirectory(MailDir, NULL); // Just in case + + strcpy(ConfigName, BaseDir); + strcat(ConfigName, "\\"); + strcat(ConfigName, "BPQMail.cfg"); + + UsingingRegConfig = FALSE; + + // if config file exists use it else try to get from Registry + + if (stat(ConfigName, &STAT) == -1) + { + UsingingRegConfig = TRUE; + + if (GetConfigFromRegistry()) + { + SaveConfig(ConfigName); + } + else + { + int retCode; + + strcpy(BBSName, GetNodeCall()); + strlop(BBSName, '-'); + strlop(BBSName, ' '); + + sprintf(msg, "No configuration found - Dummy Config created"); + + retCode = MessageBox(NULL, msg, "BPQMailChat", MB_OKCANCEL); + + if (retCode == IDCANCEL) + return FALSE; + + SaveConfig(ConfigName); + } + } + + if (GetConfig(ConfigName) == EXIT_FAILURE) + { + ret = MessageBox(NULL, + "BBS Config File seems corrupt - check before continuing", "BPQMail", MB_ICONSTOP); + return FALSE; + } + + // Got a Config File + + if (MainRect.right < 100 || MainRect.bottom < 100) + { + GetWindowRect(MainWnd, &MainRect); + } + + MoveWindow(MainWnd, MainRect.left, MainRect.top, MainRect.right-MainRect.left, MainRect.bottom-MainRect.top, TRUE); + + if (OpenMon) + CreateMonitor(); + + BBSApplMask = 1<<(BBSApplNum-1); + + ShowWindow(GetDlgItem(MainWnd, 901), SW_HIDE); + ShowWindow(GetDlgItem(MainWnd, 902), SW_HIDE); + ShowWindow(GetDlgItem(MainWnd, 903), SW_HIDE); + + // Make backup copies of Databases + + CopyBIDDatabase(); + CopyMessageDatabase(); + CopyUserDatabase(); + CopyWPDatabase(); + + SetupMyHA(); + SetupFwdAliases(); + SetupNTSAliases(NTSAliasesPath); + + GetWPDatabase(); + GetMessageDatabase(); + GetUserDatabase(); + GetBIDDatabase(); + GetBadWordFile(); + GetHTMLForms(); + + UsingingRegConfig = FALSE; + + // Make sure SYSOPCALL is set + + if (SYSOPCall[0] == 0) + strcpy(SYSOPCall, BBSName); + + // 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(); + } + + // Allocate Streams + + 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 | ChatApplMask); + Disconnect(conn->BPQStream); + } + + InitialiseTCP(); + + InitialiseNNTP(); + + SetupListenSet(); // Master set of listening sockets + + if (BBSApplNum) + { + SetupUIInterface(); + if (MailForInterval) + _beginthread(SendMailForThread, 0, 0); + } + + if (cfgMinToTray) + { + AddTrayMenuItem(MainWnd, "Mail Server"); + } + + SetTimer(hWnd,1,10000,NULL); // Slow Timer (10 Secs) + SetTimer(hWnd,2,100,NULL); // Send to Node and TCP Poll (100 ms) + + // 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 = _mkgmtime(tm); + + 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); + } + } + } + + if (strstr(CmdLine, "tidymail")) + DeleteRedundantMessages(); + + if (strstr(CmdLine, "nohomebbs")) + DontNeedHomeBBS = TRUE; + + if (strstr(CmdLine, "DontCheckFromCall")) + DontCheckFromCall = TRUE; + + CheckMenuItem(hMenu,IDM_LOGBBS, (LogBBS) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hMenu,IDM_LOGTCP, (LogTCP) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hMenu,IDM_LOGCHAT, (LogCHAT) ? MF_CHECKED : MF_UNCHECKED); + + RefreshMainWindow(); + +// CreateWPReport(); + + CreatePipeThread(); + + return TRUE; +} + +int ConnectState(Stream) +{ + int state; + + SessionStateNoAck(Stream, &state); + return state; +} +UCHAR * EncodeCall(UCHAR * Call) +{ + static char axcall[10]; + + ConvToAX25(Call, axcall); + return &axcall[0]; + +} + +/* +VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo) +{ + struct UserInfo * user; + + int i = FWDInfo->UserIndex; + + if (i == -1) + { + FWDInfo->UserIndex = FWDInfo->UserCall[0] = 0; // Not scanning users + } + + for (i++; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_POLLRMS) + { + FWDInfo->UserIndex = i; + strcpy(FWDInfo->UserCall, user->Call); + FWDInfo->FwdTimer = FWDInfo->FwdInterval - 20; + return ; + } + } + + // Finished Scan + + FWDInfo->UserIndex = FWDInfo->FwdTimer = FWDInfo->UserCall[0] = 0; +} +*/ + +#ifndef NEWROUTING + +VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo) +{ +} +VOID SetupMyHA() +{ +} +VOID SetupFwdAliases() +{ +} + +int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn) +{ + struct UserInfo * bbs; + struct BBSForwardingInfo * ForwardingInfo; + char ATBBS[41]; + char * HRoute; + int Count =0; + + strcpy(ATBBS, Msg->via); + HRoute = strlop(ATBBS, '.'); + + if (Msg->type == 'P') + { + // P messages are only sent to one BBS, but check the TO and AT of all BBSs before routing on HA + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSToList(Msg, bbs, ForwardingInfo)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSAtList(Msg, ForwardingInfo, ATBBS)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSHList(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + return FALSE; + } + + // Bulls go to all matching BBSs, so the order of checking doesn't matter + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckABBS(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + Count++; + } + } + + return Count; +} +BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) +{ + char ** Calls; + char ** HRoutes; + int i, j; + + if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS + return TRUE; + + // Check TO distributions + + if (ForwardingInfo->TOCalls) + { + Calls = ForwardingInfo->TOCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], Msg->to) == 0) + return TRUE; + + Calls++; + } + } + + // Check AT distributions + + if (ForwardingInfo->ATCalls) + { + Calls = ForwardingInfo->ATCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + if ((HRoute) && (ForwardingInfo->Haddresses)) + { + // Match on Routes + + HRoutes = ForwardingInfo->Haddresses; + + while(HRoutes[0]) + { + i = strlen(HRoutes[0]) - 1; + j = strlen(HRoute) - 1; + + while ((i >= 0) && (j >= 0)) // Until one string rus out + { + if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards + goto next; + } + + return TRUE; + next: + HRoutes++; + } + } + + + return FALSE; + +} + +BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo) +{ + char ** Calls; + + // Check TO distributions + + if (ForwardingInfo->TOCalls) + { + Calls = ForwardingInfo->TOCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], Msg->to) == 0) + return TRUE; + + Calls++; + } + } + return FALSE; +} + +BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS) +{ + char ** Calls; + + // Check AT distributions + + if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS + return TRUE; + + if (ForwardingInfo->ATCalls) + { + Calls = ForwardingInfo->ATCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + return FALSE; +} + +BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) +{ + char ** HRoutes; + int i, j; + + if ((HRoute) && (ForwardingInfo->Haddresses)) + { + // Match on Routes + + HRoutes = ForwardingInfo->Haddresses; + + while(HRoutes[0]) + { + i = strlen(HRoutes[0]) - 1; + j = strlen(HRoute) - 1; + + while ((i >= 0) && (j >= 0)) // Until one string rus out + { + if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards + goto next; + } + + return TRUE; + next: + HRoutes++; + } + } + return FALSE; +} + +#endif + +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; +} diff --git a/BPQMail.vcxproj b/BPQMail.vcxproj index 2765bdf..2ebc492 100644 --- a/BPQMail.vcxproj +++ b/BPQMail.vcxproj @@ -1,346 +1,346 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {3766AA10-C777-4ED8-A83D-F1452DE9B665} - TelnetServer - Win32Proj - - - - Application - v141 - NotSet - true - - - Application - v141 - NotSet - true - - - Application - v141 - false - NotSet - - - Application - v141 - false - NotSet - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>15.0.28307.799 - - - C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ - C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ - true - - - true - - - C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ - C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ - false - - - false - - - - - - - Disabled - ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebug - true - - Level3 - EditAndContinue - CompileAsC - - - ..\Include;%(AdditionalIncludeDirectories) - - - ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) - c:\DevProgs\bpq32\BPQMail.exe - false - LIBCMT;%(IgnoreSpecificDefaultLibraries) - true - $(IntDir)$(TargetName).pdb - true - $(IntDir)BBSListings\bpqmail.map - true - Windows - MachineX86 - - - - - - - - - Disabled - ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - true - - - Level3 - ProgramDatabase - CompileAsC - - - ..\Include;%(AdditionalIncludeDirectories) - - - ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) - c:\DevProgs\bpq32\BPQMail.exe - false - LIBCMT;%(IgnoreSpecificDefaultLibraries) - true - $(IntDir)$(TargetName).pdb - true - $(IntDir)BBSListings\bpqmail.map - true - Windows - - - - - - - - - - - MaxSpeed - false - ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) - MultiThreaded - - Level3 - ProgramDatabase - CompileAsC - - - ..\Include;%(AdditionalIncludeDirectories) - - - ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) - c:\DevProgs\bpq32\BPQMail.exe - true - c:\DevProgs\bpq32\BPQMail.pdb - true - c:\DevProgs\bpq32\BPQMail.map - Windows - true - true - - MachineX86 - - - - - - - - - - - - - MaxSpeed - false - ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) - MultiThreaded - - - Level3 - ProgramDatabase - CompileAsC - - - ..\Include;%(AdditionalIncludeDirectories) - - - ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) - c:\DevProgs\bpq32\BPQMail.exe - true - c:\DevProgs\bpq32\BPQMail.pdb - true - c:\DevProgs\bpq32\BPQMail.map - Windows - true - true - - - - - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - All - All - $(IntDir) - $(IntDir) - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - All - All - $(IntDir) - $(IntDir) - $(IntDir) - $(IntDir) - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.obj - $(IntDir)%(Filename)1.xdc - $(IntDir)%(Filename)1.xdc - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3766AA10-C777-4ED8-A83D-F1452DE9B665} + TelnetServer + Win32Proj + + + + Application + v141 + NotSet + true + + + Application + v141 + NotSet + true + + + Application + v141 + false + NotSet + + + Application + v141 + false + NotSet + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>15.0.28307.799 + + + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ + true + + + true + + + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ + false + + + false + + + + + + + Disabled + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + true + + Level3 + EditAndContinue + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + false + LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + $(IntDir)BBSListings\bpqmail.map + true + Windows + MachineX86 + + + + + + + + + Disabled + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + false + LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + $(IntDir)BBSListings\bpqmail.map + true + Windows + + + + + + + + + + + MaxSpeed + false + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + MultiThreaded + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + true + c:\DevProgs\bpq32\BPQMail.pdb + true + c:\DevProgs\bpq32\BPQMail.map + Windows + true + true + + MachineX86 + + + + + + + + + + + + + MaxSpeed + false + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + true + c:\DevProgs\bpq32\BPQMail.pdb + true + c:\DevProgs\bpq32\BPQMail.map + Windows + true + true + + + + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + All + All + $(IntDir) + $(IntDir) + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + All + All + $(IntDir) + $(IntDir) + $(IntDir) + $(IntDir) + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + + + + + + + \ No newline at end of file diff --git a/BPQMail.vcxproj.filters b/BPQMail.vcxproj.filters index 982128f..8025626 100644 --- a/BPQMail.vcxproj.filters +++ b/BPQMail.vcxproj.filters @@ -1,113 +1,113 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - - - Resource Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + \ No newline at end of file diff --git a/BPQMail.vcxproj.user b/BPQMail.vcxproj.user index be25078..6e2aec7 100644 --- a/BPQMail.vcxproj.user +++ b/BPQMail.vcxproj.user @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/Bpq32.c b/Bpq32.c index 228dabf..19c6288 100644 --- a/Bpq32.c +++ b/Bpq32.c @@ -1115,7 +1115,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // 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) -// Fixed to KISSHF driver (23) +// 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) @@ -1173,7 +1173,10 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // 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 +// 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) #define CKernel diff --git a/CBPQ32.vcproj b/CBPQ32.vcproj index f8e9834..f1a0357 100644 --- a/CBPQ32.vcproj +++ b/CBPQ32.vcproj @@ -321,10 +321,26 @@ + + + + + + + + + + + + + + + + diff --git a/HTTPcode.c b/HTTPcode.c index 7260c8e..daeb195 100644 --- a/HTTPcode.c +++ b/HTTPcode.c @@ -1645,7 +1645,7 @@ int InnerProcessHTTPMessage(struct ConnectionInfo * conn) HostPtr = strstr(MsgPtr, "Host: "); - WebSock = strstr(MsgPtr, "Upgrade"); + WebSock = strstr(MsgPtr, "Upgrade: websocket"); if (HostPtr) { diff --git a/HTTPcode.c.bak b/HTTPcode.c.bak new file mode 100644 index 0000000..209a80f --- /dev/null +++ b/HTTPcode.c.bak @@ -0,0 +1,4849 @@ +/* +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 "zlib.h" + +#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); + +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; + +char * strlop(char * buf, char delim); +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); +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 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;;) + { + 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[1024]; + int ReplyLen; + char Header[256]; + int HeaderLen; + int State; + struct HTTPConnectionInfo * Session = FindSession(Key); + int Stream; + + 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; + + struct TNCINFO * TNC = Session->TNC; + struct TCPINFO * TCP = 0; + + if (TNC) + TCP = TNC->TCPInfo; + + 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, "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, "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 (BPQDirectory[0] == 0) + { + strcpy(inputname, "bpq32.cfg"); + } + else + { + strcpy(inputname,BPQDirectory); + 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, * 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 WIN32 + + 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"); + + 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, ' '); + + + // 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) + { + 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; + } + + // Add tail + + strcpy(&Reply[ReplyLen], Tail); + ReplyLen += strlen(Tail); + + // compress if allowed + + if (allowDeflate) + Compressed = Compressit(Reply, 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; + } + } + +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; + + ReplyLen = 0; + + if (Session == 0) + Session = &Dummy; + + Session->TNC = (void *)LOCAL; // TNC only used for Web Terminal Sessions + + ProcessMailHTTPMessage(Session, Method, Context, MsgPtr, _REPLYBUFFER, &ReplyLen, MsgLen); + + 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; + } + + // 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 + + 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[1]); + PORT = GetPortTableEntryFromPortNum(Port); // Need slot not number + if (PORT) + Slot = PORT->PortSlot; + + GetParam(input, "Every", &Param[0]); + Interval[Slot] = atoi(&Param[1]); + + GetParam(input, "Dest", &Param[0]); + _strupr(Param); + strcpy(UIUIDEST[Slot], &Param[1]); + + GetParam(input, "Path", &Param[0]); + _strupr(Param); + if (UIUIDigi[Slot]) + free(UIUIDigi[Slot]); + UIUIDigi[Slot] = _strdup(&Param[1]); + + GetParam(input, "File", &Param[0]); + strcpy(FN[Slot], &Param[1]); + GetParam(input, "Text", &Param[0]); + strcpy(Message[Slot], &Param[1]); + + 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, "/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 (BPQDirectory[0] == 0) + { + strcpy(inputname, "bpq32.cfg"); + } + else + { + strcpy(inputname,BPQDirectory); + 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]; + + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], "%s", PortsHddr); + + for (count = 1; count <= NUMBEROFPORTS; count++) + { + Port = GetPortTableEntryFromSlot(count); + ExtPort = (struct _EXTPORTDATA *)Port; + + 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); + else + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortLineWithDriver, Port->PORTNUMBER, DLL, + Port->PORTDESCRIPTION, Port->PORTNUMBER, Port->TNC->WebWinX, Port->TNC->WebWinY, 200, 200); + + 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); + else + ReplyLen += sprintf(&_REPLYBUFFER[ReplyLen], PortLineWithBeacon, Port->PORTNUMBER, Port->PORTNUMBER, + DLL, DLL, Port->PORTDESCRIPTION, Port->PORTNUMBER); + } + + 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 Window
%d %s%s
%d %s%s Beacons
%d%s%s
%d%s%s Driver Window
%d%s%s BeaconsDriver Window
%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 WIN32 + } +#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 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/KernelScript1.rc b/KernelScript1.rc index 6a77e46..55876e7 100644 --- a/KernelScript1.rc +++ b/KernelScript1.rc @@ -215,7 +215,7 @@ BEGIN MENUITEM "Strip Linefeeds", BPQStripLF MENUITEM "Log Output", BPQLogOutput MENUITEM "Send Disconnected", BPQSendDisconnected - MENUITEM "Chat Terminal Mode", CHATTERM + MENUITEM "Chat Terminal Mode (Send Keppalives)", CHATTERM MENUITEM "Restore Windows on load", ID_WINDOWS_RESTORE MENUITEM "Beep if input too long", ID_WARNWRAP MENUITEM "Wrap Input", ID_WRAP diff --git a/LinBPQ.c b/LinBPQ.c index 0ca3dc5..ba6d20f 100644 --- a/LinBPQ.c +++ b/LinBPQ.c @@ -749,7 +749,10 @@ int main(int argc, char * argv[]) // Disable Console Terminal if stdout redirected - if (!isatty(STDOUT_FILENO)) +// printf("STDOUT %d\n",isatty(STDOUT_FILENO)); +// printf("STDIN %d\n",isatty(STDIN_FILENO)); + + if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) Redirected = 1; #endif diff --git a/LinBPQ.c~ b/LinBPQ.c~ index 6cb346e..d4dddf4 100644 --- a/LinBPQ.c~ +++ b/LinBPQ.c~ @@ -1,1975 +1,1975 @@ -/* -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 -#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(); - -void GetSemaphore(struct SEM * Semaphore, int ID); -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(); - -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 */ - - -UCHAR BPQDirectory[260]; -UCHAR LogDirectory[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 - -// Linux Signal Handlers - -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", - Semaphore.SemProcessID, SemHeldByAPI); - - 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 -//#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(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; - -} - - -int Redirected = 0; - -int main(int argc, char * argv[]) -{ - int i; - struct UserInfo * user = NULL; - ConnectionInfo * conn; - struct stat STAT; - PEXTPORTDATA PORTVEC; - UCHAR LogDir[260]; - -#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 - 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 - - if (!isatty(STDOUT_FILENO)) - Redirected = 1; - -#endif - - printf("G8BPQ AX25 Packet Switch System Version %s %s\n", TextVerstring, Datestring); - printf("%s\n", VerCopyright); - - if (argc > 1 && _stricmp(argv[1], "-v") == 0) - 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); - GetCurrentDirectory(256, LogDirectory); -#else - getcwd(BPQDirectory, 256); - getcwd(LogDirectory, 256); -#endif - Consoleprintf("Current Directory is %s\n", BPQDirectory); - - for (i = 1; i < argc; i++) - { - if (_memicmp(argv[i], "logdir=", 7) == 0) - { - strcpy(LogDirectory, &argv[i][7]); - break; - } - } - - - // Make sure logs directory exists - - sprintf(LogDir, "%s/logs", LogDirectory); - -#ifdef WIN32 - CreateDirectory(LogDir, NULL); -#else - mkdir(LogDir, S_IRWXU | S_IRWXG | S_IRWXO); - 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 = 1; 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 = 1; 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(); - - // 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"); - - Debugprintf("POP3 Debug Before Init TCP Timer = %d", POP3Timer); - - InitialiseTCP(); - Debugprintf("POP3 Debug Before Init NNTP Timer = %d", POP3Timer); - 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 = 1; 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"); - - } - } - - Debugprintf("POP3 Debug After Mail Init Timer = %d", POP3Timer); - - if (NUMBEROFTNCPORTS) - InitializeTNCEmulator(); - - AGWActive = AGWAPIInit(); - -#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(); - - 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 > 100) // 10 secs - { - time_t NOW = time(NULL); - struct tm * tm; - - TCPTimer(); - FWDTimerProc(); - BBSSlowTimer(); - - if (MaintClock < NOW) - { - while (MaintClock < NOW) // in case large time step - MaintClock += MaintInterval * 3600; - - Debugprintf("|Enter HouseKeeping"); - DoHouseKeeping(FALSE); - } - - tm = gmtime(&NOW); - - if (tm->tm_wday == 0) // Sunday - { - if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) - { - LastTrafficTime = NOW; - CreateBBSTrafficReport(); - } - } - } - 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 - -int 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 - - - - - +/* +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 +#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(); + +void GetSemaphore(struct SEM * Semaphore, int ID); +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(); + +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 */ + + +UCHAR BPQDirectory[260]; +UCHAR LogDirectory[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 + +// Linux Signal Handlers + +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", + Semaphore.SemProcessID, SemHeldByAPI); + + 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 +//#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(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; + +} + + +int Redirected = 0; + +int main(int argc, char * argv[]) +{ + int i; + struct UserInfo * user = NULL; + ConnectionInfo * conn; + struct stat STAT; + PEXTPORTDATA PORTVEC; + UCHAR LogDir[260]; + +#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 + 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 + + if (!isatty(STDOUT_FILENO)) + Redirected = 1; + +#endif + + printf("G8BPQ AX25 Packet Switch System Version %s %s\n", TextVerstring, Datestring); + printf("%s\n", VerCopyright); + + if (argc > 1 && _stricmp(argv[1], "-v") == 0) + 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); + GetCurrentDirectory(256, LogDirectory); +#else + getcwd(BPQDirectory, 256); + getcwd(LogDirectory, 256); +#endif + Consoleprintf("Current Directory is %s\n", BPQDirectory); + + for (i = 1; i < argc; i++) + { + if (_memicmp(argv[i], "logdir=", 7) == 0) + { + strcpy(LogDirectory, &argv[i][7]); + break; + } + } + + + // Make sure logs directory exists + + sprintf(LogDir, "%s/logs", LogDirectory); + +#ifdef WIN32 + CreateDirectory(LogDir, NULL); +#else + mkdir(LogDir, S_IRWXU | S_IRWXG | S_IRWXO); + 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 = 1; 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 = 1; 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(); + + // 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"); + + Debugprintf("POP3 Debug Before Init TCP Timer = %d", POP3Timer); + + InitialiseTCP(); + Debugprintf("POP3 Debug Before Init NNTP Timer = %d", POP3Timer); + 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 = 1; 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"); + + } + } + + Debugprintf("POP3 Debug After Mail Init Timer = %d", POP3Timer); + + if (NUMBEROFTNCPORTS) + InitializeTNCEmulator(); + + AGWActive = AGWAPIInit(); + +#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(); + + 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 > 100) // 10 secs + { + time_t NOW = time(NULL); + struct tm * tm; + + TCPTimer(); + FWDTimerProc(); + BBSSlowTimer(); + + if (MaintClock < NOW) + { + while (MaintClock < NOW) // in case large time step + MaintClock += MaintInterval * 3600; + + Debugprintf("|Enter HouseKeeping"); + DoHouseKeeping(FALSE); + } + + tm = gmtime(&NOW); + + if (tm->tm_wday == 0) // Sunday + { + if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) + { + LastTrafficTime = NOW; + CreateBBSTrafficReport(); + } + } + } + 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 + +int 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/MCP2221.c b/MCP2221.c new file mode 100644 index 0000000..233888a --- /dev/null +++ b/MCP2221.c @@ -0,0 +1,427 @@ +// MCP2221.cpp : This file contains the 'main' function. Program execution begins and ends there. +// + +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "time.h" +#include "winstdint.h" + +#include "hidapi.h" +void DecodeCM108(int Port, char * ptr); + +#ifdef WIN32 + +/* Simple Raw HID functions for Windows - for use with Teensy RawHID example + * http://www.pjrc.com/teensy/rawhid.html + * Copyright (c) 2009 PJRC.COM, LLC + * + * rawhid_open - open 1 or more devices + * rawhid_recv - receive a packet + * rawhid_send - send a packet + * rawhid_close - close a device + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above description, website URL and copyright notice and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Version 1.0: Initial Release + */ + +#include +#include +//#include +#include +#include +//#include +//#include + +typedef USHORT USAGE; + + +typedef struct _HIDD_CONFIGURATION { + PVOID cookie; + ULONG size; + ULONG RingBufferSize; +} HIDD_CONFIGURATION, *PHIDD_CONFIGURATION; + +typedef struct _HIDD_ATTRIBUTES { + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; +} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + + +typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + + +typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; + + + +// a list of all opened HID devices, so the caller can +// simply refer to them by number +typedef struct hid_struct hid_t; +static hid_t *first_hid = NULL; +static hid_t *last_hid = NULL; +struct hid_struct { + HANDLE handle; + int open; + struct hid_struct *prev; + struct hid_struct *next; +}; +static HANDLE rx_event=NULL; +static HANDLE tx_event=NULL; +static CRITICAL_SECTION rx_mutex; +static CRITICAL_SECTION tx_mutex; + + +// private functions, not intended to be used from outside this file +static void add_hid(hid_t *h); +static hid_t * get_hid(int num); +static void free_all_hid(void); +void print_win32_err(void); + + + + +// rawhid_recv - receive a packet +// Inputs: +// num = device to receive from (zero based) +// buf = buffer to receive packet +// len = buffer's size +// timeout = time to wait, in milliseconds +// Output: +// number of bytes received, or -1 on error +// +int rawhid_recv(int num, void *buf, int len, int timeout) +{ + hid_t *hid; + unsigned char tmpbuf[516]; + OVERLAPPED ov; + DWORD r; + int n; + + if (sizeof(tmpbuf) < len + 1) return -1; + hid = get_hid(num); + if (!hid || !hid->open) return -1; + EnterCriticalSection(&rx_mutex); + ResetEvent(&rx_event); + memset(&ov, 0, sizeof(ov)); + ov.hEvent = rx_event; + if (!ReadFile(hid->handle, tmpbuf, len + 1, NULL, &ov)) { + if (GetLastError() != ERROR_IO_PENDING) goto return_error; + r = WaitForSingleObject(rx_event, timeout); + if (r == WAIT_TIMEOUT) goto return_timeout; + if (r != WAIT_OBJECT_0) goto return_error; + } + if (!GetOverlappedResult(hid->handle, &ov, &n, FALSE)) goto return_error; + LeaveCriticalSection(&rx_mutex); + if (n <= 0) return -1; + n--; + if (n > len) n = len; + memcpy(buf, tmpbuf + 1, n); + return n; +return_timeout: + CancelIo(hid->handle); + LeaveCriticalSection(&rx_mutex); + return 0; +return_error: + print_win32_err(); + LeaveCriticalSection(&rx_mutex); + return -1; +} + +// rawhid_send - send a packet +// Inputs: +// num = device to transmit to (zero based) +// buf = buffer containing packet to send +// len = number of bytes to transmit +// timeout = time to wait, in milliseconds +// Output: +// number of bytes sent, or -1 on error +// +int rawhid_send(int num, void *buf, int len, int timeout) +{ + hid_t *hid; + unsigned char tmpbuf[516]; + OVERLAPPED ov; + DWORD n, r; + + if (sizeof(tmpbuf) < len + 1) return -1; + hid = get_hid(num); + if (!hid || !hid->open) return -1; + EnterCriticalSection(&tx_mutex); + ResetEvent(&tx_event); + memset(&ov, 0, sizeof(ov)); + ov.hEvent = tx_event; + tmpbuf[0] = 0; + memcpy(tmpbuf + 1, buf, len); + if (!WriteFile(hid->handle, tmpbuf, len + 1, NULL, &ov)) { + if (GetLastError() != ERROR_IO_PENDING) goto return_error; + r = WaitForSingleObject(tx_event, timeout); + if (r == WAIT_TIMEOUT) goto return_timeout; + if (r != WAIT_OBJECT_0) goto return_error; + } + if (!GetOverlappedResult(hid->handle, &ov, &n, FALSE)) goto return_error; + LeaveCriticalSection(&tx_mutex); + if (n <= 0) return -1; + return n - 1; +return_timeout: + CancelIo(hid->handle); + LeaveCriticalSection(&tx_mutex); + return 0; +return_error: + print_win32_err(); + LeaveCriticalSection(&tx_mutex); + return -1; +} + +HANDLE rawhid_open(char * Device) +{ + DWORD index=0; + HANDLE h; + hid_t *hid; + int count=0; + + if (first_hid) free_all_hid(); + + if (!rx_event) + { + rx_event = CreateEvent(NULL, TRUE, TRUE, NULL); + tx_event = CreateEvent(NULL, TRUE, TRUE, NULL); + InitializeCriticalSection(&rx_mutex); + InitializeCriticalSection(&tx_mutex); + } + h = CreateFile(Device, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + + if (h == INVALID_HANDLE_VALUE) + return 0; + + hid = (struct hid_struct *)malloc(sizeof(struct hid_struct)); + if (!hid) + { + CloseHandle(h); + return 0; + } + hid->handle = h; + hid->open = 1; + add_hid(hid); + + return h; +} + + +// rawhid_close - close a device +// +// Inputs: +// num = device to close (zero based) +// Output +// (nothing) +// +void rawhid_close(int num) +{ + hid_t *hid; + + hid = get_hid(num); + if (!hid || !hid->open) return; + + CloseHandle(hid->handle); + hid->handle = NULL; + hid->open = FALSE; +} + + + + +static void add_hid(hid_t *h) +{ + if (!first_hid || !last_hid) { + first_hid = last_hid = h; + h->next = h->prev = NULL; + return; + } + last_hid->next = h; + h->prev = last_hid; + h->next = NULL; + last_hid = h; +} + + +static hid_t * get_hid(int num) +{ + hid_t *p; + for (p = first_hid; p && num > 0; p = p->next, num--) ; + return p; +} + + +static void free_all_hid(void) +{ + hid_t *p, *q; + + for (p = first_hid; p; p = p->next) + { + CloseHandle(p->handle); + p->handle = NULL; + p->open = FALSE; + } + p = first_hid; + while (p) { + q = p; + p = p->next; + free(q); + } + first_hid = last_hid = NULL; +} + + + +void print_win32_err(void) +{ + char buf[256]; + DWORD err; + + err = GetLastError(); + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, + 0, buf, sizeof(buf), NULL); + printf("err %ld: %s\n", err, buf); +} + +#endif + +HANDLE hDevice; + +char * HIDDevice; + +int main() +{ + int Len; + unsigned char Msg[65] = ""; + + DecodeCM108(0, "0x4D8:0xDD"); + + hDevice = rawhid_open(HIDDevice); + + if (hDevice) + printf("Rigcontrol HID Device %s opened\n", HIDDevice); + + Msg[0] = 0x51; + Msg[0] = 0xB0; + Msg[1] = 0x1; + + Len = rawhid_send(0, Msg, 64, 100); + + Msg[0] = 0; + +#ifdef WIN32 + Len = rawhid_recv(0, Msg, 64, 100); +#else + Len = read(PORT->hDevice, Msg, 64); +#endif + + + return 0; +} + + +char * CM108Device = NULL; + +void DecodeCM108(int Port, char * ptr) +{ + // Called if Device Name or PTT = Param is CM108 + +#ifdef WIN32 + + // Next Param is VID and PID - 0xd8c:0x8 or Full device name + // On Windows device name is very long and difficult to find, so + // easier to use VID/PID, but allow device in case more than one needed + + char * next; + int32_t VID = 0, PID = 0; + char product[256]; + char sernum[256] = "NULL"; + + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + if (strlen(ptr) > 16) + CM108Device = _strdup(ptr); + else + { + VID = strtol(ptr, &next, 0); + if (next) + PID = strtol(++next, &next, 0); + + // Look for Device + + devs = hid_enumerate(0, 0); // so we list devices(USHORT)VID, (USHORT)PID); + cur_dev = devs; + while (cur_dev) + { + wcstombs(product, cur_dev->product_string, 255); + if (cur_dev->serial_number) + wcstombs(sernum, cur_dev->serial_number, 255); + + if (product) + printf("HID Device %s VID %X PID %X Ser %s %s\n", product, cur_dev->vendor_id, cur_dev->product_id, sernum, cur_dev->path); + else + printf("HID Device %s VID %X PID %X Ser %s %s", "Missing Product\n", cur_dev->vendor_id, cur_dev->product_id, sernum, cur_dev->path); + + if (cur_dev->vendor_id == VID && cur_dev->product_id == PID) + path_to_open = cur_dev->path; + + cur_dev = cur_dev->next; + } + + if (path_to_open) + { + HIDDevice = _strdup(path_to_open); + } + hid_free_enumeration(devs); + } +#else + + // Linux - Next Param HID Device, eg /dev/hidraw0 + + CM108Device = _strdup(ptr); +#endif + } + + diff --git a/MCP2221.vcproj b/MCP2221.vcproj new file mode 100644 index 0000000..d2c9621 --- /dev/null +++ b/MCP2221.vcproj @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MCP2221.vcproj.DESKTOP-TGEL8RC.John.user b/MCP2221.vcproj.DESKTOP-TGEL8RC.John.user new file mode 100644 index 0000000..40182c4 --- /dev/null +++ b/MCP2221.vcproj.DESKTOP-TGEL8RC.John.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/MailNode.vcxproj.filters b/MailNode.vcxproj.filters index 23094c6..9f953ec 100644 --- a/MailNode.vcxproj.filters +++ b/MailNode.vcxproj.filters @@ -1,324 +1,324 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + \ No newline at end of file diff --git a/MailNode.vcxproj.user b/MailNode.vcxproj.user index c9aaeaf..448994b 100644 --- a/MailNode.vcxproj.user +++ b/MailNode.vcxproj.user @@ -1,8 +1,8 @@ - - - - C:\Dev\Msdev2005\projects\bpq32\BPQMail\x64\Debug\LinBPQ.exe - c:\linbpq - WindowsLocalDebugger - + + + + C:\Dev\Msdev2005\projects\bpq32\BPQMail\x64\Debug\LinBPQ.exe + c:\linbpq + WindowsLocalDebugger + \ No newline at end of file diff --git a/PG/Loop.c b/PG/Loop.c index ac753d9..6a5d66e 100644 --- a/PG/Loop.c +++ b/PG/Loop.c @@ -1,24 +1,24 @@ -#include -#include - -/* - * TST_PG.C - * - * Little test program of "PG" command for FBB BBS software. - * - * (C) F6FBB 1991. - * - * FBB software 5.14 and up. - * - * - * This program echoes to the user what he types - * or executes a BBS command preceded by "CMD" - * until "BYE" is received - */ - - -main(int argc, char **argv) -{ - Sleep(10000); - return 0; +#include +#include + +/* + * TST_PG.C + * + * Little test program of "PG" command for FBB BBS software. + * + * (C) F6FBB 1991. + * + * FBB software 5.14 and up. + * + * + * This program echoes to the user what he types + * or executes a BBS command preceded by "CMD" + * until "BYE" is received + */ + + +main(int argc, char **argv) +{ + Sleep(10000); + return 0; } \ No newline at end of file diff --git a/PG/PGTest.c b/PG/PGTest.c index 4d3742d..43dcac4 100644 --- a/PG/PGTest.c +++ b/PG/PGTest.c @@ -1,53 +1,53 @@ -#include - -/* - * TST_PG.C - * - * Little test program of "PG" command for FBB BBS software. - * - * (C) F6FBB 1991. - * - * FBB software 5.14 and up. - * - * - * This program echoes to the user what he types - * or executes a BBS command preceded by "CMD" - * until "BYE" is received - */ - - -main(int argc, char **argv) -{ - int i; - int level = atoi(argv[2]); /* Get level from argument list */ - - /* and transform it to integer */ - if (level == 0) { /* Is level equal to 0 ? */ - /* This is the first call */ - printf("Hello %s, type BYE when you want to stop !\n", argv[1]); - return(1); /* program will be called again */ - } - else { - strupr(argv[5]); /* Capitalise the first word */ - if (strcmp(argv[5], "BYE") == 0) { /* is BYE received ? */ - printf("Ok, bye-bye\n"); - return(0); /* Yes, go on BBS */ - } - else if (strcmp(argv[5], "CMD") == 0) { /* is CMD received ? */ - for (i = 6 ; i < argc ; i++) /* List line arguments */ - printf("%s ", argv[i]); /* sent by user */ - putchar('\n'); - for (i = 6 ; i < argc ; i++) /* List line arguments */ - printf("%s ", argv[i]); /* sent by user */ - putchar('\n'); - return(4); /* Yes, send command */ - } - else { - printf("You told me : "); /* These are other lines */ - for (i = 5 ; i < argc ; i++) /* List line arguments */ - printf("%s ", argv[i]); /* sent by user */ - putchar('\n'); - return(1); /* No, call again program */ - } - } +#include + +/* + * TST_PG.C + * + * Little test program of "PG" command for FBB BBS software. + * + * (C) F6FBB 1991. + * + * FBB software 5.14 and up. + * + * + * This program echoes to the user what he types + * or executes a BBS command preceded by "CMD" + * until "BYE" is received + */ + + +main(int argc, char **argv) +{ + int i; + int level = atoi(argv[2]); /* Get level from argument list */ + + /* and transform it to integer */ + if (level == 0) { /* Is level equal to 0 ? */ + /* This is the first call */ + printf("Hello %s, type BYE when you want to stop !\n", argv[1]); + return(1); /* program will be called again */ + } + else { + strupr(argv[5]); /* Capitalise the first word */ + if (strcmp(argv[5], "BYE") == 0) { /* is BYE received ? */ + printf("Ok, bye-bye\n"); + return(0); /* Yes, go on BBS */ + } + else if (strcmp(argv[5], "CMD") == 0) { /* is CMD received ? */ + for (i = 6 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + for (i = 6 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + return(4); /* Yes, send command */ + } + else { + printf("You told me : "); /* These are other lines */ + for (i = 5 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + return(1); /* No, call again program */ + } + } } \ No newline at end of file diff --git a/RigControl.c b/RigControl.c index 6a7d026..83be639 100644 --- a/RigControl.c +++ b/RigControl.c @@ -3592,7 +3592,7 @@ ok: else { if (!PORT->AutoPoll) - SendResponse(RIG->Session, "Frequency Set OK"); + SendResponse(RIG->Session, "Frequency Set OK"); PORT->Timeout = 0; } @@ -5286,6 +5286,7 @@ void DecodeCM108(int Port, char * ptr) char * next; int32_t VID = 0, PID = 0; char product[256]; + char sernum[256] = "NULL"; struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; @@ -5306,11 +5307,13 @@ void DecodeCM108(int Port, char * ptr) while (cur_dev) { wcstombs(product, cur_dev->product_string, 255); + if (cur_dev->serial_number) + wcstombs(sernum, cur_dev->serial_number, 255); if (product) - Debugprintf("HID Device %s VID %X PID %X %s", product, cur_dev->vendor_id, cur_dev->product_id, cur_dev->path); + Debugprintf("HID Device %s VID %X PID %X Ser %s %s", product, cur_dev->vendor_id, cur_dev->product_id, sernum, cur_dev->path); else - Debugprintf("HID Device %s VID %X PID %X %s", "Missing Product", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path); + Debugprintf("HID Device %s VID %X PID %X Ser %s %s", "Missing Product", cur_dev->vendor_id, cur_dev->product_id, sernum, cur_dev->path); if (cur_dev->vendor_id == VID && cur_dev->product_id == PID) path_to_open = cur_dev->path; diff --git a/SCSPactor.c b/SCSPactor.c index 244a62c..14c82c6 100644 --- a/SCSPactor.c +++ b/SCSPactor.c @@ -552,6 +552,12 @@ ok: txlen = GetLengthfromBuffer(buff) - (MSGHDDRLEN + 1); // 1 as no PID + if (txlen == 1 && buff->L2DATA[0] == 0) // Keepalive Packet + { + ReleaseBuffer(buffptr); + return 0; + } + buffptr->Len = txlen; memcpy(buffptr->Data, buff->L2DATA, txlen); @@ -1092,6 +1098,56 @@ void SCSCheckRX(struct TNCINFO * TNC) // we haen't checked CRC. All I can think of is to check the CRC and if it is ok, assume frame is // complete. If CRC is duff, we will eventually time out and get a retry. The retry code // can clear the RC buffer + + if (TNC->UsingTermMode) + { + // Send response to Host if connected + + PMSGWITHLEN buffptr; + int Len = TNC->RXLen; + int Posn = 0; + + // First message is probably ACK of JHO4T - AA AA 1F 00 1E 19 + + if (TNC->RXBuffer[0] == 0xaa && Len > 6) + { + memmove(TNC->RXBuffer, &TNC->RXBuffer[6], Len - 6); + Len -= 6; + } + + // TNC seems to send 1e f7 or 1e 87 regularly + + while (TNC->RXBuffer[0] == 0x1e && Len > 1) + { + memmove(TNC->RXBuffer, &TNC->RXBuffer[2], Len - 2); + Len -= 2; + } + + if (Len == 0) + { + TNC->RXLen = 0; // Ready for next frame + return; + } + + while (Len > 250) + { + buffptr = GetBuff(); + buffptr->Len = 250; + memcpy(buffptr->Data, &TNC->RXBuffer[Posn], 250); + C_Q_ADD(&TNC->Streams[0].PACTORtoBPQ_Q, buffptr); + Len -= 250; + Posn += 250; + } + + buffptr = GetBuff(); + buffptr->Len = Len; + memcpy(buffptr->Data, &TNC->RXBuffer[Posn], Len); + C_Q_ADD(&TNC->Streams[0].PACTORtoBPQ_Q, buffptr); + + TNC->RXLen = 0; // Ready for next frame + return; + } + if (TNC->RXBuffer[0] != 170) { @@ -1247,6 +1303,51 @@ VOID SCSPoll(int Port) int nn; struct STREAMINFO * STREAM; + if (TNC->UsingTermMode) + { + if (TNC->Streams[Stream].BPQtoPACTOR_Q) + { + PMSGWITHLEN buffptr = Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q); + + // See if enter host mode command + + if (_memicmp(buffptr->Data, "ENTERHOST\r", buffptr->Len) == 0) + { + TNC->UsingTermMode = FALSE; + + memcpy(Poll, "JHOST4\r", 7); + TNC->TXLen = 7; + WriteCommBlock(TNC); + + // No response expected + + Sleep(10); + + Poll[2] = 255; // Channel + TNC->Toggle = 0; + Poll[3] = 0x41; + Poll[4] = 0; // Len-1 + Poll[5] = 'G'; // Poll + + CRCStuffAndSend(TNC, Poll, 6); + TNC->InternalCmd = FALSE; + TNC->Timeout = 5; // 1/2 sec - In case missed + + + } + else + { + // Send to TNC + + memcpy(&Poll[0], buffptr->Data, buffptr->Len); + TNC->TXLen = buffptr->Len;; + WriteCommBlock(TNC); + } + ReleaseBuffer(buffptr); + } + return; + } + if (TNC->MinLevelTimer) { TNC->MinLevelTimer--; @@ -1721,6 +1822,10 @@ VOID SCSPoll(int Port) // But we cant set digipeated bit in call, so if we find one, skip message + // This doesn't seem to work + +/* + ConvFromAX25(Buffer + 7, ICall); // Origin strlop(ICall, ' '); @@ -1762,7 +1867,7 @@ VOID SCSPoll(int Port) 1, Buffer, // Flag CmdSet as Data 2, TNC->NodeCall); // Flag as Chan 0 Command } - +*/ ReleaseBuffer((UINT *)buffptr); return; } @@ -2006,6 +2111,24 @@ VOID SCSPoll(int Port) return; } + + if ((Stream == 0) && memcmp(Buffer, "EXITHOST", 8) == 0) + { + UCHAR * Poll = TNC->TXBuffer; + + TNC->UsingTermMode = 1; + + ExitHost(TNC); + + // Send CR to get prompt from TNC + + Poll[0] = 13; + TNC->TXLen = 1; + WriteCommBlock(TNC); + + ReleaseBuffer(buffptr); + return; + } if (Stream == 0 && Buffer[0] == 'C' && datalen > 2) // Pactor Connect Poll[2] = TNC->Streams[0].DEDStream = 31; // Pactor Channel @@ -2086,8 +2209,7 @@ VOID SCSPoll(int Port) return; } } - - // Anything else send to tnc. + Poll[4] = datalen - 1; memcpy(&Poll[5], buffptr->Data, datalen); diff --git a/SCSTracker.c b/SCSTracker.c index c7d9f93..934759f 100644 --- a/SCSTracker.c +++ b/SCSTracker.c @@ -290,6 +290,9 @@ ConfigLine: if (_memicmp(buf, "UPDATEMAP", 9) == 0) TNC->PktUpdateMap = TRUE; else + if (_memicmp(buf, "TeensyRPR", 9) == 0) + TNC->TeensyRPR = TRUE; + else if (_memicmp(buf, "WL2KREPORT", 10) == 0) TNC->WL2K = DecodeWL2KReportLine(buf); else @@ -1655,10 +1658,12 @@ VOID TrkExitHost(struct TNCINFO * TNC) TNC->TXBuffer[0] = 1; TNC->TXBuffer[1] = 1; TNC->TXBuffer[2] = 1; - memcpy(&TNC->TXBuffer[3], "%R", 2); - - StuffAndSend(TNC, Poll, 5); + if (!TNC->TeensyRPR) // %R puts TNC into Program Mode. + { + memcpy(&TNC->TXBuffer[3], "%R", 2); + StuffAndSend(TNC, Poll, 5); + } return; } diff --git a/TelnetV6.c b/TelnetV6.c index c67516c..cbf8c4f 100644 --- a/TelnetV6.c +++ b/TelnetV6.c @@ -342,7 +342,7 @@ BOOL SendAndCheck(struct ConnectionInfo * sockptr, unsigned char * MsgPtr, int l VOID SendPortsForMonitor(SOCKET sock, int Secure) { - UCHAR PortInfo[1500] = {0xff, 0xff}; + UCHAR PortInfo[3000] = {0xff, 0xff}; UCHAR * ptr = &PortInfo[2]; char ID[31] = ""; struct PORTCONTROL * PORT; diff --git a/UIRoutines.c b/UIRoutines.c index ebf56a8..014fe01 100644 --- a/UIRoutines.c +++ b/UIRoutines.c @@ -32,14 +32,14 @@ static char MAILMYCALL[7]; #pragma pack(1) -UINT UIPortMask = 0; -BOOL UIEnabled[33]; -BOOL UIMF[33]; -BOOL UIHDDR[33]; -BOOL UINull[33]; -char * UIDigi[33]; -char * UIDigiAX[33]; // ax.25 version of digistring -int UIDigiLen[33]; // Length of AX string +uint64_t UIPortMask = 0; +BOOL UIEnabled[MaxBPQPortNo + 1]; +BOOL UIMF[MaxBPQPortNo + 1]; +BOOL UIHDDR[MaxBPQPortNo + 1]; +BOOL UINull[MaxBPQPortNo + 1]; +char * UIDigi[MaxBPQPortNo + 1]; +char * UIDigiAX[MaxBPQPortNo + 1]; // ax.25 version of digistring +int UIDigiLen[MaxBPQPortNo + 1]; // Length of AX string @@ -52,14 +52,13 @@ struct SEM DGSemaphore = {0, 0}; // For locking access to DG_Q; VOID UnQueueRaw(void * Param); static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue); -DllExport char * APIENTRY GetApplName(int Appl); - -int APIENTRY SendRaw(int port, char * msg, int len); +char * APIENTRY GetApplName(int Appl); int APIENTRY GetNumberofPorts(); +int APIENTRY GetPortNumber(int portslot); VOID SetupUIInterface() { - int i, NumPorts = GetNumberofPorts(); + int i; #ifndef LINBPQ struct _EXCEPTION_POINTERS exinfo; #endif @@ -70,13 +69,13 @@ VOID SetupUIInterface() UIPortMask = 0; - for (i = 1; i <= NumPorts; i++) + for (i = 1; i <= MaxBPQPortNo; i++) { if (UIEnabled[i]) { char DigiString[100], * DigiLeft; - UIPortMask |= 1 << (i-1); + UIPortMask |= (uint64_t)1 << (i-1); UIDigiLen[i] = 0; if (UIDigi[i]) @@ -122,7 +121,7 @@ VOID Free_UI() int i; PMESSAGEX AXMSG; - for (i = 1; i <= 32; i++) + for (i = 1; i <= MaxBPQPortNo; i++) { if (UIDigi[i]) { @@ -179,18 +178,25 @@ VOID SendMsgUI(struct MsgInfo * Msg) { char msg[200]; int len, i; - int Mask = UIPortMask; - int NumPorts = GetNumberofPorts(); + uint64_t Mask = UIPortMask; //12345 B 2053 TEST@ALL F6FBB 920325 This is the subject + char Via[80] = ""; struct tm *tm = gmtime((time_t *)&Msg->datecreated); - - len = sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r", - Msg->number, Msg->type, Msg->length, Msg->to, + + if (Msg->via[0]) + { + Via[0] = '@'; + strcpy(&Via[1], Msg->via); + strlop(Via, '.'); // Only show first part of via + } + + len = sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-6s%-7s %-6s %02d%02d%02d %s\r", + Msg->number, Msg->type, Msg->length, Msg->to, Via, Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title); - for (i=1; i <= NumPorts; i++) + for (i=1; i <= MaxBPQPortNo; i++) { if ((Mask & 1) && UIHDDR[i]) Send_AX_Datagram(msg, len, i, AXDEST, TRUE); @@ -212,10 +218,19 @@ VOID SendHeaders(int Number, int Port) while (Number <= LatestMsg) { + char Via[80] = ""; + Msg = FindMessageByNumber(Number); if (Msg) { + if (Msg->via[0]) + { + Via[0] = '@'; + strcpy(&Via[1], Msg->via); + strlop(Via, '.'); // Only show first part of via + } + if (len > (200 - strlen(Msg->title))) { Send_AX_Datagram(msg, len, Port, AXDEST, FALSE); @@ -224,8 +239,8 @@ VOID SendHeaders(int Number, int Port) tm = gmtime((time_t *)&Msg->datecreated); - len += sprintf(&msg[len], "%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r", - Msg->number, Msg->type, Msg->length, Msg->to, + len += sprintf(&msg[len], "%-6d %c %6d %-6s%-7s %-6s %02d%02d%02d %s\r", + Msg->number, Msg->type, Msg->length, Msg->to, Via, Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title); } else @@ -248,12 +263,11 @@ VOID SendDummyUI(int num) { char msg[100]; int len, i; - int Mask = UIPortMask; - int NumPorts = GetNumberofPorts() -; + uint64_t Mask = UIPortMask; + len = sprintf_s(msg, sizeof(msg),"%-6d #\r", num); - for (i=1; i <= NumPorts; i++) + for (i=1; i <= MaxBPQPortNo; i++) { if (Mask & 1) Send_AX_Datagram(msg, len, i, AXDEST, TRUE); @@ -266,9 +280,8 @@ VOID SendLatestUI(int Port) { char msg[20]; int len, i; - int Mask = UIPortMask; - int NumPorts = GetNumberofPorts(); - + uint64_t Mask = UIPortMask; + len = sprintf_s(msg, sizeof(msg),"%-6d !!\r", LatestMsg); if (Port) @@ -277,12 +290,12 @@ VOID SendLatestUI(int Port) return; } - for (i=1; i <= NumPorts; i++) + for (i = 1; i <= MaxBPQPortNo; i++) { - if ((Mask & 1) && UIHDDR[i]) + if ((Mask & (uint64_t)1) && UIHDDR[i]) Send_AX_Datagram(msg, len, i, AXDEST, TRUE); - Mask>>=1; + Mask >>= 1; } } @@ -324,9 +337,7 @@ static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, QueueRaw(Port, &AXMSG, Len + 16); else SendRaw(Port, (char *)&AXMSG.DEST, Len + 16); - - return; - + } VOID UnQueueRaw(void * Param) @@ -366,6 +377,20 @@ VOID ProcessUItoFBB(char * msg, int len, int Port) int Number, Sum, Sent = 0; char cksum[3]; + int n, i; + + // Send_AX_Datagram uses Port Slot, not Port Number + + for (n = 1 ; n <= GetNumberofPorts(); n++) + { + i = GetPortNumber(n); + + if (i == Port) + { + Port = n; + break; + } + } if (msg[0] == '?') { @@ -523,8 +548,7 @@ VOID ExpandMailFor() VOID SendMailFor(char * Msg, BOOL HaveCalls) { - int Mask = UIPortMask; - int NumPorts = GetNumberofPorts(); + uint64_t Mask = UIPortMask; int i; if (!HaveCalls) @@ -532,7 +556,7 @@ VOID SendMailFor(char * Msg, BOOL HaveCalls) Sleep(1000); - for (i=1; i <= NumPorts; i++) + for (i=1; i <= MaxBPQPortNo; i++) { if (Mask & 1) { diff --git a/UZ7HODrv.c b/UZ7HODrv.c index 7ec055b..50e5cf8 100644 --- a/UZ7HODrv.c +++ b/UZ7HODrv.c @@ -2914,12 +2914,14 @@ UZ7HO T GM8BPQ-2 APRS 1:Fm GM8BPQ-2 To APRS Via WIDE2-2 [1 AdjMsg = &Monframe; // Adjusted for digis ptr = strstr(Msg, "Fm "); + if (ptr == 0) return; ConvToAX25(&ptr[3], Monframe.ORIGIN); memcpy(MHCall, &ptr[3], 11); strlop(MHCall, ' '); ptr = strstr(ptr, "To "); + if (ptr == 0) return; ConvToAX25(&ptr[3], Monframe.DEST); @@ -2934,6 +2936,8 @@ UZ7HO T GM8BPQ-2 APRS 1:Fm GM8BPQ-2 To APRS Via WIDE2-2 [1 memcpy(Save, &ptr[4], 60); ptr = strtok_s(Save, ", ", &context); + if (ptr == 0) return; + DigiLoop: temp = (char *)AdjMsg; @@ -2952,6 +2956,7 @@ DigiLoop: AdjMsg->ORIGIN[6] |= 0x80; // Set end of address ptr = strtok_s(NULL, ", ", &context); + if (ptr == 0) return; if (ptr[0] != '<') goto DigiLoop; @@ -3012,18 +3017,21 @@ DigiLoop: if (memcmp(&ptr[1], "RR", 2) == 0) { nrptr = strchr(&ptr[3], '>'); + if (nrptr == 0) return; AdjMsg->CTL = 0x1 | (nrptr[-2] << 5); } else if (memcmp(&ptr[1], "RNR", 3) == 0) { nrptr = strchr(&ptr[4], '>'); + if (nrptr == 0) return; AdjMsg->CTL = 0x5 | (nrptr[-2] << 5); } else if (memcmp(&ptr[1], "REJ", 3) == 0) { nrptr = strchr(&ptr[4], '>'); + if (nrptr == 0) return; AdjMsg->CTL = 0x9 | (nrptr[-2] << 5); } else @@ -3058,12 +3066,18 @@ DigiLoop: if ((AdjMsg->CTL & 1) == 0 || AdjMsg->CTL == 3) // I or UI { ptr = strstr(ptr, "pid"); + if (ptr == 0) return; + sscanf(&ptr[4], "%x", (unsigned int *)&AdjMsg->PID); ptr = strstr(ptr, "Len"); + if (ptr == 0) return; + ILen = atoi(&ptr[4]); ptr = strstr(ptr, "]"); + if (ptr == 0) return; + ptr += 2; // Skip ] and cr memcpy(AdjMsg->L2DATA, ptr, ILen); Monframe.LENGTH += ILen; @@ -3071,6 +3085,8 @@ DigiLoop: else if (AdjMsg->CTL == 0x97) // FRMR { ptr = strstr(ptr, ">"); + if (ptr == 0) return; + sscanf(ptr+1, "%hhx %hhx %hhx", &AdjMsg->PID, &AdjMsg->L2DATA[0], &AdjMsg->L2DATA[1]); Monframe.LENGTH += 3; } diff --git a/VARA.c b/VARA.c index cf6dd7d..983f77d 100644 --- a/VARA.c +++ b/VARA.c @@ -2269,6 +2269,13 @@ VOID VARAProcessResponse(struct TNCINFO * TNC, UCHAR * Buffer, int MsgLen) return; } + if (_memicmp(Buffer, "ENCRYPTION ", 11) == 0) + { + strcat(Buffer, "\r"); + WritetoTrace(TNC, Buffer, (int)strlen(Buffer)); + return; + } + if (_memicmp(Buffer, "MISSING SOUNDCARD", 17) == 0) { strcat(Buffer, "\r"); diff --git a/Versions.h b/Versions.h index f3bfe9d..5ebd8fc 100644 --- a/Versions.h +++ b/Versions.h @@ -10,14 +10,14 @@ #endif -#define KVers 6,0,23,77 -#define KVerstring "6.0.23.77\0" +#define KVers 6,0,23,81 +#define KVerstring "6.0.23.81\0" #ifdef CKernel #define Vers KVers #define Verstring KVerstring -#define Datestring "May 2023" +#define Datestring "July 2023" #define VerComments "G8BPQ Packet Switch (C Version)" KVerstring #define VerCopyright "Copyright © 2001-2023 John Wiseman G8BPQ\0" #define VerDesc "BPQ32 Switch\0" diff --git a/bpqmail.h b/bpqmail.h index ab1052f..d910932 100644 --- a/bpqmail.h +++ b/bpqmail.h @@ -34,6 +34,8 @@ #include "asmstrucs.h" +#define MaxBPQPortNo 63 // Port 64 reserved for BBS Mon +#define MAXBPQPORTS 63 #define NEWROUTING diff --git a/bpqmail.h.bak b/bpqmail.h.bak index a153c3f..eb7323a 100644 --- a/bpqmail.h.bak +++ b/bpqmail.h.bak @@ -1,1604 +1,1604 @@ -#ifndef WINVER // Allow use of features specific to Windows XP or later. -#define WINVER 0x0501 // Change this to the appropriate value to target other versions of Windows. -#endif - -#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. -#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. -#endif - -#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. -#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later. -#endif - -#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later. -#define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE. -#endif - - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#define _CRT_SECURE_NO_DEPRECATE -#define _WINSOCK_DEPRECATED_NO_WARNINGS - -#define LIBCONFIG_STATIC -#include - -#include "compatbits.h" - -#ifndef LINBPQ -#include "bpq32.h" -#include "BPQMailrc.h" -#include "dbghelp.h" -#else -#include "CHeaders.h" -#endif - -#include "asmstrucs.h" - - -#define NEWROUTING - - - -// Standard __except handler for try/except - -VOID CheckProgramErrors(); -VOID WriteMiniDump(); - -extern int ProgramErrors; - -extern struct _EXCEPTION_POINTERS exinfox; - -#ifdef WIN32 -Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg); - -#define My__except_Routine(Message) \ -__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ -{\ - Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ - exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ - exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ - exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ - CheckProgramErrors();\ - WriteMiniDump();\ -} - - -/* -#define My__except_Routine(Message) \ -__except(memcpy(&exinfox, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ -{\ - Dump_Process_State(&exinfox, Message);\ - CheckProgramErrors();\ -} - -#define My__except_RoutineWithDisconnect(Message) \ -__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ -{\ - Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ - exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ - exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ - exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ - FreeSemaphore(&ChatSemaphore);\ - if (conn->BPQStream < 0)\ - CloseConsole(conn->BPQStream);\ - else\ - Disconnect(conn->BPQStream);\ -} -*/ -#define My_except_RoutineWithDiscBBS(Message) \ -__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ -{\ - Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ - exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ - exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ - exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ - if (conn->BPQStream < 0)\ - CloseConsole(conn->BPQStream);\ - else\ - Disconnect(conn->BPQStream);\ - CheckProgramErrors();\ -} -#endif -#define MAXUSERNAMELEN 6 - -#define WSA_ACCEPT WM_USER + 1 -#define WSA_CONNECT WM_USER + 2 -#define WSA_DATA WM_USER + 3 -#define NNTP_ACCEPT WM_USER + 4 -#define NNTP_DATA WM_USER + 5 - -#ifdef _DEBUG - -VOID * _malloc_dbg_trace(int len, int type, char * file, int line); - -#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) -#define calloc(c, s) _calloc_dbg(c, s, _NORMAL_BLOCK, __FILE__, __LINE__) -#define realloc(p, s) _realloc_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) -#define _recalloc(p, c, s) _recalloc_dbg(p, c, s, _NORMAL_BLOCK, __FILE__, __LINE__) -#define _expand(p, s) _expand_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) -#define free(p) _free_dbg(p, _NORMAL_BLOCK) -#define _strdup(s) _strdup_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) - - -#define zalloc(s) _zalloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) -#else -#define zalloc(s) _zalloc(s) -#endif - -#ifdef LINBPQ - -#undef zalloc -#define zalloc _zalloc - -#endif - -VOID * _zalloc_dbg(size_t len, int type, char * file, int line); - -#define LOG_BBS 0 -#define LOG_CHAT 1 -#define LOG_TCP 2 -#define LOG_DEBUG_X 3 - - -//Chat Duplicate suppression Code - -#define MAXDUPS 10 // Number to keep -#define DUPSECONDS 5 // TIme to Keep - -struct DUPINFO -{ - time_t DupTime; - char DupUser[10]; - char DupText[100]; -}; - - -struct UserRec -{ - char * Callsign; - char * UserName; - char * Password; -}; - - - -typedef struct ConnectionInfo_S -{ - struct ConnectionInfo_S *next; - PROC *proc; - UCHAR RadioOnlyMode; // T or R flag for Radio Only mode. - - int Number; // Number of record - for Connections display - BOOL Active; - int BPQStream; - int paclen; - UCHAR Callsign[11]; // Station call including SSID - BOOL GotHeader; - UCHAR InputMode; // Line by Line or Binary or YAPP - - UCHAR * InputBuffer; - int InputBufferLen; - int InputLen; // Data we have already = Offset of end of an incomplete packet; - - struct UserInfo * UserPointer; - int Retries; - int LoginState; // 1 = user ok, 2 = password ok - int Flags; - - // Data to the user is kept in a malloc'd buffer. This can be appended to, - // and data sucked out under both terminal and system flow control. PACLEN is - // enfored when sending to node. - - UCHAR * OutputQueue; // Messages to user - int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message - int OutputGetPointer; // Next byte to send. When Getpointer = Queue Length all is sent - free the buffer and start again. - - int CloseAfterFlush; // Close session when all sent. Set to 100ms intervals to wait. - - int ErrorCount; // Invalid Command count - BOOL Paging; // Set if user wants paging - int LinesSent; // Count when paging - int PageLen; // Lines per page - - UCHAR * MailBuffer; // Mail Message being received - UCHAR * CopyBuffer; // Mail Message being forwarded - int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct - - long lastmsg; // Last Listed. Stored here, updated in user record only on clean close - BOOL sysop; // Set if user is authenticated as a sysop - BOOL Secure_Session; // Set if Local Terminal, or Telnet connect with SYSOP status - UINT BBSFlags; // Set if defined as a bbs and SID received - struct MsgInfo * TempMsg; // Header while message is being received - struct MsgInfo * FwdMsg; // Header while message is being forwarded - - char ** To; // May be several Recipients - int ToCount; - - int BBSNumber; // The BBS number (offset into bitlist of BBSes to forward a message to - int NextMessagetoForward; // Next index to check in forward cycle - BOOL BPQBBS; // Set if SID indicates other end is BPQ - char MSGTYPES[20]; // Any MSGTYPEFLAGS - BOOL SendT; // Send T messages - BOOL SendP; // Send P messages - BOOL SendB; // Send Bulls - BOOL SendWL2KFW; // send ;FW: - int MaxBLen; // Max Size for this session - int MaxPLen; // Max Size for this session - int MaxTLen; // Max Size for this session - BOOL DoReverse; // Request Reverse Forward - char LastForwardType; // Last type of messages forwarded - struct FBBHeaderLine * FBBHeaders; // The Headers from an FFB forward block - char FBBReplyChars[36]; //The +-=!nnnn chars for the 5 proposals - int FBBReplyIndex; // current Reply Pointer - int FBBIndex; // current propopsal number - int RestartFrom; // Restart position - BOOL NeedRestartHeader; // Set if waiting for 6 byte restart header - BOOL DontSaveRestartData; // Set if corrupt data received - BOOL FBBMsgsSent; // Messages need to be maked as complete when next command received - UCHAR FBBChecksum; // Header Checksum - BOOL OpenBCM; // OpenBCM mode (escape -xFF chars) - BOOL InTelnetExcape; // Last Char was 0xff - BOOL LocalMsg; // Set if current Send command is for a local user - BOOL NewUser; // Set if first time user has accessed BBS - BOOL Paclink; // Set if receiving messages from Paclink - BOOL RMSExpress; // Set if receiving messages from RMS Express - BOOL WL2K; // Set if communicating with a CMS - BOOL PAT; // Set if communicating with PAT - char ** PacLinkCalls; // Calls we are getting messages for - BOOL SkipPrompt; // Set if a remote node sends a > at the end of his CTEXT - BOOL SkipConn; // Node sends "connected" in its CTEXT - int Watchdog; // Hung Circuit Detect. - int SessType; // BPQ32 sesstype bits - -#define Sess_L2LINK 1 -#define Sess_SESSION 2 -#define Sess_UPLINK 4 -#define Sess_DOWNLINK 8 -#define Sess_BPQHOST 0x20 -#define Sess_PACTOR 0x40 - - HANDLE DebugHandle; // File Handle for session-based debugging - - char ARQFilename[256]; // Filename from ARQ:FILE:: Header - int ARQClearCount; // To make sure queues are flushed when sending - - int SIDResponseTimer; // Used to detect incomplete handshake - - char PQChallenge[20]; // Secure User logon challange - char SecureMsg[20]; // CMS Secure Signon Response - int MCastListenTime; // Time to run session for - - int YAPPLen; // Bytes sent/received of YAPP Message - long YAPPDate; // Date for received file - if set enables YAPPC - - int SyncCompressedLen; - int SyncXMLLen; - int SyncMsgLen; - char * SyncHost; // Saved so can send "request sync" - int SyncPort; - UCHAR * SyncMessage; // Compressed SYNC message to send - - // These are used to detect CRLF split over a packet boundary - int usingCR; // Session is (normally) using CR as terminator - int lastLineEnd; // Terminator for current line - - struct ConnectionInfo_S * SysopChatStream; // Stream sysop is chatting to - -} ConnectionInfo, CIRCUIT; - -// Flags Equates - -#define GETTINGUSER 1 -#define GETTINGBBS 2 -#define CHATMODE 4 -#define GETTINGTITLE 8 -#define GETTINGMESSAGE 16 -#define CHATLINK 32 // Link to another Chat Node -#define SENDTITLE 64 -#define SENDBODY 128 -#define WAITPROMPT 256 // Waiting for prompt after message -#define PROPOSINGSYNCMSG 512 // Sent proposal to SYNC, waiting response -#define SENDINGSYNCMSG 1024 // Sent message to SYNC, waiting response -#define REQUESTINGSYNC 2048 -#define GETTINGSYNCMESSAGE 4096 // Receiving body of a SYNC message - -// BBSFlags Equates - -#define BBS 1 -#define FBBForwarding 2 -#define FBBCompressed 4 -#define FBBB1Mode 8 -#define FBBB2Mode 16 -#define RunningConnectScript 32 -#define MBLFORWARDING 64 // MBL Style Frwarding- waiting for OK/NO or Prompt following message -#define TEXTFORWARDING 128 // Plain Text forwarding -#define OUTWARDCONNECT 256 // We connected to them -#define FLARQMODE 512 // Message from FLARQ -#define FLARQMAIL 1024 // Sending FLARQ Format Message -#define ARQMAILACK 2048 // Waiting for all data to be acked -#define NEEDLF 4096 // Add LF to forward script commands (fro Telnet -#define MCASTRX 8192 // Stream in Multicast RX Mode -#define DISCONNECTING 16384 // Disconnect sent to Node -#define YAPPTX 0x008000 // Sending YAPP file -#define SYSOPCHAT 0x010000 // Chatting to BBS console -#define WINLINKRO 0x020000 // WL2K RO (no J in SID) -#define SYNCMODE 0x040000 // RMS RELAY SYNC -#define MFJMODE 0x080000 // MFJ PMS -#define NEWPACCOM 0x100000 // PACCOM PMS 3.2 -#define SETCALLTOSENDER 0x200000 // Set calling call to message sender - - -struct FBBRestartData -{ - struct MsgInfo * TempMsg; // Header while message is being received - struct UserInfo * UserPointer; - UCHAR * MailBuffer; // Mail Message being received - int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct - int Count; // Give up if too many restarts -}; - -// We need to keep the B2Message file for B2 messages we are sending until the messages is acked, so -// we can restart it. Otherwise the file may change, resulting in a checksum error - - -struct B2RestartData -{ - int CSize; // Compresses Size (B2 proto) - UCHAR * CompressedMsg; // Compressed Body fo B2 - struct MsgInfo * FwdMsg; - struct UserInfo * UserPointer; - int Count; // Give up if too many restarts -}; - -#pragma pack(1) - -struct TempUserInfo -{ - int LastAuthCode; // Protect against playback attack - - // Fields used to allow interrupting and resuming a paged listing - - BOOL ListActive; // Doing a list - BOOL ListSuspended; // Paused doing a list - int LastListedInPagedMode; - char LastListCommand[80]; - char LastListParams[80]; - int LinesSent; - char SendFullFrom; - char ListType; - char ListDirn; - char ListStatus; - char ListSelector; // < > @ etc - - int ListRangeStart; - int ListRangeEnd; - int LLCount; // Number still to send in List Last N - int UpdateLatest; // if set, save last listed as latest - BOOL IncludeKilled; // Show Killed Messages if SYSOP - -}; - -#define PMSG 1 -#define BMSG 2 -#define TMSG 3 - -struct OldUserInfo -{ - // Old format - without message type specific traffic counts - - char Call[10]; // Connected call without SSID -// indicat relai[8]; /* 64 Digis path */ - int lastmsg; /* 4 Last L number */ - int ConnectsIn; /* 4 Number of connexions in*/ - int TimeLastConnected; //Last connexion date */ -// long lastyap __a2__ ; /* 4 Last YN date */ - ULONG flags ; /* 4 Flags */ - - UCHAR PageLen; // Lines Per Page - UCHAR lang ; /* 1 Language */ - - int Xnewbanner; /* 4 Last Banner date */ - short Xdownload ; /* 2 download size (KB) = 100 */ - char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) - char BBSNumber; // BBS Bitmap Index Number - struct BBSForwardingInfo * ForwardingInfo; - struct UserInfo * BBSNext; // links BBS record - struct TempUserInfo * Temp; // Working Fields - not saved in user file - char xfree[6]; /* 6 Spare */ - char Xtheme; /* 1 Current topic */ - - char Name[18]; /* 18 1st Name */ - char Address[61]; /* 61 Address */ - - // Stats. Was City[31]; /* 31 City */ - - int MsgsReceived; - int MsgsSent; - int MsgsRejectedIn; // Messages we reject - int MsgsRejectedOut; // Messages Rejectd by other end - int BytesForwardedIn; - int BytesForwardedOut; - int ConnectsOut; // Forwarding Connects Out - - USHORT RMSSSIDBits; // SSID's to poll in RMS - - char Spare1; - - char HomeBBS[41]; /* 41 home BBS */ - char QRA[7]; /* 7 Qth Locator */ - char pass[13]; /* 13 Password */ - char ZIP[9]; /* 9 Zipcode */ - BOOL spare; -} ; /* Total : 360 bytes */ - -struct MsgStats -{ - int ConnectsIn; /* 4 Number of connexions in*/ - int ConnectsOut; // Forwarding Connects Out - - // Stats saveed by message type - - int MsgsReceived[4]; - int MsgsSent[4]; - int MsgsRejectedIn[4]; // Messages we reject - int MsgsRejectedOut[4]; // Messages Rejectd by other end - int BytesForwardedIn[4]; - int BytesForwardedOut[4]; -}; - -struct UserInfo -{ - // New Format - with stats maintained by message type and unused fields removed. - - char Call[10]; // Connected call without SSID - - int Length; // To make subsequent format changes easier - - int lastmsg; /* 4 Last L number */ - int xTimeLastConnected; //Last connexion date */ - ULONG flags ; /* 4 Flags */ - - UCHAR PageLen; // Lines Per Page - - char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) - unsigned char BBSNumber; // BBS Bitmap Index Number - struct BBSForwardingInfo * ForwardingInfo; - struct UserInfo * BBSNext; // links BBS record - struct TempUserInfo * Temp; // Working Fields - not saved in user file - char Name[18]; /* 18 1st Name */ - char Address[61]; /* 61 Address */ - - USHORT RMSSSIDBits; // SSID's to poll in RMS - - char HomeBBS[41]; /* 41 home BBS */ - char QRA[7]; /* 7 Qth Locator */ - char pass[13]; /* 13 Password */ - char ZIP[9]; /* 9 Zipcode */ - - struct MsgStats Total; - struct MsgStats Last; - - char CMSPass[16]; // For Secure Signon - int WebSeqNo; - - long long TimeLastConnected; //Last connection date */ - - char Filler[44 - 8]; // So we can add a few fields wirhout another resize -}; - -// flags equates - -#define F_Excluded 0x0001 -#define F_GGG 0x0002 -#define F_Expert 0x0004 -#define F_SYSOP 0x0008 -#define F_BBS 0x0010 -#define F_RMSREDIRECT 0x0020 -#define F_BBB 0x0040 -#define F_CCC 0x0080 -#define F_DDD 0x0100 -#define F_EEE 0x0200 -#define F_FFF 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 // "Winlink Express User" -#define F_NOWINLINK 0x00020000 // Don't add Winlink.org -#define F_NOBULLS 0x00040000 -#define F_NTSMPS 0x00080000 -#define F_APRSMFOR 0x00100000 // Send APRS message for new mail -#define F_APRSSSID 0xF0000000 // (Top 4 Bits - - -struct Override -{ - char * Call; - int Days; -}; - -struct ALIAS -{ - char * Alias; - char * Dest; -}; - -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 DIGIS[56]; // Padding in case we have digis - -}MESSAGEX, *PMESSAGEX; - - -#pragma pack() - -// Message Database Entry. Designed to be compatible with FBB - -#define NBBBS 160 // Max BBSes we can forward to. Must be Multiple of 8, and must be 80 for FBB compatibliliy -#define NBMASK NBBBS/8 // Number of bytes in Forward bitlists. - -#pragma pack(1) - -struct OldMsgInfo -{ - char type ; - char status ; - int number ; - int length ; - int datereceived; - char bbsfrom[7] ; // ? BBS we got it from ? - char via[41] ; - char from[7] ; - char to[7] ; - char bid[13] ; - char title[61] ; - char bin; - int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp - - UCHAR B2Flags; - - char free[4]; - unsigned short nblu; - int theme ; - time_t datecreated ; - time_t datechanged ; - char fbbs[10] ; - char forw[10] ; - char emailfrom[41]; -} ; - - -struct MsgInfo -{ - char type; - char status; - int number; - int length; - int xdatereceived; - char bbsfrom[7]; // ? BBS we got it from ? - char via[41]; - char from[7]; - char to[7]; - char bid[13]; - char title[61]; - int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp - - UCHAR B2Flags; - - #define B2Msg 1 // Set if Message File is a formatted B2 message - #define Attachments 2 // Set if B2 message has attachments - #define FromPaclink 4 - #define FromCMS 8 - #define FromRMSExpress 16 - #define RadioOnlyMsg 32 // Received using call-T - #define RadioOnlyFwd 64 // Received using call-R - - int xdatecreated; - int xdatechanged; - UCHAR fbbs[NBMASK]; - UCHAR forw[NBMASK]; - char emailfrom[41]; - char Locked; // Set if selected for sending (NTS Pickup) - char Defered; // FBB response '=' received - UCHAR UTF8; // Set if Message is in UTF8 (ie from POP/SMTP) - -// For 64 bit time_t compatibility define as long long -// (so struct is same with 32 or 64 bit time_t) - - long long datereceived; - long long datecreated; - long long datechanged; - - char Spare[61 - 24]; // For future use -} ; - -#define MSGTYPE_B 0 -#define MSGTYPE_P 1 - -#define MSGSTATUS_N 0 -#define MSGSTATUS_Y 1 -#define MSGSTATUS_F 2 -#define MSGSTATUS_K 3 -#define MSGSTATUS_H 4 -#define MSGSTATUS_D 5 -#define MSGSTATUS_$ 6 - -struct NNTPRec -{ - // Used for NNTP access to Bulls - - struct NNTPRec * Next; // Record held in chain, so can be held sorted - char NewsGroup[64]; // = Bull TO.at field - int FirstMsg; // Lowest Number - int LastMsg; // Highest Number - int Count; // Active Msgs - time_t DateCreated; // COntains Creation Date of First Bull in Group -}; - - -typedef struct { - char mode; - char BID[13]; - union - { /* array named screen */ - struct - { - unsigned short msgno; - unsigned short timestamp; - }; - CIRCUIT * conn; - } u; -} BIDRec, *BIDRecP; - - -typedef struct WPDBASE{ /* 194 bytes */ - char callsign[7]; - char name[13]; - unsigned char Type; - unsigned char changed; - unsigned short seen; - long long last_modif; - long long last_seen; - char first_homebbs[41]; - char secnd_homebbs[41]; - char first_zip[9]; - char secnd_zip[9]; - char first_qth[31]; - char secnd_qth[31]; -} WPRec, * WPRecP; - -#pragma pack() - -struct FWDBAND -{ - time_t FWDStartBand; - time_t FWDEndBand; -}; - - - -struct BBSForwardingInfo -{ - // Holds info for forwarding - - BOOL Enabled; // Forwarding Enabled - char ** ConnectScript; // Main Connect Script - char ** TempConnectScript; // Used with FWD command. - int ScriptIndex; // Next line in script - BOOL MoreLines; // Set until script is finsihed - - char ** TOCalls; // Calls in to field - char ** ATCalls; // Calls in ATBBS field - char ** HaddressesP; // Heirarchical Addresses for Personals to forward to (as stored) - char *** HADDRSP; // Heirarchical Addresses for Personals to forward to - char ** Haddresses; // Heirarchical Addresses to forward to (as stored) - char *** HADDRS; // Heirarchical Addresses to forward to - int * HADDROffet; // Elements added to complete the HR. At least n+1 must match to forward - char ** FWDTimes; // Time bands to forward - struct FWDBAND ** FWDBands; - int MsgCount; // Messages for this BBS - BOOL ReverseFlag; // Set if BBS wants to poll for reverse forwarding - BOOL Forwarding; // Forward in progress - int MaxFBBBlockSize; - BOOL AllowBlocked; // Allow FBB Blocked - BOOL AllowCompressed; // Allow FBB Compressed - BOOL AllowB1; // Enable B1 - BOOL AllowB2; // Enable B2 - BOOL SendCTRLZ; // Send Ctrl/z instead of /ex - BOOL PersonalOnly; // Only Forward Personals - BOOL SendNew; // Forward new messages immediately - int FwdInterval; - int RevFwdInterval; - int FwdTimer; - time_t LastReverseForward; - char *BBSHA; // HA of BBS - char ** BBSHAElements; // elements of HA of BBS - int ConTimeout; -// char UserCall[10]; // User we are forwarding on behalf of (Currently only for RMS) -// int UserIndex; // index of User we are forwarding on behalf of (Currently only for RMS) -}; - - -struct FBBHeaderLine -{ - // Holds the info from the (up to) 5 headers presented at the start of a Forward Block - - char Format; // Ascii or Binary - char MsgType; // P B etc - char From[7]; // Sender - char ATBBS[41]; // BBS of recipient (@ Field) - char To[7]; // Recipient - char BID[13]; - int Size; - int CSize; // Compresses Size (B2 proto) - BOOL B2Message; // Set if an FC type - UCHAR * CompressedMsg; // Compressed Body fo B2 - struct MsgInfo * FwdMsg; // Header so we can mark as complete -}; - -#define MAXSTACK 20 -//#define MAXLINE 10000 -#define INPUTLEN 512 - -#define MAXLINES 1000 -#define LINELEN 200 - -char RTFHeader[4000]; - -int RTFHddrLen; - -struct ConsoleInfo -{ - struct ConsoleInfo * next; - CIRCUIT * Console; - int BPQStream; - WNDPROC wpOrigInputProc; - HWND hConsole; - HWND hwndInput; - HWND hwndOutput; - HMENU hMenu; // handle of menu - RECT ConsoleRect; - RECT OutputRect; - - int Height, Width, LastY; - - int ClientHeight, ClientWidth; - char kbbuf[INPUTLEN]; - int kbptr; - - char * readbuff; // Malloc'ed - int readbufflen; // Current Length - char * KbdStack[MAXSTACK]; - - int StackIndex; - - BOOL Bells; - BOOL FlashOnBell; // Flash instead of Beep - BOOL StripLF; - - BOOL WarnWrap; - BOOL FlashOnConnect; - BOOL WrapInput; - BOOL CloseWindowOnBye; - - unsigned int WrapLen; - int WarnLen; - int maxlinelen; - - int PartLinePtr; - int PartLineIndex; // Listbox index of (last) incomplete line - - DWORD dwCharX; // average width of characters - DWORD dwCharY; // height of characters - DWORD dwClientX; // width of client area - DWORD dwClientY; // height of client area - DWORD dwLineLen; // line length - int nCaretPosX; // horizontal position of caret - int nCaretPosY; // vertical position of caret - - COLORREF FGColour; // Text Colour - COLORREF BGColour; // Background Colour - COLORREF DefaultColour; // Default Text Colour - - int CurrentLine; // Line we are writing to in circular buffer. - - int Index; - BOOL SendHeader; - BOOL Finished; - - char OutputScreen[MAXLINES][LINELEN]; - - int Colourvalue[MAXLINES]; - int LineLen[MAXLINES]; - - int CurrentColour; - int Thumb; - int FirstTime; - BOOL Scrolled; // Set if scrolled back - int RTFHeight; // Height of RTF control in pixels - -}; - - -struct MSESSION -{ - struct MSESSION * Next; - unsigned int Key; - char * FileName; - char * OrigTimeStamp; - unsigned char * Message; - int MessageLen; - unsigned char * BlockList; - char * ID; - int BlockSize; - int BlockCount; - int BlocksReceived; - BOOL Completed; - time_t Created; - time_t LastUpdated; - int Index; // Line in Display -}; - -VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...); -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); -VOID FBBputs(CIRCUIT * conn, char * buf); -void makelinks(void); -VOID * _zalloc(size_t len); -VOID FreeChatMemory(); -VOID ChatTimer(); -VOID nputs(CIRCUIT * conn, char * buf); -VOID node_close(); -VOID removelinks(); -VOID SetupChat(); -VOID SendChatLinkStatus(); -VOID ClearChatLinkStatus(); -VOID Send_MON_Datagram(UCHAR * Msg, DWORD Len); - -#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) - -int EncryptPass(char * Pass, char * Encrypt); -VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); - -// TCP Connections. FOr the moment SMTP or POP3 - -typedef struct SocketConnectionInfo -{ - struct SocketConnectionInfo * Next; - int Number; // Number of record - for Connections display - SOCKET socket; - SOCKADDR_IN sin; - int Type; // SMTP or POP3 - BOOL AMPR; // Set if sending to an AMPR.ORG server - char FromDomain[50]; // Domain we are sending from - struct UserInfo * bbs; // BBS dor forwarding to AMPR - int State; // Transaction State Machine - UCHAR CallSign[10]; - UCHAR TCPBuffer[3000]; // For converting byte stream to messages - int InputLen; // Data we have alreasdy = Offset of end of an incomplete packet; - - char * MailFrom; // Envelope Sender and Receiver - char ** RecpTo; // May be several Recipients - int Recipients; - - UCHAR * MailBuffer; // Mail Message being received. malloc'ed as needed - int MailBufferSize; // Total Malloc'ed size. Actual size is in MailSize - int MailSize; - int Flags; - - struct UserInfo * POP3User; - struct MsgInfo ** POP3Msgs; // Header List of messages for this uaer - int POP3MsgCount; // No of Messages - int POP3MsgNum; // Sequence number of message being received - - struct MsgInfo * SMTPMsg; // message for this SMTP connection - - UCHAR * SendBuffer; // Message being sent if socket is busy. malloc'ed as needed - int SendBufferSize; // Total Malloc'ed size. Actual size is in MailSize - int SendSize; // Bytes in buffer - int SendPtr; // next byte to send when ready - - struct NNTPRec * NNTPGroup; // Currently Selected Group - int NNTPNum; // Currenrly Selected Msg Number - int Timeout; // Used to close a session that is open too long - -} SocketConn; - -typedef struct KEYVALUES -{ - char * Key; - char * Value; -} KeyValues; - -typedef struct WEBMAILINFO -{ - // Info for HTML Forms Processing - - struct HtmlFormDir * Dir; // HTML Directory - char * txtFileName; // Template Name for current message - char * InputHTMLName; // Template to input message - char * DisplayHTMLName; // Template to display message - char * ReplyHTMLName; // Template for replying to message - char * txtFile; // Template data - char * OrigTo; // To field when template loaded - char * OrigSubject; // Subject field when template loaded - char * OrigBody; // Msg text when template loaded - char * OrigBID; - char OrigType; - char * To; - char * CC; - char * Subject; - char * Body; - char * BID; - char Type; - struct MsgInfo * Msg; // Msg record if replying - KeyValues txtKeys[1000]; // Key/Value pairs for txt template. Used when creating or displaying - KeyValues XMLKeys[1000]; // Key/Value pairs from XML attachment - BOOL isReply; - char * XMLName; - char * XML; // XML attachment - int XMLLen; - int Files; - char * FileName[100]; // Attachments - char * FileBody[100]; - int FileLen[100]; - - char * Header; - int HeaderLen; - - char * Footer; - int FooterLen; - - char * Reply; // put in here to save passing lots of parameters - int * RLen; - - BOOL Winlink; - BOOL P2P; - BOOL Packet; - - int CurrentMessageIndex; // Index of message currently displayed (for Prev and Next) - -}WebMailInfo; - -#define SMTPServer 1 -#define POP3SLAVE 2 -#define SMTPClient 3 -#define POP3Client 4 -#define NNTPServer 5 - -// State Values - -#define GettingUser 1 -#define GettingPass 2 -#define Authenticated 4 - -#define Connecting 8 - -// SMTP Master - -#define WaitingForGreeting 16 -#define WaitingForHELOResponse 32 -#define WaitingForFROMResponse 64 -#define WaitingForTOResponse 128 -#define WaitingForDATAResponse 256 -#define WaitingForBodyResponse 512 -#define WaitingForAUTHResponse 1024 - -// POP3 Master - -#define WaitingForUSERResponse 32 -#define WaitingForPASSResponse 64 -#define WaitingForSTATResponse 128 -#define WaitingForUIDLResponse 256 -#define WaitingForLISTResponse 512 -#define WaitingForRETRResponse 512 -#define WaitingForDELEResponse 1024 -#define WaitingForQUITResponse 2048 - - -#define SE 240 // End of subnegotiation parameters -#define NOP 241 //No operation -//#define DM 242 //Data mark Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. -#define BRK 243 //Break Indicates that the "break" or "attention" key was hi. -#define IP 244 //Suspend Interrupt or abort the process to which the NVT is connected. -#define AO 245 //Abort output Allows the current process to run to completion but does not send its output to the user. -#define AYT 246 //Are you there Send back to the NVT some visible evidence that the AYT was received. -#define EC 247 //Erase character The receiver should delete the last preceding undeleted character from the data stream. -#define EL 248 //Erase line Delete characters from the data stream back to but not including the previous CRLF. -#define GA 249 //Go ahead Under certain circumstances used to tell the other end that it can transmit. -#define SB 250 //Subnegotiation Subnegotiation of the indicated option follows. -#define WILL 251 //will Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. -#define WONT 252 //wont Indicates the refusal to perform, or continue performing, the indicated option. -#define DOx 253 //do Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. -#define DONT 254 //dont Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. -#define IAC 255 - -#define suppressgoahead 3 //858 -//#define Status 5 //859 -//#define echo 1 //857 -#define timingmark 6 //860 -#define terminaltype 24 //1091 -#define windowsize 31 //1073 -#define terminalspeed 32 //1079 -#define remoteflowcontrol 33 //1372 -#define linemode 34 //1184 -#define environmentvariables 36 //1408 - -BOOL Initialise(); -#ifdef WIN32 -INT_PTR CALLBACK ConfigWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -#endif -int DisplaySessions(); -int DoStateChange(int Stream); -int DoReceivedData(int Stream); -int DoBBSMonitorData(int Stream); -int Connected(int Stream); -int Disconnected(int Stream); -//int Socket_Accept(int SocketId); -//int Socket_Data(int SocketId,int error, int eventcode); -int DataSocket_Read(SocketConn * sockptr, SOCKET sock); -int DataSocket_Write(SocketConn * sockptr, SOCKET sock); -int DataSocket_Disconnect(SocketConn * sockptr); -int RefreshMainWindow(); -int Terminate(); -int SendtoSocket(SOCKET sock,char * Msg); -int WriteLog(char * msg); -int ConnectState(int Stream); -UCHAR * EncodeCall(UCHAR * Call); -int ParseIniFile(char * fn); -struct UserInfo * AllocateUserRecord(char * Call); -struct MsgInfo * AllocateMsgRecord(); -BIDRec * AllocateBIDRecord(); -BIDRec * AllocateTempBIDRecord(); -struct UserInfo * LookupCall(char * Call); -BIDRec * LookupBID(char * BID); -BIDRec * LookupTempBID(char * BID); -VOID RemoveTempBIDS(CIRCUIT * conn); -VOID SaveUserDatabase(); -VOID GetUserDatabase(); -VOID GetMessageDatabase(); -VOID SaveMessageDatabase(); -VOID GetBIDDatabase(); -VOID SaveBIDDatabase(); -VOID GetWPDatabase(); -VOID CopyWPDatabase(); -VOID SaveWPDatabase(); -VOID GetBadWordFile(); -WPRec * LookupWP(char * Call); -VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user); -VOID ProcessLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); -VOID ProcessChatLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); -VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user); -int QueueMsg( ConnectionInfo * conn, char * msg, int len); -VOID SendUnbuffered(int stream, char * msg, int len); -//int GetFileList(char * Dir); -BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp); -void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); -void DoKillCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); -void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context); -void DoReadCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); -void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); -int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call); -int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call); -void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); - -VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB); -int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); -int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); -int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); -void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); -void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); -int GetUserMsg(int m, char * Call, BOOL SYSOP); -void Flush(ConnectionInfo * conn); -VOID ClearQueue(ConnectionInfo * conn); -void TrytoSend(); -void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); -struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop); -char * ReadMessageFile(int msgno); -char * ReadInfoFile(char * File); -char * FormatDateAndTime(time_t Datim, BOOL DateOnly); -int CriticalErrorHandler(char * error); -BOOL DoSendCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); -BOOL CreateMessage(ConnectionInfo * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title); -VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); -VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); -VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg); -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 GetConfigFromRegistry(); -VOID Parse_SID(CIRCUIT * conn, char * SID, int len); -VOID ProcessMBLLine(CIRCUIT * conn, struct UserInfo * user, UCHAR* Buffer, int len); -VOID ProcessFBBLine(ConnectionInfo * conn, struct UserInfo * user, UCHAR * Buffer, int len); -VOID SetupNextFBBMessage(CIRCUIT * conn); -BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); -int PrintMessages(HWND hDlg, int Count, int * Indexes); -int check_fwd_bit(char *mask, int bbsnumber); -void set_fwd_bit(char *mask, int bbsnumber); -void clear_fwd_bit (char *mask, int bbsnumber); -VOID SetupForwardingStruct(struct UserInfo * user); -BOOL Forward_Message(struct UserInfo * user, struct MsgInfo * Msg); -VOID StartForwarding (int BBSNumber, char ** TempScript); -BOOL Reverse_Forward(); -int ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len); -BOOL FBBDoForward(CIRCUIT * conn); -BOOL FindMessagestoForward(CIRCUIT * conn); -BOOL SeeifMessagestoForward(int BBSNumber, CIRCUIT * Conn); -int CountMessagestoForward(struct UserInfo * user); - -VOID * GetMultiLineDialogParam(HWND hDialog, int DLGItem); - -#define LIBCONFIG_STATIC -#include "libconfig.h" -VOID * GetMultiStringValue(config_setting_t * hKey, char * ValueName); -VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName); - -int MultiLineDialogToREG_MULTI_SZ(HWND hWnd, int DLGItem, HKEY hKey, char * ValueName); -int Do_BBS_Sel_Changed(HWND hDlg); -VOID FreeForwardingStruct(struct UserInfo * user); -VOID FreeList(char ** Hddr); -int Do_User_Sel_Changed(HWND hDlg); -int Do_Msg_Sel_Changed(HWND hDlg); -VOID Do_Save_Msg(); -VOID Do_Add_User(HWND hDlg); -VOID Do_Delete_User(HWND hDlg); -VOID FlagSentMessages(CIRCUIT * conn, struct UserInfo * user); -VOID HoldSentMessages(CIRCUIT * conn, struct UserInfo * user); -VOID Do_Save_User(HWND hDlg, BOOL ShowBox); -VOID DeleteBBS(); -VOID AddBBS(); -VOID SaveBBSConfig(); -BOOL GetChatConfig(); -VOID SaveChatConfig(); -VOID SaveISPConfig(); -VOID SaveFWDConfig(); -VOID SaveMAINTConfig(); -VOID SaveWelcomeMsgs(); -VOID SavePrompts(); -VOID ReinitializeFWDStruct(struct UserInfo * user); -VOID CopyBIDDatabase(); -VOID CopyMessageDatabase(); -VOID CopyUserDatabase(); -VOID FWDTimerProc(); -VOID CreateMessageFromBuffer(CIRCUIT * conn); -VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...); -VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...); -VOID FreeOverrides(); -VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length); -struct UserInfo * FindRMS(); -VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo); -BOOL ConnecttoBBS (struct UserInfo * user); -BOOL SetupNewBBS(struct UserInfo * user); -VOID CreateRegBackup(); -VOID SaveFilters(HWND hDlg); -BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type); -BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID); -BOOL CheckifLocalRMSUser(char * FullTo); -VOID DoWPLookup(ConnectionInfo * conn, struct UserInfo * user, char Type, char *Context); -BOOL wildcardcompare(char * Target, char * Match); -VOID SendWarningToSYSOP(struct MsgInfo * Msg); -VOID DoEditUserCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID DoPollRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID DoShowRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID DoSetIdleTime(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID DoFwdCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID SaveFwdParams(char * Call, struct BBSForwardingInfo * ForwardingInfo); -VOID DoAuthCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); -VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); -VOID DoReroute(CIRCUIT * conn, struct UserInfo * user); - -// FBB Routines - -VOID SendCompressed(CIRCUIT * conn, struct MsgInfo * FwdMsg); -VOID SendCompressedB2(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); -VOID UnpackFBBBinary(CIRCUIT * conn); -void Decode(CIRCUIT * conn, __int16 DecodeOnly); -//long Encode(char * in, char * out, long inlen, BOOL B1Protocol); - -BOOL CreateB2Message(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader, char * Rline); -VOID SaveFBBBinary(CIRCUIT * conn); -BOOL LookupRestart(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); -BOOL DoWeWantIt(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); - -// Console Routines - -BOOL CreateConsole(int Stream); -int WritetoConsoleWindow(int Stream, char * Msg, int len); -int ToggleParam(HMENU hMenu, HWND hWnd, BOOL * Param, int Item); -void CopyRichTextToClipboard(HWND hWnd); -void CopyToClipboard(HWND hWnd); -VOID CloseConsole(int Stream); - -// Monitor Routines - -BOOL CreateMonitor(); -int WritetoMonitorWindow(char * Msg, int len); - -BOOL CreateDebugWindow(); -VOID WritetoDebugWindow(char * Msg, int len); -VOID ClearDebugWindow(); -int RemoveLF(char * Message, int len); - -// Utilities - -BOOL isdigits(char * string); -void GetSemaphore(struct SEM * Semaphore, int ID); -void FreeSemaphore(struct SEM * Semaphore); - -VOID __cdecl Debugprintf(const char * format, ...); -VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...); - -VOID SortBBSChain(); -VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG); -int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup); - -// TCP Routines - -BOOL InitialiseTCP(); -VOID SetupListenSet(); -VOID TCPTimer(); -VOID TCPFastTimer(); -int Socket_Data(int sock, int error, int eventcode); -static int Socket_Accept(SOCKET SocketId); -int Socket_Connect(SOCKET sock, int Error); -VOID ProcessSMTPServerMessage(SocketConn * sockptr, char * Buffer, int Len); -int CreateSMTPMessage(SocketConn * sockptr, int i, char * MsgTitle, time_t Date, char * MsgBody, int Msglen, BOOL B2Flag); -BOOL CreateSMTPMessageFile(char * Message, struct MsgInfo * Msg); -SOCKET CreateListeningSocket(int Port); -int TidyString(char * MailFrom); -VOID ProcessPOP3ServerMessage(SocketConn * sockptr, char * Buffer, int Len); -char *str_base64_encode(char *str); -int b64decode(char *str); -SocketConn * SMTPConnect(char * Host, int Port, BOOL AMPR, struct MsgInfo * Msg, char * MsgBody); -BOOL POP3Connect(char * Host, int Port); -VOID ProcessSMTPClientMessage(SocketConn * sockptr, char * Buffer, int Len); -VOID ProcessPOP3ClientMessage(SocketConn * sockptr, char * Buffer, int Len); -int CreatePOP3Message(char * From, char * To, char * MsgTitle, time_t Date, char * MsgBody, int MsgLen, BOOL B2Flag); -void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags); -int SendSock(SocketConn * sockptr, char * msg); -VOID __cdecl sockprintf(SocketConn * sockptr, const char * format, ...); -VOID SendFromQueue(SocketConn * sockptr); -VOID SendMultiPartMessage(SocketConn * sockptr, struct MsgInfo * Msg, UCHAR * msgbytes); -int CountMessagesTo(struct UserInfo * user, int * Unread); - -BOOL SendtoISP(); - -// NNTP ROutines - -VOID InitialiseNNTP(); -VOID BuildNNTPList(struct MsgInfo * Msg); -int NNTP_Data(int sock, int error, int eventcode); -int NNTP_Accept(SOCKET SocketId); - -VOID * GetOverrides(config_setting_t * group, char * ValueName); -VOID * RegGetOverrides(HKEY hKey, char * ValueName); - -VOID DoHouseKeeping(BOOL Mainual); -VOID ExpireMessages(); -VOID KillMsg(struct MsgInfo * Msg); -BOOL RemoveKilledMessages(); -VOID Renumber_Messages(); -BOOL ExpireBIDs(); -VOID MailHousekeepingResults(); -VOID CreateBBSTrafficReport(); -VOID CreateWPReport(); - -// WP Routines - -VOID ProcessWPMsg(char * MailBuffer, int Size, char * FisrtRLine); -VOID GetWPInfoFromRLine(char * From, char * FirstRLine, time_t RLineTime); -VOID UpdateWPWithUserInfo(struct UserInfo * user); -VOID GetWPBBSInfo(char * Rline); - -// UI Routines - -VOID SetupUIInterface(); -VOID Free_UI(); -VOID SendLatestUI(int Port); -VOID SendMsgUI(struct MsgInfo * Msg); -static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue); -VOID SeeifBBSUIFrame(struct _MESSAGEX * buff, int len); -struct MsgInfo * FindMessageByNumber(int msgno); -int CountConnectionsOnPort(int CheckPort); - -// Message Routing Routtines - -VOID SetupHAElements(struct BBSForwardingInfo * ForwardingInfo); -VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo); -VOID SetupHAddresesP(struct BBSForwardingInfo * ForwardingInfo); -VOID SetupMyHA(); -VOID SetupFwdAliases(); -struct Continent * FindContinent(char * Name); -int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn); -BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); -BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo); -BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); -BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); -BOOL CheckBBSHElements(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); -BOOL CheckBBSHElementsFlood(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); -int CheckBBSToForNTS(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo); -int CheckBBSATListWildCarded(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); - -VOID ReRouteMessages(); - -VOID initUTF8(); -int Is8Bit(unsigned char *cpt, int len); -int IsUTF8(unsigned char *ptr, int len); -int IsUTF8(unsigned char *ptr, int len); -int WebIsUTF8(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); - - -VOID FreeWebMailMallocs(); - -extern int _MYTIMEZONE; - -extern HKEY REGTREE; -extern char * REGTREETEXT; - -extern HBRUSH bgBrush; -extern BOOL cfgMinToTray; - -extern CIRCUIT * Console; - -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 BOOL OnlyKnown; - -extern BOOL AllowAnon; -extern BOOL UserCantKillT; -extern BOOL DontNeedHomeBBS; - -extern int LatestMsg; -extern char BBSName[]; -extern char SYSOPCall[]; -extern char BBSSID[]; -extern char NewUserPrompt[]; - -extern char * WelcomeMsg; -extern char * NewWelcomeMsg; -extern char * ChatWelcomeMsg; -extern char * NewChatWelcomeMsg; -extern char * ExpertWelcomeMsg; - -extern char * Prompt; -extern char * NewPrompt; -extern char * ExpertPrompt; - -// Filter Params - -extern char ** RejFrom; // Reject on FROM Call -extern char ** RejTo; // Reject on TO Call -extern char ** RejAt; // Reject on AT Call -extern char ** RejBID; - -extern char ** HoldFrom; // Hold on FROM Call -extern char ** HoldTo; // Hold on TO Call -extern char ** HoldAt; // Hold on AT Call -extern char ** HoldBID; - -// Send WP Params - -extern BOOL SendWP; -extern char SendWPVIA[81]; -extern char SendWPTO[11]; -extern int SendWPType; - -extern int Ver[4]; - -extern struct MsgInfo ** MsgHddrPtr; - -extern BIDRec ** BIDRecPtr; -extern int NumberofBIDs; - -extern struct NNTPRec * FirstNNTPRec; -//extern int NumberofNNTPRecs; - - -extern int NumberofMessages; -extern int FirstMessageIndextoForward; - -extern WPRec ** WPRecPtr; -extern int NumberofWPrecs; - -extern struct SEM AllocSemaphore; -extern struct SEM ConSemaphore; -extern struct SEM MsgNoSemaphore; - -extern struct MsgInfo * MsgnotoMsg[]; // Message Number to Message Slot List. - - -extern char hostname[]; -extern char RtUsr[]; -extern char RtUsrTemp[]; -extern char RtKnown[]; -extern int AXIPPort; -extern BOOL NeedStatus; - -extern BOOL ISP_Gateway_Enabled; -extern BOOL SMTPAuthNeeded; - - -extern int MaxMsgno; -extern int BidLifetime; -extern int MaxAge; -extern int MaintInterval; -extern int MaintTime; -extern int UserLifetime; - -extern int MaxRXSize; -extern int MaxTXSize; - -extern char OurNode[]; -extern char OurAlias[]; -extern BOOL SMTPMsgCreated; - -extern HINSTANCE hInst; -extern HWND hWnd; -extern RECT MainRect; - -extern char BBSName[]; -extern char HRoute[]; -extern char AMPRDomain[]; -extern BOOL SendAMPRDirect; -extern int BBSApplNum; -extern int SMTPInPort; -extern int POP3InPort; -extern int NNTPInPort; -extern BOOL RemoteEmail; - -extern int MaxStreams; -extern UCHAR * OtherNodes; -extern struct UserInfo * BBSChain; // Chain of users that are BBSes -extern struct UserInfo ** UserRecPtr; -extern int NumberofUsers; -extern struct MsgInfo ** MsgHddrPtr; -extern int NumberofMessages; -extern int HighestBBSNumber; -extern HMENU hFWDMenu; // Forward Menu Handle -extern char zeros[]; // For forward bitmask tests -extern BOOL EnableUI; -extern BOOL RefuseBulls; -extern BOOL SendSYStoSYSOPCall; -extern BOOL SendBBStoSYSOPCall; -extern BOOL DontHoldNewUsers; -extern BOOL DefaultNoWINLINK; -extern BOOL UIEnabled[]; -extern BOOL UINull[]; -extern BOOL UIMF[]; -extern BOOL UIHDDR[]; -extern char * UIDigi[]; -extern int MailForInterval; -extern char MailForText[]; - -extern BOOL ISP_Gateway_Enabled; - -extern char MyDomain[]; // Mail domain for BBS<>Internet Mapping - -extern char ISPSMTPName[]; -extern char ISPEHLOName[]; -extern int ISPSMTPPort; - -extern char ISPPOP3Name[]; -extern int ISPPOP3Port; -extern int ISPPOP3Interval; - -extern char ISPAccountName[]; -extern char ISPAccountPass[]; -extern char EncryptedISPAccountPass[]; -extern int EncryptedPassLen; -extern char *month[]; - -extern HWND hDebug; -extern RECT MonitorRect; -extern RECT DebugRect; -extern HWND hMonitor; -//extern HWND hConsole; -//extern RECT ConsoleRect; -extern int LogAge; -extern BOOL DeletetoRecycleBin; -extern BOOL SuppressMaintEmail; -extern BOOL SaveRegDuringMaint; -extern BOOL SendWP; -extern BOOL OverrideUnsent; -extern BOOL SendNonDeliveryMsgs; -extern BOOL GenerateTrafficReport; - -extern double PR; -extern double PUR; -extern double PF; -extern double PNF; -extern int BF; -extern int BNF; -extern int NTSD; -extern int NTSU; -extern int NTSF; -extern int AP; -extern int AB; - -extern struct Override ** LTFROM; -extern struct Override ** LTTO; -extern struct Override ** LTAT; - -extern time_t LastHouseKeepingTime; -extern time_t LastTrafficTime; - -extern char * MyElements[]; -extern char ** AliasText; -extern struct ALIAS ** Aliases; - -extern BOOL ReaddressLocal; -extern BOOL ReaddressReceived; -extern BOOL WarnNoRoute; -extern BOOL SendPtoMultiple; -extern BOOL Localtime; - -struct ConsoleInfo * ConsHeader[2]; - -extern BOOL NeedHomeBBS; -extern char ConfigName[250]; -extern BOOL UsingingRegConfig; - -extern BOOL MulticastRX; - -extern BOOL FilterWPBulls; -extern BOOL NoWPGuesses; -extern char ** SendWPAddrs; // Replacers WP To and VIA - -extern BOOL DontCheckFromCall; - -// YAPP stuff - -#define SOH 1 -#define STX 2 -#define ETX 3 -#define EOT 4 -#define ENQ 5 -#define ACK 6 -#define DLE 0x10 -#define NAK 0x15 -#define CAN 0x18 +#ifndef WINVER // Allow use of features specific to Windows XP or later. +#define WINVER 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. +#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later. +#endif + +#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later. +#define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE. +#endif + + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define _CRT_SECURE_NO_DEPRECATE +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#define LIBCONFIG_STATIC +#include + +#include "compatbits.h" + +#ifndef LINBPQ +#include "bpq32.h" +#include "BPQMailrc.h" +#include "dbghelp.h" +#else +#include "CHeaders.h" +#endif + +#include "asmstrucs.h" + + +#define NEWROUTING + + + +// Standard __except handler for try/except + +VOID CheckProgramErrors(); +VOID WriteMiniDump(); + +extern int ProgramErrors; + +extern struct _EXCEPTION_POINTERS exinfox; + +#ifdef WIN32 +Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg); + +#define My__except_Routine(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + CheckProgramErrors();\ + WriteMiniDump();\ +} + + +/* +#define My__except_Routine(Message) \ +__except(memcpy(&exinfox, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Dump_Process_State(&exinfox, Message);\ + CheckProgramErrors();\ +} + +#define My__except_RoutineWithDisconnect(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + FreeSemaphore(&ChatSemaphore);\ + if (conn->BPQStream < 0)\ + CloseConsole(conn->BPQStream);\ + else\ + Disconnect(conn->BPQStream);\ +} +*/ +#define My_except_RoutineWithDiscBBS(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + if (conn->BPQStream < 0)\ + CloseConsole(conn->BPQStream);\ + else\ + Disconnect(conn->BPQStream);\ + CheckProgramErrors();\ +} +#endif +#define MAXUSERNAMELEN 6 + +#define WSA_ACCEPT WM_USER + 1 +#define WSA_CONNECT WM_USER + 2 +#define WSA_DATA WM_USER + 3 +#define NNTP_ACCEPT WM_USER + 4 +#define NNTP_DATA WM_USER + 5 + +#ifdef _DEBUG + +VOID * _malloc_dbg_trace(int len, int type, char * file, int line); + +#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define calloc(c, s) _calloc_dbg(c, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define realloc(p, s) _realloc_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define _recalloc(p, c, s) _recalloc_dbg(p, c, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define _expand(p, s) _expand_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define free(p) _free_dbg(p, _NORMAL_BLOCK) +#define _strdup(s) _strdup_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) + + +#define zalloc(s) _zalloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) +#else +#define zalloc(s) _zalloc(s) +#endif + +#ifdef LINBPQ + +#undef zalloc +#define zalloc _zalloc + +#endif + +VOID * _zalloc_dbg(size_t len, int type, char * file, int line); + +#define LOG_BBS 0 +#define LOG_CHAT 1 +#define LOG_TCP 2 +#define LOG_DEBUG_X 3 + + +//Chat Duplicate suppression Code + +#define MAXDUPS 10 // Number to keep +#define DUPSECONDS 5 // TIme to Keep + +struct DUPINFO +{ + time_t DupTime; + char DupUser[10]; + char DupText[100]; +}; + + +struct UserRec +{ + char * Callsign; + char * UserName; + char * Password; +}; + + + +typedef struct ConnectionInfo_S +{ + struct ConnectionInfo_S *next; + PROC *proc; + UCHAR RadioOnlyMode; // T or R flag for Radio Only mode. + + int Number; // Number of record - for Connections display + BOOL Active; + int BPQStream; + int paclen; + UCHAR Callsign[11]; // Station call including SSID + BOOL GotHeader; + UCHAR InputMode; // Line by Line or Binary or YAPP + + UCHAR * InputBuffer; + int InputBufferLen; + int InputLen; // Data we have already = Offset of end of an incomplete packet; + + struct UserInfo * UserPointer; + int Retries; + int LoginState; // 1 = user ok, 2 = password ok + int Flags; + + // Data to the user is kept in a malloc'd buffer. This can be appended to, + // and data sucked out under both terminal and system flow control. PACLEN is + // enfored when sending to node. + + UCHAR * OutputQueue; // Messages to user + int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + int OutputGetPointer; // Next byte to send. When Getpointer = Queue Length all is sent - free the buffer and start again. + + int CloseAfterFlush; // Close session when all sent. Set to 100ms intervals to wait. + + int ErrorCount; // Invalid Command count + BOOL Paging; // Set if user wants paging + int LinesSent; // Count when paging + int PageLen; // Lines per page + + UCHAR * MailBuffer; // Mail Message being received + UCHAR * CopyBuffer; // Mail Message being forwarded + int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct + + long lastmsg; // Last Listed. Stored here, updated in user record only on clean close + BOOL sysop; // Set if user is authenticated as a sysop + BOOL Secure_Session; // Set if Local Terminal, or Telnet connect with SYSOP status + UINT BBSFlags; // Set if defined as a bbs and SID received + struct MsgInfo * TempMsg; // Header while message is being received + struct MsgInfo * FwdMsg; // Header while message is being forwarded + + char ** To; // May be several Recipients + int ToCount; + + int BBSNumber; // The BBS number (offset into bitlist of BBSes to forward a message to + int NextMessagetoForward; // Next index to check in forward cycle + BOOL BPQBBS; // Set if SID indicates other end is BPQ + char MSGTYPES[20]; // Any MSGTYPEFLAGS + BOOL SendT; // Send T messages + BOOL SendP; // Send P messages + BOOL SendB; // Send Bulls + BOOL SendWL2KFW; // send ;FW: + int MaxBLen; // Max Size for this session + int MaxPLen; // Max Size for this session + int MaxTLen; // Max Size for this session + BOOL DoReverse; // Request Reverse Forward + char LastForwardType; // Last type of messages forwarded + struct FBBHeaderLine * FBBHeaders; // The Headers from an FFB forward block + char FBBReplyChars[36]; //The +-=!nnnn chars for the 5 proposals + int FBBReplyIndex; // current Reply Pointer + int FBBIndex; // current propopsal number + int RestartFrom; // Restart position + BOOL NeedRestartHeader; // Set if waiting for 6 byte restart header + BOOL DontSaveRestartData; // Set if corrupt data received + BOOL FBBMsgsSent; // Messages need to be maked as complete when next command received + UCHAR FBBChecksum; // Header Checksum + BOOL OpenBCM; // OpenBCM mode (escape -xFF chars) + BOOL InTelnetExcape; // Last Char was 0xff + BOOL LocalMsg; // Set if current Send command is for a local user + BOOL NewUser; // Set if first time user has accessed BBS + BOOL Paclink; // Set if receiving messages from Paclink + BOOL RMSExpress; // Set if receiving messages from RMS Express + BOOL WL2K; // Set if communicating with a CMS + BOOL PAT; // Set if communicating with PAT + char ** PacLinkCalls; // Calls we are getting messages for + BOOL SkipPrompt; // Set if a remote node sends a > at the end of his CTEXT + BOOL SkipConn; // Node sends "connected" in its CTEXT + int Watchdog; // Hung Circuit Detect. + int SessType; // BPQ32 sesstype bits + +#define Sess_L2LINK 1 +#define Sess_SESSION 2 +#define Sess_UPLINK 4 +#define Sess_DOWNLINK 8 +#define Sess_BPQHOST 0x20 +#define Sess_PACTOR 0x40 + + HANDLE DebugHandle; // File Handle for session-based debugging + + char ARQFilename[256]; // Filename from ARQ:FILE:: Header + int ARQClearCount; // To make sure queues are flushed when sending + + int SIDResponseTimer; // Used to detect incomplete handshake + + char PQChallenge[20]; // Secure User logon challange + char SecureMsg[20]; // CMS Secure Signon Response + int MCastListenTime; // Time to run session for + + int YAPPLen; // Bytes sent/received of YAPP Message + long YAPPDate; // Date for received file - if set enables YAPPC + + int SyncCompressedLen; + int SyncXMLLen; + int SyncMsgLen; + char * SyncHost; // Saved so can send "request sync" + int SyncPort; + UCHAR * SyncMessage; // Compressed SYNC message to send + + // These are used to detect CRLF split over a packet boundary + int usingCR; // Session is (normally) using CR as terminator + int lastLineEnd; // Terminator for current line + + struct ConnectionInfo_S * SysopChatStream; // Stream sysop is chatting to + +} ConnectionInfo, CIRCUIT; + +// Flags Equates + +#define GETTINGUSER 1 +#define GETTINGBBS 2 +#define CHATMODE 4 +#define GETTINGTITLE 8 +#define GETTINGMESSAGE 16 +#define CHATLINK 32 // Link to another Chat Node +#define SENDTITLE 64 +#define SENDBODY 128 +#define WAITPROMPT 256 // Waiting for prompt after message +#define PROPOSINGSYNCMSG 512 // Sent proposal to SYNC, waiting response +#define SENDINGSYNCMSG 1024 // Sent message to SYNC, waiting response +#define REQUESTINGSYNC 2048 +#define GETTINGSYNCMESSAGE 4096 // Receiving body of a SYNC message + +// BBSFlags Equates + +#define BBS 1 +#define FBBForwarding 2 +#define FBBCompressed 4 +#define FBBB1Mode 8 +#define FBBB2Mode 16 +#define RunningConnectScript 32 +#define MBLFORWARDING 64 // MBL Style Frwarding- waiting for OK/NO or Prompt following message +#define TEXTFORWARDING 128 // Plain Text forwarding +#define OUTWARDCONNECT 256 // We connected to them +#define FLARQMODE 512 // Message from FLARQ +#define FLARQMAIL 1024 // Sending FLARQ Format Message +#define ARQMAILACK 2048 // Waiting for all data to be acked +#define NEEDLF 4096 // Add LF to forward script commands (fro Telnet +#define MCASTRX 8192 // Stream in Multicast RX Mode +#define DISCONNECTING 16384 // Disconnect sent to Node +#define YAPPTX 0x008000 // Sending YAPP file +#define SYSOPCHAT 0x010000 // Chatting to BBS console +#define WINLINKRO 0x020000 // WL2K RO (no J in SID) +#define SYNCMODE 0x040000 // RMS RELAY SYNC +#define MFJMODE 0x080000 // MFJ PMS +#define NEWPACCOM 0x100000 // PACCOM PMS 3.2 +#define SETCALLTOSENDER 0x200000 // Set calling call to message sender + + +struct FBBRestartData +{ + struct MsgInfo * TempMsg; // Header while message is being received + struct UserInfo * UserPointer; + UCHAR * MailBuffer; // Mail Message being received + int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct + int Count; // Give up if too many restarts +}; + +// We need to keep the B2Message file for B2 messages we are sending until the messages is acked, so +// we can restart it. Otherwise the file may change, resulting in a checksum error + + +struct B2RestartData +{ + int CSize; // Compresses Size (B2 proto) + UCHAR * CompressedMsg; // Compressed Body fo B2 + struct MsgInfo * FwdMsg; + struct UserInfo * UserPointer; + int Count; // Give up if too many restarts +}; + +#pragma pack(1) + +struct TempUserInfo +{ + int LastAuthCode; // Protect against playback attack + + // Fields used to allow interrupting and resuming a paged listing + + BOOL ListActive; // Doing a list + BOOL ListSuspended; // Paused doing a list + int LastListedInPagedMode; + char LastListCommand[80]; + char LastListParams[80]; + int LinesSent; + char SendFullFrom; + char ListType; + char ListDirn; + char ListStatus; + char ListSelector; // < > @ etc + + int ListRangeStart; + int ListRangeEnd; + int LLCount; // Number still to send in List Last N + int UpdateLatest; // if set, save last listed as latest + BOOL IncludeKilled; // Show Killed Messages if SYSOP + +}; + +#define PMSG 1 +#define BMSG 2 +#define TMSG 3 + +struct OldUserInfo +{ + // Old format - without message type specific traffic counts + + char Call[10]; // Connected call without SSID +// indicat relai[8]; /* 64 Digis path */ + int lastmsg; /* 4 Last L number */ + int ConnectsIn; /* 4 Number of connexions in*/ + int TimeLastConnected; //Last connexion date */ +// long lastyap __a2__ ; /* 4 Last YN date */ + ULONG flags ; /* 4 Flags */ + + UCHAR PageLen; // Lines Per Page + UCHAR lang ; /* 1 Language */ + + int Xnewbanner; /* 4 Last Banner date */ + short Xdownload ; /* 2 download size (KB) = 100 */ + char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) + char BBSNumber; // BBS Bitmap Index Number + struct BBSForwardingInfo * ForwardingInfo; + struct UserInfo * BBSNext; // links BBS record + struct TempUserInfo * Temp; // Working Fields - not saved in user file + char xfree[6]; /* 6 Spare */ + char Xtheme; /* 1 Current topic */ + + char Name[18]; /* 18 1st Name */ + char Address[61]; /* 61 Address */ + + // Stats. Was City[31]; /* 31 City */ + + int MsgsReceived; + int MsgsSent; + int MsgsRejectedIn; // Messages we reject + int MsgsRejectedOut; // Messages Rejectd by other end + int BytesForwardedIn; + int BytesForwardedOut; + int ConnectsOut; // Forwarding Connects Out + + USHORT RMSSSIDBits; // SSID's to poll in RMS + + char Spare1; + + char HomeBBS[41]; /* 41 home BBS */ + char QRA[7]; /* 7 Qth Locator */ + char pass[13]; /* 13 Password */ + char ZIP[9]; /* 9 Zipcode */ + BOOL spare; +} ; /* Total : 360 bytes */ + +struct MsgStats +{ + int ConnectsIn; /* 4 Number of connexions in*/ + int ConnectsOut; // Forwarding Connects Out + + // Stats saveed by message type + + int MsgsReceived[4]; + int MsgsSent[4]; + int MsgsRejectedIn[4]; // Messages we reject + int MsgsRejectedOut[4]; // Messages Rejectd by other end + int BytesForwardedIn[4]; + int BytesForwardedOut[4]; +}; + +struct UserInfo +{ + // New Format - with stats maintained by message type and unused fields removed. + + char Call[10]; // Connected call without SSID + + int Length; // To make subsequent format changes easier + + int lastmsg; /* 4 Last L number */ + int xTimeLastConnected; //Last connexion date */ + ULONG flags ; /* 4 Flags */ + + UCHAR PageLen; // Lines Per Page + + char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) + unsigned char BBSNumber; // BBS Bitmap Index Number + struct BBSForwardingInfo * ForwardingInfo; + struct UserInfo * BBSNext; // links BBS record + struct TempUserInfo * Temp; // Working Fields - not saved in user file + char Name[18]; /* 18 1st Name */ + char Address[61]; /* 61 Address */ + + USHORT RMSSSIDBits; // SSID's to poll in RMS + + char HomeBBS[41]; /* 41 home BBS */ + char QRA[7]; /* 7 Qth Locator */ + char pass[13]; /* 13 Password */ + char ZIP[9]; /* 9 Zipcode */ + + struct MsgStats Total; + struct MsgStats Last; + + char CMSPass[16]; // For Secure Signon + int WebSeqNo; + + long long TimeLastConnected; //Last connection date */ + + char Filler[44 - 8]; // So we can add a few fields wirhout another resize +}; + +// flags equates + +#define F_Excluded 0x0001 +#define F_GGG 0x0002 +#define F_Expert 0x0004 +#define F_SYSOP 0x0008 +#define F_BBS 0x0010 +#define F_RMSREDIRECT 0x0020 +#define F_BBB 0x0040 +#define F_CCC 0x0080 +#define F_DDD 0x0100 +#define F_EEE 0x0200 +#define F_FFF 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 // "Winlink Express User" +#define F_NOWINLINK 0x00020000 // Don't add Winlink.org +#define F_NOBULLS 0x00040000 +#define F_NTSMPS 0x00080000 +#define F_APRSMFOR 0x00100000 // Send APRS message for new mail +#define F_APRSSSID 0xF0000000 // (Top 4 Bits + + +struct Override +{ + char * Call; + int Days; +}; + +struct ALIAS +{ + char * Alias; + char * Dest; +}; + +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 DIGIS[56]; // Padding in case we have digis + +}MESSAGEX, *PMESSAGEX; + + +#pragma pack() + +// Message Database Entry. Designed to be compatible with FBB + +#define NBBBS 160 // Max BBSes we can forward to. Must be Multiple of 8, and must be 80 for FBB compatibliliy +#define NBMASK NBBBS/8 // Number of bytes in Forward bitlists. + +#pragma pack(1) + +struct OldMsgInfo +{ + char type ; + char status ; + int number ; + int length ; + int datereceived; + char bbsfrom[7] ; // ? BBS we got it from ? + char via[41] ; + char from[7] ; + char to[7] ; + char bid[13] ; + char title[61] ; + char bin; + int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp + + UCHAR B2Flags; + + char free[4]; + unsigned short nblu; + int theme ; + time_t datecreated ; + time_t datechanged ; + char fbbs[10] ; + char forw[10] ; + char emailfrom[41]; +} ; + + +struct MsgInfo +{ + char type; + char status; + int number; + int length; + int xdatereceived; + char bbsfrom[7]; // ? BBS we got it from ? + char via[41]; + char from[7]; + char to[7]; + char bid[13]; + char title[61]; + int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp + + UCHAR B2Flags; + + #define B2Msg 1 // Set if Message File is a formatted B2 message + #define Attachments 2 // Set if B2 message has attachments + #define FromPaclink 4 + #define FromCMS 8 + #define FromRMSExpress 16 + #define RadioOnlyMsg 32 // Received using call-T + #define RadioOnlyFwd 64 // Received using call-R + + int xdatecreated; + int xdatechanged; + UCHAR fbbs[NBMASK]; + UCHAR forw[NBMASK]; + char emailfrom[41]; + char Locked; // Set if selected for sending (NTS Pickup) + char Defered; // FBB response '=' received + UCHAR UTF8; // Set if Message is in UTF8 (ie from POP/SMTP) + +// For 64 bit time_t compatibility define as long long +// (so struct is same with 32 or 64 bit time_t) + + long long datereceived; + long long datecreated; + long long datechanged; + + char Spare[61 - 24]; // For future use +} ; + +#define MSGTYPE_B 0 +#define MSGTYPE_P 1 + +#define MSGSTATUS_N 0 +#define MSGSTATUS_Y 1 +#define MSGSTATUS_F 2 +#define MSGSTATUS_K 3 +#define MSGSTATUS_H 4 +#define MSGSTATUS_D 5 +#define MSGSTATUS_$ 6 + +struct NNTPRec +{ + // Used for NNTP access to Bulls + + struct NNTPRec * Next; // Record held in chain, so can be held sorted + char NewsGroup[64]; // = Bull TO.at field + int FirstMsg; // Lowest Number + int LastMsg; // Highest Number + int Count; // Active Msgs + time_t DateCreated; // COntains Creation Date of First Bull in Group +}; + + +typedef struct { + char mode; + char BID[13]; + union + { /* array named screen */ + struct + { + unsigned short msgno; + unsigned short timestamp; + }; + CIRCUIT * conn; + } u; +} BIDRec, *BIDRecP; + + +typedef struct WPDBASE{ /* 194 bytes */ + char callsign[7]; + char name[13]; + unsigned char Type; + unsigned char changed; + unsigned short seen; + long long last_modif; + long long last_seen; + char first_homebbs[41]; + char secnd_homebbs[41]; + char first_zip[9]; + char secnd_zip[9]; + char first_qth[31]; + char secnd_qth[31]; +} WPRec, * WPRecP; + +#pragma pack() + +struct FWDBAND +{ + time_t FWDStartBand; + time_t FWDEndBand; +}; + + + +struct BBSForwardingInfo +{ + // Holds info for forwarding + + BOOL Enabled; // Forwarding Enabled + char ** ConnectScript; // Main Connect Script + char ** TempConnectScript; // Used with FWD command. + int ScriptIndex; // Next line in script + BOOL MoreLines; // Set until script is finsihed + + char ** TOCalls; // Calls in to field + char ** ATCalls; // Calls in ATBBS field + char ** HaddressesP; // Heirarchical Addresses for Personals to forward to (as stored) + char *** HADDRSP; // Heirarchical Addresses for Personals to forward to + char ** Haddresses; // Heirarchical Addresses to forward to (as stored) + char *** HADDRS; // Heirarchical Addresses to forward to + int * HADDROffet; // Elements added to complete the HR. At least n+1 must match to forward + char ** FWDTimes; // Time bands to forward + struct FWDBAND ** FWDBands; + int MsgCount; // Messages for this BBS + BOOL ReverseFlag; // Set if BBS wants to poll for reverse forwarding + BOOL Forwarding; // Forward in progress + int MaxFBBBlockSize; + BOOL AllowBlocked; // Allow FBB Blocked + BOOL AllowCompressed; // Allow FBB Compressed + BOOL AllowB1; // Enable B1 + BOOL AllowB2; // Enable B2 + BOOL SendCTRLZ; // Send Ctrl/z instead of /ex + BOOL PersonalOnly; // Only Forward Personals + BOOL SendNew; // Forward new messages immediately + int FwdInterval; + int RevFwdInterval; + int FwdTimer; + time_t LastReverseForward; + char *BBSHA; // HA of BBS + char ** BBSHAElements; // elements of HA of BBS + int ConTimeout; +// char UserCall[10]; // User we are forwarding on behalf of (Currently only for RMS) +// int UserIndex; // index of User we are forwarding on behalf of (Currently only for RMS) +}; + + +struct FBBHeaderLine +{ + // Holds the info from the (up to) 5 headers presented at the start of a Forward Block + + char Format; // Ascii or Binary + char MsgType; // P B etc + char From[7]; // Sender + char ATBBS[41]; // BBS of recipient (@ Field) + char To[7]; // Recipient + char BID[13]; + int Size; + int CSize; // Compresses Size (B2 proto) + BOOL B2Message; // Set if an FC type + UCHAR * CompressedMsg; // Compressed Body fo B2 + struct MsgInfo * FwdMsg; // Header so we can mark as complete +}; + +#define MAXSTACK 20 +//#define MAXLINE 10000 +#define INPUTLEN 512 + +#define MAXLINES 1000 +#define LINELEN 200 + +char RTFHeader[4000]; + +int RTFHddrLen; + +struct ConsoleInfo +{ + struct ConsoleInfo * next; + CIRCUIT * Console; + int BPQStream; + WNDPROC wpOrigInputProc; + HWND hConsole; + HWND hwndInput; + HWND hwndOutput; + HMENU hMenu; // handle of menu + RECT ConsoleRect; + RECT OutputRect; + + int Height, Width, LastY; + + int ClientHeight, ClientWidth; + char kbbuf[INPUTLEN]; + int kbptr; + + char * readbuff; // Malloc'ed + int readbufflen; // Current Length + char * KbdStack[MAXSTACK]; + + int StackIndex; + + BOOL Bells; + BOOL FlashOnBell; // Flash instead of Beep + BOOL StripLF; + + BOOL WarnWrap; + BOOL FlashOnConnect; + BOOL WrapInput; + BOOL CloseWindowOnBye; + + unsigned int WrapLen; + int WarnLen; + int maxlinelen; + + int PartLinePtr; + int PartLineIndex; // Listbox index of (last) incomplete line + + DWORD dwCharX; // average width of characters + DWORD dwCharY; // height of characters + DWORD dwClientX; // width of client area + DWORD dwClientY; // height of client area + DWORD dwLineLen; // line length + int nCaretPosX; // horizontal position of caret + int nCaretPosY; // vertical position of caret + + COLORREF FGColour; // Text Colour + COLORREF BGColour; // Background Colour + COLORREF DefaultColour; // Default Text Colour + + int CurrentLine; // Line we are writing to in circular buffer. + + int Index; + BOOL SendHeader; + BOOL Finished; + + char OutputScreen[MAXLINES][LINELEN]; + + int Colourvalue[MAXLINES]; + int LineLen[MAXLINES]; + + int CurrentColour; + int Thumb; + int FirstTime; + BOOL Scrolled; // Set if scrolled back + int RTFHeight; // Height of RTF control in pixels + +}; + + +struct MSESSION +{ + struct MSESSION * Next; + unsigned int Key; + char * FileName; + char * OrigTimeStamp; + unsigned char * Message; + int MessageLen; + unsigned char * BlockList; + char * ID; + int BlockSize; + int BlockCount; + int BlocksReceived; + BOOL Completed; + time_t Created; + time_t LastUpdated; + int Index; // Line in Display +}; + +VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...); +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); +VOID FBBputs(CIRCUIT * conn, char * buf); +void makelinks(void); +VOID * _zalloc(size_t len); +VOID FreeChatMemory(); +VOID ChatTimer(); +VOID nputs(CIRCUIT * conn, char * buf); +VOID node_close(); +VOID removelinks(); +VOID SetupChat(); +VOID SendChatLinkStatus(); +VOID ClearChatLinkStatus(); +VOID Send_MON_Datagram(UCHAR * Msg, DWORD Len); + +#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) + +int EncryptPass(char * Pass, char * Encrypt); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); + +// TCP Connections. FOr the moment SMTP or POP3 + +typedef struct SocketConnectionInfo +{ + struct SocketConnectionInfo * Next; + int Number; // Number of record - for Connections display + SOCKET socket; + SOCKADDR_IN sin; + int Type; // SMTP or POP3 + BOOL AMPR; // Set if sending to an AMPR.ORG server + char FromDomain[50]; // Domain we are sending from + struct UserInfo * bbs; // BBS dor forwarding to AMPR + int State; // Transaction State Machine + UCHAR CallSign[10]; + UCHAR TCPBuffer[3000]; // For converting byte stream to messages + int InputLen; // Data we have alreasdy = Offset of end of an incomplete packet; + + char * MailFrom; // Envelope Sender and Receiver + char ** RecpTo; // May be several Recipients + int Recipients; + + UCHAR * MailBuffer; // Mail Message being received. malloc'ed as needed + int MailBufferSize; // Total Malloc'ed size. Actual size is in MailSize + int MailSize; + int Flags; + + struct UserInfo * POP3User; + struct MsgInfo ** POP3Msgs; // Header List of messages for this uaer + int POP3MsgCount; // No of Messages + int POP3MsgNum; // Sequence number of message being received + + struct MsgInfo * SMTPMsg; // message for this SMTP connection + + UCHAR * SendBuffer; // Message being sent if socket is busy. malloc'ed as needed + int SendBufferSize; // Total Malloc'ed size. Actual size is in MailSize + int SendSize; // Bytes in buffer + int SendPtr; // next byte to send when ready + + struct NNTPRec * NNTPGroup; // Currently Selected Group + int NNTPNum; // Currenrly Selected Msg Number + int Timeout; // Used to close a session that is open too long + +} SocketConn; + +typedef struct KEYVALUES +{ + char * Key; + char * Value; +} KeyValues; + +typedef struct WEBMAILINFO +{ + // Info for HTML Forms Processing + + struct HtmlFormDir * Dir; // HTML Directory + char * txtFileName; // Template Name for current message + char * InputHTMLName; // Template to input message + char * DisplayHTMLName; // Template to display message + char * ReplyHTMLName; // Template for replying to message + char * txtFile; // Template data + char * OrigTo; // To field when template loaded + char * OrigSubject; // Subject field when template loaded + char * OrigBody; // Msg text when template loaded + char * OrigBID; + char OrigType; + char * To; + char * CC; + char * Subject; + char * Body; + char * BID; + char Type; + struct MsgInfo * Msg; // Msg record if replying + KeyValues txtKeys[1000]; // Key/Value pairs for txt template. Used when creating or displaying + KeyValues XMLKeys[1000]; // Key/Value pairs from XML attachment + BOOL isReply; + char * XMLName; + char * XML; // XML attachment + int XMLLen; + int Files; + char * FileName[100]; // Attachments + char * FileBody[100]; + int FileLen[100]; + + char * Header; + int HeaderLen; + + char * Footer; + int FooterLen; + + char * Reply; // put in here to save passing lots of parameters + int * RLen; + + BOOL Winlink; + BOOL P2P; + BOOL Packet; + + int CurrentMessageIndex; // Index of message currently displayed (for Prev and Next) + +}WebMailInfo; + +#define SMTPServer 1 +#define POP3SLAVE 2 +#define SMTPClient 3 +#define POP3Client 4 +#define NNTPServer 5 + +// State Values + +#define GettingUser 1 +#define GettingPass 2 +#define Authenticated 4 + +#define Connecting 8 + +// SMTP Master + +#define WaitingForGreeting 16 +#define WaitingForHELOResponse 32 +#define WaitingForFROMResponse 64 +#define WaitingForTOResponse 128 +#define WaitingForDATAResponse 256 +#define WaitingForBodyResponse 512 +#define WaitingForAUTHResponse 1024 + +// POP3 Master + +#define WaitingForUSERResponse 32 +#define WaitingForPASSResponse 64 +#define WaitingForSTATResponse 128 +#define WaitingForUIDLResponse 256 +#define WaitingForLISTResponse 512 +#define WaitingForRETRResponse 512 +#define WaitingForDELEResponse 1024 +#define WaitingForQUITResponse 2048 + + +#define SE 240 // End of subnegotiation parameters +#define NOP 241 //No operation +//#define DM 242 //Data mark Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. +#define BRK 243 //Break Indicates that the "break" or "attention" key was hi. +#define IP 244 //Suspend Interrupt or abort the process to which the NVT is connected. +#define AO 245 //Abort output Allows the current process to run to completion but does not send its output to the user. +#define AYT 246 //Are you there Send back to the NVT some visible evidence that the AYT was received. +#define EC 247 //Erase character The receiver should delete the last preceding undeleted character from the data stream. +#define EL 248 //Erase line Delete characters from the data stream back to but not including the previous CRLF. +#define GA 249 //Go ahead Under certain circumstances used to tell the other end that it can transmit. +#define SB 250 //Subnegotiation Subnegotiation of the indicated option follows. +#define WILL 251 //will Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. +#define WONT 252 //wont Indicates the refusal to perform, or continue performing, the indicated option. +#define DOx 253 //do Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. +#define DONT 254 //dont Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. +#define IAC 255 + +#define suppressgoahead 3 //858 +//#define Status 5 //859 +//#define echo 1 //857 +#define timingmark 6 //860 +#define terminaltype 24 //1091 +#define windowsize 31 //1073 +#define terminalspeed 32 //1079 +#define remoteflowcontrol 33 //1372 +#define linemode 34 //1184 +#define environmentvariables 36 //1408 + +BOOL Initialise(); +#ifdef WIN32 +INT_PTR CALLBACK ConfigWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +#endif +int DisplaySessions(); +int DoStateChange(int Stream); +int DoReceivedData(int Stream); +int DoBBSMonitorData(int Stream); +int Connected(int Stream); +int Disconnected(int Stream); +//int Socket_Accept(int SocketId); +//int Socket_Data(int SocketId,int error, int eventcode); +int DataSocket_Read(SocketConn * sockptr, SOCKET sock); +int DataSocket_Write(SocketConn * sockptr, SOCKET sock); +int DataSocket_Disconnect(SocketConn * sockptr); +int RefreshMainWindow(); +int Terminate(); +int SendtoSocket(SOCKET sock,char * Msg); +int WriteLog(char * msg); +int ConnectState(int Stream); +UCHAR * EncodeCall(UCHAR * Call); +int ParseIniFile(char * fn); +struct UserInfo * AllocateUserRecord(char * Call); +struct MsgInfo * AllocateMsgRecord(); +BIDRec * AllocateBIDRecord(); +BIDRec * AllocateTempBIDRecord(); +struct UserInfo * LookupCall(char * Call); +BIDRec * LookupBID(char * BID); +BIDRec * LookupTempBID(char * BID); +VOID RemoveTempBIDS(CIRCUIT * conn); +VOID SaveUserDatabase(); +VOID GetUserDatabase(); +VOID GetMessageDatabase(); +VOID SaveMessageDatabase(); +VOID GetBIDDatabase(); +VOID SaveBIDDatabase(); +VOID GetWPDatabase(); +VOID CopyWPDatabase(); +VOID SaveWPDatabase(); +VOID GetBadWordFile(); +WPRec * LookupWP(char * Call); +VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user); +VOID ProcessLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID ProcessChatLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user); +int QueueMsg( ConnectionInfo * conn, char * msg, int len); +VOID SendUnbuffered(int stream, char * msg, int len); +//int GetFileList(char * Dir); +BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp); +void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void DoKillCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context); +void DoReadCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); +int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call); +int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call); +void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); + +VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB); +int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); +void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); +int GetUserMsg(int m, char * Call, BOOL SYSOP); +void Flush(ConnectionInfo * conn); +VOID ClearQueue(ConnectionInfo * conn); +void TrytoSend(); +void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); +struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop); +char * ReadMessageFile(int msgno); +char * ReadInfoFile(char * File); +char * FormatDateAndTime(time_t Datim, BOOL DateOnly); +int CriticalErrorHandler(char * error); +BOOL DoSendCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +BOOL CreateMessage(ConnectionInfo * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title); +VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg); +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 GetConfigFromRegistry(); +VOID Parse_SID(CIRCUIT * conn, char * SID, int len); +VOID ProcessMBLLine(CIRCUIT * conn, struct UserInfo * user, UCHAR* Buffer, int len); +VOID ProcessFBBLine(ConnectionInfo * conn, struct UserInfo * user, UCHAR * Buffer, int len); +VOID SetupNextFBBMessage(CIRCUIT * conn); +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); +int PrintMessages(HWND hDlg, int Count, int * Indexes); +int check_fwd_bit(char *mask, int bbsnumber); +void set_fwd_bit(char *mask, int bbsnumber); +void clear_fwd_bit (char *mask, int bbsnumber); +VOID SetupForwardingStruct(struct UserInfo * user); +BOOL Forward_Message(struct UserInfo * user, struct MsgInfo * Msg); +VOID StartForwarding (int BBSNumber, char ** TempScript); +BOOL Reverse_Forward(); +int ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len); +BOOL FBBDoForward(CIRCUIT * conn); +BOOL FindMessagestoForward(CIRCUIT * conn); +BOOL SeeifMessagestoForward(int BBSNumber, CIRCUIT * Conn); +int CountMessagestoForward(struct UserInfo * user); + +VOID * GetMultiLineDialogParam(HWND hDialog, int DLGItem); + +#define LIBCONFIG_STATIC +#include "libconfig.h" +VOID * GetMultiStringValue(config_setting_t * hKey, char * ValueName); +VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName); + +int MultiLineDialogToREG_MULTI_SZ(HWND hWnd, int DLGItem, HKEY hKey, char * ValueName); +int Do_BBS_Sel_Changed(HWND hDlg); +VOID FreeForwardingStruct(struct UserInfo * user); +VOID FreeList(char ** Hddr); +int Do_User_Sel_Changed(HWND hDlg); +int Do_Msg_Sel_Changed(HWND hDlg); +VOID Do_Save_Msg(); +VOID Do_Add_User(HWND hDlg); +VOID Do_Delete_User(HWND hDlg); +VOID FlagSentMessages(CIRCUIT * conn, struct UserInfo * user); +VOID HoldSentMessages(CIRCUIT * conn, struct UserInfo * user); +VOID Do_Save_User(HWND hDlg, BOOL ShowBox); +VOID DeleteBBS(); +VOID AddBBS(); +VOID SaveBBSConfig(); +BOOL GetChatConfig(); +VOID SaveChatConfig(); +VOID SaveISPConfig(); +VOID SaveFWDConfig(); +VOID SaveMAINTConfig(); +VOID SaveWelcomeMsgs(); +VOID SavePrompts(); +VOID ReinitializeFWDStruct(struct UserInfo * user); +VOID CopyBIDDatabase(); +VOID CopyMessageDatabase(); +VOID CopyUserDatabase(); +VOID FWDTimerProc(); +VOID CreateMessageFromBuffer(CIRCUIT * conn); +VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...); +VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...); +VOID FreeOverrides(); +VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length); +struct UserInfo * FindRMS(); +VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo); +BOOL ConnecttoBBS (struct UserInfo * user); +BOOL SetupNewBBS(struct UserInfo * user); +VOID CreateRegBackup(); +VOID SaveFilters(HWND hDlg); +BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type); +BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID); +BOOL CheckifLocalRMSUser(char * FullTo); +VOID DoWPLookup(ConnectionInfo * conn, struct UserInfo * user, char Type, char *Context); +BOOL wildcardcompare(char * Target, char * Match); +VOID SendWarningToSYSOP(struct MsgInfo * Msg); +VOID DoEditUserCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoPollRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoShowRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoSetIdleTime(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoFwdCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID SaveFwdParams(char * Call, struct BBSForwardingInfo * ForwardingInfo); +VOID DoAuthCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +VOID DoReroute(CIRCUIT * conn, struct UserInfo * user); + +// FBB Routines + +VOID SendCompressed(CIRCUIT * conn, struct MsgInfo * FwdMsg); +VOID SendCompressedB2(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); +VOID UnpackFBBBinary(CIRCUIT * conn); +void Decode(CIRCUIT * conn, __int16 DecodeOnly); +//long Encode(char * in, char * out, long inlen, BOOL B1Protocol); + +BOOL CreateB2Message(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader, char * Rline); +VOID SaveFBBBinary(CIRCUIT * conn); +BOOL LookupRestart(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); +BOOL DoWeWantIt(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); + +// Console Routines + +BOOL CreateConsole(int Stream); +int WritetoConsoleWindow(int Stream, char * Msg, int len); +int ToggleParam(HMENU hMenu, HWND hWnd, BOOL * Param, int Item); +void CopyRichTextToClipboard(HWND hWnd); +void CopyToClipboard(HWND hWnd); +VOID CloseConsole(int Stream); + +// Monitor Routines + +BOOL CreateMonitor(); +int WritetoMonitorWindow(char * Msg, int len); + +BOOL CreateDebugWindow(); +VOID WritetoDebugWindow(char * Msg, int len); +VOID ClearDebugWindow(); +int RemoveLF(char * Message, int len); + +// Utilities + +BOOL isdigits(char * string); +void GetSemaphore(struct SEM * Semaphore, int ID); +void FreeSemaphore(struct SEM * Semaphore); + +VOID __cdecl Debugprintf(const char * format, ...); +VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...); + +VOID SortBBSChain(); +VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG); +int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup); + +// TCP Routines + +BOOL InitialiseTCP(); +VOID SetupListenSet(); +VOID TCPTimer(); +VOID TCPFastTimer(); +int Socket_Data(int sock, int error, int eventcode); +static int Socket_Accept(SOCKET SocketId); +int Socket_Connect(SOCKET sock, int Error); +VOID ProcessSMTPServerMessage(SocketConn * sockptr, char * Buffer, int Len); +int CreateSMTPMessage(SocketConn * sockptr, int i, char * MsgTitle, time_t Date, char * MsgBody, int Msglen, BOOL B2Flag); +BOOL CreateSMTPMessageFile(char * Message, struct MsgInfo * Msg); +SOCKET CreateListeningSocket(int Port); +int TidyString(char * MailFrom); +VOID ProcessPOP3ServerMessage(SocketConn * sockptr, char * Buffer, int Len); +char *str_base64_encode(char *str); +int b64decode(char *str); +SocketConn * SMTPConnect(char * Host, int Port, BOOL AMPR, struct MsgInfo * Msg, char * MsgBody); +BOOL POP3Connect(char * Host, int Port); +VOID ProcessSMTPClientMessage(SocketConn * sockptr, char * Buffer, int Len); +VOID ProcessPOP3ClientMessage(SocketConn * sockptr, char * Buffer, int Len); +int CreatePOP3Message(char * From, char * To, char * MsgTitle, time_t Date, char * MsgBody, int MsgLen, BOOL B2Flag); +void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags); +int SendSock(SocketConn * sockptr, char * msg); +VOID __cdecl sockprintf(SocketConn * sockptr, const char * format, ...); +VOID SendFromQueue(SocketConn * sockptr); +VOID SendMultiPartMessage(SocketConn * sockptr, struct MsgInfo * Msg, UCHAR * msgbytes); +int CountMessagesTo(struct UserInfo * user, int * Unread); + +BOOL SendtoISP(); + +// NNTP ROutines + +VOID InitialiseNNTP(); +VOID BuildNNTPList(struct MsgInfo * Msg); +int NNTP_Data(int sock, int error, int eventcode); +int NNTP_Accept(SOCKET SocketId); + +VOID * GetOverrides(config_setting_t * group, char * ValueName); +VOID * RegGetOverrides(HKEY hKey, char * ValueName); + +VOID DoHouseKeeping(BOOL Mainual); +VOID ExpireMessages(); +VOID KillMsg(struct MsgInfo * Msg); +BOOL RemoveKilledMessages(); +VOID Renumber_Messages(); +BOOL ExpireBIDs(); +VOID MailHousekeepingResults(); +VOID CreateBBSTrafficReport(); +VOID CreateWPReport(); + +// WP Routines + +VOID ProcessWPMsg(char * MailBuffer, int Size, char * FisrtRLine); +VOID GetWPInfoFromRLine(char * From, char * FirstRLine, time_t RLineTime); +VOID UpdateWPWithUserInfo(struct UserInfo * user); +VOID GetWPBBSInfo(char * Rline); + +// UI Routines + +VOID SetupUIInterface(); +VOID Free_UI(); +VOID SendLatestUI(int Port); +VOID SendMsgUI(struct MsgInfo * Msg); +static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue); +VOID SeeifBBSUIFrame(struct _MESSAGEX * buff, int len); +struct MsgInfo * FindMessageByNumber(int msgno); +int CountConnectionsOnPort(int CheckPort); + +// Message Routing Routtines + +VOID SetupHAElements(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupHAddresesP(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupMyHA(); +VOID SetupFwdAliases(); +struct Continent * FindContinent(char * Name); +int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn); +BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); +BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo); +BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); +BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); +BOOL CheckBBSHElements(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); +BOOL CheckBBSHElementsFlood(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); +int CheckBBSToForNTS(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo); +int CheckBBSATListWildCarded(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); + +VOID ReRouteMessages(); + +VOID initUTF8(); +int Is8Bit(unsigned char *cpt, int len); +int IsUTF8(unsigned char *ptr, int len); +int IsUTF8(unsigned char *ptr, int len); +int WebIsUTF8(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); + + +VOID FreeWebMailMallocs(); + +extern int _MYTIMEZONE; + +extern HKEY REGTREE; +extern char * REGTREETEXT; + +extern HBRUSH bgBrush; +extern BOOL cfgMinToTray; + +extern CIRCUIT * Console; + +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 BOOL OnlyKnown; + +extern BOOL AllowAnon; +extern BOOL UserCantKillT; +extern BOOL DontNeedHomeBBS; + +extern int LatestMsg; +extern char BBSName[]; +extern char SYSOPCall[]; +extern char BBSSID[]; +extern char NewUserPrompt[]; + +extern char * WelcomeMsg; +extern char * NewWelcomeMsg; +extern char * ChatWelcomeMsg; +extern char * NewChatWelcomeMsg; +extern char * ExpertWelcomeMsg; + +extern char * Prompt; +extern char * NewPrompt; +extern char * ExpertPrompt; + +// Filter Params + +extern char ** RejFrom; // Reject on FROM Call +extern char ** RejTo; // Reject on TO Call +extern char ** RejAt; // Reject on AT Call +extern char ** RejBID; + +extern char ** HoldFrom; // Hold on FROM Call +extern char ** HoldTo; // Hold on TO Call +extern char ** HoldAt; // Hold on AT Call +extern char ** HoldBID; + +// Send WP Params + +extern BOOL SendWP; +extern char SendWPVIA[81]; +extern char SendWPTO[11]; +extern int SendWPType; + +extern int Ver[4]; + +extern struct MsgInfo ** MsgHddrPtr; + +extern BIDRec ** BIDRecPtr; +extern int NumberofBIDs; + +extern struct NNTPRec * FirstNNTPRec; +//extern int NumberofNNTPRecs; + + +extern int NumberofMessages; +extern int FirstMessageIndextoForward; + +extern WPRec ** WPRecPtr; +extern int NumberofWPrecs; + +extern struct SEM AllocSemaphore; +extern struct SEM ConSemaphore; +extern struct SEM MsgNoSemaphore; + +extern struct MsgInfo * MsgnotoMsg[]; // Message Number to Message Slot List. + + +extern char hostname[]; +extern char RtUsr[]; +extern char RtUsrTemp[]; +extern char RtKnown[]; +extern int AXIPPort; +extern BOOL NeedStatus; + +extern BOOL ISP_Gateway_Enabled; +extern BOOL SMTPAuthNeeded; + + +extern int MaxMsgno; +extern int BidLifetime; +extern int MaxAge; +extern int MaintInterval; +extern int MaintTime; +extern int UserLifetime; + +extern int MaxRXSize; +extern int MaxTXSize; + +extern char OurNode[]; +extern char OurAlias[]; +extern BOOL SMTPMsgCreated; + +extern HINSTANCE hInst; +extern HWND hWnd; +extern RECT MainRect; + +extern char BBSName[]; +extern char HRoute[]; +extern char AMPRDomain[]; +extern BOOL SendAMPRDirect; +extern int BBSApplNum; +extern int SMTPInPort; +extern int POP3InPort; +extern int NNTPInPort; +extern BOOL RemoteEmail; + +extern int MaxStreams; +extern UCHAR * OtherNodes; +extern struct UserInfo * BBSChain; // Chain of users that are BBSes +extern struct UserInfo ** UserRecPtr; +extern int NumberofUsers; +extern struct MsgInfo ** MsgHddrPtr; +extern int NumberofMessages; +extern int HighestBBSNumber; +extern HMENU hFWDMenu; // Forward Menu Handle +extern char zeros[]; // For forward bitmask tests +extern BOOL EnableUI; +extern BOOL RefuseBulls; +extern BOOL SendSYStoSYSOPCall; +extern BOOL SendBBStoSYSOPCall; +extern BOOL DontHoldNewUsers; +extern BOOL DefaultNoWINLINK; +extern BOOL UIEnabled[]; +extern BOOL UINull[]; +extern BOOL UIMF[]; +extern BOOL UIHDDR[]; +extern char * UIDigi[]; +extern int MailForInterval; +extern char MailForText[]; + +extern BOOL ISP_Gateway_Enabled; + +extern char MyDomain[]; // Mail domain for BBS<>Internet Mapping + +extern char ISPSMTPName[]; +extern char ISPEHLOName[]; +extern int ISPSMTPPort; + +extern char ISPPOP3Name[]; +extern int ISPPOP3Port; +extern int ISPPOP3Interval; + +extern char ISPAccountName[]; +extern char ISPAccountPass[]; +extern char EncryptedISPAccountPass[]; +extern int EncryptedPassLen; +extern char *month[]; + +extern HWND hDebug; +extern RECT MonitorRect; +extern RECT DebugRect; +extern HWND hMonitor; +//extern HWND hConsole; +//extern RECT ConsoleRect; +extern int LogAge; +extern BOOL DeletetoRecycleBin; +extern BOOL SuppressMaintEmail; +extern BOOL SaveRegDuringMaint; +extern BOOL SendWP; +extern BOOL OverrideUnsent; +extern BOOL SendNonDeliveryMsgs; +extern BOOL GenerateTrafficReport; + +extern double PR; +extern double PUR; +extern double PF; +extern double PNF; +extern int BF; +extern int BNF; +extern int NTSD; +extern int NTSU; +extern int NTSF; +extern int AP; +extern int AB; + +extern struct Override ** LTFROM; +extern struct Override ** LTTO; +extern struct Override ** LTAT; + +extern time_t LastHouseKeepingTime; +extern time_t LastTrafficTime; + +extern char * MyElements[]; +extern char ** AliasText; +extern struct ALIAS ** Aliases; + +extern BOOL ReaddressLocal; +extern BOOL ReaddressReceived; +extern BOOL WarnNoRoute; +extern BOOL SendPtoMultiple; +extern BOOL Localtime; + +struct ConsoleInfo * ConsHeader[2]; + +extern BOOL NeedHomeBBS; +extern char ConfigName[250]; +extern BOOL UsingingRegConfig; + +extern BOOL MulticastRX; + +extern BOOL FilterWPBulls; +extern BOOL NoWPGuesses; +extern char ** SendWPAddrs; // Replacers WP To and VIA + +extern BOOL DontCheckFromCall; + +// YAPP stuff + +#define SOH 1 +#define STX 2 +#define ETX 3 +#define EOT 4 +#define ENQ 5 +#define ACK 6 +#define DLE 0x10 +#define NAK 0x15 +#define CAN 0x18 diff --git a/cMain.c b/cMain.c index e6c1d88..9acb24a 100644 --- a/cMain.c +++ b/cMain.c @@ -2220,8 +2220,17 @@ L2Packet: 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); } diff --git a/debug/BuildLog.htm b/debug/BuildLog.htm new file mode 100644 index 0000000000000000000000000000000000000000..9898b8836e3a6f9f0125588cde348b5083dd2bd2 GIT binary patch literal 6660 zcmeI1ZBH9l5QXP+rTmALt43+s*nuP@f>0$GlP1F80x5lQ71`Jr7ZV)YAx+ia-u5|j zz4q>6z!0Q35?Xop?tPs*bLPz5@qhn5wF4X2k#+0`yJcUQ-kVml#^}9lCEKy*7TJ$H zcdTOfsQJlW+e`b|mMv#RJGQsX()9a1`h2u5Z#l;7rP|b6{r=)!7_GeI{SH{QXMI`+ z%=(`DJ}b@H0%Ky?G$t)wo%tKA7x(BgM|1QxV9q*wYqG)zYtwVU49m=VpX(cH>(+7^ zJIvFge5hR>1F!a)|e;0E;HL*UnOnf1J`d-w|&=Ok9(8d zEwY~qmN~XRdEcW}HDxSZv3bAd?0aeghWF7^3p@4D+JLfft^TWwtyAB!e{9!v`GB#q zR#-oHP60av^i6Na^N=Wg>z77i(6jV?85GVBQK)?$an;yaK%$V*#G3IQ;#sxA4I1Sk zld+vfW6>=iP$Msl9Dhe3LcAhw_(+}b71$^T78k7wi;D5YdsTZy-vg{swN1ClD)ntU zp}pz|JmI-bAK_EJUj>t0dxre`Y17SUvq3uBEHVKnJkdin*vSwNSE zzIVm@j&pLJXB!66iB;$B_jpH*UmJ~tmRs&AN&AU)go$t-suCG=uFm6Ib_EuW<>Ax3Yc(5D!WZ7U_d7h*yEKY3an3I~6+Omt%VM*SI(^6W!CVKXxXDu~b9(ix zX);q9BA+6ISX|!#HP1GU`8zktSS^ASWh6cipT?Q + + + + + + + \ No newline at end of file diff --git a/debug/MCP2221.exe.embed.manifest.res b/debug/MCP2221.exe.embed.manifest.res new file mode 100644 index 0000000000000000000000000000000000000000..7b55d19001b1a6c9becd61f77f12ffdedbbaed6d GIT binary patch literal 468 zcmZ8d!A`?43@s8DkhpV`i!+}rY6E-te zwqePN?PvS>B?y9mk#9D?PT`4toVEBh^-w|<1sCUS)A#3BHeBn9t-ztOCg*V&aRx@( zQki+qUndWplr!|gl%lc*a^3*);o#~r6NsRTs$sOiV9sl2`Y0Em1$MNO&Z3?9jCuk=2 n4SxP;CetD*?nDf63Q55SS@-`*t%`0d>{LefSKkRf%nyPsVFr%a literal 0 HcmV?d00001 diff --git a/debug/MCP2221.exe.intermediate.manifest b/debug/MCP2221.exe.intermediate.manifest new file mode 100644 index 0000000..1b520e1 --- /dev/null +++ b/debug/MCP2221.exe.intermediate.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/debug/MCP2221.obj b/debug/MCP2221.obj new file mode 100644 index 0000000000000000000000000000000000000000..5890fa1c84437050481312918a9ea7e9975a2bb1 GIT binary patch literal 34772 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 z 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¨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� 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

-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 literal 0 HcmV?d00001 diff --git a/debug/hid.obj b/debug/hid.obj new file mode 100644 index 0000000000000000000000000000000000000000..8fc95b4064a44a7b955f674ba0d818fe3983413f GIT binary patch literal 53913 zcmd752VhiH_CJ1;KnN3%1S5ii1Vq3>3dl(@8-fWJDp$gh1$^#uLYM9XKOD@mo*_^Vpyk0`T3woX;* zj6O;gdNpM*K&DE{POEBZv3Ll_I6Ve$Q|TRPHI>0iM_4>?f1!f_G?RQm|B|4CJO}tf z@NG1_J>gGHDmf{W;3oGBGSfxt9vucLUAsZlK`M@{0T&dABHQ#}fFUuF&FnvLvI7{z#`*}szS(qF;H^4U+^^>-d^xWl7kco+ymfwj)DzGK9SR~^xnh(NrKewIEh?JW#NjH zo)-lSAoIr;3Y)3`&~hItFB}g^O`6BLN2*6V0`~*odczxSyyc=EuYhO3$vU;(0FmnP z66np~nHtc1R5g+6@fy;*6Fg^_!Jix@y`7*R-imw|>GY^aBBeJN0HEkJQdjBk)b|UDc+!qSd1o<$D7> zQ_s|V(b{=Cg0BG2(iNI7T03t;dY^#j!IhdXT04&gU**|KU38A-<9LbG&LwDptY2XyzOeE8F6vPOzMPA6uKNK*st4t2zl)Xn=@QL12q04Xu^s+% zsphfnk=kzt0uwJ&YA!B1Bp)%~fhb=ycvf95Y|5T5!|E{+!GAM6nlo~GgOT1B;Olc` z_vvwX9|xWd4!%h3Lb?ACJl?BydeO#j8u%^(PscTyk8%(xJ?eu6*Fpx@Yd-dOq;?(+ zdhT_a$GS(7mk|i8kHi<&eoujKE%-{W*Qs%Sk5s<%P>n5d6 z$3@3}ixi!NGvZAkA?@EmiO=8GmTha>n*!=pJPx8H6A-U+_z?$%t)SL8+8 z#pPDqlYqQxcq|(2;?&gCBO59k>*o1ef;IkTe`Afmv3lv`;lZWN{$ociu4~K~H!8KU zvH_mLy6QkvOVhky+RU6u>1nw%ymLy+N2MqKz@}`A;L>xP>dQw$J)zOt>{bMKjGpa@n zPdzf%otIVNE%$n|Cl7CJt*c3^udDiX)%ydgdU4I~%3f91tDMPmO9D;v1CiRVQ62Gc$s8M5zYf{JL&hivxj05!Ly0a_tiJ-a4ioI^4 zh1oe}sblgRQpdOpT6n8Y9aCDKGd6WhVJ;%)HT@T@hv+r`Mf640kj>_rs?;&FGE&E& zT(g=dj!zwPyeD-`c~0t>#-{qF`Aw-~{DDAIAmDFq3Ir!ZQX86ssbf6De^2YxHCETR z*7%42-uNZ;mBD#Ufrhf&>2v?9w3xu6acSw!R8S!yAC;D#HP@p{nmT5-YJn}DCW2b6 z&{)4q+%OiP0p?l_4iV=Vx&W5x!Kzns9!61&%JvlHmX&9fSCskkfO?AZOWkF~6{R_D zb&z3-buvN5irhIEmHBF@36FDzmtyFa8iqp*Lk}lIwmTpB6wfO17H8$=W|e2zg^72v zxr=f`m<~5-^>Q+Wq?J=#SW;Z%E-EimqYZO{li5>*dV0Mmxr6gi!`a)(>B!r`YPFfm z$y!my^s;ga+)!2}bJTE?UZRt!3<*lZ&Zuygm8(JE`I9t$MXC3A6xrt~Dlg3{a+fQs z#ris<7kUca<#S5hSu?Xd-mGk|TODR{*~iHyrO5Mm-BhBTydw>7KPRs=T-lr=l-XUH zRqiP+QX>psl9LYw59P8NA=$|#sa2HN9XiY;wXc&2NjX@obW)rwC8fpX#W}^^9B)=x znI~5bHtFo=WOAd>Y)xzsX&D~i44vsN_2hZnxn0}Ll8ODD>^dhc7~bL>-4a%|1D%|? zZj-Imo`amBtUAiXTnjL7dEKRIKNK%pCMr)&y}zusHCWTMxKSNpN_~JcI_EhEZmy@S zq`1t(?#L=Hhft|ehI?=bw-=3*lT{=FjR`WV+-(ca5GSLjqG);%Mp~yZSVQ_CCz~Fy zR0Q^r9_kD)&6;I3r!C*9P6qVb>^Zsaax?5xO`(T5d9reH+$EIqe0Q0h&2T42PI2*c zkK2dAQm(AgesCy5VM$g_xv!+y>&cnpgO+i7X1a6jx{nBD)~QvLlt7`Cfz4{mk&)P- z0o^`bMw@X|RK`;GG}P5*937PrEmK%huF_14q&XQe^2$7A>~~DuQdql2Rtu&(`DJ`l zQc8=xc8X&|IJ$_Dr4E#(=f*ktrB~gv%S*gjo}xleSz%T=WX=-s3@2+zMYh*dRsdyT zDMHAtS{thTfimzm&X2-aSlQS*uTt!8i*b0w z6k##ev{naAVuQQ#l=*{s{z@9~(3+9TWQ8*QgQO)`C#y;xtSXlHYfSp&xvxvDN??pt z23q_zx!iz17(9=J^5g~l{!petV0y^ORKoVlscddh`y=RdI|wTWUu|V06eW72PjafX zH*a-~bwRXc-7rJnLRK6JJn*uw;t78jT27UxtHQVCf}_B4TZ0OvOrq?ZYhDG9T9nXjVAU6zwo z;uZ}@mINya&*Xi!vy;n zVwsyG$0ql+$S_q3v+N}HvB(}wBAl|AL_b5!EiQy&u-hTYknM?*vkCc=twgM*H4&}! z`kQc5^%O*#j%YEeq^!aoh17XIP+baEQt2GFrm@)Ex#*43Int(+z=3BeZWyekZeMXW z^)Lh>r+^j#4N@}1*bHJ_xxMb0dS+*V$q;KZu<3jj0Blyv>&l>tUv+xU|g9 zsh1rF%}U$PS2*2g*-3Uz2{sq|##<=u;r3Cn(uh_=OisORK5G&6lDgo!5E5z4ai+&1Y4I#B64U_0 zkz#Ym6ez|#WA10ut**x8=*bt!^viX6!6@Q-h4v0DdQRkI-rr`< zE5^Fchps5~q2GOa-6!)O`3Bm2vkSd!Xj?D_*&$ZZ%H3Y9S2`ups@wrKXP(=Q0$U^I zK$|YpJ%)Bkg+zyWREDnTl{^_Gh*3E?z8sj^UeQG)#gbH5mV>T_WUKy$Q^8wsU=D7L2_9+EM~7)I)TKVB<7XmWtEi+-C~Xx<~hvEtEi~R+)ENJnH5^H1Vgn; z(Ki`eNyg2@K3!7)YnTDbNjCF1=xmrnG5^IV2sCrc&dTA)#^~qdR8(AAi0X3SW}*bx z{)dvvcTa~Pv;PX+h1pQ<6gmc@-8zGFq3_C}VT6Md2|3VSog8KEa@lB;LNF997ZPgC zbXse2-X#|rvXd*{T?Av$D=L$LXwc3;Pti=w%2?dH?INOKJHs`ahvh6r4x8SO6k^oO ztWuFlPY$#~ujC34W7W)ZpJyBfwDu>^pABUzokNb^q(Ce&9$~yv7IHyVsz~NrT@aZW zszL5!ti@6cNa*7-%k;M6#prlwLfZsjaRavPgy8_js95NtI2b9B#7Gr|69-5ZJ z%?&JPWLA)NVqi~`0oHBk=2CZ_yA&$KEo%vu&{}>=3G=*J`J%jh<;7;QlX^3ucxOU( z`4ddu#f4IS4ofok)?}++?V`Fu={{~VNMkWlVpw2aX;!|Z%F46pN3iL!Xn_V6Nu3W` zoPzRT^D!wXOJhs1Akv?0s!bw^dBWk*EBP2igMfQV*p1VDc@-FXoEu4pWP{FySy>rO zg(58NWpZH{WEx>b?patMz|^76^I@tggF@l_Lly`&Sg^6&mBq>dQq5F2onUd5hoxn? z8f&6Ms1a6PS^+`PI}3V?K-8@4HDP4}Wee@j6)SUuoFj^RHW#wqJhVL&3lOU@Kn1h(yIIa>wNriZ2&%vne z1J7`HD!j$$vVQRK=^Z^n7zDz}C`+(%epYico_|yZ@zkUo4_Z*;gybd!m3VRs%RFZ0 z^g+v2aOshdd5hL3hve6y+8BWdCy@ z*or}~Z>p}W_t!PfYx1=O16b}!0xWMCRye+LC`D73V(ju~6`>0gl4qe%R5E%^f!8UPZW?3}mII1|6zeScibb zrJ~gnA?rV%l$Z}{v6tL%tqzh6&p&CRMdS{wl53??`zq>B5 zeUQ6^9Pw-MiG zx(9}mNL(Hy4*gY*$x6D*mnCWPaVH+CE%T#RY%eZdd?>XnUW!X(OO?&tf+TC}YJ4^R zMRnDFpU5Ly1f$jqO`DMX4j3Y4$*plV5CyKOt5!So$H@h*NE*E7GNRL z^p-@Da1IB~DUd~XQAMGQ?gIF|IX;XwHh!7TGY1?C3 zLCMQ1g<{VUnfMbq>YA!r=h-@~KvtSEYOY6UQ`U>g!s9(x@LY z8<9)wp_ECZa>@uBs^dD#AY1L2tT6I*PeYGqXL&LFeC`6Roy$bcvr!9-(v@_%X(y7} zxpC%$X_X)w6CB6bn&8?O%P5vK2JFyTqQ*IGq_E0p_!5p{R_tVS;w+f8r82-TW$G9f zQmar6AS8}4pO2$#;&?PU=cxW5i3VhCxIt%)qmZpRp9~51x-Vk4b4(7S0BQNA^ddv$ zD~6zYvr0-Zce2OX(4FlZu3fwCO8TV9nXtD+1`hL~1~dwgO~R3fcGH88;ncb%y0iC% z_gm!YEpZg&AP_0uAvJNx_eg}(GWZ3%Kn5|DXk^80X;JFjhN_leWssVUUG^&DJbZDNhTed%HYAiKv7eTU#ta=Ha`0S*?RCJboQ++ zdDsaJ+1Ax_1BV)IH_@l5@KDUkJcT9L`mvUYQs7LTe&?zRtM!EBK_*5H4TK8RrnDG$ z*it!eZ)bc~pVP3Og(-e)3~3A66#cD4tYxX`6L}&cacTdyX+rpd(Jl4e0Y8&YW*z6u;w95Jv6{*SXmNaYhTW-D710m2nNI*N@8+jAyXY6-G!LM$1R7Q*WKM=0l#ZdGb|c;roXCs;Op3`%zNu%QkVdGnKb$2+lO!J_twbSwK+^FiNBHzq&ZfrSDpmK3w$#{HiD(%rA^n`L{(_5XS^4sG#pXj07b ztFxy3wfUM}58j!v!~4OGZy=aLU+Nis<(lp-zV&C z^_~23?B2mI<6xZ7``tFS{ic)781q>A)~C+8`ut%ycqR0`uf@$j`K}Z1>-mpKs}5hk z^&=b>6Z(70&)S;Od*(NvKGrY!whKyEEA^?+7v6ExvWL$+>e4~mUwrJ#S6{-RtX{D! z=abc?pTGI%cN4Gs>xYY4Pq+?R{7|9)wD|TL{`JjgPgFkp=aSybPODX_N$CGG`sxSH zzWbcgYfrlV(F3a{v?+C=&>uVg(+`eW{qu7z6+NGSc?ixHt9ylB^vQuMZ;V^$$v)tP ziW^p^jaTX=p}+LgHDj-y@O4?O@08WY#a{ZZQYknGl@v4W?Dfq#O^?qWIxg+yj7Qd? z@y84Os&BWRoqp}Se{M>8{k^AJ=isnsxzLw1jL&blGQIZ0KmR;?{;Q8~gas?~vG*Q4 z+`T{#gH~MUU)}rmwRNTU zdV1}1gi>3De&dw-X9}lXk6Clcc8rNeq3>Gw4tx6m)Q3jKnq{m(1B{k*GoZf{&MvSAR)|GCgd^nCB#QTvWN_~G)t z?;Jd5N&@ylaQZGO=I+@SU-QAw%YMwgYu%@F{&hZ1@{JPuh^2r1^2ni26qe_-6%MrtJUk>k`kxVc}ImUy?C2b;X`j=S_KaPwO`?oO%piKoI%? zZxkgSHsbZ44=CRJQE=)%&QWTU(08uC>-#6~xTkFJHDC07bjVXUq5FZ*zZvk_H5b31 z8T;-_2aK=2`^_(~O2#Sfq?jL;CC#|EZbsczBdadfaN~H=t`^7`tW&JnZe8IT4 zf4Jb`hut{TE%eGq-k*NX`&k+H-1hO_+>P5YzD^YSWqXfab^gt@kCdz!HudYAVe6E- zUg+L=r(Jx-X&aNTc=WMPuDa;sX*m2U^fO=Evhbfb59nXI}CQJD~2oQ_P2sekIkj->ggTzTd`api;WyL>I$P3RxiU3Am!9j6`o z(m7A0{=Hw~w>W$$^oORU>^V1~{MZVOlVTn@cvkOE$|oFAxX1wdSu-RO05xk$L_CQYs&R3 zZs;5H(bWI+ti}roLSOb(*}AF!NLY93gX1gjTs$1@`MJY7GroOuOogMGqcH@GJpsQ{Y`paLO z*8bSM!;XCY_J@ZYedEke@zRyhxBR2|^RG94a$>>zUv7BvvS|n7V7AZ)-E;cN$Dhb4 zEXPlkoyN#UjFPcqk7i&z43?dpHr$$=w)>u z-@j<8@A;wQ1|6SP*LJQ_HwgWQ%4PQjmb_4%{QUCoj~#o?aY{Wc^oeJ0yRY@Cwd>1% z8t~=|@BaM|j9;OTS$ooLF&j!VN6nhl>x+J=f5AN7hvo0_$gZXD_g2d{J~ZC7=%|xl zhdc=VmRqj4GUn8N_rJ7y;*pcu@^RSRBlP4cpX{wa;mYHu?`?YWrn8PXR;l?y|Hq-z zPB{3|=Tj~`W7LH&ew_6)<`JQ%`h(YHPN{5cDz)%Un2Hu*`V z4#es5q?kE5ZKX%At2*GE?-MV)WW(8LhbcnebjmAjFS*{ldF5YTIcoBx@6caWLf`O6 z@;k46@!em}J^#z}sn1^p`bwc6{q-{+->~K2DXottrhhi&v<*t#DD>i!=brgRN9lnX z&*wi{^JCwmm3mU>d0$`m?K>CeP3Upxd)Z@8YlA-DEA;aQ&fj)f>Ck}_`}e$R#z~uz z{t%qlO^W&VH~r?kw;iqtW=9KdZmDgqtX?=NS~yd0Zfc5J?dCvBZPaR^L0c-LM#Z7; zZrZb|INtMD%etv8!=pzd9I*+Kuz^wm~=1>l#eHzrXT}n{jL_IM3=OigU}{1$@zL#eB+8-?xUD{{{&+jf;HQ#E9mI2jlOH`pZT4j8+*bOa98lnmm%HHe`n1S6@nh`v| zCVI}$3if#G8ku5IQ*TDn?`|0k$GWCybwl~8EwSiY0EFV`add4`v#_RSAzl&EssHX0 z`j=E&%JX-pQ60cLPV=G`wY70!8QcZd|JD+!oZQ09%+myPfs1;uWC*`7ND*1kg#) zo)+zygitVhB_yr7s;qL6zoyhbA1{QGULtfdA*`elZ=|*`g8IM8h_zWFWf*((L=*{S z|0D9AI1X>0{y~|KOP}<|S2Y!TxvZqY)^xBqrqJ2Sztv%d%<8X^%XFW(7rX8OAWvtY_8A_Fqlh?2R2n`lMLok zrNAZ&ZKlCoY7wvrLR)4qm%0*Iy3npQm`gnY>~NuRCzzN^{R0^OalKMFbnh0 zR=BTk@Ap7Yc+|>{)&q8EWVuU_60$7) zXz}VW+OmG&wk8@`Ql*(u2NiSDv5Qs9n(Ar7oO9oHEbzyG1x)z@%1kpdFh|j0U9qlotnmZ zpfn!nSGXutmSdXR@IV63Yn0az@qDz6`Fsm_DBynp#{upEJPPnTKs>`!KLF+c{tU=6 z?FD3>19A0IOAMAM57S9=srzl(Lk448XvZ8`LhbOZ0>17$xqWvnnfLC;`;^_%gEtUg zfhPiR{myy|kEspUb)=L;5fa$pB0@HCX0LC=q)g?BDXJ9hc9~IbrsF;Vj7mOUJ!4~U04o$4o-~@c9{|&F{{>9P{Wma92Jxb$G}a4}RUkBe z$5H3IFEAbV0AM=qP+*)i;?*HGb`-E2p&bKE$IStz<4yyn;}!$sCp3PK zNXKmfrsFOHrsJLtjFV8jT4`gK0Xte~R|C^=Zvdv_-Udv^y&D)Ot9bQS8`}tMywIKn zrsHk{rsKW=Ovim280V3A^}daL3G7IreG5#-{Rx0iOnZ4R9l159EY#*ESw-6JQPC zX25lTTL7N~d=~I?z~=xDM4m4I4gq`-a5&&LK=#SYfSe@{!=-0QVtSS&=29C>Xrg++ zU@(Mi8uFL8F7=8{d)=nJZ__@uX`J(Q9+=Th+*p)U)8Y;0lEY`3Ho&G0xA{iev~f0V zqD{-TX?ZrS)TYg}X&m)Bk7k>OLyh)v(kLe!HCaxAI$y3wO!?A4$k|_;*Lsy-xH``- z+uUyfzj?ndW3OlIt6hGf$NUN!@{{`d4*eJpM+IL@vUI{ZzHuofrPTQ&~ z@;sLc2Ca{Dn^JF$1#JPYcr^)kt(mBCH6L|8r7vFLRD%hPq?xJ_+JVb*;hKrMufqxi z4A)FmpoQD7Op_z87?(pcp}lJMk2i~yJHEprYNu@+`T*ETVXupJ|E)6lySG#w&?{3$u zI$*`d9>&aHE%j}e`)0hFr_*KrEFa3m=9sbO5++{SeWA9;A+jX2qN7KNG!^n$EmdCe zm{c4^zY$!UR~~`nIC@xl;>d^$GeXMU??HD07M=-!eO7h^NA`PgWjdJpJy@79<;i|4 zk4H)%jnkw)B;`xu383hfT`*!nj8)?VyT&O9U$Ojc72K!Y{dW5rdRG^u?(E&ZQc(8} z3wfKxV`>qLw`%2sKch4c2tfHN$=v}GxEU#|G7oH9eaS5dUGW(2s&z0jpVz+TDR}0s zE*PkNj1J!c>@UeUq(Q*5IWtwNX0`L`m8r-o7-$#hX!Uj9@7)U#iD_R+Y(+YGnvG4B(0&OM+tedjp|wyL76pA+t00eSR<1`_ zH!U@9^U7;kbJf{oL)l~!&FUpfE?Ky&g#W#ylm7RTPSIUSQAJnw&^}5jeDT^hUia#=8p6Y1iHkjI&cgf%+m{5?PS6MttZJ z2H7duJzDYh>g>T7u?UfBtumY<&qk~77x3>L`Oo*RtxfJ{~suRkrYO| zdq-*e3ngo0vq7}P?BlV0)L!^w#tY&-VTnOz;tX2iw7T6XbuL{VmumC)%=1`bF)vrOkOFUV^RVC z$*pw(e~pjtBq{T{CC>2RSO?xO)U2fP^;(JNvBGld@iaakr*7~*CSE1L6|Yv|&b^%! zu~jb+8Z2yMS$q%de4)i)+S9fOt;aP&JKCmA2X>y&xKh-77ubB4+O+$CT>-un^)Fz0 z9nRI6j&UsJ{dP$uADE`C2Bz17Yi-(t!1Oxpahvu7uylzV3uUb19t!MS$t@34}h_i;?-w1Z69=&Ud{91zs~JAV4APMrqu!SNL;=GsQIq2 z`EIajUjREt_hxX% zcB;_c0(KFw6tx4`g@S!!^Yy@ZI7Rr9fGrbjFtDY9jRv+vu<^jShZ3(cfh`iu3#?VJ z*}%9ii&r(kS_Eqb77z^8?XvgEFZpvUCLoNCwkzKGlELDF|HZKIGy2gk<-D%`Omj5C-$mFb)=*Y4V!Jl-Wn8>3CrfH(+yTaQVAM&%!>C z@h+k3+y6hk`>38)A#rLdh+Yoy`>N}kTKu3UCbOl7;aVhLbMZk#><22VPb!}1@}q1d zr-DqS$M+B;tDaHdpsMD}vRyg+!OFUNt(Fb|BaKGBvT8H(lK}o8UVckVej8zbM6T@(nEmUzk z6Q})nI20Hh%aDo)d~1<20PDb+;v@KFN+5yL$cG@v^uQQ2k68VBf1{*8gOY6*Y-pCY z94|Euxy#aZ%yXZ(Dflm1+E9-S4RubRiYcwIsf3- z6WTb#VQ0E+E#QHGmjNCGcr76IsoQP=#C}y9$Jr>rmjUtX7H$6ncqCu~dTuP>K7iQC zYvX?RRKQF?>|eF{0I`qVwh*uo@EX8kK<+VPFRSeqz}bKufPVtyPV$L>+?T8dd=n5m zgKggcE&$|^YyzABxEOE-;1a-DfJ*_Z0Z#$+1D*=l2zVOcV!$&1uLL|3@Fu_&fX@S- z1^5~ucE#J?2Rs|_3&7QY@sJEiYg-cF`G7+KF8~|{xE63C;Dvy5051YO6Yyfd3jwh$ z(RMK)Y^}Dd0j~hu2zVvn3xHPvz6pq(_qP84t_K_n$+-@2G~o4s8GvZ3wo<^q04@VW zo3*U~ycO_#z}o=t1iTCI5x~0vp9S0i_y*v^fbRkR4Uj715x^u!*rR~d1&;xa27DZF z9N-gx#{;5I+G+ux0t^76Z`%0s;a0%w0MSQn_W*7K{43zAfX@T|9q>g!JOPkBpOYag zde4WL-t!@bN87m5hDqekuwX9k?TR&%D0{KAYh3D|xYMplRPPzgrFH?Mg_EehFqn%^ zPsP?rbZnWZw zVRzo94X|m$ZQ4ki#!;c;PPA#+HjQfmonEO;n`zUkY}!1V#?hqHTV&JDv}tGCw5x5} zdYd+~q0Xl_LARm~a#DkP2oGn#XEs5(A6N@-=7Kox2NLaQcPE6d-&ec$tXRHhZ|mq# z?zpJj?e0Btwu9L^mgJM*y%JZvJeSm~nRzy?0hp$(urYK=m-Pi#f=rlUbm4pB94pT7 zpRvAhp6$2-;bX&|+A&S;jZ;h9ikVfrm?r#=iTNawtS*>r2d+Nm0x*ttT>@|#e$AD^ zS}~9Ips{IYnokDPeBdNYcOLlYuhCxd>*-t0h!hwy0n5hu8`HQth6K33WBJJPz6Y2G z_yM37@H0SaE9kj4*5yk;7M69^dX<>gtHfM<9wwoQOj9tIXim-dkxl#2=2Or|IvofS7fSSL9SSPq$)Y>NGYj6+aK)?XxDUdWB1aE2jXkAlG>kNjrje$dXVWgVY1iAd zJ8T+_BOQa{`wgyKT$XzY2n)q9x^U5c1~g~*GDwkAw7*06?CR4_R5VCeyg;;4iCS&a~OpW}l zONu!~f2v8@vSeq6`Kk5jtZ?oH(OLfW;?sV|&{<;01gtxU4(m}*0^C1fYOpup(i~bFE(Ojfb}nyvMFso}_|fIOun^V{o@uB$)?Io!irRUV}~KUF`|03%Bpw ze`hD2D3Pc%cCK5oe7g$vYjWq&pGHEUdv1K1kmS3+ET5oP+dxgoi(yWttSqH*m!!<0ei9 zDd!*=n9Q>SS2`{$Z=i6$X%C2d8Yqz(1|@^H0`S#(A2a}d0zjs$*B}Ya2sTp|H)pnk zWKC-T{BG7QKFF{yzW^A5yZZ{}R{psybu zR9387v3!*Z?%O5HomqGodk>4nhjZ=7IrIS~NV};Tx;)OS*u6R%&p3DQ%#0e%ie)`; zWLjM&{)~=adU(VhaOjNoy%0!ocR>0x4bJ^bj2dy)UYu;=4Fzc}u6P-ooH%T@8%GDV`eIj;W?Sb3Ai7EiDE1F0fCJ!v+tBub zf@Pm215(2G1Dpgn0FZ{`AV5w4_?gqT*?>a2 z-q_+e)EisGTzp+X{Z(i`7|g|^R`P&2QMR~fwYs=Za*3tHXa3Yshe%u zZ8q%zn}&@-!?($%J!jKiw`s7t4d2H$4U@c~K`;bMR6PvlQaSjxOVH2Xp8Vx4jPwWx zcEm*8tRU?;Cb4I8W)I}XHR$Hd1RxrdK`yi;o0ei@)Z=PF3XU~l|D$8DHG}ryE{OgM zoyH3Htj;cVh6$#`3FSuWfN zeHa1G@bFC#<~=@a6NG8f=(-!%m(n0WR7|6Pv5ya0^~1Se*axAeq77(iDw=>Jm<*it zh~*0HRgK7l8sRJW+YF6><)A+33m7B;ZVgyiw(|jL&|d(U3Ah$;8sLS1<$xILZ6^X= z3|I@e4ln?C2_V(_rGSX5E(5#+5M^m&eW4pcdahy6UjO{W8<4h#bFcSKurt_ z1V_uCQc2g)GG7k8vMrp4Ui098NoX|CwIrVhjGOlH>>oqB85lKiyrN!4|6x2lhP#eI z8Riu)`yS5=#+<{)!*R0kW(#Yd5bmSNMT+O!;-HruAnwP`YrISOR4#<}I; zOk`vhYn&?9xVJgx3HmU76eeNU1A`31Eo`}BwoqD+Yu_C*m#lVg45J+TQ5>|PwJ5U} zdwg$hXBLlauE9>on!;_!DZxI7X&qkG8#VTlE;M!=^h9I@z?wP~KOVs{$nit!B;fK~6z5as2Wv$qvDf)t4|<#%N)(9U z;0o=1CQfY?L`e9dl;c1<9v2+hc&>+RToutKa4N3vkU#x%g&PlljlVI>f1C0Dh;UhH zP_eBtL;|)m727_*X$;V1!a!%+y8+VxaduG*2@fF4HyyAZun_P}z+ym7UL}B+1I`5e z8z4>W^0el(octB1%Cje5eP6TAbzJW_CI$~PU5p#*zMb1Q#XksqDB_*<$DCQxh z(j}86Wj0Zs-4JuhvWZfjDDqECKULOw)S%uvkJAl?m)dRG1vU+*5e(m-ZQ2bs?QWZP zpH16h)9|F<#GS=MRh@SGZWKn;Wgm3$!^gtcWp4|{bVBWUea&vxij93%yLYTuzC*Ph zWZp>)-xzubnW>$p;WZ4rej%@6;J|dkliOogxnJFcP3r|csd?KAcUU{tNKxK`SUx36 zia1|v>Fpjk)Lx79jm6cuOC7@F-W^G2I?p4s;tuWUFrPShIHu_MV--UI*W!v-<8a3m z3ytB%eUH$lxHQ#H%xciZ!s3ms_->0~1eaUiMhzlnW!J}iO0HeN&SE<0r8(Cj+lajev(?$4( z&Ou?}3;Ks{=)@s>Y?!`enxm2Czj398>Ps9y^fd?d%lOQ;1J`(54vqQWZ096FRV5&~ zATIfl2H7;{DMi|B_H*9EWOgxM3}yFHzc~*kQkQv<09`zI=)X#PZT}MvzpcyG8zKSQ zfVzy;UuWo*&@pV6d4R_N&Iil}tOc9{SO<6#AU^xvRs+}!SPOVEAaz^-uobWca4Fy- z!1DodBC3sfECswBa2X&?`{jVtHKzf-19&cJk9Y@S1hCO8^QKXQVOUyS) zQli)b#9T6CQwkFm=Wk*zbvSCQ)1wKe)8kniO`{2?Y23Qhv|^h^4XA0gHq9LfGzBK7 z9#Y?eQ#p;5RrUVVU{k70xT6nssaBiB2Og<<^=sZK;~){8Hkdl?X!z_0XB;coFu-E# z2t3RJvSLY!>bH{32+!tBjvH*E^D$P4#YfJL!B9PG3ipT&mz@9wme4+9V0U)o(J9N(>ev0D694oXNao6gZyKy>| zjlg0g6bJ3Z42`Um-$f`}20BYzxDosJu<(zel$}QGN`y}gQ_4(}h7@-R_or48Cq^t= zR(?hZ-+2~WmU(yJ8iC89nc)jFPrLUHa%`VB*7%y{+3(kj&P_mi);<(>%$ZYakLY$S zA9kjICy1+|t{%Vlk2v3m*NM!-t}p9Q=MkQ2f+fK=+&0{$ED zIzTSAZUHg45WqVDX^h_mcpTt;fcb#;1M(@;1Ax_l4*@O%dX znSy!=?E~-;bE)OH>pVD0bslSM8q26@*Vwe{Y}%bR?OvPqm`!`qroCX(UbbmFY}ziH zhWGM|@wrcYy0V$y&1-3`5B|d2@1m1?{O7F$pM@$<5-saE-jO65*d3^=Lvv1qmsLuU zH=?w?$Y*? zK(-vqz_w?dSqHl3P1|n;90UIgfGp#SrVkFV`+%7417a?j{4{L`_%%)XU@N@R2VD=! zYO|zIB;%*w16x)X@{DXZ28te4Oy4o8*vYxL;?+^O>rqtzj90v(yyHO9+N#OZO-0NOTO@uDs?tquP1!bfvOkCj`2 zY4iFXV4Cl7VCllQ+2$k95kh+pm^P)q2Bu@sMBs`uUU4~am|*PTLj{X83eyn6Hn2oL zd=!od3+GOU)2N<|@MFS8A=9Mfeu67(=9>@A8(|Q#1Rc1>O;87cwWj+y}b ziXt(j{Szab?fEZ2Dw8h&s{y|PtONWS@N~d$0M`P33wR0OkAM#X{sj0KAdZK%y$A?X zK#q;zt##S$?E%PE=kAQ2{fTMKLCmEdMJS~yQF@7(OCD-bt`g;eDKUJu6MU3Ee74hI zF4<+H#3u5bzsRmjRl%?Gm}fAT9LmzPMKK0>jVaN zJy*zpE{6moS-lx3oC|FeFJHscn!y8F8?Jc8c}LSuv}t~u76e8aiI;c4G~c=KpC+_* zxNF*an|7N`<04CkM$$A-A+!S*!|1{_%|oDd;IhKQZM>%u7XA#1X>w%Tg{yCaowe9T>opneg<Nk=_ zvyfpn9k^H&t2jX6!skOiB_#*XVem&0kbv{SX+z}_>)rlvlQvz_bC|T8#;8~<$M&F5 z_X5l{#C;)*tj_?z9Kihnsg?!;9uGJO&<8jekaZpc$l~QdK(z1@)51&4rQ&ecH12O` z+F+ZOYSaEO!D98o1*=eK|5t+b0CEer&)7NL2$uEf4I>g%p<5B70~fo4SEu`5l&LPO zq)0-Q3L#-#4hE!9jR0h;9|B0B8VN|D!gqPvDgh4zWDTZ4IJ8g^(?Uhe#V=D!XrgRc zYZ`56%}4Rlv_DL!C_3RnHA`s!S3*T|F#ES@tB{v={%CdeYqZrE2nq!&9gu=G7LbB9 z4v>PC0Z75Z2NFcEjsm1$&47St!6K#wi;ZxyD z2oKj-Op~2KJ9Tu|sgS>SxP)IP)05b5(^!0f!j7Zms}sPN@%bqqYc33%9#g9HS(yhrAr6rF&=sIjnXds76!JXKK zl;f_{ZFWlB$mN>Dx;xv4C5emWMjK<%^%QK=&CeXG{mwhr8FW2WswvMgnt(egX@Ow% zi1I>CEs!*(%yL>?rc>UUoaSYCsGDxQ5=-zZo54=mqf<^mNTignUAFcFJCco1(bGvR z!B)wgH%ZyihP^?Hi{;1DvwjZC)Y!fz#+RRO0fua%IBt{CF_z}yf_EUUt|?R1#KrE9 zJKg@RPr;msp7Ph4O%#;o8g4qZYsxHbT80D%n7Blo$q>Z#kp3T@Kou?TXW(VOQY2Q zA_bttVdQp7U4K$yEm_|A@Z969IFdxz4M)$!F_uO*0MNkbTIPL8wNu{m0gry0=#ob0 z4G$B=F_uPSkfb$^nL6H1)If0RpUzrH31S(SZu`;$Aym=xf~72uu{780s44FUp14?+ zgYLG0ty`@)ni6_%H0dxFO6brw93yeD)womZZJ1U(Lu;F1jj>;X;BZcD!!a8d%kxW& zMK?gS%^kr#kbrQv#^gRgrJ8zO7FsVyc|=ph@}qTGNM=lW=>@txUzBB7lZ;8;S)M_l z#l?Ej92F*Xpp@r~TOYy54|jK%+y_c|MtF-dIHv@Wr`A)Xj0NSC5K1~IZBEL#qd{32 zLYV~0nh?rSpsWj_aB*;b2xS5&4~0<1gYt}%l92&QLI{OCSYg<@72kLXX;lVWpDQG; zCF(Qq@Y8IpDI&vt*CiG|kjoEGb@HTA85*b4NeiJkW&Qy0!A3INMY%hEvhuc4a6;>Y ziHxOFK0YL6^72ExmWx$foNzl$J4>RLgzzxz>@JjbT_{g($po~-=VU*)L?P3?YNU=X6|?>Yz!MUX4uP^*PYHO&oV>A=7N#d0NsHSKlOsG5yqFt z;J z>Dw1+zw=&iavy^7gyudJY;mzWai{Ekvhv}zI`@4}C|T)D0gvw-_H!zN;$rcxx2?0<6Bo-qr@QFt`B;sh0#Tgx3n+{g+JhrNiHm(2cjkD= zybX8g7V|yS`U(gR=WMY6A+CEPqYH%}JD;pN)JiG*xGd9U-xLAaP4J4wn|YHs)n(8gfK_ z!sLFqGA+jGT^`d(?skg}L@8{FXaHKGn&KR}R76GbQnvw>Y6h9f*tyUyUpV7g!`G?Go%(yi-^M^E>@HVumKH4n2C_6dg!^j*3S6EoVk-9 zHh?4d36lG}hmUzKG!}hBP3{xA7aPQ#wTQdqWyEIgwHLln58!al+^Nv?{AD%s zQ4)Xg*YTa3Pj=k#CikPd7#amxG@P@zT10QX(; zD<4^MsVSj%zbOx6g=!==U0f_J3c6R{U5W9k72ccEe6Co53OM7`D3M>Q%~pWB;5VmAGPZ%v~Rfk-sC>DOYT{cPS@Pm51B!+ zcQ|L=vq2A)@GQxFVNXw$BZ@)yH)dQ`m)xQHBi4QSC%1Y-({bjWE3uq)&yn0ef2`)a z&{*^xX>!l$lDk{dS&BPb{*42dT&`RGp@qSjAUK>e_dL+!V%uUmTiz}4V?N$?qa%tz z3r+6sF1hDRI-J4j8YX9+s&k+5_B$++!#Q&=06i{tH|{JmUA|zqJo)k^+V8ykP44+R zcfCp~Cx-`DT+a+S~gL?cmt0Z4x4 z+UF}R&++Bd`l}bZTbh;jmesZfYnm1}+FS3TWW(jb&tzv;w)lmM+vu9hBBMy*zmn=J zaQ-;u)TzFlOka9YW=WPiH?P1`nCT3jIwC78Q!z%aFAra~Ey2@0&}aYiij2fIde7kQ><=X*@Poh;6XQg`{~m@@q= zxU<+rMR|E8)4bVsu|s~!Gc1t^Kj>+tn&$#vxpOV07bp*~+-L$lAP>s_5ygQtEiEt!+-_Rh`B$u2H*mlR~#$#=#!$?7D$ zO_ep?x~f2BU};w7cq@UU^75y9ay_|)nYDE_Q^(^sN=+nq^)GOm5>R+ebi|gCXMLpE zk0B$aM4z=b=ho%=u@}~~)EvCUZ|ZpQX(C3_T>QYZ8o^%=8Zl{7#`p;qJ%>LXR8ZMi zQ|||}FzBBX61Jl)Hl!WIF|;%@->9`OvTU(R;A2Oqvgtv4`pSJvukHq-S~WM7;j7FxO6M|&IIKL z)WF%2l{t1Ig7I5L_;hVuaH+2vdZ*5>GMywUI1_+2%Iws7Wx>kog)^a9ARF>AT58jRee>#tIdz$1F}$$0EW6Sck&DI{C4yqVLz z*(F6ep6txdyiIq6eY`ECh;IB|R8Y4?*f*^Vo3{x)MQQAmsJx~?LuIfKBcpP@f0oow zcQHE5TiFtnm+CXo_^gNic#TO64B>AnvK!3cJ-Vpa1D9WzX@5Q>M4)V{^ehu^F<*Kx z2^3})PWNPc^RuUAN*ShROq~G9%&TviI#H)&sfpaoVlT4t7UX2=*Z!wY2sTZfAcaK? z53Xre98>mYoahYBv&xD&q_B8;er|DIai;!CfKGwkHg!Uc9D=tKU{OQff3}L@&O{0F zil=91WkzTkUE~pb)5`H$Gy8qgSWB{JRn`U3A7wagTJJBeT7W|e6iMA5IhBpo{(4VS zXV;bDEo^?4#W6PUvZy~OVn8;ls2XGYTJPlXJgWppdi6^J=68J?#Z)xMlUb5mm|c|X zEy(0c15+nhv(yCrdco8QrvCDNL8jHpIquB-qQb&yIVIDg#WSN`-Z#L^uPg2(2Ab+K m@n}a%s86xmg^hIP*A5Ijw`tQO9=)?e!_H0#9fP(hp#C4RP)Z8` literal 0 HcmV?d00001 diff --git a/debug/mt.dep b/debug/mt.dep new file mode 100644 index 0000000..b118ee7 --- /dev/null +++ b/debug/mt.dep @@ -0,0 +1 @@ +Manifest resource last updated at 15:17:36.17 on 21/07/2023 diff --git a/debug/vc80.idb b/debug/vc80.idb new file mode 100644 index 0000000000000000000000000000000000000000..390465e58798b019411d1a7508832ce2143192a1 GIT binary patch literal 257024 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 literal 0 HcmV?d00001 diff --git a/debug/vc80.pdb b/debug/vc80.pdb new file mode 100644 index 0000000000000000000000000000000000000000..60067923b2b93a0b9f25ff08838271266cac86d0 GIT binary patch literal 77824 zcmeHwdzf5Db!TiD+|zw~rmddtc0WcMLwp&{w4}kK znW3l0wrpaQWS0=$5E5A1n|vm`vJhZdaIy|xHb7v#KsGF}Yyw%9kOZ=9FcE|eA4~B5 zey2{|d%HC<%OATR@z&^Tb>DMNRh|0Psj5>|x2h)RXWL6FOGi7Sx$AP*Tr)aZFO1%_ z_xkHMZ`hHq8~kOAN#pB0eDZw8-?07i|9b>H0?z^jp2i=u(fj?C4bT6YS#M7D(*5(= zczIkrE^E~zZx`M!yj`5xzxaCbPorLZdE?E0X*PI2z}xFT4SV(F^i!16cB19~JOUm8 zkAO$OBj6G62zUfM0v-X6fJeY1;1PH>Bk<%)e(-G0p3j>{z$4%h@CbMWJOUm8kAO$O zBj6G62zUfM0zlv`d-tUV-t@%W0A5g=eY~^vsckR1<4s3WuT4))e|8YG>lSBDv{p3u4*#iWKASwEgv@x8?iW%$h` z21F*ES*NlaOe*s-a9kWiJ#nmGBaRIk$Hqh)+_R8YJvJxk%L)29O5bY+MvOTHPVz5o zRKr}hR2s|X4u(N_s#vR(!>JoI&T}>F?z4uiRw^YUIGm@lm!DNOR|>M_W;GnIRhm`7 zbVS2A?D~K_Kg6yNY7QcI=Zj1_vt4DcUOWD5u2G!IHi}R`S1C7w!wt>Dj%N;+%zv?5 zZ)D54Ky=&LquVY#_v*G=WYU?7RCa?I7~TWDZMo|26z#4NDS-aOK0E=Cw* zN=u8!3ag8=o%y9jbDH!$La(;kEAuOz)?&we@4G_3#M0aI_s(=$$lD&^K#VRGI^%-v znOj#EY1?zxc4m&i4PK zewB0ensUTHmQ9-jD*IfObvjU%Wy5la<@?M{BA3qGEZ^kW_LlO=ddb|~qO#9lLzdXa z@utlSRQ9$W*^5!WY=1(%v3rn*z3$6LZqf6mRr@cDc zk#;7~FguVR_E$`c{RY!6;IoWp>Ok8!X26V#d^$6s;nr@~JIC9tnYlPcO%QDf`>Z=O z?AmRq4SV9qELGKjHe4wRXWOuYcs2&9P*Ur_BxwCcLxV;v*v6Damh!B3v1TzA);1lM zRZYv;+MRfg%OYiJ!cS-FD(CFS;pgNnZhfYy@>42*mOgEM>B#C))^Xx=SY=M{A7On4LxZ}gnO^ZY{0--__ZXWC3v;$WEV zh-3BW%KQV0DdXK~!p@21qio&Lt`Ce1LGCW-=l(`bn9w~X_TsR7vF5{J#QKr~&G{*O zMlCXTi(EPrs{A(CBw6HBHW|j^+HYnw%n=Ra>Lum7<4>D8m2IhPQXj_GE#GI3sr+%3 zU)x^8dXQ^Mr<83pYQ?c;BdA-SMYaGaY{Dr%5}2ybYoh7eqFlIiX?LzZ!E2 zlF7g0^K;=`>)!dh<~eiCa(9&w=wGw9;}3nG54#?NtJPJ1hI(AG-V!CxtFWl+GRT7 z{JzkY=|es7I$L=K=!0nOPiefrpmN)^T!|^HcyV>5HRrHmKlw|LbASD&J3Z>laKy;v z>z`}9t}ZcMr^74pn||>xi%dH6DwTCOQ_f*bdw0LPu^BY4*6^><@JGXJ=@*KX@ z!^shcH)tGhRJk2%)|qg;2`7$y<(nbr+N59AFl)EN6Nk!q6opsRY zd5(aqb%*1Jd*WtVAHgT7)8itO⁢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 literal 0 HcmV?d00001 diff --git a/kiss.c b/kiss.c index 1f52345..0be9043 100644 --- a/kiss.c +++ b/kiss.c @@ -492,7 +492,7 @@ HANDLE OpenConnection(struct PORTCONTROL * PortVector) ASYSEND(PortVector, ENCBUFF, 5); } - if (KISS->KISSCMD && KISS->KISSCMDLEN) + if (KISS && KISS->KISSCMD && KISS->KISSCMDLEN) ASYSEND(PortVector, KISS->KISSCMD, KISS->KISSCMDLEN); @@ -1890,9 +1890,7 @@ VOID ConnecttoTCPThread(NPASYINFO ASY) ioctlsocket (sock, FIONBIO, ¶m); - // If configured send TNC command - - if (KISS->KISSCMD && KISS->KISSCMDLEN) + if (KISS && KISS->KISSCMD && KISS->KISSCMDLEN) send(sock, KISS->KISSCMD, KISS->KISSCMDLEN, 0); continue; diff --git a/tncinfo.h b/tncinfo.h index 299d7c9..8e0272c 100644 --- a/tncinfo.h +++ b/tncinfo.h @@ -626,9 +626,10 @@ typedef struct TNCINFO HANDLE hDevice; - int ReopenTimer; // Used to reopen device if failed (eg USB port removed) + int ReopenTimer; // Used to reopen device if failed (eg USB port removed) BOOL HostMode; // Set if in DED Host Mode // BOOL CRCMode; // Set if using SCS Extended DED Mode (JHOST4) + BOOL UsingTermMode; // Set if tnc should be left in term mode int Timeout; // Timeout response counter int Retries; int Window; // Window Size for ARQ @@ -676,6 +677,7 @@ typedef struct TNCINFO BOOL Robust; // Set if SCS Tracker is in Robust Packet mode or WINMOR TNC is in Robust Mode BOOL RobustDefault; // Set if SCS Tracker default is Robust Packet mode BOOL ForceRobust; // Don't allow Normal Packet even if scan requests it. + BOOL TeensyRPR; // Teensy RPR TNC - don't send %R char NormSpeed[8]; // Speed Param for Normal Packet on Tracker char RobustSpeed[8]; // Speed Param for Robust Packet on Tracker BOOL RPBEACON; // Send Beacon after each session