diff --git a/.svn/pristine/10/102d41bc4cd0f242aaba204301966dc699e1ff06.svn-base b/.svn/pristine/10/102d41bc4cd0f242aaba204301966dc699e1ff06.svn-base new file mode 100644 index 0000000..78e8a98 --- /dev/null +++ b/.svn/pristine/10/102d41bc4cd0f242aaba204301966dc699e1ff06.svn-base @@ -0,0 +1,2179 @@ +/* +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 +// +// FBB Forwarding Routines + +#include "bpqmail.h" + +#define GetSemaphore(Semaphore,ID) _GetSemaphore(Semaphore, ID, __FILE__, __LINE__) +void _GetSemaphore(struct SEM * Semaphore, int ID, char * File, int Line); + + +void DeleteRestartData(CIRCUIT * conn); + +int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress); +void MQTTMessageEvent(void* message); + +int MaxRXSize = 99999; +int MaxTXSize = 99999; + +struct FBBRestartData ** RestartData = NULL; +int RestartCount = 0; + +struct B2RestartData ** B2RestartRecs = NULL; +int B2RestartCount = 0; + +extern char ProperBaseDir[]; + +char RestartDir[MAX_PATH] = ""; + +void GetRestartData() +{ + int i; + struct FBBRestartData Restart; + struct FBBRestartData * RestartRec; + char MsgFile[MAX_PATH]; + FILE * hFile; + int FileSize; + struct stat STAT; + size_t ReadLen = 0; + time_t Age; + + strcpy(RestartDir, MailDir); + strcat(RestartDir, "/Restart"); + + // Make sure RestartDir exists + +#ifdef WIN32 + CreateDirectory(RestartDir, NULL); // Just in case +#else + mkdir(RestartDir, S_IRWXU | S_IRWXG | S_IRWXO); + chmod(RestartDir, S_IRWXU | S_IRWXG | S_IRWXO); +#endif + + // look for restart files. These will be numbered from 1 up + + for (i = 1; 1; i++) + { + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%d", RestartDir, i); + + if (stat(MsgFile, &STAT) == -1) + break; + + FileSize = STAT.st_size; + + Age = time(NULL) - STAT.st_ctime; + + if (Age > 86400 * 2) // Max 2 days + continue; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + break; + + // Read Restart Record + + fread(&Restart, 1, sizeof(struct FBBRestartData), hFile); + + if ((Restart.MailBufferSize + sizeof(struct FBBRestartData)) != FileSize) // Duff file + { + fclose(hFile); + break; + } + + RestartRec = zalloc(sizeof (struct FBBRestartData)); + + GetSemaphore(&AllocSemaphore, 0); + + RestartData = realloc(RestartData,(++RestartCount+1) * sizeof(void *)); + RestartData[RestartCount] = RestartRec; + + FreeSemaphore(&AllocSemaphore); + + memcpy(RestartRec, &Restart, sizeof(struct FBBRestartData)); + RestartRec->MailBuffer = malloc(RestartRec->MailBufferSize); + ReadLen = fread(RestartRec->MailBuffer, 1, RestartRec->MailBufferSize, hFile); + + Logprintf(LOG_BBS, 0, '?', "Restart Data for %s %s Len %d Loaded", RestartRec->Call, RestartRec->bid, RestartRec->length); + fclose(hFile); + } +} + + +void SaveRestartData() +{ + // Save restart data to file so we can reload on restart + // Restart data has pointers to buffers so we must save copy of data and reconstitue on restart + + // Delete and resave all restart data to keep restart directory clean + + int i, n = 1; + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + struct FBBRestartData * RestartRec = NULL; + struct stat STAT; + time_t NOW = time(NULL); + + + for (i = 1; 1; i++) + { + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%d", RestartDir, i); + + if (stat(MsgFile, &STAT) == -1) + break; + + DeleteFile(MsgFile); + } + + for (i = 1; i <= RestartCount; i++) + { + RestartRec = RestartData[i]; + + if (RestartRec == 0) + return; // Shouldn't happen! + + if ((NOW - RestartRec->TimeCreated) > 86400 * 2) // Max 2 days + continue; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%d", RestartDir, n++); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(RestartRec, 1, sizeof(struct FBBRestartData), hFile); // Save Header + WriteLen = fwrite(RestartRec->MailBuffer, 1, RestartRec->MailBufferSize, hFile); // Save Data + fclose(hFile); + } + } +} +VOID FBBputs(CIRCUIT * conn, char * buf) +{ + // Sends to user and logs + + int len = (int)strlen(buf); + + WriteLogLine(conn, '>', buf, len -1, LOG_BBS); + + QueueMsg(conn, buf, len); + + if (conn->BBSFlags & NEEDLF) + QueueMsg(conn, "\n", 1); +} + + +VOID ProcessFBBLine(CIRCUIT * conn, struct UserInfo * user, UCHAR* Buffer, int len) +{ + struct FBBHeaderLine * FBBHeader; // The Headers from an FBB forward block + int i; + int Index = 0; // Message Type Index for Stats + char * ptr; + char * Context; + char seps[] = " \r"; + int RestartPtr; + char * Respptr; + BOOL AllRejected = TRUE; + char * MPS; + char * ROChar; + + if (conn->Flags & GETTINGMESSAGE) + { + ProcessMsgLine(conn, user, Buffer, len); + if (conn->Flags & GETTINGMESSAGE) + + // Still going + return; + + SetupNextFBBMessage(conn); + return; + } + + if (conn->Flags & GETTINGTITLE) + { + ProcessMsgTitle(conn, user, Buffer, len); + return; + } + + // Should be FA FB F> FS FF FQ + + if (Buffer[0] == ';') // winlink comment or BPQ Type Select + { + if (memcmp(Buffer, "; MSGTYPES", 7) == 0) + { + char * ptr; + + conn->SendB = conn->SendP = conn->SendT = FALSE; + + ptr = strchr(&Buffer[10], 'B'); + + if (ptr) + { + conn->SendB = TRUE; + conn->MaxBLen = atoi(++ptr); + if (conn->MaxBLen == 0) conn->MaxBLen = 99999999; + } + + ptr = strchr(&Buffer[10], 'T'); + + if (ptr) + { + conn->SendT = TRUE; + conn->MaxTLen = atoi(++ptr); + if (conn->MaxTLen == 0) conn->MaxTLen = 99999999; + } + ptr = strchr(&Buffer[10], 'P'); + + if (ptr) + { + conn->SendP = TRUE; + conn->MaxPLen = atoi(++ptr); + if (conn->MaxPLen == 0) conn->MaxPLen = 99999999; + } + return; + } + + // Other ; Line - Ignore + + return; + } + + if (Buffer[0] != 'F') + { + if (strstr(Buffer, "*** Profanity detected") || strstr(Buffer, "*** Unknown message sender")) + { + // Winlink Check - hold message + + if (conn->FBBMsgsSent) + HoldSentMessages(conn, user); + } + + if (conn->BBSFlags & DISCONNECTING) + return; // Ignore if disconnect aleady started + + BBSputs(conn, "*** Protocol Error - Line should start with 'F'\r"); + Flush(conn); + Sleep(500); + conn->BBSFlags |= DISCONNECTING; + Disconnect(conn->BPQStream); + + return; + } + + switch (Buffer[1]) + { + case 'F': + + // Request Reverse + + if (conn->FBBMsgsSent) + FlagSentMessages(conn, user); + + if (!FBBDoForward(conn)) // Send proposal if anthing to forward + { + FBBputs(conn, "FQ\r"); + + conn->BBSFlags |= DISCONNECTING; + + // LinFBB needs a Disconnect Here + + if (conn->BPQBBS) + return; // BPQ will close when it sees FQ. Close collisions aren't good! + + if ((conn->SessType & Sess_PACTOR) == 0) + conn->CloseAfterFlush = 20; // 2 Secs + else + conn->CloseAfterFlush = 20; // PACTOR/WINMOR drivers support deferred disc so 5 secs should be enough + } + return; + + case 'S': + + // Proposal response + + Respptr=&Buffer[2]; + + for (i=0; i < conn->FBBIndex; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (FBBHeader->MsgType == 'P') + Index = PMSG; + else if (FBBHeader->MsgType == 'B') + Index = BMSG; + else if (FBBHeader->MsgType == 'T') + Index = TMSG; + + Respptr++; + + if (*Respptr == 'E') + { + // Rejected + + Logprintf(LOG_BBS, conn, '?', "Proposal %d Rejected by far end", i + 1); + } + + if ((*Respptr == '-') || (*Respptr == 'N') || (*Respptr == 'R') || (*Respptr == 'E')) // Not wanted + { + user->Total.MsgsRejectedOut[Index]++; + + // Zap the entry + + if (conn->Paclink || conn->RMSExpress || conn->PAT) // Not using Bit Masks + { + // Kill Messages sent to paclink/RMS Express unless BBS FWD bit set + + // What if WLE retrieves P message that is queued to differnet BBS? + // if we dont kill it will be offered again + + if (FBBHeader->FwdMsg->type == 'P' || (check_fwd_bit(FBBHeader->FwdMsg->fbbs, user->BBSNumber) == 0)) + FlagAsKilled(FBBHeader->FwdMsg, FALSE); + } + + clear_fwd_bit(FBBHeader->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(FBBHeader->FwdMsg->forw, user->BBSNumber); + + FBBHeader->FwdMsg->Locked = 0; // Unlock + + // Shouldn't we set P messages as Forwarded + // (or will check above have killed it if it is P with other FWD bits set) + // Maybe better to be safe !! + + if (FBBHeader->FwdMsg->type == 'P' || memcmp(FBBHeader->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + FBBHeader->FwdMsg->status = 'F'; // Mark as forwarded + FBBHeader->FwdMsg->datechanged=time(NULL); + } + + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); + + conn->UserPointer->ForwardingInfo->MsgCount--; + + SaveMessageDatabase(); + continue; + } + + // FBB uses H for HOLD, but I've never seen it. RMS Express sends H for Defer. + + + if (*Respptr == '=' || *Respptr == 'L' || (*Respptr == 'H' && conn->RMSExpress)) // Defer + { + // Remove entry from forwarding block + + FBBHeader->FwdMsg->Defered = 4; // Don't retry for the next few forward cycles + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); + continue; + } + + conn->RestartFrom = 0; // Assume Restart from + + if ((*Respptr == '!') || (*Respptr == 'A')) + { + // Restart + + char Num[10]; + char *numptr=&Num[0]; + + Respptr++; + + while (isdigit(*Respptr)) + { + *(numptr++) = *(Respptr++); + } + *numptr = 0; + + conn->RestartFrom = atoi(Num); + + *(--Respptr) = '+'; // So can drop through + } + + // FBB uses H for HOLD, but I've never seen it. RMS Express sends H for Defer. RMS use trapped above + + if ((*Respptr == '+') || (*Respptr == 'Y') || (*Respptr == 'H')) + { + struct tm * tm; + time_t now; + char * MsgBytes; + + conn->FBBMsgsSent = TRUE; // Messages to flag as complete when next command received + AllRejected = FALSE; + + if (conn->BBSFlags & FBBForwarding) + { + if (conn->BBSFlags & FBBB2Mode) + SendCompressedB2(conn, FBBHeader); + else + SendCompressed(conn, FBBHeader->FwdMsg); + } + else + { + nodeprintf(conn, "%s\r\n", FBBHeader->FwdMsg->title); + + MsgBytes = ReadMessageFile(FBBHeader->FwdMsg->number); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + FBBHeader->FwdMsg->length = (int)strlen(MsgBytes); + } + + now = time(NULL); + + tm = gmtime(&now); + + nodeprintf(conn, "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, + FBBHeader->FwdMsg->number, BBSName, HRoute, RlineVer); + + if (memcmp(MsgBytes, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + BBSputs(conn, "\r\n"); + + QueueMsg(conn, MsgBytes, FBBHeader->FwdMsg->length); + free(MsgBytes); + + user->Total.MsgsSent[Index]++; + user->Total.BytesForwardedOut[Index] += FBBHeader->FwdMsg->length; + + nodeprintf(conn, "%c\r\n", 26); + } + continue; + } + BBSputs(conn, "*** Protocol Error - Invalid Proposal Response'\r"); + } + + conn->FBBIndex = 0; // ready for next block; + conn->FBBChecksum = 0; + + + if (AllRejected && (conn->RMSExpress || conn->PAT)) + { + // RMS Express and PAT don't send FF or proposal after rejecting all messages + + FBBputs(conn, "FF\r"); + } + + return; + + case 'Q': + + if (conn->FBBMsgsSent) + FlagSentMessages(conn, user); + + conn->BBSFlags |= DISCONNECTING; + + Disconnect(conn->BPQStream); + return; + + case 'A': // Proposal + case 'B': // Proposal + + if (conn->FBBMsgsSent) + FlagSentMessages(conn, user); // Mark previously sent messages + + if (conn->DoReverse == FALSE) // Dont accept messages + return; + + // Accumulate checksum + + for (i=0; i< len; i++) + { + conn->FBBChecksum+=Buffer[i]; + } + + // Parse Header + + // Find free line + + for (i = 0; i < 5; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (FBBHeader->Format == 0) + break; + } + + if (i == 5) + { + BBSputs(conn, "*** Protocol Error - Too Many Proposals\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + } + + //FA P GM8BPQ G8BPQ G8BPQ 2209_GM8BPQ 8 + + FBBHeader->Format = Buffer[1]; + + ptr = strtok_s(&Buffer[3], seps, &Context); + + if (ptr == NULL) goto badparam; + + if (strlen(ptr) != 1) goto badparam; + + FBBHeader->MsgType = *ptr; + + if (FBBHeader->MsgType == 'P') + Index = PMSG; + else if (FBBHeader->MsgType == 'B') + Index = BMSG; + else if (FBBHeader->MsgType == 'T') + Index = TMSG; + + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam; + strlop(ptr, '-'); // Remove any (illegal) ssid + + if (strlen(ptr) > 6 ) goto badparam; + + strcpy(FBBHeader->From, ptr); + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam; + + if (strlen(ptr) > 40 ) goto badparam; + + strcpy(FBBHeader->ATBBS, ptr); + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam; + + if (strlen(ptr) > 6) + { + // Temp fix - reject instead of breaking connection + + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + Logprintf(LOG_BBS, conn, '?', "Message Rejected as TO field too long"); + + user->Total.MsgsRejectedIn[Index]++; + return; + } + + strlop(ptr, '-'); // Remove any (illegal) ssid + + strcpy(FBBHeader->To, ptr); + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam; + + if (strlen(ptr) > 12 ) goto badparam; + + strcpy(FBBHeader->BID, ptr); + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam; + + FBBHeader->Size = atoi(ptr); + + goto ok; + +badparam: + + BBSputs(conn, "*** Protocol Error - Proposal format error\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + return; + +ok: + + // Check Filters + + if (CheckRejFilters(FBBHeader->From, FBBHeader->To, FBBHeader->ATBBS, FBBHeader->BID, FBBHeader->MsgType, FBBHeader->Size)) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + Logprintf(LOG_BBS, conn, '?', "Message Rejected by Filters"); + + user->Total.MsgsRejectedIn[Index]++; + } + + // 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 ???) + + else if (DoWeWantIt(conn, FBBHeader) == FALSE) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + user->Total.MsgsRejectedIn[Index]++; + } + else if ((RestartPtr = LookupRestart(conn, FBBHeader)) > 0) + { + conn->FBBReplyIndex += sprintf(&conn->FBBReplyChars[conn->FBBReplyIndex], "!%d", RestartPtr); + } + else if (LookupTempBID(FBBHeader->BID)) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '='; + } + else + { + + // Save BID in temp list in case we are offered it again before completion + + BIDRec * TempBID = AllocateTempBIDRecord(); + strcpy(TempBID->BID, FBBHeader->BID); + TempBID->u.conn = conn; + + conn->FBBReplyChars[conn->FBBReplyIndex++] = '+'; + } + + FBBHeader->B2Message = FALSE; + + return; + + case 'C': // B2 Proposal + + if (conn->FBBMsgsSent) + FlagSentMessages(conn, user); // Mark previously sent messages + + if (conn->DoReverse == FALSE) // Dont accept messages + return; + + // Accumulate checksum + + for (i=0; i< len; i++) + { + conn->FBBChecksum+=Buffer[i]; + } + + // Parse Header + + // Find free line + + for (i = 0; i < 5; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (FBBHeader->Format == 0) + break; + } + + if (i == 5) + { + BBSputs(conn, "*** Protocol Error - Too Many Proposals\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + } + + + // FC EM A3EDD4P00P55 377 281 0 + + /* + + FC Proposal code. Requires B2 SID feature. + Type Message type ( 1 or 2 alphanumeric characters + + CM WinLink 2000 Control message + EM Encapsulated Message + ID Unique Message Identifier (max length 12 characters) + U-Size Uncompressed size of message + C-size Compressed size of message + + */ + FBBHeader->Format = Buffer[1]; + + ptr = strtok_s(&Buffer[3], seps, &Context); + if (ptr == NULL) goto badparam2; + if (strlen(ptr) != 2) goto badparam2; + FBBHeader->MsgType = 'P'; //ptr[0]; + + ptr = strtok_s(NULL, seps, &Context); + + if (ptr == NULL) goto badparam2; + + // Relay In RO mode adds @MPS@R to the MID. Don't know why (yet!) + + MPS = strlop(ptr, '@'); + if (MPS) + ROChar = strlop(MPS, '@'); + + if (strlen(ptr) > 12 ) goto badparam; + strcpy(FBBHeader->BID, ptr); + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) goto badparam2; + FBBHeader->Size = atoi(ptr); + + ptr = strtok_s(NULL, seps, &Context); + if (ptr == NULL) goto badparam2; + FBBHeader->CSize = atoi(ptr); + FBBHeader->B2Message = TRUE; + + // If using BPQ Extensions (From To AT in proposal) Check Filters + + Buffer[len - 1] = 0; + + if (conn->BPQBBS) + { + char * From = strtok_s(NULL, seps, &Context); + char * ATBBS = strtok_s(NULL, seps, &Context); + char * To = strtok_s(NULL, seps, &Context); + char * Type = strtok_s(NULL, seps, &Context); + + if (From && To && ATBBS && Type && CheckRejFilters(From, To, ATBBS, NULL, *Type, FBBHeader->Size)) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + user->Total.MsgsRejectedIn[Index]++; + Logprintf(LOG_BBS, conn, '?', "Message Rejected by Filters"); + + return; + } + } + goto ok2; + +badparam2: + + BBSputs(conn, "*** Protocol Error - Proposal format error\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + return; + +ok2: + if (LookupBID(FBBHeader->BID)) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + Logprintf(LOG_BBS, conn, '?', "Message Rejected by BID Check"); + user->Total.MsgsRejectedIn[Index]++; + + } + else if (FBBHeader->Size > MaxRXSize) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '-'; + Logprintf(LOG_BBS, conn, '?', "Message Rejected by Size Limit"); + user->Total.MsgsRejectedIn[Index]++; + + } + else if ((RestartPtr = LookupRestart(conn, FBBHeader)) > 0) + { + conn->FBBReplyIndex += sprintf(&conn->FBBReplyChars[conn->FBBReplyIndex], "!%d", RestartPtr); + } + + else if (LookupTempBID(FBBHeader->BID)) + { + memset(FBBHeader, 0, sizeof(struct FBBHeaderLine)); // Clear header + conn->FBBReplyChars[conn->FBBReplyIndex++] = '='; + } + else + { + // Save BID in temp list in case we are offered it again before completion + + BIDRec * TempBID = AllocateTempBIDRecord(); + strcpy(TempBID->BID, FBBHeader->BID); + TempBID->u.conn = conn; + + conn->FBBReplyChars[conn->FBBReplyIndex++] = 'Y'; + } + + return; + + case '>': + + // Optional Checksum + + if (conn->DoReverse == FALSE) // Dont accept messages + { + Logprintf(LOG_BBS, conn, '?', "Reverse Forwarding not allowed"); + Disconnect(conn->BPQStream); + return; + } + + if (len > 3) + { + int sum; + + sscanf(&Buffer[3], "%x", &sum); + + conn->FBBChecksum+=sum; + + if (conn->FBBChecksum) + { + BBSputs(conn, "*** Proposal Checksum Error\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + return; + } + } + + // Return "FS ", followed by +-= for each proposal + + conn->FBBReplyChars[conn->FBBReplyIndex] = 0; + conn->FBBReplyIndex = 0; + + nodeprintfEx(conn, "FS %s\r", conn->FBBReplyChars); + + // if all rejected, send proposals or prompt, else set up for first message + + FBBHeader = &conn->FBBHeaders[0]; + + if (FBBHeader->MsgType == 0) + { + conn->FBBIndex = 0; // ready for first block; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + conn->FBBChecksum = 0; + + if (!FBBDoForward(conn)) // Send proposal if anthing to forward + { + conn->InputMode = 0; + + if (conn->DoReverse) + FBBputs(conn, "FF\r"); + else + { + FBBputs(conn, "FQ\r"); + conn->CloseAfterFlush = 20; // 2 Secs + } + } + } + else + { + if (conn->BBSFlags & FBBForwarding) + { + conn->InputMode = 'B'; + } + + CreateMessage(conn, FBBHeader->From, FBBHeader->To, FBBHeader->ATBBS, FBBHeader->MsgType, FBBHeader->BID, NULL); + } + + return; + + } + + return; +} + +VOID HoldSentMessages(CIRCUIT * conn, struct UserInfo * user) +{ + struct FBBHeaderLine * FBBHeader; // The Headers from an FBB forward block + int i; + + conn->FBBMsgsSent = FALSE; + + for (i=0; i < 5; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (FBBHeader && FBBHeader->MsgType) // Not a zapped entry + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Message %d Held\r\n", FBBHeader->FwdMsg->number); + sprintf(Title, "Message %d Held - Rejected by Winlink", FBBHeader->FwdMsg->number); + SendMessageToSYSOP(Title, MailBuffer, Length); + + FBBHeader->FwdMsg->status = 'H'; // Mark as Held + } + } + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + SaveMessageDatabase(); +} + + + +VOID FlagSentMessages(CIRCUIT * conn, struct UserInfo * user) +{ + struct FBBHeaderLine * FBBHeader; // The Headers from an FBB forward block + int i; + + // Called if FBB command received after sending a block of messages . Flag as as sent. + + conn->FBBMsgsSent = FALSE; + + for (i=0; i < 5; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (FBBHeader && FBBHeader->MsgType) // Not a zapped entry + { + if ((conn->Paclink || conn->RMSExpress || conn->PAT) && +// ((conn->UserPointer->flags & F_NTSMPS) == 0) && + (FBBHeader->FwdMsg->type == 'P')) + { + // Kill Messages sent to paclink/RMS Express unless BBS FWD bit set + + if (check_fwd_bit(FBBHeader->FwdMsg->fbbs, user->BBSNumber) == 0) + { + FlagAsKilled(FBBHeader->FwdMsg, FALSE); + continue; + } + } + + clear_fwd_bit(FBBHeader->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(FBBHeader->FwdMsg->forw, user->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(FBBHeader->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + FBBHeader->FwdMsg->status = 'F'; // Mark as forwarded + FBBHeader->FwdMsg->datechanged=time(NULL); + } + +#ifndef NOMQTT + if (MQTT) + MQTTMessageEvent(FBBHeader->FwdMsg); +#endif + + FBBHeader->FwdMsg->Locked = 0; // Unlock + conn->UserPointer->ForwardingInfo->MsgCount--; + } + } + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + SaveMessageDatabase(); +} + + +VOID SetupNextFBBMessage(CIRCUIT * conn) +{ + struct FBBHeaderLine * FBBHeader; // The Headers from an FBB forward block + + memmove(&conn->FBBHeaders[0], &conn->FBBHeaders[1], 4 * sizeof(struct FBBHeaderLine)); + + memset(&conn->FBBHeaders[4], 0, sizeof(struct FBBHeaderLine)); + + FBBHeader = &conn->FBBHeaders[0]; + + if (FBBHeader->MsgType == 0) + { + conn->FBBIndex = 0; // ready for next block; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + + conn->FBBChecksum = 0; + conn->InputMode = 0; + + if (!FBBDoForward(conn)) // Send proposal if anthing to forward + { + conn->InputMode = 0; + FBBputs(conn, "FF\r"); + } + } + else + { + if (conn->BBSFlags & FBBForwarding) + conn->InputMode = 'B'; + + CreateMessage(conn, FBBHeader->From, FBBHeader->To, FBBHeader->ATBBS, FBBHeader->MsgType, FBBHeader->BID, NULL); + } +} + +BOOL FBBDoForward(CIRCUIT * conn) +{ + int i; + char proposal[100]; + int proplen; + + if (FindMessagestoForward(conn)) + { + // Send Proposal Block + + struct FBBHeaderLine * FBBHeader; + + for (i=0; i < conn->FBBIndex; i++) + { + FBBHeader = &conn->FBBHeaders[i]; + + if (conn->BBSFlags & FBBB2Mode) + + if (conn->BPQBBS) + + // Add From and To Header for Filters + + proplen = sprintf(proposal, "FC EM %s %d %d %s %s %s %c\r", + FBBHeader->BID, + FBBHeader->Size, + FBBHeader->CSize, + FBBHeader->From, + (FBBHeader->ATBBS[0]) ? FBBHeader->ATBBS : conn->UserPointer->Call, + FBBHeader->To, + FBBHeader->MsgType); + + else + + // FC EM A3EDD4P00P55 377 281 0 + + proplen = sprintf(proposal, "FC EM %s %d %d %d\r", + FBBHeader->BID, + FBBHeader->Size, + FBBHeader->CSize, 0); + + else + proplen = sprintf(proposal, "%s %c %s %s %s %s %d\r", + (conn->BBSFlags & FBBCompressed) ? "FA" : "FB", + FBBHeader->MsgType, + FBBHeader->From, + (FBBHeader->ATBBS[0]) ? FBBHeader->ATBBS : conn->UserPointer->Call, + FBBHeader->To, + FBBHeader->BID, + FBBHeader->Size); + + // Accumulate checksum + + while(proplen > 0) + { + conn->FBBChecksum+=proposal[--proplen]; + } + + FBBputs(conn, proposal); + } + + conn->FBBChecksum = - conn->FBBChecksum; + + nodeprintfEx(conn, "F> %02X\r", conn->FBBChecksum); + + return TRUE; + } + + return FALSE; +} + +VOID UnpackFBBBinary(CIRCUIT * conn) +{ + int MsgLen, i, offset, n; + UCHAR * ptr; + +loop: + + if (conn->CloseAfterFlush) // Failed (or complete), so discard rest of input + { + conn->InputLen = 0; + return; + } + + + ptr = conn->InputBuffer; + + if (conn->InputLen < 2) + return; // All formats need at least two bytes + + switch (*ptr) + { + case 1: // Header + + MsgLen = ptr[1] + 2; + + if (conn->InputLen < MsgLen) + return; // Wait for more + + if (strlen(&ptr[2]) > 60) + { + memcpy(conn->TempMsg->title, &ptr[2], 60); + conn->TempMsg->title[60] = 0; + Debugprintf("FBB Subject too long - truncated, %s", &ptr[2]); + } + else + strcpy(conn->TempMsg->title, &ptr[2]); + + offset = atoi(ptr+3+strlen(&ptr[2])); + + ptr += MsgLen; + + memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen); + + conn->InputLen -= MsgLen; + + conn->FBBChecksum = 0; + + if (offset) + { + struct FBBRestartData * RestartRec; + + // Trying to restart - make sure we have restart data + + for (i = 1; i <= RestartCount; i++) + { + RestartRec = RestartData[i]; + + if ((strcmp(RestartRec->Call, conn->UserPointer->Call) == 0) + && (strcmp(RestartRec->bid, conn->TempMsg->bid) == 0)) + { + if (RestartRec->length <= offset) + { + conn->TempMsg->length = RestartRec->length; + conn->MailBuffer = RestartRec->MailBuffer; + conn->MailBufferSize = RestartRec->MailBufferSize; + + // FBB Seems to insert 6 Byte message + // It looks like the original csum and length - perhaps a a consistancy check + + // But Airmail Sends the Restart Data in the next packet, move the check code. + + conn->NeedRestartHeader = TRUE; + + goto GotRestart; + } + else + { + BBSputs(conn, "*** Trying to restart from invalid position.\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + + // Remove Restart info + + for (n = i; n < RestartCount; n++) + { + RestartData[n] = RestartData[n+1]; // move down all following entries + } + RestartCount--; + SaveRestartData(); + } + } + + // No Restart Data + + BBSputs(conn, "*** Trying to restart, but no restart data.\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + + // Create initial buffer of 10K. Expand if needed later + + if (conn->MailBufferSize == 0) + { + // Dont allocate if restarting + + conn->MailBuffer=malloc(10000); + conn->MailBufferSize=10000; + } + + GotRestart: + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to create Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + + goto loop; + + + + case 2: // Data Block + + if (ptr[1] == 0) + MsgLen = 256; + else + MsgLen = ptr[1]; + + if (conn->InputLen < (MsgLen + 2)) + return; // Wait for more + + // If waiting for Restart Header, see if it has arrived + + if (conn->NeedRestartHeader) + { + conn->NeedRestartHeader = FALSE; + + if (MsgLen == 6) + { + ptr = conn->InputBuffer+2; + conn->InputLen -=8; + + for (i=0; i<6; i++) + { + conn->FBBChecksum+=ptr[0]; + ptr++; + } + memmove(conn->InputBuffer, ptr, conn->InputLen); + } + else + { + BBSputs(conn, "*** Restart Header Missing.\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + } + + goto loop; + + } + // Process it + + ptr+=2; + + for (i=0; i< MsgLen; i++) + { + conn->FBBChecksum+=ptr[i]; + } + + ptr-=2; + + 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], &ptr[2], MsgLen); + + conn->TempMsg->length += MsgLen; + + MsgLen +=2; + + ptr += MsgLen; + + memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen); + + conn->InputLen -= MsgLen; + + goto loop; + + + case 4: // EOM + + // Process EOM + + conn->FBBChecksum+=ptr[1]; + + if (conn->FBBChecksum == 0) + { +#ifndef LINBPQ + __try + { +#endif + conn->InputMode = 0; // So we won't save Restart data if decode fails + DeleteRestartData(conn); + Decode(conn, 0); // Setup Next Message will reset InputMode if needed +#ifndef LINBPQ + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + BBSputs(conn, "*** Program Error Decoding Message\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + return; + } +#endif + } + + else + { + BBSputs(conn, "*** Message Checksum Error\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + + // Don't allow restart, as saved data is probably duff + + conn->DontSaveRestartData = TRUE; + return; + } + ptr += 2; + + memmove(conn->InputBuffer, ptr, conn->InputLen-2); + + conn->InputLen -= 2; + + goto loop; + + default: + + BBSputs(conn, "*** Protocol Error - Invalid Binary Message Format (Invalid Block Type)\r"); + Flush(conn); + + if (conn->CloseAfterFlush == 0) + { + // Dont do it more than once + + conn->CloseAfterFlush = 20; // 2 Secs + + // Don't allow restart, as saved data is probably duff + + // Actually all but the last block is probably OK, but maybe + // not worth the risk of restarting + + // Actually I think it is + + if (conn->TempMsg->length > 256) + { + conn->TempMsg->length -= 256; + conn->DontSaveRestartData = FALSE; + } + else + conn->DontSaveRestartData = TRUE; + } + return; + } +} + +VOID SendCompressed(CIRCUIT * conn, struct MsgInfo * FwdMsg) +{ + struct tm * tm; + char * MsgBytes, * Save; + UCHAR * Compressed, * Compressedptr; + UCHAR * UnCompressed; + char * Title; + UCHAR * Output, * Outputptr; + int i, OrigLen, MsgLen, CompLen, DataOffset; + char Rline[80]; + int RLineLen; + int Index; + time_t temp; + + if (FwdMsg->type == 'P') + Index = PMSG; + else if (FwdMsg->type == 'B') + Index = BMSG; + else if (FwdMsg->type == 'T') + Index = TMSG; + + MsgBytes = Save = ReadMessageFile(FwdMsg->number); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + FwdMsg->length = (int)strlen(MsgBytes); + } + + OrigLen = FwdMsg->length; + + Title = FwdMsg->title; + + Compressed = Compressedptr = zalloc(2 * OrigLen + 200); + Output = Outputptr = zalloc(2 * OrigLen + 200); + + *Outputptr++ = 1; + *Outputptr++ = (int)strlen(Title) + 8; + strcpy(Outputptr, Title); + Outputptr += strlen(Title) +1; + sprintf(Outputptr, "%6d", conn->RestartFrom); + Outputptr += 7; + + DataOffset = (int)(Outputptr - Output); // Used if restarting + + memcpy(&temp, &FwdMsg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + sprintf(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, + FwdMsg->number, BBSName, HRoute, RlineVer); + + if (memcmp(MsgBytes, "R:", 2) != 0) // No R line, so must be our message + strcat(Rline, "\r\n"); + + RLineLen = (int)strlen(Rline); + + MsgLen = OrigLen + RLineLen; + + UnCompressed = zalloc(MsgLen+10); + + strcpy(UnCompressed, Rline); + + // If a B2 Message, Remove B2 Header + + if (FwdMsg->B2Flags & B2Msg) + { + char * ptr; + int BodyLen = OrigLen; + + // Remove all B2 Headers, and all but the first part. + + ptr = strstr(MsgBytes, "Body:"); + + if (ptr) + { + BodyLen = atoi(&ptr[5]); + ptr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (ptr) + ptr +=4; + else + ptr = MsgBytes; + + } + else + ptr = MsgBytes; + + if (memcmp(ptr, "R:", 2) == 0) // Already have RLines, so remove blank line after new R:line + RLineLen -= 2; + + memcpy(&UnCompressed[RLineLen], ptr, BodyLen); + + MsgLen = BodyLen + RLineLen; + } + else // Not B2 Message + { + memcpy(&UnCompressed[RLineLen], MsgBytes, OrigLen); + } + + CompLen = Encode(UnCompressed, Compressed, MsgLen, conn->BBSFlags & FBBB1Mode, conn->BBSFlags & FBBCompressed); + + conn->FBBChecksum = 0; + + // If restarting, send the checksum and length as a single record, then data from the restart point + // The count includes the header, so adjust count and pointers + + if (conn->RestartFrom) + { + *Outputptr++ = 2; + *Outputptr++ = 6; + + for (i=0; i< 6; i++) + { + conn->FBBChecksum+=Compressed[i]; + *Outputptr++ = Compressed[i]; + } + + for (i=conn->RestartFrom; i< CompLen; i++) + { + conn->FBBChecksum+=Compressed[i]; + } + + Compressedptr += conn->RestartFrom; + CompLen -= conn->RestartFrom; + } + else + { + for (i=0; i< CompLen; i++) + { + conn->FBBChecksum+=Compressed[i]; + } + } + + while (CompLen > 250) + { + *Outputptr++ = 2; + *Outputptr++ = 250; + + memcpy(Outputptr, Compressedptr, 250); + Outputptr += 250; + Compressedptr += 250; + CompLen -= 250; + } + + *Outputptr++ = 2; + *Outputptr++ = CompLen; + + memcpy(Outputptr, Compressedptr, CompLen); + + Outputptr += CompLen; + + *Outputptr++ = 4; + conn->FBBChecksum = - conn->FBBChecksum; + *Outputptr++ = conn->FBBChecksum; + + if (conn->OpenBCM) // Telnet, so escape any 0xFF + { + unsigned char * ptr1 = Output; + unsigned char * ptr2 = Compressed; // Reuse Compressed buffer + size_t Len = Outputptr - Output; + unsigned char c; + + while (Len--) + { + c = *(ptr1++); + *(ptr2++) = c; + if (c == 0xff) // FF becodes FFFF + *(ptr2++) = c; + } + + QueueMsg(conn, Compressed, (int)(ptr2 - Compressed)); + } + else + QueueMsg(conn, Output, (int)(Outputptr - Output)); + + free(Save); + free(Compressed); + free(UnCompressed); + free(Output); + +} + +BOOL CreateB2Message(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader, char * Rline) +{ + char * MsgBytes; + UCHAR * Compressed; + UCHAR * UnCompressed; + int OrigLen, MsgLen, B2HddrLen, CompLen; + char Date[20]; + struct tm * tm; + char B2From[80]; + char B2To[80]; + struct MsgInfo * Msg = FBBHeader->FwdMsg; + struct UserInfo * FromUser; + int BodyLineToBody; + int RlineLen = (int)strlen(Rline) ; + char * TypeString; +#ifndef LINBPQ + struct _EXCEPTION_POINTERS exinfo; + + __try { +#endif + + if (Msg == NULL) + Debugprintf("Msg = NULL"); + + + MsgBytes = ReadMessageFile(Msg->number); + + if (MsgBytes == 0) + { + Debugprintf("B2 Message - Message File not found"); + return FALSE; + } + + UnCompressed = zalloc(Msg->length + 2000); + + if (UnCompressed == NULL) + Debugprintf("B2 Message - zalloc for %d failed", Msg->length + 2000); + + OrigLen = Msg->length; + + // If a B2 Message add R:line at start of Body, but otherwise leave intact. + // Unless a message to Paclink, when we must remove any HA from the TO address + // Or to a CMS, when we remove HA from From or Reply-to + + if (Msg->B2Flags & B2Msg) + { + char * ptr, *ptr2; + int BodyLen; + int BodyLineLen; + int Index; + + MsgLen = OrigLen + RlineLen; + + if (conn->Paclink) + { + // Remove any HA on the TO address + + ptr = strstr(MsgBytes, "To:"); + if (ptr) + { + ptr2 = strstr(ptr, "\r\n"); + if (ptr2) + { + while (ptr < ptr2) + { + if (*ptr == '.' || *ptr == '@') + { + memset(ptr, ' ', ptr2 - ptr); + break; + } + ptr++; + } + } + } + } + + if (conn->WL2K) + { + // Remove any HA on the From or Reply-To address + + ptr = strstr(MsgBytes, "From:"); + if (ptr == NULL) + ptr = strstr(MsgBytes, "Reply-To:"); + + if (ptr) + { + ptr2 = strstr(ptr, "\r\n"); + if (ptr2) + { + while (ptr < ptr2) + { + if (*ptr == '.' || *ptr == '@') + { + memset(ptr, ' ', ptr2 - ptr); + break; + } + ptr++; + } + } + } + } + + + // Add R: Line at start of body. Will Need to Update Body Length + + ptr = strstr(MsgBytes, "Body:"); + + if (ptr == 0) + { + Debugprintf("B2 Messages without Body: Line"); + return FALSE; + } + ptr2 = strstr(ptr, "\r\n"); + + Index = (int)(ptr - MsgBytes); // Bytes Before Body: line + + if (Index <= 0 || Index > MsgLen) + { + Debugprintf("B2 Message Body: line position invalid - %d", Index); + return FALSE; + } + + // If message to saildocs adding an R: line will mess up the message processing, so add as an X header + + if (strstr(MsgBytes, "To: query@saildocs.com")) + { + int x_Len; + + memcpy(UnCompressed, MsgBytes, Index); // Up to Old Body; + x_Len = sprintf(&UnCompressed[Index], "x-R: %s", &Rline[2]); + MsgLen = OrigLen + x_Len; + Index +=x_Len; + goto copyRest; + } + + BodyLen = atoi(&ptr[5]); + + if (BodyLen < 0 || BodyLen > MsgLen) + { + Debugprintf("B2 Message Length from Body: line invalid - Msg len %d From Body %d", MsgLen, BodyLen); + return FALSE; + } + + BodyLineLen = (int)(ptr2 - ptr) + 2; + MsgLen -= BodyLineLen; // Length of Body Line may change + + ptr = strstr(ptr2, "\r\n\r\n"); // Blank line before Body + + if (ptr == 0) + { + Debugprintf("B2 Message - No Blank Line before Body"); + return FALSE; + } + + ptr += 4; + + ptr2 += 2; // Line Following Original Body: Line + + BodyLineToBody = (int)(ptr - ptr2); + + if (memcmp(ptr, "R:", 2) != 0) // No R line, so must be our message + { + strcat(Rline, "\r\n"); + RlineLen += 2; + MsgLen += 2; + } + BodyLen += RlineLen; + + memcpy(UnCompressed, MsgBytes, Index); // Up to Old Body; + BodyLineLen = sprintf(&UnCompressed[Index], "Body: %d\r\n", BodyLen); + + MsgLen += BodyLineLen; // Length of Body Line may have changed + Index += BodyLineLen; + + if (BodyLineToBody < 0 || BodyLineToBody > 1000) + { + Debugprintf("B2 Message - Body too far from Body Line - %d", BodyLineToBody); + return FALSE; + } + memcpy(&UnCompressed[Index], ptr2, BodyLineToBody); // Stuff Between Body: Line and Body + + Index += BodyLineToBody; + + memcpy(&UnCompressed[Index], Rline, RlineLen); + Index += RlineLen; + +copyRest: + + memcpy(&UnCompressed[Index], ptr, MsgLen - Index); // Rest of Message + + FBBHeader->Size = MsgLen; + + Compressed = zalloc(2 * MsgLen + 200); +#ifndef LINBPQ + __try { +#endif + CompLen = Encode(UnCompressed, Compressed, MsgLen, TRUE, conn->BBSFlags & FBBCompressed); + + FBBHeader->CompressedMsg = Compressed; + FBBHeader->CSize = CompLen; + + free(UnCompressed); + return TRUE; +#ifndef LINBPQ + } My__except_Routine("Encode B2Message"); +#endif + return FALSE; + } + + + if (memcmp(MsgBytes, "R:", 2) != 0) // No R line, so must be our message + { + strcat(Rline, "\r\n"); + RlineLen += 2; + } + + MsgLen = OrigLen + RlineLen; + +// if (conn->RestartFrom == 0) +// { +// // save time first sent, or checksum will be wrong when we restart +// +// FwdMsg->datechanged=time(NULL); +// } + + tm = gmtime((time_t *)&Msg->datechanged); + + sprintf(Date, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + // We create the B2 Header +/* + MID: XR88I1J160EB + Date: 2009/07/25 18:17 + Type: Private + From: SMTP:john.wiseman@ntlworld.com + To: G8BPQ + Subject: RE: RMS Test Message + Mbo: SMTP + Body: 213 + +*/ + if (strcmp(Msg->to, "RMS") == 0) // Address is in via + strcpy(B2To, Msg->via); + else + if (Msg->via[0] && (!conn->Paclink)) + sprintf(B2To, "%s@%s", Msg->to, Msg->via); + else + strcpy(B2To, Msg->to); + + // Try to create a full from: addrsss so RMS Express can reply + + strcpy(B2From, Msg->from); + + Logprintf(LOG_BBS, conn, '?', "B2 From %s", B2From); + + if (strcmp(conn->Callsign, "RMS") != 0 && conn->WL2K == 0) // if going to RMS - just send calll + { + if (_stricmp(Msg->from, "SMTP:") == 0) // Address is in via + strcpy(B2From, Msg->emailfrom); + else + { + FromUser = LookupCall(Msg->from); + + if (FromUser) + { + Logprintf(LOG_BBS, conn, '?', "B2 From - Local User"); + + if (FromUser->HomeBBS[0]) + sprintf(B2From, "%s@%s", Msg->from, FromUser->HomeBBS); + else + sprintf(B2From, "%s@%s", Msg->from, BBSName); + } + else + { + WPRecP WP = LookupWP(Msg->from); + + Logprintf(LOG_BBS, conn, '?', "B2 From - not local User"); + + if (WP) + sprintf(B2From, "%s@%s", Msg->from, WP->first_homebbs); + } + } + } + + Logprintf(LOG_BBS, conn, '?', "B2 From Finally %s", B2From); + + if (Msg->type == 'P') + TypeString = "Private" ; + else if (Msg->type == 'B') + TypeString = "Bulletin"; + else if (Msg->type == 'T') + TypeString = "Traffic"; + + B2HddrLen = sprintf(UnCompressed, + "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\nTo: %s\r\nSubject: %s\r\nMbo: %s\r\n" + "Content-Type: text/plain\r\nContent-Transfer-Encoding: 8bit\r\nBody: %d\r\n\r\n", + Msg->bid, Date, TypeString, B2From, B2To, Msg->title, BBSName, MsgLen); + + + memcpy(&UnCompressed[B2HddrLen], Rline, RlineLen); + memcpy(&UnCompressed[B2HddrLen + RlineLen], MsgBytes, OrigLen); // Rest of Message + + MsgLen += B2HddrLen; + + FBBHeader->Size = MsgLen; + + Compressed = zalloc(2 * MsgLen + 200); + + CompLen = Encode(UnCompressed, Compressed, MsgLen, TRUE, conn->BBSFlags & FBBCompressed); + + FBBHeader->CompressedMsg = Compressed; + FBBHeader->CSize = CompLen; + + free(UnCompressed); + + return TRUE; +#ifndef LINBPQ + } My__except_Routine("CreateB2Message"); +#endif + return FALSE; + +} + +VOID SendCompressedB2(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader) +{ + UCHAR * Compressed, * Compressedptr; + UCHAR * Output, * Outputptr; + int i, CompLen; + int Index; + + if (FBBHeader->FwdMsg->type == 'P') + Index = PMSG; + else if (FBBHeader->FwdMsg->type == 'B') + Index = BMSG; + else if (FBBHeader->FwdMsg->type == 'T') + Index = TMSG; + + Compressed = Compressedptr = FBBHeader->CompressedMsg; + + Output = Outputptr = zalloc(FBBHeader->CSize + 10000); + + *Outputptr++ = 1; + *Outputptr++ = (int)strlen(FBBHeader->FwdMsg->title) + 8; + strcpy(Outputptr, FBBHeader->FwdMsg->title); + Outputptr += strlen(FBBHeader->FwdMsg->title) +1; + sprintf(Outputptr, "%06d", conn->RestartFrom); + Outputptr += 7; + + CompLen = FBBHeader->CSize; + + conn->FBBChecksum = 0; + + // If restarting, send the checksum and length as a single record, then data from the restart point + // The count includes the header, so adjust count and pointers + + if (conn->RestartFrom) + { + *Outputptr++ = 2; + *Outputptr++ = 6; + + for (i=0; i< 6; i++) + { + conn->FBBChecksum+=Compressed[i]; + *Outputptr++ = Compressed[i]; + } + + for (i=conn->RestartFrom; i< CompLen; i++) + { + conn->FBBChecksum+=Compressed[i]; + } + + Compressedptr += conn->RestartFrom; + CompLen -= conn->RestartFrom; + } + else + { + for (i=0; i< CompLen; i++) + { + conn->FBBChecksum+=Compressed[i]; + } + conn->UserPointer->Total.MsgsSent[Index]++; + conn->UserPointer->Total.BytesForwardedOut[Index] += FBBHeader->FwdMsg->length; + + } + + while (CompLen > 256) + { + *Outputptr++ = 2; + *Outputptr++ = 0; + + memcpy(Outputptr, Compressedptr, 256); + Outputptr += 256; + Compressedptr += 256; + CompLen -= 256; + } + + *Outputptr++ = 2; + *Outputptr++ = CompLen; + + memcpy(Outputptr, Compressedptr, CompLen); + + Outputptr += CompLen; + + *Outputptr++ = 4; + conn->FBBChecksum = - conn->FBBChecksum; + *Outputptr++ = conn->FBBChecksum; + + if (conn->OpenBCM) // Telnet, so escape any 0xFF + { + unsigned char * ptr1 = Output; + unsigned char * ptr2 = Compressed; // Reuse Compressed buffer + int Len = (int)(Outputptr - Output); + unsigned char c; + + while (Len--) + { + c = *(ptr1++); + *(ptr2++) = c; + if (c == 0xff) // FF becodes FFFF + *(ptr2++) = c; + } + + QueueMsg(conn, Compressed, (int)(ptr2 - Compressed)); + } + else + QueueMsg(conn, Output, (int)(Outputptr - Output)); + + free(Compressed); + free(Output); +} + +// Restart Routines. + +VOID SaveFBBBinary(CIRCUIT * conn) +{ + // Disconnected during binary transfer + + char Msg[120]; + int i, len; + struct FBBRestartData * RestartRec = NULL; + + if (conn->TempMsg == NULL) + return; + + if (conn->TempMsg->length < 256) + return; // Not worth it. + + // If we already have a restart record, reuse it + + for (i = 1; i <= RestartCount; i++) + { + RestartRec = RestartData[i]; + + if ((strcmp(RestartRec->Call, conn->UserPointer->Call) == 0) + && (strcmp(RestartRec->bid, conn->TempMsg->bid) == 0)) + { + // Found it, so reuse + + // If we have more data, reset retry count + + if (RestartRec->length < conn->TempMsg->length) + RestartRec->Count = 0;; + + break; + } + } + + if (RestartRec == NULL) + { + RestartRec = zalloc(sizeof (struct FBBRestartData)); + + GetSemaphore(&AllocSemaphore, 0); + + RestartData=realloc(RestartData,(++RestartCount+1) * sizeof(void *)); + RestartData[RestartCount] = RestartRec; + + FreeSemaphore(&AllocSemaphore); + RestartRec->TimeCreated = time(NULL); + } + + strcpy(RestartRec->Call, conn->UserPointer->Call); + RestartRec->length = conn->TempMsg->length; + strcpy(RestartRec->bid, conn->TempMsg->bid); + RestartRec->MailBuffer = conn->MailBuffer; + RestartRec->MailBufferSize = conn->MailBufferSize; + + len = sprintf_s(Msg, sizeof(Msg), "Disconnect received from %s during Binary Transfer - %d Bytes Saved for restart", + conn->Callsign, conn->TempMsg->length); + + SaveRestartData(); + + WriteLogLine(conn, '|',Msg, len, LOG_BBS); +} + +void DeleteRestartData(CIRCUIT * conn) +{ + struct FBBRestartData * RestartRec = NULL; + int i, n; + + if (conn->TempMsg == NULL) + return; + + for (i = 1; i <= RestartCount; i++) + { + RestartRec = RestartData[i]; + + if ((strcmp(RestartRec->Call, conn->UserPointer->Call) == 0) + && (strcmp(RestartRec->bid, conn->TempMsg->bid) == 0)) + { + // Remove restrt data + + for (n = i; n < RestartCount; n++) + { + RestartData[n] = RestartData[n+1]; // move down all following entries + } + + RestartCount--; + SaveRestartData(); + return; + } + } +} + + +BOOL LookupRestart(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader) +{ + int i, n; + + struct FBBRestartData * RestartRec; + + if ((conn->BBSFlags & FBBB1Mode) == 0) + return FALSE; // Only B1 & B2 support restart + + for (i = 1; i <= RestartCount; i++) + { + RestartRec = RestartData[i]; + + if ((strcmp(RestartRec->Call, conn->UserPointer->Call) == 0) + && (strcmp(RestartRec->bid, FBBHeader->BID) == 0)) + { + char Msg[120]; + int len; + + RestartRec->Count++; + + if (RestartRec->Count > 10) + { + len = sprintf_s(Msg, sizeof(Msg), "Too many restarts for %s - Requesting restart from beginning", + FBBHeader->BID); + + WriteLogLine(conn, '|',Msg, len, LOG_BBS); + + // Remove restrt data + + for (n = i; n < RestartCount; n++) + { + RestartData[n] = RestartData[n+1]; // move down all following entries + } + + RestartCount--; + SaveRestartData(); + return FALSE; + } + + len = sprintf_s(Msg, sizeof(Msg), "Restart Data found for %s - Requesting restart from %d", + FBBHeader->BID, RestartRec->length); + + WriteLogLine(conn, '|',Msg, len, LOG_BBS); + + return (RestartRec->length); + } + } + + return FALSE; // Not Found +} + + + +BOOL DoWeWantIt(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader) +{ + struct MsgInfo * Msg; + BIDRec * BID; + int m; + + if (RefuseBulls && FBBHeader->MsgType == 'B') + { + Logprintf(LOG_BBS, conn, '?', "Message Rejected by RefuseBulls"); + return FALSE; + } + if (FBBHeader->Size > MaxRXSize) + { + Logprintf(LOG_BBS, conn, '?', "Message Rejected by Size Check"); + return FALSE; + } + + BID = LookupBID(FBBHeader->BID); + + if (BID) + { + if (FBBHeader->MsgType == 'B') + { + Logprintf(LOG_BBS, conn, '?', "Message Rejected by BID Check"); + return FALSE; + } + + // Treat P messages to SYSOP@WW as Bulls + + if (strcmp(FBBHeader->To, "SYSOP") == 0 && strcmp(FBBHeader->ATBBS, "WW") == 0) + { + Logprintf(LOG_BBS, conn, '?', "Message Rejected by BID Check"); + return FALSE; + } + + m = NumberofMessages; + + while (m > 0) + { + Msg = MsgHddrPtr[m]; + + if (Msg->number == BID->u.msgno) + { + // if the same TO we will assume the same message + + if (strcmp(Msg->to, FBBHeader->To) == 0) + { + // We have this message. If we have already forwarded it, we should accept it again + + if ((Msg->status == 'N') || (Msg->status == 'Y')|| (Msg->status == 'H')) + { + Logprintf(LOG_BBS, conn, '?', "Message Rejected by BID Check"); + return FALSE; // Dont want it + } + else + return TRUE; // Get it again + } + + // Same number. but different message (why?) Accept for now + + return TRUE; + } + + m--; + } + + return TRUE; // A personal Message we have had before, but don't still have. + } + else + { + // We don't know the BID + + return TRUE; // We want it + } +} + + + + diff --git a/.svn/pristine/11/1105d6155b0f09c6edbdbc765bbd700d7d4d582a.svn-base b/.svn/pristine/11/1105d6155b0f09c6edbdbc765bbd700d7d4d582a.svn-base new file mode 100644 index 0000000..3ea8f94 --- /dev/null +++ b/.svn/pristine/11/1105d6155b0f09c6edbdbc765bbd700d7d4d582a.svn-base @@ -0,0 +1,402 @@ +/* +Copyright 2001-2022 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + +// +// C replacement for TNCCode.asm +// +#define Kernel + +#define _CRT_SECURE_NO_DEPRECATE + +#pragma data_seg("_BPQDATA") + +#include "time.h" +#include "stdio.h" +#include + +#include "cheaders.h" +#include "tncinfo.h" + +int C_Q_COUNT(VOID *PQ); +VOID SENDUIMESSAGE(struct DATAMESSAGE * Msg); + +VOID TNCTimerProc() +{ + // CALLED AT 10 HZ + + int n = BPQHOSTSTREAMS; + PBPQVECSTRUC HOSTSESS = BPQHOSTVECTOR; + TRANSPORTENTRY * Session; + UCHAR DISCFLAG = 0; + + while (n--) + { + // Action any DISC Requests (must be done in timer owning process) + + if (HOSTSESS->HOSTFLAGS & 0x40) // DISC REQUEST + { + if (HOSTSESS->HOSTFLAGS & 0x20) // Stay? + DISCFLAG = 'S'; + + HOSTSESS->HOSTFLAGS &= 0x9F; // Clear Flags + + Session = HOSTSESS->HOSTSESSION; + + if (Session == 0) // Gone?? + { + HOSTSESS->HOSTFLAGS |= 3; // STATE CHANGE +#ifndef LINBPQ + if (HOSTSESS->HOSTHANDLE); + { + PostMessage(HOSTSESS->HOSTHANDLE, BPQMsg, HOSTSESS->HOSTSTREAM, 4); + } +#endif + continue; + } + + if (Session->L4CROSSLINK) + Session->L4CROSSLINK->STAYFLAG = DISCFLAG; + + HOSTSESS->HOSTSESSION = 0; + HOSTSESS->HOSTFLAGS |= 3; // STATE CHANGE + + PostStateChange(Session); + + CloseSessionPartner(Session); // SEND CLOSE TO PARTNER (IF PRESENT) + } + + // Check Trace Q + + if (HOSTSESS->HOSTAPPLFLAGS & 0x80) + { + if (HOSTSESS->HOSTTRACEQ) + { + int Count = C_Q_COUNT(&HOSTSESS->HOSTTRACEQ); + + if (Count > 100) + ReleaseBuffer((void *)Q_REM((void *)&HOSTSESS->HOSTTRACEQ)); + } + } + HOSTSESS++; + } +} + +VOID SendSmartID(struct PORTCONTROL * PORT) +{ + struct _MESSAGE * ID = IDMSG; + struct _MESSAGE * Buffer; + + PORT->SmartIDNeeded = 0; + + Buffer = GetBuff(); + + if (Buffer) + { + memcpy(Buffer, ID, ID->LENGTH); + + Buffer->PORT = PORT->PORTNUMBER; + + // IF PORT HAS A CALLSIGN DEFINED, SEND THAT INSTEAD + + if (PORT->PORTCALL[0] > 0x40) + { + memcpy(Buffer->ORIGIN, PORT->PORTCALL, 7); + Buffer->ORIGIN[6] |= 1; // SET END OF CALL BIT + } + + // If Pactor Style add to UI_Q + + if (PORT->PROTOCOL == 10 && PORT->TNC && PORT->TNC->Hardware != H_KISSHF && PORT->UICAPABLE) + { + EXTPORTDATA * EXTPORT = (EXTPORTDATA *) PORT; + + C_Q_ADD(&EXTPORT->UI_Q, Buffer); + return; + } + + PUT_ON_PORT_Q(PORT, Buffer); + } +} + + +VOID SENDIDMSG() +{ + struct PORTCONTROL * PORT = PORTTABLE; + struct _MESSAGE * ID = IDMSG; + struct _MESSAGE * Buffer; + + while (PORT) + { + if (PORT->PROTOCOL < 10) // Not Pactor-style + { + Buffer = GetBuff(); + + if (Buffer) + { + memcpy(Buffer, ID, ID->LENGTH); + + Buffer->PORT = PORT->PORTNUMBER; + + // IF PORT HAS A CALLSIGN DEFINED, SEND THAT INSTEAD + + if (PORT->PORTCALL[0] > 0x40) + { + memcpy(Buffer->ORIGIN, PORT->PORTCALL, 7); + Buffer->ORIGIN[6] |= 1; // SET END OF CALL BIT + } + C_Q_ADD(&IDMSG_Q, Buffer); + } + } + PORT = PORT->PORTPOINTER; + } +} + + + +VOID SENDBTMSG() +{ + struct PORTCONTROL * PORT = PORTTABLE; + struct _MESSAGE * Buffer; + char * ptr1, * ptr2; + + while (PORT) + { + if (PORT->PROTOCOL >= 10 || PORT->PORTUNPROTO == 0) // Pactor-style or no UNPROTO ADDR? + { + PORT = PORT->PORTPOINTER; + continue; + } + + Buffer = GetBuff(); + + if (Buffer) + { + memcpy(Buffer->DEST, PORT->PORTUNPROTO, 7); + Buffer->DEST[6] |= 0xC0; // Set Command bits + + // Send from BBSCALL unless PORTBCALL defined + + if (PORT->PORTBCALL[0] > 32) + memcpy(Buffer->ORIGIN, PORT->PORTBCALL, 7); + else if (APPLCALLTABLE->APPLCALL[0] > 32) + memcpy(Buffer->ORIGIN, APPLCALLTABLE->APPLCALL, 7); + else + memcpy(Buffer->ORIGIN, MYCALL, 7); + + ptr1 = &PORT->PORTUNPROTO[7]; // First Digi + ptr2 = &Buffer->CTL; // Digi field in buffer + + // Copy any digis + + while (*(ptr1)) + { + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + } + + *(ptr2 - 1) |= 1; // Set End of Address + *(ptr2++) = UI; + + memcpy(ptr2, &BTHDDR.PID, BTHDDR.LENGTH); + ptr2 += BTHDDR.LENGTH; + Buffer->LENGTH = (int)(ptr2 - (char *)Buffer); + Buffer->PORT = PORT->PORTNUMBER; + + C_Q_ADD(&IDMSG_Q, Buffer); + } + PORT = PORT->PORTPOINTER; + } +} + +VOID SENDUIMESSAGE(struct DATAMESSAGE * Msg) +{ + struct PORTCONTROL * PORT = PORTTABLE; + struct _MESSAGE * Buffer; + char * ptr1, * ptr2; + + Msg->LENGTH -= MSGHDDRLEN; // Remove Header + + while (PORT) + { + if ((PORT->PROTOCOL == 10 && PORT->UICAPABLE == 0) || PORT->PORTUNPROTO == 0) // Pactor-style or no UNPROTO ADDR? + { + PORT = PORT->PORTPOINTER; + continue; + } + + Buffer = GetBuff(); + + if (Buffer) + { + memcpy(Buffer->DEST, PORT->PORTUNPROTO, 7); + Buffer->DEST[6] |= 0xC0; // Set Command bits + + // Send from BBSCALL unless PORTBCALL defined + + if (PORT->PORTBCALL[0] > 32) + memcpy(Buffer->ORIGIN, PORT->PORTBCALL, 7); + else if (APPLCALLTABLE->APPLCALL[0] > 32) + memcpy(Buffer->ORIGIN, APPLCALLTABLE->APPLCALL, 7); + else + memcpy(Buffer->ORIGIN, MYCALL, 7); + + ptr1 = &PORT->PORTUNPROTO[7]; // First Digi + ptr2 = &Buffer->CTL; // Digi field in buffer + + // Copy any digis + + while (*(ptr1)) + { + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + } + + *(ptr2 - 1) |= 1; // Set End of Address + *(ptr2++) = UI; + + memcpy(ptr2, &Msg->PID, Msg->LENGTH); + ptr2 += Msg->LENGTH; + Buffer->LENGTH = (int)(ptr2 - (char *)Buffer); + Buffer->PORT = PORT->PORTNUMBER; + + if (PORT->PROTOCOL == 10) + { + EXTPORTDATA * EXTPORT = (EXTPORTDATA *) PORT; + C_Q_ADD(&EXTPORT->UI_Q, Buffer); + } + else + C_Q_ADD(&IDMSG_Q, Buffer); + } + PORT = PORT->PORTPOINTER; + } +} + +Dll VOID APIENTRY Send_AX(UCHAR * Block, DWORD Len, UCHAR Port) +{ + // Block included the 7/11 byte header, Len does not + + struct PORTCONTROL * PORT; + PMESSAGE Copy; + + if (Len > 360 - 15) + return; + + if (QCOUNT < 50) + return; // Running low + + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == 0) + return; + + Copy = GetBuff(); + + if (Copy == 0) + return; + + memcpy(&Copy->DEST[0], &Block[MSGHDDRLEN], Len); + + Copy->LENGTH = (USHORT)Len + MSGHDDRLEN; + + if (PORT->PROTOCOL == 10 && PORT->TNC && PORT->TNC->Hardware != H_KISSHF) + { + // Pactor Style. Probably will only be used for Tracker uneless we do APRS over V4 or WINMOR + + EXTPORTDATA * EXTPORT = (EXTPORTDATA *) PORT; + + if (EXTPORT->UI_Q) + C_Q_ADD(&EXTPORT->UI_Q, Copy); + else + C_Q_ADD(&EXTPORT->UI_Q, Copy); + return; + } + + Copy->PORT = Port; + + PUT_ON_PORT_Q(PORT, Copy); +} + + +TRANSPORTENTRY * SetupSessionFromHost(PBPQVECSTRUC HOST, UINT ApplMask) +{ + // Create a Transport (L4) session linked to an incoming HOST (API) Session + + TRANSPORTENTRY * NewSess = L4TABLE; + int Index = 0; + + + while (Index < MAXCIRCUITS) + { + if (NewSess->L4USER[0] == 0) + { + // Got One + + UCHAR * ourcall = &MYCALL[0]; + + // IF APPL PORT USE APPL CALL, ELSE NODE CALL + + if (ApplMask) + { + // Circuit for APPL - look for an APPLCALL + + APPLCALLS * APPL = APPLCALLTABLE; + + while ((ApplMask & 1) == 0) + { + ApplMask >>= 1; + APPL++; + } + if (APPL->APPLCALL[0] > 0x40) // We have an applcall + ourcall = &APPL->APPLCALL[0]; + } + + memcpy(NewSess->L4USER, ourcall, 7); + memcpy(NewSess->L4MYCALL, ourcall, 7); + + NewSess->CIRCUITINDEX = Index; //OUR INDEX + NewSess->CIRCUITID = NEXTID; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // Keep Non-Zero + + NewSess->L4TARGET.HOST = HOST; + NewSess->L4STATE = 5; + + + NewSess->SESSIONT1 = L4T1; + NewSess->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + NewSess->SESSPACLEN = PACLEN; // Default; + + return NewSess; + } + Index++; + NewSess++; + } + + // Table Full + + return NULL; +} + + + + diff --git a/.svn/pristine/18/189d898ab4fc086c05a5870e1f63d0706d6449c3.svn-base b/.svn/pristine/18/189d898ab4fc086c05a5870e1f63d0706d6449c3.svn-base new file mode 100644 index 0000000..4d12d2d --- /dev/null +++ b/.svn/pristine/18/189d898ab4fc086c05a5870e1f63d0706d6449c3.svn-base @@ -0,0 +1,2172 @@ +/* +Copyright 2001-2022 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + +// +// DLL to inteface KAM TNC in Pactor Mode to BPQ32 switch +// +// Uses BPQ EXTERNAL interface +// + +// Version 1.2.1.2 July 2010 + +// Send Change to ISS before each transmission +// Support up to 32 BPQ Ports + +// Version 1.2.1.3 August 2010 + +// Drop RTS as well as DTR on close + +// Version 1.2.1.4 August 2010 + +// Save Minimized State + +// Version 1.2.1.5 September 2010 + +// Fix Freq Display after Node reconfig +// Only use AutoConnect APPL for Pactor Connects + +// Version 1.2.2.1 September 2010 + +// Add option to get config from bpq32.cfg + +//#define WIN32_LEAN_AND_MEAN +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "time.h" + +#include "cheaders.h" +#include "tncinfo.h" + +#include "bpq32.h" + +#define NARROWMODE Report_P1 +#define WIDEMODE Report_P1 // Only supports PI + +static char ClassName[]="KAMPACTORSTATUS"; +static char WindowTitle[] = "KAM Pactor"; +static int RigControlRow = 165; + +extern UCHAR LogDirectory[]; +static RECT Rect; + +int DoScanLine(struct TNCINFO * TNC, char * Buff, int Len); +VOID WritetoTrace(struct TNCINFO * TNC, char * Msg, int Len); +VOID SuspendOtherPorts(struct TNCINFO * ThisTNC); +VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC); + +static FILE * LogHandle[32] = {0}; + +//char * Logs[4] = {"1", "2", "3", "4"}; + +static char BaseDir[MAX_PATH]="c:\\"; + +static BOOL WRITELOG = FALSE; + +BOOL KAMStopPort(struct PORTCONTROL * PORT) +{ + // Disable Port - close TCP Sockets or Serial Port + + struct TNCINFO * TNC = PORT->TNC; + + TNC->CONNECTED = FALSE; + TNC->Alerted = FALSE; + + if (TNC->Streams[0].Attached) + TNC->Streams[0].ReportDISC = TRUE; + + if (TNC->hDevice) + { + CloseCOMPort(TNC->hDevice); + TNC->hDevice = 0; + } + + TNC->HostMode = FALSE; + + sprintf(PORT->TNC->WEB_COMMSSTATE, "%s", "Port Stopped"); + MySetWindowText(PORT->TNC->xIDC_COMMSSTATE, PORT->TNC->WEB_COMMSSTATE); + + return TRUE; +} + +BOOL KAMStartPort(struct PORTCONTROL * PORT) +{ + // Restart Port - Open Sockets or Serial Port + + struct TNCINFO * TNC = PORT->TNC; + + TNC->ReopenTimer = 999; + TNC->ReinitState = 0; + + sprintf(PORT->TNC->WEB_COMMSSTATE, "%s", "Port Restarted"); + MySetWindowText(PORT->TNC->xIDC_COMMSSTATE, PORT->TNC->WEB_COMMSSTATE); + + return TRUE; +} + + + +static VOID CloseLogFile(int Flags) +{ + if (WRITELOG) + { + fclose(LogHandle[Flags]); + LogHandle[Flags] = NULL; + } +} + +static BOOL OpenLogFile(int Flags) +{ + if (WRITELOG) + { + UCHAR FN[MAX_PATH]; + + time_t T; + struct tm * tm; + + T = time(NULL); + tm = gmtime(&T); + + sprintf(FN,"%s/logs/KAMLog_%02d%02d_%d.txt", LogDirectory, tm->tm_mon + 1, tm->tm_mday, Flags); + + LogHandle[Flags] = fopen(FN, "ab"); + + return (LogHandle[Flags] != NULL); + } + return 0; +} + +static void WriteLogLine(int Port, char * Msg, int MsgLen) +{ + if (WRITELOG) + { + OpenLogFile(Port); + + if (LogHandle[Port]) + { + fwrite(Msg, 1, MsgLen, LogHandle[Port]); + fwrite("\r\n", 1, 2, LogHandle[Port]); + } + CloseLogFile(Port); + } +} + + + +int ProcessLine(char * buf, int Port) +{ + UCHAR * ptr,* p_cmd; + char * p_ipad = 0; + char * p_port = 0; + unsigned short WINMORport = 0; + int BPQport; + int len=510; + struct TNCINFO * TNC; + char errbuf[256]; + + strcpy(errbuf, buf); + + BPQport = Port; + + TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO)); + memset(TNC, 0, sizeof(struct TNCINFO)); + + TNC->InitScript = malloc(1000); + TNC->InitScript[0] = 0; + + goto ConfigLine; + + + // Read Initialisation lines + + while(TRUE) + { + if (GetLine(buf) == 0) + return TRUE; +ConfigLine: + + strcpy(errbuf, buf); + + if (memcmp(buf, "****", 4) == 0) + return TRUE; + + ptr = strchr(buf, ';'); + if (ptr) + { + *ptr++ = 13; + *ptr = 0; + } + + if (_memicmp(buf, "DEBUGLOG", 8) == 0) // Write Debug Log + WRITELOG = atoi(&buf[9]); + + else if (_memicmp(buf, "APPL", 4) == 0) + { + p_cmd = strtok(&buf[5], " \t\n\r"); + + if (p_cmd && p_cmd[0] != ';' && p_cmd[0] != '#') + TNC->ApplCmd=_strdup(_strupr(p_cmd)); + } + + else if (_memicmp(buf, "OLDMODE", 7) == 0) + TNC->OldMode = TRUE; + + else if (_memicmp(buf, "VERYOLDMODE", 7) == 0) + { + TNC->OldMode = TRUE; + TNC->VeryOldMode = TRUE; + } + + else if (_memicmp(buf, "BUSYWAIT", 8) == 0) // Wait time beofre failing connect if busy + TNC->BusyWait = atoi(&buf[8]); + + else if (_memicmp(buf, "WL2KREPORT", 10) == 0) + TNC->WL2K = DecodeWL2KReportLine(buf); + + else + strcat (TNC->InitScript, buf); + } + return (TRUE); +} + +#define FEND 0xC0 // KISS CONTROL CODES +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD + +static int MaxStreams = 26; + +BOOL CloseConnection(struct TNCINFO * conn); +static BOOL WriteCommBlock(struct TNCINFO * TNC); +BOOL DestroyTTYInfo(int port); +void CheckRXKAM(struct TNCINFO * TNC); +VOID KAMPoll(int Port); +VOID ProcessDEDFrame(struct TNCINFO * TNC, UCHAR * rxbuff, int len); +static VOID ProcessTermModeResponse(struct TNCINFO * TNC); +static VOID DoTNCReinit(struct TNCINFO * TNC); +static VOID DoTermModeTimeout(struct TNCINFO * TNC); + +VOID ProcessPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len); +VOID ProcessKPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len); +VOID ProcessKHOSTPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len); +VOID ProcessKNormCommand(struct TNCINFO * TNC, UCHAR * rxbuffer); +VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len); +static VOID DoMonitor(struct TNCINFO * TNC, UCHAR * Msg, int Len); + +// Note that Kantronics host Mode uses KISS format Packets (without a KISS COntrol Byte) + +VOID EncodeAndSend(struct TNCINFO * TNC, UCHAR * txbuffer, int Len); +static int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); +static int KissDecode(UCHAR * inbuff, UCHAR * outbuff, int len); + +static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) +{ + int txlen; + PMSGWITHLEN buffptr; + + struct TNCINFO * TNC = TNCInfo[port]; + struct STREAMINFO * STREAM; + + int Stream; + + size_t Param; + struct ScanEntry * Scan; + + if (TNC == NULL) + return 0; + + if (TNC->hDevice == 0) + { + // Clear anything from UI_Q + + while (TNC->PortRecord->UI_Q) + { + buffptr = Q_REM(&TNC->PortRecord->UI_Q); + ReleaseBuffer(buffptr); + } + + // Try to reopen every 30 secs + + if (fn > 3 && fn < 7) + goto ok; + + TNC->ReopenTimer++; + + if (TNC->ReopenTimer < 300) + return 0; + + TNC->ReopenTimer = 0; + + if (TNC->PortRecord->PORTCONTROL.PortStopped == 0) + OpenCOMMPort(TNC, TNC->PortRecord->PORTCONTROL.SerialPortName, TNC->PortRecord->PORTCONTROL.BAUDRATE, TRUE); + + if (TNC->hDevice == 0) + return 0; + } + +ok: + + switch (fn) + { + case 7: + + // 100 mS Timer. May now be needed, as Poll can be called more frequently in some circumstances + + // G7TAJ's code to record activity for stats display + + if ( TNC->BusyFlags && CDBusy ) + TNC->PortRecord->PORTCONTROL.ACTIVE += 2; + + if ( TNC->PTTState ) + TNC->PortRecord->PORTCONTROL.SENDING += 2; + + CheckRXKAM(TNC); + KAMPoll(port); + + return 0; + + case 1: // poll + + while (TNC->PortRecord->UI_Q) // Release anything accidentally put on UI_Q + { + buffptr = Q_REM(&TNC->PortRecord->UI_Q); + ReleaseBuffer(buffptr); + } + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (STREAM->ReportDISC) + { + STREAM->ReportDISC = FALSE; + buff->PORT = Stream; + + return -1; + } + } + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (STREAM->PACTORtoBPQ_Q !=0) + { + int datalen; + + buffptr=Q_REM(&STREAM->PACTORtoBPQ_Q); + + datalen = (int)buffptr->Len; + + buff->PORT = Stream; + buff->PID= 0xf0; + memcpy(buff->L2DATA, buffptr->Data, datalen); // Data goes to +7, but we have an extra byte + datalen += (MSGHDDRLEN + 1); + + PutLengthinBuffer(buff, datalen); + + ReleaseBuffer(buffptr); + + return (1); + } + } + + return 0; + + case 2: // send + + buffptr = GetBuff(); + + if (buffptr == 0) return (0); // No buffers, so ignore + + // Find TNC Record + + Stream = buff->PORT; + STREAM = &TNC->Streams[Stream]; + + if (!TNC->TNCOK) + { + // Send Error Response + + buffptr->Len = sprintf(buffptr->Data, "No Connection to PACTOR TNC\r"); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + return 0; + } + + txlen = GetLengthfromBuffer(buff) - (MSGHDDRLEN + 1); + + buffptr->Len = txlen; + memcpy(buffptr->Data, buff->L2DATA, txlen); + + C_Q_ADD(&STREAM->BPQtoPACTOR_Q, buffptr); + + if(STREAM->Connected) + { + STREAM->FramesOutstanding++; + STREAM->FramesQueued++; + + STREAM->BytesOutstanding += txlen; + } + return (0); + + + case 3: // CHECK IF OK TO SEND. Also used to check if TNC is responding + + Stream = (int)(size_t)buff; + + STREAM = &TNC->Streams[Stream]; + + if (Stream == 0) + { + if (TNC->HFPacket) + { + if (TNC->Mem1 < 2000 || TNC->Streams[0].FramesOutstanding > 4) + return (1 | TNC->HostMode << 8 | STREAM->Disconnecting << 15); + + } + else + { + if (TNC->Streams[0].FramesQueued > 4) + return (1 | TNC->HostMode << 8 | STREAM->Disconnecting << 15); + } + } + else + { + if (STREAM->FramesOutstanding > 3 || STREAM->BytesOutstanding > 500 || TNC->Mem1 < 500) + return (1 | TNC->HostMode << 8 | STREAM->Disconnecting << 15); + } + + return TNC->HostMode << 8 | STREAM->Disconnecting << 15; // OK, but lock attach if disconnecting + + case 4: // reinit + + return (0); + + case 5: // Close + + EncodeAndSend(TNC, "Q", 1); // Exit Host Mode + Sleep(50); + + CloseCOMPort(TNCInfo[port]->hDevice); + return (0); + + case 6: // Scan Control + + // Use P0 to Disable Pactor, P1 to enable + + Param = (size_t)buff; + + if (Param == 2) // Check Permission (shouldn't happen) + { + return 1; // OK to change + } + + if (!TNC->HostMode) + return 0; // No connection so no interlock + + if (Param == 1) // Request Permission + return 0; // OK to Change // Dont want to mess with disabling it + + if (Param == 3) // Release Permission + return 0; + + // Param is Address of a struct ScanEntry + + Scan = (struct ScanEntry *)buff; + + if (Scan->PMaxLevel >= '0' && Scan->PMaxLevel < '5') // 1 - 4 + { + if (TNC->Bandwidth != Scan->PMaxLevel) + { + // Enable or disable by setting mycall + + TNC->Bandwidth = Scan->PMaxLevel; + + if (Scan->PMaxLevel == '0') + { + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? + return 0; + } + if (TNC->OldMode) + EncodeAndSend(TNC, "C20PACTOR", 9); // Back to Listen + else + EncodeAndSend(TNC, "C20TOR", 6); // Back to Listen + + TNC->InternalCmd = 'T'; + } + } + } + return 0; +} + +VOID KAMSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) +{ + struct STREAMINFO * STREAM = &TNC->Streams[0]; + + strcpy(TNC->WEB_TNCSTATE, "Interlocked"); + MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + +// STREAM->CmdSet = STREAM->CmdSave = zalloc(100); +// sprintf(STREAM->CmdSet, "I%s\r", "SCSPTC"); // Should prevent connects + +} + +VOID KAMReleasePort(struct TNCINFO * TNC) +{ + struct STREAMINFO * STREAM = &TNC->Streams[0]; + + strcpy(TNC->WEB_TNCSTATE, "Free"); + MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + +} + + +static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL) +{ + int Len = sprintf(Buff, "" + "KAM Pactor Status

KAM Pactor Status

"); + + Len += sprintf(&Buff[Len], ""); + + Len += sprintf(&Buff[Len], "", TNC->WEB_COMMSSTATE); + Len += sprintf(&Buff[Len], "", TNC->WEB_TNCSTATE); + Len += sprintf(&Buff[Len], "", TNC->WEB_MODE); + Len += sprintf(&Buff[Len], "", TNC->WEB_STATE); + Len += sprintf(&Buff[Len], "", TNC->WEB_TXRX); + Len += sprintf(&Buff[Len], "", TNC->WEB_BUFFERS); + Len += sprintf(&Buff[Len], "", TNC->WEB_TRAFFIC); + Len += sprintf(&Buff[Len], "
Comms State%s
TNC State%s
Mode%s
Status%s
TX/RX State%s
Free Space%s
Traffic%s
"); + + Len += sprintf(&Buff[Len], "", TNC->WebBuffer); + Len = DoScanLine(TNC, Buff, Len); + + return Len; +} + + +void * KAMExtInit(EXTPORTDATA * PortEntry) +{ + char msg[500]; + struct TNCINFO * TNC; + int port; + char * ptr; + char * TempScript; + + port=PortEntry->PORTCONTROL.PORTNUMBER; + + sprintf(msg,"KAM Pactor %s", PortEntry->PORTCONTROL.SerialPortName); + WritetoConsole(msg); + + ReadConfigFile(port, ProcessLine); + + TNC = TNCInfo[port]; + + if (TNC == NULL) + { + // Not defined in Config file + + sprintf(msg," ** Error - no info in BPQ32.cfg for this port\n"); + WritetoConsole(msg); + + return ExtProc; + } + TNC->Port = port; + TNC->PortRecord = PortEntry; + + TNC->PortRecord->PORTCONTROL.HWType = TNC->Hardware = H_KAM; + + if (TNC->BusyWait == 0) + TNC->BusyWait = 10; + + PortEntry->MAXHOSTMODESESSIONS = 11; // Default + + + if (PortEntry->PORTCONTROL.PORTCALL[0] == 0) + memcpy(TNC->NodeCall, MYNODECALL, 10); + else + ConvFromAX25(&PortEntry->PORTCONTROL.PORTCALL[0], TNC->NodeCall); + + PortEntry->PORTCONTROL.PROTOCOL = 10; // WINMOR/Pactor + PortEntry->PORTCONTROL.PORTQUALITY = 0; + PortEntry->SCANCAPABILITIES = NONE; // No Scan Control + + if (PortEntry->PORTCONTROL.PORTINTERLOCK && TNC->RXRadio == 0 && TNC->TXRadio == 0) + TNC->RXRadio = TNC->TXRadio = PortEntry->PORTCONTROL.PORTINTERLOCK; + + if (PortEntry->PORTCONTROL.PORTPACLEN == 0) + PortEntry->PORTCONTROL.PORTPACLEN = 100; + + PortEntry->PORTCONTROL.PORTSTARTCODE = KAMStartPort; + PortEntry->PORTCONTROL.PORTSTOPCODE = KAMStopPort; + +// TNC->SuspendPortProc = KAMSuspendPort; +// TNC->ReleasePortProc = KAMReleasePort; + + + + ptr=strchr(TNC->NodeCall, ' '); + if (ptr) *(ptr) = 0; // Null Terminate + + // Set Essential Params and MYCALL + + TempScript = malloc(4000); + + strcpy(TempScript, "MARK 1400\r"); + strcat(TempScript, "SPACE 1600\r"); + strcat(TempScript, "SHIFT MODEM\r"); + strcat(TempScript, "INV ON\r"); + strcat(TempScript, "PTERRS 30\r"); // Default Retries + strcat(TempScript, "MAXUSERS 1/10\r"); + strcat(TempScript, TNC->InitScript); + + free(TNC->InitScript); + TNC->InitScript = TempScript; + + // Others go on end so they can't be overriden + + strcat(TNC->InitScript, "ECHO OFF\r"); + strcat(TNC->InitScript, "XMITECHO ON\r"); + strcat(TNC->InitScript, "TXFLOW OFF\r"); + strcat(TNC->InitScript, "XFLOW OFF\r"); + strcat(TNC->InitScript, "TRFLOW OFF\r"); + strcat(TNC->InitScript, "AUTOCR 0\r"); + strcat(TNC->InitScript, "AUTOLF OFF\r"); + strcat(TNC->InitScript, "CRADD OFF\r"); + strcat(TNC->InitScript, "CRSUP OFF\r"); + strcat(TNC->InitScript, "CRSUP OFF/OFF\r"); + strcat(TNC->InitScript, "LFADD OFF/OFF\r"); + strcat(TNC->InitScript, "LFADD OFF\r"); + strcat(TNC->InitScript, "LFSUP OFF/OFF\r"); + strcat(TNC->InitScript, "LFSUP OFF\r"); + strcat(TNC->InitScript, "RING OFF\r"); + strcat(TNC->InitScript, "ARQBBS OFF\r"); + + // Set the ax.25 MYCALL + + + sprintf(msg, "MYCALL %s/%s\r", TNC->NodeCall, TNC->NodeCall); + strcat(TNC->InitScript, msg); + + // look for the MAXUSERS config line, and get the limits + + TNC->InitScript = _strupr(TNC->InitScript); + + ptr = strstr(TNC->InitScript, "MAXUSERS"); + + if (ptr) + { + ptr = strchr(ptr,'/'); // to the separator + if (ptr) + PortEntry->MAXHOSTMODESESSIONS = atoi(++ptr) + 1; + } + + if (PortEntry->MAXHOSTMODESESSIONS > 26) + PortEntry->MAXHOSTMODESESSIONS = 26; + + PortEntry->PORTCONTROL.TNC = TNC; + + TNC->WebWindowProc = WebProc; + TNC->WebWinX = 510; + TNC->WebWinY = 280; + + TNC->WEB_COMMSSTATE = zalloc(100); + TNC->WEB_TNCSTATE = zalloc(100); + strcpy(TNC->WEB_TNCSTATE, "Free"); + TNC->WEB_MODE = zalloc(100); + TNC->WEB_TRAFFIC = zalloc(100); + TNC->WEB_BUFFERS = zalloc(100); + TNC->WEB_STATE = zalloc(100); + TNC->WEB_TXRX = zalloc(100); + TNC->WebBuffer = zalloc(5000); + +#ifndef LINBPQ + + CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 500, 500, ForcedClose); + + + CreateWindowEx(0, "STATIC", "Comms State", WS_CHILD | WS_VISIBLE, 10,6,120,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_COMMSSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,6,386,20, TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "TNC State", WS_CHILD | WS_VISIBLE, 10,28,106,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_TNCSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,28,520,20, TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "Mode", WS_CHILD | WS_VISIBLE, 10,50,80,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_MODE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,50,200,20, TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "Status", WS_CHILD | WS_VISIBLE, 10,72,110,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_STATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,72,144,20, TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "TX/RX State", WS_CHILD | WS_VISIBLE,10,94,80,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_TXRX = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,94,374,20 , TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "Free Space", WS_CHILD | WS_VISIBLE,10,116,80,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_BUFFERS = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,116,374,20 , TNC->hDlg, NULL, hInstance, NULL); + + CreateWindowEx(0, "STATIC", "Traffic", WS_CHILD | WS_VISIBLE,10,138,80,20, TNC->hDlg, NULL, hInstance, NULL); + TNC->xIDC_TRAFFIC = CreateWindowEx(0, "STATIC", "RX 0 TX 0 ACKED 0", WS_CHILD | WS_VISIBLE,116,138,374,20 , TNC->hDlg, NULL, hInstance, NULL); + + TNC->hMonitor= CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | LBS_NOINTEGRALHEIGHT | + LBS_DISABLENOSCROLL | WS_HSCROLL | WS_VSCROLL, + 0,RigControlRow + 44,250,300, TNC->hDlg, NULL, hInstance, NULL); + + TNC->ClientHeight = 500; + TNC->ClientWidth = 500; + + MoveWindows(TNC); +#endif + OpenCOMMPort(TNC, PortEntry->PORTCONTROL.SerialPortName, PortEntry->PORTCONTROL.BAUDRATE, FALSE); + + WritetoConsole("\n"); + + return ExtProc; +} + + + +void CheckRXKAM(struct TNCINFO * TNC) +{ + int Length, Len; + char debug[512] = "RX: "; + + // only try to read number of bytes in queue + + if (TNC->RXLen == 500) + TNC->RXLen = 0; + + Len = ReadCOMBlock(TNC->hDevice, &TNC->RXBuffer[TNC->RXLen], 500 - TNC->RXLen); + + if (Len == 0) + return; + + TNC->RXLen += Len; + + memcpy(&debug[4], TNC->RXBuffer, TNC->RXLen); + debug[TNC->RXLen + 4] = 0; + WriteLogLine(TNC->Port, debug, TNC->RXLen + 4); + + Length = TNC->RXLen; + + // If first char != FEND, then probably a Terminal Mode Frame. Wait for CR on end + + if (TNC->RXBuffer[0] != FEND) + { + // Char Mode Frame I think we need to see cmd: on end + + // If we think we are in host mode, then to could be noise - just discard. + + if (TNC->HostMode) + { + TNC->RXLen = 0; // Ready for next frame + return; + } + + TNC->RXBuffer[TNC->RXLen] = 0; + +// if (TNC->RXBuffer[TNC->RXLen-2] != ':') + if (strstr(TNC->RXBuffer, "cmd:") == 0) + return; // Wait for rest of frame + + // Complete Char Mode Frame + + TNC->RXLen = 0; // Ready for next frame + + if (TNC->HostMode == 0) + { + // We think TNC is in Terminal Mode + ProcessTermModeResponse(TNC); + return; + } + // We thought it was in Host Mode, but are wrong. + + TNC->HostMode = FALSE; + return; + } + + // Receiving a Host Mode frame + + if (TNC->HostMode == 0) // If we are in Term Mode, discard it. Probably in recovery + { + TNC->RXLen = 0; // Ready for next frame + return; + } + + if (Length < 3) // Minimum Frame Sise + return; + + if (TNC->RXBuffer[Length-1] != FEND) + return; // Wait till we have a full frame + + ProcessHostFrame(TNC, TNC->RXBuffer, Length); // Could have multiple packets in buffer + + TNC->RXLen = 0; // Ready for next frame + + + return; + +} + +VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len) +{ + UCHAR * FendPtr; + int NewLen; + + // Split into KISS Packets. By far the most likely is a single KISS frame + // so treat as special case + + if (rxbuffer[1] == FEND) // Two FENDS - probably got out of sync + { + rxbuffer++; + Len--; + } + + FendPtr = memchr(&rxbuffer[1], FEND, Len-1); + + if (FendPtr == &rxbuffer[Len-1]) + { + ProcessKHOSTPacket(TNC, &rxbuffer[1], Len - 2); + return; + } + + // Process the first Packet in the buffer + + NewLen = (int)(FendPtr - rxbuffer - 1); + + ProcessKHOSTPacket(TNC, &rxbuffer[1], NewLen); + + // Loop Back + + ProcessHostFrame(TNC, FendPtr+1, Len - NewLen - 2); + return; + +} + + + +static BOOL WriteCommBlock(struct TNCINFO * TNC) +{ + char debug[512] = "TX: "; + + memcpy(&debug[4], TNC->TXBuffer, TNC->TXLen); + debug[TNC->TXLen + 4] = 0; + + WriteLogLine(TNC->Port, debug, TNC->TXLen + 4); + + WriteCOMBlock(TNC->hDevice, TNC->TXBuffer, TNC->TXLen); + + return TRUE; +} + +VOID KAMPoll(int Port) +{ + struct TNCINFO * TNC = TNCInfo[Port]; + struct STREAMINFO * STREAM; + + UCHAR * Poll = TNC->TXBuffer; + char Status[80]; + int Stream; + + if (TNC->PortRecord == 0) + Stream = 0; + + + // If Pactor Session has just been attached, drop back to cmd mode and set Pactor Call to + // the connecting user's callsign + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] && STREAM->Attached == 0) + { + // New Attach + + STREAM->Attached = TRUE; + + if (Stream == 0) // HF Port + { + int calllen; + UCHAR TXMsg[1000] = "D20"; + int datalen; + char Msg[80]; + + TNC->HFPacket = FALSE; + TNC->TimeInRX = 0; + + calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4USER, TNC->Streams[0].MyCall); + TNC->Streams[0].MyCall[calllen] = 0; + + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? + if (TNC->VeryOldMode) + datalen = sprintf(TXMsg, "C20MYCALL %s", TNC->Streams[0].MyCall); + else + datalen = sprintf(TXMsg, "C20MYPTCALL %s", TNC->Streams[0].MyCall); + EncodeAndSend(TNC, TXMsg, datalen); + TNC->InternalCmd = 'M'; + + TNC->NeedPACTOR = 0; // Cancel enter Pactor + + sprintf(TNC->WEB_TNCSTATE, "In Use by %s", TNC->Streams[0].MyCall); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + // Stop Scanning + + sprintf(Msg, "%d SCANSTOP", TNC->Port); + + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + + } + } + } + + if (TNC->Timeout) + { + TNC->Timeout--; + + if (TNC->Timeout) // Still waiting + return; + + // Timed Out + + if (TNC->HostMode == 0) + { + DoTermModeTimeout(TNC); + return; + } + + // Timed out in host mode - Clear any connection and reinit the TNC + + Debugprintf("KAM PACTOR - Link to TNC Lost"); + TNC->TNCOK = FALSE; + TNC->HostMode = 0; + TNC->ReinitState = 0; + + sprintf(TNC->WEB_COMMSSTATE, "%s Open but TNC not responding", TNC->PortRecord->PORTCONTROL.SerialPortName); + SetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE); + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + PMSGWITHLEN buffptr; + + STREAM = &TNC->Streams[Stream]; + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream]) // Connected + { + STREAM->Connected = FALSE; // Back to Command Mode + STREAM->ReportDISC = TRUE; // Tell Node + } + + STREAM->FramesQueued = 0; + + while(STREAM->BPQtoPACTOR_Q) + { + buffptr=Q_REM(&STREAM->BPQtoPACTOR_Q); + ReleaseBuffer(buffptr); + } + + while(STREAM->PACTORtoBPQ_Q) + { + buffptr=Q_REM(&STREAM->PACTORtoBPQ_Q); + ReleaseBuffer(buffptr); + } + } + } + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + if (STREAM->Attached) + CheckForDetach(TNC, Stream, STREAM, TidyClose, ForcedClose, CloseComplete); + + } + + // if we have just restarted or TNC appears to be in terminal mode, run Initialisation Sequence + + if (!TNC->HostMode) + { + DoTNCReinit(TNC); + return; + } + + if (TNC->BusyDelay) // Waiting to send connect + { + // Still Busy? + + if (InterlockedCheckBusy(TNC) == 0) + { + // No, so send + + EncodeAndSend(TNC, TNC->ConnectCmd, (int)strlen(TNC->ConnectCmd)); + free(TNC->ConnectCmd); + + TNC->Timeout = 50; + TNC->InternalCmd = 'C'; // So we dont send the reply to the user. + STREAM->Connecting = TRUE; + + TNC->Streams[0].Connecting = TRUE; + + sprintf(TNC->WEB_TNCSTATE, "%s Connecting to %s", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + TNC->BusyDelay = 0; + return; + } + else + { + // Wait Longer + + TNC->BusyDelay--; + + if (TNC->BusyDelay == 0) + { + // Timed out - Send Error Response + + PMSGWITHLEN buffptr = GetBuff(); + + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data, "Sorry, Can't Connect - Channel is busy\r"); + + C_Q_ADD(&TNC->Streams[0].PACTORtoBPQ_Q, buffptr); + + free(TNC->ConnectCmd); + + } + } + } + + if (TNC->NeedPACTOR) + { + TNC->NeedPACTOR--; + + if (TNC->NeedPACTOR == 0) + { + int datalen; + UCHAR TXMsg[80] = "D20"; + + if (TNC->VeryOldMode) + datalen = sprintf(TXMsg, "C20MYCALL %s", TNC->NodeCall); + else + datalen = sprintf(TXMsg, "C20MYPTCALL %s", TNC->NodeCall); + EncodeAndSend(TNC, TXMsg, datalen); + + if (TNC->OldMode) + EncodeAndSend(TNC, "C20PACTOR", 9); // Back to Listen + else + EncodeAndSend(TNC, "C20TOR", 6); // Back to Listen + + TNC->InternalCmd = 'T'; + TNC->Timeout = 50; + TNC->IntCmdDelay--; + + // Restart Scanning + + sprintf(Status, "%d SCANSTART 15", TNC->Port); + + Rig_Command( (TRANSPORTENTRY *) -1, Status); + + return; + } + } + + for (Stream = 0; Stream <= MaxStreams; Stream++) + { + STREAM = &TNC->Streams[Stream]; + + // If in HF Packet mode, normal flow control doesn't seem to work + // If more that 4 packets sent, send a status poll. and use response to + // reset frames outstanding + + if ((Stream == 0) && (TNC->HFPacket) && (TNC->Streams[0].FramesOutstanding > 4)) + { + EncodeAndSend(TNC, "C10S", 4); + TNC->InternalCmd = 'S'; + TNC->Timeout = 50; + return; + + } + + if (TNC->TNCOK && STREAM->BPQtoPACTOR_Q) + { + int datalen; + UCHAR TXMsg[1000] = "D20"; + PMSGWITHLEN buffptr; + UCHAR * MsgPtr; + char Status[80]; + + if (STREAM->Connected) + { + int Next; + + if (Stream > 0) + sprintf(TXMsg, "D1%c", Stream + '@'); + else if (TNC->HFPacket) + memcpy(TXMsg, "D2A", 3); + else + { + // Pactor + + // Limit amount in TX, so we keep some on the TX Q and don't send turnround too early + + if (TNC->Streams[0].bytesTXed - TNC->Streams[0].BytesAcked > 200) + continue; + + // Dont send if IRS State + // If in IRS state for too long, force turnround + + if (TNC->TXRXState == 'R') + { + if (TNC->TimeInRX++ > 15) + EncodeAndSend(TNC, "T", 1); // Changeover to ISS + else + goto Poll; + } + TNC->TimeInRX = 0; + } + + buffptr=Q_REM(&STREAM->BPQtoPACTOR_Q); + STREAM->FramesQueued--; + + datalen = buffptr->Len; + MsgPtr = buffptr->Data; + + if (TNC->SwallowSignon && Stream == 0) + { + TNC->SwallowSignon = FALSE; + if (strstr(MsgPtr, "Connected")) // Discard *** connected + { + ReleaseBuffer(buffptr); + return; + } + } + + Next = 0; + STREAM->bytesTXed += datalen; + + if (Stream == 0) + { + while (datalen > 100) // Limit Pactor Sends + { + memcpy(&TXMsg[3], &MsgPtr[Next], 100); + EncodeAndSend(TNC, TXMsg, 103); + Next += 100; + datalen -= 100; + + WritetoTrace(TNC, &TXMsg[3], 100); + } + } + + memcpy(&TXMsg[3], &MsgPtr[Next], datalen); + EncodeAndSend(TNC, TXMsg, datalen + 3); + + WritetoTrace(TNC, &TXMsg[3], datalen); + + ReleaseBuffer(buffptr); + + if (Stream == 0) + { + sprintf(Status, "RX %d TX %d ACKED %d ", + TNC->Streams[0].bytesRXed, TNC->Streams[0].bytesTXed, TNC->Streams[0].BytesAcked); + SetWindowText(TNC->xIDC_TRAFFIC, Status); + + if ((TNC->HFPacket == 0) && (TNC->Streams[0].BPQtoPACTOR_Q == 0)) // Nothing following + { + EncodeAndSend(TNC, "E", 1); // Changeover when all sent + } + } + + if (STREAM->Disconnecting) + { + Debugprintf("Send with Disc Pending, Q = %x", STREAM->BPQtoPACTOR_Q); + if (STREAM->BPQtoPACTOR_Q == 0) // All Sent + + // KAM doesnt have a tidy close! + + STREAM->DisconnectingTimeout = 100; // Give 5 secs to get to other end + } + return; + } + else // Not Connected + { + buffptr = Q_REM(&STREAM->BPQtoPACTOR_Q); + datalen = buffptr->Len; + MsgPtr = buffptr->Data; + + // Command. Do some sanity checking and look for things to process locally + + datalen--; // Exclude CR + MsgPtr[datalen] = 0; // Null Terminate + _strupr(MsgPtr); + + if ((Stream == 0) && memcmp(MsgPtr, "RADIO ", 6) == 0) + { + sprintf(&MsgPtr[40], "%d %s", TNC->Port, &MsgPtr[6]); + if (Rig_Command(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK, &MsgPtr[40])) + { + ReleaseBuffer(buffptr); + } + else + { + buffptr->Len = sprintf(buffptr->Data, "%s", &MsgPtr[40]); + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + } + return; + } + + if (_memicmp(MsgPtr, "D\r", 2) == 0) + { + STREAM->ReportDISC = TRUE; // Tell Node + return; + } + + if ((Stream == 0) && memcmp(MsgPtr, "HFPACKET", 8) == 0) + { + TNC->HFPacket = TRUE; + buffptr->Len = sprintf(buffptr->Data, "KAM} OK\r"); + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + return; + } + + if (MsgPtr[0] == 'C' && MsgPtr[1] == ' ' && datalen > 2) // Connect + { + memcpy(STREAM->RemoteCall, &MsgPtr[2], 9); + STREAM->Connecting = TRUE; + + // If Stream 0, Convert C CALL to PACTOR CALL + + if (Stream == 0) + { + if (TNC->HFPacket) + datalen = sprintf(TXMsg, "C2AC %s", TNC->Streams[0].RemoteCall); + else + datalen = sprintf(TXMsg, "C20PACTOR %s", TNC->Streams[0].RemoteCall); + + // If Pactor, check busy detecters on any interlocked ports + + if (TNC->HFPacket == 0 && InterlockedCheckBusy(TNC) && TNC->OverrideBusy == 0) + { + // Channel Busy. Wait + + TNC->ConnectCmd = _strdup(TXMsg); + + sprintf(TNC->WEB_TNCSTATE, "Waiting for clear channel"); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + TNC->BusyDelay = TNC->BusyWait * 10; + + return; + } + + TNC->OverrideBusy = FALSE; + + sprintf(TNC->WEB_TNCSTATE, "%s Connecting to %s", + TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + } + else + datalen = sprintf(TXMsg, "C1%cC %s", Stream + '@', STREAM->RemoteCall); + + EncodeAndSend(TNC, TXMsg, datalen); + TNC->Timeout = 50; + TNC->InternalCmd = 'C'; // So we dont send the reply to the user. + ReleaseBuffer(buffptr); + STREAM->Connecting = TRUE; + + return; + } + + if (memcmp(MsgPtr, "GTOR ", 5) == 0) // GTOR Connect + { + memcpy(STREAM->RemoteCall, &MsgPtr[5], 9); + STREAM->Connecting = TRUE; + + // If Stream 0, Convert C CALL to PACTOR CALL + + if (Stream == 0) + { + datalen = sprintf(TXMsg, "C20GTOR %s", TNC->Streams[0].RemoteCall); + + // If Pactor, check busy detecters on any interlocked ports + + if (TNC->HFPacket == 0 && InterlockedCheckBusy(TNC) && TNC->OverrideBusy == 0) + { + // Channel Busy. Wait + + TNC->ConnectCmd = _strdup(TXMsg); + + sprintf(TNC->WEB_TNCSTATE, "Waiting for clear channel"); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + TNC->BusyDelay = TNC->BusyWait * 10; + + return; + } + + TNC->OverrideBusy = FALSE; + + sprintf(TNC->WEB_TNCSTATE, "%s Connecting to %s", + TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + } + else + datalen = sprintf(TXMsg, "C1%cC %s", Stream + '@', STREAM->RemoteCall); + + EncodeAndSend(TNC, TXMsg, datalen); + TNC->Timeout = 50; + TNC->InternalCmd = 'C'; // So we dont send the reply to the user. + ReleaseBuffer(buffptr); + STREAM->Connecting = TRUE; + + return; + } + + if (memcmp(MsgPtr, "DISCONNECT", datalen) == 0) // Disconnect + { + if (Stream == 0) + { + if (TNC->HFPacket) + EncodeAndSend(TNC, "C2AD", 4); // ??Return to packet mode?? + else + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? + + TNC->NeedPACTOR = 50; + } + else + { + sprintf(TXMsg, "C1%cD", Stream + '@'); + EncodeAndSend(TNC, TXMsg, 4); + TNC->CmdStream = Stream; + TNC->Timeout = 50; + } + + TNC->Timeout = 0; // Don't expect a response + STREAM->Connecting = FALSE; + STREAM->ReportDISC = TRUE; + ReleaseBuffer(buffptr); + + return; + } + + // Other Command ?? + + if (Stream > 0) + datalen = sprintf(TXMsg, "C1%c%s", Stream + '@', MsgPtr); + else + datalen = sprintf(TXMsg, "C20%s", MsgPtr); + EncodeAndSend(TNC, TXMsg, datalen); + ReleaseBuffer(buffptr); + TNC->Timeout = 50; + TNC->InternalCmd = 0; + TNC->CmdStream = Stream; + + } + } + } + +Poll: + + // Need to poll data and control channel (for responses to commands) + + // Also check status if we have data buffered (for flow control) + + if (TNC->TNCOK) + { + if (TNC->IntCmdDelay == 50) + { + EncodeAndSend(TNC, "C10S", 4); + TNC->InternalCmd = 'S'; + TNC->Timeout = 50; + TNC->IntCmdDelay--; + return; + } + + if (TNC->IntCmdDelay <=0) + { + if (TNC->VeryOldMode == FALSE) + { + EncodeAndSend(TNC, "?", 1); + TNC->InternalCmd = '?'; + TNC->Timeout = 50; + } + TNC->IntCmdDelay = 100; // Every 30 + return; + } + else + TNC->IntCmdDelay--; + } + + return; + +} + +static VOID DoTNCReinit(struct TNCINFO * TNC) +{ + UCHAR * Poll = TNC->TXBuffer; + + if (TNC->ReinitState == 1) // Forcing back to Term + TNC->ReinitState = 0; // Got Response, so must be back in term mode + + if (TNC->ReinitState == 0) + { + // Just Starting - Send a TNC Mode Command to see if in Terminal or Host Mode + + sprintf(TNC->WEB_COMMSSTATE,"%s Initialising TNC", TNC->PortRecord->PORTCONTROL.SerialPortName); + SetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE); + + Poll[0] = 13; + TNC->TXLen = 1; + + WriteCommBlock(TNC); + TNC->Timeout = 50; + + return; + } + + if (TNC->ReinitState == 2) // In Term State, Sending Initialisation Commands + { + char * start, * end; + int len; + + start = TNC->InitPtr; + + if (*(start) == 0) // End of Script + { + // Put into Host Mode + + memcpy(Poll, "INTFACE HOST\r", 13); + + TNC->TXLen = 13; + WriteCommBlock(TNC); + TNC->Timeout = 50; + + TNC->ReinitState = 4; // Need Reset + + return; + } + + end = strchr(start, 13); + len = (int)(++end - start); + TNC->InitPtr = end; + memcpy(Poll, start, len); + + TNC->TXLen = len; + WriteCommBlock(TNC); + TNC->Timeout = 50; + + return; + + } +} + +static VOID DoTermModeTimeout(struct TNCINFO * TNC) +{ + UCHAR * Poll = TNC->TXBuffer; + + if (TNC->ReinitState == 0) + { + //Checking if in Terminal Mode - Try to set back to Term Mode + + TNC->ReinitState = 1; + Poll[0] = 3; + Poll[1] = 0x58; // ?? Back to cmd: mode ?? + TNC->TXLen = 2; + + Poll[0] = 0xc0; + Poll[1] = 'Q'; // ?? Back to cmd: mode ?? + Poll[2] = 0xc0; + TNC->TXLen = 3; + + WriteCommBlock(TNC); + + return; + } + if (TNC->ReinitState == 1) + { + // Forcing back to Term Mode + + TNC->ReinitState = 0; + DoTNCReinit(TNC); // See if worked + return; + } + + if (TNC->ReinitState == 3) + { + // Entering Host Mode + + // Assume ok + + TNC->HostMode = TRUE; + return; + } +} + + + +static VOID ProcessTermModeResponse(struct TNCINFO * TNC) +{ + UCHAR * Poll = TNC->TXBuffer; + + if (TNC->ReinitState == 0 || TNC->ReinitState == 1) + { + // Testing if in Term Mode. It is, so can now send Init Commands + + TNC->InitPtr = TNC->InitScript; + TNC->ReinitState = 2; + DoTNCReinit(TNC); // Send First Command + return; + } + if (TNC->ReinitState == 2) + { + // Sending Init Commands + + DoTNCReinit(TNC); // Send Next Command + return; + } + + if (TNC->ReinitState == 4) // Send INTFACE, Need RESET + { + TNC->ReinitState = 5; + + memcpy(Poll, "RESET\r", 6); + + TNC->TXLen = 6; + WriteCommBlock(TNC); + TNC->Timeout = 50; + TNC->HostMode = TRUE; // Should now be in Host Mode + TNC->NeedPACTOR = 50; // Need to Send PACTOR command after 5 secs + + return; + } + + if (TNC->ReinitState == 5) // RESET sent + { + TNC->ReinitState = 5; + + return; + } + + + +} + +VOID ProcessKHOSTPacket(struct TNCINFO * TNC, UCHAR * Msg, int Len) +{ + PMSGWITHLEN buffptr; + char * Buffer = &Msg[3]; // Data portion of frame + char * Call; + char Status[80]; + int Stream = 0; + struct STREAMINFO * STREAM; + + // Any valid frame is an ACK + + TNC->TNCOK = TRUE; + + Len = KissDecode(Msg, Msg, Len); // Remove KISS transparency + + if (Msg[1] == '0' && Msg[2] == '0') + Stream = 0; + else + if (Msg[1] == '2') Stream = 0; else Stream = Msg[2] - '@'; + + STREAM = &TNC->Streams[Stream]; + + // See if Poll Reply or Data + + Msg[Len] = 0; // Terminate + + if (Msg[0] == 'M') // Monitor + { + DoMonitor(TNC, Msg, Len); + return; + } + + + if (Msg[0] == 'E') // Data Echo + { + if (Msg[1] == '2') // HF Port + { + if (TNC->Streams[0].bytesTXed) + TNC->Streams[0].BytesAcked += Len - 3; // We get an ack before the first send + + sprintf(Status, "RX %d TX %d ACKED %d ", + TNC->Streams[0].bytesRXed, TNC->Streams[0].bytesTXed, TNC->Streams[0].BytesAcked); + SetWindowText(TNC->xIDC_TRAFFIC, Status); + + if (TNC->Streams[0].bytesTXed - TNC->Streams[0].BytesAcked < 500) + TNC->Streams[0].FramesOutstanding = 0; + } + return; + } + + if (Msg[0] == 'D') // Data + { + // Pass to Appl + + buffptr = GetBuff(); + if (buffptr == NULL) return; // No buffers, so ignore + + Len-=3; // Remove Header + + buffptr->Len = Len; // Length + STREAM->bytesRXed += Len; + memcpy(buffptr->Data, Buffer, Len); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + if (Stream == 0) + { + sprintf(TNC->WEB_TRAFFIC, "RX %d TX %d ACKED %d ", + TNC->Streams[0].bytesRXed, TNC->Streams[0].bytesTXed, TNC->Streams[0].BytesAcked); + SetWindowText(TNC->xIDC_TRAFFIC, TNC->WEB_TRAFFIC); + } + + WritetoTrace(TNC, Buffer, Len); + + return; + } + + + if (Msg[0] == 'C') // Command Reponse + { + TNC->Timeout = 0; + + // See if we need to process locally (Response to our command, Incoming Call, Disconencted, etc + + // See if a response to internal command + + if (TNC->InternalCmd) + { + // Process it + + if (TNC->InternalCmd == 'S') // Status + { + char * Line; + char * ptr; + + // Message is line giving free bytes, followed by a line for each active (packet) stream + + // FREE BYTES 1366/5094 + // A/2 #1145(12) CONNECTED to KE7XO-3 + // S/2 CONNECTED to NLV + + // each line is teminated by CR, and by the time it gets here it is null terminated + + //FREE BYTES 2628 + //A/H #80(1) CONNECTED to DK0MNL.. + + if (TNC->HFPacket) + TNC->Streams[0].FramesOutstanding = 0; + + Line = strchr(&Msg[3], 13); + if (Line == 0) return; + + *(Line) = 0; + + ptr = strchr(&Msg[13], '/'); + TNC->Mem1 = atoi(&Msg[13]); + if (ptr) + TNC->Mem2 = atoi(++ptr); + else + TNC->Mem2 = 0; + + SetWindowText(TNC->xIDC_BUFFERS, &Msg[14]); + strcpy(TNC->WEB_BUFFERS, &Msg[14]); + + while (Line[1] != 0) // End of stream + { + Stream = Line[1] - '@'; + STREAM = &TNC->Streams[Stream]; + + if (Line[5] == '#') + { + STREAM->BytesOutstanding = atoi(&Line[6]); + ptr = strchr(&Line[6], '('); + if (ptr) + STREAM->FramesOutstanding = atoi(++ptr); + } + else + { + STREAM->BytesOutstanding = 0; + STREAM->FramesOutstanding = 0; + } + + Line = strchr(&Line[1], 13); + } + return; + } + return; + } + + + WritetoTrace(TNC, Buffer, Len); + + + // Pass to Appl + + Stream = TNC->CmdStream; + + + buffptr = GetBuff(); + + if (buffptr == NULL) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data,"KAM} %s", Buffer); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + return; + } + + if (Msg[0] == 'I') // ISS/IRS State + { + if (Msg[2] == '1') + { + strcpy(TNC->WEB_TXRX, "Sender"); + SetWindowText(TNC->xIDC_TXRX, "Sender"); + TNC->TXRXState = 'S'; + } + else + { + strcpy(TNC->WEB_TXRX, "Receiver"); + SetWindowText(TNC->xIDC_TXRX, "Receiver"); + TNC->TXRXState = 'R'; + } + return; + } + + if (Msg[0] == '?') // Status + { + TNC->Timeout = 0; + return; + } + + if (Msg[0] == 'S') // Status + { + if (Len < 4) + { + // Reset Response FEND FEND S00 FEND + + TNC->Timeout = 0; + + sprintf(TNC->WEB_COMMSSTATE,"%s TNC link OK", TNC->PortRecord->PORTCONTROL.SerialPortName); + SetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE); + + return; + } + + // Pass to Appl + + if (strstr(Buffer, "STANDBY>") || strstr(Buffer, "*** DISCONNECTED")) + { + if ((STREAM->Connecting | STREAM->Connected) == 0) + { + // Not connected or Connecting. Probably response to going into Pactor Listen Mode + + return; + } + + if (STREAM->Connecting && STREAM->Disconnecting == FALSE) + { + // Connect Failed + + buffptr = GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data, "*** Failure with %s\r", STREAM->RemoteCall); + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + STREAM->Connecting = FALSE; + STREAM->Connected = FALSE; // In case! + STREAM->FramesOutstanding = 0; + + return; + } + + // Must Have been connected or disconnecting - Release Session + + STREAM->Connecting = FALSE; + STREAM->Connected = FALSE; // Back to Command Mode + STREAM->FramesOutstanding = 0; + + if (STREAM->Disconnecting == FALSE) + STREAM->ReportDISC = TRUE; // Tell Node + + STREAM->Disconnecting = FALSE; + + if (Stream == 0) + { + // Need to reset Pactor Call in case it was changed + + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? + TNC->NeedPACTOR = 20; + } + + return; + } + + if (Msg[2] == '0') + Call = strstr(Buffer, "HFPacket == 0) + { + Buffer[Len-4] = 0; + } + + STREAM->bytesRXed = STREAM->bytesTXed = STREAM->BytesAcked = 0; + STREAM->ConnectTime = time(NULL); + + if (Stream == 0) + { + // Stop Scanner + + char Msg[80]; + + sprintf(Msg, "%d SCANSTOP", TNC->Port); + + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + + sprintf(TNC->WEB_TRAFFIC, "RX %d TX %d ACKED %d ", + TNC->Streams[0].bytesRXed, TNC->Streams[0].bytesTXed, TNC->Streams[0].BytesAcked); + + SetWindowText(TNC->xIDC_TRAFFIC, TNC->WEB_TRAFFIC); + } + + if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0) + { + // Incoming Connect + + TRANSPORTENTRY * SESS; + + if (Msg[1] == '2' && Msg[2] == 'A') + TNC->HFPacket = TRUE; + + // Stop other ports in same group + + SuspendOtherPorts(TNC); + + ProcessIncommingConnect(TNC, Call, Stream, TRUE); + + SESS = TNC->PortRecord->ATTACHEDSESSIONS[Stream]; + + if (Stream == 0) + { + struct WL2KInfo * WL2K = TNC->WL2K; + char FreqAppl[10] = ""; // Frequecy-specific application + + if (TNC->RIG && TNC->RIG != &TNC->DummyRig) + { + sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound Freq %s", TNC->Streams[0].RemoteCall, TNC->NodeCall, TNC->RIG->Valchar); + SESS->Frequency = (atof(TNC->RIG->Valchar) * 1000000.0) + 1500; // Convert to Centre Freq + + // If Scan Entry has a Appl, save it + + if (TNC->RIG->FreqPtr && TNC->RIG->FreqPtr[0]->APPL[0]) + strcpy(FreqAppl, &TNC->RIG->FreqPtr[0]->APPL[0]); + } + else + { + sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound", TNC->Streams[0].RemoteCall, TNC->NodeCall); + + if (WL2K) + { + SESS->Frequency = WL2K->Freq; + } + } + + SESS->Mode = 11; // P1 + + if (WL2K) + strcpy(SESS->RMSCall, WL2K->RMSCall); + + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + EncodeAndSend(TNC, "T", 1); // Changeover to ISS + + // If an autoconnect APPL is defined, send it + + if (FreqAppl[0]) // Frequency spcific APPL overrides TNC APPL + { + buffptr = GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data, "%s\r", FreqAppl); + C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr); + TNC->SwallowSignon = TRUE; + return; + } + + if (TNC->ApplCmd) + { + buffptr = GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data, "%s\r", TNC->ApplCmd); + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + TNC->SwallowSignon = TRUE; + return; + } + } + + if (FULL_CTEXT && HFCTEXTLEN == 0) + { + char CTBuff[300] = "D20"; + int Len = CTEXTLEN, CTPaclen = 50; + int Next = 0; + + if (Stream > 0) + sprintf(CTBuff, "D1%c", Stream + '@'); + + while (Len > CTPaclen) // CTEXT Paclen + { + memcpy(&CTBuff[3], &CTEXTMSG[Next], CTPaclen); + EncodeAndSend(TNC, CTBuff, CTPaclen + 3); + Next += CTPaclen; + Len -= CTPaclen; + } + + memcpy(&CTBuff[3], &CTEXTMSG[Next], Len); + EncodeAndSend(TNC, CTBuff, Len + 3); + EncodeAndSend(TNC, "E", 1); // Changeover when all sent + TNC->Streams[0].bytesTXed += CTEXTLEN; + } + return; + + } + else + { + // Connect Complete + + buffptr = GetBuff(); + if (buffptr == 0) return; // No buffers, so ignore + + buffptr->Len = sprintf(buffptr->Data, "*** Connected to %s\r", Call);; + + C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr); + + STREAM->Connecting = FALSE; + STREAM->Connected = TRUE; // Subsequent data to data channel + + + if (Stream == 0) + { + if (TNC->RIG) + sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound Freq %s", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall, TNC->RIG->Valchar); + else + sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound", TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall); + + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + UpdateMH(TNC, Call, '+', 'O'); + + } + + return; + } + } + } +} + + +static int KissDecode(UCHAR * inbuff, UCHAR * outbuff, int len) +{ + int i,txptr=0; + UCHAR c; + + for (i=0;iTXLen = KissEncode(txbuffer, TNC->TXBuffer, Len); + WriteCommBlock(TNC); +} + +static int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len) +{ + int i,txptr=0; + UCHAR c; + + outbuff[0]=FEND; + txptr=1; + + for (i=0;i'); + + if (ptr) *(ptr) = 0; + + UpdateMH(TNC, &Msg[3], ' ', 0); + +} +VOID TidyClose(struct TNCINFO * TNC, int Stream) +{ + if (Stream == 0) // Pactor Stream + { + TNC->TimeInRX = 0; + if (TNC->HFPacket) + EncodeAndSend(TNC, "C2AD", 4); // Disconnect + else + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? +// EncodeAndSend(TNC, "C20TOR", 6); // Disconnect + + TNC->HFPacket = FALSE; + } + else + { + UCHAR TXMsg[10]; + + sprintf(TXMsg, "C1%cD", Stream + '@'); + EncodeAndSend(TNC, TXMsg, 4); + TNC->Timeout = 50; + } +} + +VOID ForcedClose(struct TNCINFO * TNC, int Stream) +{ + if (Stream == 0) // Pactor Stream + { + TNC->TimeInRX = 0; + + if (TNC->HFPacket) + EncodeAndSend(TNC, "C2AD", 4); // Disconnect + else + EncodeAndSend(TNC, "X", 1); // ??Return to packet mode?? + + TNC->HFPacket = FALSE; + } + else + { + UCHAR TXMsg[10]; + + sprintf(TXMsg, "C1%cD", Stream + '@'); + EncodeAndSend(TNC, TXMsg, 4); // Send twice - must force a disconnect + TNC->Timeout = 50; + }} + +VOID CloseComplete(struct TNCINFO * TNC, int Stream) +{ + sprintf(TNC->WEB_TNCSTATE, "Free"); + SetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE); + + ReleaseOtherPorts(TNC); + TNC->NeedPACTOR = 50; +} + + + +/* + + +MARK 1400 +SPACE 1600 +SHIFT MODEM +INV ON +MAXUSERS 1/10 +PTERRS 30 + + // Others go on end so they can't be overriden + +ECHO OFF +XMITECHO ON +TXFLOW OFF +XFLOW OFF +TRFLOW OFF +AUTOCR 0 +AUTOLF OFF +CRADD OFF +CRSUP OFF +CRSUP OFF/OFF +LFADD OFF/OFF +LFADD OFF +LFSUP OFF/OFF +LFSUP OFF +RING OFF +ARQBBS OFF + +MYCALL + +*/ diff --git a/.svn/pristine/1a/1aa9d4e92ce6b8878c3ec022bb10c4789a6bed84.svn-base b/.svn/pristine/1a/1aa9d4e92ce6b8878c3ec022bb10c4789a6bed84.svn-base new file mode 100644 index 0000000..239598b --- /dev/null +++ b/.svn/pristine/1a/1aa9d4e92ce6b8878c3ec022bb10c4789a6bed84.svn-base @@ -0,0 +1,6472 @@ +/* +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 +*/ + +#define _CRT_SECURE_NO_DEPRECATE + +#include "cheaders.h" +#include "bpqmail.h" + +#define MAIL +#include "httpconnectioninfo.h" + +#ifdef WIN32 +//#include "C:\Program Files (x86)\GnuWin32\include\iconv.h" +#else +#include +#include +#endif + +static struct HTTPConnectionInfo * FindSession(char * Key); +int APIENTRY SessionControl(int stream, int command, int param); +int SetupNodeMenu(char * Buff); +VOID SetMultiStringValue(char ** values, char * Multi); +char * GetTemplateFromFile(int Version, char * FN); +VOID FormatTime(char * Time, time_t cTime); +struct MsgInfo * GetMsgFromNumber(int msgno); +BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP); +BOOL OkToKillMessage(BOOL SYSOP, char * Call, struct MsgInfo * Msg); +int DisplayWebForm(struct HTTPConnectionInfo * Session, struct MsgInfo * Msg, char * FileName, char * XML, char * Reply, char * RawMessage, int RawLen); +struct HTTPConnectionInfo * AllocateWebMailSession(); +VOID SaveNewMessage(struct HTTPConnectionInfo * Session, char * MsgPtr, char * Reply, int * RLen, char * Rest, int InputLen); +void ConvertTitletoUTF8(WebMailInfo * WebMail, char * Title, char * UTF8Title, int Len); +char *stristr (char *ch1, char *ch2); +char * ReadTemplate(char * FormSet, char * DirName, char * FileName); +VOID DoStandardTemplateSubsitutions(struct HTTPConnectionInfo * Session, char * txtFile); +BOOL CheckifPacket(char * Via); +int GetHTMLFormSet(char * FormSet); +void ProcessFormInput(struct HTTPConnectionInfo * Session, char * input, char * Reply, int * RLen, int InputLen); +char * WebFindPart(char ** Msg, char * Boundary, int * PartLen, char * End); +struct HTTPConnectionInfo * FindWMSession(char * Key); +int SendWebMailHeaderEx(char * Reply, char * Key, struct HTTPConnectionInfo * Session, char * Alert); +char * BuildFormMessage(struct HTTPConnectionInfo * Session, struct MsgInfo * Msg, char * Keys[1000], char * Values[1000], int NumKeys); +char * FindXMLVariable(WebMailInfo * WebMail, char * Var); +int ReplyToFormsMessage(struct HTTPConnectionInfo * Session, struct MsgInfo * Msg, char * Reply, BOOL Reenter); +BOOL ParsetxtTemplate(struct HTTPConnectionInfo * Session, struct HtmlFormDir * Dir, char * FN, BOOL isReply); +VOID UpdateFormAction(char * Template, char * Key); +BOOL APIENTRY GetAPRSLatLon(double * PLat, double * PLon); +BOOL APIENTRY GetAPRSLatLonString(char * PLat, char * PLon); +void FreeWebMailFields(WebMailInfo * WebMail); +VOID BuildXMLAttachment(struct HTTPConnectionInfo * Session, char * Keys[1000], char * Values[1000], int NumKeys); +VOID SaveTemplateMessage(struct HTTPConnectionInfo * Session, char * MsgPtr, char * Reply, int * RLen, char * Rest); +VOID DownloadAttachments(struct HTTPConnectionInfo * Session, char * Reply, int * RLen, char * Rest); +VOID getAttachmentList(struct HTTPConnectionInfo * Session, char * Reply, int * RLen, char * Rest); +char * BuildB2Header(WebMailInfo * WebMail, struct MsgInfo * Msg, char ** ToCalls, int Calls); +VOID FormatTime2(char * Time, time_t cTime); +VOID ProcessSelectResponse(struct HTTPConnectionInfo * Session, char * URLParams); +VOID ProcessAskResponse(struct HTTPConnectionInfo * Session, char * URLParams); +char * CheckFile(struct HtmlFormDir * Dir, char * FN); +VOID GetPage(struct HTTPConnectionInfo * Session, char * NodeURL); +VOID SendTemplateSelectScreen(struct HTTPConnectionInfo * Session, char *URLParams, int InputLen); +BOOL isAMPRMsg(char * Addr); +char * doXMLTransparency(char * string); +Dll BOOL APIENTRY APISendAPRSMessage(char * Text, char * ToCall); +void SendMessageReadEvent(char * Call, struct MsgInfo * Msg); +void SendNewMessageEvent(char * call, struct MsgInfo * Msg); +void MQTTMessageEvent(void* message); + +extern char NodeTail[]; +extern char BBSName[10]; + +extern char LTFROMString[2048]; +extern char LTTOString[2048]; +extern char LTATString[2048]; + + UCHAR BPQDirectory[260]; + +int LineCount = 35; // Lines per page on message list + +// Forms + +struct HtmlFormDir ** HtmlFormDirs = NULL; +int FormDirCount = 0; + +struct HtmlForm +{ + char * FileName; + BOOL HasInitial; + BOOL HasViewer; + BOOL HasReply; + BOOL HasReplyViewer; +}; + +struct HtmlFormDir +{ + char * FormSet; + char * DirName; + struct HtmlForm ** Forms; + int FormCount; + struct HtmlFormDir ** Dirs; // Nested Directories + int DirCount; +}; + + +char FormDirList[4][MAX_PATH] = {"Standard_Templates", "Standard Templates", "Local_Templates"}; + +static char PassError[] = "

Sorry, User or Password is invalid - please try again

"; +static char BusyError[] = "

Sorry, No sessions available - please try later

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

BPQ32 Mail Server %s Access

" + "

Please enter Callsign and Password to access WebMail

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

"; + +static char MsgInputPage[] = "" + "" + "" + "" + "" + "

Webmail Interface - Message Input Form

" + "
" + "
" + "To      %s
" + "Subject      " +// "" +// "
" + "" + "
Type    " + "" + " BID
" + "" + "" + "
" + "
" + "
"; + +static char CheckFormMsgPage[] = "" + "" + "" + "

Webmail Forms Interface - Check Message

" + "
" + + "
" + "To      
" + "CC      
" + "Subject " +//"" + "
Type    " + "" + " BID

" + "
" + + "
" + "
"; + + +extern char * WebMailTemplate; +extern char * WebMailMsgTemplate; +extern char * jsTemplate; + +static char *dat[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +char *longday[] = {"Sunday", "Monday", "Tusday", "Wednesday", "Thusday", "Friday", "Saturday"}; + +static struct HTTPConnectionInfo * WebSessionList = NULL; // active WebMail sessions + +#ifdef LINBPQ +UCHAR * GetBPQDirectory(); +#endif + +void UndoTransparency(char * input); + +#ifndef LINBPQ + +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; +} +#endif + +void ReleaseWebMailStruct(WebMailInfo * WebMail) +{ + // release any malloc'ed resources + + if (WebMail == NULL) + return; + + FreeWebMailFields(WebMail); + free(WebMail); + return; +} + +VOID FreeWebMailMallocs() +{ + // called when closing. Not really needed, but simplifies tracking down real memory leaks + + struct HTTPConnectionInfo * Session, * SaveNext; + int i; + Session = WebSessionList; + + while (Session) + { + SaveNext = Session->Next; + + // Release amy malloc'ed resouces + + ReleaseWebMailStruct(Session->WebMail); + free(Session); + Session = SaveNext; + } + + for (i = 0; i < FormDirCount; i++) + { + struct HtmlFormDir * Dir = HtmlFormDirs[i]; + + int j; + + for (j = 0; j < Dir->FormCount; j++) + { + free(Dir->Forms[j]->FileName); + free(Dir->Forms[j]); + } + + if (Dir->DirCount) + { + struct HtmlFormDir * SubDir; + + int k, l; + + for (l = 0; l < Dir->DirCount; l++) + { + SubDir = Dir->Dirs[l]; + + for (k = 0; k < Dir->Dirs[l]->FormCount; k++) + { + free(SubDir->Forms[k]->FileName); + free(SubDir->Forms[k]); + } + free(SubDir->DirName); + free(SubDir->Forms); + free(SubDir->FormSet); + + free(Dir->Dirs[l]); + } + } + free(Dir->DirName); + free(Dir->Forms); + free(Dir->FormSet); + free(Dir); + } + + free(HtmlFormDirs); + return; +} + +char * initMultipartUnpack(char ** Input) +{ + // Check if Multipart and return Boundary. Update Input to first part + + // look through header for Content-Type line, and if multipart + // find boundary string. + + char * ptr, * ptr2; + char Boundary[128]; + BOOL Multipart = FALSE; + + ptr = *Input; + + while(*ptr != 13) + { + ptr2 = strchr(ptr, 10); // Find CR + + while(ptr2[1] == ' ' || ptr2[1] == 9) // Whitespace - continuation line + ptr2 = strchr(&ptr2[1], 10); // Find CR + + if (_memicmp(ptr, "Content-Type: ", 14) == 0) + { + char Line[256] = ""; + char * ptr3; + size_t len = ptr2-ptr-14; + + if (len >255) + return NULL; + + memcpy(Line, &ptr[14], len); + + if (_memicmp(Line, "Multipart/", 10) == 0) + { + ptr3 = stristr(Line, "boundary"); + if (ptr3) + { + ptr3+=9; + + if ((*ptr3) == '"') + ptr3++; + + strcpy(Boundary, ptr3); + ptr3 = strchr(Boundary, '"'); + if (ptr3) *ptr3 = 0; + ptr3 = strchr(Boundary, 13); // CR + if (ptr3) *ptr3 = 0; + break; + } + else + return NULL; // Can't do anything without a boundary ?? + } + } + ptr = ptr2; + ptr++; + } + + // Find First part - there is a boundary before it + + ptr = strstr(ptr2, Boundary); + + // Next should be crlf then part + + ptr = strstr(ptr, "\r\n"); + + if (ptr) + ptr += 2; // Over CRLF + + *Input = ptr; // Return first part or NULL + return _strdup(Boundary); +} + +BOOL unpackPart(char * Boundary, char ** Input, char ** Name, char ** Value, int * ValLen, char * End) +{ + // Format seems to be +/* + ------WebKitFormBoundaryABJaEbBWB5SuAHmq + Content-Disposition: form-data; name="Subj" + + subj + ------WebKitFormBoundaryABJaEbBWB5SuAHmq + Content-Disposition: form-data; name="myFile[]"; filename="exiftool.txt" + Content-Type: text/plain + + c:\exiftool "-filenameTo = _strdup(Value); + else if (strcmp(Name, "CC") == 0) + WebMail->CC = _strdup(Value); + else if (strcmp(Name, "Subj") == 0) + WebMail->Subject = _strdup(Value); + else if (strcmp(Name, "Type") == 0) + WebMail->Type = Value[0]; + else if (strcmp(Name, "BID") == 0) + WebMail->BID = _strdup(Value); + else if (strcmp(Name, "Msg") == 0) + WebMail->Body = _strdup(Value); + + else if (_memicmp(Name, "myFile[]", 8) == 0) + { + // Get File Name from param string - myFile[]"; filename="exiftool.txt" \r\nContent-Type: text/plain + + char * fn = strstr(Name, "filename="); + char * endfn; + if (fn) + { + fn += 10; + + endfn = strchr(fn, '"'); + if (endfn) + { + *endfn = 0; + + if (strlen(fn)) + { + WebMail->FileName[WebMail->Files] = _strdup(fn); + WebMail->FileBody[WebMail->Files] = malloc(ValLength); + memcpy(WebMail->FileBody[WebMail->Files], Value, ValLength); + WebMail->FileLen[WebMail->Files++] = ValLength; + } + } + } + } + + else if (_memicmp(Name, "myFile2[]", 8) == 0) + { + // Get File Name from param string - myFile[]"; filename="exiftool.txt" \r\nContent-Type: text/plain + + char * fn = strstr(Name, "filename="); + char * endfn; + if (fn) + { + fn += 10; + + endfn = strchr(fn, '"'); + if (endfn) + { + *endfn = 0; + + if (strlen(fn)) + { + WebMail->Header = malloc(ValLength + 1); + memcpy(WebMail->Header, Value, ValLength + 1); + WebMail->HeaderLen = RemoveLF(WebMail->Header, ValLength); + } + } + } + } + + else if (_memicmp(Name, "myFile3[]", 8) == 0) + { + // Get File Name from param string - myFile[]"; filename="exiftool.txt" \r\nContent-Type: text/plain + + char * fn = strstr(Name, "filename="); + char * endfn; + if (fn) + { + fn += 10; + + endfn = strchr(fn, '"'); + if (endfn) + { + *endfn = 0; + + if (strlen(fn)) + { + WebMail->Footer = malloc(ValLength + 1); + memcpy(WebMail->Footer, Value, ValLength + 1); + WebMail->FooterLen = RemoveLF(WebMail->Footer, ValLength); + } + } + } + } + + return TRUE; +} + + +struct HTTPConnectionInfo * AllocateWebMailSession() +{ + int KeyVal; + struct HTTPConnectionInfo * Session, * SaveNext; + time_t NOW = time(NULL); + + // First see if any session records havent been used for a while + + Session = WebSessionList; + + while (Session) + { + if (NOW - Session->WebMailLastUsed > 1200) // 20 Mins + { + SaveNext = Session->Next; + + // Release amy malloc'ed resouces + + ReleaseWebMailStruct(Session->WebMail); + + memset(Session, 0, sizeof(struct HTTPConnectionInfo)); + + Session->Next = SaveNext; + goto UseThis; + } + Session = Session->Next; + } + + Session = zalloc(sizeof(struct HTTPConnectionInfo)); + + if (Session == NULL) + return NULL; + + if (WebSessionList) + Session->Next = WebSessionList; + + WebSessionList = Session; + +UseThis: + + Session->WebMail = zalloc(sizeof(WebMailInfo)); + + KeyVal = ((rand() % 100) + 1); + + KeyVal *= (int)time(NULL); + + sprintf(Session->Key, "%c%08X", 'W', KeyVal); + + return Session; +} + +struct HTTPConnectionInfo * FindWMSession(char * Key) +{ + struct HTTPConnectionInfo * Session = WebSessionList; + + while (Session) + { + if (strcmp(Session->Key, Key) == 0) + { + Session->WebMailLastUsed = time(NULL); + return Session; + } + Session = Session->Next; + } + + return NULL; +} + + +// Build list of available forms + +VOID ProcessFormDir(char * FormSet, char * DirName, struct HtmlFormDir *** xxx, int * DirCount) +{ + struct HtmlFormDir * FormDir; + struct HtmlFormDir ** FormDirs = *xxx; + struct HtmlForm * Form; + char Search[MAX_PATH]; + int count = *DirCount; + +#ifdef WIN32 + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd; +#else + DIR *dir; + struct dirent *entry; + char name[256]; +#endif + + FormDir = zalloc(sizeof (struct HtmlFormDir)); + + FormDir->DirName = _strdup(DirName); + FormDir->FormSet = _strdup(FormSet); + FormDirs=realloc(FormDirs, (count + 1) * sizeof(void *)); + FormDirs[count++] = FormDir; + + *DirCount = count; + *xxx = FormDirs; + + + // Scan Directory for .txt files + + sprintf(Search, "%s/%s/%s/*", GetBPQDirectory(), FormSet, DirName); + + // Find the first file in the directory. + +#ifdef WIN32 + + hFind = FindFirstFile(Search, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + return; + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + char Dir[MAX_PATH]; + + if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) + continue; + + // Recurse in subdir + + sprintf(Dir, "%s/%s", DirName, ffd.cFileName); + + ProcessFormDir(FormSet, Dir, &FormDir->Dirs, &FormDir->DirCount); + + continue; + + } + + // Add to list + + Form = zalloc(sizeof (struct HtmlForm)); + + Form->FileName = _strdup(ffd.cFileName); + + FormDir->Forms=realloc(FormDir->Forms, (FormDir->FormCount + 1) * sizeof(void *)); + FormDir->Forms[FormDir->FormCount++] = Form; + } + + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + +#else + + sprintf(Search, "%s/%s/%s", GetBPQDirectory(), FormSet, DirName); + + if (!(dir = opendir(Search))) + { + Debugprintf("%s %d %d", "cant open forms dir", errno, dir); + return ; + } + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_type == DT_DIR) + { + char Dir[MAX_PATH]; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + // Recurse in subdir + + sprintf(Dir, "%s/%s", DirName, entry->d_name); + + ProcessFormDir(FormSet, Dir, &FormDir->Dirs, &FormDir->DirCount); + continue; + } + + // Add to list + + Form = zalloc(sizeof (struct HtmlForm)); + + Form->FileName = _strdup(entry->d_name); + + FormDir->Forms=realloc(FormDir->Forms, (FormDir->FormCount + 1) * sizeof(void *)); + FormDir->Forms[FormDir->FormCount++] = Form; + } + closedir(dir); +#endif + return; +} + +int GetHTMLForms() +{ + int n = 0; + + while (FormDirList[n][0]) + GetHTMLFormSet(FormDirList[n++]); + + return 0; +} + +int GetHTMLFormSet(char * FormSet) +{ + int i; + +#ifdef WIN32 + + WIN32_FIND_DATA ffd; + char szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + DWORD dwError=0; + + sprintf(szDir, "%s/%s/*", BPQDirectory, FormSet); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + // Accept either + return 0; + } + + // Scan all directories looking for file + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) + continue; + + // Add to Directory List + + ProcessFormDir(FormSet, ffd.cFileName, &HtmlFormDirs, &FormDirCount); + } + } + + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + +#else + + DIR *dir; + struct dirent *entry; + char name[256]; + + sprintf(name, "%s/%s", BPQDirectory, FormSet); + + if (!(dir = opendir(name))) + { + Debugprintf("cant open forms dir %s %d %d", name, errno, dir); + } + else + { + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_type == DT_DIR) + { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + // Add to Directory List + + ProcessFormDir(FormSet, entry->d_name, &HtmlFormDirs, &FormDirCount); + } + } + closedir(dir); + } +#endif + + // List for testing + + return 0; + + Debugprintf("%d form dirs", FormDirCount); + + for (i = 0; i < FormDirCount; i++) + { + struct HtmlFormDir * Dir = HtmlFormDirs[i]; + + int j; + Debugprintf("%3d %s", Dir->FormCount, Dir->DirName); + + for (j = 0; j < Dir->FormCount; j++) + Debugprintf(" %s", Dir->Forms[j]->FileName); + + if (Dir->DirCount) + { + int k, l; + + for (l = 0; l < Dir->DirCount; l++) + { + Debugprintf("Subdir %3d %s", Dir->Dirs[l]->DirCount, Dir->Dirs[l]->DirName); + for (k = 0; k < Dir->Dirs[l]->FormCount; k++) + Debugprintf(" %s", Dir->Dirs[l]->Forms[k]->FileName); + } + } + } + + + return 0; +} + + +static int compare(const void *arg1, const void *arg2) +{ + // Compare Calls. Fortunately call is at start of stuct + + return _stricmp(*(char**)arg1 , *(char**)arg2); +} + + +int SendWebMailHeader(char * Reply, char * Key, struct HTTPConnectionInfo * Session) +{ + return SendWebMailHeaderEx(Reply, Key, Session, NULL); +} + + +int SendWebMailHeaderEx(char * Reply, char * Key, struct HTTPConnectionInfo * Session, char * Alert) +{ + // Ex includes an alert string to be sent before message + + struct UserInfo * User = Session->User; + char Messages[245000]; + int m; + struct MsgInfo * Msg; + char * ptr = Messages; + int n = NumberofMessages; //LineCount; + char Via[64]; + int Count = 0; + + Messages[0] = 0; + + if (Alert && Alert[0]) + ptr += sprintf(Messages, "", Alert, Key); + + ptr += sprintf(ptr, "%s", " # Date XX Len To @ From Subject\r\n\r\n"); + + for (m = LatestMsg; m >= 1; m--) + { + if (ptr > &Messages[244000]) + break; // protect buffer + + Msg = GetMsgFromNumber(m); + + if (Msg == 0 || Msg->type == 0 || Msg->status == 0) + continue; // Protect against corrupt messages + + if (Msg && CheckUserMsg(Msg, User->Call, User->flags & F_SYSOP)) + { + char UTF8Title[4096]; + char * EncodedTitle; + + // List if it is the right type and in the page range we want + + if (Session->WebMailTypes[0] && strchr(Session->WebMailTypes, Msg->type) == 0) + continue; + + // All Types or right Type. Check Mine Flag + + if (Session->WebMailMine) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to) != 0 && strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyTX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyRX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to)!= 0) + continue; + } + + if (Count++ < Session->WebMailSkip) + continue; + + strcpy(Via, Msg->via); + strlop(Via, '.'); + + // make sure title is HTML safe (no < > etc) and UTF 8 encoded + + EncodedTitle = doXMLTransparency(Msg->title); + + memset(UTF8Title, 0, 4096); // In case convert fails part way through + ConvertTitletoUTF8(Session->WebMail, EncodedTitle, UTF8Title, 4095); + + free(EncodedTitle); + + ptr += sprintf(ptr, "%6d %s %c%c %5d %-8s%-8s%-8s%s\r\n", + Key, Msg->number, Msg->number, + FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, + Msg->status, Msg->length, Msg->to, Via, + Msg->from, UTF8Title); + + n--; + + if (n == 0) + break; + } + } + + if (WebMailTemplate == NULL) + WebMailTemplate = GetTemplateFromFile(6, "WebMailPage.txt"); + + return sprintf(Reply, WebMailTemplate, BBSName, User->Call, Key, Key, Key, Key, Key, Key, Key, Key, Key, Key, Messages); +} + +int ViewWebMailMessage(struct HTTPConnectionInfo * Session, char * Reply, int Number, BOOL DisplayHTML) +{ + char * Key = Session->Key; + struct UserInfo * User = Session->User; + WebMailInfo * WebMail = Session->WebMail; + char * DisplayStyle; + + char Message[200000] = ""; + struct MsgInfo * Msg; + char * ptr = Message; + char * MsgBytes, * Save; + int msgLen; + + char FullTo[100]; + char UTF8Title[4096]; + int Index; + char * crcrptr; + char DownLoad[256] = ""; + + DisplayStyle = "textarea"; // Prevents interpretation of html and xml + + Msg = GetMsgFromNumber(Number); + + if (Msg == NULL) + { + ptr += sprintf(ptr, "Message %d not found\r\n", Number); + return sprintf(Reply, WebMailTemplate, BBSName, User->Call, Key, Key, Key, Key, Key, Key, Key, Message); + } + + // New Display so free any old values + + FreeWebMailFields(WebMail); + + WebMail->CurrentMessageIndex = Number; + + + if (!CheckUserMsg(Msg, User->Call, User->flags & F_SYSOP)) + { + ptr += sprintf(ptr, "Message %d not for you\r", Number); + return sprintf(Reply, WebMailTemplate, BBSName, User->Call, Key, Key, Key, Key, Key, Key, Key, Message); + } + + 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); + + // make sure title is UTF 8 encoded + + memset(UTF8Title, 0, 4096); // In case convert fails part way through + ConvertTitletoUTF8(Session->WebMail, Msg->title, UTF8Title, 4095); + + // if a B2 message diplay B2 Header instead of a locally generated one + + if ((Msg->B2Flags & B2Msg) == 0) + { + ptr += sprintf(ptr, "From: %s%s\nTo: %s\nType/Status: %c%c\nDate/Time: %s\nBid: %s\nTitle: %s\n\n", + Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, UTF8Title); + } + + MsgBytes = Save = ReadMessageFile(Number); + + msgLen = Msg->length; + + if (Msg->type == 'P') + Index = PMSG; + else if (Msg->type == 'B') + Index = BMSG; + else + Index = TMSG; + + if (MsgBytes) + { + if (Msg->B2Flags & B2Msg) + { + char * ptr1; + + // if message has attachments, display them if plain text + + if (Msg->B2Flags & Attachments) + { + int BodyLen, NewLen; + int i; + char *ptr2, *attptr; + + sprintf(DownLoad, "Save Attachments", Key, Msg->number); + + WebMail->Files = 0; + + ptr1 = MsgBytes; + + // ptr += sprintf(ptr, "Message has Attachments\r\n\r\n"); + + while(*ptr1 != 13) + { + ptr2 = strchr(ptr1, 10); // Find CR + + if (memcmp(ptr1, "Body: ", 6) == 0) + { + BodyLen = atoi(&ptr1[6]); + } + + if (memcmp(ptr1, "File: ", 6) == 0) + { + char * ptr3 = strchr(&ptr1[6], ' '); // Find Space + *(ptr2 - 1) = 0; + + WebMail->FileLen[WebMail->Files] = atoi(&ptr1[6]); + WebMail->FileName[WebMail->Files++] = _strdup(&ptr3[1]); + *(ptr2 - 1) = ' '; // put space back + } + + ptr1 = ptr2; + ptr1++; + } + + ptr1 += 2; // Over Blank Line and Separator + + // ptr1 is pointing to body. Save for possible reply + + WebMail->Body = malloc(BodyLen + 2); + memcpy(WebMail->Body, ptr1, BodyLen); + WebMail->Body[BodyLen] = 0; + + *(ptr1 + BodyLen) = 0; + + ptr += sprintf(ptr, "%s", MsgBytes); // B2 Header and Body + + ptr1 += BodyLen + 2; // to first file + + // Save pointers to file + + attptr = ptr1; + + for (i = 0; i < WebMail->Files; i++) + { + WebMail->FileBody[i] = malloc(WebMail->FileLen[i]); + memcpy(WebMail->FileBody[i], attptr, WebMail->FileLen[i]); + attptr += (WebMail->FileLen[i] + 2); + } + + // if first (only??) attachment is XML and filename + // starts "RMS_Express_Form" process as HTML Form + + if (DisplayHTML && _memicmp(ptr1, "FileName[0], "RMS_Express_Form_", 16) == 0) + { + int Len = DisplayWebForm(Session, Msg, WebMail->FileName[0], ptr1, Reply, MsgBytes, BodyLen + 32); // 32 for added "has attachments" + free(MsgBytes); + + // Flag as read + + if ((_stricmp(Msg->to, User->Call) == 0) || ((User->flags & F_SYSOP) && (_stricmp(Msg->to, "SYSOP") == 0))) + { + if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) + { + if (Msg->status != 'Y') + { + Msg->status = 'Y'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + SendMessageReadEvent(Session->Callsign, Msg); + } + } + } + + return Len; + } + + for (i = 0; i < WebMail->Files; i++) + { + int n; + char * p = ptr1; + char c; + + // Check if message is probably binary + + int BinCount = 0; + + NewLen = WebMail->FileLen[i]; + + for (n = 0; n < NewLen; n++) + { + c = *p; + + if (c==0 || (c & 128)) + BinCount++; + + p++; + + } + + if (BinCount > NewLen/10) + { + // File is probably Binary + + ptr += sprintf(ptr, "\rAttachment %s is a binary file\r", WebMail->FileName[i]); + } + else + { + *(ptr1 + NewLen) = 0; + ptr += sprintf(ptr, "\rAttachment %s\r\r", WebMail->FileName[i]); + RemoveLF(ptr1, NewLen + 1); // Removes LF after CR but not on its own + + ptr += sprintf(ptr, "%s\r\r", ptr1); + + User->Total.MsgsSent[Index] ++; + User->Total.BytesForwardedOut[Index] += NewLen; + } + + ptr1 += WebMail->FileLen[i]; + ptr1 +=2; // Over separator + } + + free(Save); + + ptr += sprintf(ptr, "\r\r[End of Message #%d from %s]\r", Number, Msg->from); + + RemoveLF(Message, (int)strlen(Message) + 1); // Removes LF after CR but not on its own + + if ((_stricmp(Msg->to, User->Call) == 0) || ((User->flags & F_SYSOP) && (_stricmp(Msg->to, "SYSOP") == 0))) + { + if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) + { + if (Msg->status != 'Y') + { + Msg->status = 'Y'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + SendMessageReadEvent(Session->Callsign, Msg); + } + } + } + + if (DisplayHTML && stristr(Message, "")) + DisplayStyle = "div"; // Use div so HTML and XML are interpreted + + return sprintf(Reply, WebMailMsgTemplate, BBSName, User->Call, Msg->number, Msg->number, Key, Msg->number, Key, DownLoad, Key, Key, Key, DisplayStyle, Message, DisplayStyle); + } + + // Remove B2 Headers (up to the File: Line) + + // ptr1 = strstr(MsgBytes, "Body:"); + + // if (ptr1) + // MsgBytes = ptr1; + } + + // Body may have cr cr lf which causes double space + + crcrptr = strstr(MsgBytes, "\r\r\n"); + + while (crcrptr) + { + *crcrptr = ' '; + crcrptr = strstr(crcrptr, "\r\r\n"); + } + + // Remove lf chars + + msgLen = RemoveLF(MsgBytes, msgLen); + + User->Total.MsgsSent[Index] ++; + // User->Total.BytesForwardedOut[Index] += Length; + + // if body not UTF-8, convert it + + if (WebIsUTF8(MsgBytes, msgLen) == FALSE) + { + int code = TrytoGuessCode(MsgBytes, msgLen); + + UCHAR * UTF = malloc(msgLen * 3); + + if (code == 437) + msgLen = Convert437toUTF8(MsgBytes, msgLen, UTF); + else if (code == 1251) + msgLen = Convert1251toUTF8(MsgBytes, msgLen, UTF); + else + msgLen = Convert1252toUTF8(MsgBytes, msgLen, UTF); + + free(MsgBytes); + Save = MsgBytes = UTF; + + MsgBytes[msgLen] = 0; + } + + // ptr += sprintf(ptr, "%s", MsgBytes); + + memcpy(ptr, MsgBytes, msgLen); + ptr += msgLen; + ptr[0] = 0; + + free(Save); + + ptr += sprintf(ptr, "\r\r[End of Message #%d from %s]\r", Number, Msg->from); + + if ((_stricmp(Msg->to, User->Call) == 0) || ((User->flags & F_SYSOP) && (_stricmp(Msg->to, "SYSOP") == 0))) + { + if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) + { + if (Msg->status != 'Y') + { + Msg->status = 'Y'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + SendMessageReadEvent(Session->Callsign, Msg); + } + } + } + } + else + { + ptr += sprintf(ptr, "File for Message %d not found\r", Number); + } + + if (DisplayHTML && stristr(Message, "")) + DisplayStyle = "div"; // Use div so HTML and XML are interpreted + + + return sprintf(Reply, WebMailMsgTemplate, BBSName, User->Call, Msg->number, Msg->number, Key, Msg->number, Key, DownLoad, Key, Key, Key, DisplayStyle, Message, DisplayStyle); +} + +int KillWebMailMessage(char * Reply, char * Key, struct UserInfo * User, int Number) +{ + struct MsgInfo * Msg; + char Message[100] = ""; + + Msg = GetMsgFromNumber(Number); + + if (Msg == NULL) + { + sprintf(Message, "Message %d not found", Number); + goto returnit; + } + + if (OkToKillMessage(User->flags & F_SYSOP, User->Call, Msg)) + { + FlagAsKilled(Msg, TRUE); + sprintf(Message, "Message #%d Killed\r", Number); + goto returnit; + } + + sprintf(Message, "Not your message\r"); + +returnit: + return sprintf(Reply, WebMailMsgTemplate, BBSName, User->Call, Msg->number, Msg->number, Key, Msg->number, Key, "", Key, Key, Key, "div", Message, "div"); +} + +void freeKeys(KeyValues * Keys) +{ + while (Keys->Key) + { + free(Keys->Key); + free(Keys->Value); + Keys++; + } +} + +void FreeWebMailFields(WebMailInfo * WebMail) +{ + // release any malloc'ed resources + + int i; + char * SaveReply; + int * SaveRlen; + + if (WebMail == NULL) + return; + + if (WebMail->txtFile) + free(WebMail->txtFile); + + if (WebMail->txtFileName) + free(WebMail->txtFileName); + + if (WebMail->InputHTMLName) + free(WebMail->InputHTMLName); + + if (WebMail->DisplayHTMLName) + free(WebMail->DisplayHTMLName); + + if (WebMail->ReplyHTMLName) + free(WebMail->ReplyHTMLName); + + if (WebMail->To) + free(WebMail->To); + if (WebMail->CC) + free(WebMail->CC); + if (WebMail->Subject) + free(WebMail->Subject); + if (WebMail->BID) + free(WebMail->BID); + if (WebMail->Body) + free(WebMail->Body); + if (WebMail->XML) + free(WebMail->XML); + if (WebMail->XMLName) + free(WebMail->XMLName); + + if (WebMail->OrigTo) + free(WebMail->OrigTo); + if (WebMail->OrigSubject) + free(WebMail->OrigSubject); + if (WebMail->OrigBID) + free(WebMail->OrigBID); + if (WebMail->OrigBody) + free(WebMail->OrigBody); + + freeKeys(WebMail->txtKeys); + freeKeys(WebMail->XMLKeys); + + for (i = 0; i < WebMail->Files; i++) + { + free(WebMail->FileBody[i]); + free(WebMail->FileName[i]); + } + + if (WebMail->Header) + free(WebMail->Header); + if (WebMail->Footer) + free(WebMail->Footer); + + SaveReply = WebMail->Reply; + SaveRlen = WebMail->RLen; + +#ifndef WIN32 + if (WebMail->iconv_toUTF8) + iconv_close(WebMail->iconv_toUTF8); +#endif + + memset(WebMail, 0, sizeof(WebMailInfo)); + + WebMail->Reply = SaveReply; + WebMail->RLen = SaveRlen; + + return; +} + + +void ProcessWebMailMessage(struct HTTPConnectionInfo * Session, char * Key, BOOL LOCAL, char * Method, char * NodeURL, char * input, char * Reply, int * RLen, int InputLen) +{ + char * URLParams = strlop(Key, '&'); + int ReplyLen; + char Appl = 'M'; + + // Webmail doesn't use the normal Mail Key. + + // webscript.js doesn't need a key + + if (_stricmp(NodeURL, "/WebMail/webscript.js") == 0) + { + if (jsTemplate) + free(jsTemplate); + + jsTemplate = GetTemplateFromFile(2, "webscript.js"); + + ReplyLen = sprintf(Reply, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n" + "Cache-Control: max-age=60\r\nContent-Type: text/javascript\r\n\r\n%s", (int)strlen(jsTemplate), jsTemplate); + *RLen = ReplyLen; + return; + } + + // Neither do js or file downloads + + // This could be a request for a Template file + // WebMail/Local_Templates/My Forms/inc/logo_ad63.png + // WebMail/Standard Templates/ + + + if (_memicmp(NodeURL, "/WebMail/Local", 14) == 0 || (_memicmp(NodeURL, "/WebMail/Standard", 17) == 0)) + { + int FileSize; + char * MsgBytes; + char MsgFile[512]; + FILE * hFile; + size_t ReadLen; + char TimeString[64]; + char FileTimeString[64]; + struct stat STAT; + char * FN = &NodeURL[9]; + char * fileBit = FN; + char * ext; + char Type[64] = "Content-Type: text/html\r\n"; + + UndoTransparency(FN); + ext = strchr(FN, '.'); + + sprintf(MsgFile, "%s/%s", BPQDirectory, FN); + + while (strchr(fileBit, '/')) + fileBit = strlop(fileBit, '/'); + + if (stat(MsgFile, &STAT) == -1) + { + *RLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nPage not found\r\n"); + return; + } + + hFile = fopen(MsgFile, "rb"); + + if (hFile == 0) + { + *RLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nPage not found\r\n"); + return; + } + + FileSize = STAT.st_size; + MsgBytes = malloc(FileSize + 1); + ReadLen = fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + FormatTime2(FileTimeString, STAT.st_ctime); + FormatTime2(TimeString, time(NULL)); + + ext++; + + if (_stricmp(ext, "js") == 0) + strcpy(Type, "Content-Type: text/javascript\r\n"); + + if (_stricmp(ext, "css") == 0) + strcpy(Type, "Content-Type: text/css\r\n"); + + if (_stricmp(ext, "pdf") == 0) + strcpy(Type, "Content-Type: application/pdf\r\n"); + + if (_stricmp(ext, "jpg") == 0 || _stricmp(ext, "jpeg") == 0 || _stricmp(ext, "png") == 0 || + _stricmp(ext, "gif") == 0 || _stricmp(ext, "bmp") == 0 || _stricmp(ext, "ico") == 0) + strcpy(Type, "Content-Type: image\r\n"); + + // File may be binary so output header then copy in message + + *RLen = sprintf(Reply, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n" + "%s" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n", FileSize, Type,TimeString, FileTimeString); + + memcpy(&Reply[*RLen], MsgBytes, FileSize); + *RLen += FileSize; + free (MsgBytes); + return; + } + + // + + if (_memicmp(NodeURL, "/WebMail/WMFile/", 16) == 0) + { + int FileSize; + char * MsgBytes; + char MsgFile[512]; + FILE * hFile; + size_t ReadLen; + char TimeString[64]; + char FileTimeString[64]; + struct stat STAT; + char * FN = &NodeURL[16]; + char * fileBit = FN; + char * ext; + char Type[64] = "Content-Type: text/html\r\n"; + + + UndoTransparency(FN); + ext = strchr(FN, '.'); + + sprintf(MsgFile, "%s/%s", BPQDirectory, FN); + + while (strchr(fileBit, '/')) + fileBit = strlop(fileBit, '/'); + + if (stat(MsgFile, &STAT) == -1) + { + *RLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nPage not found\r\n"); + return; + } + + hFile = fopen(MsgFile, "rb"); + + if (hFile == 0) + { + *RLen = sprintf(Reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 16\r\n\r\nPage not found\r\n"); + return; + } + + FileSize = STAT.st_size; + MsgBytes = malloc(FileSize + 1); + ReadLen = fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + FormatTime2(FileTimeString, STAT.st_ctime); + FormatTime2(TimeString, time(NULL)); + + ext++; + + if (_stricmp(ext, "js") == 0) + strcpy(Type, "Content-Type: text/javascript\r\n"); + + if (_stricmp(ext, "css") == 0) + strcpy(Type, "Content-Type: text/css\r\n"); + + if (_stricmp(ext, "pdf") == 0) + strcpy(Type, "Content-Type: application/pdf\r\n"); + + if (_stricmp(ext, "jpg") == 0 || _stricmp(ext, "jpeg") == 0 || _stricmp(ext, "png") == 0 || + _stricmp(ext, "gif") == 0 || _stricmp(ext, "bmp") == 0 || _stricmp(ext, "ico") == 0) + strcpy(Type, "Content-Type: image\r\n"); + + // File may be binary so output header then copy in message + + *RLen = sprintf(Reply, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n" + "%s" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n", FileSize, Type,TimeString, FileTimeString); + + memcpy(&Reply[*RLen], MsgBytes, FileSize); + *RLen += FileSize; + free (MsgBytes); + return; + } + + Session = NULL; + + if (Key && Key[0]) + Session = FindWMSession(Key); + + if (Session == NULL) + { + // Lost Session + + if (LOCAL) + { + Session = AllocateWebMailSession(); + + Key = Session->Key; + + if (SYSOPCall[0]) + Session->User = LookupCall(SYSOPCall); + else + Session->User = LookupCall(BBSName); + + if (Session->User) + { + strcpy(NodeURL, "/WebMail/WebMail"); + Session->WebMailSkip = 0; + Session->WebMailLastUsed = time(NULL); + } + } + else + { + // Send Login Page unless Signon request + + if (_stricmp(NodeURL, "/WebMail/Signon") != 0 || strcmp(Method, "POST") != 0) + { + ReplyLen = sprintf(Reply, WebMailSignon, BBSName, BBSName); + *RLen = ReplyLen; + return; + } + } + } + + if (strcmp(Method, "POST") == 0) + { + if (_stricmp(NodeURL, "/WebMail/Signon") == 0) + { + char * msg = strstr(input, "\r\n\r\n"); // End of headers + char * user, * password, * Key; + char Msg[128]; + int n; + + if (msg) + { + struct UserInfo * User; + + if (strstr(msg, "Cancel=Cancel")) + { + *RLen = sprintf(Reply, ""); + return; + } + // Webmail Gets Here with a dummy Session + + Session = AllocateWebMailSession(); + Session->WebMail->Reply = Reply; + Session->WebMail->RLen = RLen; + + + Key = Session->Key; + + user = strtok_s(&msg[9], "&", &Key); + password = strtok_s(NULL, "=", &Key); + password = Key; + + Session->User = User = LookupCall(user); + + if (User) + { + // Check Password + + if (password[0] && strcmp(User->pass, password) == 0) + { + // send Message Index + + Session->WebMailLastUsed = time(NULL); + Session->WebMailSkip = 0; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + Session->WebMailMine = FALSE; + + if (WebMailTemplate) + { + free(WebMailTemplate); + WebMailTemplate = NULL; + } + + if (User->flags & F_Excluded) + { + n = sprintf_s(Msg, sizeof(Msg), "Webmail Connect from %s Rejected by Exclude Flag", _strupr(user)); + WriteLogLine(NULL, '|',Msg, n, LOG_BBS); + ReplyLen = sprintf(Reply, WebMailSignon, BBSName, BBSName); + *RLen = ReplyLen; + return; + } + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + n=sprintf_s(Msg, sizeof(Msg), "Webmail Connect from %s", _strupr(user)); + WriteLogLine(NULL, '|',Msg, n, LOG_BBS); + + return; + } + + } + + // Bad User or Pass + + ReplyLen = sprintf(Reply, WebMailSignon, BBSName, BBSName); + *RLen = ReplyLen; + return; + } + } + + Session->WebMail->Reply = Reply; + Session->WebMail->RLen = RLen; + + if (_stricmp(NodeURL, "/WebMail/EMSave") == 0) + { + // Save New Message + + SaveNewMessage(Session, input, Reply, RLen, Key, InputLen); + return; + } + + if (_stricmp(NodeURL, "/WebMail/Submit") == 0) + { + // Get the POST data from the page and place in message + + char * param = strstr(input, "\r\n\r\n"); // End of headers + WebMailInfo * WebMail = Session->WebMail; + + if (WebMail == NULL) + return; // Can't proceed if we have no info on form + + ProcessFormInput(Session, input, Reply, RLen, InputLen); + return; + } + + if (_stricmp(NodeURL, "/WebMail/FormMsgSave") == 0) + { + // Save New Message + + SaveTemplateMessage(Session, input, Reply, RLen, Key); + return; + } + + if (_stricmp(NodeURL, "/WebMail/GetTemplates") == 0) + { + SendTemplateSelectScreen(Session, input, InputLen); + return; + } + + // End of POST section + } + + if (_stricmp(NodeURL, "/WebMail/WMLogout") == 0) + { + Session->Key[0] = 0; + Session->WebMailLastUsed = 0; + ReplyLen = sprintf(Reply, WebMailSignon, BBSName, BBSName); + *RLen = ReplyLen; + return; + } + + if ((_stricmp(NodeURL, "/WebMail/MailEntry") == 0) || + (_stricmp(NodeURL, "/WebMail") == 0) || + (_stricmp(NodeURL, "/WebMail/") == 0)) + { + // Entry from Menu if signed in, continue. If not and Localhost + // signin as sysop. + + if (Session->User == NULL) + { + // Not yet signed in + + if (LOCAL) + { + // Webmail Gets Here with a dummy Session + + Session = AllocateWebMailSession(); + Session->WebMail->Reply = Reply; + Session->WebMail->RLen = RLen; + + Key = Session->Key; + + if (SYSOPCall[0]) + Session->User = LookupCall(SYSOPCall); + else + Session->User = LookupCall(BBSName); + + if (Session->User) + { + strcpy(NodeURL, "/WebMail/WebMail"); + Session->WebMailSkip = 0; + Session->WebMailLastUsed = time(NULL); + } + } + else + { + // Send Login Page + + ReplyLen = sprintf(Reply, WebMailSignon, BBSName, BBSName); + *RLen = ReplyLen; + return; + } + } + } + + Session->WebMail->Reply = Reply; + Session->WebMail->RLen = RLen; + + if (_stricmp(NodeURL, "/WebMail/WebMail") == 0) + { + if (WebMailTemplate) + { + free(WebMailTemplate); + WebMailTemplate = NULL; + } + + Session->WebMailSkip = 0; + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMAll") == 0) + { + Session->WebMailSkip = 0; + Session->WebMailTypes[0] = 0; + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMB") == 0) + { + Session->WebMailSkip = 0; + strcpy(Session->WebMailTypes, "B"); + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMP") == 0) + { + Session->WebMailSkip = 0; + strcpy(Session->WebMailTypes, "P"); + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMT") == 0) + { + Session->WebMailSkip = 0; + strcpy(Session->WebMailTypes, "T"); + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMMine") == 0) + { + Session->WebMailSkip = 0; + Session->WebMailTypes[0] = 0; + Session->WebMailMine = TRUE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMtoMe") == 0) + { + Session->WebMailSkip = 0; + Session->WebMailTypes[0] = 0; + Session->WebMailMine = FALSE; + Session->WebMailMyTX = FALSE; + Session->WebMailMyRX = TRUE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMfromMe") == 0) + { + Session->WebMailSkip = 0; + Session->WebMailTypes[0] = 0; + Session->WebMailMine = TRUE; + Session->WebMailMyTX = TRUE; + Session->WebMailMyRX = FALSE; + + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + + if (_stricmp(NodeURL, "/WebMail/WMSame") == 0) + { + *RLen = SendWebMailHeader(Reply, Session->Key, Session); + return; + } + + if (_stricmp(NodeURL, "/WebMail/WMAuto") == 0) + { + // Auto Refresh Version of index page. Uses Web Sockets + + char Page[4096]; + + char WebSockPage[] = + "\r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + "\r\n" + "\r\n" + + "\r\n" + "WebMail \r\n" + "\r\n" + + "\r\n" + "

%s Webmail Interface - User %s - Message List

\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "
BullsPersonalNTSAll TypesMineMy SentMy RxedAuto RefreshSend MessageLogoutNode Menu
\r\n" + "
\r\n" + + "
Waiting for data...
\r\n" + "\r\n"; + + sprintf(Page, WebSockPage, Key, Key ,BBSName, Session->User->Call, Key, Key, Key, Key, Key, Key, Key, Key, Key, Key); + + *RLen = sprintf(Reply, "%s", Page); + return; + } + + + if (memcmp(NodeURL, "/WebMail/QuoteOriginal/", 15) == 0) + { + // Reply to Message + + int n, len; + struct MsgInfo * Msg; + char Message[100] = ""; + char Title[100]; + char * MsgBytes, * Save, * NewBytes; + char * ptr; + char * ptr1, * ptr2; + char * EncodedTitle; + + n = Session->WebMail->CurrentMessageIndex; + + Msg = GetMsgFromNumber(n); + + if (Msg == NULL) + { + sprintf(Message, "Message %d not found", n); + *RLen = sprintf(Reply, "%s", Message); + return; + } + + Session->WebMail->Msg = Msg; + + if (stristr(Msg->title, "Re:") == 0) + sprintf(Title, "Re:%s", Msg->title); + else + sprintf(Title, "%s", Msg->title); + + MsgBytes = Save = ReadMessageFile(n); + + + ptr = NewBytes = malloc((Msg->length * 2) + 256); + + // Copy a line at a time with "> " in front of each + + ptr += sprintf(ptr, "%s", "\r\n\r\n\r\n\r\n\r\nOriginal Message\r\n\r\n> "); + + ptr1 = ptr2 = MsgBytes; + len = (int)strlen(MsgBytes); + + while (len-- > 0) + { + *ptr++ = *ptr1; + + if (*(ptr1) == '\n') + { + *ptr++ = '>'; + *ptr++ = ' '; + } + + ptr1++; + } + + *ptr++ = 0; + + EncodedTitle = doXMLTransparency(Msg->title); + + *RLen = sprintf(Reply, MsgInputPage, Key, Msg->from, "", EncodedTitle , NewBytes); + + free(EncodedTitle); + + free(MsgBytes); + free(NewBytes); + + return; + } + + + + if (memcmp(NodeURL, "/WebMail/Reply/", 15) == 0) + { + // Reply to Message + + int n = atoi(&NodeURL[15]); + struct MsgInfo * Msg; + char Message[100] = ""; + char Title[100]; + char * EncodedTitle; + + // Quote Original + + char Button[] = + "      " + ""; + + char Temp[1024]; + char ReplyAddr[128]; + + Msg = GetMsgFromNumber(n); + + if (Msg == NULL) + { + sprintf(Message, "Message %d not found", n); + *RLen = sprintf(Reply, "%s", Message); + return; + } + + Session->WebMail->Msg = Msg; + + // See if the message was displayed in an HTML form with a reply template + + *RLen = ReplyToFormsMessage(Session, Msg, Reply, FALSE); + + // If couldn't build reply form use normal text reply + + if (*RLen) + return; + + + sprintf(Temp, Button, Key); + + if (stristr(Msg->title, "Re:") == 0) + sprintf(Title, "Re:%s", Msg->title); + else + sprintf(Title, "%s", Msg->title); + + strcpy(ReplyAddr, Msg->from); + strcat(ReplyAddr, Msg->emailfrom); + + EncodedTitle = doXMLTransparency(Msg->title); + + *RLen = sprintf(Reply, MsgInputPage, Key, Msg->from, Temp, EncodedTitle , ""); + + free(EncodedTitle); + return; + } + + if (strcmp(NodeURL, "/WebMail/WM") == 0) + { + // Read Message + + int n = 0; + + if (URLParams) + n = atoi(URLParams); + + if (WebMailMsgTemplate) + free(WebMailMsgTemplate); + + WebMailMsgTemplate = GetTemplateFromFile(5, "WebMailMsg.txt"); + + *RLen = ViewWebMailMessage(Session, Reply, n, TRUE); + + return; + } + + if (strcmp(NodeURL, "/WebMail/WMPrev") == 0) + { + // Read Previous Message + + int m; + struct MsgInfo * Msg; + struct UserInfo * User = Session->User; + + + for (m = Session->WebMail->CurrentMessageIndex - 1; m >= 1; m--) + { + + Msg = GetMsgFromNumber(m); + + if (Msg == 0 || Msg->type == 0 || Msg->status == 0) + continue; // Protect against corrupt messages + + if (Msg && CheckUserMsg(Msg, User->Call, User->flags & F_SYSOP)) + { + // Display if it is the right type and in the page range we want + + if (Session->WebMailTypes[0] && strchr(Session->WebMailTypes, Msg->type) == 0) + continue; + + // All Types or right Type. Check Mine Flag + + if (Session->WebMailMine) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to) != 0 && strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyTX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyRX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to) != 0) + continue; + } + *RLen = ViewWebMailMessage(Session, Reply, m, TRUE); + + return; + } + } + + // No More + + *RLen = sprintf(Reply, "", Session->Key); + return; + + } + + if (strcmp(NodeURL, "/WebMail/WMNext") == 0) + { + // Read Previous Message + + int m; + struct MsgInfo * Msg; + struct UserInfo * User = Session->User; + + for (m = Session->WebMail->CurrentMessageIndex + 1; m <= LatestMsg; m++) + { + Msg = GetMsgFromNumber(m); + + if (Msg == 0 || Msg->type == 0 || Msg->status == 0) + continue; // Protect against corrupt messages + + if (Msg && CheckUserMsg(Msg, User->Call, User->flags & F_SYSOP)) + { + // Display if it is the right type and in the page range we want + + if (Session->WebMailTypes[0] && strchr(Session->WebMailTypes, Msg->type) == 0) + continue; + + // All Types or right Type. Check Mine Flag + + if (Session->WebMailMine) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to) != 0 && strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyTX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->from) != 0) + continue; + } + + if (Session->WebMailMyRX) + { + // Only list if to or from me + + if (strcmp(User->Call, Msg->to) != 0) + continue; + } + *RLen = ViewWebMailMessage(Session, Reply, m, TRUE); + + return; + } + } + + // No More + + *RLen = sprintf(Reply, "", Session->Key); + return; + + } + + + if (strcmp(NodeURL, "/WebMail/DisplayText") == 0) + { + // Read Message + + int n = 0; + + if (URLParams) + n = atoi(URLParams); + + if (WebMailMsgTemplate) + free(WebMailMsgTemplate); + + WebMailMsgTemplate = GetTemplateFromFile(5, "WebMailMsg.txt"); + + *RLen = ViewWebMailMessage(Session, Reply, n, FALSE); + + return; + } + if (memcmp(NodeURL, "/WebMail/WMDel/", 15) == 0) + { + // Kill Message + + int n = atoi(&NodeURL[15]); + + *RLen = KillWebMailMessage(Reply, Session->Key, Session->User, n); + + return; + } + + if (_stricmp(NodeURL, "/WebMail/NewMsg") == 0) + { + // Add HTML Template Button if we have any HTML Form + + char Button[] = + "      " + ""; + + char Temp[1024]; + + FreeWebMailFields(Session->WebMail); // Tidy up for new message + + sprintf(Temp, Button, Key); + + if (FormDirCount == 0) + *RLen = sprintf(Reply, MsgInputPage, Key, "", "", "", ""); + else + *RLen = sprintf(Reply, MsgInputPage, Key, "", Temp, "", ""); + + return; + } + + if (_memicmp(NodeURL, "/WebMail/GetPage/", 17) == 0) + { + // Read and Parse Template File + + GetPage(Session, NodeURL); + return; + } + + if (_memicmp(NodeURL, "/WebMail/GetList/", 17) == 0) + { + // Send Select Template Popup + + char * SubDir; + int DirNo = 0; + int SubDirNo = 0; + char popup[10000]; + + char popuphddr[] = + + "" + "" + "

" + "Select Required Template from %s

" + "

"); + + *RLen = sprintf(Reply, "%s", popup); + return; + } + + if (_stricmp(NodeURL, "/WebMail/DL") == 0) + { + getAttachmentList(Session, Reply, RLen, URLParams); + return; + } + + if (_stricmp(NodeURL, "/WebMail/GetDownLoad") == 0) + { + DownloadAttachments(Session, Reply, RLen, URLParams); + return; + } + + if (_stricmp(NodeURL, "/WebMail/DoSelect") == 0) + { + // User has selected item from Template "; + + char NewGroup [] = + "" + "

"); + + *WebMail->RLen = sprintf(WebMail->Reply, "%s", popup); + + free(Boundary); + return; +} + +static char WinlinkAddr[] = "WINLINK.ORG"; + +VOID SaveNewMessage(struct HTTPConnectionInfo * Session, char * MsgPtr, char * Reply, int * RLen, char * Rest, int InputLen) +{ + int i, ReplyLen = 0; + struct MsgInfo * Msg; + FILE * hFile; + int Template=0; + char * via = NULL; + BIDRec * BIDRec; + char MsgFile[MAX_PATH]; + size_t WriteLen=0; + char * HDest; + char * HDestCopy; + char * HDestRest; + char * Vptr = NULL; + char * FileList = NULL; + char Prompt[256] = "Message Saved"; + char OrigTo[256]; + WebMailInfo * WebMail = Session->WebMail; + struct UserInfo * user; + CIRCUIT conn; + + // So we can have attachments input is now Content-Type: multipart/form-data; + + char * Input; + size_t MsgLen = 0; + char * Boundary; + + strcpy(conn.Callsign, Session->User->Call); + + Input = MsgPtr; + + Boundary = initMultipartUnpack(&Input); + + if (Boundary == NULL) + return; // Can't work without one + + // Input points to start of part. Normally preceeded by \r\n which is Boundary Terminator. If preceeded by -- we have used last part + + while(Input && Input[-1] != '-') + { + char * Name, * Value; + int ValLen; + + if (unpackPart(Boundary, &Input, &Name, &Value, &ValLen, MsgPtr + InputLen) == FALSE) + { + // ReportCorrupt(WebMail); + free(Boundary); + return; + } + if (SaveInputValue(WebMail, Name, Value, ValLen) == FALSE) + { + *RLen = sprintf(Reply, "", Session->Key); + return; + } + } + + + if (WebMail->txtFileName) + { + // Processing Form Input + + SaveTemplateMessage(Session, MsgPtr, Reply, RLen, Rest); + + // Prevent re-entry + + free(WebMail->txtFileName); + WebMail->txtFileName = NULL; + + return; + } + + // If we aren't using a template then all the information is in the WebMail fields, as we haven't been here before. + + strlop(WebMail->BID, ' '); + if (strlen(WebMail->BID) > 12) + WebMail->BID[12] = 0; + + UndoTransparency(WebMail->BID); + UndoTransparency(WebMail->To); + UndoTransparency(WebMail->Subject); + UndoTransparency(WebMail->Body); + + MsgLen = strlen(WebMail->Body); + + // We will need to mess about with To field. Create a copy so the original can go in B2 header if we use one + + if (WebMail->To[0] == 0) + { + *RLen = sprintf(Reply, "%s", ""); + FreeWebMailFields(WebMail); // We will reprocess message and attachments so reinitialise + return; + } + + if (strlen(WebMail->To) > 255) + { + *RLen = sprintf(Reply, "%s", ""); + FreeWebMailFields(WebMail); // We will reprocess message and attachments so reinitialise + return; + } + + HDest = _strdup(WebMail->To); + + if (strlen(WebMail->BID)) + { + if (LookupBID(WebMail->BID)) + { + // Duplicate bid + *RLen = sprintf(Reply, "%s", ""); + FreeWebMailFields(WebMail); // We will reprocess message and attachments so reinitialise + return; + } + } + + if (WebMail->Type == 'B') + { + if (RefuseBulls) + { + *RLen = sprintf(Reply, "%s", ""); + FreeWebMailFields(WebMail); // We will reprocess message and attachments so reinitialise + return; + } + } + + // ?? Can we just loop though the rest of the code to allow multiple dests ?? + + HDestCopy = HDest; + + while (HDest && HDest[0]) + { + HDestRest = strlop(HDest, ';'); + + Msg = AllocateMsgRecord(); + + // Set number here so they remain in sequence + + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + strcpy(Msg->from, Session->User->Call); + + if (_memicmp(HDest, "rms:", 4) == 0 || _memicmp(HDest, "rms/", 4) == 0) + { + Vptr=&HDest[4]; + strcpy(Msg->to, "RMS"); + } + else if (_memicmp(HDest, "smtp:", 5) == 0) + { + if (ISP_Gateway_Enabled) + { + Vptr=&HDest[5]; + Msg->to[0] = 0; + } + } + else if (strchr(HDest, '@')) + { + strcpy(OrigTo, HDest); + + Vptr = strlop(HDest, '@'); + + if (Vptr) + { + // If looks like a valid email address, treat as such + + if (strlen(HDest) > 6 || !CheckifPacket(Vptr)) + { + // Assume Email address + + Vptr = OrigTo; + + if (FindRMS() || strchr(Vptr, '!')) // have RMS or source route + strcpy(Msg->to, "RMS"); + else if (ISP_Gateway_Enabled) + Msg->to[0] = 0; + else if (isAMPRMsg(OrigTo)) + strcpy(Msg->to, "RMS"); // Routing will redirect it + else + { + *RLen = sprintf(Reply, "%s", ""); + FreeWebMailFields(WebMail); // We will reprocess message and attachments so reinitialise + return; + + } + } + else + strcpy(Msg->to, _strupr(HDest)); + } + } + else + { + // No @ + + if (strlen(HDest) > 6) + HDest[6] = 0; + + strcpy(Msg->to, _strupr(HDest)); + } + + if (SendBBStoSYSOPCall) + if (_stricmp(HDest, BBSName) == 0) + strcpy(Msg->to, SYSOPCall); + + if (Vptr) + { + if (strlen(Vptr) > 40) + Vptr[40] = 0; + + strcpy(Msg->via, _strupr(Vptr)); + } + else + { + // No via. If not local user try to add BBS + + struct UserInfo * ToUser = LookupCall(Msg->to); + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->flags & F_RMSREDIRECT) + { + // sent to Winlink + + strcpy(Msg->via, WinlinkAddr); + sprintf(Prompt, "Redirecting to winlink.org\r"); + } + else if (ToUser->HomeBBS[0]) + { + strcpy(Msg->via, ToUser->HomeBBS); + sprintf(Prompt, "%s added from HomeBBS. Message Saved", Msg->via); + } + } + else + { + // Not local user - Check WP + + WPRecP WP = LookupWP(Msg->to); + + if (WP) + { + strcpy(Msg->via, WP->first_homebbs); + sprintf(Prompt, "%s added from WP", Msg->via); + } + } + } + + if (strlen(WebMail->Subject) > 60) + WebMail->Subject[60] = 0; + + strcpy(Msg->title, WebMail->Subject); + Msg->type = WebMail->Type; + + if (Session->User->flags & F_HOLDMAIL) + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Msg->status = 'H'; + + Length = sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, "User has Hold Messages flag set"); + SendMessageToSYSOP(Title, MailBuffer, Length); + } + else + Msg->status = 'N'; + + if (strlen(WebMail->BID) == 0) + sprintf(Msg->bid, "%d_%s", LatestMsg, BBSName); + else + strcpy(Msg->bid, WebMail->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); + + Msg->length = (int)MsgLen + WebMail->HeaderLen + WebMail->FooterLen; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + // BuildFormMessage(Session, Msg); + + if (WebMail->Files) + { + // Send as B2 + + char * B2Header = BuildB2Header(WebMail, Msg, NULL, 0); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(B2Header, 1, strlen(B2Header), hFile); + WriteLen += fwrite(WebMail->Header, 1, WebMail->HeaderLen, hFile); + WriteLen += fwrite(WebMail->Body, 1, MsgLen, hFile); + WriteLen += fwrite(WebMail->Footer, 1, WebMail->FooterLen, hFile); + WriteLen += fwrite("\r\n", 1, 2, hFile); + + for (i = 0; i < WebMail->Files; i++) + { + WriteLen += fwrite(WebMail->FileBody[i], 1, WebMail->FileLen[i], hFile); + WriteLen += fwrite("\r\n", 1, 2, hFile); + } + fclose(hFile); + free(B2Header); + + Msg->length = (int)WriteLen; + } + } + else + { + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen += fwrite(WebMail->Header, 1, WebMail->HeaderLen, hFile); + WriteLen += fwrite(WebMail->Body, 1, MsgLen, hFile); + WriteLen += fwrite(WebMail->Footer, 1, WebMail->FooterLen, hFile); + fclose(hFile); + } + } + MatchMessagetoBBSList(Msg, &conn); + + BuildNNTPList(Msg); // Build NNTP Groups list + + if (Msg->status != 'H' && Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + + + if (EnableUI) + SendMsgUI(Msg); + + user = LookupCall(Msg->to); + + // If Event Notifications enabled report a new message event + + SendNewMessageEvent(user->Call, Msg); + +#ifndef NOMQTT + if (MQTT) + MQTTMessageEvent(Msg); +#endif + + 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); + } + + HDest = HDestRest; + } + + *RLen = SendWebMailHeaderEx(Reply, Session->Key, Session, Prompt); + + SaveMessageDatabase(); + SaveBIDDatabase(); + FreeWebMailFields(WebMail); + free(HDestCopy); + + return; +} + + + + + + +// RMS Express Forms Support + +char * GetHTMLViewerTemplate(char * FN, struct HtmlFormDir ** FormDir) +{ + int i, j, k, l; + + // Seach list of forms for base file (without .html) + + for (i = 0; i < FormDirCount; i++) + { + struct HtmlFormDir * Dir = HtmlFormDirs[i]; + + for (j = 0; j < Dir->FormCount; j++) + { + if (strcmp(FN, Dir->Forms[j]->FileName) == 0) + { + *FormDir = Dir; + return CheckFile(Dir, FN); + } + } + + if (Dir->DirCount) + { + for (l = 0; l < Dir->DirCount; l++) + { + struct HtmlFormDir * SDir = Dir->Dirs[l]; + + if (SDir->DirCount) + { + struct HtmlFormDir * SSDir = SDir->Dirs[0]; + int x = 1; + } + + for (k = 0; k < SDir->FormCount; k++) + { + if (_stricmp(FN, SDir->Forms[k]->FileName) == 0) + { + *FormDir = SDir; + return CheckFile(SDir, SDir->Forms[k]->FileName); + } + } + if (SDir->DirCount) + { + struct HtmlFormDir * SSDir = SDir->Dirs[0]; + int x = 1; + } + } + } + } + + return NULL; +} +VOID GetReply(struct HTTPConnectionInfo * Session, char * NodeURL) +{ +} + +VOID GetPage(struct HTTPConnectionInfo * Session, char * NodeURL) +{ + // Read the HTML Template file and do any needed substitutions + + WebMailInfo * WebMail = Session->WebMail; + KeyValues * txtKey = WebMail->txtKeys; + + int DirNo; + char * ptr; + int FileNo = 0; + char * SubDir; + int SubDirNo; + int i; + struct HtmlFormDir * Dir; + char * Template; + char * inptr; + char FormDir[MAX_PATH] = ""; + char FN[MAX_PATH] = ""; + char * InputName = NULL; // HTML to input message + char * ReplyName = NULL; + char * To = NULL; + char * CC = NULL; + char * BID = NULL; + char Type = 0; + char * Subject = NULL; + char * MsgBody = NULL; + char * varptr; + char * endptr; + size_t varlen, vallen = 0; + char val[256]=""; // replacement text + char var[100] = "\""; + char * MsgBytes; + char Submit[64]; + + if (NodeURL == NULL) + { + //rentry after processing or substitutions + + // if Dir not specified search all for Filename + + if (Dir == NULL) + { + for (i = 0; i < FormDirCount; i++) + { + int n; + + Dir = HtmlFormDirs[i]; + + MsgBytes = CheckFile(Dir, WebMail->txtFileName); + if (MsgBytes) + goto gotFile; + + // Recurse any Subdirs + + n = 0; + while (n < Dir->DirCount) + { + MsgBytes = CheckFile(Dir->Dirs[n], FN); + if (MsgBytes) + { + Dir = Dir->Dirs[n]; + goto gotFile; + } + n++; + } + } + return; + } + else + MsgBytes = CheckFile(Dir, WebMail->txtFileName); + +gotFile: + + WebMail->Dir = Dir; + + if (WebMail->txtFile) + free(WebMail->txtFile); + + WebMail->txtFile = MsgBytes; + +reEnter: + + if (ParsetxtTemplate(Session, Dir, WebMail->txtFileName, FALSE) == FALSE) + { + // Template has or + + if (WebMail->InputHTMLName == NULL) + { + // This is a plain text template without HTML +/* + if (To == NULL) + To = ""; + + if (To[0] == 0 && WebMail->To && WebMail->To[0]) + To = WebMail->To; + + if (CC == NULL) + CC = ""; + + if (CC[0] == 0 && WebMail->CC && WebMail->CC[0]) + CC = WebMail->CC; + + if (Subject == NULL) + Subject = ""; + + if (Subject[0] == 0 && WebMail->Subject && WebMail->Subject[0]) + Subject = WebMail->Subject; + + if (MsgBody == NULL) + MsgBody = ""; + + if (MsgBody[0] == 0 && WebMail->Body && WebMail->Body[0]) + MsgBody = WebMail->Body; + + *WebMail->RLen = sprintf(WebMail->Reply, CheckFormMsgPage, Session->Key, To, CC, Subject, MsgBody); + */ + return *WebMail->RLen; + } + + Template = CheckFile(WebMail->Dir, WebMail->InputHTMLName); + + if (Template == NULL) + { + // Missing HTML + + *WebMail->RLen = sprintf(WebMail->Reply, "", WebMail->InputHTMLName); + return *WebMail->RLen; + } + + // I've going to update the template in situ, as I can't see a better way + // of making sure all occurances of variables in any order are substituted. + // The space allocated to Template is twice the size of the file + // to allow for insertions + + UpdateFormAction(Template, Session->Key); // Update "Submit" Action + + // Search for "{var }" strings in form and replace with + // corresponding variable from XML + + while (txtKey->Key) + { + char Key[256] = "{var "; + + strcpy(&Key[5], txtKey->Key); + strcat(Key, "}"); + + inptr = Template; + varptr = stristr(inptr, Key); + + while (varptr) + { + // Move the remaining message up/down the buffer to make space for substitution + + varlen = (int)strlen(Key); + if (txtKey->Value) + vallen = (int)strlen(txtKey->Value); + else vallen = 0; + + endptr = varptr + varlen; + + memmove(varptr + vallen, endptr, strlen(endptr) + 1); // copy null on end + memcpy(varptr, txtKey->Value, vallen); + + inptr = endptr + 1; + + varptr = stristr(inptr, Key); + } + txtKey++; + } + + // Remove from end as we add it on later + + ptr = stristr(Template, ""); + + if (ptr) + *ptr = 0; + + Len = sprintf(Reply, "%s", Template); + free(Template); + return Len; +} + +char * CheckFile(struct HtmlFormDir * Dir, char * FN) +{ + struct stat STAT; + FILE * hFile; + char MsgFile[MAX_PATH]; + char * MsgBytes; + int ReadLen; + int FileSize; + +#ifndef WIN32 + + // Need to do case insensitive file search + + DIR *dir; + struct dirent *entry; + char name[256]; + + sprintf(name, "%s/%s/%s", BPQDirectory, Dir->FormSet, Dir->DirName); + + if (!(dir = opendir(name))) + { + Debugprintf("cant open forms dir %s %s %d", Dir->DirName, name, errno); + return 0; + } + + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_type == DT_DIR) + continue; + + if (stricmp(entry->d_name, FN) == 0) + { + sprintf(MsgFile, "%s/%s/%s/%s", GetBPQDirectory(), Dir->FormSet, Dir->DirName, entry->d_name); + break; + } + } + closedir(dir); + +#else + + sprintf(MsgFile, "%s/%s/%s/%s", GetBPQDirectory(), Dir->FormSet, Dir->DirName, FN); + +#endif + + if (stat(MsgFile, &STAT) != -1) + { + hFile = fopen(MsgFile, "rb"); + + if (hFile == 0) + { + MsgBytes = _strdup("File is missing"); + return MsgBytes; + } + + FileSize = STAT.st_size; + MsgBytes = malloc(FileSize * 10); // Allow plenty of room for template substitution + ReadLen = (int)fread(MsgBytes, 1, FileSize, hFile); + MsgBytes[FileSize] = 0; + fclose(hFile); + + return MsgBytes; + } + return NULL; +} + +BOOL DoSelectPrompt(struct HTTPConnectionInfo * Session, char * Select) +{ + // Send a Popup window to select value. Reply handling code will update template then reenter ParsetxtTemplate + + char popuphddr[] = + + "" + "" + "
%s

" + "" + "
'); + + if (ptr) + *ptr = 0; + + ptr = SelCopy; + + if (*ptr == '"') + { + // String has " " round it + + ptr++; + + ptr1 = strchr(ptr, '"'); + if (ptr1 == NULL) + goto returnDuff; + + *(ptr1++) = 0; + prompt = ptr; + } + else + { + // Normal comma terminated + + ptr1 = strchr(ptr, ','); + if (ptr1 == NULL) + goto returnDuff; + + *(ptr1++) = 0; + prompt = ptr; + } + + ptr = ptr1; + + while (ptr && ptr[0]) + { + if (*ptr == '"') + { + // String has " " round it + + ptr++; + + ptr1 = strchr(ptr, '"'); + if (ptr1 == NULL) + goto returnDuff; + + *(ptr1++) = 0; + while(ptr1 && *ptr1 == ',') + ptr1++; + } + else + { + // Normal comma terminated + + ptr1 = strchr(ptr, ','); + if (ptr1) + *(ptr1++) = 0; + } + + var[vars++] = ptr; + + ptr = ptr1; + } + + len = sprintf(popup, popuphddr, Session->Key, prompt, vars + 1); + + for (i = 0; i < vars; i++) + { + char * key = strlop(var[i], '='); + + if (key == NULL) + key = var[i]; + + len += sprintf(&popup[len], "

"); + + *WebMail->RLen = sprintf(WebMail->Reply, "%s", popup); + free(SelCopy); + return TRUE; + +returnDuff: + *WebMail->RLen = sprintf(WebMail->Reply, "", Session->Key); + free(SelCopy); + return TRUE; + +} + +BOOL DoAskPrompt(struct HTTPConnectionInfo * Session, char * Select) +{ + return TRUE; +} + +VOID ProcessSelectResponse(struct HTTPConnectionInfo * Session, char * URLParams) +{ + // User has entered a response for a Template RLen = sprintf(WebMail->Reply, "", Session->Key); + return; + } + + varptr = Select; + endptr = strchr(Select, '>'); + + if (endptr == NULL) + { + *WebMail->RLen = sprintf(WebMail->Reply, "", Session->Key); + return; + } + + *endptr = 0; + varlen = endptr - varptr; + + + valptr = URLParams; + + // Move the remaining message up/down the buffer to make space for substitution + + vallen = strlen(valptr); + + endptr = varptr + varlen; + + memcpy(varptr, valptr, vallen); + memmove(varptr + vallen, endptr + 1, strlen(endptr + 1) + 1); // copy null on end + + if (WebMail->isReply) + *WebMail->RLen = ReplyToFormsMessage(Session, Session->Msg, WebMail->Reply, TRUE); + else + GetPage(Session, NULL); + + return ; +} + +BOOL ParsetxtTemplate(struct HTTPConnectionInfo * Session, struct HtmlFormDir * Dir, char * FN, BOOL isReply) +{ + WebMailInfo * WebMail = Session->WebMail; + KeyValues * txtKey = WebMail->txtKeys; + + char * MsgBytes; + + char * txtFile; + char * ptr, *ptr1; + char * InputName = NULL; // HTML to input message + char * ReplyName = NULL; + char * To = NULL; + char * Subject = NULL; + char * MsgBody = NULL; + char * Select = NULL; + char * Ask = NULL; + + char Date[16]; + char UDate[16]; + char DateTime[32]; + char UDateTime[32]; + char Day[16]; + char UDay[16]; + char UDTG[32]; + char Seq[16]; + char FormDir[MAX_PATH]; + double Lat; + double Lon; + char LatString[32], LonString[32], GPSString[32]; + BOOL GPSOK; + + struct tm * tm; + time_t NOW; + + // Template is now read before entering here + + MsgBytes = WebMail->txtFile; + + // if Template uses tm_year + 1900,tm->tm_mon + 1, tm->tm_mday); + + sprintf(DateTime, "%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); + + strcpy(Day, longday[tm->tm_wday]); + + tm = gmtime(&NOW); + + sprintf(UDate, "%04d-%02d-%02dZ", + tm->tm_year + 1900,tm->tm_mon + 1, tm->tm_mday); + + sprintf(UDateTime, "%04d-%02d-%02d %02d:%02d:%02dZ", + tm->tm_year + 100,tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + sprintf(UDTG, "%02d%02d%02dZ %s %04d", + tm->tm_mday, tm->tm_hour, tm->tm_min, month[tm->tm_mon], tm->tm_year + 1900); + + strcpy(UDay, longday[tm->tm_wday]); + + sprintf(Seq, "%d", Session->User->WebSeqNo); + sprintf(FormDir, "/WebMail/WMFile/%s/%s/", WebMail->Dir->FormSet, WebMail->Dir->DirName); + + // Keep SeqNo at front + + txtKey->Key = _strdup(""); + txtKey++->Value = _strdup(Seq); + + txtKey->Key = _strdup(""); + txtKey++->Value = _strdup(DateTime); + txtKey->Key = _strdup(""); + txtKey++->Value = _strdup(UDateTime); + txtKey->Key = _strdup(""); + txtKey++->Value = _strdup(Date); + txtKey->Key = _strdup(""); + txtKey++->Value = _strdup(UDate); + txtKey->Key = _strdup("