From 75b5bcc9a97b38c6c0c210bf14fabad9cadf704a Mon Sep 17 00:00:00 2001 From: g8bpq Date: Wed, 21 Jun 2023 08:21:04 +0100 Subject: [PATCH] 6.0.23.76 --- AGWAPI.c | 31 +- APRSCode.c | 2 +- ARDOP.c | 10 +- BBSUtilities.c | 665 +- BBSUtilities.c.bak | 14926 ++++++++++++++++++++ BPQMail.c | 14 +- BPQMail.c.bak | 3651 +++++ BPQMail.vcxproj | 346 + BPQMail.vcxproj.filters | 113 + BPQMail.vcxproj.user | 4 + BPQTermMDI.c | 6 - BPQtoAGW.c | 2 +- Bpq32.c | 13 +- CBPQ32.vcproj | 2 +- CHeaders.h | 6 +- ChatUtils.c | 4 +- Cmd.c | 31 +- CommonCode.c | 7 +- FLDigi.c | 12 +- FreeDATA.c | 22 +- GetVersion.h | 2 +- HFCommon.c | 2 +- HSMODEM.c | 16 +- HTTPcode.c | 2 +- HanksRT.c | 13 +- KAMPactor.c | 8 + KISSHF.c | 4 +- L2Code.c | 4 +- L4Code.c | 10 +- LinBPQ.c | 208 +- MailDataDefs.c | 1 + MailNode.vcproj.DESKTOP-TGEL8RC.John.user | 2 +- MailNode.vcxproj | 28 +- MailNode.vcxproj.filters | 324 + MailNode.vcxproj.user | 8 + MailTCP.c | 5 +- Moncode.c | 4 +- MultiConsole.c | 3 + PG/Loop.c | 24 + PG/Loop.exe | Bin 0 -> 77824 bytes PG/Loop.obj | Bin 0 -> 716 bytes PG/PGTest.c | 53 + PG/PGTest.exe | Bin 0 -> 103936 bytes PG/PGTest.obj | Bin 0 -> 2284 bytes RigControl.c | 8 +- SCSPactor.c | 12 +- SCSTracker.c | 10 +- SerialPort.c | 4 +- TNCEmulators.c | 541 +- TelnetV6.c | 26 +- UIARQ.c | 2 +- UZ7HODrv.c | 17 +- V4.c | 2 +- VARA.c | 13 +- Versions.h | 4 +- WINMOR.c | 12 +- WinRPR.c | 12 +- asmstrucs.h | 6 + bpqaxip.c | 4 +- bpqchat.c | 9 +- bpqchat.h | 5 +- bpqmail.h | 24 +- bpqmail.h.bak | 1604 +++ cMain.c | 24 + config.c | 26 +- configstructs.h | 6 + kiss.c | 55 +- kiss.h | 4 +- makefile | 2 +- rigcontrol.h | 7 +- telnetserver.h | 6 +- winstdint.h | 4 +- 72 files changed, 22574 insertions(+), 463 deletions(-) create mode 100644 BBSUtilities.c.bak create mode 100644 BPQMail.c.bak create mode 100644 BPQMail.vcxproj create mode 100644 BPQMail.vcxproj.filters create mode 100644 BPQMail.vcxproj.user create mode 100644 MailNode.vcxproj.filters create mode 100644 MailNode.vcxproj.user create mode 100644 PG/Loop.c create mode 100644 PG/Loop.exe create mode 100644 PG/Loop.obj create mode 100644 PG/PGTest.c create mode 100644 PG/PGTest.exe create mode 100644 PG/PGTest.obj create mode 100644 bpqmail.h.bak diff --git a/AGWAPI.c b/AGWAPI.c index dd85c48..c28d8f3 100644 --- a/AGWAPI.c +++ b/AGWAPI.c @@ -276,9 +276,9 @@ VOID Poll_AGW() } -SOCKADDR_IN local_sin; /* Local socket - internet style */ +static SOCKADDR_IN local_sin; /* Local socket - internet style */ -PSOCKADDR_IN psin; +static PSOCKADDR_IN psin; BOOL AGWAPIInit() @@ -343,19 +343,19 @@ BOOL AGWAPIInit() { if (PORT->Hide == 0) { - VisiblePortToRealPort[v++] = i - 1; - memcpy(ptr,"Port",4); - ptr += sprintf(ptr, "%d", i); - memcpy(ptr, " with ", 6); - ptr+=6; - memcpy(ptr, PORT->PORTDESCRIPTION, 29); // ";" - ptr+=29; - - while (*(--ptr) == ' ') {} + VisiblePortToRealPort[v++] = i - 1; + memcpy(ptr,"Port",4); + ptr += sprintf(ptr, "%d", i); + memcpy(ptr, " with ", 6); + ptr+=6; + memcpy(ptr, PORT->PORTDESCRIPTION, 29); // ";" + ptr+=29; - ptr++; + while (*(--ptr) == ' ') {} - *(ptr++)=';'; + ptr++; + + *(ptr++)=';'; } i++; PORT=PORT->PORTPOINTER; @@ -364,16 +364,15 @@ BOOL AGWAPIInit() *(ptr)=0; AGWMONVECPTR->HOSTAPPLFLAGS = 0x80; // Requext Monitoring - + return TRUE; } - int SetUpHostSessions() { int Stream, i; if (AGWMask == 0) return 0; - + for (i = 1; i <= AGWSessions; i++) { strcpy(pgm, "AGW"); diff --git a/APRSCode.c b/APRSCode.c index 1749795..67ab0c2 100644 --- a/APRSCode.c +++ b/APRSCode.c @@ -117,7 +117,7 @@ BOOL LogAPRSIS = FALSE; static char ConfigClassName[]="CONFIG"; -BPQVECSTRUC * APRSMONVECPTR; +extern BPQVECSTRUC * APRSMONVECPTR; extern int MONDECODE(); extern VOID * zalloc(int len); diff --git a/ARDOP.c b/ARDOP.c index d24f3db..01ee591 100644 --- a/ARDOP.c +++ b/ARDOP.c @@ -861,6 +861,14 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) // approx 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; + // Check session limit timer if ((STREAM->Connecting || STREAM->Connected) && !STREAM->Disconnecting) @@ -1867,7 +1875,7 @@ VOID ARDOPReleaseTNC(struct TNCINFO * TNC) } -VOID ARDOPSuspendPort(struct TNCINFO * TNC) +VOID ARDOPSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { ARDOPSendCommand(TNC, "LISTEN FALSE", TRUE); } diff --git a/BBSUtilities.c b/BBSUtilities.c index 87d21f0..487b6a9 100644 --- a/BBSUtilities.c +++ b/BBSUtilities.c @@ -222,10 +222,25 @@ extern BOOL EventsEnabled; extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; +/* --- TAJ PG Server--- */ + +void run_pg( CIRCUIT * conn, struct UserInfo * user ); +void startrun_pgThread( RUNPGARGS_PTR Args ); + +char * SERVERLIST[256][3]; + +int NUM_SERVERS = 0; + +/*------- TAJ END ----------*/ + + #ifdef LINBPQ extern BPQVECSTRUC ** BPQHOSTVECPTR; extern char WL2KModes [54][18]; + + #else + __declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; @@ -1020,6 +1035,20 @@ Next: } SortBBSChain(); + +/*------- TAJ PG SERVER ----------*/ + +#ifndef WIN32 + printf("Number of PG Servers = %d\n", NUM_SERVERS ); + + for ( int i=0; i< NUM_SERVERS; i++ ) { + printf("Server #%d\t%s\tExec->%s\tDESC->%s\n", i, SERVERLIST[i][0], SERVERLIST[i][1], SERVERLIST[i][2]); + } + + +#endif +/*------- TAJ END ----------*/ + } VOID CopyUserDatabase() @@ -9946,7 +9975,7 @@ BOOL GetConfig(char * ConfigName) return(EXIT_FAILURE); } -#if IBCONFIG_VER_MINOR > 4 +#if LIBCONFIG_VER_MINOR > 5 config_set_option(&cfg, CONFIG_OPTION_AUTOCONVERT, 1); #else config_set_auto_convert (&cfg, 1); @@ -10624,6 +10653,15 @@ int Disconnected (int Stream) conn->InputBufferLen = 0; } + /* ---- TAJ PG SERVER ---- */ + if ( conn->UserPointer->Temp->RUNPGPARAMS ) { + printf("Freeing RUNPGPARAMS\n"); + free(conn->UserPointer->Temp->RUNPGPARAMS); + conn->UserPointer->Temp->RUNPGPARAMS = NULL; + } + + /*------- TAJ END --------- */ + if (conn->InputMode == 'B') { // Save partly received message for a restart @@ -10738,6 +10776,17 @@ int DoReceivedData(int Stream) return 0; } + + /* ---------- TAJ START - PG server --------- */ + + if (conn->InputMode == 'P') // Inside PG Server + { + user = conn->UserPointer; + run_pg(conn, user); + return 0; + } + /* ---------- TAJ END --------- */ + if (conn->InputMode == 'B') { // if in OpenBCM mode, remove FF transparency @@ -11487,6 +11536,527 @@ extern UCHAR * infile; BOOL CheckforMIME(SocketConn * sockptr, char * Msg, char ** Body, int * MsgLen); +/* ---TAJ PG Server --- */ +#ifndef WIN32 + +#define verbose 1 +#define TRUE 1 +#define FALSE 0 +#include +#include + +typedef struct _POPENRET +{ + FILE * fp; + pid_t pid; +} POPENRET; + +/* +* Check if the PG is still running after 5 sec +* if so, kill it +*/ +void run_pgTimeoutThread( pid_t process ) +{ + + printf("watchdog thread: PID of subprocess: %d\n", process); + fflush(stdout); + Sleep(5000); + // if still running PID (?) then kill. + if ( getpgid(process) >= 0 ) { + printf("watchdog thread: Still running, so killing %d ... ", process); +// if ( kill( process, SIGTERM ) == 0 ) { + if ( kill( -process, SIGKILL ) == 0 ) { + printf("Killed\n"); + } else { + printf("Failed\n"); + } + } + printf("watchdog thread: PID=%d Exit\n", process); + fflush(stdout); + //return; +} + + +//https://sources.debian.org/src/cron/3.0pl1-45/popen.c/ + +POPENRET my_popen(char *program, char *type, CIRCUIT *conn) +{ + register char *cp; + FILE *iop; + int argc, pdes[2]; + pid_t pid; + POPENRET PRET; + + if (*type != 'r' && *type != 'w' || type[1]) + return(PRET); + if (pipe(pdes) < 0) + return(PRET); + + iop = NULL; + switch(pid = fork()) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + return PRET; + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != 1) { + dup2(pdes[1], 1); + dup2(pdes[1], 2); /* stderr, too! */ + (void)close(pdes[1]); + } + (void)close(pdes[0]); + } else { + if (pdes[0] != 0) { + dup2(pdes[0], 0); + (void)close(pdes[0]); + } + (void)close(pdes[1]); + } + setpgid(-pid,pid); + char *args[] = {"sh", "-c", program, NULL}; + execve("/bin/sh", args, NULL); + _exit(1); + } + + /* parent; assume fdopen can't fail... */ + + printf("PID=%d\n", pid ); + _beginthread((void (*)(void *))run_pgTimeoutThread, 0, (VOID *) pid ); + + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + char buffer[128]; + while (fgets(buffer, sizeof(buffer), iop) != NULL) { + BBSputs(conn, buffer); +// printf("%s", buffer); +// sleep(200); + buffer[0] = '\0'; + } +// BBSputs(conn,"\n"); + PRET.fp = iop; + PRET.pid= pid; +// (void)close(pdes[0]); +// (void)close(pdes[1]); + + return(PRET); +} + +int +my_pclose( POPENRET pret ) +{ + register int fdes; + sigset_t omask, mask; + int stat_loc; + pid_t pid; + FILE * iop = pret.fp; + + fdes = fileno(iop); + (void)fclose(iop); + + sigemptyset(&mask); + sigaddset(&mask, SIGQUIT); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGHUP); + sigprocmask(SIG_BLOCK, &mask, &omask); + pid = waitpid(pret.pid, &stat_loc, 0); + sigprocmask(SIG_SETMASK, &omask, NULL); + if (pid == -1 || !WIFEXITED(stat_loc)) + return -1; +// return WEXITSTATUS(stat_loc); + printf( "return = %d\n", WEXITSTATUS(stat_loc)); + return stat_loc; +} + + +int run_server (char **cmd, int nb_cmd, int mode, char *log, char *pgdir, char *data, CIRCUIT * conn) +{ + int i; + int ret = 0; + FILE *fp; + POPENRET PRET; + pid_t pid; + char *ptr; + char file[256]; + char buf[256]; + char dir[256]; + char arg[256]; + + if (mode) +// sprintf (file, " %s", log); +// sprintf (file, " >>%s", log); +// sprintf (file, " | tee -a %s", log); + sprintf(file, "" ); + else + sprintf (file, " > 8; + + if (verbose) { + printf ("Debug: command = {%s}\n", buf); + printf ("Debug: exit code = %d\n", ret); + } + + /* fail-safe bypass if filter executable isn't found (exit code 127) (was ret ==127)*/ + if (ret > 5) // should never be more than 5 + ret = 0; + } + return ( ret ); +} + + +void run_pg( CIRCUIT * conn, struct UserInfo * user ) +{ + + if (!user->Temp->RUNPGPARAMS) { +// printf("Allocating new RUNPGPARAMS\n"); + user->Temp->RUNPGPARAMS = (RUNPGARGS_PTR) zalloc(sizeof(RUNPGARGS)); + } + + user->Temp->RUNPGPARAMS->user = user; + user->Temp->RUNPGPARAMS->conn = conn; + strncpy(user->Temp->RUNPGPARAMS->InputBuffer, conn->InputBuffer, 80); // needs to be length of actual input! + user->Temp->RUNPGPARAMS->Len = conn->InputLen; + + if ( conn == 0 || user == 0 ) { + printf("run_pg null err\n"); + return; + } + + _beginthread((void (*)(void *))startrun_pgThread, 0, user->Temp->RUNPGPARAMS ); + + return; +} + + +void startrun_pgThread( RUNPGARGS_PTR Args ) { + + CIRCUIT * conn = Args->conn; + struct UserInfo * user = Args->user; + + char cmd[20]; + sprintf( cmd, "./%s", SERVERLIST[user->Temp->PG_SERVER][1] ); + char *ptr = cmd; + char pg_dir[] = "/home/pi/linbpq/linbpq/downloads/new/linbpq/pg/"; + char log_file[50] = "pg.log"; + char call[6]; + char data[80]; + char line[80]; + char *line_ptr = line; + int index; + char *data_ptr = data; + size_t bufsize = 80; + + strcpy( call, conn->UserPointer->Call); +// sprintf( log_file, "%s-%d.log", call, conn); + index = user->Temp->PG_INDEX; + + line[0] = '\0'; + int Len = Args->Len; + UCHAR * Msg = Args->InputBuffer; + strncpy( line, Msg, Len); + line[ Len - 1 ] = 0; //remove LF + + sprintf( data, "%s %d 0 0 %s", call, index, line); + + // clear the input queue + conn->InputLen = 0; + conn->InputBufferLen = 0; + + int ret = run_server (&ptr, 1, 1, log_file, pg_dir, data_ptr, conn); + + switch (ret) + { + case -1: // ERROR or forced closed + case 0: index=0; // Goodbye/Exit + conn->InputMode=0; + SendPrompt(conn, user); + break; + case 1: index++; // inc & keep in PG + break; + case 2: index=0; // disconnect + conn->InputMode=0; + Disconnect(conn->BPQStream); + break; + case 3: printf("data->BBS & end\n"); + break; + case 4: printf("data->BBS and inc %d\n", index++); + break; + case 5: printf("call no inc %d\n", ret); + break; + + } + + user->Temp->PG_INDEX=index; +} +/*---- TAJ END ----- */ + +#else + +#define BUFSIZE 4096 + +HANDLE g_hChildStd_IN_Rd = NULL; +HANDLE g_hChildStd_IN_Wr = NULL; +HANDLE g_hChildStd_OUT_Rd = NULL; +HANDLE g_hChildStd_OUT_Wr = NULL; + +HANDLE g_hInputFile = NULL; + +int CreateChildProcess(void); +void WriteToPipe(void); +void ReadFromPipe(void); + + +void run_pg( CIRCUIT * conn, struct UserInfo * user ) +{ + // Run PG program, rend anything from program's stdout to the user + + int retcode = -1; + SECURITY_ATTRIBUTES saAttr; + char szCmdline[256] = "C:\\test\\hello.exe g8bpq 0"; + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + BOOL bSuccess = FALSE; + DWORD dwRead, dwWritten; + CHAR chBuf[BUFSIZE]; + int index = 0; + int ret = 0; + + // if first entay allocate RUNPGPARAMS + if (!user->Temp->RUNPGPARAMS) + { + user->Temp->RUNPGPARAMS = (RUNPGARGS_PTR) zalloc(sizeof(RUNPGARGS)); + } + + user->Temp->RUNPGPARAMS->user = user; + user->Temp->RUNPGPARAMS->conn = conn; + strncpy(user->Temp->RUNPGPARAMS->InputBuffer, conn->InputBuffer, 80); // needs to be length of actual input! + user->Temp->RUNPGPARAMS->Len = conn->InputLen; + index = user->Temp->PG_INDEX; + + conn->InputBuffer[conn->InputLen] = 0; + strlop(conn->InputBuffer, 13); + conn->InputLen = 0; + + // Build command line. Parmas are: + + // - Callsign (format as F6FBB-8). + // - Level number (0 is the first time, up to 99). + // - Flags of the user (binary number as user`s mask of INIT.SRV). + // - Record number of the user in INF.SYS. + // - Received data (each word is a new argument). + + // BPQ doesn't support params 3 and 4 (but may supply copy of user record later) + + sprintf(szCmdline, "%s/PG/%s %s %d 0 0 %s", BaseDir, + SERVERLIST[user->Temp->PG_SERVER][1], user->Call, index, conn->InputBuffer); + + // Set the bInheritHandle flag so pipe handles are inherited. + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDOUT. + + if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) + return; + + // Ensure the read handle to the pipe for STDOUT is not inherited. + + if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ) + return; + + // Create the child process. + + + // Set up members of the PROCESS_INFORMATION structure. + + ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); + + // Set up members of the STARTUPINFO structure. + // This structure specifies the STDIN and STDOUT handles for redirection. + + ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = g_hChildStd_OUT_Wr; + siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; + siStartInfo.hStdInput = g_hChildStd_IN_Rd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + + bSuccess = CreateProcess(NULL, + szCmdline, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + + // If an error occurs, exit the application. + + if (!bSuccess) + retcode = -1; + else + { + // Wait until child process exits. + + if (WaitForSingleObject(piProcInfo.hProcess, 5000) == 0) // Wait max 5 seconds + { + // Success + + GetExitCodeProcess(piProcInfo.hProcess, &retcode); + } + else + { + // Failed or ran too long - kill + + TerminateProcess(piProcInfo.hProcess, 0); + } + + // Close handles to the child process and its primary thread. + // Some applications might keep these handles to monitor the status + // of the child process, for example. + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + + // Close handles to the stdin and stdout pipes no longer needed by the child process. + // If they are not explicitly closed, there is no way to recognize that the child process has ended. + + CloseHandle(g_hChildStd_OUT_Wr); + + // Send output to User + + for (;;) + { + bSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL); + if( ! bSuccess || dwRead == 0 ) break; + + chBuf[dwRead] = 0; + + if (retcode == 4) + ProcessLine(conn, user, chBuf, dwRead); + else + BBSputs(conn, chBuf); + + if (! bSuccess ) break; + } + } + + + switch (retcode) + { + case -1: // ERROR or forced closed + + BBSputs(conn, "Problem running PG program\r"); + index=0; + conn->InputMode=0; + SendPrompt(conn, user); + break; + + case 0: + + // Goodbye/Exit + + index=0; + conn->InputMode=0; + SendPrompt(conn, user); + break; + + case 1: + index++; // inc & keep in PG + break; + + case 2: + index=0; // disconnect + conn->InputMode=0; + Disconnect(conn->BPQStream); + break; + + case 3: + printf("data->BBS & end\n"); + break; + + case 4: + + // Send Output to BBS - was down above + break; + + case 5: + printf("call no inc %d\n", ret); + break; + } + + user->Temp->PG_INDEX=index; + + // The remaining open handles are cleaned up when this process terminates. + // To avoid resource leaks in a larger application, close handles explicitly. + + return; +} + + + +#endif + VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) { @@ -12293,6 +12863,55 @@ VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) return; } + /*---- TAJ PG Server ----- */ + + + if (_stricmp(Cmd, "PG") == 0) + { + if ( NUM_SERVERS == 0 ) + { + BBSputs(conn, "No PG Servers currently defined\r"); + SendPrompt(conn, user); + Flush(conn); + return; + } + + if ( !Arg1 ) + { + char reply[80]; + int i; + for (i=0; i< NUM_SERVERS; i++ ) + { + sprintf(reply, "%s -> %s\r", SERVERLIST[i][0], SERVERLIST[i][2]); + BBSputs(conn, reply); + } + SendPrompt(conn, user); + return; + } + else + { + int i; + for (i=0; i < NUM_SERVERS; i++ ) + { + if ( _stricmp( _strupr(Arg1), SERVERLIST[i][0] ) == 0 ) { + user->Temp->PG_SERVER = i; // index to server to run + user->Temp->PG_INDEX = 0; // newly starting PG + conn->InputMode = 'P'; // Inside PG Server + + // conn->InputBuffer is altered above and split into Cmd,Arg1,Context + // so put it back and call PG (removing PG) + sprintf( conn->InputBuffer, "%s", Context); + conn->InputLen = strlen(Context); + run_pg( conn, user ); + return; + } + } + } + + } + + /*---- TAJ END ---- */ + if (conn->Flags == 0) { if (!CheckForTooManyErrors(conn)) @@ -14924,3 +15543,47 @@ int decode_quoted_printable(char *ptr, int len) } return ptr2 - Start; } + + +VOID GetPGConfig() +{ + char FN[256]; + FILE *file; + char buf[256],errbuf[256]; + char * p_prog, * p_name, * p_desc; + int n = 0; + + + strcpy(FN, BaseDir); + strcat(FN, "/"); + strcat(FN, "PG/PGList.txt"); + + if ((file = fopen(FN, "r")) == NULL) + return; + + while(fgets(buf, 255, file) != NULL) + { + strcpy(errbuf,buf); // save in case of error + + p_prog = strtok(buf, ",\n\r"); + p_name = strtok(NULL, ",\n\r"); + p_desc = strtok(NULL, ",\n\r"); + + + if (p_desc && p_desc[0]) + { + while(*(p_name) == ' ') // Remove leading spaces + p_name++; + while(*(p_desc) == ' ') + p_desc++; + SERVERLIST[n][0] = _strdup(p_prog); + SERVERLIST[n][1] = _strdup(p_name); + SERVERLIST[n++][2] = _strdup(p_desc); + } + if (n > 255) + break; + } + + NUM_SERVERS = n; + fclose(file); +} diff --git a/BBSUtilities.c.bak b/BBSUtilities.c.bak new file mode 100644 index 0000000..b60c77d --- /dev/null +++ b/BBSUtilities.c.bak @@ -0,0 +1,14926 @@ +/* +Copyright 2001-2018 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + +// Mail and Chat Server for BPQ32 Packet Switch +// +// Utility Routines + +#include "bpqmail.h" +#ifdef WIN32 +#include "Winspool.h" +#else +#include +#endif + + +BOOL Bells; +BOOL FlashOnBell; // Flash instead of Beep +BOOL StripLF; + +BOOL WarnWrap; +BOOL FlashOnConnect; +BOOL WrapInput; +BOOL CloseWindowOnBye; + +RECT ConsoleRect; + +BOOL OpenConsole; +BOOL OpenMon; + +int reportNewMesageEvents = 0; + + +extern struct ConsoleInfo BBSConsole; + +extern char LOC[7]; + +//#define BBSIDLETIME 120 +//#define USERIDLETIME 300 + + +#define BBSIDLETIME 900 +#define USERIDLETIME 900 + +#ifdef LINBPQ +extern BPQVECSTRUC ** BPQHOSTVECPTR; +UCHAR * GetLogDirectory(); +DllExport int APIENTRY SessionStateNoAck(int stream, int * state); +int RefreshWebMailIndex(); +#else +__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; +typedef char * (WINAPI FAR *FARPROCZ)(); +typedef int (WINAPI FAR *FARPROCX)(); +FARPROCZ pGetLOC; +FARPROCX pRefreshWebMailIndex; + +#endif + +Dll BOOL APIENTRY APISendAPRSMessage(char * Text, char * ToCall); +VOID APIENTRY md5 (char *arg, unsigned char * checksum); +int APIENTRY GetRaw(int stream, char * msg, int * len, int * count); +void GetSemaphore(struct SEM * Semaphore, int ID); +void FreeSemaphore(struct SEM * Semaphore); +int EncryptPass(char * Pass, char * Encrypt); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); +void DeletetoRecycle(char * FN); +VOID DoImportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoExportCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID TidyPrompts(); +char * ReadMessageFileEx(struct MsgInfo * MsgRec); +char * APIENTRY GetBPQDirectory(); +BOOL SendARQMail(CIRCUIT * conn); +int APIENTRY ChangeSessionIdletime(int Stream, int idletime); +int APIENTRY GetApplNum(int Stream); +VOID FormatTime(char * Time, time_t cTime); +BOOL CheckifPacket(char * Via); +char * APIENTRY GetVersionString(); +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename); +void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); +int GetCMSHash(char * Challenge, char * Password); +BOOL SendAMPRSMTP(CIRCUIT * conn); +VOID ProcessMCASTLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen); +VOID MCastTimer(); +VOID MCastConTimer(ConnectionInfo * conn); +int FindFreeBBSNumber(); +VOID DoSetMsgNo(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +BOOL ProcessYAPPMessage(CIRCUIT * conn); +void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename); +void YAPPSendData(ConnectionInfo * conn); +VOID CheckBBSNumber(int i); +struct UserInfo * FindAMPR(); +VOID SaveInt64Value(config_setting_t * group, char * name, long long value); +VOID SaveIntValue(config_setting_t * group, char * name, int value); +VOID SaveStringValue(config_setting_t * group, char * name, char * value); +char *stristr (char *ch1, char *ch2); +BOOL CheckforMessagetoServer(struct MsgInfo * Msg); +void DoHousekeepingCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled); +void ListCategories(ConnectionInfo * conn); +void RebuildNNTPList(); +long long GetInt64Value(config_setting_t * group, char * name); +void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +int ReformatSyncMessage(CIRCUIT * conn); +char * initMultipartUnpack(char ** Input); +char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg); +int decode_quoted_printable(char *ptr, int len); +void decodeblock( unsigned char in[4], unsigned char out[3]); +int encode_quoted_printable(char *s, char * out, int Len); +int32_t Encode(char * in, char * out, int32_t inlen, BOOL B1Protocol, int Compress); +int APIENTRY ChangeSessionCallsign(int Stream, unsigned char * AXCall); + +config_t cfg; +config_setting_t * group; + +extern ULONG BBSApplMask; + +//static int SEMCLASHES = 0; + +char SecureMsg[80] = ""; // CMS Secure Signon Response + +int NumberofStreams; + +extern char VersionStringWithBuild[50]; + +#define MaxSockets 64 + +extern struct SEM OutputSEM; + +extern ConnectionInfo Connections[MaxSockets+1]; + +extern struct UserInfo ** UserRecPtr; +extern int NumberofUsers; + +extern struct UserInfo * BBSChain; // Chain of users that are BBSes + +extern struct MsgInfo ** MsgHddrPtr; +extern int NumberofMessages; + +extern int FirstMessageIndextoForward; // Lowest Message wirh a forward bit set - limits search + +extern char UserDatabaseName[MAX_PATH]; +extern char UserDatabasePath[MAX_PATH]; + +extern char MsgDatabasePath[MAX_PATH]; +extern char MsgDatabaseName[MAX_PATH]; + +extern char BIDDatabasePath[MAX_PATH]; +extern char BIDDatabaseName[MAX_PATH]; + +extern char WPDatabasePath[MAX_PATH]; +extern char WPDatabaseName[MAX_PATH]; + +extern char BadWordsPath[MAX_PATH]; +extern char BadWordsName[MAX_PATH]; + +extern char BaseDir[MAX_PATH]; +extern char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% +extern char ProperBaseDir[MAX_PATH]; // BPQ Directory/BPQMailChat + + +extern char MailDir[MAX_PATH]; + +extern BIDRec ** BIDRecPtr; +extern int NumberofBIDs; + +extern BIDRec ** TempBIDRecPtr; +extern int NumberofTempBIDs; + +extern WPRec ** WPRecPtr; +extern int NumberofWPrecs; + +extern char ** BadWords; +extern int NumberofBadWords; +extern char * BadFile; + +extern int LatestMsg; +extern struct SEM MsgNoSemaphore; // For locking updates to LatestMsg +extern int HighestBBSNumber; + +extern int MaxMsgno; +extern int BidLifetime; +extern int MaxAge; +extern int MaintInterval; +extern int MaintTime; + +extern int ProgramErrors; + +extern BOOL MonBBS; +extern BOOL MonCHAT; +extern BOOL MonTCP; + +BOOL SendNewUserMessage = TRUE; +BOOL AllowAnon = FALSE; +BOOL UserCantKillT = FALSE; + +typedef int (WINAPI FAR *FARPROCX)(); +FARPROCX pRunEventProgram; + +int RunEventProgram(char * Program, char * Param); + + +extern BOOL EventsEnabled; + +#define BPQHOSTSTREAMS 64 + +// Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 + +extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; + +#ifdef LINBPQ +extern BPQVECSTRUC ** BPQHOSTVECPTR; +extern char WL2KModes [54][18]; +#else +__declspec(dllimport) BPQVECSTRUC ** BPQHOSTVECPTR; + + +char WL2KModes [54][18] = { + "Packet 1200", "Packet 2400", "Packet 4800", "Packet 9600", "Packet 19200", "Packet 38400", "High Speed Packet", "", "", "", "", + "", "Pactor 1", "", "", "Pactor 2", "", "Pactor 3", "", "", "Pactor 4", // 10 - 20 + "Winmor 500", "Winmor 1600", "", "", "", "", "", "", "", // 21 - 29 + "Robust Packet", "", "", "", "", "", "", "", "", "", // 30 - 39 + "ARDOP 200", "ARDOP 500", "ARDOP 1000", "ARDOP 2000", "ARDOP 2000 FM", "", "", "", "", "", // 40 - 49 + "VARA", "VARA FM", "VARA FM WIDE", "VARA 500"}; +#endif + + + + + +FILE * LogHandle[4] = {NULL, NULL, NULL, NULL}; + +time_t LastLogTime[4] = {0, 0, 0, 0}; + +char FilesNames[4][100] = {"", "", "", ""}; + +char * Logs[4] = {"BBS", "CHAT", "TCP", "DEBUG"}; + + +BOOL OpenLogfile(int Flags) +{ + UCHAR FN[MAX_PATH]; + time_t LT; + struct tm * tm; + + LT = time(NULL); + tm = gmtime(<); + + sprintf(FN,"%s/logs/log_%02d%02d%02d_%s.txt", GetLogDirectory(), tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Logs[Flags]); + + LogHandle[Flags] = fopen(FN, "ab"); + +#ifndef WIN32 + + if (strcmp(FN, &FilesNames[Flags][0])) + { + UCHAR SYMLINK[MAX_PATH]; + + sprintf(SYMLINK,"%s/logLatest_%s.txt", GetBPQDirectory(), Logs[Flags]); + unlink(SYMLINK); + strcpy(&FilesNames[Flags][0], FN); + symlink(FN, SYMLINK); + } + +#endif + + return (LogHandle[Flags] != NULL); +} + +typedef int (WINAPI FAR *FARPROCX)(); + +extern FARPROCX pDllBPQTRACE; + +struct SEM LogSEM = {0, 0}; + +void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags) +{ + char CRLF[2] = {0x0d,0x0a}; + struct tm * tm; + char Stamp[20]; + time_t LT; +// struct _EXCEPTION_POINTERS exinfo; + + // Write to Node BPQTRACE system + + if ((Flags == LOG_BBS || Flags == LOG_DEBUG_X) && MsgLen < 250) + { + MESSAGE Monframe; + memset(&Monframe, 0, sizeof(Monframe)); + + Monframe.PORT = 64; + Monframe.LENGTH = 12 + MsgLen; + Monframe.DEST[0] = 1; // Plain Text Monitor + + memcpy(&Monframe.DEST[1], Msg, MsgLen); + Monframe.DEST[1 + MsgLen] = 0; + + time(&Monframe.Timestamp); +#ifdef LINBPQ + GetSemaphore(&Semaphore, 88); + BPQTRACE(&Monframe, FALSE); + FreeSemaphore(&Semaphore); +#else + if (pDllBPQTRACE) + pDllBPQTRACE(&Monframe, FALSE); +#endif + } +#ifndef LINBPQ + __try + { +#endif + + + +#ifndef LINBPQ + + if (hMonitor) + { + if (Flags == LOG_TCP && MonTCP) + { + WritetoMonitorWindow((char *)&Flag, 1); + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_CHAT && MonCHAT) + { + WritetoMonitorWindow((char *)&Flag, 1); + + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + WritetoMonitorWindow(call, 10); + } + else + WritetoMonitorWindow(" ", 10); + + WritetoMonitorWindow(Msg, MsgLen); + if (Msg[MsgLen-1] != '\r') + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_BBS && MonBBS) + { + WritetoMonitorWindow((char *)&Flag, 1); + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + WritetoMonitorWindow(call, 10); + } + else + WritetoMonitorWindow(" ", 10); + + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + else if (Flags == LOG_DEBUG_X) + { + WritetoMonitorWindow((char *)&Flag, 1); + WritetoMonitorWindow(Msg, MsgLen); + WritetoMonitorWindow(CRLF , 1); + } + } +#endif + + if (Flags == LOG_TCP && !LogTCP) + return; + if (Flags == LOG_BBS && !LogBBS) + return; + if (Flags == LOG_CHAT && !LogCHAT) + return; + + GetSemaphore(&LogSEM, 0); + + if (LogHandle[Flags] == NULL) + OpenLogfile(Flags); + + if (LogHandle[Flags] == NULL) + { + FreeSemaphore(&LogSEM); + return; + } + LT = time(NULL); + tm = gmtime(<); + + sprintf(Stamp,"%02d%02d%02d %02d:%02d:%02d %c", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, Flag); + + fwrite(Stamp, 1, strlen(Stamp), LogHandle[Flags]); + + if (conn && conn->Callsign[0]) + { + char call[20]; + sprintf(call, "%s ", conn->Callsign); + fwrite(call, 1, 10, LogHandle[Flags]); + } + else + fwrite(" ", 1, 10, LogHandle[Flags]); + + fwrite(Msg, 1, MsgLen, LogHandle[Flags]); + + if (Flags == LOG_CHAT && Msg[MsgLen-1] == '\r') + fwrite(&CRLF[1], 1, 1, LogHandle[Flags]); + else + fwrite(CRLF, 1, 2, LogHandle[Flags]); + + // Don't close/reopen logs every time + +// if ((LT - LastLogTime[Flags]) > 60) + { + LastLogTime[Flags] = LT; + fclose(LogHandle[Flags]); + LogHandle[Flags] = NULL; + } + FreeSemaphore(&LogSEM); + +#ifndef LINBPQ + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +#endif +} + +int CriticalErrorHandler(char * error) +{ + Debugprintf("Critical Error %s", error); + ProgramErrors = 25; + CheckProgramErrors(); // Force close + return 0; +} + +BOOL CheckForTooManyErrors(ConnectionInfo * conn) +{ + conn->ErrorCount++; + + if (conn->ErrorCount > 4) + { + BBSputs(conn, "Too many errors - closing\r"); + conn->CloseAfterFlush = 20; + return TRUE; + } + return FALSE; +} + + + + + +VOID __cdecl Debugprintf(const char * format, ...) +{ + char Mess[16384]; + va_list(arglist); + int Len; + + va_start(arglist, format); + Len = vsprintf(Mess, format, arglist); +#ifndef LINBPQ + WriteLogLine(NULL, '!',Mess, Len, LOG_DEBUG_X); +#endif + // #ifdef _DEBUG + strcat(Mess, "\r\n"); + OutputDebugString(Mess); + +// #endif + return; +} + +VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...) +{ + char Mess[1000]; + va_list(arglist);int Len; + + va_start(arglist, format); + Len = vsprintf(Mess, format, arglist); + WriteLogLine(conn, InOut, Mess, Len, LogMode); + + return; +} + +struct MsgInfo * GetMsgFromNumber(int msgno) +{ + if (msgno < 1 || msgno > 999999) + return NULL; + + return MsgnotoMsg[msgno]; +} + +struct UserInfo * AllocateUserRecord(char * Call) +{ + struct UserInfo * User = zalloc(sizeof (struct UserInfo)); + + strcpy(User->Call, Call); + User->Length = sizeof (struct UserInfo); + + GetSemaphore(&AllocSemaphore, 0); + + UserRecPtr=realloc(UserRecPtr,(++NumberofUsers+1) * sizeof(void *)); + UserRecPtr[NumberofUsers]= User; + + FreeSemaphore(&AllocSemaphore); + + return User; +} + +struct MsgInfo * AllocateMsgRecord() +{ + struct MsgInfo * Msg = zalloc(sizeof (struct MsgInfo)); + + GetSemaphore(&AllocSemaphore, 0); + + MsgHddrPtr=realloc(MsgHddrPtr,(++NumberofMessages+1) * sizeof(void *)); + MsgHddrPtr[NumberofMessages] = Msg; + + FreeSemaphore(&AllocSemaphore); + + return Msg; +} + +BIDRec * AllocateBIDRecord() +{ + BIDRec * BID = zalloc(sizeof (BIDRec)); + + GetSemaphore(&AllocSemaphore, 0); + + BIDRecPtr = realloc(BIDRecPtr,(++NumberofBIDs+1) * sizeof(void *)); + BIDRecPtr[NumberofBIDs] = BID; + + FreeSemaphore(&AllocSemaphore); + + return BID; +} + +BIDRec * AllocateTempBIDRecord() +{ + BIDRec * BID = zalloc(sizeof (BIDRec)); + + GetSemaphore(&AllocSemaphore, 0); + + TempBIDRecPtr=realloc(TempBIDRecPtr,(++NumberofTempBIDs+1) * sizeof(void *)); + TempBIDRecPtr[NumberofTempBIDs] = BID; + + FreeSemaphore(&AllocSemaphore); + + return BID; +} + +struct UserInfo * LookupCall(char * Call) +{ + struct UserInfo * ptr = NULL; + int i; + + for (i=1; i <= NumberofUsers; i++) + { + ptr = UserRecPtr[i]; + + if (_stricmp(ptr->Call, Call) == 0) return ptr; + + } + + return NULL; +} + +int GetNetInt(char * Line) +{ + char temp[1024]; + char * ptr = strlop(Line, ','); + int n = atoi(Line); + if (ptr == NULL) + Line[0] = 0; + else + { + strcpy(temp, ptr); + strcpy(Line, temp); + } + return n; +} + +VOID GetUserDatabase() +{ + struct UserInfo UserRec; + + FILE * Handle; + size_t ReadLen; + struct UserInfo * user; + time_t UserLimit = time(NULL) - (UserLifetime * 86400); // Oldest user to keep + int i; + + // See if user config is in main config + + group = config_lookup (&cfg, "BBSUsers"); + + if (group) + { + // We have User config in the main config file. so use that + + int index = 0; + char * stats; + struct MsgStats * Stats; + char * ptr, * ptr2; + + config_setting_t * entry = config_setting_get_elem (group, index++); + + // Initialise a new File + + UserRecPtr = malloc(sizeof(void *)); + UserRecPtr[0] = malloc(sizeof (struct UserInfo)); + memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); + UserRecPtr[0]->Length = sizeof (struct UserInfo); + + NumberofUsers = 0; + + while (entry) + { + char call[16]; + + // entry->name is call, will have * in front if a call stating woth number + + if (entry->name[0] == '*') + strcpy(call, &entry->name[1]); + else + strcpy(call, entry->name); + + user = AllocateUserRecord(call); + + ptr = entry->value.sval; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->Name, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->Address, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->HomeBBS, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->QRA, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->pass, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->ZIP, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) strcpy(user->CMSPass, ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->lastmsg = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->flags = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->PageLen = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->BBSNumber = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->RMSSSIDBits = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->WebSeqNo = atoi(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) user->TimeLastConnected = atol(ptr); + ptr = ptr2; + + ptr2 = strlop(ptr, '^'); + if (ptr) Stats = &user->Total; + stats = ptr; + + if (Stats == NULL) + { + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + + Stats->ConnectsIn = GetNetInt(stats); + Stats->ConnectsOut = GetNetInt(stats); + Stats->MsgsReceived[0] = GetNetInt(stats); + Stats->MsgsReceived[1] = GetNetInt(stats); + Stats->MsgsReceived[2] = GetNetInt(stats); + Stats->MsgsReceived[3] = GetNetInt(stats); + Stats->MsgsSent[0] = GetNetInt(stats); + Stats->MsgsSent[1] = GetNetInt(stats); + Stats->MsgsSent[2] = GetNetInt(stats); + Stats->MsgsSent[3] = GetNetInt(stats); + Stats->MsgsRejectedIn[0] = GetNetInt(stats); + Stats->MsgsRejectedIn[1] = GetNetInt(stats); + Stats->MsgsRejectedIn[2] = GetNetInt(stats); + Stats->MsgsRejectedIn[3] = GetNetInt(stats); + Stats->MsgsRejectedOut[0] = GetNetInt(stats); + Stats->MsgsRejectedOut[1] = GetNetInt(stats); + Stats->MsgsRejectedOut[2] = GetNetInt(stats); + Stats->MsgsRejectedOut[3] = GetNetInt(stats); + Stats->BytesForwardedIn[0] = GetNetInt(stats); + Stats->BytesForwardedIn[1] = GetNetInt(stats); + Stats->BytesForwardedIn[2] = GetNetInt(stats); + Stats->BytesForwardedIn[3] = GetNetInt(stats); + Stats->BytesForwardedOut[0] = GetNetInt(stats); + Stats->BytesForwardedOut[1] = GetNetInt(stats); + Stats->BytesForwardedOut[2] = GetNetInt(stats); + Stats->BytesForwardedOut[3] = GetNetInt(stats); + + Stats = &user->Last; + stats = ptr2; + + if (Stats == NULL) + { + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + + Stats->ConnectsIn = GetNetInt(stats); + Stats->ConnectsOut = GetNetInt(stats); + Stats->MsgsReceived[0] = GetNetInt(stats); + Stats->MsgsReceived[1] = GetNetInt(stats); + Stats->MsgsReceived[2] = GetNetInt(stats); + Stats->MsgsReceived[3] = GetNetInt(stats); + Stats->MsgsSent[0] = GetNetInt(stats); + Stats->MsgsSent[1] = GetNetInt(stats); + Stats->MsgsSent[2] = GetNetInt(stats); + Stats->MsgsSent[3] = GetNetInt(stats); + Stats->MsgsRejectedIn[0] = GetNetInt(stats); + Stats->MsgsRejectedIn[1] = GetNetInt(stats); + Stats->MsgsRejectedIn[2] = GetNetInt(stats); + Stats->MsgsRejectedIn[3] = GetNetInt(stats); + Stats->MsgsRejectedOut[0] = GetNetInt(stats); + Stats->MsgsRejectedOut[1] = GetNetInt(stats); + Stats->MsgsRejectedOut[2] = GetNetInt(stats); + Stats->MsgsRejectedOut[3] = GetNetInt(stats); + Stats->BytesForwardedIn[0] = GetNetInt(stats); + Stats->BytesForwardedIn[1] = GetNetInt(stats); + Stats->BytesForwardedIn[2] = GetNetInt(stats); + Stats->BytesForwardedIn[3] = GetNetInt(stats); + Stats->BytesForwardedOut[0] = GetNetInt(stats); + Stats->BytesForwardedOut[1] = GetNetInt(stats); + Stats->BytesForwardedOut[2] = GetNetInt(stats); + Stats->BytesForwardedOut[3] = GetNetInt(stats); + + + if ((user->flags & F_BBS) == 0) // Not BBS - Check Age + { + if (UserLifetime && user->TimeLastConnected) // Dont delete manually added Users that havent yet connected + { + if (user->TimeLastConnected < UserLimit) + { + // Too Old - ignore + + NumberofUsers--; + free(user); + entry = config_setting_get_elem (group, index++); + continue; + } + } + } + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (user->lastmsg < 0 || user->lastmsg > LatestMsg) + user->lastmsg = LatestMsg; + + + entry = config_setting_get_elem (group, index++); + } + } + else + { + Handle = fopen(UserDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memset(UserRecPtr[0], 0, sizeof (struct UserInfo)); + UserRecPtr[0]->Length = sizeof (struct UserInfo); + + NumberofUsers = 0; + + return; + } + + + // Get First Record + + ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&UserRec, 0, sizeof (struct UserInfo)); + UserRec.Length = sizeof (struct UserInfo); + } + else + { + // See if format has changed + + if (UserRec.Length == 0) + { + // Old format without a Length field + + struct OldUserInfo * OldRec = (struct OldUserInfo *)&UserRec; + int Users = OldRec->ConnectsIn; // User Count in control record + char Backup1[MAX_PATH]; + + // Create a backup in case reversion is needed and Reposition to first User record + + fclose(Handle); + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".oldformat"); + + CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak + + Handle = fopen(UserDatabasePath, "rb"); + + ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); // Skip Control Record + + // Set up control record + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); + UserRecPtr[0]->Length = sizeof (UserRec); + + NumberofUsers = 0; + +OldNext: + + ReadLen = fread(&UserRec, 1, (int)sizeof (struct OldUserInfo), Handle); + + if (ReadLen > 0) + { + if (OldRec->Call[0] < '0') + goto OldNext; // Blank record + + user = AllocateUserRecord(OldRec->Call); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + // Copy info from Old record + + user->lastmsg = OldRec->lastmsg; + user->Total.ConnectsIn = OldRec->ConnectsIn; + user->TimeLastConnected = OldRec->TimeLastConnected; + user->flags = OldRec->flags; + user->PageLen = OldRec->PageLen; + user->BBSNumber = OldRec->BBSNumber; + memcpy(user->Name, OldRec->Name, 18); + memcpy(user->Address, OldRec->Address, 61); + user->Total.MsgsReceived[0] = OldRec->MsgsReceived; + user->Total.MsgsSent[0] = OldRec->MsgsSent; + user->Total.MsgsRejectedIn[0] = OldRec->MsgsRejectedIn; // Messages we reject + user->Total.MsgsRejectedOut[0] = OldRec->MsgsRejectedOut; // Messages Rejectd by other end + user->Total.BytesForwardedIn[0] = OldRec->BytesForwardedIn; + user->Total.BytesForwardedOut[0] = OldRec->BytesForwardedOut; + user->Total.ConnectsOut = OldRec->ConnectsOut; // Forwarding Connects Out + user->RMSSSIDBits = OldRec->RMSSSIDBits; // SSID's to poll in RMS + memcpy(user->HomeBBS, OldRec->HomeBBS, 41); + memcpy(user->QRA, OldRec->QRA, 7); + memcpy(user->pass, OldRec->pass, 13); + memcpy(user->ZIP, OldRec->ZIP, 9); + + // Read any forwarding info, even if not a BBS. + // This allows a BBS to be temporarily set as a + // normal user without loosing forwarding info + + SetupForwardingStruct(user); + + if (user->flags & F_BBS) + { + // Defined as BBS - allocate and initialise forwarding structure + + // Add to BBS Chain; + + user->BBSNext = BBSChain; + BBSChain = user; + + // Save Highest BBS Number + + if (user->BBSNumber > HighestBBSNumber) HighestBBSNumber = user->BBSNumber; + } + goto OldNext; + } + + SortBBSChain(); + fclose(Handle); + + return; + } + } + + // Set up control record + + UserRecPtr=malloc(sizeof(void *)); + UserRecPtr[0]= malloc(sizeof (struct UserInfo)); + memcpy(UserRecPtr[0], &UserRec, sizeof (UserRec)); + UserRecPtr[0]->Length = sizeof (UserRec); + + NumberofUsers = 0; + +Next: + + ReadLen = fread(&UserRec, 1, (int)sizeof (UserRec), Handle); + + if (ReadLen > 0) + { + if (UserRec.Call[0] < '0') + goto Next; // Blank record + + if (UserRec.TimeLastConnected == 0) + UserRec.TimeLastConnected = UserRec.xTimeLastConnected; + + if ((UserRec.flags & F_BBS) == 0) // Not BBS - Check Age + if (UserLifetime) // if limit set + if (UserRec.TimeLastConnected) // Dont delete manually added Users that havent yet connected + if (UserRec.TimeLastConnected < UserLimit) + goto Next; // Too Old - ignore + + user = AllocateUserRecord(UserRec.Call); + memcpy(user, &UserRec, sizeof (UserRec)); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + user->ForwardingInfo = NULL; // In case left behind on crash + user->BBSNext = NULL; + user->POP3Locked = FALSE; + + if (user->lastmsg < 0 || user->lastmsg > LatestMsg) + user->lastmsg = LatestMsg; + + goto Next; + } + fclose(Handle); + } + + // Setting up BBS struct has been moved until all user record + // have been read so we can fix corrupt BBSNUmber + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + // Read any forwarding info, even if not a BBS. + // This allows a BBS to be temporarily set as a + // normal user without loosing forwarding info + + SetupForwardingStruct(user); + + if (user->flags & F_BBS) + { + // Add to BBS Chain; + + if (user->BBSNumber == NBBBS) // Fix corrupt records + { + user->BBSNumber = FindFreeBBSNumber(); + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; // cant really do much else + } + + user->BBSNext = BBSChain; + BBSChain = user; + +// Logprintf(LOG_BBS, NULL, '?', "BBS %s BBSNumber %d", user->Call, user->BBSNumber); + + // Save Highest BBS Number + + if (user->BBSNumber > HighestBBSNumber) + HighestBBSNumber = user->BBSNumber; + } + } + + // Check for dulicate BBS numbers + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_BBS) + { + if (user->BBSNumber == 0) + user->BBSNumber = FindFreeBBSNumber(); + + CheckBBSNumber(user->BBSNumber); + } + } + + SortBBSChain(); +} + +VOID CopyUserDatabase() +{ + return; // User config now in main config file +/* + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + + // Keep 4 Generations + + strcpy(Backup2, UserDatabasePath); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, UserDatabasePath); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, UserDatabasePath); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); //Move .bak to .bak.1 + + CopyFile(UserDatabasePath, Backup1, FALSE); // Copy to .bak +*/ +} + +VOID CopyConfigFile(char * ConfigName) +{ + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + + // Keep 4 Generations + + strcpy(Backup2, ConfigName); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, ConfigName); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, ConfigName); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, ConfigName); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); // Move .bak to .bak.1 + + CopyFile(ConfigName, Backup1, FALSE); // Copy to .bak +} + + + +VOID SaveUserDatabase() +{ + SaveConfig(ConfigName); // User config is now in main config file + GetConfig(ConfigName); + +/* + FILE * Handle; + size_t WriteLen; + int i; + + Handle = fopen(UserDatabasePath, "wb"); + + UserRecPtr[0]->Total.ConnectsIn = NumberofUsers; + + for (i=0; i <= NumberofUsers; i++) + { + WriteLen = fwrite(UserRecPtr[i], 1, (int)sizeof (struct UserInfo), Handle); + } + + fclose(Handle); +*/ + return; +} + +VOID GetMessageDatabase() +{ + struct MsgInfo MsgRec; + FILE * Handle; + size_t ReadLen; + struct MsgInfo * Msg; + char * MsgBytes; + int FileRecsize = sizeof(struct MsgInfo); // May be changed if reformating + BOOL Reformatting = FALSE; + char HEX[3] = ""; + int n; + + // See if Message Database is in main config + + group = config_lookup (&cfg, "MSGS"); + +// group = 0; + + if (group) + { + // We have User config in the main config file. so use that + + int index = 0; + char * ptr, * ptr2; + config_setting_t * entry = config_setting_get_elem (group, index++); + + // Initialise a new File + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); + NumberofMessages = 0; + MsgHddrPtr[0]->status = 2; + + if (entry) + { + // First Record has current message number + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + ptr2 = strlop(ptr2, '|'); + if (ptr2) + LatestMsg = atoi(ptr2); + } + + entry = config_setting_get_elem (group, index++); + + while (entry) + { + // entry->name is MsgNo with 'R' in front + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + + memset(&MsgRec, 0, sizeof(struct MsgInfo)); + + MsgRec.number = atoi(&entry->name[1]); + MsgRec.type = ptr[0]; + + ptr = ptr2; + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + ptr2 = strlop(ptr, '|'); + MsgRec.status = ptr[0]; + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.length = atoi(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datereceived = atol(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.bbsfrom, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.via, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.from, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.to, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.bid, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.B2Flags = atoi(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datecreated = atol(ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.datechanged = atol(ptr); + + ptr = ptr2; + if (ptr) ptr2 = strlop(ptr, '|'); + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + if (ptr[0]) + { + char String[50] = "00000000000000000000"; + String[20] = 0; + memcpy(String, ptr, strlen(ptr)); + for (n = 0; n < NBMASK; n++) + { + memcpy(HEX, &String[n * 2], 2); + MsgRec.fbbs[n] = (UCHAR)strtol(HEX, 0, 16); + } + } + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + + if (ptr == NULL) + { + entry = config_setting_get_elem (group, index++); + continue; + } + + if (ptr[0]) + { + char String[50] = "00000000000000000000"; + String[20] = 0; + memcpy(String, ptr, strlen(ptr)); + for (n = 0; n < NBMASK; n++) + { + memcpy(HEX, &String[n * 2], 2); + MsgRec.forw[n] = (UCHAR)strtol(HEX, 0, 16); + } + } + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) strcpy(MsgRec.emailfrom, ptr); + + ptr = ptr2; + ptr2 = strlop(ptr, '|'); + if (ptr) MsgRec.UTF8 = atoi(ptr); + + ptr = ptr2; + + if (ptr) + { + strcpy(MsgRec.title, ptr); + + MsgBytes = ReadMessageFileEx(&MsgRec); + + if (MsgBytes) + { + free(MsgBytes); + Msg = AllocateMsgRecord(); + memcpy(Msg, &MsgRec, sizeof (MsgRec)); + + MsgnotoMsg[Msg->number] = Msg; + + // Fix Corrupted NTS Messages + + if (Msg->type == 'N') + Msg->type = 'T'; + + // Look for corrupt FROM address (ending in @) + + strlop(Msg->from, '@'); + + BuildNNTPList(Msg); // Build NNTP Groups list + + // If any forward bits are set, increment count on corresponding BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + } + } + } + entry = config_setting_get_elem (group, index++); + } + + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + + return; + } + + Handle = fopen(MsgDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= zalloc(sizeof (MsgRec)); + NumberofMessages = 0; + MsgHddrPtr[0]->status = 2; + + return; + } + + // Get First Record + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&MsgRec, 0, sizeof (MsgRec)); + MsgRec.status = 2; + } + + // Set up control record + + MsgHddrPtr=malloc(sizeof(void *)); + MsgHddrPtr[0]= malloc(sizeof (MsgRec)); + memcpy(MsgHddrPtr[0], &MsgRec, sizeof (MsgRec)); + + LatestMsg=MsgHddrPtr[0]->length; + + NumberofMessages = 0; + + if (MsgRec.status == 1) // Used as file format version + // 0 = original, 1 = Extra email from addr, 2 = More BBS's. + { + char Backup1[MAX_PATH]; + + // Create a backup in case reversion is needed and Reposition to first User record + + fclose(Handle); + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".oldformat"); + + CopyFile(MsgDatabasePath, Backup1, FALSE); // Copy to .oldformat + + Handle = fopen(MsgDatabasePath, "rb"); + + FileRecsize = sizeof(struct OldMsgInfo); + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + MsgHddrPtr[0]->status = 2; + } + +Next: + + ReadLen = fread(&MsgRec, 1, FileRecsize, Handle); + + if (ReadLen > 0) + { + // Validate Header + + if (FileRecsize == sizeof(struct MsgInfo)) + { + if (MsgRec.type == 0 || MsgRec.number == 0) + goto Next; + + MsgBytes = ReadMessageFileEx(&MsgRec); + + if (MsgBytes) + { + // MsgRec.length = strlen(MsgBytes); + free(MsgBytes); + } + else + goto Next; + + Msg = AllocateMsgRecord(); + + memcpy(Msg, &MsgRec, +sizeof (MsgRec)); + } + else + { + // Resizing - record from file is an OldRecInfo + + struct OldMsgInfo * OldMessage = (struct OldMsgInfo *) &MsgRec; + + if (OldMessage->type == 0) + goto Next; + + if (OldMessage->number > 99999 || OldMessage->number < 1) + goto Next; + + Msg = AllocateMsgRecord(); + + + Msg->B2Flags = OldMessage->B2Flags; + memcpy(Msg->bbsfrom, OldMessage->bbsfrom, 7); + memcpy(Msg->bid, OldMessage->bid, 13); + Msg->datechanged = OldMessage->datechanged; + Msg->datecreated = OldMessage->datecreated; + Msg->datereceived = OldMessage->datereceived; + memcpy(Msg->emailfrom, OldMessage->emailfrom, 41); + memcpy(Msg->fbbs , OldMessage->fbbs, 10); + memcpy(Msg->forw , OldMessage->forw, 10); + memcpy(Msg->from, OldMessage->from, 7); + Msg->length = OldMessage->length; + Msg->nntpnum = OldMessage->nntpnum; + Msg->number = OldMessage->number; + Msg->status = OldMessage->status; + memcpy(Msg->title, OldMessage->title, 61); + memcpy(Msg->to, OldMessage->to, 7); + Msg->type = OldMessage->type; + memcpy(Msg->via, OldMessage->via, 41); + } + + MsgnotoMsg[Msg->number] = Msg; + + // Fix Corrupted NTS Messages + + if (Msg->type == 'N') + Msg->type = 'T'; + + // Look for corrupt FROM address (ending in @) + + strlop(Msg->from, '@'); + + // Move Dates if first run with new format + + if (Msg->datecreated == 0) + Msg->datecreated = Msg->xdatecreated; + + if (Msg->datereceived == 0) + Msg->datereceived = Msg->xdatereceived; + + if (Msg->datechanged == 0) + Msg->datechanged = Msg->xdatechanged; + + BuildNNTPList(Msg); // Build NNTP Groups list + + Msg->Locked = 0; // In case left locked + Msg->Defered = 0; // In case left set. + + // If any forward bits are set, increment count on corresponding BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + } + + goto Next; + } + + if (FirstMessageIndextoForward == 0) + FirstMessageIndextoForward = NumberofMessages; // limit search + + fclose(Handle); +} + +VOID CopyMessageDatabase() +{ + char Backup1[MAX_PATH]; + char Backup2[MAX_PATH]; + +// return; + + // Keep 4 Generations + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak.3"); + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".bak.2"); + + DeleteFile(Backup2); // Remove old .bak.3 + MoveFile(Backup1, Backup2); // Move .bak.2 to .bak.3 + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak.1"); + + MoveFile(Backup2, Backup1); // Move .bak.1 to .bak.2 + + strcpy(Backup1, MsgDatabasePath); + strcat(Backup1, ".bak"); + + MoveFile(Backup1, Backup2); //Move .bak to .bak.1 + + strcpy(Backup2, MsgDatabasePath); + strcat(Backup2, ".bak"); + + CopyFile(MsgDatabasePath, Backup2, FALSE); // Copy to .bak + +} + +VOID SaveMessageDatabase() +{ + FILE * Handle; + size_t WriteLen; + int i; + char Key[16]; + struct MsgInfo *Msg; +// char CfgName[MAX_PATH]; + char HEXString1[64]; + char HEXString2[64]; + int n; +// char * CfgBuffer; + char Cfg[1024]; +// int CfgLen = 0; +// FILE * hFile; + +// SaveConfig(ConfigName); // Message Headers now in main config +// return; + +#ifdef LINBPQ + RefreshWebMailIndex(); +#else + if (pRefreshWebMailIndex) + pRefreshWebMailIndex(); +#endif + + Handle = fopen(MsgDatabasePath, "wb"); + + if (Handle == NULL) + { + CriticalErrorHandler("Failed to open message database"); + return; + } + + MsgHddrPtr[0]->status = 2; + MsgHddrPtr[0]->number = NumberofMessages; + MsgHddrPtr[0]->length = LatestMsg; + + for (i=0; i <= NumberofMessages; i++) + { + WriteLen = fwrite(MsgHddrPtr[i], 1, sizeof (struct MsgInfo), Handle); + + if (WriteLen != sizeof(struct MsgInfo)) + { + CriticalErrorHandler("Failed to write message database record"); + return; + } + } + + if (fclose(Handle) != 0) + CriticalErrorHandler("Failed to close message database"); + + for (i = 1; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); + + n = 39; + while (n >=0 && HEXString1[n] == '0') + HEXString1[n--] = 0; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); + + n = 39; + while (n >= 0 && HEXString2[n] == '0') + HEXString2[n--] = 0; + + sprintf(Key, "R%d:\r\n", i); + + n = sprintf(Cfg, "%c|%c|%d|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, + Msg->number, Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], + &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, + &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); + } + + return; +} + +VOID GetBIDDatabase() +{ + BIDRec BIDRec; + FILE * Handle; + size_t ReadLen; + BIDRecP BID; + int index = 0; + char * ptr, * ptr2; + + // If BID info is in main config file, use it + + group = config_lookup (&cfg, "BIDS"); + + if (group) + { + config_setting_t * entry = config_setting_get_elem (group, index++); + + BIDRecPtr=malloc(sizeof(void *)); + BIDRecPtr[0]= malloc(sizeof (BIDRec)); + memset(BIDRecPtr[0], 0, sizeof (BIDRec)); + NumberofBIDs = 0; + + while (entry) + { + // entry->name is Bid with 'R' in front + + ptr = entry->value.sval; + ptr2 = strlop(ptr, '|'); + + if (ptr && ptr2) + { + BID = AllocateBIDRecord(); + strcpy(BID->BID, &entry->name[1]); + BID->mode = atoi(ptr); + BID->u.timestamp = atoi(ptr2); + + if (BID->u.timestamp == 0) + BID->u.timestamp = LOWORD(time(NULL)/86400); + + } + entry = config_setting_get_elem (group, index++); + } + return; + } + + Handle = fopen(BIDDatabasePath, "rb"); + + if (Handle == NULL) + { + // Initialise a new File + + BIDRecPtr=malloc(sizeof(void *)); + BIDRecPtr[0]= malloc(sizeof (BIDRec)); + memset(BIDRecPtr[0], 0, sizeof (BIDRec)); + NumberofBIDs = 0; + + return; + } + + + // Get First Record + + ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); + + if (ReadLen == 0) + { + // Duff file + + memset(&BIDRec, 0, sizeof (BIDRec)); + } + + // Set up control record + + BIDRecPtr = malloc(sizeof(void *)); + BIDRecPtr[0] = malloc(sizeof (BIDRec)); + memcpy(BIDRecPtr[0], &BIDRec, sizeof (BIDRec)); + + NumberofBIDs = 0; + +Next: + + ReadLen = fread(&BIDRec, 1, sizeof (BIDRec), Handle); + + if (ReadLen > 0) + { + BID = AllocateBIDRecord(); + memcpy(BID, &BIDRec, sizeof (BIDRec)); + + if (BID->u.timestamp == 0) + BID->u.timestamp = LOWORD(time(NULL)/86400); + + goto Next; + } + + fclose(Handle); +} + +VOID CopyBIDDatabase() +{ + char Backup[MAX_PATH]; + +// return; + + + strcpy(Backup, BIDDatabasePath); + strcat(Backup, ".bak"); + + CopyFile(BIDDatabasePath, Backup, FALSE); +} + +VOID SaveBIDDatabase() +{ + FILE * Handle; + size_t WriteLen; + int i; + +// return; // Bids are now in main config and are saved when message is saved + + Handle = fopen(BIDDatabasePath, "wb"); + + BIDRecPtr[0]->u.msgno = NumberofBIDs; // First Record has file size + + for (i=0; i <= NumberofBIDs; i++) + { + WriteLen = fwrite(BIDRecPtr[i], 1, sizeof (BIDRec), Handle); + } + + fclose(Handle); + + return; +} + +BIDRec * LookupBID(char * BID) +{ + BIDRec * ptr = NULL; + int i; + + for (i=1; i <= NumberofBIDs; i++) + { + ptr = BIDRecPtr[i]; + + if (_stricmp(ptr->BID, BID) == 0) + return ptr; + } + + return NULL; +} + +BIDRec * LookupTempBID(char * BID) +{ + BIDRec * ptr = NULL; + int i; + + for (i=1; i <= NumberofTempBIDs; i++) + { + ptr = TempBIDRecPtr[i]; + + if (_stricmp(ptr->BID, BID) == 0) return ptr; + } + + return NULL; +} + +VOID RemoveTempBIDS(CIRCUIT * conn) +{ + // Remove any Temp BID records for conn. Called when connection closes - Msgs will be complete or failed + + if (NumberofTempBIDs == 0) + return; + else + { + BIDRec * ptr = NULL; + BIDRec ** NewTempBIDRecPtr = zalloc((NumberofTempBIDs+1) * sizeof(void *)); + int i = 0, n; + + GetSemaphore(&AllocSemaphore, 0); + + for (n = 1; n <= NumberofTempBIDs; n++) + { + ptr = TempBIDRecPtr[n]; + + if (ptr) + { + if (ptr->u.conn == conn) + // Remove this entry + free(ptr); + else + NewTempBIDRecPtr[++i] = ptr; + } + } + + NumberofTempBIDs = i; + + free(TempBIDRecPtr); + + TempBIDRecPtr = NewTempBIDRecPtr; + FreeSemaphore(&AllocSemaphore); + } + +} + +VOID GetBadWordFile() +{ + FILE * Handle; + DWORD FileSize; + char * ptr1, * ptr2; + struct stat STAT; + + if (stat(BadWordsPath, &STAT) == -1) + return; + + FileSize = STAT.st_size; + + Handle = fopen(BadWordsPath, "rb"); + + if (Handle == NULL) + return; + + // Release old info in case a re-read + + if (BadWords) free(BadWords); + if (BadFile) free(BadFile); + + BadWords = NULL; + BadFile = NULL; + NumberofBadWords = 0; + + BadFile = malloc(FileSize+1); + + fread(BadFile, 1, FileSize, Handle); + + fclose(Handle); + + BadFile[FileSize]=0; + + _strlwr(BadFile); // Compares are case-insensitive + + ptr1 = BadFile; + + while (ptr1) + { + if (*ptr1 == '\n') ptr1++; + + ptr2 = strtok_s(NULL, "\r\n", &ptr1); + if (ptr2) + { + if (*ptr2 != '#') + { + BadWords = realloc(BadWords,(++NumberofBadWords+1) * sizeof(void *)); + BadWords[NumberofBadWords] = ptr2; + } + } + else + break; + } +} + +BOOL CheckBadWord(char * Word, char * Msg) +{ + char * ptr1 = Msg, * ptr2; + size_t len = strlen(Word); + + while (*ptr1) // Stop at end + { + ptr2 = strstr(ptr1, Word); + + if (ptr2 == NULL) + return FALSE; // OK + + // Only bad if it ia not part of a longer word + + if ((ptr2 == Msg) || !(isalpha(*(ptr2 - 1)))) // No alpha before + if (!(isalpha(*(ptr2 + len)))) // No alpha after + return TRUE; // Bad word + + // Keep searching + + ptr1 = ptr2 + len; + } + + return FALSE; // OK +} + +BOOL CheckBadWords(char * Msg) +{ + char * dupMsg = _strlwr(_strdup(Msg)); + int i; + + for (i = 1; i <= NumberofBadWords; i++) + { + if (CheckBadWord(BadWords[i], dupMsg)) + { + free(dupMsg); + return TRUE; // Bad + } + } + + free(dupMsg); + return FALSE; // OK + +} + +VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user) +{ + if (user->flags & F_Expert) + ExpandAndSendMessage(conn, ExpertWelcomeMsg, LOG_BBS); + else if (conn->NewUser) + ExpandAndSendMessage(conn, NewWelcomeMsg, LOG_BBS); + else + ExpandAndSendMessage(conn, WelcomeMsg, LOG_BBS); + + if (user->HomeBBS[0] == 0 && !DontNeedHomeBBS) + BBSputs(conn, "Please enter your Home BBS using the Home command.\rYou may also enter your QTH and ZIP/Postcode using qth and zip commands.\r"); + +// if (user->flags & F_Temp_B2_BBS) +// nodeprintf(conn, "%s CMS >\r", BBSName); +// else + SendPrompt(conn, user); +} + +VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user) +{ + if (user->Temp->ListSuspended) + return; // Dont send prompt if pausing a listing + + if (user->flags & F_Expert) + ExpandAndSendMessage(conn, ExpertPrompt, LOG_BBS); + else if (conn->NewUser) + ExpandAndSendMessage(conn, NewPrompt, LOG_BBS); + else + ExpandAndSendMessage(conn, Prompt, LOG_BBS); + +// if (user->flags & F_Expert) +// nodeprintf(conn, "%s\r", ExpertPrompt); +// else if (conn->NewUser) +// nodeprintf(conn, "%s\r", NewPrompt); +// else +// nodeprintf(conn, "%s\r", Prompt); +} + + + +VOID * _zalloc(size_t len) +{ + // ?? malloc and clear + + void * ptr; + + ptr=malloc(len); + memset(ptr, 0, len); + + return ptr; +} + +BOOL isAMPRMsg(char * Addr) +{ + // See if message is addressed to ampr.org and is either + // for us or we have SendAMPRDirect (ie don't need RMS or SMTP to send it) + + size_t toLen = strlen(Addr); + + if (_memicmp(&Addr[toLen - 8], "ampr.org", 8) == 0) + { + // message is for ampr.org + + char toCall[48]; + char * via; + + strcpy(toCall, _strupr(Addr)); + + via = strlop(toCall, '@'); + + if (_stricmp(via, AMPRDomain) == 0) + { + // message is for us. + + return TRUE; + } + + if (SendAMPRDirect) + { + // We want to send ampr mail direct to host. Queue to BBS AMPR + + if (FindAMPR()) + { + // We have bbs AMPR + + return TRUE; + } + } + } + return FALSE; +} + +struct UserInfo * FindAMPR() +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, "AMPR") == 0) + return bbs; + } + + return NULL; +} + +struct UserInfo * FindRMS() +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, "RMS") == 0) + return bbs; + } + + return NULL; +} + +struct UserInfo * FindBBS(char * Name) +{ + struct UserInfo * bbs; + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + if (strcmp(bbs->Call, Name) == 0) + return bbs; + } + + return NULL; +} + +int CountConnectionsOnPort(int CheckPort) +{ + int n, Count = 0; + CIRCUIT * conn; + int port, sesstype, paclen, maxframe, l4window; + char callsign[11]; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active) + { + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + if (port == CheckPort) + Count++; + } + } + + return Count; +} + + +BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type) +{ + char ** Calls; + + if (Type == 'B' && FilterWPBulls && _stricmp(To, "WP") == 0) + return TRUE; + + if (RejFrom && From) + { + Calls = RejFrom; + + while(Calls[0]) + { + if (_stricmp(Calls[0], From) == 0) + return TRUE; + + Calls++; + } + } + + if (RejTo && To) + { + Calls = RejTo; + + while(Calls[0]) + { + if (_stricmp(Calls[0], To) == 0) + return TRUE; + + Calls++; + } + } + + if (RejAt && ATBBS) + { + Calls = RejAt; + + while(Calls[0]) + { + if (_stricmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + + if (RejBID && BID) + { + Calls = RejBID; + + while(Calls[0]) + { + if (Calls[0][0] == '*') + { + if (stristr(BID, &Calls[0][1])) + return TRUE; + } + else + { + if (_stricmp(BID, Calls[0]) == 0) + return TRUE; + } + + Calls++; + } + } + return FALSE; // Ok to accept +} + +BOOL CheckValidCall(char * From) +{ + unsigned int i; + + if (DontCheckFromCall) + return TRUE; + + if (strcmp(From, "SYSOP") == 0 || strcmp(From, "SYSTEM") == 0 || + strcmp(From, "IMPORT") == 0 || strcmp(From, "SMTP:") == 0 || strcmp(From, "RMS:") == 0) + return TRUE; + + for (i = 1; i < strlen(From); i++) // skip first which may also be digit + { + if (isdigit(From[i])) + { + // Has a digit. Check Last is not digit + + if (isalpha(From[strlen(From) - 1])) + return TRUE; + } + } + + // No digit, return false + + return FALSE; +} + +BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID) +{ + char ** Calls; + + if (HoldFrom && From) + { + Calls = HoldFrom; + + while(Calls[0]) + { + if (_stricmp(Calls[0], From) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldTo && To) + { + Calls = HoldTo; + + while(Calls[0]) + { + if (_stricmp(Calls[0], To) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldAt && ATBBS) + { + Calls = HoldAt; + + while(Calls[0]) + { + if (_stricmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + + if (HoldBID && BID) + { + Calls = HoldBID; + + while(Calls[0]) + { + if (Calls[0][0] == '*') + { + if (stristr(BID, &Calls[0][1])) + return TRUE; + } + else + { + if (_stricmp(BID, Calls[0]) == 0) + return TRUE; + } + + Calls++; + } + } + return FALSE; // Ok to accept +} + +BOOL CheckifLocalRMSUser(char * FullTo) +{ + struct UserInfo * user = LookupCall(FullTo); + + if (user) + if (user->flags & F_POLLRMS) + return TRUE; + + return FALSE; + +} + + + +int check_fwd_bit(char *mask, int bbsnumber) +{ + if (bbsnumber) + return (mask[(bbsnumber - 1) / 8] & (1 << ((bbsnumber - 1) % 8))); + else + return 0; +} + + +void set_fwd_bit(char *mask, int bbsnumber) +{ + if (bbsnumber) + mask[(bbsnumber - 1) / 8] |= (1 << ((bbsnumber - 1) % 8)); +} + + +void clear_fwd_bit (char *mask, int bbsnumber) +{ + if (bbsnumber) + mask[(bbsnumber - 1) / 8] &= (~(1 << ((bbsnumber - 1) % 8))); +} + +VOID BBSputs(CIRCUIT * conn, char * buf) +{ + // Sends to user and logs + + WriteLogLine(conn, '>',buf, (int)strlen(buf) -1, LOG_BBS); + + QueueMsg(conn, buf, (int)strlen(buf)); +} + +VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...) +{ + char Mess[1000]; + int len; + va_list(arglist); + + + va_start(arglist, format); + len = vsprintf(Mess, format, arglist); + + QueueMsg(conn, Mess, len); + + WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); + + return; +} + +// nodeprintfEx add a LF if NEEFLF is set + +VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...) +{ + char Mess[1000]; + int len; + va_list(arglist); + + + va_start(arglist, format); + len = vsprintf(Mess, format, arglist); + + QueueMsg(conn, Mess, len); + + WriteLogLine(conn, '>',Mess, len-1, LOG_BBS); + + if (conn->BBSFlags & NEEDLF) + QueueMsg(conn, "\r", 1); + + return; +} + + +int compare( const void *arg1, const void *arg2 ); + +VOID SortBBSChain() +{ + struct UserInfo * user; + struct UserInfo * users[161]; + int i = 0, n; + + // Get array of addresses + + for (user = BBSChain; user; user = user->BBSNext) + { + users[i++] = user; + if (i > 160) break; + } + + qsort((void *)users, i, sizeof(void *), compare ); + + BBSChain = NULL; + + // Rechain (backwards, as entries ate put on front of chain) + + for (n = i-1; n >= 0; n--) + { + users[n]->BBSNext = BBSChain; + BBSChain = users[n]; + } +} + +int compare(const void *arg1, const void *arg2) +{ + // Compare Calls. Fortunately call is at start of stuct + + return _stricmp(*(char**)arg1 , *(char**)arg2); +} + +int CountMessagesTo(struct UserInfo * user, int * Unread) +{ + int i, Msgs = 0; + UCHAR * Call = user->Call; + + *Unread = 0; + + for (i = NumberofMessages; i > 0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (_stricmp(MsgHddrPtr[i]->to, Call) == 0) + { + Msgs++; + if (MsgHddrPtr[i]->status == 'N') + *Unread = *Unread + 1; + } + } + return(Msgs); +} + + + +// Custimised message handling routines. +/* + Variables - a subset of those used by FBB + + $C : Number of the next message. + $I : First name of the connected user. + $L : Number of the latest message. + $N : Number of active messages + $U : Callsign of the connected user. + $W : Inserts a carriage return. + $Z : Last message read by the user (L command). + %X : Number of messages for the user. + %x : Number of new messages for the user. +*/ + +VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG) +{ + char NewMessage[10000]; + char * OldP = Msg; + char * NewP = NewMessage; + char * ptr, * pptr; + size_t len; + char Dollar[] = "$"; + char CR[] = "\r"; + char num[20]; + int Msgs = 0, Unread = 0; + + ptr = strchr(OldP, '$'); + + while (ptr) + { + len = ptr - OldP; // Chars before $ + memcpy(NewP, OldP, len); + NewP += len; + + switch (*++ptr) + { + case 'I': // First name of the connected user. + + pptr = conn->UserPointer->Name; + break; + + case 'L': // Number of the latest message. + + sprintf(num, "%d", LatestMsg); + pptr = num; + break; + + case 'N': // Number of active messages. + + sprintf(num, "%d", NumberofMessages); + pptr = num; + break; + + case 'U': // Callsign of the connected user. + + pptr = conn->UserPointer->Call; + break; + + case 'W': // Inserts a carriage return. + + pptr = CR; + break; + + case 'Z': // Last message read by the user (L command). + + sprintf(num, "%d", conn->UserPointer->lastmsg); + pptr = num; + break; + + case 'X': // Number of messages for the user. + + Msgs = CountMessagesTo(conn->UserPointer, &Unread); + sprintf(num, "%d", Msgs); + pptr = num; + break; + + case 'x': // Number of new messages for the user. + + Msgs = CountMessagesTo(conn->UserPointer, &Unread); + sprintf(num, "%d", Unread); + pptr = num; + break; + + case 'F': // Number of new messages to forward to this BBS. + + Msgs = CountMessagestoForward(conn->UserPointer); + sprintf(num, "%d", Msgs); + pptr = num; + break; + + default: + + pptr = Dollar; // Just Copy $ + } + + len = strlen(pptr); + memcpy(NewP, pptr, len); + NewP += len; + + OldP = ++ptr; + ptr = strchr(OldP, '$'); + } + + strcpy(NewP, OldP); + + len = RemoveLF(NewMessage, (int)strlen(NewMessage)); + + WriteLogLine(conn, '>', NewMessage, (int)len, LOG); + QueueMsg(conn, NewMessage, (int)len); +} + +BOOL isdigits(char * string) +{ + // Returns TRUE id sting is decimal digits + + size_t i, n = strlen(string); + + for (i = 0; i < n; i++) + { + if (isdigit(string[i]) == FALSE) return FALSE; + } + return TRUE; +} + +BOOL wildcardcompare(char * Target, char * Match) +{ + // Do a compare with string *string string* *string* + + // Strings should all be UC + + char Pattern[100]; + char * firststar; + + strcpy(Pattern, Match); + firststar = strchr(Pattern,'*'); + + if (firststar) + { + size_t Len = strlen(Pattern); + + if (Pattern[0] == '*' && Pattern[Len - 1] == '*') // * at start and end + { + Pattern[Len - 1] = 0; + return !(strstr(Target, &Pattern[1]) == NULL); + } + if (Pattern[0] == '*') // * at start + { + // Compare the last len - 1 chars of Target + + size_t Targlen = strlen(Target); + size_t Comparelen = Targlen - (Len - 1); + + if (Len == 1) // Just * + return TRUE; + + if (Comparelen < 0) // Too Short + return FALSE; + + return (memcmp(&Target[Comparelen], &Pattern[1], Len - 1) == 0); + } + + // Must be * at end - compare first Len-1 char + + return (memcmp(Target, Pattern, Len - 1) == 0); + } + + // No WildCards - straight strcmp + return (strcmp(Target, Pattern) == 0); +} + +#ifndef LINBPQ + +PrintMessage(HDC hDC, struct MsgInfo * Msg); + +PrintMessages(HWND hDlg, int Count, int * Indexes) +{ + int i, CurrentMsgIndex; + char MsgnoText[10]; + int Msgno; + struct MsgInfo * Msg; + int Len = MAX_PATH; + BOOL hResult; + PRINTDLG pdx = {0}; + HDC hDC; + +// CHOOSEFONT cf; + LOGFONT lf; + HFONT hFont; + + + // Initialize the PRINTDLG structure. + + pdx.lStructSize = sizeof(PRINTDLG); + pdx.hwndOwner = hWnd; + pdx.hDevMode = NULL; + pdx.hDevNames = NULL; + pdx.hDC = NULL; + pdx.Flags = PD_RETURNDC | PD_COLLATE; + pdx.nMinPage = 1; + pdx.nMaxPage = 1000; + pdx.nCopies = 1; + pdx.hInstance = 0; + pdx.lpPrintTemplateName = NULL; + + // Invoke the Print property sheet. + + hResult = PrintDlg(&pdx); + + memset(&lf, 0, sizeof(LOGFONT)); + + /* + + // Initialize members of the CHOOSEFONT structure. + + cf.lStructSize = sizeof(CHOOSEFONT); + cf.hwndOwner = (HWND)NULL; + cf.hDC = pdx.hDC; + cf.lpLogFont = &lf; + cf.iPointSize = 0; + cf.Flags = CF_PRINTERFONTS | CF_FIXEDPITCHONLY; + cf.rgbColors = RGB(0,0,0); + cf.lCustData = 0L; + cf.lpfnHook = (LPCFHOOKPROC)NULL; + cf.lpTemplateName = (LPSTR)NULL; + cf.hInstance = (HINSTANCE) NULL; + cf.lpszStyle = (LPSTR)NULL; + cf.nFontType = PRINTER_FONTTYPE; + cf.nSizeMin = 0; + cf.nSizeMax = 0; + + // Display the CHOOSEFONT common-dialog box. + + ChooseFont(&cf); + + // Create a logical font based on the user's + // selection and return a handle identifying + // that font. +*/ + + lf.lfHeight = -56; + lf.lfWeight = 600; + lf.lfOutPrecision = 3; + lf.lfClipPrecision = 2; + lf.lfQuality = 1; + lf.lfPitchAndFamily = '1'; + strcpy (lf.lfFaceName, "Courier New"); + + hFont = CreateFontIndirect(&lf); + + if (hResult) + { + // User clicked the Print button, so use the DC and other information returned in the + // PRINTDLG structure to print the document. + + DOCINFO pdi; + + pdi.cbSize = sizeof(DOCINFO); + pdi.lpszDocName = "BBS Message Print"; + pdi.lpszOutput = NULL; + pdi.lpszDatatype = "RAW"; + pdi.fwType = 0; + + hDC = pdx.hDC; + + SelectObject(hDC, hFont); + + StartDoc(hDC, &pdi); + StartPage(hDC); + + for (i = 0; i < Count; i++) + { + SendDlgItemMessage(hDlg, 0, LB_GETTEXT, Indexes[i], (LPARAM)(LPCTSTR)&MsgnoText); + + Msgno = atoi(MsgnoText); + + for (CurrentMsgIndex = 1; CurrentMsgIndex <= NumberofMessages; CurrentMsgIndex++) + { + Msg = MsgHddrPtr[CurrentMsgIndex]; + + if (Msg->number == Msgno) + { + PrintMessage(hDC, Msg); + break; + } + } + } + + EndDoc(hDC); + } + + if (pdx.hDevMode != NULL) + GlobalFree(pdx.hDevMode); + if (pdx.hDevNames != NULL) + GlobalFree(pdx.hDevNames); + + if (pdx.hDC != NULL) + DeleteDC(pdx.hDC); + + return 0; +} + +PrintMessage(HDC hDC, struct MsgInfo * Msg) +{ + int Len = MAX_PATH; + char * MsgBytes; + char * Save; + int Msglen; + + StartPage(hDC); + + Save = MsgBytes = ReadMessageFile(Msg->number); + + Msglen = Msg->length; + + if (MsgBytes) + { + char Hddr[1000]; + char FullTo[100]; + int HRes, VRes; + char * ptr1, * ptr2; + int LineLen; + + RECT Rect; + + if (_stricmp(Msg->to, "RMS") == 0) + sprintf(FullTo, "RMS:%s", Msg->via); + else + if (Msg->to[0] == 0) + sprintf(FullTo, "smtp:%s", Msg->via); + else + strcpy(FullTo, Msg->to); + + + sprintf(Hddr, "From: %s%s\r\nTo: %s\r\nType/Status: %c%c\r\nDate/Time: %s\r\nBid: %s\r\nTitle: %s\r\n\r\n", + Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); + + + if (Msg->B2Flags & B2Msg) + { + // Remove B2 Headers (up to the File: Line) + + char * ptr; + ptr = strstr(MsgBytes, "Body:"); + if (ptr) + { + Msglen = atoi(ptr + 5); + ptr = strstr(ptr, "\r\n\r\n"); + } + if (ptr) + MsgBytes = ptr + 4; + } + + HRes = GetDeviceCaps(hDC, HORZRES) - 50; + VRes = GetDeviceCaps(hDC, VERTRES) - 50; + + Rect.top = 50; + Rect.left = 50; + Rect.right = HRes; + Rect.bottom = VRes; + + DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_CALCRECT | DT_WORDBREAK); + DrawText(hDC, Hddr, strlen(Hddr), &Rect, DT_WORDBREAK); + + // process message a line at a time. When page is full, output a page break + + ptr1 = MsgBytes; + ptr2 = ptr1; + + while (Msglen-- > 0) + { + if (*ptr1++ == '\r') + { + // Output this line + + // First check if it will fit + + Rect.top = Rect.bottom; + Rect.right = HRes; + Rect.bottom = VRes; + + LineLen = ptr1 - ptr2 - 1; + + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); + + if (Rect.bottom >= VRes) + { + EndPage(hDC); + StartPage(hDC); + + Rect.top = 50; + Rect.bottom = VRes; + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_CALCRECT | DT_WORDBREAK); + } + + if (LineLen == 0) // Blank line + Rect.bottom = Rect.top + 40; + else + DrawText(hDC, ptr2, ptr1 - ptr2 - 1, &Rect, DT_WORDBREAK); + + if (*(ptr1) == '\n') + { + ptr1++; + Msglen--; + } + + ptr2 = ptr1; + } + } + + free(Save); + + EndPage(hDC); + + } + return 0; +} + +#endif + + +int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup) +{ + char FileName[MAX_PATH] = "Messages.in"; + int Files = 0; + int WriteLen=0; + FILE *in; + CIRCUIT dummyconn; + struct UserInfo User; + int Index = 0; + + char Buffer[100000]; + char *buf = Buffer; + + if (FN[0]) // Name supplled + strcpy(FileName, FN); + + else + { +#ifndef LINBPQ + OPENFILENAME Ofn; + + memset(&Ofn, 0, sizeof(Ofn)); + + Ofn.lStructSize = sizeof(OPENFILENAME); + Ofn.hInstance = hInst; + Ofn.hwndOwner = MainWnd; + Ofn.lpstrFilter = NULL; + Ofn.lpstrFile= FileName; + Ofn.nMaxFile = sizeof(FileName)/ sizeof(*FileName); + Ofn.lpstrFileTitle = NULL; + Ofn.nMaxFileTitle = 0; + Ofn.lpstrInitialDir = BaseDir; + Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + Ofn.lpstrTitle = NULL;//; + + if (!GetOpenFileName(&Ofn)) + return 0; +#endif + } + + in = fopen(FileName, "rb"); + + if (!(in)) + { + char msg[500]; + sprintf_s(msg, sizeof(msg), "Failed to open %s", FileName); + if (conn) + nodeprintf(conn, "%s\r", msg); +#ifdef WIN32 + else + if (Nopopup == FALSE) + MessageBox(NULL, msg, "BPQMailChat", MB_OK); +#endif + return 0; + } + + memset(&dummyconn, 0, sizeof(CIRCUIT)); + memset(&User, 0, sizeof(struct UserInfo)); + + if (conn == 0) + { + conn = &dummyconn; + + dummyconn.UserPointer = &User; // Was SYSOPCall, but I think that is wrong. + strcpy(User.Call, "IMPORT"); + User.flags |= F_EMAIL; + dummyconn.sysop = TRUE; + dummyconn.BBSFlags = BBS; + + strcpy(dummyconn.Callsign, "IMPORT"); + } + + while(fgets(Buffer, 99999, in)) + { + // First line should start SP/SB ?ST? + + char * From = NULL; + char * BID = NULL; + char * ATBBS = NULL; + char seps[] = " \t\r"; + struct MsgInfo * Msg; + char To[100]= ""; + int msglen; + char * Context; + char * Arg1, * Cmd; + +NextMessage: + + From = NULL; + BID = NULL; + ATBBS = NULL; + To[0]= 0; + + Sleep(100); + + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + + if (Buffer[0] == 0) //Blank Line + continue; + + WriteLogLine(conn, '>', Buffer, (int)strlen(Buffer), LOG_BBS); + + if (dummyconn.sysop == 0) + { + nodeprintf(conn, "%s\r", Buffer); + Flush(conn); + } + + Cmd = strtok_s(Buffer, seps, &Context); + + if (Cmd == NULL) + { + fclose(in); + return Files; + } + + Arg1 = strtok_s(NULL, seps, &Context); + + if (Arg1 == NULL) + { + if (dummyconn.sysop) + Debugprintf("Bad Import Line %s", Buffer); + else + nodeprintf(conn, "Bad Import Line %s\r", Buffer); + + fclose(in); + return Files; + } + + strcpy(To, Arg1); + + if (DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + { + if (CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL)) + { + Msg = conn->TempMsg; + + // SP is Ok, read message; + + ClearQueue(conn); + + fgets(Buffer, 99999, in); + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + if (strlen(Buffer) > 60) + Buffer[60] = 0; + + strcpy(Msg->title, Buffer); + + // Read the lines + + conn->Flags |= GETTINGMESSAGE; + + Buffer[0] = 0; + + fgets(Buffer, 99999, in); + + while ((conn->Flags & GETTINGMESSAGE) && Buffer[0]) + { + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + msglen = (int)strlen(Buffer); + Buffer[msglen++] = 13; + ProcessMsgLine(conn, conn->UserPointer,Buffer, msglen); + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + } + + // Message completed (or off end of file) + + Files ++; + + ClearQueue(conn); + + if (Buffer[0]) + goto NextMessage; // We have read the SP/SB line; + else + { + fclose(in); + return Files; + } + } + else + { + // Create failed + + Flush(conn); + } + } + + // Search for next message + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + + while (Buffer[0]) + { + strlop(Buffer, 10); + strlop(Buffer, 13); // Remove cr and/or lf + + if (_stricmp(Buffer, "/EX") == 0) + { + // Found end + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + + if (dummyconn.sysop) + ClearQueue(conn); + else + Flush(conn); + + if (Buffer[0]) + goto NextMessage; // We have read the SP/SB line; + } + + Buffer[0] = 0; + fgets(Buffer, 99999, in); + } + } + + fclose(in); + + if (dummyconn.sysop) + ClearQueue(conn); + else + Flush(conn); + + return Files; +} +char * ReadMessageFileEx(struct MsgInfo * MsgRec) +{ + // Sets Message Size from File Size + + int msgno = MsgRec->number; + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes=malloc(FileSize+1); + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize]=0; + MsgRec->length = FileSize; + + return MsgBytes; +} + +char * ReadMessageFile(int msgno) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, msgno); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes = malloc(FileSize + 100); // A bit of space for alias substitution on B2 + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize]=0; + + return MsgBytes; +} + + +int QueueMsg(ConnectionInfo * conn, char * msg, int len) +{ + // Add Message to queue for this connection + + // UCHAR * OutputQueue; // Messages to user + // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. + + // Create or extend buffer + + GetSemaphore(&OutputSEM, 0); + + conn->OutputQueue=realloc(conn->OutputQueue, conn->OutputQueueLength + len); + + if (conn->OutputQueue == NULL) + { + // relloc failed - should never happen, but clean up + + CriticalErrorHandler("realloc failed to expand output queue"); + FreeSemaphore(&OutputSEM); + return 0; + } + + memcpy(&conn->OutputQueue[conn->OutputQueueLength], msg, len); + conn->OutputQueueLength += len; + FreeSemaphore(&OutputSEM); + + return len; +} + +void TrytoSend() +{ + // call Flush on any connected streams with queued data + + ConnectionInfo * conn; + struct ConsoleInfo * Cons; + + int n; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active == TRUE) + { + Flush(conn); + + // if an FLARQ mail has been sent see if queues have cleared + + if (conn->BBSFlags & YAPPTX) + { + YAPPSendData(conn); + } + else if (conn->OutputQueue == NULL && (conn->BBSFlags & ARQMAILACK)) + { + int n = TXCount(conn->BPQStream); // All Sent and Acked? + + if (n == 0) + { + struct MsgInfo * Msg = conn->FwdMsg; + + conn->ARQClearCount--; + + if (conn->ARQClearCount <= 0) + { + Logprintf(LOG_BBS, conn, '>', "ARQ Send Complete"); + + // Mark mail as sent, and look for more + + clear_fwd_bit(Msg->fbbs, conn->UserPointer->BBSNumber); + set_fwd_bit(Msg->forw, conn->UserPointer->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(Msg->fbbs, zeros, NBMASK) == 0) + { + Msg->status = 'F'; // Mark as forwarded + Msg->datechanged=time(NULL); + } + + conn->BBSFlags &= ~ARQMAILACK; + conn->UserPointer->ForwardingInfo->MsgCount--; + + SaveMessageDatabase(); + SendARQMail(conn); // See if any more - close if not + } + } + else + conn->ARQClearCount = 10; + } + } + } +#ifndef LINBPQ + for (Cons = ConsHeader[0]; Cons; Cons = Cons->next) + { + if (Cons->Console) + Flush(Cons->Console); + } +#endif +} + + +void Flush(CIRCUIT * conn) +{ + int tosend, len, sent; + + // Try to send data to user. May be stopped by user paging or node flow control + + // UCHAR * OutputQueue; // Messages to user + // int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + // int OutputGetPointer; // Next byte to send. When Getpointer = Quele Length all is sent - free the buffer and start again. + + // BOOL Paging; // Set if user wants paging + // int LinesSent; // Count when paging + // int PageLen; // Lines per page + + + if (conn->OutputQueue == NULL) + { + // Nothing to send. If Close after Flush is set, disconnect + + if (conn->CloseAfterFlush) + { + conn->CloseAfterFlush--; + + if (conn->CloseAfterFlush) + return; + + Disconnect(conn->BPQStream); + conn->ErrorCount = 0; + } + + return; // Nothing to send + } + tosend = conn->OutputQueueLength - conn->OutputGetPointer; + + sent=0; + + while (tosend > 0) + { + if (TXCount(conn->BPQStream) > 15) + return; // Busy + + if (conn->BBSFlags & SYSOPCHAT) // Suspend queued output while sysop chatting + return; + + if (conn->Paging && (conn->LinesSent >= conn->PageLen)) + return; + + if (tosend <= conn->paclen) + len=tosend; + else + len=conn->paclen; + + GetSemaphore(&OutputSEM, 0); + + if (conn->Paging) + { + // look for CR chars in message to send. Increment LinesSent, and stop if at limit + + UCHAR * ptr1 = &conn->OutputQueue[conn->OutputGetPointer]; + UCHAR * ptr2; + int lenleft = len; + + ptr2 = memchr(ptr1, 0x0d, len); + + while (ptr2) + { + conn->LinesSent++; + ptr2++; + lenleft = len - (int)(ptr2 - ptr1); + + if (conn->LinesSent >= conn->PageLen) + { + len = (int)(ptr2 - &conn->OutputQueue[conn->OutputGetPointer]); + + SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); + conn->OutputGetPointer+=len; + tosend-=len; + SendUnbuffered(conn->BPQStream, "bort, Continue..>", 25); + FreeSemaphore(&OutputSEM); + return; + + } + ptr2 = memchr(ptr2, 0x0d, lenleft); + } + } + + SendUnbuffered(conn->BPQStream, &conn->OutputQueue[conn->OutputGetPointer], len); + + conn->OutputGetPointer+=len; + + FreeSemaphore(&OutputSEM); + + tosend-=len; + sent++; + + if (sent > 15) + return; + } + + // All Sent. Free buffers and reset pointers + + conn->LinesSent = 0; + + ClearQueue(conn); +} + +VOID ClearQueue(ConnectionInfo * conn) +{ + if (conn->OutputQueue == NULL) + return; + + GetSemaphore(&OutputSEM, 0); + + free(conn->OutputQueue); + + conn->OutputQueue=NULL; + conn->OutputGetPointer=0; + conn->OutputQueueLength=0; + + FreeSemaphore(&OutputSEM); +} + + + +VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB) +{ + struct UserInfo * user; + + Msg->status='K'; + Msg->datechanged=time(NULL); + + // Remove any forwarding references + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + for (user = BBSChain; user; user = user->BBSNext) + { + if (check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + user->ForwardingInfo->MsgCount--; + clear_fwd_bit(Msg->fbbs, user->BBSNumber); + } + } + } + if (SaveDB) + SaveMessageDatabase(); + RebuildNNTPList(); +} + +void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + struct MsgInfo * Msg; + + while (Arg1) + { + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + goto Next; + } + + if (Msg->type != 'T') + { + nodeprintf(conn, "Message %d not an NTS Message\r", msgno); + goto Next; + } + + if (Msg->status == 'N') + nodeprintf(conn, "Warning - Message has status N\r"); + + Msg->status = 'D'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + + nodeprintf(conn, "Message #%d Flagged as Delivered\r", msgno); + Next: + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; +} + +void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + // Param is either ALL or a list of numbers + + if (Arg1 == NULL) + { + nodeprintf(conn, "No message number\r"); + return; + } + + if (_stricmp(Arg1, "ALL") == 0) + { + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if (Msg->status == 'H') + { + if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + else + Msg->status = 'N'; + + nodeprintf(conn, "Message #%d Unheld\r", Msg->number); + } + } + return; + } + + while (Arg1) + { + msgno = atoi(Arg1); + Msg = GetMsgFromNumber(msgno); + + if (Msg) + { + if (Msg->status == 'H') + { + if (Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + else + Msg->status = 'N'; + + nodeprintf(conn, "Message #%d Unheld\r", msgno); + } + else + { + nodeprintf(conn, "Message #%d was not held\r", msgno); + } + } + else + nodeprintf(conn, "Message #%d not found\r", msgno); + + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; +} + +void DoKillCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + switch (toupper(Cmd[1])) + { + + case 0: // Just K + + while (Arg1) + { + msgno = atoi(Arg1); + KillMessage(conn, user, msgno); + + Arg1 = strtok_s(NULL, " \r", &Context); + } + + SaveMessageDatabase(); + return; + + case 'M': // Kill Mine + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + { + if (Msg->type == 'P' && Msg->status == 'Y') + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", Msg->number); + } + } + } + + SaveMessageDatabase(); + return; + + case 'H': // Kill Held + + if (conn->sysop) + { + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + + if (Msg->status == 'H') + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", Msg->number); + } + } + } + SaveMessageDatabase(); + return; + + case '>': // K> - Kill to + + if (conn->sysop) + { + if (Arg1) + if (KillMessagesTo(conn, user, Arg1) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + } + + case '<': + + if (conn->sysop) + { + if (Arg1) + if (KillMessagesFrom(conn, user, Arg1) == 0); + BBSputs(conn, "No Messages found\r"); + + return; + } + } + + nodeprintf(conn, "*** Error: Invalid Kill option %c\r", Cmd[1]); + + return; + +} + +int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call) +{ + int i, Msgs = 0; + struct MsgInfo * Msg; + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + if (Msg->status != 'K' && _stricmp(Msg->to, Call) == 0) + { + Msgs++; + KillMessage(conn, user, MsgHddrPtr[i]->number); + } + } + + SaveMessageDatabase(); + return(Msgs); +} + +int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call) +{ + int i, Msgs = 0; + struct MsgInfo * Msg; + + + for (i=NumberofMessages; i>0; i--) + { + Msg = MsgHddrPtr[i]; + if (Msg->status != 'K' && _stricmp(Msg->from, Call) == 0) + { + Msgs++; + KillMessage(conn, user, MsgHddrPtr[i]->number); + } + } + + SaveMessageDatabase(); + return(Msgs); +} + +BOOL OkToKillMessage(BOOL SYSOP, char * Call, struct MsgInfo * Msg) +{ + if (SYSOP || (Msg->type == 'T' && UserCantKillT == FALSE)) + return TRUE; + + if (Msg->type == 'P') + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return TRUE; + + if (Msg->type == 'B') + if (_stricmp(Msg->from, Call) == 0) + return TRUE; + + return FALSE; +} + +void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) +{ + struct MsgInfo * Msg; + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL || Msg->status == 'K') + { + nodeprintf(conn, "Message %d not found\r", msgno); + return; + } + + if (OkToKillMessage(conn->sysop, user->Call, Msg)) + { + FlagAsKilled(Msg, FALSE); + nodeprintf(conn, "Message #%d Killed\r", msgno); + } + else + nodeprintf(conn, "Not your message\r"); +} + + +BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp) +{ + char FullFrom[80]; + char FullTo[80]; + + strcpy(FullFrom, Msg->from); + + if ((_stricmp(Msg->from, "RMS:") == 0) || (_stricmp(Msg->from, "SMTP:") == 0) || + Temp->SendFullFrom || (_stricmp(Msg->emailfrom, "@winlink.org") == 0)) + strcat(FullFrom, Msg->emailfrom); + + if (_stricmp(Msg->to, "RMS") == 0) + { + sprintf(FullTo, "RMS:%s", Msg->via); + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); + } + else + + if (Msg->to[0] == 0 && Msg->via[0] != 0) + { + sprintf(FullTo, "smtp:%s", Msg->via); + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, FullTo, FullFrom, Msg->title); + } + + else + if (Msg->via[0] != 0) + { + char Via[80]; + strcpy(Via, Msg->via); + strlop(Via, '.'); // Only show first part of via + nodeprintf(conn, "%-6d %s %c%c %5d %-7s@%-6s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, Via, FullFrom, Msg->title); + } + else + nodeprintf(conn, "%-6d %s %c%c %5d %-7s %-6s %-s\r", + Msg->number, FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type, Msg->status, Msg->length, Msg->to, FullFrom, Msg->title); + + // if paging, stop two before page lengh. This lets us send the continue prompt, save status + // and exit without triggering the system paging code. We can then read a message then resume listing + + if (Temp->ListActive && conn->Paging) + { + Temp->LinesSent++; + + if ((Temp->LinesSent + 1) >= conn->PageLen) + { + nodeprintf(conn, "bort, , = Continue..>"); + Temp->LastListedInPagedMode = Msg->number; + Temp->ListSuspended = TRUE; + return TRUE; + } + } + + return FALSE; +} + +void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context) +{ + struct TempUserInfo * Temp = user->Temp; + struct MsgInfo * Msg; + + // Allow compound selection, eg LTN or LFP + + // types P N T + // Options LL LR L< L> L@ LM LC (L* used internally for just L, ie List New + // Status N Y H F K D + + // Allowing options in any order complicates paging. May be best to parse options once and restore if paging. + + Temp->ListActive = TRUE; + Temp->LinesSent = 0; + + if (Resuming) + { + // Entered after a paging pause. Selection fields are already set up + + // We have reentered list command after a pause. The next message to list is in Temp->LastListedInPagedMode + +// Start = Temp->LastListedInPagedMode; + Temp->ListSuspended = FALSE; + } + else + { + Temp->ListRangeEnd = LatestMsg; + Temp->ListRangeStart = 1; + Temp->LLCount = 0; + Temp->SendFullFrom = 0; + Temp->ListType = 0; + Temp->ListStatus = 0; + Temp->ListSelector = 0; + Temp->UpdateLatest = 0; + Temp->LastListParams[0] = 0; + Temp->IncludeKilled = 1; // SYSOP include Killed except LM + + //Analyse L params. + + _strupr(Cmd); + + if (strcmp(Cmd, "LC") == 0) // List Bull Categories + { + ListCategories(conn); + return; + } + + // if command is just L or LR start from last listed + + if (Arg1 == NULL) + { + if (strcmp(Cmd, "L") == 0 || strcmp(Cmd, "LR") == 0) + { + if (LatestMsg == conn->lastmsg) + { + BBSputs(conn, "No New Messages\r"); + return; + } + + Temp->UpdateLatest = 1; + Temp->ListRangeStart = conn->lastmsg; + } + } + + if (strchr(Cmd, 'V')) // Verbose + Temp->SendFullFrom = 'V'; + + if (strchr(Cmd, 'R')) + Temp->ListDirn = 'R'; + else + Temp->ListDirn = '*'; // Default newest first + + Cmd++; // skip L + + if (strchr(Cmd, 'T')) + Temp->ListType = 'T'; + else if (strchr(Cmd, 'P')) + Temp->ListType = 'P'; + else if (strchr(Cmd, 'B')) + Temp->ListType = 'B'; + + if (strchr(Cmd, 'N')) + Temp->ListStatus = 'N'; + else if (strchr(Cmd, 'Y')) + Temp->ListStatus = 'Y'; + else if (strchr(Cmd, 'F')) + Temp->ListStatus = 'F'; + else if (strchr(Cmd, '$')) + Temp->ListStatus = '$'; + else if (strchr(Cmd, 'H')) + Temp->ListStatus = 'H'; + else if (strchr(Cmd, 'K')) + Temp->ListStatus = 'K'; + else if (strchr(Cmd, 'D')) + Temp->ListStatus = 'D'; + + // H or K only by Sysop + + switch (Temp->ListStatus) + { + case 'K': + case 'H': // List Status + + if (conn->sysop) + break; + + BBSputs(conn, "LH or LK can only be used by SYSOP\r"); + return; + } + + if (strchr(Cmd, '<')) + Temp->ListSelector = '<'; + else if (strchr(Cmd, '>')) + Temp->ListSelector = '>'; + else if (strchr(Cmd, '@')) + Temp->ListSelector = '@'; + else if (strchr(Cmd, 'M')) + { + Temp->ListSelector = 'M'; + Temp->IncludeKilled = FALSE; + } + + // Param could be single number, number range or call + + if (Arg1) + { + if (strchr(Cmd, 'L')) // List Last + { + // Param is number + + if (Arg1) + Temp->LLCount = atoi(Arg1); + } + else + { + // Range nnn-nnn or single value or callsign + + char * Arg2, * Arg3, * Range; + char seps[] = " \t\r"; + UINT From=LatestMsg, To=0; + + Arg2 = strtok_s(NULL, seps, &Context); + Arg3 = strtok_s(NULL, seps, &Context); + + if (Temp->ListSelector && Temp->ListSelector != 'M') + { + // < > or @ - first param is callsign + + strcpy(Temp->LastListParams, Arg1); + + // Just possible number range + + Arg1 = Arg2; + Arg2 = Arg3; + Arg3 = strtok_s(NULL, seps, &Context); + } + + if (Arg1) + { + Range = strchr(Arg1, '-'); + + // A number could be a Numeric Bull Dest (eg 44) + // I think this can only resaonably be > + + if (isdigits(Arg1)) + To = From = atoi(Arg1); + + if (Arg2) + From = atoi(Arg2); + else + { + if (Range) + { + Arg3 = strlop(Arg1, '-'); + + To = atoi(Arg1); + + if (Arg3 && Arg3[0]) + From = atoi(Arg3); + else + From = LatestMsg; + } + } + if (From > 100000 || To > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + Temp->ListRangeStart = To; + Temp->ListRangeEnd = From; + } + } + } + } + + // Run through all messages (either forwards or backwards) and list any that match all selection criteria + + while (1) + { + if (Temp->ListDirn == 'R') + Msg = GetMsgFromNumber(Temp->ListRangeStart); + else + Msg = GetMsgFromNumber(Temp->ListRangeEnd); + + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop, Temp->IncludeKilled)) // Check if user is allowed to list this message + { + // Check filters + + if (Temp->ListStatus && Temp->ListStatus != Msg->status) + goto skip; + + if (Temp->ListType && Temp->ListType != Msg->type) + goto skip; + + if (Temp->ListSelector == '<') + if (_stricmp(Msg->from, Temp->LastListParams) != 0) + goto skip; + + if (Temp->ListSelector == '>') + if (_stricmp(Msg->to, Temp->LastListParams) != 0) + goto skip; + + if (Temp->ListSelector == '@') + if (_memicmp(Msg->via, Temp->LastListParams, strlen(Temp->LastListParams)) != 0 && + (_stricmp(Temp->LastListParams, "SMTP:") != 0 || Msg->to[0] != 0)) + goto skip; + + if (Temp->ListSelector == 'M') + if (_stricmp(Msg->to, user->Call) != 0 && + (_stricmp(Msg->to, "SYSOP") != 0 || ((user->flags & F_SYSOP_IN_LM) == 0))) + + goto skip; + + if (ListMessage(Msg, conn, Temp)) + { + if (Temp->ListDirn == 'R') + Temp->ListRangeStart++; + else + Temp->ListRangeEnd--; + + return; // Hit page limit + } + + if (Temp->LLCount) + { + Temp->LLCount--; + if (Temp->LLCount == 0) + return; // LL count reached + } +skip:; + } + + if (Temp->ListRangeStart == Temp->ListRangeEnd) + { + // if using L or LR (list new) update last listed field + + if (Temp->UpdateLatest) + conn->lastmsg = LatestMsg; + + return; + } + + if (Temp->ListDirn == 'R') + Temp->ListRangeStart++; + else + Temp->ListRangeEnd--; + + if (Temp->ListRangeStart > 100000 || Temp->ListRangeEnd < 0) // Loop protection! + return; + + } + +/* + + switch (Cmd[0]) + { + + case '*': // Just L + case 'R': // LR = List Reverse + + if (Arg1) + { + // Range nnn-nnn or single value + + char * Arg2, * Arg3; + char * Context; + char seps[] = " -\t\r"; + UINT From=LatestMsg, To=0; + char * Range = strchr(Arg1, '-'); + + Arg2 = strtok_s(Arg1, seps, &Context); + Arg3 = strtok_s(NULL, seps, &Context); + + if (Arg2) + To = From = atoi(Arg2); + + if (Arg3) + From = atoi(Arg3); + else + if (Range) + From = LatestMsg; + + if (From > 100000 || To > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + if (Cmd[1] == 'R') + { + if (Start) + To = Start + 1; + + ListMessagesInRangeForwards(conn, user, user->Call, From, To, Temp->SendFullFrom); + } + else + { + if (Start) + From = Start - 1; + + ListMessagesInRange(conn, user, user->Call, From, To, Temp->SendFullFrom); + } + } + else + + if (LatestMsg == conn->lastmsg) + BBSputs(conn, "No New Messages\r"); + else if (Cmd[1] == 'R') + ListMessagesInRangeForwards(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); + else + ListMessagesInRange(conn, user, user->Call, LatestMsg, conn->lastmsg + 1, SendFullFrom); + + conn->lastmsg = LatestMsg; + + return; + + + case 'L': // List Last + + if (Arg1) + { + int i = atoi(Arg1); + int m = NumberofMessages; + + if (Resuming) + i = Temp->LLCount; + else + Temp->LLCount = i; + + for (; i>0 && m != 0; i--) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + i++; + continue; + } + + Temp->LLCount--; + + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + m--; + } + } + } + return; + + case 'M': // LM - List Mine + + if (ListMessagesTo(conn, user, user->Call, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + return; + + case '>': // L> - List to + + if (Arg1) + if (ListMessagesTo(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + + return; + + case '<': + + if (Arg1) + if (ListMessagesFrom(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + + case '@': + + if (Arg1) + if (ListMessagesAT(conn, user, Arg1, SendFullFrom, Start) == 0) + BBSputs(conn, "No Messages found\r"); + + return; + + case 'N': + case 'Y': + case 'F': + case '$': + case 'D': // Delivered NTS Traffic can be listed by anyone + { + int m = NumberofMessages; + + while (m > 0) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + continue; + } + + if (Temp->ListType) + { + if (MsgHddrPtr[m]->status == Cmd[1] && MsgHddrPtr[m]->type == Temp->ListType) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + } + else + { + if (MsgHddrPtr[m]->status == toupper(Cmd[1])) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + } + m--; + } + } + } + return; + + case 'K': + case 'H': // List Status + + if (conn->sysop) + { + int i, Msgs = Start; + + for (i=NumberofMessages; i>0; i--) + { + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (MsgHddrPtr[i]->status == toupper(Cmd[1])) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, SendFullFrom)) + return; // Hit page limit + + } + } + + if (Msgs == 0) + BBSputs(conn, "No Messages found\r"); + } + else + BBSputs(conn, "LH or LK can only be used by SYSOP\r"); + + return; + + case 'C': + { + struct NNTPRec * ptr = FirstNNTPRec; + char Cat[100]; + char NextCat[100]; + int Line = 0; + int Count; + + while (ptr) + { + // if the next name is the same, combine counts + + strcpy(Cat, ptr->NewsGroup); + strlop(Cat, '.'); + Count = ptr->Count; + Catloop: + if (ptr->Next) + { + strcpy(NextCat, ptr->Next->NewsGroup); + strlop(NextCat, '.'); + if (strcmp(Cat, NextCat) == 0) + { + ptr = ptr->Next; + Count += ptr->Count; + goto Catloop; + } + } + + nodeprintf(conn, "%-6s %-3d", Cat, Count); + Line += 10; + if (Line > 80) + { + Line = 0; + nodeprintf(conn, "\r"); + } + + ptr = ptr->Next; + } + + if (Line) + nodeprintf(conn, "\r\r"); + else + nodeprintf(conn, "\r"); + + return; + } + } + + // Could be P B or T if specified without a status + + switch (Temp->ListType) + { + case 'P': + case 'B': + case 'T': // NTS Traffic can be listed by anyone + { + int m = NumberofMessages; + + while (m > 0) + { + m = GetUserMsg(m, user->Call, conn->sysop); + + if (m > 0) + { + if (Start && MsgHddrPtr[m]->number >= Start) + { + m--; + continue; + } + + if (MsgHddrPtr[m]->type == Temp->ListType) + if (ListMessage(MsgHddrPtr[m], conn, SendFullFrom)) + return; // Hit page limit + m--; + } + } + + return; + } + } + +*/ + nodeprintf(conn, "*** Error: Invalid List option %c\r", Cmd[1]); + +} + +void ListCategories(ConnectionInfo * conn) +{ + // list bull categories + struct NNTPRec * ptr = FirstNNTPRec; + char Cat[100]; + char NextCat[100]; + int Line = 0; + int Count; + + while (ptr) + { + // if the next name is the same, combine counts + + strcpy(Cat, ptr->NewsGroup); + strlop(Cat, '.'); + Count = ptr->Count; +Catloop: + if (ptr->Next) + { + strcpy(NextCat, ptr->Next->NewsGroup); + strlop(NextCat, '.'); + if (strcmp(Cat, NextCat) == 0) + { + ptr = ptr->Next; + Count += ptr->Count; + goto Catloop; + } + } + + nodeprintf(conn, "%-6s %-3d", Cat, Count); + Line += 10; + if (Line > 80) + { + Line = 0; + nodeprintf(conn, "\r"); + } + + ptr = ptr->Next; + } + + if (Line) + nodeprintf(conn, "\r\r"); + else + nodeprintf(conn, "\r"); + + return; +} + +/* +int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) +{ + int i, Msgs = Start; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if ((_stricmp(MsgHddrPtr[i]->to, Call) == 0) || + ((conn->sysop) && _stricmp(Call, SYSOPCall) == 0 && + _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0 && (user->flags & F_SYSOP_IN_LM))) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + break; // Hit page limit + } + } + + return(Msgs); +} + +int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start) +{ + int i, Msgs = 0; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (_stricmp(MsgHddrPtr[i]->from, Call) == 0) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + return Msgs; // Hit page limit + + } + } + + return(Msgs); +} + +int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom,int Start) +{ + int i, Msgs = 0; + + for (i=NumberofMessages; i>0; i--) + { + if (MsgHddrPtr[i]->status == 'K') + continue; + + if (Start && MsgHddrPtr[i]->number >= Start) + continue; + + if (_memicmp(MsgHddrPtr[i]->via, Call, strlen(Call)) == 0 || + (_stricmp(Call, "SMTP:") == 0 && MsgHddrPtr[i]->to[0] == 0)) + { + Msgs++; + if (ListMessage(MsgHddrPtr[i], conn, Temp->SendFullFrom)) + break; // Hit page limit + } + } + + return(Msgs); +} +*/ +int GetUserMsg(int m, char * Call, BOOL SYSOP) +{ + struct MsgInfo * Msg; + + // Get Next (usually backwards) message which should be shown to this user + // ie Not Deleted, and not Private unless to or from Call + + do + { + Msg=MsgHddrPtr[m]; + + if (SYSOP) return m; // Sysop can list or read anything + + if (Msg->status != 'K') + { + + if (Msg->status != 'H') + { + if (Msg->type == 'B' || Msg->type == 'T') return m; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return m; + } + } + } + + m--; + + } while (m> 0); + + return 0; +} + + +BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP, BOOL IncludeKilled) +{ + // Return TRUE if user is allowed to read message + + if (Msg->status == 'K' && IncludeKilled == 0) + return FALSE; + + if (SYSOP) + return TRUE; // Sysop can list or read anything + + if ((Msg->status != 'K') && (Msg->status != 'H')) + { + if (Msg->type == 'B' || Msg->type == 'T') return TRUE; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return TRUE; + } + } + + return FALSE; +} +/* +int GetUserMsgForwards(int m, char * Call, BOOL SYSOP) +{ + struct MsgInfo * Msg; + + // Get Next (usually backwards) message which should be shown to this user + // ie Not Deleted, and not Private unless to or from Call + + do + { + Msg=MsgHddrPtr[m]; + + if (Msg->status != 'K') + { + if (SYSOP) return m; // Sysop can list or read anything + + if (Msg->status != 'H') + { + if (Msg->type == 'B' || Msg->type == 'T') return m; + + if (Msg->type == 'P') + { + if ((_stricmp(Msg->to, Call) == 0) || (_stricmp(Msg->from, Call) == 0)) + return m; + } + } + } + + m++; + + } while (m <= NumberofMessages); + + return 0; + +} + + +void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom) +{ + int m; + struct MsgInfo * Msg; + + for (m = Start; m >= End; m--) + { + Msg = GetMsgFromNumber(m); + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) + if (ListMessage(Msg, conn, Temp->SendFullFrom)) + return; // Hit page limit + + } +} + + +void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int End, int Start, BOOL SendFullFrom) +{ + int m; + struct MsgInfo * Msg; + + for (m = Start; m <= End; m++) + { + Msg = GetMsgFromNumber(m); + + if (Msg && CheckUserMsg(Msg, user->Call, conn->sysop)) + if (ListMessage(Msg, conn, Temp->SendFullFrom)) + return; // Hit page limit + } +} +*/ + +void DoReadCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + int msgno=-1; + int i; + struct MsgInfo * Msg; + + + switch (toupper(Cmd[1])) + { + case 0: // Just R + + while (Arg1) + { + msgno = atoi(Arg1); + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return; + } + + ReadMessage(conn, user, msgno); + Arg1 = strtok_s(NULL, " \r", &Context); + } + + return; + + case 'M': // Read Mine (Unread Messages) + + if (toupper(Cmd[2]) == 'R') + { + for (i = 1; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + if (Msg->status == 'N') + ReadMessage(conn, user, Msg->number); + } + } + else + { + for (i = NumberofMessages; i > 0; i--) + { + Msg = MsgHddrPtr[i]; + + if ((_stricmp(Msg->to, user->Call) == 0) || (conn->sysop && _stricmp(Msg->to, "SYSOP") == 0 && user->flags & F_SYSOP_IN_LM)) + if (Msg->status == 'N') + ReadMessage(conn, user, Msg->number); + } + } + + return; + } + + nodeprintf(conn, "*** Error: Invalid Read option %c\r", Cmd[1]); + + return; +} + +int RemoveLF(char * Message, int len) +{ + // Remove lf chars and nulls + + char * ptr1, * ptr2; + + ptr1 = ptr2 = Message; + + while (len-- > 0) + { + while (*ptr1 == 0 && len) + { + ptr1++; + len--; + } + + *ptr2 = *ptr1; + + if (*ptr1 == '\r') + if (*(ptr1+1) == '\n') + { + ptr1++; + len--; + } + ptr1++; + ptr2++; + } + + return (int)(ptr2 - Message); +} + + + +int RemoveNulls(char * Message, int len) +{ + // Remove nulls + + char * ptr1, * ptr2; + + ptr1 = ptr2 = Message; + + while (len-- > 0) + { + while (*ptr1 == 0 && len) + { + ptr1++; + len--; + } + + *ptr2 = *ptr1; + + ptr1++; + ptr2++; + } + + return (int)(ptr2 - Message); +} + +void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno) +{ + struct MsgInfo * Msg; + char * MsgBytes, * Save; + char FullTo[100]; + int Index = 0; + + Msg = GetMsgFromNumber(msgno); + + if (Msg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return; + } + + if (!CheckUserMsg(Msg, user->Call, conn->sysop, TRUE)) + { + nodeprintf(conn, "Message %d not for you\r", msgno); + return; + } + + if (_stricmp(Msg->to, "RMS") == 0) + sprintf(FullTo, "RMS:%s", Msg->via); + else + if (Msg->to[0] == 0) + sprintf(FullTo, "smtp:%s", Msg->via); + else + strcpy(FullTo, Msg->to); + + + nodeprintf(conn, "From: %s%s\rTo: %s\rType/Status: %c%c\rDate/Time: %s\rBid: %s\rTitle: %s\r\r", + Msg->from, Msg->emailfrom, FullTo, Msg->type, Msg->status, FormatDateAndTime((time_t)Msg->datecreated, FALSE), Msg->bid, Msg->title); + + MsgBytes = Save = ReadMessageFile(msgno); + + if (Msg->type == 'P') + Index = PMSG; + else if (Msg->type == 'B') + Index = BMSG; + else if (Msg->type == 'T') + Index = TMSG; + + if (MsgBytes) + { + int Length = Msg->length; + + if (Msg->B2Flags & B2Msg) + { + char * ptr; + + // if message has attachments, display them if plain text + + if (Msg->B2Flags & Attachments) + { + char * FileName[100]; + int FileLen[100]; + int Files = 0; + int BodyLen, NewLen; + int i; + char *ptr2; + char Msg[512]; + int Len; + + ptr = MsgBytes; + + Len = sprintf(Msg, "Message has Attachments\r\r"); + QueueMsg(conn, Msg, Len); + + while(*ptr != 13) + { + ptr2 = strchr(ptr, 10); // Find CR + + if (memcmp(ptr, "Body: ", 6) == 0) + { + BodyLen = atoi(&ptr[6]); + } + + if (memcmp(ptr, "File: ", 6) == 0) + { + char * ptr1 = strchr(&ptr[6], ' '); // Find Space + + FileLen[Files] = atoi(&ptr[6]); + + FileName[Files++] = &ptr1[1]; + *(ptr2 - 1) = 0; + } + + ptr = ptr2; + ptr++; + } + + ptr += 2; // Over Blank Line and Separator + + NewLen = RemoveLF(ptr, BodyLen); + + QueueMsg(conn, ptr, NewLen); // Display Body + + ptr += BodyLen + 2; // to first file + + for (i = 0; i < Files; i++) + { + char Msg[512]; + int Len, n; + char * p = ptr; + char c; + + // Check if message is probably binary + + int BinCount = 0; + + NewLen = RemoveLF(ptr, FileLen[i]); // Removes LF agter CR but not on its own + + for (n = 0; n < NewLen; n++) + { + c = *p; + + if (c == 10) + *p = 13; + + if (c==0 || (c & 128)) + BinCount++; + + p++; + + } + + if (BinCount > NewLen/10) + { + // File is probably Binary + + Len = sprintf(Msg, "\rAttachment %s is a binary file\r", FileName[i]); + QueueMsg(conn, Msg, Len); + } + else + { + Len = sprintf(Msg, "\rAttachment %s\r\r", FileName[i]); + QueueMsg(conn, Msg, Len); + + user->Total.MsgsSent[Index] ++; + user->Total.BytesForwardedOut[Index] += NewLen; + + QueueMsg(conn, ptr, NewLen); + } + + ptr += FileLen[i]; + ptr +=2; // Over separator + } + goto sendEOM; + } + + // Remove B2 Headers (up to the File: Line) + + ptr = strstr(MsgBytes, "Body:"); + + if (ptr) + { + MsgBytes = ptr; + Length = (int)strlen(ptr); + } + } + + // Remove lf chars + + Length = RemoveLF(MsgBytes, Length); + + user->Total.MsgsSent[Index] ++; + user->Total.BytesForwardedOut[Index] += Length; + + QueueMsg(conn, MsgBytes, Length); + +sendEOM: + + free(Save); + + nodeprintf(conn, "\r\r[End of Message #%d from %s%s]\r", msgno, Msg->from, Msg->emailfrom); + + if ((_stricmp(Msg->to, user->Call) == 0) || ((conn->sysop) && (_stricmp(Msg->to, "SYSOP") == 0))) + { + if ((Msg->status != 'K') && (Msg->status != 'H') && (Msg->status != 'F') && (Msg->status != 'D')) + { + if (Msg->status != 'Y') + { + Msg->status = 'Y'; + Msg->datechanged=time(NULL); + SaveMessageDatabase(); + } + } + } + } + else + { + nodeprintf(conn, "File for Message %d not found\r", msgno); + } +} + struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop) + { + int m=NumberofMessages; + + struct MsgInfo * Msg; + + do + { + m = GetUserMsg(m, Call, sysop); + + if (m == 0) + return NULL; + + Msg=MsgHddrPtr[m]; + + if (Msg->number == msgno) + return Msg; + + m--; + + } while (m> 0); + + return NULL; + +} + + +char * ReadInfoFile(char * File) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + char * ptr1 = 0, * ptr2; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%s", BaseDir, File); + + if (stat(MsgFile, &STAT) == -1) + return NULL; + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + return NULL; + + MsgBytes=malloc(FileSize+1); + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize] = 0; + + ptr1 = MsgBytes; + + // Replace LF or CRLF with CR + + // First remove cr from crlf + + while(ptr2 = strstr(ptr1, "\r\n")) + { + memmove(ptr2, ptr2 + 1, strlen(ptr2)); + } + + // Now replace lf with cr + + ptr1 = MsgBytes; + + while (*ptr1) + { + if (*ptr1 == '\n') + *(ptr1) = '\r'; + + ptr1++; + } + + return MsgBytes; +} + +char * FormatDateAndTime(time_t Datim, BOOL DateOnly) +{ + struct tm *tm; + static char Date[]="xx-xxx hh:mmZ"; + + tm = gmtime(&Datim); + + if (tm) + sprintf_s(Date, sizeof(Date), "%02d-%3s %02d:%02dZ", + tm->tm_mday, month[tm->tm_mon], tm->tm_hour, tm->tm_min); + + if (DateOnly) + { + Date[6]=0; + return Date; + } + + return Date; +} + +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); + + +BOOL DoSendCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context) +{ + // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY + + char * From = NULL; + char * BID = NULL; + char * ATBBS = NULL; + char seps[] = " \t\r"; + struct MsgInfo * OldMsg; + char OldTitle[62]; + char NewTitle[62]; + char To[100]= ""; + int msgno; + + if (Cmd[1] == 0) Cmd[1] ='P'; // Just S means SP + + switch (toupper(Cmd[1])) + { + case 'B': + + if (RefuseBulls) + { + nodeprintf(conn, "*** Error: This system doesn't allow sending Bulls\r"); + return FALSE; + } + + if (user->flags & F_NOBULLS) + { + nodeprintf(conn, "*** Error: You are not allowed to send Bulls\r"); + return FALSE; + } + + + case 'P': + case 'T': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); + return FALSE; + } + + strcpy(To, Arg1); + + if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + return FALSE; + + return CreateMessage(conn, From, To, ATBBS, toupper(Cmd[1]), BID, NULL); + + case 'R': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: Message Number is missing\r"); + return FALSE; + } + + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return FALSE; + } + + OldMsg = FindMessage(user->Call, msgno, conn->sysop); + + if (OldMsg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return FALSE; + } + + Arg1=&OldMsg->from[0]; + + strcpy(To, Arg1); + + if (_stricmp(Arg1, "SMTP:") == 0 || _stricmp(Arg1, "RMS:") == 0 || OldMsg->emailfrom) + { + // SMTP message. Need to get the real sender from the message + + sprintf(To, "%s%s", Arg1, OldMsg->emailfrom); + } + + if (!DecodeSendParams(conn, "", &From, To, &ATBBS, &BID)) + return FALSE; + + strcpy(OldTitle, OldMsg->title); + + if (strlen(OldTitle) > 57) OldTitle[57] = 0; + + strcpy(NewTitle, "Re:"); + strcat(NewTitle, OldTitle); + + return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); + + return TRUE; + + case 'C': + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: Message Number is missing\r"); + return FALSE; + } + + msgno = atoi(Arg1); + + if (msgno > 100000) + { + BBSputs(conn, "Message Number too high\r"); + return FALSE; + } + + Arg1 = strtok_s(NULL, seps, &Context); + + if (Arg1 == NULL) + { + nodeprintf(conn, "*** Error: The 'TO' callsign is missing\r"); + return FALSE; + } + + strcpy(To, Arg1); + + if (!DecodeSendParams(conn, Context, &From, To, &ATBBS, &BID)) + return FALSE; + + OldMsg = FindMessage(user->Call, msgno, conn->sysop); + + if (OldMsg == NULL) + { + nodeprintf(conn, "Message %d not found\r", msgno); + return FALSE; + } + + strcpy(OldTitle, OldMsg->title); + + if (strlen(OldTitle) > 56) OldTitle[56] = 0; + + strcpy(NewTitle, "Fwd:"); + strcat(NewTitle, OldTitle); + + conn->CopyBuffer = ReadMessageFile(msgno); + + return CreateMessage(conn, From, To, ATBBS, 'P', BID, NewTitle); + } + + + nodeprintf(conn, "*** Error: Invalid Send option %c\r", Cmd[1]); + + return FALSE; +} + +char * CheckToAddress(CIRCUIT * conn, char * Addr) +{ + // Check one element of Multiple Address + + if (conn == NULL || !(conn->BBSFlags & BBS)) + { + // if a normal user, check that TO and/or AT are known and warn if not. + + if (_stricmp(Addr, "SYSOP") == 0) + { + return _strdup(Addr); + } + + if (SendBBStoSYSOPCall) + if (_stricmp(Addr, BBSName) == 0) + return _strdup(SYSOPCall); + + + if (strchr(Addr, '@') == 0) + { + // No routing, if not a user and not known to forwarding or WP warn + + struct UserInfo * ToUser = LookupCall(Addr); + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->HomeBBS[0]) + { + char * NewAddr = malloc(250); + if (conn) + nodeprintf(conn, "Address %s - @%s added from HomeBBS\r", Addr, ToUser->HomeBBS); + sprintf(NewAddr, "%s@%s", Addr, ToUser->HomeBBS); + return NewAddr; + } + } + else + { + WPRecP WP = LookupWP(Addr); + + if (WP) + { + char * NewAddr = malloc(250); + + if (conn) + nodeprintf(conn, "Address %s - @%s added from WP\r", Addr, WP->first_homebbs); + sprintf(NewAddr, "%s@%s", Addr, WP->first_homebbs); + return NewAddr; + } + } + } + } + + // Check SMTP and RMS Addresses + + if ((_memicmp(Addr, "rms:", 4) == 0) || (_memicmp(Addr, "rms/", 4) == 0)) + { + Addr[3] = ':'; // Replace RMS/ with RMS: + + if (conn && !FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + } + else if ((_memicmp(Addr, "smtp:", 5) == 0) || (_memicmp(Addr, "smtp/", 5) == 0)) + { + Addr[4] = ':'; // Replace smpt/ with smtp: + + if (ISP_Gateway_Enabled) + { + if (conn && (conn->UserPointer->flags & F_EMAIL) == 0) + { + nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); + return FALSE; + } + } + else + { + if (conn) + nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); + return FALSE; + } + } + + return _strdup(Addr); +} + + +char Winlink[] = "WINLINK.ORG"; + +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char *To, char ** ATBBS, char ** BID) +{ + char * ptr; + char seps[] = " \t\r"; + WPRecP WP; + char * ToCopy = _strdup(To); + int Len; + + conn->ToCount = 0; + + // SB WANT @ ALLCAN < N6ZFJ $4567_N0ARY + + // Having trailing ; will mess up parsing multiple addresses, so remove. + + while (To[strlen(To) - 1] == ';') + To[strlen(To) - 1] = 0; + + if (strchr(Context, ';') || strchr(To, ';')) + { + // Multiple Addresses - put address list back together + + char * p; + + To[strlen(To)] = ' '; + Context = To; + + while (p = strchr(Context, ';')) + { + // Multiple Addressees + + To = strtok_s(NULL, ";", &Context); + Len = (int)strlen(To); + conn->To = realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); + if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) + conn->ToCount++; + } + + To = strtok_s(NULL, seps, &Context); + + Len = (int)strlen(To); + conn->To=realloc(conn->To, (conn->ToCount+1) * sizeof(void *)); + if (conn->To[conn->ToCount] = CheckToAddress(conn, To)) + conn->ToCount++; + } + else + { + // Single Call + + // accept CALL!CALL for source routed message + + if (strchr(To, '@') == 0 && strchr(To, '!')) // Bang route without @ + { + char * bang = strchr(To, '!'); + + memmove(bang + 1, bang, strlen(bang)); // Move !call down one + + *ATBBS = strlop(To, '!');; + } + + // Accept call@call (without spaces) - but check for smtp addresses + + if (_memicmp(To, "smtp:", 5) != 0 && _memicmp(To, "rms:", 4) != 0 && _memicmp(To, "rms/", 4) != 0) + { + ptr = strchr(To, '@'); + + if (ptr) + { + // If looks like a valid email address, treat as such + + int tolen; + *ATBBS = strlop(To, '@'); + + strlop(To, '-'); // Cant have SSID on BBS Name + + tolen = (int)strlen(To); + + if (tolen > 6 || !CheckifPacket(*ATBBS)) + { + // Probably Email address. Add smtp: or rms: + + if (FindRMS() || strchr(*ATBBS, '!')) // have RMS or source route + sprintf(To, "rms:%s", ToCopy); + else if (ISP_Gateway_Enabled) + sprintf(To, "smtp:%s", ToCopy); + else if (isAMPRMsg(ToCopy)) + sprintf(To, "rms:%s", ToCopy); + + } + } + } + } + + free(ToCopy); + + // Look for Optional fields; + + ptr = strtok_s(NULL, seps, &Context); + + while (ptr) + { + if (strcmp(ptr, "@") == 0) + { + *ATBBS = _strupr(strtok_s(NULL, seps, &Context)); + } + else if(strcmp(ptr, "<") == 0) + { + *From = strtok_s(NULL, seps, &Context); + } + else if (ptr[0] == '$') + *BID = &ptr[1]; + else + { + nodeprintf(conn, "*** Error: Invalid Format\r"); + return FALSE; + } + ptr = strtok_s(NULL, seps, &Context); + } + + // Only allow < from a BBS + + if (*From) + { + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "*** < can only be used by a BBS\r"); + return FALSE; + } + } + + if (!*From) + *From = conn->UserPointer->Call; + + if (!(conn->BBSFlags & BBS)) + { + // if a normal user, check that TO and/or AT are known and warn if not. + + if (_stricmp(To, "SYSOP") == 0) + { + conn->LocalMsg = TRUE; + return TRUE; + } + + if (!*ATBBS && conn->ToCount == 0) + { + // No routing, if not a user and not known to forwarding or WP warn + + struct UserInfo * ToUser = LookupCall(To); + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->flags & F_RMSREDIRECT) + { + // sent to Winlink + + *ATBBS = Winlink; + nodeprintf(conn, "Redirecting to winlink.org\r", *ATBBS); + } + else if (ToUser->HomeBBS[0]) + { + *ATBBS = ToUser->HomeBBS; + nodeprintf(conn, "Address @%s added from HomeBBS\r", *ATBBS); + } + else + { + conn->LocalMsg = TRUE; + } + } + else + { + conn->LocalMsg = FALSE; + WP = LookupWP(To); + + if (WP) + { + *ATBBS = WP->first_homebbs; + nodeprintf(conn, "Address @%s added from WP\r", *ATBBS); + } + } + } + } + return TRUE; +} + +BOOL CreateMessage(CIRCUIT * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title) +{ + struct MsgInfo * Msg, * TestMsg; + char * via = NULL; + char * FromHA; + + // Create a temp msg header entry + + if (conn->ToCount) + { + } + else + { + if (CheckRejFilters(From, ToCall, ATBBS, BID, MsgType)) + { + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - REJECTED\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error - Message Filters prevent sending this message\r"); + + return FALSE; + } + } + + Msg = malloc(sizeof (struct MsgInfo)); + + if (Msg == 0) + { + CriticalErrorHandler("malloc failed for new message header"); + return FALSE; + } + + memset(Msg, 0, sizeof (struct MsgInfo)); + + conn->TempMsg = Msg; + + Msg->type = MsgType; + + if (conn->UserPointer->flags & F_HOLDMAIL) + Msg->status = 'H'; + else + Msg->status = 'N'; + + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + if (BID) + { + BIDRec * TempBID; + + // If P Message, dont immediately reject on a Duplicate BID. Check if we still have the message + // If we do, reject it. If not, accept it again. (do we need some loop protection ???) + + TempBID = LookupBID(BID); + + if (TempBID) + { + if (MsgType == 'B') + { + // Duplicate bid + + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - BID\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error- Duplicate BID\r"); + + return FALSE; + } + + TestMsg = GetMsgFromNumber(TempBID->u.msgno); + + // if the same TO we will assume the same message + + if (TestMsg && strcmp(TestMsg->to, ToCall) == 0) + { + // We have this message. If we have already forwarded it, we should accept it again + + if ((TestMsg->status == 'N') || (TestMsg->status == 'Y')|| (TestMsg->status == 'H')) + { + // Duplicate bid + + if ((conn->BBSFlags & BBS)) + { + nodeprintf(conn, "NO - BID\r"); + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + else + nodeprintf(conn, "*** Error- Duplicate BID\r"); + + return FALSE; + } + } + } + + if (strlen(BID) > 12) BID[12] = 0; + strcpy(Msg->bid, BID); + + // Save BID in temp list in case we are offered it again before completion + + TempBID = AllocateTempBIDRecord(); + strcpy(TempBID->BID, BID); + TempBID->u.conn = conn; + + } + + if (conn->ToCount) + { + } + else + { + if (_memicmp(ToCall, "rms:", 4) == 0) + { + if (!FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + + via=strlop(ToCall, ':'); + _strupr(ToCall); + } + else if (_memicmp(ToCall, "rms/", 4) == 0) + { + if (!FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } + + via=strlop(ToCall, '/'); + _strupr(ToCall); + } + else if (_memicmp(ToCall, "smtp:", 5) == 0) + { + if (ISP_Gateway_Enabled) + { + if ((conn->UserPointer->flags & F_EMAIL) == 0) + { + nodeprintf(conn, "*** Error - You need to ask the SYSOP to allow you to use Internet Mail\r"); + return FALSE; + } + via=strlop(ToCall, ':'); + ToCall[0] = 0; + } + else + { + nodeprintf(conn, "*** Error - Sending mail to smtp addresses is disabled\r"); + return FALSE; + } + } + else + { + _strupr(ToCall); + if (ATBBS) + via=_strupr(ATBBS); + } + + strlop(ToCall, '-'); // Remove any (illegal) ssid + if (strlen(ToCall) > 6) ToCall[6] = 0; + + strcpy(Msg->to, ToCall); + + if (SendBBStoSYSOPCall) + if (_stricmp(ToCall, BBSName) == 0) + strcpy(Msg->to, SYSOPCall); + + if (via) + { + if (strlen(via) > 40) via[40] = 0; + + strcpy(Msg->via, via); + } + + } // End of Multiple Dests + + // Look for HA in From (even if we shouldn't be getting it!) + + FromHA = strlop(From, '@'); + + + strlop(From, '-'); // Remove any (illegal) ssid + if (strlen(From) > 6) From[6] = 0; + strcpy(Msg->from, From); + + if (FromHA) + { + if (strlen(FromHA) > 39) FromHA[39] = 0; + Msg->emailfrom[0] = '@'; + strcpy(&Msg->emailfrom[1], _strupr(FromHA)); + } + + if (Title) // Only used by SR and SC + { + strcpy(Msg->title, Title); + conn->Flags |= GETTINGMESSAGE; + + // Create initial buffer of 10K. Expand if needed later + + conn->MailBuffer=malloc(10000); + conn->MailBufferSize=10000; + + nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); + return TRUE; + } + + if (conn->BBSFlags & FLARQMODE) + return TRUE; + + if (!(conn->BBSFlags & FBBCompressed)) + conn->Flags |= GETTINGTITLE; + + if (!(conn->BBSFlags & BBS)) + nodeprintf(conn, "Enter Title (only):\r"); + else + if (!(conn->BBSFlags & FBBForwarding)) + nodeprintf(conn, "OK\r"); + + return TRUE; +} + +VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int msglen) +{ + + conn->Flags &= ~GETTINGTITLE; + + if (msglen == 1) + { + nodeprintf(conn, "*** Message Cancelled\r"); + SendPrompt(conn, user); + return; + } + + if (msglen > 60) msglen = 60; + + Buffer[msglen-1] = 0; + + strcpy(conn->TempMsg->title, Buffer); + + // Create initial buffer of 10K. Expand if needed later + + conn->MailBuffer=malloc(10000); + conn->MailBufferSize=10000; + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to create Message Buffer\r"); + return; + } + + conn->Flags |= GETTINGMESSAGE; + + if (!conn->BBSFlags & BBS) + nodeprintf(conn, "Enter Message Text (end with /ex or ctrl/z)\r"); + +} + +VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int msglen) +{ + char * ptr2 = NULL; + + if (((msglen < 3) && (Buffer[0] == 0x1a)) || ((msglen == 4) && (_memicmp(Buffer, "/ex", 3) == 0))) + { + int Index = 0; + + if (conn->TempMsg->type == 'P') + Index = PMSG; + else if (conn->TempMsg->type == 'B') + Index = BMSG; + else if (conn->TempMsg->type == 'T') + Index = TMSG; + + conn->Flags &= ~GETTINGMESSAGE; + + user->Total.MsgsReceived[Index]++; + user->Total.BytesForwardedIn[Index] += conn->TempMsg->length; + + if (conn->ToCount) + { + // Multiple recipients + + struct MsgInfo * Msg = conn->TempMsg; + int i; + struct MsgInfo * SaveMsg = Msg; + char * SaveBody = conn->MailBuffer; + int SaveMsgLen = Msg->length; + BOOL SentToRMS = FALSE; + int ToLen = 0; + char * ToString = zalloc(conn->ToCount * 100); + + // If no BID provided, allocate one + + if (Msg->bid[0] == 0) + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg + 1, BBSName); + + for (i = 0; i < conn->ToCount; i++) + { + char * Addr = conn->To[i]; + char * Via; + + if (_memicmp (Addr, "SMTP:", 5) == 0) + { + // For Email + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 10); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + Msg->to[0] = 0; + strcpy(Msg->via, &Addr[5]); + + CreateMessageFromBuffer(conn); + continue; + } + + if (_memicmp (Addr, "RMS:", 4) == 0) + { + // Add to B2 Message for RMS + + Addr+=4; + + Via = strlop(Addr, '@'); + + if (Via && _stricmp(Via, "winlink.org") == 0) + { + if (CheckifLocalRMSUser(Addr)) + { + // Local RMS - Leave Here + + Via = 0; // Drop Through + goto PktMsg; + } + else + { + ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); + continue; + } + } + + ToLen = sprintf(ToString, "%sTo: %s@%s\r\n", ToString, Addr, Via); + continue; + } + + _strupr(Addr); + + Via = strlop(Addr, '@'); + + if (Via && _stricmp(Via, "winlink.org") == 0) + { + if (CheckifLocalRMSUser(Addr)) + { + // Local RMS - Leave Here + + Via = 0; // Drop Through + } + else + { + ToLen = sprintf(ToString, "%sTo: %s\r\n", ToString, Addr); + + // Add to B2 Message for RMS + + continue; + } + } + + PktMsg: + + conn->LocalMsg = FALSE; + + // Normal BBS Message + + if (_stricmp(Addr, "SYSOP") == 0) + conn->LocalMsg = TRUE; + else + { + struct UserInfo * ToUser = LookupCall(Addr); + + if (ToUser) + conn->LocalMsg = TRUE; + } + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 10); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + strcpy(Msg->to, Addr); + + if (Via) + { + Msg->bid[0] = 0; // if we are forwarding it, we must change BID to be safe + strcpy(Msg->via, Via); + } + + CreateMessageFromBuffer(conn); + } + + if (ToLen) + { + char * B2Hddr = zalloc(ToLen + 1000); + int B2HddrLen; + char DateString[80]; + struct tm * tm; + time_t Date = time(NULL); + char Type[16] = "Private"; + + // Get Type + + if (conn->TempMsg->type == 'B') + strcpy(Type, "Bulletin"); + else if (conn->TempMsg->type == 'T') + strcpy(Type, "Traffic"); + + tm = gmtime(&Date); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000 + ToLen); + + Msg->B2Flags = B2Msg; + + B2HddrLen = sprintf(B2Hddr, + "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\n%sSubject: %s\r\nMbo: %s\r\nBody: %d\r\n\r\n", + SaveMsg->bid, DateString, Type, + SaveMsg->from, ToString, SaveMsg->title, BBSName, SaveMsgLen); + + memcpy(conn->MailBuffer, B2Hddr, B2HddrLen); + memcpy(&conn->MailBuffer[B2HddrLen], SaveBody, SaveMsgLen); + + Msg->length += B2HddrLen; + + strcpy(Msg->to, "RMS"); + + CreateMessageFromBuffer(conn); + + free(B2Hddr); + } + + free(SaveMsg); + free(SaveBody); + conn->MailBuffer = NULL; + conn->MailBufferSize=0; + + if (!(conn->BBSFlags & BBS)) + SendPrompt(conn, conn->UserPointer); + else + if (!(conn->BBSFlags & FBBForwarding)) + { + if (conn->BBSFlags & OUTWARDCONNECT) + BBSputs(conn, "F>\r"); // if Outward connect must be reverse forward + else + BBSputs(conn, ">\r"); + } + + /* + // From a client - Create one copy with all RMS recipients, and another for each packet recipient + + // Merge all RMS To: lines + + ToLen = 0; + ToString[0] = 0; + + for (i = 0; i < Recipients; i++) + { + if (LocalMsg[i]) + continue; // For a local RMS user + + if (_stricmp(Via[i], "WINLINK.ORG") == 0 || _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || + _stricmp(RecpTo[i], "RMS") == 0) + { + ToLen += strlen(HddrTo[i]); + strcat(ToString, HddrTo[i]); + } + } + + if (ToLen) + { + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + + memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); + memcpy(&conn->MailBuffer[B2To], ToString, ToLen); + + conn->TempMsg->length += ToLen; + + strcpy(Msg->to, "RMS"); + strcpy(Msg->via, "winlink.org"); + + // Must Change the BID + + Msg->bid[0] = 0; + + CreateMessageFromBuffer(conn); + } + + } + + free(ToString); + + for (i = 0; i < Recipients; i++) + { + // Only Process Non - RMS Dests or local RMS Users + + if (LocalMsg[i] == 0) + if (_stricmp (Via[i], "WINLINK.ORG") == 0 || + _memicmp (&HddrTo[i][4], "SMTP:", 5) == 0 || + _stricmp(RecpTo[i], "RMS") == 0) + continue; + + conn->TempMsg = Msg = malloc(sizeof(struct MsgInfo)); + memcpy(Msg, SaveMsg, sizeof(struct MsgInfo)); + + conn->MailBuffer = malloc(SaveMsgLen + 1000); + memcpy(conn->MailBuffer, SaveBody, SaveMsgLen); + + // Add our To: + + ToLen = strlen(HddrTo[i]); + + if (_memicmp(HddrTo[i], "CC", 2) == 0) // Replace CC: with TO: + memcpy(HddrTo[i], "To", 2); + + memmove(&conn->MailBuffer[B2To + ToLen], &conn->MailBuffer[B2To], count); + memcpy(&conn->MailBuffer[B2To], HddrTo[i], ToLen); + + conn->TempMsg->length += ToLen; + + strcpy(Msg->to, RecpTo[i]); + strcpy(Msg->via, Via[i]); + + Msg->bid[0] = 0; + + CreateMessageFromBuffer(conn); + } + } // End not from RMS + + free(SaveMsg); + free(SaveBody); + conn->MailBuffer = NULL; + conn->MailBufferSize=0; + + SetupNextFBBMessage(conn); + return; + + } My__except_Routine("Process Multiple Destinations"); + + BBSputs(conn, "*** Program Error Processing Multiple Destinations\r"); + Flush(conn); + conn->CloseAfterFlush = 20; // 2 Secs + + return; +*/ + + conn->ToCount = 0; + + return; + } + + + CreateMessageFromBuffer(conn); + return; + + } + + Buffer[msglen++] = 0x0a; + + if ((conn->TempMsg->length + msglen) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to extend Message Buffer\r"); + + conn->Flags &= ~GETTINGMESSAGE; + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, msglen); + + conn->TempMsg->length += msglen; +} + +VOID CreateMessageFromBuffer(CIRCUIT * conn) +{ + struct MsgInfo * Msg; + BIDRec * BIDRec; + char * ptr1, * ptr2 = NULL; + char * ptr3, * ptr4; + int FWDCount = 0; + char OldMess[] = "\r\n\r\nOriginal Message:\r\n\r\n"; + time_t Age; + int OurCount; + char * HoldReason = "User has Hold Messages flag set"; + struct UserInfo * user; + + +#ifndef LINBPQ + struct _EXCEPTION_POINTERS exinfo; +#endif + + // If doing SC, Append Old Message + + if (conn->CopyBuffer) + { + if ((conn->TempMsg->length + (int) strlen(conn->CopyBuffer) + 80 )> conn->MailBufferSize) + { + conn->MailBufferSize += (int)strlen(conn->CopyBuffer) + 80; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + nodeprintf(conn, "Failed to extend Message Buffer\r"); + + conn->Flags &= ~GETTINGMESSAGE; + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], OldMess, strlen(OldMess)); + + conn->TempMsg->length += (int)strlen(OldMess); + + memcpy(&conn->MailBuffer[conn->TempMsg->length], conn->CopyBuffer, strlen(conn->CopyBuffer)); + + conn->TempMsg->length += (int)strlen(conn->CopyBuffer); + + free(conn->CopyBuffer); + conn->CopyBuffer = NULL; + } + + // Allocate a message Record slot + + Msg = AllocateMsgRecord(); + memcpy(Msg, conn->TempMsg, sizeof(struct MsgInfo)); + + free(conn->TempMsg); + + // Set number here so they remain in sequence + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + FreeSemaphore(&MsgNoSemaphore); + MsgnotoMsg[Msg->number] = Msg; + + if (Msg->status == 0) + Msg->status = 'N'; + + // Create BID if non supplied + + if (Msg->bid[0] == 0) + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + // if message body had R: lines, get date created from last (not very accurate, but best we can do) + + // Also check if we have had message before to detect loops + + ptr1 = conn->MailBuffer; + OurCount = 0; + + // If it is a B2 Message, Must Skip B2 Header + + if (Msg->B2Flags & B2Msg) + { + ptr1 = strstr(ptr1, "\r\n\r\n"); + if (ptr1) + ptr1 += 4; + else + ptr1 = conn->MailBuffer; + } + +nextline: + + if (memcmp(ptr1, "R:", 2) == 0) + { + // Is if ours? + + // BPQ RLINE Format R:090920/1041Z 6542@N4JOA.#WPBFL.FL.USA.NOAM BPQ1.0.2 + + ptr3 = strchr(ptr1, '@'); + ptr4 = strchr(ptr1, '.'); + + if (ptr3 && ptr4 && (ptr4 > ptr3)) + { + if (memcmp(ptr3+1, BBSName, ptr4-ptr3-1) == 0) + OurCount++; + } + + GetWPBBSInfo(ptr1); // Create WP /I record from R: Line + + // see if another + + ptr2 = ptr1; // save + ptr1 = strchr(ptr1, '\r'); + if (ptr1 == 0) + { + Debugprintf("Corrupt Message %s from %s - truncated within R: line", Msg->bid, Msg->from); + return; + } + ptr1++; + if (*ptr1 == '\n') ptr1++; + + goto nextline; + } + + // ptr2 points to last R: line (if any) + + if (ptr2) + { + struct tm rtime; + time_t result; + + memset(&rtime, 0, sizeof(struct tm)); + + if (ptr2[10] == '/') + { + // Dodgy 4 char year + + sscanf(&ptr2[2], "%04d%02d%02d/%02d%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); + rtime.tm_year -= 1900; + rtime.tm_mon--; + } + else if (ptr2[8] == '/') + { + sscanf(&ptr2[2], "%02d%02d%02d/%02d%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min); + + if (rtime.tm_year < 90) + rtime.tm_year += 100; // Range 1990-2089 + rtime.tm_mon--; + } + + // Otherwise leave date as zero, which should be rejected + + // result = _mkgmtime(&rtime); + + if ((result = mktime(&rtime)) != (time_t)-1 ) + { + result -= (time_t)_MYTIMEZONE; + + Msg->datecreated = result; + Age = (time(NULL) - result)/86400; + + if ( Age < -7) + { + Msg->status = 'H'; + HoldReason = "Suspect Date Sent"; + } + else if (Age > BidLifetime || Age > MaxAge) + { + Msg->status = 'H'; + HoldReason = "Message too old"; + + } + else + GetWPInfoFromRLine(Msg->from, ptr2, result); + } + else + { + // Can't decode R: Datestamp + + Msg->status = 'H'; + HoldReason = "Corrupt R: Line - can't determine age"; + } + + if (OurCount > 1) + { + // Message is looping + + Msg->status = 'H'; + HoldReason = "Message may be looping"; + + } + } + + if (strcmp(Msg->to, "WP") == 0) + { + // If Reject WP Bulls is set, Kill message here. + // It should only get here if B2 - otherwise it should be + // rejected earlier + + if (Msg->type == 'B' && FilterWPBulls) + Msg->status = 'K'; + + } + + conn->MailBuffer[Msg->length] = 0; + + if (CheckBadWords(Msg->title) || CheckBadWords(conn->MailBuffer)) + { + Msg->status = 'H'; + HoldReason = "Bad word in title or body"; + } + + if (CheckHoldFilters(Msg->from, Msg->to, Msg->via, Msg->bid)) + { + Msg->status = 'H'; + HoldReason = "Matched Hold Filters"; + } + + if (CheckValidCall(Msg->from) == 0) + { + Msg->status = 'H'; + HoldReason = "Probable Invalid From Call"; + } + + // Process any WP Messages + + if (strcmp(Msg->to, "WP") == 0) + { + if (Msg->status == 'N') + { + ProcessWPMsg(conn->MailBuffer, Msg->length, ptr2); + + if (Msg->type == 'P') // Kill any processed private WP messages. + { + char VIA[80]; + + strcpy(VIA, Msg->via); + strlop(VIA, '.'); + + if (strcmp(VIA, BBSName) == 0) + Msg->status = 'K'; + } + } + } + + CreateMessageFile(conn, Msg); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + if (Msg->length > MaxTXSize) + { + Msg->status = 'H'; + HoldReason = "Message too long"; + + if (!(conn->BBSFlags & BBS)) + nodeprintf(conn, "*** Warning Message length exceeds sysop-defined maximum of %d - Message will be held\r", MaxTXSize); + } + + // Check for message to internal server + + if (Msg->via[0] == 0 + || _stricmp(Msg->via, BBSName) == 0 // our BBS a + || _stricmp(Msg->via, AMPRDomain) == 0) // our AMPR Address + { + if (CheckforMessagetoServer(Msg)) + { + // Flag as killed and send prompt + + FlagAsKilled(Msg, TRUE); + + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "Message %d to Server Processed and Killed.\r", Msg->number); + SendPrompt(conn, conn->UserPointer); + } + return; // no need to process further + } + } + + if (Msg->to[0]) + FWDCount = MatchMessagetoBBSList(Msg, conn); + else + { + // If addressed @winlink.org, and to a local user, Keep here. + + char * Call; + char * AT; + + // smtp or rms - don't warn no route + + FWDCount = 1; + + Call = _strupr(_strdup(Msg->via)); + AT = strlop(Call, '@'); + + if (AT && _stricmp(AT, "WINLINK.ORG") == 0) + { + struct UserInfo * user = LookupCall(Call); + + if (user) + { + if (user->flags & F_POLLRMS) + { + Logprintf(LOG_BBS, conn, '?', "SMTP Message @ winlink.org, but local RMS user - leave here"); + strcpy(Msg->to, Call); + strcpy(Msg->via, AT); + if (user->flags & F_BBS) // User is a BBS, so set FWD bit so he can get it + set_fwd_bit(Msg->fbbs, user->BBSNumber); + + } + } + } + free(Call); + } + + // Warn SYSOP if P or T forwarded in, and has nowhere to go + + if ((conn->BBSFlags & BBS) && Msg->type != 'B' && FWDCount == 0 && WarnNoRoute && + strcmp(Msg->to, "SYSOP") && strcmp(Msg->to, "WP")) + { + if (Msg->via[0]) + { + if (_stricmp(Msg->via, BBSName)) // Not for our BBS a + if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address + SendWarningToSYSOP(Msg); + } + else + { + // No via - is it for a local user? + + if (LookupCall(Msg->to) == 0) + SendWarningToSYSOP(Msg); + } + } + + if ((conn->BBSFlags & SYNCMODE) == 0) + { + if (!(conn->BBSFlags & BBS)) + { + nodeprintf(conn, "Message: %d Bid: %s Size: %d\r", Msg->number, Msg->bid, Msg->length); + + if (Msg->via[0]) + { + if (_stricmp(Msg->via, BBSName)) // Not for our BBS a + if (_stricmp(Msg->via, AMPRDomain)) // Not for our AMPR Address + + if (FWDCount == 0 && Msg->to[0] != 0) // unless smtp msg + nodeprintf(conn, "@BBS specified, but no forwarding info is available - msg may not be delivered\r"); + } + else + { + if (FWDCount == 0 && conn->LocalMsg == 0 && Msg->type != 'B') + // Not Local and no forward route + nodeprintf(conn, "Message is not for a local user, and no forwarding info is available - msg may not be delivered\r"); + } + if (conn->ToCount == 0) + SendPrompt(conn, conn->UserPointer); + } + else + { + if (!(conn->BBSFlags & FBBForwarding)) + { + if (conn->ToCount == 0) + if (conn->BBSFlags & OUTWARDCONNECT) + nodeprintf(conn, "F>\r"); // if Outward connect must be reverse forward + else + nodeprintf(conn, ">\r"); + } + } + } + + if(Msg->to[0] == 0) + SMTPMsgCreated=TRUE; + + if (Msg->status != 'H' && Msg->type == 'B' && memcmp(Msg->fbbs, zeros, NBMASK) != 0) + Msg->status = '$'; // Has forwarding + + if (Msg->status == 'H') + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, HoldReason); + SendMessageToSYSOP(Title, MailBuffer, Length); + } + + BuildNNTPList(Msg); // Build NNTP Groups list + + SaveMessageDatabase(); + SaveBIDDatabase(); + + // If Event Notifications enabled report a new message event + + if (reportNewMesageEvents) + { + char msg[200]; + + //12345 B 2053 TEST@ALL F6FBB 920325 This is the subject + + struct tm *tm = gmtime((time_t *)&Msg->datecreated); + + sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r", + Msg->number, Msg->type, Msg->length, Msg->to, + Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title); + +#ifdef WIN32 + if (pRunEventProgram) + pRunEventProgram("MailNewMsg.exe", msg); +#else + { + char prog[256]; + sprintf(prog, "%s/%s", BPQDirectory, "MailNewMsg"); + RunEventProgram(prog, msg); + } +#endif + } + + + if (EnableUI) +#ifdef LINBPQ + SendMsgUI(Msg); +#else + __try + { + SendMsgUI(Msg); + } + My__except_Routine("SendMsgUI"); +#endif + + user = LookupCall(Msg->to); + + if (user && (user->flags & F_APRSMFOR)) + { + char APRS[128]; + char Call[16]; + int SSID = user->flags >> 28; + + if (SSID) + sprintf(Call, "%s-%d", Msg->to, SSID); + else + strcpy(Call, Msg->to); + + sprintf(APRS, "New BBS Message %s From %s", Msg->title, Msg->from); + APISendAPRSMessage(APRS, Call); + } + + return; +} + +VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg) +{ + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + char Mess[255]; + int len; + BOOL AutoImport = FALSE; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + // If title is "Batched messages for AutoImport from BBS xxxxxx and first line is S? and it is + // for this BBS, Import file and set message as Killed. May need to strip B2 Header and R: lines + + if (strcmp(Msg->to, BBSName) == 0 && strstr(Msg->title, "Batched messages for AutoImport from BBS ")) + { + UCHAR * ptr = conn->MailBuffer; + + // If it is a B2 Message, Must Skip B2 Header + + if (Msg->B2Flags & B2Msg) + { + ptr = strstr(ptr, "\r\n\r\n"); + if (ptr) + ptr += 4; + else + ptr = conn->MailBuffer; + } + + if (memcmp(ptr, "R:", 2) == 0) + { + ptr = strstr(ptr, "\r\n\r\n"); //And remove R: Lines + if (ptr) + ptr += 4; + } + + if (*(ptr) == 'S' && ptr[2] == ' ') + { + int HeaderLen = (int)(ptr - conn->MailBuffer); + Msg->length -= HeaderLen; + memmove(conn->MailBuffer, ptr, Msg->length); + Msg->status = 'K'; + AutoImport = TRUE; + } + } + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(conn->MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + if (AutoImport) + ImportMessages(NULL, MsgFile, TRUE); + + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + if (WriteLen != Msg->length) + { + len = sprintf_s(Mess, sizeof(Mess), "Failed to create Message File\r"); + QueueMsg(conn, Mess, len); + Debugprintf(Mess); + } + return; +} + + + + +VOID SendUnbuffered(int stream, char * msg, int len) +{ +#ifndef LINBPQ + if (stream < 0) + WritetoConsoleWindow(stream, msg, len); + else +#endif + SendMsg(stream, msg, len); +} + +BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen); + +BOOL FindMessagestoForward (CIRCUIT * conn) +{ + struct UserInfo * user = conn->UserPointer; + +#ifndef LINBPQ + + struct _EXCEPTION_POINTERS exinfo; + + __try { +#endif + + // This is a hack so Winpack or WLE users can use forwarding + // protocols to get their messages without being defined as a BBS + + // !!IMPORTANT Getting this wrong can see message repeatedly proposed !! + // !! Anything sent using this must be killed if sent or rejected. + + // I'm not sure about this. I think I only need the PacLinkCalls + // if from RMS Express, and it always sends an FW; line + // Ah, no. What about WinPack ?? + // If from RMS Express must have Temp_B2 or BBS set or SID will + // be rejected. So maybe just Temp_B2 && BBS = 0?? + // No, someone may have Temp_B2 set and not be using WLE ?? So what ?? + + if ((user->flags & F_Temp_B2_BBS) && ((user->flags & F_BBS) == 0) || conn->RMSExpress || conn->PAT) + { + if (conn->PacLinkCalls == NULL) + { + // create a list with just the user call + + char * ptr1; + + conn->PacLinkCalls = zalloc(30); + + ptr1 = (char *)conn->PacLinkCalls; + ptr1 += 16; // Must be room for a null pointer on end (64 bit bug) + strcpy(ptr1, user->Call); + + conn->PacLinkCalls[0] = ptr1; + } + } + + if (conn->SendT && FindMessagestoForwardLoop(conn, 'T', conn->MaxTLen)) + { + conn->LastForwardType = 'T'; + return TRUE; + } + + if (conn->LastForwardType == 'T') + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (conn->SendP && FindMessagestoForwardLoop(conn, 'P', conn->MaxPLen)) + { + conn->LastForwardType = 'P'; + return TRUE; + } + + if (conn->LastForwardType == 'P') + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (conn->SendB && FindMessagestoForwardLoop(conn, 'B', conn->MaxBLen)) + { + conn->LastForwardType = 'B'; + return TRUE; + } + + conn->LastForwardType = 0; + return FALSE; +#ifndef LINBPQ + } My__except_Routine("FindMessagestoForward"); +#endif + return FALSE; + +} + + +BOOL FindMessagestoForwardLoop(CIRCUIT * conn, char Type, int MaxLen) +{ + // See if any messages are queued for this BBS + + int m; + struct MsgInfo * Msg; + struct UserInfo * user = conn->UserPointer; + struct FBBHeaderLine * FBBHeader; + BOOL Found = FALSE; + char RLine[100]; + int TotalSize = 0; + time_t NOW = time(NULL); + +// Debugprintf("FMTF entered Call %s Type %c Maxlen %d NextMsg = %d BBSNo = %d", +// conn->Callsign, Type, MaxLen, conn->NextMessagetoForward, user->BBSNumber); + + if (conn->PacLinkCalls || (conn->UserPointer->flags & F_NTSMPS)) // Looking for all messages, so reset + conn->NextMessagetoForward = 1; + + conn->FBBIndex = 0; + + for (m = conn->NextMessagetoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + // If an NTS MPS, see if anything matches + + if (Type == 'T' && (conn->UserPointer->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + int depth; + + if (Msg->type == 'T' && Msg->status == 'N' && Msg->length <= MaxLen && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + goto Forwardit; + + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + goto Forwardit; + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + goto Forwardit; + } + } + + // If forwarding to Paclink or RMS Express, look for any message matching the + // requested call list with status 'N' (maybe should also be 'P' ??) + + if (conn->PacLinkCalls) + { + int index = 1; + + char * Call = conn->PacLinkCalls[0]; + + while (Call) + { + if (Msg->type == Type && Msg->status == 'N') + { +// Debugprintf("Comparing RMS Call %s %s", Msg->to, Call); + if (_stricmp(Msg->to, Call) == 0) + if (Msg->status == 'N' && Msg->type == Type && Msg->length <= MaxLen) + goto Forwardit; + else + Debugprintf("Call Match but Wrong Type/Len %c %d", Msg->type, Msg->length); + } + Call = conn->PacLinkCalls[index++]; + } +// continue; + } + + if (Msg->type == Type && Msg->length <= MaxLen && (Msg->status != 'H') + && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + // Message to be sent - do a consistancy check (State, etc) + + Forwardit: + + if (Msg->Defered > 0) // = response received + { + Msg->Defered--; + Debugprintf("Message %d deferred", Msg->number); + continue; + } + + if ((Msg->from[0] == 0) || (Msg->to[0] == 0)) + { + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, "Missing From: or To: field"); + SendMessageToSYSOP(Title, MailBuffer, Length); + + Msg->status = 'H'; + continue; + } + + conn->NextMessagetoForward = m + 1; // So we don't offer again if defered + + Msg->Locked = 1; // So other MPS can't pick it up + + // if FBB forwarding add to list, eise save pointer + + if (conn->BBSFlags & FBBForwarding) + { + struct tm *tm; + time_t temp; + + FBBHeader = &conn->FBBHeaders[conn->FBBIndex++]; + + FBBHeader->FwdMsg = Msg; + FBBHeader->MsgType = Msg->type; + FBBHeader->Size = Msg->length; + TotalSize += Msg->length; + strcpy(FBBHeader->From, Msg->from); + strcpy(FBBHeader->To, Msg->to); + strcpy(FBBHeader->ATBBS, Msg->via); + strcpy(FBBHeader->BID, Msg->bid); + + // Set up R:Line, so se can add its length to the sise + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + FBBHeader->Size += sprintf_s(RLine, sizeof(RLine),"R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + Msg->number, BBSName, HRoute, RlineVer); + + // If using B2 forwarding we need the message size and Compressed size for FC proposal + + if (conn->BBSFlags & FBBB2Mode) + { + if (CreateB2Message(conn, FBBHeader, RLine) == FALSE) + { + char * MailBuffer = malloc(100); + char Title[100]; + int Length; + + // Corrupt B2 Message + + Debugprintf("Corrupt B2 Message found - Message %d will be held", Msg->number); + Msg->status = 'H'; + SaveMessageDatabase(); + + conn->FBBIndex--; + TotalSize -= Msg->length; + memset(&conn->FBBHeaders[conn->FBBIndex], 0, sizeof(struct FBBHeaderLine)); + + Length = sprintf(MailBuffer, "Message %d Held\r\n", Msg->number); + sprintf(Title, "Message %d Held - %s", Msg->number, "Corrupt B2 Message"); + SendMessageToSYSOP(Title, MailBuffer, Length); + + continue; + } + } + + if (conn->FBBIndex == 5 || TotalSize > user->ForwardingInfo->MaxFBBBlockSize) + return TRUE; // Got max number or too big + + Found = TRUE; // Remember we have some + } + else + { + conn->FwdMsg = Msg; + return TRUE; + } + } + } + + return Found; +} + +BOOL SeeifMessagestoForward (int BBSNumber, CIRCUIT * conn) +{ + // See if any messages are queued for this BBS + + // if Conn is not NULL, also check Msg Type + + int m; + struct MsgInfo * Msg; + + for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + if (conn) + { + char Type = Msg->type; + + if ((conn->SendB && Type == 'B') || (conn->SendP && Type == 'P') || (conn->SendT && Type == 'T')) + { +// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); + return TRUE; + } + } + else + { +// Debugprintf("SeeifMessagestoForward BBSNo %d Msg %d", BBSNumber, Msg->number); + return TRUE; + } + } + } + + return FALSE; +} + +int CountMessagestoForward (struct UserInfo * user) +{ + // See if any messages are queued for this BBS + + int m, n=0; + struct MsgInfo * Msg; + int BBSNumber = user->BBSNumber; + int FirstMessage = FirstMessageIndextoForward; + + if ((user->flags & F_NTSMPS)) + FirstMessage = 1; + + for (m = FirstMessage; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + n++; + continue; // So we dont count twice in Flag set and NTS MPS + } + + // if an NTS MPS, also check for any matches + + if (Msg->type == 'T' && (user->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + int depth; + + if (Msg->status == 'N' && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + { + n++; + continue; + } + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + { + n++; + continue; + } + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + { + n++; + continue; + } + } + } + } + + return n; +} + +int ListMessagestoForward(CIRCUIT * conn, struct UserInfo * user) +{ + // See if any messages are queued for this BBS + + int m, n=0; + struct MsgInfo * Msg; + int BBSNumber = user->BBSNumber; + int FirstMessage = FirstMessageIndextoForward; + + if ((user->flags & F_NTSMPS)) + FirstMessage = 1; + + for (m = FirstMessage; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; // So we dont count twice in Flag set and NTS MPS + } + + // if an NTS MPS, also check for any matches + + if (Msg->type == 'T' && (user->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + int depth; + + if (Msg->status == 'N' && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + { + nodeprintf(conn, "%d %s\r", Msg->number, Msg->title); + continue; + } + } + } + } + + return n; +} + +VOID SendWarningToSYSOP(struct MsgInfo * Msg) +{ + int Length=0; + char * MailBuffer = malloc(100); + char Title[100]; + + Length += sprintf(MailBuffer, "Warning - Message %d has nowhere to go", Msg->number); + sprintf(Title, "Warning - Message %d has nowhere to go", Msg->number); + SendMessageToSYSOP(Title, MailBuffer, Length); +} + + + +VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length) +{ + struct MsgInfo * Msg = AllocateMsgRecord(); + BIDRec * BIDRec; + + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + + Msg->length = Length; + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + FreeSemaphore(&MsgNoSemaphore); + + strcpy(Msg->from, "SYSTEM"); + if (SendSYStoSYSOPCall) + strcpy(Msg->to, SYSOPCall); + else + strcpy(Msg->to, "SYSOP"); + + strcpy(Msg->title, Title); + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + BIDRec = AllocateBIDRecord(); + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + MatchMessagetoBBSList(Msg, NULL); + free(MailBuffer); +} + +VOID CheckBBSNumber(int i) +{ + // Make sure number is unique + + int Count = 0; + struct UserInfo * user; + + for (user = BBSChain; user; user = user->BBSNext) + { + if (user->BBSNumber == i) + { + Count++; + + if (Count > 1) + { + // Second with same number - Renumber this one + + user->BBSNumber = FindFreeBBSNumber(); + + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; // cant really do much else + + Logprintf(LOG_BBS, NULL, '?', "Duplicate BBS Number found. BBS %s Old BBSNumber %d New BBS Number %d", user->Call, i, user->BBSNumber); + + } + } + } +} + + +int FindFreeBBSNumber() +{ + // returns the lowest number not used by any bbs or message. + + struct MsgInfo * Msg; + struct UserInfo * user; + int i, m; + + for (i = 1; i<= NBBBS; i++) + { + for (user = BBSChain; user; user = user->BBSNext) + { + if (user->BBSNumber == i) + goto nexti; // In use + } + + // Not used by BBS - check messages + + for (m = 1; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if (check_fwd_bit(Msg->fbbs, i)) + goto nexti; // In use + + if (check_fwd_bit(Msg->forw, i)) + goto nexti; // In use + } + + // Not in Use + + return i; + +nexti:; + + } + + return 0; // All used +} + +BOOL SetupNewBBS(struct UserInfo * user) +{ + user->BBSNumber = FindFreeBBSNumber(); + + if (user->BBSNumber == 0) + return FALSE; + + user->BBSNext = BBSChain; + BBSChain = user; + + SortBBSChain(); + + ReinitializeFWDStruct(user); + + return TRUE; +} + +VOID DeleteBBS(struct UserInfo * user) +{ + struct UserInfo * BBSRec, * PrevBBS = NULL; + +#ifndef LINBPQ + RemoveMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, MF_BYCOMMAND); +#endif + for (BBSRec = BBSChain; BBSRec; PrevBBS = BBSRec, BBSRec = BBSRec->BBSNext) + { + if (user == BBSRec) + { + if (PrevBBS == NULL) // First in chain; + { + BBSChain = BBSRec->BBSNext; + break; + } + PrevBBS->BBSNext = BBSRec->BBSNext; + break; + } + } +} + + +VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo); + +VOID SetupForwardingStruct(struct UserInfo * user) +{ + struct BBSForwardingInfo * ForwardingInfo; + + char Key[100] = "BBSForwarding."; + char Temp[100]; + + HKEY hKey=0; + char RegKey[100] = "SOFTWARE\\G8BPQ\\BPQ32\\BPQMailChat\\BBSForwarding\\"; + + int m; + struct MsgInfo * Msg; + + ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + + if (UsingingRegConfig == 0) + { + // Config from file + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + strcat(Key, "*"); + + strcat(Key, user->Call); + + group = config_lookup (&cfg, Key); + + if (group == NULL) // No info + return; + else + { + ForwardingInfo->TOCalls = GetMultiStringValue(group, "TOCalls"); + ForwardingInfo->ConnectScript = GetMultiStringValue(group, "ConnectScript"); + ForwardingInfo->ATCalls = GetMultiStringValue(group, "ATCalls"); + ForwardingInfo->Haddresses = GetMultiStringValue(group, "HRoutes"); + ForwardingInfo->HaddressesP = GetMultiStringValue(group, "HRoutesP"); + ForwardingInfo->FWDTimes = GetMultiStringValue(group, "FWDTimes"); + + ForwardingInfo->Enabled = GetIntValue(group, "Enabled"); + ForwardingInfo->ReverseFlag = GetIntValue(group, "RequestReverse"); + ForwardingInfo->AllowBlocked = GetIntValue(group, "AllowBlocked"); + ForwardingInfo->AllowCompressed = GetIntValue(group, "AllowCompressed"); + ForwardingInfo->AllowB1 = GetIntValue(group, "UseB1Protocol"); + ForwardingInfo->AllowB2 = GetIntValue(group, "UseB2Protocol"); + ForwardingInfo->SendCTRLZ = GetIntValue(group, "SendCTRLZ"); + + if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) + ForwardingInfo->AllowCompressed = TRUE; + + if (ForwardingInfo->AllowCompressed) + ForwardingInfo->AllowBlocked = TRUE; + + ForwardingInfo->PersonalOnly = GetIntValue(group, "FWDPersonalsOnly"); + ForwardingInfo->SendNew = GetIntValue(group, "FWDNewImmediately"); + ForwardingInfo->FwdInterval = GetIntValue(group, "FwdInterval"); + ForwardingInfo->RevFwdInterval = GetIntValue(group, "RevFWDInterval"); + ForwardingInfo->MaxFBBBlockSize = GetIntValue(group, "MaxFBBBlock"); + ForwardingInfo->ConTimeout = GetIntValue(group, "ConTimeout"); + + if (ForwardingInfo->MaxFBBBlockSize == 0) + ForwardingInfo->MaxFBBBlockSize = 10000; + + if (ForwardingInfo->FwdInterval == 0) + ForwardingInfo->FwdInterval = 3600; + + if (ForwardingInfo->ConTimeout == 0) + ForwardingInfo->ConTimeout = 120; + + GetStringValue(group, "BBSHA", Temp); + + if (Temp[0]) + ForwardingInfo->BBSHA = _strdup(Temp); + else + ForwardingInfo->BBSHA = _strdup(""); + } + } + else + { +#ifndef LINBPQ + + int retCode,Type,Vallen; + + strcat(RegKey, user->Call); + retCode = RegOpenKeyEx (REGTREE, RegKey, 0, KEY_QUERY_VALUE, &hKey); + + if (retCode != ERROR_SUCCESS) + return; + else + { + ForwardingInfo->ConnectScript = RegGetMultiStringValue(hKey, "Connect Script"); + ForwardingInfo->TOCalls = RegGetMultiStringValue(hKey, "TOCalls"); + ForwardingInfo->ATCalls = RegGetMultiStringValue(hKey, "ATCalls"); + ForwardingInfo->Haddresses = RegGetMultiStringValue(hKey, "HRoutes"); + ForwardingInfo->HaddressesP = RegGetMultiStringValue(hKey, "HRoutesP"); + ForwardingInfo->FWDTimes = RegGetMultiStringValue(hKey, "FWD Times"); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Enabled", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->Enabled,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "RequestReverse", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->ReverseFlag,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "AllowCompressed", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowCompressed,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Use B1 Protocol", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB1,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "Use B2 Protocol", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->AllowB2,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "SendCTRLZ", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendCTRLZ,(ULONG *)&Vallen); + + if (ForwardingInfo->AllowB1 || ForwardingInfo->AllowB2) + ForwardingInfo->AllowCompressed = TRUE; + + Vallen=4; + retCode += RegQueryValueEx(hKey, "FWD Personals Only", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->PersonalOnly,(ULONG *)&Vallen); + + Vallen=4; + retCode += RegQueryValueEx(hKey, "FWD New Immediately", 0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->SendNew,(ULONG *)&Vallen); + + Vallen=4; + RegQueryValueEx(hKey,"FWDInterval",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->FwdInterval,(ULONG *)&Vallen); + + Vallen=4; + RegQueryValueEx(hKey,"RevFWDInterval",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->RevFwdInterval,(ULONG *)&Vallen); + + RegQueryValueEx(hKey,"MaxFBBBlock",0, + (ULONG *)&Type,(UCHAR *)&ForwardingInfo->MaxFBBBlockSize,(ULONG *)&Vallen); + + if (ForwardingInfo->MaxFBBBlockSize == 0) + ForwardingInfo->MaxFBBBlockSize = 10000; + + if (ForwardingInfo->FwdInterval == 0) + ForwardingInfo->FwdInterval = 3600; + + Vallen=0; + retCode = RegQueryValueEx(hKey,"BBSHA",0 , (ULONG *)&Type,NULL, (ULONG *)&Vallen); + + if (retCode != 0) + { + // No Key - Get from WP?? + + WPRec * ptr = LookupWP(user->Call); + + if (ptr) + { + if (ptr->first_homebbs) + { + ForwardingInfo->BBSHA = _strdup(ptr->first_homebbs); + } + } + } + + if (Vallen) + { + ForwardingInfo->BBSHA = malloc(Vallen); + RegQueryValueEx(hKey, "BBSHA", 0, (ULONG *)&Type, ForwardingInfo->BBSHA,(ULONG *)&Vallen); + } + + RegCloseKey(hKey); + } +#endif + } + + // Convert FWD Times and H Addresses + + if (ForwardingInfo->FWDTimes) + SetupFwdTimes(ForwardingInfo); + + if (ForwardingInfo->Haddresses) + SetupHAddreses(ForwardingInfo); + + if (ForwardingInfo->HaddressesP) + SetupHAddresesP(ForwardingInfo); + + if (ForwardingInfo->BBSHA) + { + if (ForwardingInfo->BBSHA[0]) + SetupHAElements(ForwardingInfo); + else + { + free(ForwardingInfo->BBSHA); + ForwardingInfo->BBSHA = NULL; + } + } + + for (m = FirstMessageIndextoForward; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + // If any forward bits are set, increment count on BBS record. + + if (memcmp(Msg->fbbs, zeros, NBMASK) != 0) + { + if (Msg->type && check_fwd_bit(Msg->fbbs, user->BBSNumber)) + { + user->ForwardingInfo->MsgCount++; + } + } + } +} + +VOID * GetMultiStringValue(config_setting_t * group, char * ValueName) +{ + char * ptr1; + char * MultiString = NULL; + const char * ptr; + int Count = 0; + char ** Value; + config_setting_t *setting; + char * Save; + + Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values + Value[0] = NULL; + + setting = config_setting_get_member (group, ValueName); + + if (setting) + { + ptr = config_setting_get_string (setting); + + Save = _strdup(ptr); // DOnt want to change config string + ptr = Save; + + while (ptr && strlen(ptr)) + { + ptr1 = strchr(ptr, '|'); + + if (ptr1) + *(ptr1++) = 0; + + if (strlen(ptr)) // ignore null elements + { + Value = realloc(Value, (Count+2) * sizeof(void *)); + Value[Count++] = _strdup(ptr); + } + ptr = ptr1; + } + free(Save); + } + + Value[Count] = NULL; + return Value; +} + + +VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName) +{ +#ifdef LINBPQ + return NULL; +#else + int retCode,Type,Vallen; + char * MultiString = NULL; + int ptr, len; + int Count = 0; + char ** Value; + + Value = zalloc(sizeof(void *)); // always NULL entry on end even if no values + + Value[0] = NULL; + + Vallen=0; + + + retCode = RegQueryValueEx(hKey, ValueName, 0, (ULONG *)&Type, NULL, (ULONG *)&Vallen); + + if ((retCode != 0) || (Vallen < 3)) // Two nulls means empty multistring + { + free(Value); + return FALSE; + } + + MultiString = malloc(Vallen); + + retCode = RegQueryValueEx(hKey, ValueName, 0, + (ULONG *)&Type,(UCHAR *)MultiString,(ULONG *)&Vallen); + + ptr=0; + + while (MultiString[ptr]) + { + len=strlen(&MultiString[ptr]); + + Value = realloc(Value, (Count+2) * sizeof(void *)); + Value[Count++] = _strdup(&MultiString[ptr]); + ptr+= (len + 1); + } + + Value[Count] = NULL; + + free(MultiString); + + return Value; +#endif +} + +VOID FreeForwardingStruct(struct UserInfo * user) +{ + struct BBSForwardingInfo * ForwardingInfo; + int i; + + + ForwardingInfo = user->ForwardingInfo; + + FreeList(ForwardingInfo->TOCalls); + FreeList(ForwardingInfo->ATCalls); + FreeList(ForwardingInfo->Haddresses); + FreeList(ForwardingInfo->HaddressesP); + + i=0; + if(ForwardingInfo->HADDRS) + { + while(ForwardingInfo->HADDRS[i]) + { + FreeList(ForwardingInfo->HADDRS[i]); + i++; + } + free(ForwardingInfo->HADDRS); + free(ForwardingInfo->HADDROffet); + } + + i=0; + if(ForwardingInfo->HADDRSP) + { + while(ForwardingInfo->HADDRSP[i]) + { + FreeList(ForwardingInfo->HADDRSP[i]); + i++; + } + free(ForwardingInfo->HADDRSP); + } + + FreeList(ForwardingInfo->ConnectScript); + FreeList(ForwardingInfo->FWDTimes); + if (ForwardingInfo->FWDBands) + { + i=0; + while(ForwardingInfo->FWDBands[i]) + { + free(ForwardingInfo->FWDBands[i]); + i++; + } + free(ForwardingInfo->FWDBands); + } + if (ForwardingInfo->BBSHAElements) + { + i=0; + while(ForwardingInfo->BBSHAElements[i]) + { + free(ForwardingInfo->BBSHAElements[i]); + i++; + } + free(ForwardingInfo->BBSHAElements); + } + free(ForwardingInfo->BBSHA); + +} + +VOID FreeList(char ** Hddr) +{ + VOID ** Save; + + if (Hddr) + { + Save = (void **)Hddr; + while(Hddr[0]) + { + free(Hddr[0]); + Hddr++; + } + free(Save); + } +} + + +VOID ReinitializeFWDStruct(struct UserInfo * user) +{ + if (user->ForwardingInfo) + { + FreeForwardingStruct(user); + free(user->ForwardingInfo); + } + + SetupForwardingStruct(user); + +} + +VOID SetupFwdTimes(struct BBSForwardingInfo * ForwardingInfo) +{ + char ** Times = ForwardingInfo->FWDTimes; + int Start, End; + int Count = 0; + + ForwardingInfo->FWDBands = zalloc(sizeof(struct FWDBAND)); + + if (Times) + { + while(Times[0]) + { + ForwardingInfo->FWDBands = realloc(ForwardingInfo->FWDBands, (Count+2)* sizeof(struct FWDBAND)); + ForwardingInfo->FWDBands[Count] = zalloc(sizeof(struct FWDBAND)); + + Start = atoi(Times[0]); + End = atoi(&Times[0][5]); + + ForwardingInfo->FWDBands[Count]->FWDStartBand = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; + ForwardingInfo->FWDBands[Count]->FWDEndBand = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; + + Count++; + Times++; + } + ForwardingInfo->FWDBands[Count] = NULL; + } +} +void StartForwarding(int BBSNumber, char ** TempScript) +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + time_t NOW = time(NULL); + + + for (user = BBSChain; user; user = user->BBSNext) + { + // See if any messages are queued for this BBS + + ForwardingInfo = user->ForwardingInfo; + + if ((BBSNumber == 0) || (user->BBSNumber == BBSNumber)) + if (ForwardingInfo) + if (ForwardingInfo->Enabled || BBSNumber) // Menu Command overrides enable + if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) + if (BBSNumber || SeeifMessagestoForward(user->BBSNumber, NULL) || + (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) // Menu Command overrides Reverse + { + user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used + + // See if TempScript requested + + if (user->ForwardingInfo->TempConnectScript) + FreeList(user->ForwardingInfo->TempConnectScript); + + user->ForwardingInfo->TempConnectScript = TempScript; + + if (ConnecttoBBS(user)) + ForwardingInfo->Forwarding = TRUE; + } + } + + return; +} + +size_t fwritex(CIRCUIT * conn, void * _Str, size_t _Size, size_t _Count, FILE * _File) +{ + if (_File) + return fwrite(_Str, _Size, _Count, _File); + + // Appending to MailBuffer + + memcpy(&conn->MailBuffer[conn->InputLen], _Str, _Count); + conn->InputLen += (int)_Count; + + return _Count; +} + + +BOOL ForwardMessagestoFile(CIRCUIT * conn, char * FN) +{ + BOOL AddCRLF = FALSE; + BOOL AutoImport = FALSE; + FILE * Handle = NULL; + char * Context; + BOOL Email = FALSE; + time_t now = time(NULL); + char * param; + + FN = strtok_s(FN, " ,", &Context); + + param = strtok_s(NULL, " ,", &Context); + + if (param) + { + if (_stricmp(param, "ADDCRLF") == 0) + AddCRLF = TRUE; + + if (_stricmp(param, "AutoImport") == 0) + AutoImport = TRUE; + + param = strtok_s(NULL, " ,", &Context); + + if (param) + { + if (_stricmp(param, "ADDCRLF") == 0) + AddCRLF = TRUE; + + if (_stricmp(param, "AutoImport") == 0) + AutoImport = TRUE; + + } + } + // If FN is an email address, write to a temp file, and send via rms or emali gateway + + if (strchr(FN, '@') || _memicmp(FN, "RMS:", 4) == 0) + { + Email = TRUE; + AddCRLF =TRUE; + conn->MailBuffer=malloc(100000); + conn->MailBufferSize=100000; + conn->InputLen = 0; + } + else + { + Handle = fopen(FN, "ab"); + + if (Handle == NULL) + { + int err = GetLastError(); + Logprintf(LOG_BBS, conn, '!', "Failed to open Export File %s", FN); + return FALSE; + } + } + + while (FindMessagestoForward(conn)) + { + struct MsgInfo * Msg; + struct tm * tm; + time_t temp; + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + int MsgLen; + char * MsgPtr; + char Line[256]; + int len; + struct UserInfo * user = conn->UserPointer; + int Index = 0; + + Msg = conn->FwdMsg; + + if (Email) + if (conn->InputLen + Msg->length + 500 > conn->MailBufferSize) + break; + + if (Msg->type == 'P') + Index = PMSG; + else if (Msg->type == 'B') + Index = BMSG; + else if (Msg->type == 'T') + Index = TMSG; + + + if (Msg->via[0]) + len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, + Msg->via, Msg->from, Msg->bid); + else + len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); + + fwritex(conn, Line, 1, len, Handle); + + len = sprintf(Line, "%s\r\n", Msg->title); + fwritex(conn, Line, 1, len, Handle); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + MsgLen = conn->FwdMsg->length; + + // If a B2 Message, remove B2 Header + + if (conn->FwdMsg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + MsgPtr = strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + conn->FwdMsg->number, BBSName, HRoute, RlineVer); + + fwritex(conn, Line, 1, len, Handle); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + fwritex(conn, "\r\n", 1, 2, Handle); + + fwritex(conn, MsgPtr, 1, MsgLen, Handle); + + if (MsgPtr[MsgLen - 2] == '\r') + fwritex(conn, "/EX\r\n", 1, 5, Handle); + else + fwritex(conn, "\r\n/EX\r\n", 1, 7, Handle); + + if (AddCRLF) + fwritex(conn, "\r\n", 1, 2, Handle); + + free(MsgBytes); + + user->Total.MsgsSent[Index]++; + user->Total.BytesForwardedOut[Index] += MsgLen; + + Msg->datechanged = now; + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->UserPointer->ForwardingInfo->MsgCount--; + } + + if (Email) + { + struct MsgInfo * Msg; + BIDRec * BIDRec; + + if (conn->InputLen == 0) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + return TRUE; + } + + // Allocate a message Record slot + + Msg = AllocateMsgRecord(); + + // Set number here so they remain in sequence + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + FreeSemaphore(&MsgNoSemaphore); + MsgnotoMsg[Msg->number] = Msg; + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datecreated = Msg->datechanged = Msg->datereceived = now; + + strcpy(Msg->from, BBSName); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + if (AutoImport) + sprintf(Msg->title, "Batched messages for AutoImport from BBS %s", BBSName); + else + sprintf(Msg->title, "Batched messages from BBS %s", BBSName); + + Msg->length = conn->InputLen; + CreateMessageFile(conn, Msg); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + if (_memicmp(FN, "SMTP:", 5) == 0) + { + strcpy(Msg->via, &FN[5]); + SMTPMsgCreated=TRUE; + } + else + { + strcpy(Msg->to, "RMS"); + if (_memicmp(FN, "RMS:", 4) == 0) + strcpy(Msg->via, &FN[4]); + else + strcpy(Msg->via, FN); + } + + MatchMessagetoBBSList(Msg, conn); + + SaveMessageDatabase(); + SaveBIDDatabase(); + } + else + fclose(Handle); + + SaveMessageDatabase(); + return TRUE; +} + +BOOL ForwardMessagetoFile(struct MsgInfo * Msg, FILE * Handle) +{ + struct tm * tm; + time_t temp; + + char * MsgBytes = ReadMessageFile(Msg->number); + char * MsgPtr; + char Line[256]; + int len; + int MsgLen = Msg->length; + + if (Msg->via[0]) + len = sprintf(Line, "S%c %s @ %s < %s $%s\r\n", Msg->type, Msg->to, + Msg->via, Msg->from, Msg->bid); + else + len = sprintf(Line, "S%c %s < %s $%s\r\n", Msg->type, Msg->to, Msg->from, Msg->bid); + + fwrite(Line, 1, len, Handle); + + len = sprintf(Line, "%s\r\n", Msg->title); + fwrite(Line, 1, len, Handle); + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r\n"); + MsgLen = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + + // If a B2 Message, remove B2 Header + + if (Msg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + + MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &Msg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + len = sprintf(Line, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r\n", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + Msg->number, BBSName, HRoute, RlineVer); + + fwrite(Line, 1, len, Handle); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + fwrite("\r\n", 1, 2, Handle); + + fwrite(MsgPtr, 1, MsgLen, Handle); + + if (MsgPtr[MsgLen - 2] == '\r') + fwrite("/EX\r\n", 1, 5, Handle); + else + fwrite("\r\n/EX\r\n", 1, 7, Handle); + + free(MsgBytes); + + return TRUE; + +} + +BOOL ConnecttoBBS (struct UserInfo * user) +{ + int n, p; + CIRCUIT * conn; + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + + for (n = NumberofStreams-1; n >= 0 ; n--) + { + conn = &Connections[n]; + + if (conn->Active == FALSE) + { + p = conn->BPQStream; + memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything + conn->BPQStream = p; + + // Can't set Active until Connected or Stuck Session detertor can clear session. + // But must set Active before Connected() runs or will appear is Incoming Connect. + // Connected() is semaphored, so get semaphore before ConnectUsingAppl + // Probably better to semaphore lost session code instead + + + strcpy(conn->Callsign, user->Call); + conn->BBSFlags |= (RunningConnectScript | OUTWARDCONNECT); + conn->UserPointer = user; + + Logprintf(LOG_BBS, conn, '|', "Connecting to BBS %s", user->Call); + + ForwardingInfo->MoreLines = TRUE; + + GetSemaphore(&ConSemaphore, 1); + conn->Active = TRUE; + ConnectUsingAppl(conn->BPQStream, BBSApplMask); + FreeSemaphore(&ConSemaphore); + + // If we are sending to a dump pms we may need to connect using the message sender's callsign. + // But we wont know until we run the connect script, which is a bit late to change call. Could add + // flag to forwarding config, but easier to look for SETCALLTOSENDER in the connect script. + + if (strstr(ForwardingInfo->ConnectScript[0], "SETCALLTOSENDER")) + { + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + // We have a message to send + + struct MsgInfo * Msg; + unsigned char AXCall[7]; + + Msg = conn->FwdMsg; + ConvToAX25(Msg->from, AXCall); + ChangeSessionCallsign(p, AXCall); + + conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER | NEWPACCOM; + conn->NextMessagetoForward = 0; // was set by FindMessages + } + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; + } +#ifdef LINBPQ + { + BPQVECSTRUC * SESS; + SESS = &BPQHOSTVECTOR[conn->BPQStream - 1]; + + if (SESS->HOSTSESSION == NULL) + { + Logprintf(LOG_BBS, NULL, '|', "No L4 Sessions for connect to BBS %s", user->Call); + return FALSE; + } + + SESS->HOSTSESSION->Secure_Session = 1; + } +#endif + + strcpy(conn->Callsign, user->Call); + + // Connected Event will trigger connect to remote system + + RefreshMainWindow(); + + return TRUE; + } + } + + Logprintf(LOG_BBS, NULL, '|', "No Free Streams for connect to BBS %s", user->Call); + + return FALSE; + +} + +struct DelayParam +{ + struct UserInfo * User; + CIRCUIT * conn; + int Delay; +}; + +struct DelayParam DParam; // Not 100% safe, but near enough + +VOID ConnectDelayThread(struct DelayParam * DParam) +{ + struct UserInfo * User = DParam->User; + int Delay = DParam->Delay; + + User->ForwardingInfo->Forwarding = TRUE; // Minimize window for two connects + + Sleep(Delay); + + User->ForwardingInfo->Forwarding = TRUE; + ConnecttoBBS(User); + + return; +} + +VOID ConnectPauseThread(struct DelayParam * DParam) +{ + CIRCUIT * conn = DParam->conn; + int Delay = DParam->Delay; + char Msg[] = "Pause Ok\r "; + + Sleep(Delay); + + ProcessBBSConnectScript(conn, Msg, 9); + + return; +} + + +/* +BOOL ProcessBBSConnectScriptInner(CIRCUIT * conn, char * Buffer, int len); + + +BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) +{ + BOOL Ret; + GetSemaphore(&ScriptSEM); + Ret = ProcessBBSConnectScriptInner(conn, Buffer, len); + FreeSemaphore(&ScriptSEM); + + return Ret; +} +*/ + +BOOL ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len) +{ + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + char ** Scripts; + char callsign[10]; + int port, sesstype, paclen, maxframe, l4window; + char * ptr, * ptr2; + + WriteLogLine(conn, '<',Buffer, len-1, LOG_BBS); + + Buffer[len]=0; + _strupr(Buffer); + + if (ForwardingInfo->TempConnectScript) + Scripts = ForwardingInfo->TempConnectScript; + else + Scripts = ForwardingInfo->ConnectScript; + + if (ForwardingInfo->ScriptIndex == -1) + { + // First Entry - if first line is TIMES, check and skip forward if necessary + + int n = 0; + int Start, End; + time_t now = time(NULL), StartSecs, EndSecs; + char * Line; + + if (Localtime) + now -= (time_t)_MYTIMEZONE; + + now %= 86400; + Line = Scripts[n]; + + if (_memicmp(Line, "TIMES", 5) == 0) + { + NextBand: + Start = atoi(&Line[6]); + End = atoi(&Line[11]); + + StartSecs = (time_t)(Start / 100) * 3600 + (Start % 100) * 60; + EndSecs = (time_t)(End / 100) * 3600 + (End % 100) * 60 + 59; + + if ((StartSecs <= now) && (EndSecs >= now)) + goto InBand; // In band + + // Look for next TIME + NextLine: + Line = Scripts[++n]; + + if (Line == NULL) + { + // No more lines - Disconnect + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Line, "TIMES", 5) != 0) + goto NextLine; + else + goto NextBand; +InBand: + ForwardingInfo->ScriptIndex = n; + } + + } + else + { + // Dont check first time through + + if (strcmp(Buffer, "*** CONNECTED ") != 0) + { + if (Scripts[ForwardingInfo->ScriptIndex] == NULL || + _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished + _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished + { + ForwardingInfo->MoreLines = FALSE; + } + if (!ForwardingInfo->MoreLines) + goto CheckForSID; + } + } + + if (strstr(Buffer, "BUSY") || strstr(Buffer, "FAILURE") || + (strstr(Buffer, "DOWNLINK") && strstr(Buffer, "ATTEMPTING") == 0) || + strstr(Buffer, "SORRY") || strstr(Buffer, "INVALID") || strstr(Buffer, "RETRIED") || + strstr(Buffer, "NO CONNECTION TO") || strstr(Buffer, "ERROR - ") || + strstr(Buffer, "UNABLE TO CONNECT") || strstr(Buffer, "DISCONNECTED") || + strstr(Buffer, "FAILED TO CONNECT") || strstr(Buffer, "REJECTED")) + { + // Connect Failed + + char * Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + int Delay = 1000; + + // Look for an alternative connect block (Starting with ELSE) + + ElseLoop: + + // Skip any comments + + while (Cmd && ((strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#'))) + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + + // TIMES terminates a script + + if (Cmd == 0 || _memicmp(Cmd, "TIMES", 5) == 0) // Only Check until script is finished + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "ELSE", 4) != 0) + { + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + goto ElseLoop; + } + + if (_memicmp(&Cmd[5], "DELAY", 5) == 0) + Delay = atoi(&Cmd[10]) * 1000; + else + Delay = 1000; + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + + DParam.Delay = Delay; + DParam.User = conn->UserPointer; + + _beginthread((void (*)(void *))ConnectDelayThread, 0, &DParam); + + return FALSE; + } + + // The pointer is only updated when we get the connect, so we can tell when the last line is acked + // The first entry is always from Connected event, so don't have to worry about testing entry -1 below + + + // NETROM to KA node returns + + //c 1 milsw + //WIRAC:N9PMO-2} Connected to MILSW + //###CONNECTED TO NODE MILSW(N9ZXS) CHANNEL A + //You have reached N9ZXS's KA-Node MILSW + //ENTER COMMAND: B,C,J,N, or Help ? + + //C KB9PRF-7 + //###LINK MADE + //###CONNECTED TO NODE KB9PRF-7(KB9PRF-4) CHANNEL A + + // Look for (Space)Connected so we aren't fooled by ###CONNECTED TO NODE, which is not + // an indication of a connect. + + if (strstr(Buffer, " CONNECTED") || strstr(Buffer, "PACLEN") || strstr(Buffer, "IDLETIME") || + strstr(Buffer, "OK") || strstr(Buffer, "###LINK MADE") || strstr(Buffer, "VIRTUAL CIRCUIT ESTABLISHED")) + { + // If connected to SYNC, save IP address and port + + char * Cmd; + + if (strstr(Buffer, "*** CONNECTED TO SYNC")) + { + char * IPAddr = &Buffer[22]; + char * Port = strlop(IPAddr, ':'); + + if (Port) + { + if (conn->SyncHost) + free(conn->SyncHost); + + conn->SyncHost = _strdup(IPAddr); + conn->SyncPort = atoi(Port); + } + } + + if (conn->SkipConn) + { + conn->SkipConn = FALSE; + return TRUE; + } + + LoopBack: + + Cmd = Scripts[++ForwardingInfo->ScriptIndex]; + + // Only Check until script is finished + + if (Cmd && (strcmp(Cmd, " ") == 0 || Cmd[0] == ';' || Cmd[0] == '#')) + goto LoopBack; // Blank line + + if (Cmd && _memicmp(Cmd, "TIMES", 5) != 0 && _memicmp(Cmd, "ELSE", 4) != 0) // Only Check until script is finished + { + if (_memicmp(Cmd, "MSGTYPE", 7) == 0) + { + char * ptr; + + // Select Types to send. Only send types in param. Only reverse if R in param + + _strupr(Cmd); + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = FALSE; + + strcpy(conn->MSGTYPES, &Cmd[8]); + + if (strchr(&Cmd[8], 'R')) conn->DoReverse = TRUE; + + ptr = strchr(&Cmd[8], 'B'); + + if (ptr) + { + conn->SendB = TRUE; + conn->MaxBLen = atoi(++ptr); + if (conn->MaxBLen == 0) conn->MaxBLen = 99999999; + } + + ptr = strchr(&Cmd[8], 'T'); + + if (ptr) + { + conn->SendT = TRUE; + conn->MaxTLen = atoi(++ptr); + if (conn->MaxTLen == 0) conn->MaxTLen = 99999999; + } + ptr = strchr(&Cmd[8], 'P'); + + if (ptr) + { + conn->SendP = TRUE; + conn->MaxPLen = atoi(++ptr); + if (conn->MaxPLen == 0) conn->MaxPLen = 99999999; + } + + // If nothing to do, terminate script + + if (conn->DoReverse || SeeifMessagestoForward(conn->UserPointer->BBSNumber, conn)) + goto LoopBack; + + Logprintf(LOG_BBS, conn, '?', "Nothing to do - quitting"); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "INTERLOCK ", 10) == 0) + { + // Used to limit connects on a port to 1 + + int Port; + char Option[80]; + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + sscanf(&Cmd[10], "%d %s", &Port, &Option[0]); + + if (CountConnectionsOnPort(Port)) + { + Logprintf(LOG_BBS, conn, '?', "Interlocked Port is busy - quitting"); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + goto LoopBack; + } + + if (_memicmp(Cmd, "RADIO AUTH", 10) == 0) + { + // Generate a Password to enable RADIO commands on a remote node + char AuthCommand[80]; + + _strupr(Cmd); + strcpy(AuthCommand, Cmd); + + CreateOneTimePassword(&AuthCommand[11], &Cmd[11], 0); + + nodeprintf(conn, "%s\r", AuthCommand); + return TRUE; + } + + if (_memicmp(Cmd, "SKIPCON", 7) == 0) + { + // Remote Node sends Connected in CTEXT - we need to swallow it + + conn->SkipConn = TRUE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SendWL2KPM", 10) == 0|| _memicmp(Cmd, "SendWL2KFW", 10) == 0) + { + // Send ;FW: command + + conn->SendWL2KFW = TRUE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SKIPPROMPT", 10) == 0) + { + // Remote Node sends > at end of CTEXT - we need to swallow it + + conn->SkipPrompt++; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "TEXTFORWARDING", 10) == 0) + { + conn->BBSFlags |= TEXTFORWARDING; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SETCALLTOSENDER", 15) == 0) + { + conn->BBSFlags |= TEXTFORWARDING | SETCALLTOSENDER; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "RADIOONLY", 9) == 0) + { + conn->BBSFlags |= WINLINKRO; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "SYNC", 4) == 0) + { + conn->BBSFlags |= SYNCMODE; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "NEEDLF", 6) == 0) + { + conn->BBSFlags |= NEEDLF; + goto CheckForEnd; + } + + if (_memicmp(Cmd, "MCASTRX", 6) == 0) + { + conn->BBSFlags |= MCASTRX; + conn->MCastListenTime = atoi(&Cmd[7]) * 6; // Time to run session for *6 as value is mins put timer ticks 10 secs + + // send MCAST to Node + + nodeprintfEx(conn, "MCAST\r"); + return TRUE; + } + + if (_memicmp(Cmd, "FLARQ", 5) == 0) + { + conn->BBSFlags |= FLARQMAIL; + + CheckForEnd: + if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || + memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished + memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished + ForwardingInfo->MoreLines = FALSE; + + goto LoopBack; + } + if (_memicmp(Cmd, "PAUSE", 5) == 0) + { + // Pause script + + Logprintf(LOG_BBS, conn, '?', "Script %s", Cmd); + + DParam.Delay = atoi(&Cmd[6]) * 1000; + DParam.conn = conn; + + _beginthread((void (*)(void *))ConnectPauseThread, 0, &DParam); + + return TRUE; + } + + if (_memicmp(Cmd, "FILE", 4) == 0) + { + if (Cmd[4] == 0) + { + // Missing Filename + + Logprintf(LOG_BBS, conn, '!', "Export file name missing"); + } + else + ForwardMessagestoFile(conn, &Cmd[5]); + + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (_memicmp(Cmd, "SMTP", 4) == 0) + { + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + + SendAMPRSMTP(conn); + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + + if (_memicmp(Cmd, "IMPORT", 6) == 0) + { + char * File, * Context; + int Num; + char * Temp = _strdup(Cmd); + + File = strtok_s(&Temp[6], " ", &Context); + + if (File && File[0]) + { + Num = ImportMessages(NULL, File, TRUE); + + Logprintf(LOG_BBS, NULL, '|', "Imported %d Message(s) from %s", Num, File); + + if (Context && _stricmp(Context, "delete") == 0) + DeleteFile(File); + } + free(Temp); + + if (Scripts[ForwardingInfo->ScriptIndex + 1] == NULL || + memcmp(Scripts[ForwardingInfo->ScriptIndex +1], "TIMES", 5) == 0 || // Only Check until script is finished + memcmp(Scripts[ForwardingInfo->ScriptIndex + 1], "ELSE", 4) == 0) // Only Check until script is finished + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + goto LoopBack; + } + + // Anything else is sent to Node + + // Replace \ with # so can send commands starting with # + + if (Cmd[0] == '\\') + { + Cmd[0] = '#'; + nodeprintfEx(conn, "%s\r", Cmd); + Cmd[0] = '\\'; // Put \ back in script + } + else + nodeprintfEx(conn, "%s\r", Cmd); + + return TRUE; + } + + // End of script. + + ForwardingInfo->MoreLines = FALSE; + + if (conn->BBSFlags & MCASTRX) + { + // No session with Multicast, so no SID + + conn->BBSFlags &= ~RunningConnectScript; + return TRUE; + } + + if (conn->BBSFlags & FLARQMAIL) + { + // FLARQ doesnt send a prompt - Just send message(es) + + conn->UserPointer->Total.ConnectsOut++; + conn->BBSFlags &= ~RunningConnectScript; + ForwardingInfo->LastReverseForward = time(NULL); + + // Update Paclen + + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + + if (paclen > 0) + conn->paclen = paclen; + + SendARQMail(conn); + return TRUE; + } + + + return TRUE; + } + + ptr = strchr(Buffer, '}'); + + if (ptr && ForwardingInfo->MoreLines) // Beware it could be part of ctext + { + // Could be respsonse to Node Command + + ptr+=2; + + ptr2 = strchr(&ptr[0], ' '); + + if (ptr2) + { + if (_memicmp(ptr, Scripts[ForwardingInfo->ScriptIndex], ptr2-ptr) == 0) // Reply to last sscript command + { + if (Scripts[ForwardingInfo->ScriptIndex+1] && _memicmp(Scripts[ForwardingInfo->ScriptIndex+1], "else", 4) == 0) + { + // stray match or misconfigured + + return TRUE; + } + + ForwardingInfo->ScriptIndex++; + + if (Scripts[ForwardingInfo->ScriptIndex]) + if (_memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) != 0) + nodeprintf(conn, "%s\r", Scripts[ForwardingInfo->ScriptIndex]); + + return TRUE; + } + } + } + + // Not Success or Fail. If last line is still outstanding, wait fot Response + // else look for SID or Prompt + + if (conn->SkipPrompt && Buffer[len-2] == '>') + { + conn->SkipPrompt--; + return TRUE; + } + + if (ForwardingInfo->MoreLines) + return TRUE; + + // No more steps, Look for SID or Prompt + +CheckForSID: + + if (strstr(Buffer, "POSYNCHELLO")) // RMS RELAY Sync process + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + ForwardingInfo->LastReverseForward = time(NULL); + + ProcessLine(conn, 0, Buffer, len); + return FALSE; + } + + if (strstr(Buffer, "SORRY, NO")) // URONODE + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + if (memcmp(Buffer, ";PQ: ", 5) == 0) + { + // Secure CMS challenge + + int Len; + struct UserInfo * User = conn->UserPointer; + char * Pass = User->CMSPass; + int Response ; + char RespString[12]; + char ConnectingCall[10]; + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + + SESS += conn->BPQStream - 1; + + ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); + + strlop(ConnectingCall, ' '); + + if (Pass[0] == 0) + { + Pass = User->pass; // Old Way + if (Pass[0] == 0) + { + strlop(ConnectingCall, '-'); + User = LookupCall(ConnectingCall); + if (User) + Pass = User->CMSPass; + } + } + + // + + Response = GetCMSHash(&Buffer[5], Pass); + + sprintf(RespString, "%010d", Response); + + Len = sprintf(conn->SecureMsg, ";PR: %s\r", &RespString[2]); + + // Save challengs in case needed for FW lines + + strcpy(conn->PQChallenge, &Buffer[5]); + + return FALSE; + } + + + if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID + { + // Update PACLEN + + GetConnectionInfo(conn->BPQStream, callsign, &port, &sesstype, &paclen, &maxframe, &l4window); + + if (paclen > 0) + conn->paclen = paclen; + + + Parse_SID(conn, &Buffer[1], len-4); + + if (conn->BBSFlags & FBBForwarding) + { + conn->FBBIndex = 0; // ready for first block; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + conn->FBBChecksum = 0; + } + + return TRUE; + } + + if (memcmp(Buffer, "[PAKET ", 7) == 0) + { + conn->BBSFlags |= BBS; + conn->BBSFlags |= MBLFORWARDING; + } + + if (Buffer[len-2] == '>') + { + if (conn->SkipPrompt) + { + conn->SkipPrompt--; + return TRUE; + } + + conn->NextMessagetoForward = FirstMessageIndextoForward; + conn->UserPointer->Total.ConnectsOut++; + conn->BBSFlags &= ~RunningConnectScript; + ForwardingInfo->LastReverseForward = time(NULL); + + if (memcmp(Buffer, "[AEA PK", 7) == 0 || (conn->BBSFlags & TEXTFORWARDING)) + { + // PK232. Don't send a SID, and switch to Text Mode + + conn->BBSFlags |= (BBS | TEXTFORWARDING); + conn->Flags |= SENDTITLE; + + // Send Message. There is no mechanism for reverse forwarding + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg; + + // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ + + Msg = conn->FwdMsg; + + if ((conn->BBSFlags & SETCALLTOSENDER)) + nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); + else + nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, + Msg->from, Msg->bid); + } + else + { + conn->BBSFlags &= ~RunningConnectScript; // so it doesn't get reentered + Disconnect(conn->BPQStream); + return FALSE; + } + + return TRUE; + } + + if (strcmp(conn->Callsign, "RMS") == 0 || conn->SendWL2KFW) + { + // Build a ;FW: line with all calls with PollRMS Set + + // According to Lee if you use secure login the first + // must be the BBS call + // Actually I don't think we need the first, + // as that is implied + + // If a secure password is available send the new + // call|response format. + + // I think this should use the session callsign, which + // normally will be the BBS ApplCall, and not the BBS Name, + // but coudl be changed by *** LINKED + + int i, s; + char FWLine[10000] = ";FW: "; + struct UserInfo * user; + char RMSCall[20]; + char ConnectingCall[10]; + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + + SESS += conn->BPQStream - 1; + + ConvFromAX25(SESS->HOSTSESSION->L4USER, ConnectingCall); + strlop(ConnectingCall, ' '); + + strcat (FWLine, ConnectingCall); + + for (i = 0; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_POLLRMS) + { + if (user->RMSSSIDBits == 0) user->RMSSSIDBits = 1; + + for (s = 0; s < 16; s++) + { + if (user->RMSSSIDBits & (1 << s)) + { + if (s) + sprintf(RMSCall, "%s-%d", user->Call, s); + else + sprintf(RMSCall, "%s", user->Call); + + // We added connectingcall at front + + if (strcmp(RMSCall, ConnectingCall) != 0) + { + strcat(FWLine, " "); + strcat(FWLine, RMSCall); + + if (user->CMSPass[0]) + { + int Response = GetCMSHash(conn->PQChallenge, user->CMSPass); + char RespString[12]; + + sprintf(RespString, "%010d", Response); + strcat(FWLine, "|"); + strcat(FWLine, &RespString[2]); + } + } + } + } + } + } + + strcat(FWLine, "\r"); + + nodeprintf(conn, FWLine); + } + + // Only declare B1 and B2 if other end did, and we are configued for it + + nodeprintfEx(conn, BBSSID, "BPQ-", + Ver[0], Ver[1], Ver[2], Ver[3], + (conn->BBSFlags & FBBCompressed) ? "B" : "", + (conn->BBSFlags & FBBB1Mode && !(conn->BBSFlags & FBBB2Mode)) ? "1" : "", + (conn->BBSFlags & FBBB2Mode) ? "2" : "", + (conn->BBSFlags & FBBForwarding) ? "F" : "", + (conn->BBSFlags & WINLINKRO) ? "" : "J"); + + if (conn->SecureMsg[0]) + { + struct UserInfo * user; + BBSputs(conn, conn->SecureMsg); + conn->SecureMsg[0] = 0; + + // Also send a Location Comment Line + + //; GM8BPQ-10 DE G8BPQ (IO92KX) + //; WL2K DE GM8BPQ () (PAT) + + user = LookupCall(BBSName); + + if (LOC && LOC[0]) + nodeprintf(conn, "; WL2K DE %s (%s)\r", BBSName, LOC); + } + + if (conn->BPQBBS && conn->MSGTYPES[0]) + + // Send a ; MSGTYPES to control what he sends us + + nodeprintf(conn, "; MSGTYPES %s\r", conn->MSGTYPES); + + if (conn->BBSFlags & FBBForwarding) + { + if (!FBBDoForward(conn)) // Send proposal if anthing to forward + { + if (conn->DoReverse) + FBBputs(conn, "FF\r"); + else + { + FBBputs(conn, "FQ\r"); + conn->CloseAfterFlush = 20; // 2 Secs + } + } + + return TRUE; + } + + return TRUE; + } + + return TRUE; +} + +VOID Parse_SID(CIRCUIT * conn, char * SID, int len) +{ + ChangeSessionIdletime(conn->BPQStream, BBSIDLETIME); // Default Idletime for BBS Sessions + + // scan backwards for first '-' + + if (strstr(SID, "BPQCHATSERVER")) + { + Disconnect(conn->BPQStream); + return; + } + + if (strstr(SID, "RMS Ex") || strstr(SID, "Winlink Ex")) + { + conn->RMSExpress = TRUE; + conn->Paclink = FALSE; + conn->PAT = FALSE; + + // Set new RMS Users as RMS User + + if (conn->NewUser) + conn->UserPointer->flags |= F_Temp_B2_BBS; + } + + if (stristr(SID, "PAT")) + { + // Set new PAT Users as RMS User + + conn->RMSExpress = FALSE; + conn->Paclink = FALSE; + conn->PAT = TRUE; + + if (conn->NewUser) + conn->UserPointer->flags |= F_Temp_B2_BBS; + } + if (strstr(SID, "Paclink")) + { + conn->RMSExpress = FALSE; + conn->Paclink = TRUE; + } + + if (strstr(SID, "WL2K-")) + { + conn->WL2K = TRUE; + conn->BBSFlags |= WINLINKRO; + } + + if (strstr(SID, "MFJ-")) + { + conn->BBSFlags |= MFJMODE; + } + + if (_memicmp(SID, "OpenBCM", 7) == 0) + { + // We should really only do this on Telnet Connections, as OpenBCM flag is used to remove relnet transparency + + + conn->OpenBCM = TRUE; + } + + if (_memicmp(SID, "PMS-3.2", 7) == 0) + { + // Paccom TNC that doesn't send newline prompt ater receiving subject + + conn->BBSFlags |= NEWPACCOM; + } + + // See if BPQ for selective forwarding + + if (strstr(SID, "BPQ")) + conn->BPQBBS = TRUE; + + while (len > 0) + { + switch (SID[len--]) + { + case '-': + + len=0; + break; + + case '$': + + conn->BBSFlags |= BBS | MBLFORWARDING; + conn->Paging = FALSE; + + break; + + case 'F': // FBB Blocked Forwarding + + // We now support blocked uncompressed. Not necessarily compatible with FBB + + if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) + { + // We need to allocate a forwarding structure + + conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; + conn->UserPointer->ForwardingInfo->AllowBlocked = TRUE; + conn->UserPointer->BBSNumber = NBBBS; + } + + if (conn->UserPointer->ForwardingInfo->AllowBlocked) + { + conn->BBSFlags |= FBBForwarding | BBS; + conn->BBSFlags &= ~MBLFORWARDING; + + conn->Paging = FALSE; + + if ((conn->UserPointer->ForwardingInfo == NULL) && (conn->UserPointer->flags & F_PMS)) + { + // We need to allocate a forwarding structure + + conn->UserPointer->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + conn->UserPointer->ForwardingInfo->AllowCompressed = TRUE; + conn->UserPointer->BBSNumber = NBBBS; + } + + // Allocate a Header Block + + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + } + break; + + case 'J': + + // Suspected to be associated with Winlink Radio Only + + conn->BBSFlags &= ~WINLINKRO; + break; + + case 'B': + + if (conn->UserPointer->ForwardingInfo->AllowCompressed) + { + conn->BBSFlags |= FBBCompressed; + conn->DontSaveRestartData = FALSE; // Allow restarts + + // Look for 1 or 2 or 12 as next 2 chars + + if (SID[len+2] == '1') + { + if (conn->UserPointer->ForwardingInfo->AllowB1 || + conn->UserPointer->ForwardingInfo->AllowB2) // B2 implies B1 + conn->BBSFlags |= FBBB1Mode; + + if (SID[len+3] == '2') + if (conn->UserPointer->ForwardingInfo->AllowB2) + conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) + + break; + } + + if (SID[len+2] == '2') + { + if (conn->UserPointer->ForwardingInfo->AllowB2) + conn->BBSFlags |= FBBB1Mode | FBBB2Mode; // B2 uses B1 mode (crc on front of file) + + if (conn->UserPointer->ForwardingInfo->AllowB1) + conn->BBSFlags |= FBBB1Mode; // B2 should allow fallback to B1 (but RMS doesnt!) + + } + break; + } + + break; + } + } + + // Only allow blocked non-binary to other BPQ Nodes + + if ((conn->BBSFlags & FBBForwarding) && ((conn->BBSFlags & FBBCompressed) == 0) && (conn->BPQBBS == 0)) + { + // Switch back to MBL + + conn->BBSFlags |= MBLFORWARDING; + conn->BBSFlags &= ~FBBForwarding; // Turn off FBB Blocked + } + + return; +} + +VOID BBSSlowTimer() +{ + ConnectionInfo * conn; + int n; + + // Called every 10 seconds + + MCastTimer(); + + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (conn->Active == TRUE) + { + // Check for stuck BBS sessions (BBS session but no Node Session) + + int state; + + GetSemaphore(&ConSemaphore, 1); + SessionStateNoAck(conn->BPQStream, &state); + FreeSemaphore(&ConSemaphore); + + if (state == 0) // No Node Session + { + // is it safe just to clear Active ?? + + conn->InputMode = 0; // So Disconnect wont save partial transfer + conn->BBSFlags = 0; + Disconnected (conn->BPQStream); + continue; + } + + if (conn->BBSFlags & MCASTRX) + MCastConTimer(conn); + + + // Check SIDTImers - used to detect failure to compete SID Handshake + + if (conn->SIDResponseTimer) + { + conn->SIDResponseTimer--; + if (conn->SIDResponseTimer == 0) + { + // Disconnect Session + + Disconnect(conn->BPQStream); + } + } + } + } + + // Flush logs + + for (n = 0; n < 4; n++) + { + if (LogHandle[n]) + { + time_t LT = time(NULL); + if ((LT - LastLogTime[n]) > 30) + { + LastLogTime[n] = LT; + fclose(LogHandle[n]); + LogHandle[n] = NULL; + } + } + } +} + + +VOID FWDTimerProc() +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + time_t NOW = time(NULL); + + for (user = BBSChain; user; user = user->BBSNext) + { + // See if any messages are queued for this BBS + + ForwardingInfo = user->ForwardingInfo; + ForwardingInfo->FwdTimer+=10; + + if (ForwardingInfo->FwdTimer >= ForwardingInfo->FwdInterval) + { + ForwardingInfo->FwdTimer=0; + + if (ForwardingInfo->FWDBands && ForwardingInfo->FWDBands[0]) + { + // Check Timebands + + struct FWDBAND ** Bands = ForwardingInfo->FWDBands; + int Count = 0; + time_t now = time(NULL); + + if (Localtime) + now -= (time_t)_MYTIMEZONE; + + now %= 86400; // Secs in day + + while(Bands[Count]) + { + if ((Bands[Count]->FWDStartBand < now) && (Bands[Count]->FWDEndBand >= now)) + goto FWD; // In band + + Count++; + } + continue; // Out of bands + } + FWD: + if (ForwardingInfo->Enabled) + { + if (ForwardingInfo->ConnectScript && (ForwardingInfo->Forwarding == 0) && ForwardingInfo->ConnectScript[0]) + { + //Temp Debug Code + +// Debugprintf("ReverseFlag = %d, Msgs to Forward Flag %d Msgs to Forward Count %d", +// ForwardingInfo->ReverseFlag, +// SeeifMessagestoForward(user->BBSNumber, NULL), +// CountMessagestoForward(user)); + + if (SeeifMessagestoForward(user->BBSNumber, NULL) || + (ForwardingInfo->ReverseFlag && ((NOW - ForwardingInfo->LastReverseForward) >= ForwardingInfo->RevFwdInterval))) + + { + user->ForwardingInfo->ScriptIndex = -1; // Incremented before being used + + + // remove any old TempScript + + if (user->ForwardingInfo->TempConnectScript) + { + FreeList(user->ForwardingInfo->TempConnectScript); + user->ForwardingInfo->TempConnectScript = NULL; + } + + if (ConnecttoBBS(user)) + ForwardingInfo->Forwarding = TRUE; + } + } + } + } + } +} + +VOID * _zalloc_dbg(size_t len, int type, char * file, int line) +{ + // ?? malloc and clear + + void * ptr; + +#ifdef WIN32 + ptr=_malloc_dbg(len, type, file, line); +#else + ptr = malloc(len); +#endif + if (ptr) + memset(ptr, 0, len); + + return ptr; +} + +struct MsgInfo * FindMessageByNumber(int msgno) + { + int m=NumberofMessages; + + struct MsgInfo * Msg; + + do + { + Msg=MsgHddrPtr[m]; + + if (Msg->number == msgno) + return Msg; + + if (Msg->number && Msg->number < msgno) // sometimes get zero msg number + return NULL; // Not found + + m--; + + } while (m > 0); + + return NULL; +} + +struct MsgInfo * FindMessageByBID(char * BID) +{ + int m = NumberofMessages; + + struct MsgInfo * Msg; + + while (m > 0) + { + Msg = MsgHddrPtr[m]; + + if (strcmp(Msg->bid, BID) == 0) + return Msg; + + m--; + } + + return NULL; +} + +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len) +{ + unsigned char hash[50]; + unsigned char key[100]; + unsigned int i, j = 0, val1, val2; + unsigned char hostname[100]=""; + + gethostname(hostname, 100); + + strcpy(key, hostname); + strcat(key, ISPPOP3Name); + + md5(key, hash); + memcpy(&hash[16], hash, 16); // in case very long password + + // String is now encoded as hex pairs, but still need to decode old format + + for (i=0; i < len; i++) + { + if (Encrypt[i] < '0' || Encrypt[i] > 'F') + goto OldFormat; + } + + // Only '0' to 'F' + + for (i=0; i < len; i++) + { + val1 = Encrypt[i++]; + val1 -= '0'; + if (val1 > 9) + val1 -= 7; + + val2 = Encrypt[i]; + val2 -= '0'; + if (val2 > 9) + val2 -= 7; + + Pass[j] = (val1 << 4) | val2; + Pass[j] ^= hash[j]; + j++; + } + + return; + +OldFormat: + + for (i=0; i < len; i++) + { + Pass[i] = Encrypt[i] ^ hash[i]; + } + + return; +} + +int EncryptPass(char * Pass, char * Encrypt) +{ + unsigned char hash[50]; + unsigned char key[100]; + unsigned int i, val; + unsigned char hostname[100]; + unsigned char extendedpass[100]; + unsigned int passlen; + unsigned char * ptr; + + gethostname(hostname, 100); + + strcpy(key, hostname); + strcat(key, ISPPOP3Name); + + md5(key, hash); + memcpy(&hash[16], hash, 16); // in case very long password + + // if password is less than 16 chars, extend with zeros + + passlen=(int)strlen(Pass); + + strcpy(extendedpass, Pass); + + if (passlen < 16) + { + for (i=passlen+1; i <= 16; i++) + { + extendedpass[i] = 0; + } + + passlen = 16; + } + + ptr = Encrypt; + Encrypt[0] = 0; + + for (i=0; i < passlen; i++) + { + val = extendedpass[i] ^ hash[i]; + ptr += sprintf(ptr, "%02X", val); + } + + return passlen * 2; +} + + + +VOID SaveIntValue(config_setting_t * group, char * name, int value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_INT); + if(setting) + config_setting_set_int(setting, value); +} + +VOID SaveInt64Value(config_setting_t * group, char * name, long long value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_INT64); + if(setting) + config_setting_set_int64(setting, value); +} + +VOID SaveFloatValue(config_setting_t * group, char * name, double value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_FLOAT); + if (setting) + config_setting_set_float(setting, value); +} + +VOID SaveStringValue(config_setting_t * group, char * name, char * value) +{ + config_setting_t *setting; + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, value); + +} + + +VOID SaveOverride(config_setting_t * group, char * name, struct Override ** values) +{ + config_setting_t *setting; + struct Override ** Calls; + char Multi[10000]; + char * ptr = &Multi[1]; + + *ptr = 0; + + if (values) + { + Calls = values; + + while(Calls[0]) + { + ptr += sprintf(ptr, "%s, %d|", Calls[0]->Call, Calls[0]->Days); + Calls++; + } + *(--ptr) = 0; + } + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, &Multi[1]); + +} + + +VOID SaveMultiStringValue(config_setting_t * group, char * name, char ** values) +{ + config_setting_t *setting; + char ** Calls; + char Multi[100000]; + char * ptr = &Multi[1]; + + *ptr = 0; + + if (values) + { + Calls = values; + + while(Calls[0]) + { + strcpy(ptr, Calls[0]); + ptr += strlen(Calls[0]); + *(ptr++) = '|'; + Calls++; + } + *(--ptr) = 0; + } + + setting = config_setting_add(group, name, CONFIG_TYPE_STRING); + if (setting) + config_setting_set_string(setting, &Multi[1]); + +} + +int configSaved = 0; + +VOID SaveConfig(char * ConfigName) +{ + struct UserInfo * user; + struct BBSForwardingInfo * ForwardingInfo ; + config_setting_t *root, *group, *bbs; + int i; + char Size[80]; + struct BBSForwardingInfo DummyForwardingInfo; + char Line[1024]; + + if (configSaved == 0) + { + // only create backup once per run + + CopyConfigFile(ConfigName); + configSaved = 1; + } + + memset(&DummyForwardingInfo, 0, sizeof(struct BBSForwardingInfo)); + + // Get rid of old config before saving + + config_destroy(&cfg); + + memset((void *)&cfg, 0, sizeof(config_t)); + + config_init(&cfg); + + root = config_root_setting(&cfg); + + group = config_setting_add(root, "main", CONFIG_TYPE_GROUP); + + SaveIntValue(group, "Streams", MaxStreams); + SaveIntValue(group, "BBSApplNum", BBSApplNum); + SaveStringValue(group, "BBSName", BBSName); + SaveStringValue(group, "SYSOPCall", SYSOPCall); + SaveStringValue(group, "H-Route", HRoute); + SaveStringValue(group, "AMPRDomain", AMPRDomain); + SaveIntValue(group, "EnableUI", EnableUI); + SaveIntValue(group, "RefuseBulls", RefuseBulls); + SaveIntValue(group, "OnlyKnown", OnlyKnown); + SaveIntValue(group, "SendSYStoSYSOPCall", SendSYStoSYSOPCall); + SaveIntValue(group, "SendBBStoSYSOPCall", SendBBStoSYSOPCall); + SaveIntValue(group, "DontHoldNewUsers", DontHoldNewUsers); + SaveIntValue(group, "DefaultNoWINLINK", DefaultNoWINLINK); + SaveIntValue(group, "AllowAnon", AllowAnon); + SaveIntValue(group, "DontNeedHomeBBS", DontNeedHomeBBS); + SaveIntValue(group, "DontCheckFromCall", DontCheckFromCall); + SaveIntValue(group, "UserCantKillT", UserCantKillT); + + SaveIntValue(group, "ForwardToMe", ForwardToMe); + SaveIntValue(group, "SMTPPort", SMTPInPort); + SaveIntValue(group, "POP3Port", POP3InPort); + SaveIntValue(group, "NNTPPort", NNTPInPort); + SaveIntValue(group, "RemoteEmail", RemoteEmail); + SaveIntValue(group, "SendAMPRDirect", SendAMPRDirect); + + SaveIntValue(group, "MailForInterval", MailForInterval); + SaveStringValue(group, "MailForText", MailForText); + + EncryptedPassLen = EncryptPass(ISPAccountPass, EncryptedISPAccountPass); + + SaveIntValue(group, "AuthenticateSMTP", SMTPAuthNeeded); + + SaveIntValue(group, "MulticastRX", MulticastRX); + + SaveIntValue(group, "SMTPGatewayEnabled", ISP_Gateway_Enabled); + SaveIntValue(group, "ISPSMTPPort", ISPSMTPPort); + SaveIntValue(group, "ISPPOP3Port", ISPPOP3Port); + SaveIntValue(group, "POP3PollingInterval", ISPPOP3Interval); + + SaveStringValue(group, "MyDomain", MyDomain); + SaveStringValue(group, "ISPSMTPName", ISPSMTPName); + SaveStringValue(group, "ISPEHLOName", ISPEHLOName); + SaveStringValue(group, "ISPPOP3Name", ISPPOP3Name); + SaveStringValue(group, "ISPAccountName", ISPAccountName); + SaveStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); + + + // Save Window Sizes + +#ifndef LINBPQ + + if (ConsoleRect.right) + { + sprintf(Size,"%d,%d,%d,%d",ConsoleRect.left, ConsoleRect.right, + ConsoleRect.top, ConsoleRect.bottom); + + SaveStringValue(group, "ConsoleSize", Size); + } + + sprintf(Size,"%d,%d,%d,%d,%d",MonitorRect.left,MonitorRect.right,MonitorRect.top,MonitorRect.bottom, hMonitor ? 1 : 0); + SaveStringValue(group, "MonitorSize", Size); + + sprintf(Size,"%d,%d,%d,%d",MainRect.left,MainRect.right,MainRect.top,MainRect.bottom); + SaveStringValue(group, "WindowSize", Size); + + SaveIntValue(group, "Bells", Bells); + SaveIntValue(group, "FlashOnBell", FlashOnBell); + SaveIntValue(group, "StripLF", StripLF); + SaveIntValue(group, "WarnWrap", WarnWrap); + SaveIntValue(group, "WrapInput", WrapInput); + SaveIntValue(group, "FlashOnConnect", FlashOnConnect); + SaveIntValue(group, "CloseWindowOnBye", CloseWindowOnBye); + +#endif + + SaveIntValue(group, "Log_BBS", LogBBS); + SaveIntValue(group, "Log_TCP", LogTCP); + + sprintf(Size,"%d,%d,%d,%d", Ver[0], Ver[1], Ver[2], Ver[3]); + SaveStringValue(group, "Version", Size); + + // Save Welcome Messages and prompts + + SaveStringValue(group, "WelcomeMsg", WelcomeMsg); + SaveStringValue(group, "NewUserWelcomeMsg", NewWelcomeMsg); + SaveStringValue(group, "ExpertWelcomeMsg", ExpertWelcomeMsg); + + SaveStringValue(group, "Prompt", Prompt); + SaveStringValue(group, "NewUserPrompt", NewPrompt); + SaveStringValue(group, "ExpertPrompt", ExpertPrompt); + SaveStringValue(group, "SignoffMsg", SignoffMsg); + + SaveMultiStringValue(group, "RejFrom", RejFrom); + SaveMultiStringValue(group, "RejTo", RejTo); + SaveMultiStringValue(group, "RejAt", RejAt); + SaveMultiStringValue(group, "RejBID", RejBID); + + SaveMultiStringValue(group, "HoldFrom", HoldFrom); + SaveMultiStringValue(group, "HoldTo", HoldTo); + SaveMultiStringValue(group, "HoldAt", HoldAt); + SaveMultiStringValue(group, "HoldBID", HoldBID); + + SaveIntValue(group, "SendWP", SendWP); + SaveIntValue(group, "SendWPType", SendWPType); + SaveIntValue(group, "FilterWPBulls", FilterWPBulls); + SaveIntValue(group, "NoWPGuesses", NoWPGuesses); + + SaveStringValue(group, "SendWPTO", SendWPTO); + SaveStringValue(group, "SendWPVIA", SendWPVIA); + + SaveMultiStringValue(group, "SendWPAddrs", SendWPAddrs); + + // Save Forwarding Config + + // Interval and Max Sizes and Aliases are not user specific + + SaveIntValue(group, "MaxTXSize", MaxTXSize); + SaveIntValue(group, "MaxRXSize", MaxRXSize); + SaveIntValue(group, "ReaddressLocal", ReaddressLocal); + SaveIntValue(group, "ReaddressReceived", ReaddressReceived); + SaveIntValue(group, "WarnNoRoute", WarnNoRoute); + SaveIntValue(group, "Localtime", Localtime); + SaveIntValue(group, "SendPtoMultiple", SendPtoMultiple); + + SaveMultiStringValue(group, "FWDAliases", AliasText); + + bbs = config_setting_add(root, "BBSForwarding", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + ForwardingInfo = user->ForwardingInfo; + + if (ForwardingInfo == NULL) + continue; + + if (memcmp(ForwardingInfo, &DummyForwardingInfo, sizeof(struct BBSForwardingInfo)) == 0) + continue; // Ignore empty records; + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + { + char Key[20] = "*"; + strcat (Key, user->Call); + group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); + } + else + group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); + + SaveMultiStringValue(group, "TOCalls", ForwardingInfo->TOCalls); + SaveMultiStringValue(group, "ConnectScript", ForwardingInfo->ConnectScript); + SaveMultiStringValue(group, "ATCalls", ForwardingInfo->ATCalls); + SaveMultiStringValue(group, "HRoutes", ForwardingInfo->Haddresses); + SaveMultiStringValue(group, "HRoutesP", ForwardingInfo->HaddressesP); + SaveMultiStringValue(group, "FWDTimes", ForwardingInfo->FWDTimes); + + SaveIntValue(group, "Enabled", ForwardingInfo->Enabled); + SaveIntValue(group, "RequestReverse", ForwardingInfo->ReverseFlag); + SaveIntValue(group, "AllowBlocked", ForwardingInfo->AllowBlocked); + SaveIntValue(group, "AllowCompressed", ForwardingInfo->AllowCompressed); + SaveIntValue(group, "UseB1Protocol", ForwardingInfo->AllowB1); + SaveIntValue(group, "UseB2Protocol", ForwardingInfo->AllowB2); + SaveIntValue(group, "SendCTRLZ", ForwardingInfo->SendCTRLZ); + + SaveIntValue(group, "FWDPersonalsOnly", ForwardingInfo->PersonalOnly); + SaveIntValue(group, "FWDNewImmediately", ForwardingInfo->SendNew); + SaveIntValue(group, "FwdInterval", ForwardingInfo->FwdInterval); + SaveIntValue(group, "RevFWDInterval", ForwardingInfo->RevFwdInterval); + SaveIntValue(group, "MaxFBBBlock", ForwardingInfo->MaxFBBBlockSize); + SaveIntValue(group, "ConTimeout", ForwardingInfo->ConTimeout); + + SaveStringValue(group, "BBSHA", ForwardingInfo->BBSHA); + } + + + // Save Housekeeping config + + group = config_setting_add(root, "Housekeeping", CONFIG_TYPE_GROUP); + + SaveInt64Value(group, "LastHouseKeepingTime", LastHouseKeepingTime); + SaveInt64Value(group, "LastTrafficTime", LastTrafficTime); + SaveIntValue(group, "MaxMsgno", MaxMsgno); + SaveIntValue(group, "BidLifetime", BidLifetime); + SaveIntValue(group, "MaxAge", MaxAge); + SaveIntValue(group, "LogLifetime", LogAge); + SaveIntValue(group, "LogLifetime", LogAge); + SaveIntValue(group, "MaintInterval", MaintInterval); + SaveIntValue(group, "UserLifetime", UserLifetime); + SaveIntValue(group, "MaintTime", MaintTime); + SaveFloatValue(group, "PR", PR); + SaveFloatValue(group, "PUR", PUR); + SaveFloatValue(group, "PF", PF); + SaveFloatValue(group, "PNF", PNF); + SaveIntValue(group, "BF", BF); + SaveIntValue(group, "BNF", BNF); + SaveIntValue(group, "NTSD", NTSD); + SaveIntValue(group, "NTSF", NTSF); + SaveIntValue(group, "NTSU", NTSU); +// SaveIntValue(group, "AP", AP); +// SaveIntValue(group, "AB", AB); + SaveIntValue(group, "DeletetoRecycleBin", DeletetoRecycleBin); + SaveIntValue(group, "SuppressMaintEmail", SuppressMaintEmail); + SaveIntValue(group, "MaintSaveReg", SaveRegDuringMaint); + SaveIntValue(group, "OverrideUnsent", OverrideUnsent); + SaveIntValue(group, "SendNonDeliveryMsgs", SendNonDeliveryMsgs); + SaveIntValue(group, "GenerateTrafficReport", GenerateTrafficReport); + + SaveOverride(group, "LTFROM", LTFROM); + SaveOverride(group, "LTTO", LTTO); + SaveOverride(group, "LTAT", LTAT); + + // Save UI config + + for (i=1; i<=32; i++) + { + char Key[100]; + + sprintf(Key, "UIPort%d", i); + + group = config_setting_add(root, Key, CONFIG_TYPE_GROUP); + + if (group) + { + SaveIntValue(group, "Enabled", UIEnabled[i]); + SaveIntValue(group, "SendMF", UIMF[i]); + SaveIntValue(group, "SendHDDR", UIHDDR[i]); + SaveIntValue(group, "SendNull", UINull[i]); + + if (UIDigi[i]) + SaveStringValue(group, "Digis", UIDigi[i]); + } + } + + // Save User Config + + bbs = config_setting_add(root, "BBSUsers", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofUsers; i++) + { + char stats[256], stats2[256]; + struct MsgStats * Stats; + char Key[20] = "*"; + + user = UserRecPtr[i]; + + if (isdigit(user->Call[0]) || user->Call[0] == '_') + { + strcat (Key, user->Call); +// group = config_setting_add(bbs, Key, CONFIG_TYPE_GROUP); + } + else + { + strcpy(Key, user->Call); +// group = config_setting_add(bbs, user->Call, CONFIG_TYPE_GROUP); + } + /* + SaveStringValue(group, "Name", user->Name); + SaveStringValue(group, "Address", user->Address); + SaveStringValue(group, "HomeBBS", user->HomeBBS); + SaveStringValue(group, "QRA", user->QRA); + SaveStringValue(group, "pass", user->pass); + SaveStringValue(group, "ZIP", user->ZIP); + SaveStringValue(group, "CMSPass", user->CMSPass); + + SaveIntValue(group, "lastmsg", user->lastmsg); + SaveIntValue(group, "flags", user->flags); + SaveIntValue(group, "PageLen", user->PageLen); + SaveIntValue(group, "BBSNumber", user->BBSNumber); + SaveIntValue(group, "RMSSSIDBits", user->RMSSSIDBits); + SaveIntValue(group, "WebSeqNo", user->WebSeqNo); + + SaveInt64Value(group, "TimeLastConnected", user->TimeLastConnected); +*/ + Stats = &user->Total; + +// sprintf(stats, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + sprintf(stats, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", + Stats->ConnectsIn, Stats->ConnectsOut, + Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], + Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], + Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], + Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], + Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], + Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); + +// SaveStringValue(group, "Totsl", stats); + + Stats = &user->Last; + + sprintf(stats2, "%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d,%.0d", +// sprintf(stats2, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + Stats->ConnectsIn, Stats->ConnectsOut, + Stats->MsgsReceived[0], Stats->MsgsReceived[1], Stats->MsgsReceived[2], Stats->MsgsReceived[3], + Stats->MsgsSent[0], Stats->MsgsSent[1], Stats->MsgsSent[2], Stats->MsgsSent[3], + Stats->MsgsRejectedIn[0], Stats->MsgsRejectedIn[1], Stats->MsgsRejectedIn[2], Stats->MsgsRejectedIn[3], + Stats->MsgsRejectedOut[0], Stats->MsgsRejectedOut[1], Stats->MsgsRejectedOut[2], Stats->MsgsRejectedOut[3], + Stats->BytesForwardedIn[0], Stats->BytesForwardedIn[1], Stats->BytesForwardedIn[2], Stats->BytesForwardedIn[3], + Stats->BytesForwardedOut[0], Stats->BytesForwardedOut[1], Stats->BytesForwardedOut[2], Stats->BytesForwardedOut[3]); + +// SaveStringValue(group, "Last", stats2); + + sprintf(Line,"%s^%s^%s^%s^%s^%s^%s^%d^%d^%d^%d^%d^%d^%lld^%s^%s", + user->Name, user->Address, user->HomeBBS, user->QRA, user->pass, user->ZIP, user->CMSPass, + user->lastmsg, user->flags, user->PageLen, user->BBSNumber, user->RMSSSIDBits, user->WebSeqNo, + user->TimeLastConnected, stats, stats2); + + if (strlen(Line) < 10) + continue; + + SaveStringValue(bbs, Key, Line); + } + +/* + wp = config_setting_add(root, "WP", CONFIG_TYPE_GROUP); + + for (i = 0; i <= NumberofWPrecs; i++) + { + char WPString[1024]; + long long val1, val2; + + WP = WPRecPtr[i]; + val1 = WP->last_modif; + val2 = WP->last_seen; + + sprintf(Key, "R%d", i); + + sprintf(WPString, "%s|%s|%d|%d|%d|%s|%s|%s|%s|%s|%s|%ld|%ld", + &WP->callsign[0], &WP->name[0], WP->Type, WP->changed, WP->seen, &WP->first_homebbs[0], + &WP->secnd_homebbs[0], &WP->first_zip[0], &WP->secnd_zip[0], &WP->first_qth[0], &WP->secnd_qth[0], + val1, val2); + + SaveStringValue(wp, Key, WPString); + } + + // Save Message Headers + + msgs = config_setting_add(root, "MSGS", CONFIG_TYPE_GROUP); + + memset(MsgHddrPtr[0], 0, sizeof(struct MsgInfo)); + + MsgHddrPtr[0]->type = 'X'; + MsgHddrPtr[0]->status = '2'; + MsgHddrPtr[0]->number = 0; + MsgHddrPtr[0]->length = LatestMsg; + + + for (i = 0; i <= NumberofMessages; i++) + { + Msg = MsgHddrPtr[i]; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString1[n * 2], "%02X", Msg->fbbs[n]); + + n = 39; + while (n >=0 && HEXString1[n] == '0') + HEXString1[n--] = 0; + + for (n = 0; n < NBMASK; n++) + sprintf(&HEXString2[n * 2], "%02X", Msg->forw[n]); + + n = 39; + while (n >= 0 && HEXString2[n] == '0') + HEXString2[n--] = 0; + + sprintf(Key, "R%d", Msg->number); + + n = sprintf(Line, "%c|%c|%d|%lld|%s|%s|%s|%s|%s|%d|%lld|%lld|%s|%s|%s|%d|%s", Msg->type, Msg->status, + Msg->length, Msg->datereceived, &Msg->bbsfrom[0], &Msg->via[0], &Msg->from[0], + &Msg->to[0], &Msg->bid[0], Msg->B2Flags, Msg->datecreated, Msg->datechanged, HEXString1, HEXString2, + &Msg->emailfrom[0], Msg->UTF8, &Msg->title[0]); + + SaveStringValue(msgs, Key, Line); + } + + // Save Bids + + msgs = config_setting_add(root, "BIDS", CONFIG_TYPE_GROUP); + + for (i=1; i <= NumberofBIDs; i++) + { + sprintf(Key, "R%s", BIDRecPtr[i]->BID); + sprintf(Line, "%d|%d", BIDRecPtr[i]->mode, BIDRecPtr[i]->u.timestamp); + SaveStringValue(msgs, Key, Line); + } + +#ifdef LINBPQ + + if(! config_write_file(&cfg,"/dev/shm/linmail.cfg.temp" )) + { + print("Error while writing file.\n"); + config_destroy(&cfg); + return; + } + + CopyFile("/dev/shm/linmail.cfg.temp", ConfigName, FALSE); + +#else +*/ + if(! config_write_file(&cfg, ConfigName)) + { + fprintf(stderr, "Error while writing file.\n"); + config_destroy(&cfg); + return; + } + +//#endif + + config_destroy(&cfg); + +/* + +#ifndef LINBPQ + + // Save a copy with current Date/Time Stamp for debugging + + { + char Backup[MAX_PATH]; + time_t LT; + struct tm * tm; + + LT = time(NULL); + tm = gmtime(<); + + sprintf(Backup,"%s.%02d%02d%02d%02d%02d.save", ConfigName, tm->tm_year-100, tm->tm_mon+1, + tm->tm_mday, tm->tm_hour, tm->tm_min); + + CopyFile(ConfigName, Backup, FALSE); // Copy to .bak + } +#endif +*/ +} + +int GetIntValue(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int (setting); + + return 0; +} + +long long GetInt64Value(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int64 (setting); + + return 0; +} + +double GetFloatValue(config_setting_t * group, char * name) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + + if (setting) + { + return config_setting_get_float (setting); + } + return 0; +} + +int GetIntValueWithDefault(config_setting_t * group, char * name, int Default) +{ + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + return config_setting_get_int (setting); + + return Default; +} + + +BOOL GetStringValue(config_setting_t * group, char * name, char * value) +{ + const char * str; + config_setting_t *setting; + + setting = config_setting_get_member (group, name); + if (setting) + { + str = config_setting_get_string (setting); + strcpy(value, str); + return TRUE; + } + value[0] = 0; + return FALSE; +} + +BOOL GetConfig(char * ConfigName) +{ + int i; + char Size[80]; + config_setting_t *setting; + const char * ptr; + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + + if(! config_read_file(&cfg, ConfigName)) + { + char Msg[256]; + sprintf(Msg, "Config File Line %d - %s\n", + config_error_line(&cfg), config_error_text(&cfg)); +#ifdef WIN32 + MessageBox(NULL, Msg, "BPQMail", MB_ICONSTOP); +#else + printf("%s", Msg); +#endif + config_destroy(&cfg); + return(EXIT_FAILURE); + } + +#if LIBCONFIG_VER_MINOR > 5 + config_set_option(&cfg, CONFIG_OPTION_AUTOCONVERT, 1); +#else + config_set_auto_convert (&cfg, 1); +#endif + + group = config_lookup (&cfg, "main"); + + if (group == NULL) + return EXIT_FAILURE; + + SMTPInPort = GetIntValue(group, "SMTPPort"); + POP3InPort = GetIntValue(group, "POP3Port"); + NNTPInPort = GetIntValue(group, "NNTPPort"); + RemoteEmail = GetIntValue(group, "RemoteEmail"); + MaxStreams = GetIntValue(group, "Streams"); + BBSApplNum = GetIntValue(group, "BBSApplNum"); + EnableUI = GetIntValue(group, "EnableUI"); + MailForInterval = GetIntValue(group, "MailForInterval"); + RefuseBulls = GetIntValue(group, "RefuseBulls"); + OnlyKnown = GetIntValue(group, "OnlyKnown"); + SendSYStoSYSOPCall = GetIntValue(group, "SendSYStoSYSOPCall"); + SendBBStoSYSOPCall = GetIntValue(group, "SendBBStoSYSOPCall"); + DontHoldNewUsers = GetIntValue(group, "DontHoldNewUsers"); + DefaultNoWINLINK = GetIntValue(group, "DefaultNoWINLINK"); + ForwardToMe = GetIntValue(group, "ForwardToMe"); + AllowAnon = GetIntValue(group, "AllowAnon"); + UserCantKillT = GetIntValue(group, "UserCantKillT"); + + DontNeedHomeBBS = GetIntValue(group, "DontNeedHomeBBS"); + DontCheckFromCall = GetIntValue(group, "DontCheckFromCall"); + MaxTXSize = GetIntValue(group, "MaxTXSize"); + MaxRXSize = GetIntValue(group, "MaxRXSize"); + ReaddressLocal = GetIntValue(group, "ReaddressLocal"); + ReaddressReceived = GetIntValue(group, "ReaddressReceived"); + WarnNoRoute = GetIntValue(group, "WarnNoRoute"); + SendPtoMultiple = GetIntValue(group, "SendPtoMultiple"); + Localtime = GetIntValue(group, "Localtime"); + AliasText = GetMultiStringValue(group, "FWDAliases"); + GetStringValue(group, "BBSName", BBSName); + GetStringValue(group, "MailForText", MailForText); + GetStringValue(group, "SYSOPCall", SYSOPCall); + GetStringValue(group, "H-Route", HRoute); + GetStringValue(group, "AMPRDomain", AMPRDomain); + SendAMPRDirect = GetIntValue(group, "SendAMPRDirect"); + ISP_Gateway_Enabled = GetIntValue(group, "SMTPGatewayEnabled"); + ISPPOP3Interval = GetIntValue(group, "POP3PollingInterval"); + GetStringValue(group, "MyDomain", MyDomain); + GetStringValue(group, "ISPSMTPName", ISPSMTPName); + GetStringValue(group, "ISPPOP3Name", ISPPOP3Name); + ISPSMTPPort = GetIntValue(group, "ISPSMTPPort"); + ISPPOP3Port = GetIntValue(group, "ISPPOP3Port"); + GetStringValue(group, "ISPAccountName", ISPAccountName); + GetStringValue(group, "ISPAccountPass", EncryptedISPAccountPass); + GetStringValue(group, "ISPAccountName", ISPAccountName); + + sprintf(SignoffMsg, "73 de %s\r", BBSName); // Default + GetStringValue(group, "SignoffMsg", SignoffMsg); + + DecryptPass(EncryptedISPAccountPass, ISPAccountPass, (int)strlen(EncryptedISPAccountPass)); + + SMTPAuthNeeded = GetIntValue(group, "AuthenticateSMTP"); + LogBBS = GetIntValue(group, "Log_BBS"); + LogTCP = GetIntValue(group, "Log_TCP"); + + MulticastRX = GetIntValue(group, "MulticastRX"); + +#ifndef LINBPQ + + GetStringValue(group, "MonitorSize", Size); + sscanf(Size,"%d,%d,%d,%d,%d",&MonitorRect.left,&MonitorRect.right,&MonitorRect.top,&MonitorRect.bottom,&OpenMon); + + GetStringValue(group, "WindowSize", Size); + sscanf(Size,"%d,%d,%d,%d",&MainRect.left,&MainRect.right,&MainRect.top,&MainRect.bottom); + + Bells = GetIntValue(group, "Bells"); + + FlashOnBell = GetIntValue(group, "FlashOnBell"); + + StripLF = GetIntValue(group, "StripLF"); + CloseWindowOnBye = GetIntValue(group, "CloseWindowOnBye"); + WarnWrap = GetIntValue(group, "WarnWrap"); + WrapInput = GetIntValue(group, "WrapInput"); + FlashOnConnect = GetIntValue(group, "FlashOnConnect"); + + GetStringValue(group, "ConsoleSize", Size); + sscanf(Size,"%d,%d,%d,%d,%d", &ConsoleRect.left, &ConsoleRect.right, + &ConsoleRect.top, &ConsoleRect.bottom,&OpenConsole); + +#endif + + // Get Welcome Messages + + setting = config_setting_get_member (group, "WelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + WelcomeMsg = _strdup(ptr); + } + else + WelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); + + + setting = config_setting_get_member (group, "NewUserWelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + NewWelcomeMsg = _strdup(ptr); + } + else + NewWelcomeMsg = _strdup("Hello $I. Latest Message is $L, Last listed is $Z\r\n"); + + + setting = config_setting_get_member (group, "ExpertWelcomeMsg"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + ExpertWelcomeMsg = _strdup(ptr); + } + else + ExpertWelcomeMsg = _strdup(""); + + // Get Prompts + + setting = config_setting_get_member (group, "Prompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + Prompt = _strdup(ptr); + } + else + { + Prompt = malloc(20); + sprintf(Prompt, "de %s>\r\n", BBSName); + } + + setting = config_setting_get_member (group, "NewUserPrompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + NewPrompt = _strdup(ptr); + } + else + { + NewPrompt = malloc(20); + sprintf(NewPrompt, "de %s>\r\n", BBSName); + } + + setting = config_setting_get_member (group, "ExpertPrompt"); + + if (setting && setting->value.sval[0]) + { + ptr = config_setting_get_string (setting); + ExpertPrompt = _strdup(ptr); + } + else + { + ExpertPrompt = malloc(20); + sprintf(ExpertPrompt, "de %s>\r\n", BBSName); + } + + TidyPrompts(); + + RejFrom = GetMultiStringValue(group, "RejFrom"); + RejTo = GetMultiStringValue(group, "RejTo"); + RejAt = GetMultiStringValue(group, "RejAt"); + RejBID = GetMultiStringValue(group, "RejBID"); + + HoldFrom = GetMultiStringValue(group, "HoldFrom"); + HoldTo = GetMultiStringValue(group, "HoldTo"); + HoldAt = GetMultiStringValue(group, "HoldAt"); + HoldBID = GetMultiStringValue(group, "HoldBID"); + + // Send WP Params + + SendWP = GetIntValue(group, "SendWP"); + SendWPType = GetIntValue(group, "SendWPType"); + + GetStringValue(group, "SendWPTO", SendWPTO); + GetStringValue(group, "SendWPVIA", SendWPVIA); + + SendWPAddrs = GetMultiStringValue(group, "SendWPAddrs"); + + FilterWPBulls = GetIntValue(group, "FilterWPBulls"); + NoWPGuesses = GetIntValue(group, "NoWPGuesses"); + + if (SendWPAddrs[0] == NULL && SendWPTO[0]) + { + // convert old format TO and VIA to entry in SendWPAddrs + + SendWPAddrs = realloc(SendWPAddrs, 8); // Add entry + + if (SendWPVIA[0]) + { + char WP[256]; + + sprintf(WP, "%s@%s", SendWPTO, SendWPVIA); + SendWPAddrs[0] = _strdup(WP); + } + else + SendWPAddrs[0] = _strdup(SendWPTO); + + + SendWPAddrs[1] = 0; + + SendWPTO[0] = 0; + SendWPVIA[0] = 0; + } + + GetStringValue(group, "Version", Size); + sscanf(Size,"%d,%d,%d,%d", &LastVer[0], &LastVer[1], &LastVer[2], &LastVer[3]); + + for (i=1; i<=32; i++) + { + char Key[100]; + + sprintf(Key, "UIPort%d", i); + + group = config_lookup (&cfg, Key); + + if (group) + { + UIEnabled[i] = GetIntValue(group, "Enabled"); + UIMF[i] = GetIntValueWithDefault(group, "SendMF", UIEnabled[i]); + UIHDDR[i] = GetIntValueWithDefault(group, "SendHDDR", UIEnabled[i]); + UINull[i] = GetIntValue(group, "SendNull"); + Size[0] = 0; + GetStringValue(group, "Digis", Size); + if (Size[0]) + UIDigi[i] = _strdup(Size); + } + } + + group = config_lookup (&cfg, "Housekeeping"); + + if (group) + { + LastHouseKeepingTime = GetIntValue(group, "LastHouseKeepingTime"); + LastTrafficTime = GetIntValue(group, "LastTrafficTime"); + MaxMsgno = GetIntValue(group, "MaxMsgno"); + LogAge = GetIntValue(group, "LogLifetime"); + BidLifetime = GetIntValue(group, "BidLifetime"); + MaxAge = GetIntValue(group, "MaxAge"); + if (MaxAge == 0) + MaxAge = 30; + UserLifetime = GetIntValue(group, "UserLifetime"); + MaintInterval = GetIntValue(group, "MaintInterval"); + + if (MaintInterval == 0) + MaintInterval = 24; + + MaintTime = GetIntValue(group, "MaintTime"); + + PR = GetFloatValue(group, "PR"); + PUR = GetFloatValue(group, "PUR"); + PF = GetFloatValue(group, "PF"); + PNF = GetFloatValue(group, "PNF"); + + BF = GetIntValue(group, "BF"); + BNF = GetIntValue(group, "BNF"); + NTSD = GetIntValue(group, "NTSD"); + NTSU = GetIntValue(group, "NTSU"); + NTSF = GetIntValue(group, "NTSF"); +// AP = GetIntValue(group, "AP"); +// AB = GetIntValue(group, "AB"); + DeletetoRecycleBin = GetIntValue(group, "DeletetoRecycleBin"); + SuppressMaintEmail = GetIntValue(group, "SuppressMaintEmail"); + SaveRegDuringMaint = GetIntValue(group, "MaintSaveReg"); + OverrideUnsent = GetIntValue(group, "OverrideUnsent"); + SendNonDeliveryMsgs = GetIntValue(group, "SendNonDeliveryMsgs"); + OverrideUnsent = GetIntValue(group, "OverrideUnsent"); + GenerateTrafficReport = GetIntValueWithDefault(group, "GenerateTrafficReport", 1); + + LTFROM = GetOverrides(group, "LTFROM"); + LTTO = GetOverrides(group, "LTTO"); + LTAT = GetOverrides(group, "LTAT"); + } + + return EXIT_SUCCESS; +} + + +int Connected(int Stream) +{ + int n, Mask; + CIRCUIT * conn; + struct UserInfo * user = NULL; + char callsign[10]; + int port, paclen, maxframe, l4window; + char ConnectedMsg[] = "*** CONNECTED "; + char Msg[100]; + char Title[100]; + int Freq = 0; + int Mode = 0; + BPQVECSTRUC * SESS; + TRANSPORTENTRY * Sess1 = NULL, * Sess2; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (Stream == conn->BPQStream) + { + if (conn->Active) + { + // Probably an outgoing connect + + ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + conn->ErrorCount = 0; + + if (conn->BBSFlags & RunningConnectScript) + { + // BBS Outgoing Connect + + conn->paclen = 236; + + // Run first line of connect script + + ChangeSessionIdletime(Stream, BBSIDLETIME); // Default Idletime for BBS Sessions + ProcessBBSConnectScript(conn, ConnectedMsg, 15); + return 0; + } + } + + // Incoming Connect + + // Try to find port, freq, mode, etc + +#ifdef LINBPQ + SESS = &BPQHOSTVECTOR[0]; +#else + SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + SESS +=(Stream - 1); + + if (SESS) + Sess1 = SESS->HOSTSESSION; + + if (Sess1) + { + Sess2 = Sess1->L4CROSSLINK; + + if (Sess2) + { + // See if L2 session - if so, get info from WL2K report line + + // if Session has report info, use it + + if (Sess2->Mode) + { + Freq = Sess2->Frequency; + Mode = Sess2->Mode; + } + else if (Sess2->L4CIRCUITTYPE & L2LINK) + { + LINKTABLE * LINK = Sess2->L4TARGET.LINK; + PORTCONTROLX * PORT = LINK->LINKPORT; + + Freq = PORT->WL2KInfo.Freq; + Mode = PORT->WL2KInfo.mode; + } + else + { + if (Sess2->RMSCall[0]) + { + Freq = Sess2->Frequency; + Mode = Sess2->Mode; + } + } + } + } + + memset(conn, 0, sizeof(ConnectionInfo)); // Clear everything + conn->Active = TRUE; + conn->BPQStream = Stream; + ChangeSessionIdletime(Stream, USERIDLETIME); // Default Idletime for BBS Sessions + + conn->SendB = conn->SendP = conn->SendT = conn->DoReverse = TRUE; + conn->MaxBLen = conn->MaxPLen = conn->MaxTLen = 99999999; + conn->ErrorCount = 0; + + conn->Secure_Session = GetConnectionInfo(Stream, callsign, + &port, &conn->SessType, &paclen, &maxframe, &l4window); + + strlop(callsign, ' '); // Remove trailing spaces + + if (strcmp(&callsign[strlen(callsign) - 2], "-T") == 0) + conn->RadioOnlyMode = 'T'; + else if (strcmp(&callsign[strlen(callsign) - 2], "-R") == 0) + conn->RadioOnlyMode = 'R'; + else + conn->RadioOnlyMode = 0; + + memcpy(conn->Callsign, callsign, 10); + + strlop(callsign, '-'); // Remove any SSID + + user = LookupCall(callsign); + + if (user == NULL) + { + int Length=0; + + if (OnlyKnown) + { + // Unknown users not allowed + + n = sprintf_s(Msg, sizeof(Msg), "Incoming Connect from unknown user %s Rejected", callsign); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + Disconnect(Stream); + return 0; + } + + user = AllocateUserRecord(callsign); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (SendNewUserMessage) + { + char * MailBuffer = malloc(100); + Length += sprintf(MailBuffer, "New User %s Connected to Mailbox on Port %d Freq %d Mode %d\r\n", callsign, port, Freq, Mode); + + sprintf(Title, "New User %s", callsign); + + SendMessageToSYSOP(Title, MailBuffer, Length); + } + + if (user == NULL) return 0; // Cant happen?? + + if (!DontHoldNewUsers) + user->flags |= F_HOLDMAIL; + + if (DefaultNoWINLINK) + user->flags |= F_NOWINLINK; + + // Always set WLE User - can't see it doing any harm + + user->flags |= F_Temp_B2_BBS; + + conn->NewUser = TRUE; + } + + user->TimeLastConnected = time(NULL); + user->Total.ConnectsIn++; + + conn->UserPointer = user; + + conn->lastmsg = user->lastmsg; + + conn->NextMessagetoForward = FirstMessageIndextoForward; + + if (paclen == 0) + { + paclen = 236; + + if (conn->SessType & Sess_PACTOR) + paclen = 100; + } + + conn->paclen = paclen; + + // Set SYSOP flag if user is defined as SYSOP and Host Session + + if (((conn->SessType & Sess_BPQHOST) == Sess_BPQHOST) && (user->flags & F_SYSOP)) + conn->sysop = TRUE; + + if (conn->Secure_Session && (user->flags & F_SYSOP)) + conn->sysop = TRUE; + + Mask = 1 << (GetApplNum(Stream) - 1); + + if (user->flags & F_Excluded) + { + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s Rejected by Exclude Flag", user->Call); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + Disconnect(Stream); + return 0; + } + + if (port) + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s on Port %d Freq %d Mode %s", + user->Call, port, Freq, WL2KModes[Mode]); + else + n=sprintf_s(Msg, sizeof(Msg), "Incoming Connect from %s", user->Call); + + // Send SID and Prompt (Unless Sync) + + if (user->ForwardingInfo && user->ForwardingInfo->ConTimeout) + conn->SIDResponseTimer = user->ForwardingInfo->ConTimeout / 10; // 10 sec ticks + else + conn->SIDResponseTimer = 12; // Allow a couple of minutes for response to SID + + { + BOOL B1 = FALSE, B2 = FALSE, BIN = FALSE, BLOCKED = FALSE; + BOOL WL2KRO = FALSE; + + struct BBSForwardingInfo * ForwardingInfo; + + if (conn->RadioOnlyMode == 'R') + WL2KRO = 1; + + conn->PageLen = user->PageLen; + conn->Paging = (user->PageLen > 0); + + if (user->flags & F_Temp_B2_BBS) + { + // An RMS Express user that needs a temporary BBS struct + + if (user->ForwardingInfo == NULL) + { + // we now save the Forwarding info if BBS flag is cleared, + // so there may already be a ForwardingInfo + + user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + } + + if (user->BBSNumber == 0) + user->BBSNumber = NBBBS; + + ForwardingInfo = user->ForwardingInfo; + + ForwardingInfo->AllowCompressed = TRUE; + B1 = ForwardingInfo->AllowB1 = FALSE; + B2 = ForwardingInfo->AllowB2 = TRUE; + BLOCKED = ForwardingInfo->AllowBlocked = TRUE; + } + + if (conn->NewUser) + { + BLOCKED = TRUE; + BIN = TRUE; + B2 = TRUE; + } + + if (user->ForwardingInfo) + { + BLOCKED = user->ForwardingInfo->AllowBlocked; + if (BLOCKED) + { + BIN = user->ForwardingInfo->AllowCompressed; + B1 = user->ForwardingInfo->AllowB1; + B2 = user->ForwardingInfo->AllowB2; + } + } + + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + if (conn->RadioOnlyMode) + nodeprintf(conn,";WL2K-Radio/Internet_Network\r"); + + if (!(conn->BBSFlags & SYNCMODE)) + { + + nodeprintf(conn, BBSSID, "BPQ-", + Ver[0], Ver[1], Ver[2], Ver[3], + BIN ? "B" : "", B1 ? "1" : "", B2 ? "2" : "", + BLOCKED ? "FW": "", WL2KRO ? "" : "J"); + + // if (user->flags & F_Temp_B2_BBS) + // nodeprintf(conn,";PQ: 66427529\r"); + + // nodeprintf(conn,"[WL2K-BPQ.1.0.4.39-B2FWIHJM$]\r"); + } + } + + if ((user->Name[0] == 0) & AllowAnon) + strcpy(user->Name, user->Call); + + if (!(conn->BBSFlags & SYNCMODE)) + { + if (user->Name[0] == 0) + { + conn->Flags |= GETTINGUSER; + BBSputs(conn, NewUserPrompt); + } + else + SendWelcomeMsg(Stream, conn, user); + } + else + { + // Seems to be a timing problem - see if this fixes it + + Sleep(500); + } + + RefreshMainWindow(); + + return 0; + } + } + + return 0; +} + +int Disconnected (int Stream) +{ + struct UserInfo * user = NULL; + CIRCUIT * conn; + int n; + char Msg[255]; + int len; + char DiscMsg[] = "DISCONNECTED "; + + for (n = 0; n <= NumberofStreams-1; n++) + { + conn=&Connections[n]; + + if (Stream == conn->BPQStream) + { + if (conn->Active == FALSE) + return 0; + + // if still running connect script, reenter it to see if + // there is an else + + if (conn->BBSFlags & RunningConnectScript) + { + // We need to see if we got as far as connnected, + // as if we have we need to reset the connect script + // over the ELSE + + struct BBSForwardingInfo * ForwardingInfo = conn->UserPointer->ForwardingInfo; + char ** Scripts; + + if (ForwardingInfo->TempConnectScript) + Scripts = ForwardingInfo->TempConnectScript; + else + Scripts = ForwardingInfo->ConnectScript; + + // First see if any script left + + if (Scripts[ForwardingInfo->ScriptIndex]) + { + if (ForwardingInfo->MoreLines == FALSE) + { + // Have reached end of script, so need to set back over ELSE + + ForwardingInfo->ScriptIndex--; + ForwardingInfo->MoreLines = TRUE; + } + + // if (Scripts[ForwardingInfo->ScriptIndex] == NULL || + // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "TIMES", 5) == 0 || // Only Check until script is finished + // _memicmp(Scripts[ForwardingInfo->ScriptIndex], "ELSE", 4) == 0) // Only Check until script is finished + + + ProcessBBSConnectScript(conn, DiscMsg, 15); + return 0; + } + } + + // if sysop was chatting to user clear link +#ifndef LINBPQ + if (conn->BBSFlags & SYSOPCHAT) + { + SendUnbuffered(-1, "User has disconnected\n", 23); + BBSConsole.Console->SysopChatStream = 0; + } +#endif + ClearQueue(conn); + + if (conn->PacLinkCalls) + free(conn->PacLinkCalls); + + if (conn->InputBuffer) + { + free(conn->InputBuffer); + conn->InputBuffer = NULL; + conn->InputBufferLen = 0; + } + + if (conn->InputMode == 'B') + { + // Save partly received message for a restart + + if (conn->BBSFlags & FBBB1Mode) + if (conn->Paclink == 0) // Paclink doesn't do restarts + if (strcmp(conn->Callsign, "RMS") != 0) // Neither does RMS Packet. + if (conn->DontSaveRestartData == FALSE) + SaveFBBBinary(conn); + } + + conn->Active = FALSE; + + if (conn->FwdMsg) + conn->FwdMsg->Locked = 0; // Unlock + + RefreshMainWindow(); + + RemoveTempBIDS(conn); + + len=sprintf_s(Msg, sizeof(Msg), "%s Disconnected", conn->Callsign); + WriteLogLine(conn, '|',Msg, len, LOG_BBS); + + if (conn->FBBHeaders) + { + struct FBBHeaderLine * FBBHeader; + int n; + + for (n = 0; n < 5; n++) + { + FBBHeader = &conn->FBBHeaders[n]; + + if (FBBHeader->FwdMsg) + FBBHeader->FwdMsg->Locked = 0; // Unlock + + } + + free(conn->FBBHeaders); + conn->FBBHeaders = NULL; + } + + if (conn->UserPointer) + { + struct BBSForwardingInfo * FWDInfo = conn->UserPointer->ForwardingInfo; + + if (FWDInfo) + { + FWDInfo->Forwarding = FALSE; + +// if (FWDInfo->UserCall[0]) // Will be set if RMS +// { +// FindNextRMSUser(FWDInfo); +// } +// else + FWDInfo->FwdTimer = 0; + } + } + + conn->BBSFlags = 0; // Clear ARQ Mode + + return 0; + } + } + return 0; +} + +int DoReceivedData(int Stream) +{ + int count, InputLen; + size_t MsgLen; + int n; + CIRCUIT * conn; + struct UserInfo * user; + char * ptr, * ptr2; + char * Buffer; + + for (n = 0; n < NumberofStreams; n++) + { + conn = &Connections[n]; + + if (Stream == conn->BPQStream) + { + conn->SIDResponseTimer = 0; // Got a message, so cancel timeout. + + do + { + // May have several messages per packet, or message split over packets + + OuterLoop: + if (conn->InputLen + 1000 > conn->InputBufferLen ) // Shouldnt have lines longer than this in text mode + { + conn->InputBufferLen += 1000; + conn->InputBuffer = realloc(conn->InputBuffer, conn->InputBufferLen); + } + + GetMsg(Stream, &conn->InputBuffer[conn->InputLen], &InputLen, &count); + + if (InputLen == 0 && conn->InputMode != 'Y') + return 0; + + conn->InputLen += InputLen; + + if (conn->InputLen == 0) return 0; + + conn->Watchdog = 900; // 15 Minutes + + if (conn->InputMode == 'Y') // YAPP + { + if (ProcessYAPPMessage(conn)) // Returns TRUE if there could be more to process + goto OuterLoop; + + return 0; + } + + if (conn->InputMode == 'B') + { + // if in OpenBCM mode, remove FF transparency + + if (conn->OpenBCM) // Telnet, so escape any 0xFF + { + unsigned char * ptr1 = conn->InputBuffer; + unsigned char * ptr2; + int Len; + unsigned char c; + + // We can come through here again for the + // same data as we wait for a full packet + // So only check last InputLen bytes + + ptr1 += (conn->InputLen - InputLen); + ptr2 = ptr1; + Len = InputLen; + + while (Len--) + { + c = *(ptr1++); + + if (conn->InTelnetExcape) // Last char was ff + { + conn->InTelnetExcape = FALSE; + continue; + } + + *(ptr2++) = c; + + if (c == 0xff) // + conn->InTelnetExcape = TRUE; + } + + conn->InputLen = (int)(ptr2 - conn->InputBuffer); + } + + UnpackFBBBinary(conn); + goto OuterLoop; + } + else + { + + loop: + + if (conn->InputLen == 1 && conn->InputBuffer[0] == 0) // Single Null + { + conn->InputLen = 0; + return 0; + } + + user = conn->UserPointer; + + if (conn->BBSFlags & (MCASTRX | SYNCMODE)) + { + // MCAST and SYNCMODE deliver full packets + + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); + else + ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); + + conn->InputLen=0; + continue; + } + + // This looks for CR, CRLF, LF or CR/Null and removes any LF or NULL, + // but this relies on both arriving in same packet. + // Need to check for LF and start of packet and ignore it + // But what if client is only using LF?? + // (WLE sends SID with CRLF, other packets with CR only) + + // We don't get here on the data part of a binary transfer, so + // don't need to worry about messing up binary data. + + ptr = memchr(conn->InputBuffer, '\r', conn->InputLen); + ptr2 = memchr(conn->InputBuffer, '\n', conn->InputLen); + + if (ptr) + conn->usingCR = 1; + + if ((ptr2 && ptr2 < ptr) || ptr == 0) // LF before CR, or no CR + ptr = ptr2; // Use LF + + if (ptr) // CR or LF in buffer + { + conn->lastLineEnd = *(ptr); + + *(ptr) = '\r'; // In case was LF + + ptr2 = &conn->InputBuffer[conn->InputLen]; + + if (++ptr == ptr2) + { + // Usual Case - single msg in buffer + + // if Length is 1 and Term is LF and normal line end is CR + // this is from a split CRLF - Ignore it + + if (conn->InputLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) + Debugprintf("BPQMail split Line End Detected"); + else + { + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, conn->InputBuffer, conn->InputLen); + else + ProcessLine(conn, user, conn->InputBuffer, conn->InputLen); + } + conn->InputLen=0; + } + else + { + // buffer contains more that 1 message + + MsgLen = conn->InputLen - (ptr2-ptr); + + Buffer = malloc(MsgLen + 100); + + memcpy(Buffer, conn->InputBuffer, MsgLen); + + // if Length is 1 and Term is LF and normal line end is CR + // this is from a split CRLF - Ignore it + + if (MsgLen == 1 && conn->lastLineEnd == 0x0a && conn->usingCR) + Debugprintf("BPQMail split Line End Detected"); + else + { + if (conn->BBSFlags & RunningConnectScript) + ProcessBBSConnectScript(conn, Buffer, (int)MsgLen); + else + ProcessLine(conn, user, Buffer, (int)MsgLen); + } + free(Buffer); + + if (*ptr == 0 || *ptr == '\n') + { + /// CR LF or CR Null + + ptr++; + conn->InputLen--; + } + + memmove(conn->InputBuffer, ptr, conn->InputLen-MsgLen); + + conn->InputLen -= (int)MsgLen; + + goto loop; + + } + } + else + { + // Could be a YAPP Header + + + if (conn->InputLen == 2 && conn->InputBuffer[0] == ENQ && conn->InputBuffer[1] == 1) // YAPP Send_Init + { + UCHAR YAPPRR[2]; + YAPPRR[0] = ACK; + YAPPRR[1] = 1; + + conn->InputMode = 'Y'; + QueueMsg(conn, YAPPRR, 2); + + conn->InputLen = 0; + return 0; + } + } + } + + } while (count > 0); + + return 0; + } + } + + // Socket not found + + return 0; + +} +int DoBBSMonitorData(int Stream) +{ +// UCHAR Buffer[1000]; + UCHAR buff[500]; + + int len = 0,count=0; + int stamp; + + do + { + stamp=GetRaw(Stream, buff,&len,&count); + + if (len == 0) return 0; + + SeeifBBSUIFrame((struct _MESSAGEX *)buff, len); + } + + while (count > 0); + + + return 0; + +} + +VOID ProcessFLARQLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int MsgLen) +{ + Buffer[MsgLen] = 0; + + if (MsgLen == 1 && Buffer[0] == 13) + return; + + if (strcmp(Buffer, "ARQ::ETX\r") == 0) + { + // Decode it. + + UCHAR * ptr1, * ptr2, * ptr3; + int len, linelen; + struct MsgInfo * Msg = conn->TempMsg; + time_t Date; + char FullTo[100]; + char FullFrom[100]; + char ** RecpTo = NULL; // May be several Recipients + char ** HddrTo = NULL; // May be several Recipients + char ** Via = NULL; // May be several Recipients + int LocalMsg[1000] ; // Set if Recipient is a local wl2k address + + int B2To; // Offset to To: fields in B2 header + int Recipients = 0; + int RMSMsgs = 0, BBSMsgs = 0; + +// Msg->B2Flags |= B2Msg; + + + ptr1 = conn->MailBuffer; + len = Msg->length; + ptr1[len] = 0; + + if (strstr(ptr1, "ARQ:ENCODING::")) + { + // a file, not a message. If is called "BBSPOLL" do a reverse forward else Ignore for now + + _strupr(conn->MailBuffer); + if (strstr(conn->MailBuffer, "BBSPOLL")) + { + SendARQMail(conn); + } + + free(conn->MailBuffer); + conn->MailBuffer = NULL; + conn->MailBufferSize = 0; + + return; + } + Loop: + ptr2 = strchr(ptr1, '\r'); + + linelen = (int)(ptr2 - ptr1); + + if (_memicmp(ptr1, "From:", 5) == 0 && linelen > 6) // Can have empty From: + { + char SaveFrom[100]; + char * FromHA; + + memcpy(FullFrom, ptr1, linelen); + FullFrom[linelen] = 0; + + // B2 From may now contain an @BBS + + strcpy(SaveFrom, FullFrom); + + FromHA = strlop(SaveFrom, '@'); + + if (strlen(SaveFrom) > 12) SaveFrom[12] = 0; + + strcpy(Msg->from, &SaveFrom[6]); + + if (FromHA) + { + if (strlen(FromHA) > 39) FromHA[39] = 0; + Msg->emailfrom[0] = '@'; + strcpy(&Msg->emailfrom[1], _strupr(FromHA)); + } + + // Remove any SSID + + ptr3 = strchr(Msg->from, '-'); + if (ptr3) *ptr3 = 0; + + } + else if (_memicmp(ptr1, "To:", 3) == 0 || _memicmp(ptr1, "cc:", 3) == 0) + { + HddrTo=realloc(HddrTo, (Recipients+1) * sizeof(void *)); + HddrTo[Recipients] = zalloc(100); + + memset(FullTo, 0, 99); + memcpy(FullTo, &ptr1[4], linelen-4); + memcpy(HddrTo[Recipients], ptr1, linelen+2); + LocalMsg[Recipients] = FALSE; + + _strupr(FullTo); + + B2To = (int)(ptr1 - conn->MailBuffer); + + if (_memicmp(FullTo, "RMS:", 4) == 0) + { + // remove RMS and add @winlink.org + + strcpy(FullTo, "RMS"); + strcpy(Msg->via, &FullTo[4]); + } + else + { + ptr3 = strchr(FullTo, '@'); + + if (ptr3) + { + *ptr3++ = 0; + strcpy(Msg->via, ptr3); + } + else + Msg->via[0] = 0; + } + + if (_memicmp(&ptr1[4], "SMTP:", 5) == 0) + { + // Airmail Sends MARS messages as SMTP + + if (CheckifPacket(Msg->via)) + { + // Packet Message + + memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); + _strupr(FullTo); + _strupr(Msg->via); + + // Update the saved to: line (remove the smtp:) + + strcpy(&HddrTo[Recipients][4], &HddrTo[Recipients][9]); + BBSMsgs++; + goto BBSMsg; + } + + // If a winlink.org address we need to convert to call + + if (_stricmp(Msg->via, "winlink.org") == 0) + { + memmove(FullTo, &FullTo[5], strlen(FullTo) - 4); + _strupr(FullTo); + LocalMsg[Recipients] = CheckifLocalRMSUser(FullTo); + } + else + { + memcpy(Msg->via, &ptr1[9], linelen); + Msg->via[linelen - 9] = 0; + strcpy(FullTo,"RMS"); + } +// FullTo[0] = 0; + + BBSMsg: + _strupr(FullTo); + _strupr(Msg->via); + } + + if (memcmp(FullTo, "RMS:", 4) == 0) + { + // remove RMS and add @winlink.org + + memmove(FullTo, &FullTo[4], strlen(FullTo) - 3); + strcpy(Msg->via, "winlink.org"); + sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); + } + + if (strcmp(Msg->via, "RMS") == 0) + { + // replace RMS with @winlink.org + + strcpy(Msg->via, "winlink.org"); + sprintf(HddrTo[Recipients], "To: %s@winlink.org\r\n", FullTo); + } + + if (strlen(FullTo) > 6) + FullTo[6] = 0; + + strlop(FullTo, '-'); + + strcpy(Msg->to, FullTo); + + if (SendBBStoSYSOPCall) + if (_stricmp(FullTo, BBSName) == 0) + strcpy(Msg->to, SYSOPCall); + + if ((Msg->via[0] == 0 || strcmp(Msg->via, "BPQ") == 0 || strcmp(Msg->via, "BBS") == 0)) + { + // No routing - check @BBS and WP + + struct UserInfo * ToUser = LookupCall(FullTo); + + Msg->via[0] = 0; // In case BPQ and not found + + if (ToUser) + { + // Local User. If Home BBS is specified, use it + + if (ToUser->HomeBBS[0]) + { + strcpy(Msg->via, ToUser->HomeBBS); + } + } + else + { + WPRecP WP = LookupWP(FullTo); + + if (WP) + { + strcpy(Msg->via, WP->first_homebbs); + + } + } + + // Fix To: address in B2 Header + + if (Msg->via[0]) + sprintf(HddrTo[Recipients], "To: %s@%s\r\n", FullTo, Msg->via); + else + sprintf(HddrTo[Recipients], "To: %s\r\n", FullTo); + + } + + RecpTo=realloc(RecpTo, (Recipients+1) * sizeof(void *)); + RecpTo[Recipients] = zalloc(10); + + Via=realloc(Via, (Recipients+1) * sizeof(void *)); + Via[Recipients] = zalloc(50); + + strcpy(Via[Recipients], Msg->via); + strcpy(RecpTo[Recipients++], FullTo); + + // Remove the To: Line from the buffer + + } + else if (_memicmp(ptr1, "Type:", 4) == 0) + { + if (ptr1[6] == 'N') + Msg->type = 'T'; // NTS + else + Msg->type = ptr1[6]; + } + else if (_memicmp(ptr1, "Subject:", 8) == 0) + { + size_t Subjlen = ptr2 - &ptr1[9]; + if (Subjlen > 60) Subjlen = 60; + memcpy(Msg->title, &ptr1[9], Subjlen); + + goto ProcessBody; + } +// else if (_memicmp(ptr1, "Body:", 4) == 0) +// { +// MsgLen = atoi(&ptr1[5]); +// StartofMsg = ptr1; +// } + else if (_memicmp(ptr1, "File:", 5) == 0) + { + Msg->B2Flags |= Attachments; + } + else if (_memicmp(ptr1, "Date:", 5) == 0) + { + struct tm rtime; + char seps[] = " ,\t\r"; + + memset(&rtime, 0, sizeof(struct tm)); + + // Date: 2009/07/25 10:08 + + sscanf(&ptr1[5], "%04d/%02d/%02d %02d:%02d:%02d", + &rtime.tm_year, &rtime.tm_mon, &rtime.tm_mday, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + sscanf(&ptr1[5], "%02d/%02d/%04d %02d:%02d:%02d", + &rtime.tm_mday, &rtime.tm_mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + rtime.tm_year -= 1900; + + Date = mktime(&rtime) - (time_t)_MYTIMEZONE; + + if (Date == (time_t)-1) + Date = time(NULL); + + Msg->datecreated = Date; + + } + + if (linelen) // Not Null line + { + ptr1 = ptr2 + 2; // Skip cr + goto Loop; + } + + + // Processed all headers +ProcessBody: + + ptr2 +=2; // skip crlf + + Msg->length = (int)(&conn->MailBuffer[Msg->length] - ptr2); + + memmove(conn->MailBuffer, ptr2, Msg->length); + + CreateMessageFromBuffer(conn); + + conn->BBSFlags = 0; // Clear ARQ Mode + return; + } + + // File away the data + + Buffer[MsgLen++] = 0x0a; // BBS Msgs stored with crlf + + if ((conn->TempMsg->length + MsgLen) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, MsgLen); + + conn->TempMsg->length += MsgLen; + + return; + + // Not sure what to do yet with files, but will process emails (using text style forwarding + +/* +ARQ:FILE::flarqmail-1.eml +ARQ:EMAIL:: +ARQ:SIZE::96 +ARQ::STX +//FLARQ COMPOSER +Date: 16/01/2014 22:26:06 +To: g8bpq +From: +Subject: test message + +Hello +Hello + +ARQ::ETX +*/ + + return; +} + +VOID ProcessTextFwdLine(ConnectionInfo * conn, struct UserInfo * user, char * Buffer, int len) +{ + Buffer[len] = 0; +// Debugprintf(Buffer); + + // With TNC2 body prompt is a single CR, so that shouldn't be ignored. + + // If thia causes problems with other TNC PMS implementations I'll have to revisit this + +// if (len == 1 && Buffer[0] == 13) +// return; + + if (conn->Flags & SENDTITLE) + { + // Waiting for Subject: prompt + + struct MsgInfo * Msg = conn->FwdMsg; + + nodeprintf(conn, "%s\r", Msg->title); + + conn->Flags &= ~SENDTITLE; + conn->Flags |= SENDBODY; + + // New Paccom PMS (V3.2) doesn't prompt for body so drop through and send it + if ((conn->BBSFlags & NEWPACCOM) == 0) + return; + + } + + if (conn->Flags & SENDBODY) + { + // Waiting for Enter Message Prompt + + struct tm * tm; + time_t temp; + + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + char * MsgPtr; + int MsgLen; + int Index = 0; + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + MsgPtr = MsgBytes; + MsgLen = conn->FwdMsg->length; + + // If a B2 Message, remove B2 Header + + if (conn->FwdMsg->B2Flags & B2Msg) + { + // Remove all B2 Headers, and all but the first part. + + MsgPtr = strstr(MsgBytes, "Body:"); + + if (MsgPtr) + { + MsgLen = atoi(&MsgPtr[5]); + MsgPtr= strstr(MsgBytes, "\r\n\r\n"); // Blank Line after headers + + if (MsgPtr) + MsgPtr +=4; + else + MsgPtr = MsgBytes; + + } + else + MsgPtr = MsgBytes; + } + + memcpy(&temp, &conn->FwdMsg->datereceived, sizeof(time_t)); + tm = gmtime(&temp); + + nodeprintf(conn, "R:%02d%02d%02d/%02d%02dZ %d@%s.%s %s\r", + tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, + conn->FwdMsg->number, BBSName, HRoute, RlineVer); + + if (memcmp(MsgPtr, "R:", 2) != 0) // No R line, so must be our message - put blank line after header + BBSputs(conn, "\r"); + + MsgLen = RemoveLF(MsgPtr, MsgLen); + + QueueMsg(conn, MsgPtr, MsgLen); + + if (user->ForwardingInfo->SendCTRLZ) + nodeprintf(conn, "\r\x1a"); + else + nodeprintf(conn, "\r/ex\r"); + + free(MsgBytes); + + conn->FBBMsgsSent = TRUE; + + + if (conn->FwdMsg->type == 'P') + Index = PMSG; + else if (conn->FwdMsg->type == 'B') + Index = BMSG; + else if (conn->FwdMsg->type == 'T') + Index = TMSG; + + user->Total.MsgsSent[Index]++; + user->Total.BytesForwardedOut[Index] += MsgLen; + + conn->Flags &= ~SENDBODY; + conn->Flags |= WAITPROMPT; + + return; + } + + if (conn->Flags & WAITPROMPT) + { + if (Buffer[len-2] != '>') + return; + + conn->Flags &= ~WAITPROMPT; + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + SaveMessageDatabase(); + + conn->UserPointer->ForwardingInfo->MsgCount--; + + // See if any more to forward + + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg; + + // If we are using SETCALLTOSENDER make sure this message is from the same sender + +#ifdef LINBPQ + BPQVECSTRUC * SESS = &BPQHOSTVECTOR[0]; +#else + BPQVECSTRUC * SESS = (BPQVECSTRUC *)BPQHOSTVECPTR; +#endif + unsigned char AXCall[7]; + + Msg = conn->FwdMsg; + ConvToAX25(Msg->from, AXCall); + if (memcmp(SESS[conn->BPQStream - 1].HOSTSESSION->L4USER, AXCall, 7) != 0) + { + Disconnect(conn->BPQStream); + return; + } + + // Send S line and wait for response - SB WANT @ USA < W8AAA $1029_N0XYZ + + conn->Flags |= SENDTITLE; + + + if ((conn->BBSFlags & SETCALLTOSENDER)) + nodeprintf(conn, "S%c %s @ %s \r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call); + else + nodeprintf(conn, "S%c %s @ %s < %s $%s\r", Msg->type, Msg->to, + (Msg->via[0]) ? Msg->via : conn->UserPointer->Call, + Msg->from, Msg->bid); + } + else + { + Disconnect(conn->BPQStream); + } + return; + } +} + + +#define N 2048 /* buffer size */ +#define F 60 /* lookahead buffer size */ +#define THRESHOLD 2 +#define NIL N /* leaf of tree */ + +extern UCHAR * infile; + +BOOL CheckforMIME(SocketConn * sockptr, char * Msg, char ** Body, int * MsgLen); + + +VOID ProcessLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + char * Cmd, * Arg1; + char * Context; + char seps[] = " \t\r"; + int CmdLen; + + if (_memicmp(Buffer, "POSYNCLOGON", 11) == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->BBSFlags |= SYNCMODE; + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + + Sleep(500); + + BBSputs(conn, "OK\r"); + Flush(conn); + return; + } + + if (_memicmp(Buffer, "POSYNCHELLO", 11) == 0) + { + // This is first message received after connecting to SYNC + // Save Callsign + + char Reply[32]; + conn->BBSFlags |= SYNCMODE; + conn->FBBHeaders = zalloc(5 * sizeof(struct FBBHeaderLine)); + + sprintf(Reply, "POSYNCLOGON %s\r", BBSName); + BBSputs(conn, Reply); + return; + } + + if (conn->BBSFlags & SYNCMODE) + { + ProcessSyncModeMessage(conn, user, Buffer, len); + return; + } + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // A few messages should be trapped here and result in an immediate disconnect, whatever mode I think the session is in (it could be wrong) + + // *** Protocol Error + // Already Connected + // Invalid Command + + if (_memicmp(Buffer, "Already Connected", 17) == 0 || + _memicmp(Buffer, "Invalid Command", 15) == 0 || + _memicmp(Buffer, "*** Protocol Error", 18) == 0) + { + conn->BBSFlags |= DISCONNECTING; + Disconnect(conn->BPQStream); + return; + } + + if (conn->BBSFlags & FBBForwarding) + { + ProcessFBBLine(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & FLARQMODE) + { + ProcessFLARQLine(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & MCASTRX) + { + ProcessMCASTLine(conn, user, Buffer, len); + return; + } + + + if (conn->BBSFlags & TEXTFORWARDING) + { + ProcessTextFwdLine(conn, user, Buffer, len); + return; + } + + // if chatting to sysop pass message to BBS console + + if (conn->BBSFlags & SYSOPCHAT) + { + SendUnbuffered(-1, Buffer,len); + return; + } + + if (conn->Flags & GETTINGMESSAGE) + { + ProcessMsgLine(conn, user, Buffer, len); + return; + } + if (conn->Flags & GETTINGTITLE) + { + ProcessMsgTitle(conn, user, Buffer, len); + return; + } + + if (conn->BBSFlags & MBLFORWARDING) + { + ProcessMBLLine(conn, user, Buffer, len); + return; + } + + if (conn->Flags & GETTINGUSER || conn->NewUser) // Could be new user but dont need name + { + if (memcmp(Buffer, ";FW:", 4) == 0 || Buffer[0] == '[') + { + struct BBSForwardingInfo * ForwardingInfo; + + conn->Flags &= ~GETTINGUSER; + + // New User is a BBS - create a temp struct for it + + if ((user->flags & (F_BBS | F_Temp_B2_BBS)) == 0) // It could already be a BBS without a user name + { + // Not defined as BBS - allocate and initialise forwarding structure + + user->flags |= F_Temp_B2_BBS; + + // An RMS Express user that needs a temporary BBS struct + + ForwardingInfo = user->ForwardingInfo = zalloc(sizeof(struct BBSForwardingInfo)); + + ForwardingInfo->AllowCompressed = TRUE; + ForwardingInfo->AllowBlocked = TRUE; + conn->UserPointer->ForwardingInfo->AllowB2 = TRUE; + } + SaveUserDatabase(); + } + else + { + if (conn->Flags & GETTINGUSER) + { + conn->Flags &= ~GETTINGUSER; + if (len > 18) + len = 18; + + memcpy(user->Name, Buffer, len-1); + SendWelcomeMsg(conn->BPQStream, conn, user); + SaveUserDatabase(); + UpdateWPWithUserInfo(user); + return; + } + } + } + + // Process Command + + if (conn->Paging && (conn->LinesSent >= conn->PageLen)) + { + // Waiting for paging prompt + + if (len > 1) + { + if (_memicmp(Buffer, "Abort", 1) == 0) + { + ClearQueue(conn); + conn->LinesSent = 0; + + nodeprintf(conn, AbortedMsg); + + if (conn->UserPointer->Temp->ListSuspended) + nodeprintf(conn, "bort, , = Continue..>"); + + SendPrompt(conn, user); + return; + } + } + + conn->LinesSent = 0; + return; + } + + if (user->Temp->ListSuspended) + { + // Paging limit hit when listing. User may abort, continue, or read one or more messages + + ProcessSuspendedListCommand(conn, user, Buffer, len); + return; + } + if (len == 1) + { + SendPrompt(conn, user); + return; + } + + Buffer[len] = 0; + + if (strstr(Buffer, "ARQ:FILE:")) + { + // Message from FLARQ + + conn->BBSFlags |= FLARQMODE; + strcpy(conn->ARQFilename, &Buffer[10]); // Will need name when we decide what to do with files + + // Create a Temp Messge Stucture + + CreateMessage(conn, conn->Callsign, "", "", 'P', NULL, NULL); + + Buffer[len++] = 0x0a; // BBS Msgs stored with crlf + + if ((conn->TempMsg->length + len) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); + + conn->TempMsg->length += len; + + return; + } + if (Buffer[0] == ';') // WL2K Comment + { + if (memcmp(Buffer, ";FW:", 4) == 0) + { + // Paclink User Select (poll for list) + + char * ptr1,* ptr2, * ptr3; + int index=0; + + // Convert string to Multistring + + Buffer[len-1] = 0; + + conn->PacLinkCalls = zalloc(len*3); + + ptr1 = &Buffer[5]; + ptr2 = (char *)conn->PacLinkCalls; + ptr2 += (len * 2); + strcpy(ptr2, ptr1); + + while (ptr2) + { + ptr3 = strlop(ptr2, ' '); + + if (strlen(ptr2)) + conn->PacLinkCalls[index++] = ptr2; + + ptr2 = ptr3; + } + + return; + } + + if (memcmp(Buffer, ";FR:", 4) == 0) + { + // New Message from TriMode - Just igonre till I know what to do with it + + return; + } + + // Ignore other ';' message + + return; + } + + + + if (Buffer[0] == '[' && Buffer[len-2] == ']') // SID + { + // If a BBS, set BBS Flag + + if (user->flags & ( F_BBS | F_Temp_B2_BBS)) + { + if (user->ForwardingInfo) + { + if (user->ForwardingInfo->Forwarding && ((conn->BBSFlags & OUTWARDCONNECT) == 0)) + { + BBSputs(conn, "Already Connected\r"); + Flush(conn); + Sleep(500); + Disconnect(conn->BPQStream); + return; + } + } + + if (user->ForwardingInfo) + { + user->ForwardingInfo->Forwarding = TRUE; + user->ForwardingInfo->FwdTimer = 0; // So we dont send to immediately + } + } + + if (user->flags & ( F_BBS | F_PMS | F_Temp_B2_BBS)) + { + Parse_SID(conn, &Buffer[1], len-4); + + if (conn->BBSFlags & FBBForwarding) + { + conn->FBBIndex = 0; // ready for first block; + conn->FBBChecksum = 0; + memset(&conn->FBBHeaders[0], 0, 5 * sizeof(struct FBBHeaderLine)); + } + else + FBBputs(conn, ">\r"); + + } + + return; + } + + Cmd = strtok_s(Buffer, seps, &Context); + + if (Cmd == NULL) + { + if (!CheckForTooManyErrors(conn)) + BBSputs(conn, "Invalid Command\r"); + + SendPrompt(conn, user); + return; + } + + Arg1 = strtok_s(NULL, seps, &Context); + CmdLen = (int)strlen(Cmd); + + // Check List first. If any other, save last listed to user record. + + if (_memicmp(Cmd, "L", 1) == 0 && _memicmp(Cmd, "LISTFILES", 3) != 0) + { + DoListCommand(conn, user, Cmd, Arg1, FALSE, Context); + SendPrompt(conn, user); + return; + } + + if (conn->lastmsg > user->lastmsg) + { + user->lastmsg = conn->lastmsg; + SaveUserDatabase(); + } + + if (_stricmp(Cmd, "SHOWRMSPOLL") == 0) + { + DoShowRMSCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "AUTH") == 0) + { + DoAuthCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "Abort", 1) == 0) + { + ClearQueue(conn); + conn->LinesSent = 0; + + nodeprintf(conn, AbortedMsg); + + if (conn->UserPointer->Temp->ListSuspended) + nodeprintf(conn, "bort, , = Continue..>"); + + SendPrompt(conn, user); + return; + } + if (_memicmp(Cmd, "Bye", CmdLen) == 0 || _stricmp(Cmd, "ELSE") == 0) + { + ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); + Flush(conn); + Sleep(1000); + + if (conn->BPQStream > 0) + Disconnect(conn->BPQStream); +#ifndef LINBPQ + else + CloseConsole(conn->BPQStream); +#endif + return; + } + + if (_memicmp(Cmd, "Node", 4) == 0) + { + ExpandAndSendMessage(conn, SignoffMsg, LOG_BBS); + Flush(conn); + Sleep(1000); + + if (conn->BPQStream > 0) + ReturntoNode(conn->BPQStream); +#ifndef LINBPQ + else + CloseConsole(conn->BPQStream); +#endif + return; + } + + if (_memicmp(Cmd, "IDLETIME", 4) == 0) + { + DoSetIdleTime(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "SETNEXTMESSAGENUMBER") == 0) + { + DoSetMsgNo(conn, user, Arg1, Context); + return; + } + + if (strlen(Cmd) < 12 && _memicmp(Cmd, "D", 1) == 0) + { + DoDeliveredCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "K", 1) == 0) + { + DoKillCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + + if (_memicmp(Cmd, "LISTFILES", 3) == 0 || _memicmp(Cmd, "FILES", 5) == 0) + { + ListFiles(conn, user, Arg1); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "READFILE", 4) == 0) + { + ReadBBSFile(conn, user, Arg1); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "REROUTEMSGS", 7) == 0) + { + if (conn->sysop == 0) + nodeprintf(conn, "Reroute Messages needs SYSOP status\r"); + else + { + ReRouteMessages(); + nodeprintf(conn, "Ok\r"); + } + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "YAPP", 4) == 0) + { + YAPPSendFile(conn, user, Arg1); + return; + } + + if (_memicmp(Cmd, "UH", 2) == 0 && conn->sysop) + { + DoUnholdCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_stricmp(Cmd, "IMPORT") == 0) + { + DoImportCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "EXPORT") == 0) + { + DoExportCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "I", 1) == 0) + { + char * Save; + char * MsgBytes; + + if (Arg1) + { + // User WP lookup + + DoWPLookup(conn, user, Cmd[1], Arg1); + SendPrompt(conn, user); + return; + } + + + MsgBytes = Save = ReadInfoFile("info.txt"); + if (MsgBytes) + { + int Length; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(Save); + } + else + BBSputs(conn, "SYSOP has not created an INFO file\r"); + + + SendPrompt(conn, user); + return; + } + + + if (_memicmp(Cmd, "Name", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 17) + Arg1[17] = 0; + + strcpy(user->Name, Arg1); + UpdateWPWithUserInfo(user); + + } + + SendWelcomeMsg(conn->BPQStream, conn, user); + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "OP", 2) == 0) + { + int Lines; + + // Paging Control. Param is number of lines per page + + if (Arg1) + { + Lines = atoi(Arg1); + + if (Lines) // Sanity Check + { + if (Lines < 10) + { + nodeprintf(conn,"Page Length %d is too short\r", Lines); + SendPrompt(conn, user); + return; + } + } + + user->PageLen = Lines; + conn->PageLen = Lines; + conn->Paging = (Lines > 0); + SaveUserDatabase(); + } + + nodeprintf(conn,"Page Length is %d\r", user->PageLen); + SendPrompt(conn, user); + + return; + } + + if (_memicmp(Cmd, "QTH", CmdLen) == 0) + { + if (Arg1) + { + // QTH may contain spaces, so put back together, and just split at cr + + Arg1[strlen(Arg1)] = ' '; + strtok_s(Arg1, "\r", &Context); + + if (strlen(Arg1) > 60) + Arg1[60] = 0; + + strcpy(user->Address, Arg1); + UpdateWPWithUserInfo(user); + + } + + nodeprintf(conn,"QTH is %s\r", user->Address); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "ZIP", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 8) + Arg1[8] = 0; + + strcpy(user->ZIP, _strupr(Arg1)); + UpdateWPWithUserInfo(user); + } + + nodeprintf(conn,"ZIP is %s\r", user->ZIP); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if (_memicmp(Cmd, "CMSPASS", 7) == 0) + { + if (Arg1 == 0) + { + nodeprintf(conn,"Must specify a password\r"); + } + else + { + if (strlen(Arg1) > 15) + Arg1[15] = 0; + + strcpy(user->CMSPass, Arg1); + nodeprintf(conn,"CMS Password Set\r"); + SaveUserDatabase(); + } + + SendPrompt(conn, user); + + return; + } + + if (_memicmp(Cmd, "PASS", CmdLen) == 0) + { + if (Arg1 == 0) + { + nodeprintf(conn,"Must specify a password\r"); + } + else + { + if (strlen(Arg1) > 12) + Arg1[12] = 0; + + strcpy(user->pass, Arg1); + nodeprintf(conn,"BBS Password Set\r"); + SaveUserDatabase(); + } + + SendPrompt(conn, user); + + return; + } + + + if (_memicmp(Cmd, "R", 1) == 0) + { + DoReadCommand(conn, user, Cmd, Arg1, Context); + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "S", 1) == 0) + { + if (!DoSendCommand(conn, user, Cmd, Arg1, Context)) + SendPrompt(conn, user); + return; + } + + if ((_memicmp(Cmd, "Help", CmdLen) == 0) || (_memicmp(Cmd, "?", 1) == 0)) + { + char * Save; + char * MsgBytes = Save = ReadInfoFile("help.txt"); + + if (MsgBytes) + { + int Length; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(Save); + } + else + { + BBSputs(conn, "A - Abort Output\r"); + BBSputs(conn, "B - Logoff\r"); + BBSputs(conn, "CMSPASS Password - Set CMS Password\r"); + BBSputs(conn, "D - Flag NTS Message(s) as Delivered - D num\r"); + BBSputs(conn, "HOMEBBS - Display or get HomeBBS\r"); + BBSputs(conn, "INFO - Display information about this BBS\r"); + BBSputs(conn, "I CALL - Lookup CALL in WP Allows *CALL CALL* *CALL* wildcards\r"); + BBSputs(conn, "I@ PARAM - Lookup @BBS in WP\r"); + BBSputs(conn, "IZ PARAM - Lookup Zip Codes in WP\r"); + BBSputs(conn, "IH PARAM - Lookup HA elements in WP - eg USA EU etc\r"); + + BBSputs(conn, "K - Kill Message(s) - K num, KM (Kill my read messages)\r"); + BBSputs(conn, "L - List Message(s) - \r"); + BBSputs(conn, " L = List New, LR = List New (Oldest first)\r"); + BBSputs(conn, " LM = List Mine, L> Call, L< Call, L@ = List to, from or at\r"); + BBSputs(conn, " LL num = List msg num, L num-num = List Range\r"); + BBSputs(conn, " LN LY LH LK LF L$ LD = List Message with corresponding Status\r"); + BBSputs(conn, " LB LP LT = List Mesaage with corresponding Type\r"); + BBSputs(conn, " LC = List TO fields of all active bulletins\r"); + BBSputs(conn, " You can combine most selections eg LMP, LMN LB< G8BPQ\r"); + BBSputs(conn, "LISTFILES or FILES - List files available for download\r"); + + BBSputs(conn, "N Name - Set Name\r"); + BBSputs(conn, "NODE - Return to Node\r"); + BBSputs(conn, "OP n - Set Page Length (Output will pause every n lines)\r"); + BBSputs(conn, "PASS Password - Set BBS Password\r"); + BBSputs(conn, "POLLRMS - Manage Polling for messages from RMS \r"); + BBSputs(conn, "Q QTH - Set QTH\r"); + BBSputs(conn, "R - Read Message(s) - R num \r"); + BBSputs(conn, " RM (Read new messages to me), RMR (RM oldest first)\r"); + BBSputs(conn, "READ Name - Read File\r"); + + BBSputs(conn, "S - Send Message - S or SP Send Personal, SB Send Bull, ST Send NTS,\r"); + BBSputs(conn, " SR Num - Send Reply, SC Num - Send Copy\r"); + BBSputs(conn, "X - Toggle Expert Mode\r"); + BBSputs(conn, "YAPP - Download file from BBS using YAPP protocol\r"); + if (conn->sysop) + { + BBSputs(conn, "DOHOUSEKEEPING - Run Housekeeping process\r"); + BBSputs(conn, "EU - Edit User Flags - Type EU for Help\r"); + BBSputs(conn, "EXPORT - Export messages to file - Type EXPORT for Help\r"); + BBSputs(conn, "FWD - Control Forwarding - Type FWD for Help\r"); + BBSputs(conn, "IMPORT - Import messages from file - Type IMPORT for Help\r"); + BBSputs(conn, "REROUTEMSGS - Rerun message routing process\r"); + BBSputs(conn, "SETNEXTMESSAGENUMBER - Sets next message number\r"); + BBSputs(conn, "SHOWRMSPOLL - Displays your RMS polling list\r"); + BBSputs(conn, "UH - Unhold Message(s) - UH ALL or UH num num num...\r"); + } + } + + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "Ver", CmdLen) == 0) + { + nodeprintf(conn, "BBS Version %s\rNode Version %s\r", VersionStringWithBuild, GetVersionString()); + + SendPrompt(conn, user); + return; + } + + if (_memicmp(Cmd, "HOMEBBS", CmdLen) == 0) + { + if (Arg1) + { + if (strlen(Arg1) > 40) Arg1[40] = 0; + + strcpy(user->HomeBBS, _strupr(Arg1)); + UpdateWPWithUserInfo(user); + + if (!strchr(Arg1, '.')) + BBSputs(conn, "Please enter HA with HomeBBS eg g8bpq.gbr.eu - this will help message routing\r"); + } + + nodeprintf(conn,"HomeBBS is %s\r", user->HomeBBS); + SendPrompt(conn, user); + + SaveUserDatabase(); + + return; + } + + if ((_memicmp(Cmd, "EDITUSER", 5) == 0) || (_memicmp(Cmd, "EU", 2) == 0)) + { + DoEditUserCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "POLLRMS") == 0) + { + DoPollRMSCmd(conn, user, Arg1, Context); + return; + } + + if (_stricmp(Cmd, "DOHOUSEKEEPING") == 0) + { + DoHousekeepingCmd(conn, user, Arg1, Context); + return; + } + + + if (_stricmp(Cmd, "FWD") == 0) + { + DoFwdCmd(conn, user, Arg1, Context); + return; + } + + if (_memicmp(Cmd, "X", 1) == 0) + { + user->flags ^= F_Expert; + + if (user->flags & F_Expert) + BBSputs(conn, "Expert Mode\r"); + else + BBSputs(conn, "Expert Mode off\r"); + + SaveUserDatabase(); + SendPrompt(conn, user); + return; + } + + if (conn->Flags == 0) + { + if (!CheckForTooManyErrors(conn)) + BBSputs(conn, "Invalid Command\r"); + + SendPrompt(conn, user); + } + + // Send if possible + + Flush(conn); +} + +VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...) +{ + // seems to be printf to a socket + + char buff[600]; + va_list(arglist); + + va_start(arglist, format); + vsprintf(buff, format, arglist); + + BBSputs(conn, buff); +} + +// Code to delete obsolete files from Mail folder + +#ifdef WIN32 + +int DeleteRedundantMessages() +{ + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + char File[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + int Msgno; + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, MailDir); + strcat(szDir, "\\*.mes"); + + + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + return 0; + } + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + OutputDebugString(ffd.cFileName); + } + else + { + Msgno = atoi(&ffd.cFileName[2]); + + if (MsgnotoMsg[Msgno] == 0) + { + sprintf(File, "%s/%s%c", MailDir, ffd.cFileName, 0); + Debugprintf("Tidy Mail - Delete %s\n", File); + +// if (DeletetoRecycleBin) + DeletetoRecycle(File); +// else +// DeleteFile(File); + } + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + return 0; +} + +#else + +#include + +int MsgFilter(const struct dirent * dir) +{ + return (strstr(dir->d_name, ".mes") != 0); +} + +int DeleteRedundantMessages() +{ + struct dirent **namelist; + int n; + struct stat STAT; + int Msgno = 0, res; + char File[100]; + + n = scandir("Mail", &namelist, MsgFilter, alphasort); + + if (n < 0) + perror("scandir"); + else + { + while(n--) + { + if (stat(namelist[n]->d_name, &STAT) == 0); + { + Msgno = atoi(&namelist[n]->d_name[2]); + + if (MsgnotoMsg[Msgno] == 0) + { + sprintf(File, "Mail/%s", namelist[n]->d_name); + printf("Deleting %s\n", File); + unlink(File); + } + } + free(namelist[n]); + } + free(namelist); + } + return 0; +} +#endif + +VOID TidyWelcomeMsg(char ** pPrompt) +{ + // Make sure Welcome Message doesn't ends with > + + char * Prompt = *pPrompt; + + int i = (int)strlen(Prompt) - 1; + + *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it + + Prompt = *pPrompt; + + while (Prompt[i] == 10 || Prompt[i] == 13) + { + Prompt[i--] = 0; + } + + while (i >= 0 && Prompt[i] == '>') + Prompt[i--] = 0; + + strcat(Prompt, "\r\n"); +} + +VOID TidyPrompt(char ** pPrompt) +{ + // Make sure prompt ends > CR LF + + char * Prompt = *pPrompt; + + int i = (int)strlen(Prompt) - 1; + + *pPrompt = realloc(Prompt, i + 5); // In case we need to expand it + + Prompt = *pPrompt; + + while (Prompt[i] == 10 || Prompt[i] == 13) + { + Prompt[i--] = 0; + } + + if (Prompt[i] != '>') + strcat(Prompt, ">"); + + strcat(Prompt, "\r\n"); +} + +VOID TidyPrompts() +{ + TidyPrompt(&Prompt); + TidyPrompt(&NewPrompt); + TidyPrompt(&ExpertPrompt); +} + +BOOL SendARQMail(CIRCUIT * conn) +{ + conn->NextMessagetoForward = FirstMessageIndextoForward; + + // Send Message. There is no mechanism for reverse forwarding + + if (FindMessagestoForward(conn)) + { + struct MsgInfo * Msg; + char MsgHddr[512]; + int HddrLen; + char TimeString[64]; + char * WholeMessage; + + char * MsgBytes = ReadMessageFile(conn->FwdMsg->number); + int MsgLen; + + if (MsgBytes == 0) + { + MsgBytes = _strdup("Message file not found\r"); + conn->FwdMsg->length = (int)strlen(MsgBytes); + } + + Msg = conn->FwdMsg; + WholeMessage = malloc(Msg->length + 512); + + FormatTime(TimeString, (time_t)Msg->datecreated); + +/* +ARQ:FILE::flarqmail-1.eml +ARQ:EMAIL:: +ARQ:SIZE::96 +ARQ::STX +//FLARQ COMPOSER +Date: 16/01/2014 22:26:06 +To: g8bpq +From: +Subject: test message + +Hello +Hello + +ARQ::ETX +*/ + Logprintf(LOG_BBS, conn, '>', "ARQ Send Msg %d From %s To %s", Msg->number, Msg->from, Msg->to); + + HddrLen = sprintf(MsgHddr, "Date: %s\nTo: %s\nFrom: %s\nSubject %s\n\n", + TimeString, Msg->to, Msg->from, Msg->title); + + MsgLen = sprintf(WholeMessage, "ARQ:FILE::Msg%s_%d\nARQ:EMAIL::\nARQ:SIZE::%d\nARQ::STX\n%s%s\nARQ::ETX\n", + BBSName, Msg->number, (int)(HddrLen + strlen(MsgBytes)), MsgHddr, MsgBytes); + + WholeMessage[MsgLen] = 0; + QueueMsg(conn,WholeMessage, MsgLen); + + free(WholeMessage); + free(MsgBytes); + + // FLARQ doesn't ACK the message, so set flag to look for all acked + + conn->BBSFlags |= ARQMAILACK; + conn->ARQClearCount = 10; // To make sure clear isn't reported too soon + + return TRUE; + } + + // Nothing to send - close + + Logprintf(LOG_BBS, conn, '>', "ARQ Send - Nothing to Send - Closing"); + + conn->CloseAfterFlush = 20; + return FALSE; +} + +char *stristr (char *ch1, char *ch2) +{ + char *chN1, *chN2; + char *chNdx; + char *chRet = NULL; + + chN1 = _strdup (ch1); + chN2 = _strdup (ch2); + if (chN1 && chN2) + { + chNdx = chN1; + while (*chNdx) + { + *chNdx = (char) tolower (*chNdx); + chNdx ++; + } + chNdx = chN2; + while (*chNdx) + { + *chNdx = (char) tolower (*chNdx); + chNdx ++; + } + + chNdx = strstr (chN1, chN2); + if (chNdx) + chRet = ch1 + (chNdx - chN1); + } + free (chN1); + free (chN2); + return chRet; +} + +#ifdef WIN32 + +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, GetBPQDirectory()); + strcat(szDir, "\\BPQMailChat\\Files\\*.*"); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + nodeprintf(conn, "No Files\r"); + return; + } + + // List all the files in the directory with some info about them. + + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + {} + else + { + if (filename == NULL || stristr(ffd.cFileName, filename)) + nodeprintf(conn, "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); +} + +#else + +#include + +void ListFiles(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + struct dirent **namelist; + int n, i; + struct stat STAT; + time_t now = time(NULL); + int Age = 0, res; + char FN[256]; + + n = scandir("Files", &namelist, NULL, alphasort); + + if (n < 0) + perror("scandir"); + else + { + for (i = 0; i < n; i++) + { + sprintf(FN, "Files/%s", namelist[i]->d_name); + + if (filename == NULL || stristr(namelist[i]->d_name, filename)) + if (FN[6] != '.' && stat(FN, &STAT) == 0) + nodeprintf(conn, "%s %d\r", namelist[i]->d_name, STAT.st_size); + + free(namelist[i]); + } + free(namelist); + } + return; +} +#endif + +void ReadBBSFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + char * MsgBytes; + + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + struct stat STAT; + + if (filename == NULL) + { + nodeprintf(conn, "Missing Filename\r"); + return; + } + + if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) + { + nodeprintf(conn, "Invalid filename\r"); + return; + } + + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + int Length; + + MsgBytes=malloc(FileSize+1); + fread(MsgBytes, 1, FileSize, hFile); + fclose(hFile); + + MsgBytes[FileSize]=0; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + QueueMsg(conn, MsgBytes, Length); + free(MsgBytes); + + nodeprintf(conn, "\r\r[End of File %s]\r", filename); + return; + } + } + + nodeprintf(conn, "File %s not found\r", filename); +} + +VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + struct TempUserInfo * Temp = user->Temp; + + Buffer[len] = 0; + + // Command entered during listing pause. May be A R or C (or ) + + if (Buffer[0] == 'A' || Buffer[0] == 'a') + { + // Abort + + Temp->ListActive = Temp->ListSuspended = FALSE; + SendPrompt(conn, user); + return; + } + + if (_memicmp(Buffer, "R ", 2) == 0) + { + // Read Message(es) + + int msgno; + char * ptr; + char * Context; + + ptr = strtok_s(&Buffer[2], " ", &Context); + + while (ptr) + { + msgno = atoi(ptr); + ReadMessage(conn, user, msgno); + + ptr = strtok_s(NULL, " ", &Context); + } + + nodeprintf(conn, "bort, , = Continue..>"); + return; + } + + if (Buffer[0] == 'C' || Buffer[0] == 'c' || Buffer[0] == '\r' ) + { + // Resume Listing from where we left off + + DoListCommand(conn, user, Temp->LastListCommand, Temp->LastListParams, TRUE, ""); + SendPrompt(conn, user); + return; + } + + nodeprintf(conn, "bort, , = Continue..>"); + +} +/* +CreateMessageWithAttachments() +{ + int i; + char * ptr, * ptr2, * ptr3, * ptr4; + char Boundary[1000]; + BOOL Multipart = FALSE; + BOOL ALT = FALSE; + int Partlen; + char * Save; + BOOL Base64 = FALSE; + BOOL QuotedP = FALSE; + + char FileName[100][250] = {""}; + int FileLen[100]; + char * FileBody[100]; + char * MallocSave[100]; + UCHAR * NewMsg; + + int Files = 0; + + ptr = Msg; + + if ((sockptr->MailSize + 2000) > sockptr->MailBufferSize) + { + sockptr->MailBufferSize += 2000; + sockptr->MailBuffer = realloc(sockptr->MailBuffer, sockptr->MailBufferSize); + + if (sockptr->MailBuffer == NULL) + { + CriticalErrorHandler("Failed to extend Message Buffer"); + shutdown(sockptr->socket, 0); + return FALSE; + } + } + + + NewMsg = sockptr->MailBuffer + 1000; + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", FileLen[0]); + + for (i = 1; i < Files; i++) + { + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[i], FileName[i]); + } + + NewMsg += sprintf(NewMsg, "\r\n"); + + for (i = 0; i < Files; i++) + { + memcpy(NewMsg, FileBody[i], FileLen[i]); + NewMsg += FileLen[i]; + free(MallocSave[i]); + NewMsg += sprintf(NewMsg, "\r\n"); + } + + *MsgLen = NewMsg - (sockptr->MailBuffer + 1000); + *Body = sockptr->MailBuffer + 1000; + + return TRUE; // B2 Message +} + +*/ +VOID CreateUserReport() +{ + struct UserInfo * User; + int i; + char Line[200]; + int len; + char File[MAX_PATH]; + FILE * hFile; + + sprintf(File, "%s/UserList.csv", BaseDir); + + hFile = fopen(File, "wb"); + + if (hFile == NULL) + { + Debugprintf("Failed to create UserList.csv"); + return; + } + + for (i=1; i <= NumberofUsers; i++) + { + User = UserRecPtr[i]; + + len = sprintf(Line, "%s,%d,%s,%x,%s,\"%s\",%x,%s,%s,%s\r\n", + User->Call, + User->lastmsg, + FormatDateAndTime((time_t)User->TimeLastConnected, FALSE), + User->flags, + User->Name, + User->Address, + User->RMSSSIDBits, + User->HomeBBS, + User->QRA, + User->ZIP +// struct MsgStats Total; +// struct MsgStats Last; + ); + fwrite(Line, 1, len, hFile); + } + + fclose(hFile); +} + +BOOL ProcessYAPPMessage(CIRCUIT * conn) +{ + int Len = conn->InputLen; + UCHAR * Msg = conn->InputBuffer; + int pktLen = Msg[1]; + char Reply[2] = {ACK}; + int NameLen, SizeLen, OptLen; + char * ptr; + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char Mess[255]; + int len; + char * FN = &Msg[2]; + + switch (Msg[0]) + { + case ENQ: // YAPP Send_Init + + // Shouldn't occur in session. Reset state + + Mess[0] = ACK; + Mess[1] = 1; + QueueMsg(conn, Mess, 2); + Flush(conn); + conn->InputLen = 0; + if (conn->MailBuffer) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + } + return TRUE; + + case SOH: + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + // YAPPC has date/time in dos format + + if (Len < Msg[1] + 1) + return 0; + + NameLen = (int)strlen(FN); + strcpy(conn->ARQFilename, FN); + ptr = &Msg[3 + NameLen]; + SizeLen = (int)strlen(ptr); + FileSize = atoi(ptr); + + // Check file name for unsafe characters (.. / \) + + if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) + { + Mess[0] = NAK; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPP File Name %s invalid\r", FN); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + OptLen = pktLen - (NameLen + SizeLen + 2); + + conn->YAPPDate = 0; + + if (OptLen >= 8) // We have a Date/Time for YAPPC + { + ptr = ptr + SizeLen + 1; + conn->YAPPDate = strtol(ptr, NULL, 16); + } + + // Check Size + + if (FileSize > MaxRXSize) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); + QueueMsg(conn, Mess, Mess[1] + 2); + + Flush(conn); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s size %d larger than limit %d\r", conn->ARQFilename, FileSize, MaxRXSize); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + // Make sure file does not exist + + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "YAPP File %s already exists\r", conn->ARQFilename);; + QueueMsg(conn, Mess, Mess[1] + 2); + + Flush(conn); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP File %s already exists\r", conn->ARQFilename); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + fclose(hFile); + + conn->InputLen = 0; + conn->InputMode = 0; + + return FALSE; + } + + + conn->MailBufferSize = FileSize; + conn->MailBuffer=malloc(FileSize); + conn->YAPPLen = 0; + + if (conn->YAPPDate) // If present use YAPPC + Reply[1] = ACK; //Receive_TPK + else + Reply[1] = 2; //Rcv_File + + QueueMsg(conn, Reply, 2); + + len = sprintf_s(Mess, sizeof(Mess), "YAPP upload to %s started", conn->ARQFilename); + WriteLogLine(conn, '!', Mess, len, LOG_BBS); + + conn->InputLen = 0; + return FALSE; + + case STX: + + // Data Packet + + // Check we have it all + + if (conn->YAPPDate) // If present use YAPPC so have checksum + { + if (pktLen > (Len - 3)) // -3 for header and checksum + return 0; // Wait for rest + } + else + { + if (pktLen > (Len - 2)) // -2 for header + return 0; // Wait for rest + } + + // Save data and remove from buffer + + // if YAPPC check checksum + + if (conn->YAPPDate) + { + UCHAR Sum = 0; + int i; + UCHAR * uptr = &Msg[2]; + + i = pktLen; + + while(i--) + Sum += *(uptr++); + + if (Sum != *uptr) + { + // Checksum Error + + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPPC Checksum Error\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + return TRUE; + } + } + + if ((conn->YAPPLen) + pktLen > conn->MailBufferSize) + { + // Too Big ?? + + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "YAPP Too much data received\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + return TRUE; + } + + + memcpy(&conn->MailBuffer[conn->YAPPLen], &Msg[2], pktLen); + conn->YAPPLen += pktLen; + + if (conn->YAPPDate) + ++pktLen; // Add Checksum + + conn->InputLen -= (pktLen + 2); + memmove(conn->InputBuffer, &conn->InputBuffer[pktLen + 2], conn->InputLen); + + return TRUE; + + case ETX: + + // End Data + + + + if (conn->YAPPLen == conn->MailBufferSize) + { + // All received + + int ret; + DWORD Written = 0; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, conn->ARQFilename); + +#ifdef WIN32 + hFile = CreateFile(MsgFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + ret = WriteFile(hFile, conn->MailBuffer, conn->YAPPLen, &Written, NULL); + + if (conn->YAPPDate) + { + FILETIME FileTime; + struct tm TM; + struct timeval times[2]; + time_t TT; +/* + The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) +*/ + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; + TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; + + Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); + + TT = mktime(&TM); + times[0].tv_sec = times[1].tv_sec = + times[0].tv_usec = times[1].tv_usec = 0; + + DosDateTimeToFileTime((WORD)(conn->YAPPDate >> 16), (WORD)conn->YAPPDate & 0xFFFF, &FileTime); + ret = SetFileTime(hFile, &FileTime, &FileTime, &FileTime); + ret = GetLastError(); + + } + CloseHandle(hFile); + } +#else + + hFile = fopen(MsgFile, "wb"); + if (hFile) + { + Written = fwrite(conn->MailBuffer, 1, conn->YAPPLen, hFile); + fclose(hFile); + + if (conn->YAPPDate) + { + struct tm TM; + struct timeval times[2]; +/* + The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) +*/ + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (conn->YAPPDate & 0x1f) << 1; + TM.tm_min = ((conn->YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((conn->YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((conn->YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((conn->YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((conn->YAPPDate >> 25) & 0x7f) + 80; + + Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); + + times[0].tv_sec = times[1].tv_sec = mktime(&TM); + times[0].tv_usec = times[1].tv_usec = 0; + } + } +#endif + + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + + if (Written != conn->YAPPLen) + { + Mess[0] = CAN; + Mess[1] = 0; + QueueMsg(conn, Mess, 2); + Flush(conn); + len = sprintf_s(Mess, sizeof(Mess), "Failed to save YAPP File\r"); + QueueMsg(conn, Mess, len); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + conn->InputLen = 0; + conn->InputMode = 0; + } + } + + Reply[1] = 3; //Ack_EOF + QueueMsg(conn, Reply, 2); + Flush(conn); + conn->InputLen = 0; + + return TRUE; + + case EOT: + + // End Session + + Reply[1] = 4; // Ack_EOT + QueueMsg(conn, Reply, 2); + Flush(conn); + conn->InputLen = 0; + conn->InputMode = 0; + + len = sprintf_s(Mess, sizeof(Mess), "YAPP file %s received\r", conn->ARQFilename); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + QueueMsg(conn, Mess, len); + SendPrompt(conn, conn->UserPointer); + + return TRUE; + + case CAN: + + // Abort + + Mess[0] = ACK; + Mess[1] = 5; // CAN Ack + QueueMsg(conn, Mess, 2); + Flush(conn); + + if (conn->MailBuffer) + { + free(conn->MailBuffer); + conn->MailBufferSize=0; + conn->MailBuffer=0; + } + + // There may be a reason after the CAN + + len = Msg[1]; + + if (len) + { + char * errormsg = &Msg[2]; + errormsg[len] = 0; + nodeprintf(conn, "File Rejected - %s\r", errormsg); + } + else + + nodeprintf(conn, "File Rejected\r"); + + + len = sprintf_s(Mess, sizeof(Mess), "YAPP Transfer cancelled by Terminal\r"); + WriteLogLine(conn, '!', Mess, len - 1, LOG_BBS); + + conn->InputLen = 0; + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + + return FALSE; + + case ACK: + + switch (Msg[1]) + { + case 1: // Rcv_Rdy + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + len = (int)strlen(conn->ARQFilename) + 3; + + strcpy(&Mess[2], conn->ARQFilename); + len += sprintf(&Mess[len], "%d", conn->MailBufferSize); + len++; // include null + Mess[0] = SOH; + Mess[1] = len - 2; + + QueueMsg(conn, Mess, len); + Flush(conn); + conn->InputLen = 0; + + return FALSE; + + case 2: + + // Start sending message + + YAPPSendData(conn); + conn->InputLen = 0; + return FALSE; + + case 3: + + // ACK EOF - Send EOT + + + Mess[0] = EOT; + Mess[1] = 1; + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->InputLen = 0; + return FALSE; + + case 4: + + // ACK EOT + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + + conn->InputLen = 0; + return FALSE; + + default: + conn->InputLen = 0; + return FALSE; + + + + } + + case NAK: + + // Either Reject or Restart + + // RE Resume NAK len R NULL (File size in ASCII) NULL + + if (conn->InputLen > 2 && Msg[2] == 'R' && Msg[3] == 0) + { + int posn = atoi(&Msg[4]); + + conn->YAPPLen += posn; + conn->MailBufferSize -= posn; + + YAPPSendData(conn); + conn->InputLen = 0; + return FALSE; + + } + + // There may be a reason after the ack + + len = Msg[1]; + + if (len) + { + char * errormsg = &Msg[2]; + errormsg[len] = 0; + nodeprintf(conn, "File Rejected - %s\r", errormsg); + } + else + + nodeprintf(conn, "File Rejected\r"); + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + conn->InputLen = 0; + SendPrompt(conn, conn->UserPointer); + return FALSE; + } + + nodeprintf(conn, "Unexpected message during YAPP Transfer. Transfer canncelled\r"); + + conn->InputMode = 0; + conn->BBSFlags &= ~YAPPTX; + conn->InputLen = 0; + SendPrompt(conn, conn->UserPointer); + + return FALSE; + +} + +void YAPPSendFile(ConnectionInfo * conn, struct UserInfo * user, char * filename) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + struct stat STAT; + + if (filename == NULL) + { + nodeprintf(conn, "Filename missing\r"); + SendPrompt(conn, user); + return; + } + + if (strstr(filename, "..") || strchr(filename, '/') || strchr(filename, '\\')) + { + nodeprintf(conn, "Invalid filename\r"); + SendPrompt(conn, user); + return; + } + + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, filename); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", filename); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + char Mess[255]; + strcpy(conn->ARQFilename, filename); + conn->MailBuffer = malloc(FileSize); + conn->MailBufferSize = FileSize; + conn->YAPPLen = 0; + fread(conn->MailBuffer, 1, FileSize, hFile); + fclose(hFile); + + Mess[0] = ENQ; + Mess[1] = 1; + + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->InputMode = 'Y'; + + return; + } + } + + nodeprintf(conn, "File %s not found\r", filename); + SendPrompt(conn, user); +} + +void YAPPSendData(ConnectionInfo * conn) +{ + char Mess[258]; + + conn->BBSFlags |= YAPPTX; + + while (TXCount(conn->BPQStream) < 15) + { + int Left = conn->MailBufferSize; + + if (Left == 0) + { + // Finished - send End Data + + Mess[0] = ETX; + Mess[1] = 1; + + QueueMsg(conn, Mess, 2); + Flush(conn); + + conn->BBSFlags &= ~YAPPTX; + break; + } + + if (Left > conn->paclen - 2) // 2 byte header + Left = conn->paclen -2; + + memcpy(&Mess[2], &conn->MailBuffer[conn->YAPPLen], Left); + Mess[0] = STX; + Mess[1] = Left; + + QueueMsg(conn, Mess, Left + 2); + Flush(conn); + + conn->YAPPLen += Left; + conn->MailBufferSize -= Left; + } +} + +char * AddUser(char * Call, char * password, BOOL BBSFlag) +{ + struct UserInfo * USER; + + strlop(Call, '-'); + + if (strlen(Call) > 6) + Call[6] = 0; + + _strupr(Call); + + if (Call[0] == 0 || LookupCall(Call)) + { + return("User already exists\r\n"); + } + + USER = AllocateUserRecord(Call); + USER->Temp = zalloc(sizeof (struct TempUserInfo)); + + if (strlen(password) > 12) + password[12] = 0; + + strcpy(USER->pass, password); + + if (BBSFlag) + { + if(SetupNewBBS(USER)) + USER->flags |= F_BBS; + else + printf("Cannot set user to be a BBS - you already have 160 BBS's defined\r\n"); + } + + SaveUserDatabase(); + UpdateWPWithUserInfo(USER); + + return("User added\r\n"); +} + +// Server Support Code + +// For the moment only internal REQDIR and REQFIL. + +// May add WPSERV and user implemented servers +/* +F6FBB BBS > + SP REQDIR @ F6ABJ.FRA.EU + Title of message : + YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU + Text of message : + /EX + + F6FBB BBS > + SP REQFIL @ F6ABJ.FRA.EU + Title of message : + DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU + Text of message : + /EX + + Note Text not used. + +*/ + +VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To); + +BOOL ProcessReqDir(struct MsgInfo * Msg) +{ + char * Buffer; + int Len = 0; + char * ptr; + + // Parse title - gives directory and return address + + // YAPP\*.ZIP @ F6FBB.FMLR.FRA.EU + + // At the moment we don't allow subdirectories but no harm handling here + + char Pattern[64]; + char * Address; + char * filename = NULL; // ?? Pattern Match ?? + +#ifdef WIN32 + + WIN32_FIND_DATA ffd; + + char szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + +#else + + #include + + struct dirent **namelist; + int n, i; + struct stat STAT; + int res; + char FN[256]; + +#endif + + strcpy(Pattern, Msg->title); + + ptr = strchr(Pattern, '@'); + + if (ptr == NULL) + + // if we don't have return address no point + // but could we default to sender?? + + return FALSE; + + *ptr++ = 0; // Terminate Path + + strlop(Pattern, ' '); + + while (*ptr == ' ') + ptr++; // accept with or without spaces round @ + + Address = ptr; + + ptr = Buffer = malloc(MaxTXSize); + +#ifdef WIN32 + + // Prepare string for use with FindFile functions. First, copy the + // string to a buffer, then append '\*' to the directory name. + + strcpy(szDir, GetBPQDirectory()); + strcat(szDir, "\\BPQMailChat\\Files\\"); + strcat(szDir, Pattern); + + // Find the first file in the directory. + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + Len = sprintf(Buffer, "No Files\r"); + } + else + { + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + {} + else + { + if (filename == NULL || stristr(ffd.cFileName, filename)) + Len += sprintf(&Buffer[Len], "%s %d\r", ffd.cFileName, ffd.nFileSizeLow); + } + } + while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + } + +#else + + n = scandir("Files", &namelist, NULL, alphasort); + + if (n < 0) + perror("scandir"); + else + { + for (i = 0; i < n; i++) + { + sprintf(FN, "Files/%s", namelist[i]->d_name); + + if (filename == NULL || stristr(namelist[i]->d_name, filename)) + if (FN[6] != '.' && stat(FN, &STAT) == 0) + Len += sprintf(&Buffer[Len], "%s %d\r", namelist[i]->d_name, STAT.st_size); + + free(namelist[i]); + } + free(namelist); + } + +#endif + + // Build Message + + SendServerReply("REQDIR Reply", Buffer, Len, _strupr(Address)); + return TRUE; +} + +/* + ' Augment Message ID with the Message Pickup Station we're directing this message to. + ' + Dim strAugmentedMessageID As String + If GetMidRMS(MessageId) <> "" Then + ' The MPS RMS is already set on the message ID + strAugmentedMessageID = MessageId + strMPS = GetMidRMS(MessageId) + ' "@R" at the end of the MID means route message only via radio + If GetMidForwarding(MessageId) = "" And (blnRadioOnly Or UploadThroughInternet()) Then + strAugmentedMessageID &= "@" & strHFOnlyFlag + End If + ElseIf strMPS <> "" Then + ' Add MPS to the message ID + strAugmentedMessageID = MessageId & "@" & strMPS + ' "@R" at the end of the MID means route message only via radio + If blnRadioOnly Or UploadThroughInternet() Then + strAugmentedMessageID &= "@" & strHFOnlyFlag + End If + Else + strAugmentedMessageID = MessageId + End If + +*/ + +void ProcessSyncModeMessage(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len) +{ + Buffer[len] = 0; + + if (conn->Flags & GETTINGSYNCMESSAGE) + { + // Data + + if ((conn->TempMsg->length + len) > conn->MailBufferSize) + { + conn->MailBufferSize += 10000; + conn->MailBuffer = realloc(conn->MailBuffer, conn->MailBufferSize); + + if (conn->MailBuffer == NULL) + { + BBSputs(conn, "*** Failed to extend Message Buffer\r"); + conn->CloseAfterFlush = 20; // 2 Secs + + return; + } + } + + memcpy(&conn->MailBuffer[conn->TempMsg->length], Buffer, len); + + conn->TempMsg->length += len; + + if (conn->TempMsg->length >= conn->SyncCompressedLen) + { + // Complete - decompress it + + conn->BBSFlags |= FBBCompressed; + Decode(conn, 1); + + conn->Flags &= !GETTINGSYNCMESSAGE; + + BBSputs(conn, "OK\r"); + return; + } + return; + } + + if (conn->Flags & PROPOSINGSYNCMSG) + { + // Waiting for response to TR AddMessage + + if (strcmp(Buffer, "OK\r") == 0) + { + char Msg[256]; + int n; + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Send the message, it has already been built + + conn->Flags &= !PROPOSINGSYNCMSG; + conn->Flags |= SENDINGSYNCMSG; + + n = sprintf_s(Msg, sizeof(Msg), "Sending SYNC message %s", conn->FwdMsg->bid); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + QueueMsg(conn, conn->SyncMessage, conn->SyncCompressedLen); + return; + } + + if (strcmp(Buffer, "NO\r") == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Message Rejected - ? duplicate + + if (conn->FwdMsg) + { + // Zap the entry + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + conn->UserPointer->ForwardingInfo->MsgCount--; + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->FwdMsg->Locked = 0; // Unlock + } + } + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->Flags &= !PROPOSINGSYNCMSG; + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (conn->Flags & SENDINGSYNCMSG) + { + if (strcmp(Buffer, "OK\r") == 0) + { + // Message Sent + + conn->Flags &= !SENDINGSYNCMSG; + free(conn->SyncMessage); + + if (conn->FwdMsg) + { + char Msg[256]; + int n; + + n = sprintf_s(Msg, sizeof(Msg), "SYNC message %s Sent", conn->FwdMsg->bid); + WriteLogLine(conn, '|',Msg, n, LOG_BBS); + + clear_fwd_bit(conn->FwdMsg->fbbs, user->BBSNumber); + set_fwd_bit(conn->FwdMsg->forw, user->BBSNumber); + conn->UserPointer->ForwardingInfo->MsgCount--; + + // Only mark as forwarded if sent to all BBSs that should have it + + if (memcmp(conn->FwdMsg->fbbs, zeros, NBMASK) == 0) + { + conn->FwdMsg->status = 'F'; // Mark as forwarded + conn->FwdMsg->datechanged=time(NULL); + } + + conn->FwdMsg->Locked = 0; // Unlock + } + + // drop through to send any more + } + else + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + conn->Flags &= !SENDINGSYNCMSG; + free(conn->SyncMessage); + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + + return; + } + } + + if (strcmp(Buffer, "OK\r") == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + // Send Message(?s) to RMS Relay SYNC + +/* +OK +>TR AddMessage_V5JLSGH591JR 786 1219 522 True +BYE*/ + if (FindMessagestoForward(conn) && conn->FwdMsg) + { + struct MsgInfo * Msg = conn->FwdMsg; + char Buffer[128]; + char * Message; + + Message = FormatSYNCMessage(conn, Msg); + + // Need to compress it + + conn->SyncMessage = malloc(conn->SyncXMLLen + conn->SyncMsgLen + 4096); + + conn->SyncCompressedLen = Encode(Message, conn->SyncMessage, conn->SyncXMLLen + conn->SyncMsgLen, 0, 1); + + sprintf(Buffer, "TR AddMessage_%s %d %d %d True\r", // The True on end indicates compressed + Msg->bid, conn->SyncCompressedLen, conn->SyncXMLLen, conn->SyncMsgLen); + + free(Message); + + conn->Flags |= PROPOSINGSYNCMSG; + + BBSputs(conn, Buffer); + return; + } + + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (memcmp(Buffer, "TR ", 2) == 0) + { + // Messages have TR_COMMAND_BID Compressed Len XML Len Bosy Len + + char * Command; + char * BIDptr; + + BIDRec * BID; + char *ptr1, *ptr2, *context; + + // TR AddMessage_1145_G8BPQ 727 1202 440 True + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + + Command = strtok_s(&Buffer[3], "_", &context); + BIDptr = strtok_s(NULL, " ", &context); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncCompressedLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncXMLLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + conn->SyncMsgLen = atoi(ptr2); + ptr2 = strtok_s(NULL, " ", &context); + + // If addmessage need to check bid doesn't exist + + if (strcmp(Command, "AddMessage") == 0) + { + strlop(BIDptr, '@'); // sometimes has @CALL@R + if (strlen(BIDptr) > 12) + BIDptr[12] = 0; + + BID = LookupBID(BIDptr); + + if (BID) + { + BBSputs(conn, "Rejected - Duplicate BID\r"); + return; + } + } + + conn->TempMsg = zalloc(sizeof(struct MsgInfo)); + + conn->Flags |= GETTINGSYNCMESSAGE; + + BBSputs(conn, "OK\r"); + return; + } + + if (memcmp(Buffer, "BYE\r", 4) == 0) + { + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; + } + + if (memcmp(Buffer, "BBS\r", 4) == 0) + { + // Out of Sync + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + conn->BBSFlags &= ~SYNCMODE; + return; + } + + WriteLogLine(conn, '<', Buffer, len-1, LOG_BBS); + WriteLogLine(conn, '<', "Unexpected SYNC Message", 23, LOG_BBS); + + BBSputs(conn, "BYE\r"); + conn->CloseAfterFlush = 20; // 2 Secs + conn->BBSFlags &= ~SYNCMODE; + return; +} +BOOL ProcessReqFile(struct MsgInfo * Msg) +{ + char FN[128]; + char * Buffer; + int Len = 0; + char * ptr; + struct stat STAT; + char MsgFile[MAX_PATH]; + FILE * hFile; + int FileSize; + char * MsgBytes; + + // Parse title - gives file and return address + + // DEMOS\ESSAI.TXT @ F6FBB.FMLR.FRA.EU + + // At the moment we don't allow subdirectories but no harm handling here + + char * Address; + char * filename = NULL; // ?? Pattern Match ?? + + strcpy(FN, Msg->title); + + ptr = strchr(FN, '@'); + + if (ptr == NULL) + + // if we don't have return address no point + // but could we default to sender?? + + return FALSE; + + *ptr++ = 0; // Terminate Path + + strlop(FN, ' '); + + while (*ptr == ' ') + ptr++; // accept with or without spaces round @ + + Address = ptr; + + ptr = Buffer = malloc(MaxTXSize + 1); // Allow terminating Null + + // Build Message + + if (FN == NULL) + { + Len = sprintf(Buffer, "Missing Filename\r"); + } + else if (strstr(FN, "..") || strchr(FN, '/') || strchr(FN, '\\')) + { + Len = sprintf(Buffer,"Invalid filename %s\r", FN); + } + else + { + if (BaseDir[0]) + sprintf_s(MsgFile, sizeof(MsgFile), "%s/Files/%s", BaseDir, FN); + else + sprintf_s(MsgFile, sizeof(MsgFile), "Files/%s", FN); + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + int Length; + + if (FileSize > MaxTXSize) + FileSize = MaxTXSize; // Truncate to max size + + MsgBytes=malloc(FileSize+1); + fread(MsgBytes, 1, FileSize, hFile); + fclose(hFile); + + MsgBytes[FileSize]=0; + + // Remove lf chars + + Length = RemoveLF(MsgBytes, (int)strlen(MsgBytes)); + + Len = sprintf(Buffer, "%s", MsgBytes); + free(MsgBytes); + } + } + else + Len = sprintf(Buffer, "File %s not found\r", FN); + } + + SendServerReply("REQFIL Reply", Buffer, Len, _strupr(Address)); + return TRUE; +} + +BOOL CheckforMessagetoServer(struct MsgInfo * Msg) +{ + if (_stricmp(Msg->to, "REQDIR") == 0) + return ProcessReqDir(Msg); + + if (_stricmp(Msg->to, "REQFIL") == 0) + return ProcessReqFile(Msg); + + return FALSE; +} + +VOID SendServerReply(char * Title, char * MailBuffer, int Length, char * To) +{ + struct MsgInfo * Msg = AllocateMsgRecord(); + BIDRec * BIDRec; + char * Via; + char MsgFile[MAX_PATH]; + FILE * hFile; + size_t WriteLen=0; + + Msg->length = Length; + + GetSemaphore(&MsgNoSemaphore, 0); + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + FreeSemaphore(&MsgNoSemaphore); + + strcpy(Msg->from, BBSName); + Via = strlop(To, '@'); + + if (Via) + strcpy(Msg->via, Via); + + strcpy(Msg->to, To); + strcpy(Msg->title, Title); + + Msg->type = 'P'; + Msg->status = 'N'; + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + sprintf_s(Msg->bid, sizeof(Msg->bid), "%d_%s", LatestMsg, BBSName); + + BIDRec = AllocateBIDRecord(); + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + WriteLen = fwrite(MailBuffer, 1, Msg->length, hFile); + fclose(hFile); + } + + MatchMessagetoBBSList(Msg, NULL); + free(MailBuffer); +} + +void SendRequestSync(CIRCUIT * conn) +{ + // Only need XML Header + + char * Buffer = malloc(4096); + int Len = 0; + + struct tm *tm; + char Date[32]; + char MsgTime[32]; + time_t Time = time(NULL); + + char * Encoded; + + tm = gmtime(&Time); + + sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + Len += sprintf(&Buffer[Len], "\r\n"); + + Len += sprintf(&Buffer[Len], "\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " request_sync\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Date); + Len += sprintf(&Buffer[Len], " %s\r\n", BBSName); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " BBSName\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", conn->SyncHost); + Len += sprintf(&Buffer[Len], " %d\r\n", conn->SyncPort); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); + +/* + + + + request_sync + 20230205100652 + GI8BPQ + + + GI8BPQ + + 127.0.0.1 + 8780 + + + +*/ + + // Need to compress it + + conn->SyncXMLLen = Len; + conn->SyncMsgLen = 0; + + conn->SyncMessage = malloc(conn->SyncXMLLen + 4096); + + conn->SyncCompressedLen = Encode(Buffer, conn->SyncMessage, conn->SyncXMLLen, 0, 1); + + sprintf(Buffer, "TR RequestSync_%s_%d %d %d 0 True\r", // The True on end indicates compressed + 50, conn->SyncCompressedLen, conn->SyncXMLLen); + + free(Buffer); + + conn->Flags |= REQUESTINGSYNC; + + BBSputs(conn, Buffer); + return; +} + + +void ProcessSyncXML(CIRCUIT * conn, char * XML) +{ + // Process XML from RMS Relay Sync + + // All seem to start + + // + // + // + // + + char * Type = strstr(XML, ""); + + if (Type == NULL) + return; + + Type += strlen(""); + + if (memcmp(Type, "rms_location", 12) == 0) + { + return; + } + + + if (memcmp(Type, "request_sync", 12) == 0) + { + char * Call; + struct UserInfo * BBSREC; + + // This isn't requesting a poll, it is asking to be added as a sync partner + + Call = strstr(Type, ""); + + if (Call == NULL) + return; + + Call += 10; + strlop(Call, '<'); + BBSREC = FindBBS(Call); + + if (BBSREC == NULL) + return; + + if (BBSREC->ForwardingInfo->Forwarding == 0) + StartForwarding(BBSREC->BBSNumber, NULL); + + return; + } + + if (memcmp(Type, "remove_message", 14) == 0) + { + char * MID = strstr(Type, ""); + struct MsgInfo * Msg; + + if (MID == NULL) + return; + + MID += 11; + strlop(MID, '<'); + + strlop(MID, '@'); // sometimes has @CALL@R + if (strlen(MID) > 12) + MID[12] = 0; + + Msg = FindMessageByBID(MID); + + if (Msg == NULL) + return; + + Logprintf(LOG_BBS, conn, '|', "Killing Msg %d %s", Msg->number, Msg->bid); + + FlagAsKilled(Msg, TRUE); + return; + } + + if (memcmp(Type, "delivered", 9) == 0) + { + char * MID = strstr(Type, ""); + struct MsgInfo * Msg; + + if (MID == NULL) + return; + + MID += 11; + strlop(MID, '<'); + + strlop(MID, '@'); // sometimes has @CALL@R + if (strlen(MID) > 12) + MID[12] = 0; + + Msg = FindMessageByBID(MID); + + if (Msg == NULL) + return; + + Logprintf(LOG_BBS, conn, '|', "Message Msg %d %s Delivered", Msg->number, Msg->bid); + return; + } + + Debugprintf(Type); + return; + +/* + + + + request_sync + 20230205100652 + GI8BPQ + + + GI8BPQ + + 127.0.0.1 + 8780 + + + +} + + + + delivered + 20230205093113 + G8BPQ + + + 10845_GM8BPB + G8BPQ + G8BPQ + 3 + + + + Public Enum MessageDeliveryMethod + ' + ' Method used to deliver a message. None if the message hasn't been delivered. + ' + Unspecified = -1 + None = 0 + Telnet = 1 + CMS = 2 + Radio = 3 + Email = 4 +End Enum +*/ +} + +int ReformatSyncMessage(CIRCUIT * conn) +{ + // Message has been decompressed - reformat to look like a WLE message + + char * MsgBit; + char *ptr1, *ptr2; + int linelen; + char FullFrom[80]; + char FullTo[80]; + char BID[80]; + time_t Date; + char Mon[80]; + char Subject[80]; + int i = 0; + char * Boundary; + char * Input; + char * via = NULL; + char * NewMsg = conn->MailBuffer; + char * SaveMsg = NewMsg; + char DateString[80]; + struct tm * tm; + char Type[16] = "Private"; + char * part[100] = {""}; + char * partname[100]; + int partLen[100]; + char xml[4096]; + + // Message has an XML header then the message + + // The XML may have control info, so examine it. + + /* + Date: Mon, 25 Oct 2021 10:22:00 -0000 + From: GM8BPQ + Subject: Test + To: 2E1BGT + Message-ID: ALYJQJRXVQAO + X-Source: GM8BPQ + X-Relay: G8BPQ + MIME-Version: 1.0 + MIME-Version: 1.0 + Content-Type: multipart/mixed; boundary="boundaryBSoxlw==" + + --boundaryBSoxlw== + Content-Type: text/plain; charset="iso-8859-1" + Content-Transfer-Encoding: quoted-printable + + Hello Hello + + --boundaryBSoxlw==-- + */ + + // I think the best way is to reformat as if from Winlink Express, then pass + //through the normal B2 code. + +// WriteLogLine(conn, '<', conn->MailBuffer, conn->TempMsg->length, LOG_BBS); + + // display the message for testing + + conn->MailBuffer[conn->TempMsg->length] = 0; + +// OutputDebugString(conn->MailBuffer); + memcpy(xml, conn->MailBuffer, conn->SyncXMLLen); + xml[conn->SyncXMLLen] = 0; + + if (conn->SyncMsgLen == 0) + { + // No message, Just xml. Looks like a status report + + ProcessSyncXML(conn, xml); + return 0; + } + + MsgBit = &conn->MailBuffer[conn->SyncXMLLen]; + conn->TempMsg->length -= conn->SyncXMLLen; + + ptr1 = MsgBit; + +Loop: + + ptr2 = strchr(ptr1, '\r'); + + linelen = (int)(ptr2 - ptr1); + + if (_memicmp(ptr1, "From:", 5) == 0) + { + memcpy(FullFrom, &ptr1[6], linelen - 6); + FullFrom[linelen - 6] = 0; + } + + if (_memicmp(ptr1, "To:", 3) == 0) + { + memcpy(FullTo, &ptr1[4], linelen - 4); + FullTo[linelen - 4] = 0; + } + + else if (_memicmp(ptr1, "Subject:", 8) == 0) + { + memcpy(Subject, &ptr1[9], linelen - 9); + Subject[linelen - 9] = 0; + } + + else if (_memicmp(ptr1, "Message-ID", 10) == 0) + { + memcpy(BID, &ptr1[12], linelen - 12); + BID[linelen - 12] = 0; + } + + else if (_memicmp(ptr1, "Date:", 5) == 0) + { + struct tm rtime; + char seps[] = " ,\t\r"; + + memset(&rtime, 0, sizeof(struct tm)); + + // Date: Mon, 25 Oct 2021 10:22:00 -0000 + + sscanf(&ptr1[11], "%02d %s %04d %02d:%02d:%02d", + &rtime.tm_mday, &Mon, &rtime.tm_year, &rtime.tm_hour, &rtime.tm_min, &rtime.tm_sec); + + rtime.tm_year -= 1900; + + for (i = 0; i < 12; i++) + { + if (strcmp(Mon, month[i]) == 0) + break; + } + + rtime.tm_mon = i; + + Date = mktime(&rtime) - (time_t)_MYTIMEZONE; + + if (Date == (time_t)-1) + Date = time(NULL); + + } + + if (linelen) // Not Null line + { + ptr1 = ptr2 + 2; // Skip crlf + goto Loop; + } + + // Unpack Body - seems to be multipart even if only one + + // Can't we just send the whole body through ?? + // No, Attachment format is different + + // Mbo: GM8BPQ + // Body: 17 + // File: 1471 leadercoeffs.txt + + Input = MsgBit; + Boundary = initMultipartUnpack(&Input); + + i = 0; + + if (Boundary) + { + // input should be start of part + + // Find End of part - ie -- Boundary + CRLF or -- + + char * ptr, * saveptr; + char * Msgptr; + size_t BLen = strlen(Boundary); + size_t Partlen; + + saveptr = Msgptr = ptr = Input; + + while(ptr) // Just in case we run off end + { + if (*ptr == '-' && *(ptr+1) == '-') + { + if (memcmp(&ptr[2], Boundary, BLen) == 0) + { + // Found Boundary + + char * p1, *p2, *ptr3, *ptr4; + int llen; + int Base64 = 0; + int QuotedP = 0; + char * BoundaryStart = ptr; + + Partlen = ptr - Msgptr; + + ptr += (BLen + 2); // End of Boundary + + if (*ptr == '-') // Terminating Boundary + Input = NULL; + else + Input = ptr + 2; + + // Will check for quoted printable + + p1 = Msgptr; +Loop2: + p2 = strchr(p1, '\r'); + llen = (int)(p2 - p1); + + if (llen) + { + + if (_memicmp(p1, "Content-Transfer-Encoding:", 26) == 0) + { + if (_memicmp(&p1[27], "base64", 6) == 0) + Base64 = TRUE; + else if (_memicmp(&p1[27], "quoted", 6) == 0) + QuotedP = TRUE; + } + else if (_memicmp(p1, "Content-Disposition: ", 21) == 0) + { + ptr3 = strstr(&p1[21], "name"); + + if (ptr3) + { + ptr3 += 5; + if (*ptr3 == '"') ptr3++; + ptr4 = strchr(ptr3, '"'); + if (ptr4) *ptr4 = 0; + + partname[i] = ptr3; + } + } + + if (llen) // Not Null line + { + p1 = p2 + 2; // Skip crlf + goto Loop2; + } + } + + part[i] = strstr(p2, "\r\n"); // Over separator + + if (part[i]) + { + part[i] += 2; + partLen[i] = BoundaryStart - part[i] - 2; + if (QuotedP) + partLen[i] = decode_quoted_printable(part[i], partLen[i]); + else if (Base64) + { + int Len = partLen[i], NewLen; + char * ptr = part[i]; + char * ptr2 = part[i]; + + // WLE sends base64 with embedded crlf, so remove them + + while (Len-- > 0) + { + if ((*ptr) != 10 && (*ptr) != 13) + *(ptr2++) = *(ptr++); + else + ptr ++; + } + + Len = ptr2 - part[i]; + ptr = part[i]; + ptr2 = part[i]; + + while (Len > 0) + { + decodeblock(ptr, ptr2); + ptr += 4; + ptr2 += 3; + Len -= 4; + } + + NewLen = (int)(ptr2 - part[i]); + + if (*(ptr-1) == '=') + NewLen--; + + if (*(ptr-2) == '=') + NewLen--; + + partLen[i] = NewLen; + } + } + Msgptr = ptr = Input; + i++; + continue; } + + // See if more parts + } + ptr++; + } + ptr++; + } + + + // Build the message + + tm = gmtime(&Date); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + NewMsg += sprintf(NewMsg, + "MID: %s\r\n" + "Date: %s\r\n" + "Type: %s\r\n" + "From: %s\r\n", + BID, DateString, Type, FullFrom); + +// if (ToCalls) +// { +// int i; + +// for (i = 0; i < Calls; i++) +// NewMsg += sprintf(NewMsg, "To: %s\r\n", ToCalls[i]); + +// } +// else + { + NewMsg += sprintf(NewMsg, "To: %s\r\n", + FullTo); + } +// if (WebMail->CC && WebMail->CC[0]) +// NewMsg += sprintf(NewMsg, "CC: %s\r\n", WebMail->CC); + + NewMsg += sprintf(NewMsg, + "Subject: %s\r\n" + "Mbo: %s\r\n", + Subject, BBSName); + + // Write the Body: line and any File Lines + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", partLen[0]); + + i = 1; + + while (part[i]) + { + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", + partLen[i], partname[i]); + + i++; + } + + NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line to end header + + // Now add parts + + i = 0; + + while (part[i]) + { + memmove(NewMsg, part[i], partLen[i]); + NewMsg += partLen[i]; + i++; + NewMsg += sprintf(NewMsg, "\r\n"); // Blank Line between attachments + } + + conn->TempMsg->length = NewMsg - SaveMsg; + conn->TempMsg->datereceived = conn->TempMsg->datechanged = time(NULL); + conn->TempMsg->datecreated = Date; + strcpy(conn->TempMsg->bid, BID); + + if (strlen(Subject) > 60) + Subject[60] = 0; + + strcpy(conn->TempMsg->title, Subject); + + return TRUE; +} + +char * FormatSYNCMessage(CIRCUIT * conn, struct MsgInfo * Msg) +{ + // First an XML Header + + char * Buffer = malloc(4096 + Msg->length); + int Len = 0; + + struct tm *tm; + char Date[32]; + char MsgTime[32]; + char Separator[33]=""; + time_t Time = time(NULL); + char * MailBuffer; + int BodyLen; + char * Encoded; + + // Get the message - may need length in header + + MailBuffer = ReadMessageFile(Msg->number); + + BodyLen = Msg->length; + + // Remove any B2 Header + + if (Msg->B2Flags & B2Msg) + { + // Remove B2 Headers (up to the File: Line) + + char * ptr; + ptr = strstr(MailBuffer, "Body:"); + if (ptr) + { + BodyLen = atoi(ptr + 5); + ptr = strstr(ptr, "\r\n\r\n"); + } + if (ptr) + { + memcpy(MailBuffer, ptr + 4, BodyLen); + MailBuffer[BodyLen] = 0; + } + } + + // encode body as quoted printable; + + Encoded = malloc(Msg->length * 3); + + BodyLen = encode_quoted_printable(MailBuffer, Encoded, BodyLen); + + // Create multipart Boundary + + CreateOneTimePassword(&Separator[0], "Key", 0); + CreateOneTimePassword(&Separator[16], "Key", 1); + + + tm = gmtime(&Time); + + sprintf_s(Date, sizeof(Date), "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + tm = gmtime((time_t *)&Msg->datecreated); + + sprintf_s(MsgTime, sizeof(Date), "%04d/%02d/%02d %02d:%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + Len += sprintf(&Buffer[Len], "\r\n"); + + Len += sprintf(&Buffer[Len], "\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " add_message\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Date); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); + Len += sprintf(&Buffer[Len], " \r\n", MsgTime); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], " 2\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", (Msg->B2Flags & Attachments) ? "true" : "false"); + Len += sprintf(&Buffer[Len], " %d\r\n", BodyLen); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->title); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->bid); + Len += sprintf(&Buffer[Len], " 450443\r\n"); + Len += sprintf(&Buffer[Len], " %s\r\n", Msg->to); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " 0\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " True\r\n"); + Len += sprintf(&Buffer[Len], " False\r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], " \r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); + +// Debugprintf(Buffer); + + conn->SyncXMLLen = Len; + + Len += sprintf(&Buffer[Len], "Date: Sat, 04 Feb 2023 11:19:00 +0000\r\n"); + Len += sprintf(&Buffer[Len], "From: %s\r\n", Msg->from); + Len += sprintf(&Buffer[Len], "Subject: %s\r\n", Msg->title); + Len += sprintf(&Buffer[Len], "To: %s\r\n", Msg->to); + Len += sprintf(&Buffer[Len], "Message-ID: %s\r\n", Msg->bid); +// Len += sprintf(&Buffer[Len], "X-Source: G8BPQ\r\n"); +// Len += sprintf(&Buffer[Len], "X-Location: 52.979167N, 1.125000W (GRID SQUARE)\r\n"); +// Len += sprintf(&Buffer[Len], "X-RMS-Originator: G8BPQ\r\n"); +// Len += sprintf(&Buffer[Len], "X-RMS-Path: G8BPQ@2023-02-04-11:19:29\r\n"); + Len += sprintf(&Buffer[Len], "X-Relay: %s\r\n", BBSName); + + Len += sprintf(&Buffer[Len], "MIME-Version: 1.0\r\n"); + Len += sprintf(&Buffer[Len], "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", Separator); + + Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before separator + Len += sprintf(&Buffer[Len], "--%s\r\n", Separator); + Len += sprintf(&Buffer[Len], "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n"); + Len += sprintf(&Buffer[Len], "Content-Transfer-Encoding: quoted-printable\r\n"); + Len += sprintf(&Buffer[Len], "\r\n"); // Blank line before body + + Len += sprintf(&Buffer[Len], "%s\r\n", Encoded); + Len += sprintf(&Buffer[Len], "--%s--\r\n", Separator); + + conn->SyncMsgLen = Len - conn->SyncXMLLen; + + free(Encoded); + free(MailBuffer); + + return Buffer; +} + +int encode_quoted_printable(char *s, char * out, int Len) +{ + int n = 0; + char * start = out; + + while(Len--) + { + if (n >= 73 && *s != 10 && *s != 13) + {strcpy(out, "=\r\n"); n = 0; out +=3;} + if (*s == 10 || *s == 13) {putchar(*s); n = 0;} + else if (*s<32 || *s==61 || *s>126) + out += sprintf(out, "=%02x", (unsigned char)*s); + else if (*s != 32 || (*(s+1) != 10 && *(s+1) != 13)) + {*(out++) = *s; n++;} + else n += printf("=20"); + + s++; + } + *out = 0; + + return out - start; +} + +int decode_quoted_printable(char *ptr, int len) +{ + // overwrite input with decoded version + + char * ptr2 = ptr; + char * End = ptr + len; + char * Start = ptr; + + while (ptr < End) + { + if ((*ptr) == '=') + { + char c = *(++ptr); + char d; + + c = c - 48; + if (c < 0) + { + // = CRLF as a soft break + + ptr += 2; + continue; + } + if (c > 9) c -= 7; + d = *(++ptr); + d = d - 48; + if (d > 9) d -= 7; + + *(ptr2) = c << 4 | d; + ptr2++; + ptr++; + } + else + *ptr2++ = *ptr++; + } + return ptr2 - Start; +} diff --git a/BPQMail.c b/BPQMail.c index a4eecaf..82e58bc 100644 --- a/BPQMail.c +++ b/BPQMail.c @@ -1355,6 +1355,7 @@ BOOL CreateMulticastConsole(); char * CheckToAddress(CIRCUIT * conn, char * Addr); BOOL CheckifPacket(char * Via); int GetHTMLForms(); +VOID GetPGConfig(); struct _EXCEPTION_POINTERS exinfox; @@ -1708,6 +1709,14 @@ int APIENTRY WinMain(HINSTANCE hInstance, FreeForwardingStruct(user); free(user->ForwardingInfo); } + /* ---------- TAJ --PG Server------*/ + + if ( user->Temp->RUNPGPARAMS ) { + + printf("Also freeing RUNPGARGS\n"); + free(user->Temp->RUNPGPARAMS); + } + /* --------------------------------*/ free(user->Temp); @@ -3003,9 +3012,9 @@ int RefreshMainWindow() #define VERSION_MAJOR 2 #define VERSION_MINOR 0 -SOCKADDR_IN local_sin; /* Local socket - internet style */ +static SOCKADDR_IN local_sin; /* Local socket - internet style */ -PSOCKADDR_IN psin; +static PSOCKADDR_IN psin; SOCKET sock; @@ -3333,6 +3342,7 @@ BOOL Initialise() // CreateWPReport(); CreatePipeThread(); + GetPGConfig(); return TRUE; } diff --git a/BPQMail.c.bak b/BPQMail.c.bak new file mode 100644 index 0000000..ff79254 --- /dev/null +++ b/BPQMail.c.bak @@ -0,0 +1,3651 @@ +// Mail and Chat Server for BPQ32 Packet Switch +// +// + +// Version 1.0.0.17re + +// Split Messasge, User and BBS Editing from Main Config. +// Add word wrap to Console input and output +// Flash Console on chat user connect +// Fix processing Name response in chat mode +// Fix processing of *RTL from station not defined as a Chat Node +// Fix overlength lines ln List responses +// Housekeeping expires BIDs +// Killing a message removes it from the forwarding counts + +// Version 1.0.0.18 + +// Save User Database when name is entered or updated so it is not lost on a crash +// Fix Protocol Error in Compressed Forwarding when switching direction +// Add Housekeeping results dialog. + +// Version 1.0.0.19 + +// Allow PACLEN in forward scripts. +// Store and forward messages with CRLF as line ends +// Send Disconnect after FQ ( for LinFBB) +// "Last Listed" is saved if MailChat is closed without closing Console +// Maximum acceptable message length can be specified (in Forwarding Config) + +// Version 1.0.0.20 + +// Fix error in saving forwarding config (introduced in .19) +// Limit size of FBB forwarding block. +// Clear old connection (instead of new) if duplicate connect on Chat Node-Node link +// Send FA for Compressed Mail (was sending FB for both Compressed and Uncompressed) + +// Version 1.0.0.21 + +// Fix Connect Script Processing (wasn't waiting for CONNECTED from last step) +// Implement Defer +// Fix MBL-style forwarding +// Fix Add User (Params were not saved) +// Add SC (Send Copy) Command +// Accept call@bbs as well as call @ bbs + +// Version 1.0.0.22 + +// Implement RB RP LN LR LF LN L$ Commands. +// Implement QTH and ZIP Commands. +// Entering an empty Title cancels the message. +// Uses HomeBBS field to set @ field for local users. +// Creates basic WP Database. +// Uses WP to lookup @ field for non-local calls. +// Console "Actions" Menu renamed "Options". +// Excluded flag is actioned. +// Asks user to set HomeBBS if not already set. +// Fix "Shrinking Message" problem, where message got shorter each time it was read Initroduced in .19). +// Flash Server window when anyone connects to chat (If Console Option "Flash on Chat User Connect" set). + +// Version 1.0.0.23 + +// Fix R: line scan bug + +// Version 1.0.0.24 + +// Fix closing console window on 'B'. +// Fix Message Creation time. +// Enable Delete function in WP edit dialog + +// Version 1.0.0.25 + +// Implement K< and K> commands +// Experimental support for B1 and B2 forwarding +// Experimental UI System +// Fix extracting QTH from WP updates + +// Version 1.0.0.26 + +// Add YN etc responses for FBB B1/B2 + +// Version 1.0.0.27 + +// Fix crash if NULL received as start of a packet. +// Add Save WP command +// Make B2 flag BBS-specific. +// Implement B2 Send + +// Version 1.0.0.28 + +// Fix parsing of smtp to addresses - eg smtp:john.wiseman@cantab.net +// Flag messages as Held if smtp server rejects from or to addresses +// Fix Kill to (K> Call) +// Edit Message dialog shows latest first +// Add chat debug window to try to track down occasional chat connection problems + +// Version 1.0.0.29 + +// Add loads of try/excspt + +// Version 1.0.0.30 + +// Writes Debug output to LOG_DEBUG_X and Monitor Window + +// Version 1.0.0.32 + +// Allow use of GoogleMail for ISP functions +// Accept SYSOP as alias for SYSOPCall - ie user can do SP SYSOP, and it will appear in sysop's LM, RM, etc +// Email Housekeeping Results to SYSOP + +// Version 1.0.0.33 + +// Housekeeping now runs at Maintenance Time. Maintenance Interval removed. +// Allow multiple numbers on R and K commands +// Fix L command with single number +// Log if Forward count is out of step with messages to forward. +// UI Processing improved and F< command implemented + +// Version 1.0.0.34 + +// Semaphore Chat Messages +// Display Semaphore Clashes +// More Program Error Traps +// Kill Messages more than BIDLifetime old + +// Version 1.0.0.35 + +// Test for Mike - Remove B1 check from Parse_SID + +// Version 1.0.0.36 + +// Fix calculation of Housekeeping Time. +// Set dialog box background explicitly. +// Remove tray entry for chat debug window. +// Add date to log file name. +// Add Actions Menu option to disable logging. +// Fix size of main window when it changes between versions. + +// Version 1.0.0.37 + +// Implement Paging. +// Fix L< command (was giving no messages). +// Implement LR LR mmm-nnn LR nnn- (and L nnn-) +// KM should no longer kill SYSOP bulls. +// ISP interfaces allows SMTP Auth to be configured +// SMTP Client would fail to send any more messages if a connection failed + +// Version 1.0.0.38 + +// Don't include killed messages in L commands (except LK!) +// Implement l@ +// Add forwarding timebands +// Allow resizing of main window. +// Add Ver command. + +// Version 1.0.1.1 + +// First Public Beta + +// Fix part line handling in Console +// Maintenance deletes old log files. +// Add option to delete files to the recycle bin. + +// Version 1.0.2.1 + +// Allow all Node SYSOP commands in connect scripts. +// Implement FBB B1 Protocol with Resume +// Make FBB Max Block size settable for each BBS. +// Add extra logging when Chat Sessions refused. +// Fix Crash on invalid housekeeping override. +// Add Hold Messages option. +// Trap CRT Errors +// Sort Actions/Start Forwarding List + +// Version 1.0.2.2 + +// Fill in gaps in BBS Number sequence +// Fix PE if ctext contains } +// Run Houskeeping at startup if previous Housekeeping was missed + +// Version 1.0.2.3 + +// Add configured nodes to /p listing + +// Version 1.0.2.4 + +// Fix RMS (it wanted B2 not B12) +// Send messages if available after rejecting all proposals +// Dont try to send msg back to originator. + +// Version 1.0.2.5 + +// Fix timeband processing when none specified. +// Improved Chat Help display. +// Add helpful responses to /n /q and /t + +// Version 1.0.2.6 + +// Kill Personal WP messages after processing +// Make sure a node doesnt try to "join" or "leave" a node as a user. +// More tracing to try to track down lost topic links. +// Add command recall to Console +// Show users in new topic when changing topic +// Add Send From Clipboard" Action + +// Version 1.0.2.7 + +// Hold messages from the future, or with invalid dates. +// Add KH (kill held) command. +// Send Message to SYSOP when a new user connects. + +// Version 1.0.2.8 + +// Don't reject personal message on Dup BID unless we already have an unforwarded copy. +// Hold Looping messages. +// Warn SYSOP of held messages. + +// Version 1.0.2.9 + +// Close connecton on receipt of *** DONE (MBL style forwarding). +// Improved validation in link_drop (Chat Node) +// Change to welcome prompt and Msg Header for Outpost. +// Fix Connect Script processing for KA Nodes + +// Version 1.0.3.1 + +// Fix incorrect sending of NO - BID. +// Fix problems caused by a user being connected to more than one chat node. +// Show idle time on Chat /u display. +// Rewrite forwarding by HA. +// Add "Bad Words" Test. +// Add reason for holding to SYSOP "Message Held" Message. +// Make topics case-insensitive. +// Allow SR for smtp mail. +// Try to fix some user's "Add User" problem. + + +// Version 1.0.3.2 + +// Fix program error when prcessing - response in FBB forwarding. +// Fix code to flag messages as sent. + + +// Version 1.0.3.3 + +// Attempt to fix message loop on topic_change +// Fix loop if compressed size is greater than 32K when receiving with B1 protocol. +// Fix selection of B1 + +// Version 1.0.3.4 + +// Add "KISS ONLY" Flag to R: Lines (Needs Node Version 4.10.12 (4.10l) or above) +// Add Basic NNTP Interface +// Fix possible loop in lzhuf encode + +// Version 1.0.3.5 + +// Fix forwarding of Held Messages +// More attempts to fix Chat crashes. +// Limit join/leave problem with mismatched nodes. +// Add Chat Node Monitoring System. +// Change order of elements in nntp addresses (now to.at, was at.to) + +// Version 1.0.3.6 + +// Restart and Exit if too many errors +// Fix forwarding of killed messages. +// Fix Forwarding to PaKet. +// Fix problem if BBS signon contains words from the "Fail" list + +// Version 1.0.3.7 + +// re-fix loop if compressed size is greater than 32K - reintroduced in 1.0.3.4 +// Add last message to edit users +// Change Console and Monitor Buffer sizes +// Don't flag msg as 'Y' on read if it was Held or Killed + +// Version 1.0.3.8 + +// Don't connect if all messages for a BBS are held. +// Hold message if From or To are missing. +// Fix parsing of /n and /q commands +// fix possible loop on changing name or qth + +// Version 1.0.3.9 + +// More Chat fixes and monitoring +// Added additional console for chat + +// Version 1.0.3.10 + +// Fix for corruption of CIrcuit-Node chain. + +// Version 1.0.3.11 + +// Fix flow control for SMTP and NNTP + +// Version 1.0.3.12 + +// Fix crash in SendChatStatus if no Chat Links Defined. +// Disable Chat Mode if there is no ApplCall for ChatApplNum, +// Add Edit Message to Manage Messages Dialog +// NNTP needs authentication + + +// Version 1.0.3.13 + +// Fix Chat ApplCall warning when ChatAppl = 0 +// Add NNTP NEWGROUPS Command +// Fix MBL Forwarding (remove extra > prompt after SP) + +// Version 1.0.3.14 + +// Fix topic switch code. +// Send SYSOP messages on POP3 interface if User SYSOP flag is set. +// NNTP only needs Authentication for posting, not reading. + +// Version 1.0.3.15 + +// Fix reset of First to Forward after househeeping + +// Version 1.0.3.16 + +// Fix check of HA for terminating WW +// MBL Mode remove extra > prompts +// Fix program error if WP record has unexpected format +// Connect Script changes for WINMOR +// Fix typo in unconfigured node has connected message + +// Version 1.0.3.17 + +// Fix forwarding of Personals + +// Version 1.0.3.18 + +// Fix detection of misconfigured nodes to work with new nodes. +// Limit connection attempt rate when a chat node is unavailable. +// Fix Program Error on long input lines (> ~250 chars). + +// Version 1.0.3.19 + +// Fix Restart of B2 mode transfers. +// Fix error if other end offers B1 and you are configured for B2 only. + + +// Version 1.0.3.20 + +// Fix Paging in Chat Mode. +// Report Node Versions. + +// Version 1.0.3.21 + +// Check node is not already known when processing OK +// Add option to suppress emailing of housekeeping results + +// Version 1.0.3.22 + +// Correct Version processing when user connects via the network +// Add time controlled forwarding scripts + +// Version 1.0.3.23 + +// Changes to RMS forwarding + +// Version 1.0.3.24 + +// Fix RMS: from SMTP interface +// Accept RMS/ instead of RMS: for Thunderbird + +// Version 1.0.3.25 + +// Accept smtp: addresses from smtp client, and route to ISP gateway. +// Set FROM address of messages from RMS that are delivered to smtp client so a reply will go back via RMS. + +// Version 1.0.3.26 + +// Improve display of rms and smtp messages in message lists and message display. + +// Version 1.0.3.27 + +// Correct code that prevents mail being retured to originating BBS. +// Tidy stuck Nodes and Topics when all links close +// Fix B2 handling of @ to TO Address. + +// Version 1.0.3.28 + +// Ensure user Record for the BBS Call has BBS bit set. +// Don't send messages addressed @winlink.org if addressee is a local user with Poll RMS set. +// Add user configurable welcome messages. + +// Version 1.0.3.29 + +// Add AUTH feature to Rig Control + +// Version 1.0.3.30 + +// Process Paclink Header (;FW:) + +// Version 1.0.3.31 + +// Process Messages with attachments. +// Add inactivity timeout to Chat Console sessions. + +// Version 1.0.3.32 + +// Fix for Paclink > BBS Addresses + +// Version 1.0.3.33 + +// Fix multiple transfers per session for B2. +// Kill messages eent to paclink. +// Add option to forward messages on arrival. + +// Version 1.0.3.34 + +// Fix bbs addresses to winlink. +// Fix adding @winlink.org to imcoming paclink msgs + +// Version 1.0.3.35 + +// Fix bbs addresses to winlink. (Again) + +// Version 1.0.3.36 + +// Restart changes for RMS/paclink + +// Version 1.0.3.37 + +// Fix for RMS Express forwarding + +// Version 1.0.3.38 + +// Fixes for smtp and lower case packet addresses from Airmail +// Fix missing > afer NO - Bid in MBL mode + +// Version 1.0.3.39 + +// Use ;FW: for RMS polling. + +// Version 1.0.3.40 + +// Add ELSE Option to connect scripts. + +// Version 1.0.3.41 + +// Improved handling of Multiple Addresses +// Add user colours to chat. + +// Version 1.0.3.42 + +// Poll multiple SSID's for RMS +// Colour support for BPQTEerminal +// New /C chat command to toggle colour on or off. + +// Version 1.0.3.43 + +// Add SKIPPROMPT command to forward scripts + +// Version 1.0.4.1 + +// Non - Beta Release +// Fix possible crash/corruption with long B2 messages + +// Version 1.0.4.2 + +// Add @winlink.org to the B2 From addresss if it is just a callsign +// Route Flood Bulls on TO as well as @ + +// Version 1.0.4.3 + +// Handle Packet Addresses from RMS Express +// Fix for Housekeeping B$ messages + +// Version 1.0.4.4 + +// Remove B2 header and all but the Body part from messages forwared using MBL +// Fix handling of ;FW: from RMS Express + +// Version 1.0.4.5 + +// Disable Paging on forwarding sessions. +// Kill Msgs sent to RMS Exxpress +// Add Name to Chat *** Joined msg + +// Version 1.0.4.6 + +// Pass smtp:winlink.org messages from Airmail to local user check +// Only apply local user check to RMS: messages @winlink.org +// Check locally input smtp: messages for local winlink.org users +// Provide facility to allow only one connect on a port + +// Version 1.0.4.8 + +// Only reset last listed on L or LR commands. + +// Version 1.0.4.9 + +// Fix error in handling smtp: messages to winlink.org addresses from Airmail + +// Version 1.0.4.10 + +// Fix Badwords processing +// Add Connect Script PAUSE command + +// Version 1.0.4.11 + +// Suppress display and listing of held messages +// Add option to exclude SYSOP messages from LM, KM, etc +// Fix crash whan receiving messages with long lines via plain text forwarding + +// Version 1.0.4.12 Jul 2010 + +// Route P messages on AT +// Allow Applications above 8 + +// Version 1.0.4.13 Aug 2010 + +// Fix TidyString for addresses of form John Wiseman +// Add Try/Except around socket routines + +// Version 1.0.4.14 Aug 2010 + +// Trap "Error - TNC Not Ready" in forward script response +// Fix restart after program error +// Add INFO command +// Add SYSOP-configurable HELP Text. + +// Version 1.0.4.15 Aug 2010 + +// Semaphore Connect/Disconnect +// Semaphore RemoveTempBIDS + +// Version 1.0.4.16 Aug 2010 + +// Remove prompt after receiving unrecognised line in MBL mode. (for MSYS) + +// Version 1.0.4.17 Aug 2010 + +// Fix receiving multiple messages in FBB Uncompressed Mode +// Try to trap phantom chat node connections +// Add delay to close + + +// Version 1.0.4.18 Aug 2010 + +// Add "Send SYSTEM messages to SYSOP Call" Option +// set fwd bit on local winlink.org msgs if user is a BBS +// add winlink.org to from address of messages from WL2K that don't already have an @ + +// Version 1.0.4.19 Sept 2010 + +// Build a B2 From: address if possible, so RMS Express can reply to packet messages. +// Fix handling of addresses from WL2K with SSID's +// L@ now only matches up to length of input string. +// Remove "Type H for help" from login prompt. + +// Version 1.0.4.20 Sept 2010 + +// Process FBB 'E' response +// Handle FROM addresses with an @BBS +// Fix FROM addresses with @ on end. +// Extend delay before close after sending FQ on winmor/pactor sessions. + +// Version 1.0.4.21 Sept 2010 + +// Fix handling B2 From: with an HA +// Add "Expert User" welcome message. + +// Version 1.0.4.22 Sept 2010 + +// Version 1.0.4.23 Oct 2010 + +// Add Dup message supression +// Dont change B2 from if going to RMS + +// Version 1.0.4.24 Oct 2010 + +// Add "Save Registry Config" command +// Add forwarding on wildcarded TO for NTS +// Add option to force text mode forwarding +// Define new users as a temporaty BBS if SID received in reply to Name prompt +// Reduce delay before sending close after sending FQ on pactor sessions +// Fix processing of MIME boundary from GMail + +// Send /ex instead of ctrl/z for text mode forwarding +// Send [WL2K-BPQ... SID if user flagged as RMS Express +// Fix Chat Map reporting when more than one AXIP port +// Add Message State D for NTS Messages +// Forward messages in priority order - T, P, B +// Add Reject and Hold Filters +// Fix holding messages to local RMS users when received as part of a multiple addressee message + +// Version 1.0.4.25 Nov 2010 + +// Renumbered for release +// Add option to save Registry Config during Housekeeping + +// Version 1.0.4.26 Nov 2010 + +// Fix F> loop when doing MBL forwarding between BPQ BBSes +// Allow multiple To: addresses, separated by ; +// Allow Houskeeping Lifetime Overrides to apply to Unsent Messages. +// Set Unforwarded Bulls to status '$' +// Accept MARS and USA as continent codes for MARS Packet Addresses +// Add option to send Non-delivery notifications. + +// Version 1.0.4.27 Dec 2010 + +// Add MSGTYPES fwd file option + +// Version 1.0.4.28 Dec 2010 + +// Renumbered to for release + +// Version 1.0.4.30 Dec 2010 + +// Fix rescan requeuing where bull was rejected by a BBS +// Fiz flagging bulls received by NNTP with $ if they need to be forwarded. +// Add Chat Keepalive option. +// Fix bug in non-delivery notification. + +// Version 1.0.4.32 Jan 2011 + +// Allow "Send from Clipboard" to send to rms: or smtp: +// Allow messages received via SMTP to be bulls (TO preceeded by bull/) or NTS (to nnnnn@NTSXX or nnnnn@NTSXX.NTS) +// Fix corruption of messages converted to B2 if body contains binary data +// Fix occasional program error when forwarding B2 messages +// Limit FBB protocol data blocks to 250 to try to fix restart problem. +// Add F2 to F5 to open windows. + +// Version 1.0.4.33 Jan 2011 + +// Fix holding old bulls with forwarding info. + +// Version 1.0.4.33 Jan 2011 + +// Prevent transfer restarting after a program error. +// Allow Housekeeping to kill held messages. + +// Version 1.0.4.35 Jan 2011 + +// Add Size limits for P and T messages to MSGTYPES command +// Fix Error in MBL processing when blank lines received (introduced in .33) +// Trap possible PE in Send_MON_Datagram +// Don't use paging on chat sessions + +// Version 1.0.4.36 Jan 2011 + +// Fix error after handling first FBB block. +// Add $X and $x welcome message options. + +// Version 1.0.4.37 Jan 2011 + +// Change L command not to list the last message if no new ones are available +// Add LC I I@ IH IZ commands +// Add option to send warning to sysop if forwarded P or T message has nowhere to go +// Fixes for Winpack Compressed Download +// Fix Houskeeping when "Apply Overrides to Unsent Bulls" is set. +// Add console copy/paste. +// Add "No Bulls" Option. +// Add "Mail For" Beacon. +// Tidied up Tab order in config dialogs to help text-to-speech programs. +// Limit MaxMsgno to 99000. + +// Version 1.0.4.38 Feb 2011 + +// Renumbered for release + +// Version 1.0.4.40 April 2011 + +// Add POLLRMS command + +// Changes for Vista/Win7 (registry key change) +// Workaround for changes to RMS Express +// Fix AUTH bug in SMTP server +// Add filter to Edit Messages dialog + +// Version 1.0.4.41 April 2011 + +// Extend B2 proposals to other BPQMail systems so Reject Filter will work. +// Add Edit User Command +// Use internal Registry Save routine instead of Regedit +// Fix Start Forward/All +// Allow Winpack Compressed Upload/Download if PMS flag set (as well as BBS flag) +// Add FWD SYSOP command +// Fix security on POLLRMS command +// Add AUTH command +// Leave selection in same place after Delete User +// Combine SMTP server messages to multiple WL2K addresses into one message to WL2k +// Add option to show name as well as call on Chat messages +// Fix program error if you try to define more than 80 BBS's + +// Version 1.0.4.45 October 2011 + +// Changes to program error reporting. +// BBS "Returh to Node" command added +// Move config to "Standard" location (BPQ Directory/BPQMailChat) . +// Fix crash if "Edit Message" clicked with no message selected. + +// Version 1.0.4.46 October 2011 + +// Fix BaseDir test when BaseDir ends with \ or / +// Fix long BaseDir values (>50 chars) + +// Version 1.4.47.1 January 2012 + +// Call CloseBPQ32 on exit +// Add option to flash window instead of sounding bell on Chat Connects +// Add ShowRMS SYSOP command +// Update WP with I records from R: lines +// Send WP Updates +// Fix Paclen on Pactor-like sessions +// Fix SID and Prompt when RMS Express User is set +// Try to stop loop in Program Error/Restarting code +// Trap "UNABLE TO CONNECT" response in connect script +// Add facility to print messages or save them to a text file + +// Version 1.4.48.1 January 2012 + +// Add Send Message (as well as Send from Clipboard) +// Fix Email From: Address when forwaring using B2 +// Send WP from BBSCALL not SYSOPCALL +// Send Chat Map reports via BPQ32.dll + + +// Version 1.4.49.1 February 2012 + + +// Fix Setting Paclink mode on SNOS connects +// Remove creation of debugging file for each message +// Add Message Export and Import functions +// All printing of more than one message at a time +// Add command to toggle "Expert" status + +// Version 1.4.50.1 February 2012 + +// Fix forwarding to RMS Express users +// Route messages received via B2 to an Internet email address to RMS +// Add Reverse Poll interval +// Add full FROM address to POP3 messages +// Include HOMEBBS command in Help + + +// Version 1.4.51.1 June 2012 + +// Allow bulls to be sent from RMS Express. +// Handle BASE64 and Quoted-printable encoding of single part messages +// Work round for RMS Express "All proposals rejected" Bug. + +// Version 1.4.52.1 August 2012 + +// Fix size limit on B2 To List when sending to multiple dests +// Fix initialisation of DIRMES.SYS control record +// Allow use of Tracker and UZ7HO ports for UI messages + +// Version 1.4.53.1 September 2012 + +// Fix crash if R: line with out a CR found. + +// Version 1.4.54.1 ?? 2012 + +// Add configurable prompts +// Fix KISS-Only Test +// Send EHLO instead of HELO when Authentication is needed on SMTP session +// Add option to use local tome for bbs forwarding config +// Allow comment lines (; or @) or single space in fwd scripts +// Fix loss of forwarding info if SAVE is clicked before selecting a call + +// Version 1.4.55.1 June 2013 + +// Add option to remove users that have not connected for a long time. +// Add l@ smtp: +// Fix From: sent to POP3 Client when meaages is from RMS +// Display Email From on Manage Messages + +// Version 1.4.56.1 July 2013 + +// Add timeout +// Verify prompts +// Add IDLETIME command + + + +// Version 1.4.57.1 + +// Change default IDLETIME +// Fix display of BBS's in Web "Manage Messages" +// Add separate househeeping lifetines for T messages +// Don't change flag on forwarded or delivered messages if they sre subsequently read +// Speed up processing, mainly to stop RMS Express timing out when connecting via Telnet +// Don't append winlink.org to RMS Express or Paclink addresses if RMS is not configured +// Fix receiving NTS messages via B2 +// Add option to send "Mail For", but not FBB Headers +// Fix corruption caused with Subject longer than 60 bytes reveived from Winlink systems +// Fix Endian bug in FBB Compression code + + +// Version 1.4.58.1 + +// Change control of appending winlink.org to RMS Express or Paclink addresses to a user flag +// Lookup HomeBBS and WP for calls without a via received from RMS Express or Paclink +// Treat call@bpq as request to look up address in Home BBS/WP for messages received from RMS Express or Paclink +// Collect stats by message type +// Fix Non-Delivery notifications to SMTP messages +// Add Message Type Stats to BBS Trafic Report +// Add "Batch forward to email" +// Add EXPORT command +// Allow more BBS records +// Allow lower case connect scripts +// Fix POP3 LIST command +// Fix MIME Multipart Alternate with first part Base64 or Quoted Printable encoding +// Fix duplicates of SP SYSOP@WW Messages +// Add command line option (tidymail) to delete redundant Mail files +// Add command line option (nohomebbs) to suppress HomeBBS prompt + +// 59 April 2014 + +// Add FLARQ Mail Mode +// Fix possible crash saving restart data +// Add script command ADDLF for connect scripts over Telnet +// Add recogniton of URONODE connected message +// Add option to stop Name prompt +// Add new RMS Express users with "RMS Express User" flag set +// Validate HTML Pages +// Add NTS swap file +// Add basic File list and read functions +// Fix Traffic report + +// 60 + +// Fix security hole in readfile + +// 61 August 2014 +// Set Messages to NTS:nnnnn@NTSXX to type 'T' and remove NTS +// Dont treat "Attempting downlink" as a failure +// Add option to read messages during a list +// Fix crash during message renumber on MAC +// Timeout response to SID to try to avoid hang on an incomplete connection. +// Save config in file instead of registry +// Fix Manage Messages "EXPORT" option and check filename on EXPORT command +// Fix reverse forward prompt in MBL mode. +// Fix From address in POP3 messages where path is @winlink.org +// Fix possible program error in T message procesing +// Add MaxAge param (for incoming Bulls) + + +//62 November 2014 +// Add ZIP and Permit Bulls flag to Manage Users +// Allow users to kill their own B and anyone to kill T messages +// Improve saving of "Last Listed" +// Fix LL when paging +// Send Date received in R: Line (should fix B2 message restarts) +// Fix occasional crash in terminal part line processing +// Add "SKIPCON" forwarding command to handle nodes that include "Connected" in their CTEXT +// Fix possible retry loop when message is deferred (FBB '=' response); +// Don't remove Attachments from received bulls. + +//63 Feb 2015 + +// Fix creating Bulls from RMS Express messages. +// Fix PE if message with no To: received. +// Fix setting "RMS Express User" flag on new connects from RMS Express +// Fix deleting 'T' messages downloaded by RMS Express +// Include MPS messages in count of messages to forward. +// Add new Welcome Message variable $F for messages to forward +// Fix setting Type in B2 header when usong NTS: or BULL: +// Remove trailing spaces from BID when Creating Message from Clipboard. +// Improved handling of FBB B1/B2 Restarts. + +//64 September 2015 + +// Fix Message Type in msgs from RMS Express to Internet +// Reopen Monitor window if open when program list closed +// Only apply NTS alias file to NTS Messages +// Fix failure to store some encrypted ISP passwords +// Allow EDITUSER to change "RMS Express User" flag +// Fix reporting of Config File errors +// Fix Finding MPS Messages (First to Forward was being used incorrectly) +// Add "Save Attachment" to Web Mgmt Interface +// Support Secure Signon on Forwarding sessions to CMS +// Save Forwarding config when BBS flag on user is cleared +// Pass internally generated SYSOP messages through routing process +// Add POP3 TOP command. +// Don't set 'T' messages to 'Y' when read. +// Add optional temporary connect script on "FWD NOW" command +// Add automatic import facility +// Accept RMS mail to BBS Call even if "Poll RMS" not set. + +// 65 November 2015 + +// Fix loading Housekeeping value for forwarded bulls. +// Fix re-using Fwd script override in timer driven forwarding. +// Add ampr.org handling +// Add "Dont forward" match on TO address for NTS +// Allow listing a combinatiom of state and type, such as LNT or LPF +// Fix handling ISP messages from gmail without a '+' +// Add basic WebMail support + +// 66 + +// Autoimport messages as Dummy Call, not SYSOP Call +// Add "My Messages" display option to WebMail +// Create .csv extract of User List during hourekeeping. +// Fix processing of NTS Alising of @ Addresses +// Don't reroute Delivered NTS Messages +// Add option to stop users killing T messages +// Add multicast Receive +// Fix initialising new message database format field +// Fix "Forward Messages to BBS Call" option. +// Add Filter WP Bulls option and allow multiple WP "TO" addresses +// Fix deleting P WP messages for other stations +// Fix saving blank lines in forwarding config +// Fix paging on L@ and l< +// Fix removing DELETE from IMPORT XXX DELETE and allow multiple IMPORT lines in script +// Run DeleteRedundantMessages before renumbering messages +// Connect script now tries ELSE lines if prompt not received from remote BBS +// Send connecting call instead of BBS Name when connecting to CMS server. +// Add BID filter to Manage Messages +// Fix handling of over long suject lines in IMPORT +// Allow comments before ELSE in connect script +// Add Copy and Clear to Multicast Window +// Fix possible duplicate messages with MBL forwarding +// Set "Permit EMail" on IMPORT dummy User. +// Fix repeated running of housekeeping if clock is stepped forward. +// Fix corruption of CMS Pass field by Web interface +// Kill B2 WP bulls if FilterWPBulls set +// Include Message Type in BPQ B2 proposal extensions + +// 6.0.14.1 July 2017 + +// Fix corruption of BBSNumber if RMS Ex User and BBS both checked +// Tread B messages without an AT as Flood. +// Make sure Message headers are always saved to disk when a message status changes +// Reject message instead of failing session if TO address too long in FBB forwarding +// Fix error when FBB restart data exactly fills a packet. +// Fix possible generation of msg number zero in send nondlivery notification +// Fix problem with Web "Manage Messages" when stray message number zero appears +// Fix Crash in AMPR forward when host missing from VIA +// Fix possible addition of an spurious password entry to the ;FW: line when connecting to CMS +// Fix test for Status "D" in forward check. +// Don't cancel AUTH on SMTP RSET +// Fix "nowhere to go" message on some messages sent to smtp addresses +// Add @ from Home BBS or WP is not spcified in "Send from Clipboard" + +// 6.0.15.1 Feb 2018 + +// Fix PE if Filename missing from FILE connect script command +// Suppress reporting errors after receiving FQ +// Fix problem caused by trailing spaces on callsign in WP database +// Support mixed case WINLINK Passwords + +// 6.0.16.1 March 2018 + +// Make sure messages sent to WL2K don;'t have @ on from: address +// If message to saildocs add R: line as an X header instead of to body +// Close session if more than 4 Invalid Commmad responses sent +// Report TOP in POP3 CAPA list. Allows POP3 to work with Windows Mail client + +// 6.0.17.1 November 2018 + +// Add source routing using ! eg sp g8bpq@winlink.org!gm8bpq to send via RMS on gm8bpq +// Accept an internet email address without rms: or smtp: +// Fix "Forward messages for BBS Call" when TO isn't BBS Call +// Accept NNTP commands in either case +// Add NNTP BODY command +// Timeout POP or SMTP TCP connections that are open too long +// Add YAPP support +// Fix connect script when Node CTEXT contains "} BBS " +// Fix handling null H Route +// Detect and correct duplicate BBS Numbers +// Fix problem if BBS requests FBB blocked forwarding without compression (ie SID of F without B) +// Fix crash if YAPP entered without filenmame and send BBS prompt after YAPP error messages +// Add support for Winlink HTML Forms to WebMail interface +// Update B2 header when using NTS alias file with B2 messages + +// 6.0.18.1 January 2019 + +// Ensure callsigns in WP database are upper case. +// Various fixes for Webmail +// Fix sending direct to ampr.org addresses +// Use SYSOP Call as default for Webmail if set +// Preparations for 64 bit version + + +// 6.0.19.1 September 2019 + +// Trap missing HTML reply Template or HTML files +// Fix case problems in HTML Templates +// Fix setting To call on reply to HTML messages +// More preparations for 64 bit including saving WP info as a text file. +// Set "RMS Express User" when a new user connects using PAT +// Increace maximum length on Forwarding Alias string in Web interface +// Expand multiaddress messages from Winlink Express if "Don't add @Winlink.org" set or no RMS BBS +// Fix program error if READ used without a filename +// Trap reject messages from Winlink CMS +// Fix "delete to recycle bin" on Linux +// Handle Radio Only Messages (-T or -R suffix on calling station) +// Fix program error on saving empty Alias list on Web Forwarding page +// Add REQDIR and REQFIL +// Experimental Blocked Uncompressed forwarding +// Security fix for YAPP +// Fix WebMail Cancel Send Message +// Fix processing Hold Message response from Winlink Express + +// 6.0.20.1 April 2020 + +// Improvments to YAPP +// Add Copy forwarding config +// Add Next and Previous buttons to Webmail message read screen +// Move HTML templates from HTMLPages to inline code. +// Fix Paclen on YAPP send +// Fix bug in handling "RMS Express User" +// Fix WINPACK compressed forwarding +// Add option to send P messages to more than one BBS +// Add "Default to Don't Add WINLINK.ORG" Config option +// Re-read Badwords.sys during Housekeeping +// Add BID Hold and Reject Filters +// On SMTP Send try HELO if EHLO rejected +// Allow SID response timeout to be configured per BBS +// Fix sending bulls with PAT +// Set "Forward Messages to BBS Call" when routing Bulls on TO +// Add option to send Mail For Message to APRS +// Fix WP update +// Fix Holding messages from Webmail Interface +// Add RMR command +// Add REROUTEMSGS BBS SYSOP command +// Disable null passwords and check Exclude flag in Webmail Signin +// Add basic Webmail logging + +// 6.0.21.1 December 2020 + +// Remove nulls from displayed messages. +// Fix Holding messages from SMTP and POP3 Interfaces +// Various fixes for handling messages to/from Internet email addresses +// Fix saving Email From field in Manage Messages +// Fix sending WL2K traffic reports via TriMode. +// Fix removing successive CR from Webmail Message display +// Fix Wildcarded @ forwarding +// Fix message type when receiving NTS Msgs form Airmail +// Fix address on SERVICE messages from Winlink +// Add multiple TO processing to Webmail non-template messages +// Don't backup config file if reading it fails +// Include Port and Freq on Connected log record +// Make sure welcome mesages don't end in > +// Allow flagging unread T messages as Delivered +// Replace \ with # in forward script so commands starting with # can be sent +// Fix forwarding NTS on TO field +// Fix possible crash in text mode forwarding +// Allow decimals of days in P message lifetimes and allow Houskeeping interval to be configured +// Add DOHOUSEKEEPING sysop command +// Add MARS continent code +// Try to trap 'zombie' BBS Sessions +// On Linux if "Delete to Recycle Bin" is set move deleted messages and logs to directory Deleted under current directory. +// Fix corruption of message length when reading R2 message via Read command +// Fix paging on List command and add new combinations of List options +// Fix NNTP list and LC command when bulls are killed + +// 6.0.22.1 August 2021 + +// Fix flagging messages with attachments as read. +// Fix possible corruption of WP database and subsequent crash on reloading. +// Fix format of Web Manage Messages display +// Include SETNEXTMESSAGENUMBER in SYSOP Help Message +// Fix occasional "Incoming Connect from SWITCH" +// Fix L> with numeric dests +// Improved diagnostic for MailTCP select() error. +// Clear "RMS Express User" if user is changed to a BBS +// Fix saving Window positions on exit +// Fix parsing ReplyTemplate name in Webmail +// Handle multiple addressees for WebMail Forms messages to packet stations +// Add option to allow only known users to connect +// Add basic callsign validation to From address +// Add option to forward a user's messages to Winlink +// Move User config to main config file. +// Update message status whne reading a Forms Webmail message +// Speed up killing multiple messages +// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command + +// 6.0.23.1 June 2022 + +// Fix crash when ; added to call in send commands +// Allow smtp/ override for messages from RMS Express to send via ISP gateway +// Send Internet email from RMS Express to ISP Gateway if enabled and RMS BBS not configured +// Recompiled for Web Interface changes in Node +// Add RMS Relay SYNC Mode (.17) +// Add Protocol changes for Relay RO forwarding +// Add SendWL2KPM command to connect script to allow users other than RMS to send ;FW: string to RMS Relay +// Fix B2 Header Date in Webmail message with sttachments. +// Fix bug when using YAPP with VARA (.27) +// Allow SendWL2KFW as well as the (incorrect)SendWL2KPM command +// Add mechsnism to send bbs log records to qttermtcp. (32) +// Add MFJ forwarding Mode (No @BBS on send) +// Fix handling CR/LF split over packet boundaries +// Add Header and Footers for Webmail Send (42) +// Fix Maintenance Interval in LinBPQ (53) +// Add RMS: to valid from addresses (.56) +// Fix Web management on Android deviced (.58) +// Disconnect immediately if "Invalid Command" "*** Protocol Error" or "Already Connected" received (.70) +// Check Badword and Reject filters before processing WP Messages + +// 6.0.24.1 ?? 2022 + +// Fix ' in Webmail subject (8) +// Change web buttons to white on black when pressed (10) +// Add auto-refresh option to Webmail index page (25) +// Fix displaying help and info files with crlf line endings on Linux (28) +// Improve validation of extended FC message (32) +// Improve WP check for SYSTEM as a callsign (33) +// Improvements to RMS Relay SYNC mode (47) +// Fix BID Hold and Reject filters +// Fix Webmail auto-refresh when page exceeds 64K bytes (54) +// Fix Webmail send when using both headers/footers and attachmonts (55) +// Fix R: line corruption on some 64 bit builds +// Dont drop empty lines inm TEXTFORWARDING (61) +// Dont wait for body prompt for TEXTFORWARDING for SID [PMS-3.2-C$] (62) +// Add forwarding mode SETCALLTOSENDER for PMS Systems that don't accept < in SP (63) +// QtTerm Monitoring fixed for 63 port version of BPQ (69) + + +#include "bpqmail.h" +#include "winstdint.h" +#define MAIL +#include "Versions.h" + +#include "GetVersion.h" + +#define MAX_LOADSTRING 100 + +typedef int (WINAPI FAR *FARPROCX)(); +typedef int (WINAPI FAR *FARPROCZ)(); + +FARPROCX pDllBPQTRACE; +FARPROCZ pGetLOC; +FARPROCX pRefreshWebMailIndex; +FARPROCX pRunEventProgram; + +BOOL WINE = FALSE; + +INT_PTR CALLBACK UserEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK MsgEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK FwdEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK WPEditDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +VOID SetupNTSAliases(char * FN); + +HKEY REGTREE = HKEY_LOCAL_MACHINE; // Default +char * REGTREETEXT = "HKEY_LOCAL_MACHINE"; + +// Global Variables: +HINSTANCE hInst; // current instance +TCHAR szTitle[MAX_LOADSTRING]; // The title bar text +TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name + +extern int LastVer[4]; // In case we need to do somthing the first time a version is run + +UINT BPQMsg; + +HWND MainWnd; +HWND hWndSess; +RECT MainRect; +HMENU hActionMenu; +static HMENU hMenu; +HMENU hDisMenu; // Disconnect Menu Handle +HMENU hFWDMenu; // Forward Menu Handle + +int SessX, SessY, SessWidth; // Params for Session Window + +char szBuff[80]; + +#define MaxSockets 64 + +int _MYTIMEZONE = 0; + +ConnectionInfo Connections[MaxSockets+1]; + +//struct SEM AllocSemaphore = {0, 0}; +//struct SEM ConSemaphore = {0, 0}; +//struct SEM OutputSEM = {0, 0}; + +//struct UserInfo ** UserRecPtr=NULL; +//int NumberofUsers=0; + +//struct UserInfo * BBSChain = NULL; // Chain of users that are BBSes + +//struct MsgInfo ** MsgHddrPtr=NULL; +//int NumberofMessages=0; + +//int FirstMessageIndextoForward=0; // Lowest Message wirh a forward bit set - limits search + +//BIDRec ** BIDRecPtr=NULL; +//int NumberofBIDs=0; + +extern BIDRec ** TempBIDRecPtr; +//int NumberofTempBIDs=0; + +//WPRec ** WPRecPtr=NULL; +//int NumberofWPrecs=0; + +extern char ** BadWords; +//int NumberofBadWords=0; +extern char * BadFile; + +//int LatestMsg = 0; +//struct SEM MsgNoSemaphore = {0, 0}; // For locking updates to LatestMsg +//int HighestBBSNumber = 0; + +//int MaxMsgno = 60000; +//int BidLifetime = 60; +//int MaintInterval = 24; +//int MaintTime = 0; +//int UserLifetime = 0; + + +BOOL cfgMinToTray; + +BOOL DisconnectOnClose; + +extern char PasswordMsg[100]; + +char cfgHOSTPROMPT[100]; + +char cfgCTEXT[100]; + +char cfgLOCALECHO[100]; + +char AttemptsMsg[]; +char disMsg[]; + +char LoginMsg[]; + +char BlankCall[]; + + +ULONG BBSApplMask; +ULONG ChatApplMask; + +int BBSApplNum; + +//int StartStream=0; +int NumberofStreams; +int MaxStreams; + +extern char BBSSID[]; +extern char ChatSID[]; + +extern char NewUserPrompt[100]; + +extern char * WelcomeMsg; +extern char * NewWelcomeMsg; +extern char * ExpertWelcomeMsg; + +extern char * Prompt; +extern char * NewPrompt; +extern char * ExpertPrompt; + +extern BOOL DontNeedHomeBBS; + +char BBSName[100]; +char MailForText[100]; + +char SignoffMsg[100]; + +char AbortedMsg[100]; + +extern char UserDatabaseName[MAX_PATH]; +extern char UserDatabasePath[MAX_PATH]; + +extern char MsgDatabasePath[MAX_PATH]; +extern char MsgDatabaseName[MAX_PATH]; + +extern char BIDDatabasePath[MAX_PATH]; +extern char BIDDatabaseName[MAX_PATH]; + +extern char WPDatabasePath[MAX_PATH]; +extern char WPDatabaseName[MAX_PATH]; + +extern char BadWordsPath[MAX_PATH]; +extern char BadWordsName[MAX_PATH]; + +char NTSAliasesPath[MAX_PATH]; +extern char NTSAliasesName[MAX_PATH]; + +char BaseDir[MAX_PATH]; +char BaseDirRaw[MAX_PATH]; // As set in registry - may contain %NAME% + +char MailDir[MAX_PATH]; + +char RlineVer[50]; + +extern BOOL KISSOnly; + +extern BOOL OpenMon; + +extern struct ALIAS ** NTSAliases; + +extern int EnableUI; +extern int RefuseBulls; +extern int SendSYStoSYSOPCall; +extern int SendBBStoSYSOPCall; +extern int DontHoldNewUsers; +extern int ForwardToMe; + +extern int MailForInterval; + +char zeros[NBMASK]; // For forward bitmask tests + +time_t MaintClock; // Time to run housekeeping + +struct MsgInfo * MsgnotoMsg[100000]; // Message Number to Message Slot List. + +// Filter Params + +char ** RejFrom; // Reject on FROM Call +char ** RejTo; // Reject on TO Call +char ** RejAt; // Reject on AT Call +char ** RejBID; // Reject on BID + +char ** HoldFrom; // Hold on FROM Call +char ** HoldTo; // Hold on TO Call +char ** HoldAt; // Hold on AT Call +char ** HoldBID; // Hold on BID + + +// Send WP Params + +BOOL SendWP; +char SendWPVIA[81]; +char SendWPTO[11]; +int SendWPType; + + +int ProgramErrors = 0; + +UCHAR BPQDirectory[260] = ""; + + +// Forward declarations of functions included in this code module: +ATOM MyRegisterClass(HINSTANCE hInstance); +ATOM RegisterMainWindowClass(HINSTANCE hInstance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK ChatMapDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +unsigned long _beginthread( void( *start_address )(VOID * DParam), + unsigned stack_size, VOID * DParam); + +VOID SendMailForThread(VOID * Param); +BOOL CreatePipeThread(); +int DeleteRedundantMessages(); +VOID BBSSlowTimer(); +VOID CopyConfigFile(char * ConfigName); +BOOL CreateMulticastConsole(); +char * CheckToAddress(CIRCUIT * conn, char * Addr); +BOOL CheckifPacket(char * Via); +int GetHTMLForms(); + +struct _EXCEPTION_POINTERS exinfox; + +CONTEXT ContextRecord; +EXCEPTION_RECORD ExceptionRecord; + +DWORD Stack[16]; + +BOOL Restarting = FALSE; + +Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg) +{ + unsigned int SPPtr; + unsigned int SPVal; + + memcpy(&ContextRecord, exinfo->ContextRecord, sizeof(ContextRecord)); + memcpy(&ExceptionRecord, exinfo->ExceptionRecord, sizeof(ExceptionRecord)); + + SPPtr = ContextRecord.Esp; + + Debugprintf("BPQMail *** Program Error %x at %x in %s", + ExceptionRecord.ExceptionCode, ExceptionRecord.ExceptionAddress, Msg); + + + __asm{ + + mov eax, SPPtr + mov SPVal,eax + lea edi,Stack + mov esi,eax + mov ecx,64 + rep movsb + + } + + Debugprintf("EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x ESP %x", + ContextRecord.Eax, ContextRecord.Ebx, ContextRecord.Ecx, + ContextRecord.Edx, ContextRecord.Esi, ContextRecord.Edi, SPVal); + + Debugprintf("Stack:"); + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + SPVal, Stack[0], Stack[1], Stack[2], Stack[3], Stack[4], Stack[5], Stack[6], Stack[7]); + + Debugprintf("%08x %08x %08x %08x %08x %08x %08x %08x %08x ", + SPVal+32, Stack[8], Stack[9], Stack[10], Stack[11], Stack[12], Stack[13], Stack[14], Stack[15]); + +} + + + +void myInvalidParameterHandler(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) +{ + Logprintf(LOG_DEBUG_X, NULL, '!', "*** Error **** C Run Time Invalid Parameter Handler Called"); + + if (expression && function && file) + { + Logprintf(LOG_DEBUG_X, NULL, '!', "Expression = %S", expression); + Logprintf(LOG_DEBUG_X, NULL, '!', "Function %S", function); + Logprintf(LOG_DEBUG_X, NULL, '!', "File %S Line %d", file, line); + } +} + +// If program gets too many program errors, it will restart itself and shut down + +VOID CheckProgramErrors() +{ + STARTUPINFO SInfo; // pointer to STARTUPINFO + PROCESS_INFORMATION PInfo; // pointer to PROCESS_INFORMATION + char ProgName[256]; + + if (Restarting) + exit(0); // Make sure can't loop in restarting + + ProgramErrors++; + + if (ProgramErrors > 25) + { + Restarting = TRUE; + + Logprintf(LOG_DEBUG_X, NULL, '!', "Too Many Program Errors - Closing"); + + if (cfgMinToTray) + { + DeleteTrayMenuItem(MainWnd); + if (ConsHeader[0]->hConsole) + DeleteTrayMenuItem(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DeleteTrayMenuItem(ConsHeader[1]->hConsole); + if (hMonitor) + DeleteTrayMenuItem(hMonitor); + } + + SInfo.cb=sizeof(SInfo); + SInfo.lpReserved=NULL; + SInfo.lpDesktop=NULL; + SInfo.lpTitle=NULL; + SInfo.dwFlags=0; + SInfo.cbReserved2=0; + SInfo.lpReserved2=NULL; + + GetModuleFileName(NULL, ProgName, 256); + + Debugprintf("Attempting to Restart %s", ProgName); + + CreateProcess(ProgName, "MailChat.exe WAIT", NULL, NULL, FALSE, 0, NULL, NULL, &SInfo, &PInfo); + + exit(0); + } +} + + +VOID WriteMiniDump() +{ +#ifdef WIN32 + + HANDLE hFile; + BOOL ret; + char FN[256]; + + sprintf(FN, "%s/Logs/MiniDump%x.dmp", GetBPQDirectory(), time(NULL)); + + hFile = CreateFile(FN, GENERIC_READ | GENERIC_WRITE, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + // Create the minidump + + ret = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + hFile, MiniDumpNormal, 0, 0, 0 ); + + if(!ret) + Debugprintf("MiniDumpWriteDump failed. Error: %u", GetLastError()); + else + Debugprintf("Minidump %s created.", FN); + CloseHandle(hFile); + } +#endif +} + + +void GetSemaphore(struct SEM * Semaphore, int ID) +{ + // + // Wait for it to be free + // +#ifdef WIN32 + if (Semaphore->Flag != 0) + { + Semaphore->Clashes++; + } +loop1: + + while (Semaphore->Flag != 0) + { + Sleep(10); + } + + // + // try to get semaphore + // + + _asm{ + + mov eax,1 + mov ebx, Semaphore + xchg [ebx],eax // this instruction is locked + + cmp eax,0 + jne loop1 // someone else got it - try again +; +; ok, weve got the semaphore +; + } +#else + + while (Semaphore->Flag) + usleep(10000); + + Semaphore->Flag = 1; + +#endif + return; +} + +void FreeSemaphore(struct SEM * Semaphore) +{ + Semaphore->Flag = 0; + + return; +} + +char * CmdLine; + +extern int configSaved; + +int APIENTRY WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, + int nCmdShow) +{ + MSG msg; + HACCEL hAccelTable; + int BPQStream, n; + struct UserInfo * user; + struct _EXCEPTION_POINTERS exinfo; + _invalid_parameter_handler oldHandler, newHandler; + char Msg[100]; + int i = 60; + struct NNTPRec * NNTPREC; + struct NNTPRec * SaveNNTPREC; + + CmdLine = _strdup(lpCmdLine); + _strlwr(CmdLine); + + if (_stricmp(lpCmdLine, "Wait") == 0) // If AutoRestart then Delay 60 Secs + { + hWnd = CreateWindow("STATIC", "Mail Restarting after Failure - Please Wait", 0, + CW_USEDEFAULT, 100, 550, 70, + NULL, NULL, hInstance, NULL); + + ShowWindow(hWnd, nCmdShow); + + while (i-- > 0) + { + sprintf(Msg, "Mail Restarting after Failure - Please Wait %d secs.", i); + SetWindowText(hWnd, Msg); + + Sleep(1000); + } + + DestroyWindow(hWnd); + } + + __try { + + // Trap CRT Errors + + newHandler = myInvalidParameterHandler; + oldHandler = _set_invalid_parameter_handler(newHandler); + + // Initialize global strings + LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadString(hInstance, IDC_BPQMailChat, szWindowClass, MAX_LOADSTRING); + MyRegisterClass(hInstance); + + // Perform application initialization: + + if (!InitInstance (hInstance, nCmdShow)) + { + return FALSE; + } + + hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BPQMailChat)); + + // Main message loop: + + Logprintf(LOG_DEBUG_X, NULL, '!', "Program Starting"); + Logprintf(LOG_BBS, NULL, '!', "BPQMail Starting"); + Debugprintf("BPQMail Starting"); + + if (pDllBPQTRACE == 0) + Logprintf(LOG_BBS, NULL, '!', "Remote Monitor Log not available - update BPQ32.dll to enable"); + + + } My__except_Routine("Init"); + + while (GetMessage(&msg, NULL, 0, 0)) + { + __try + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + #define EXCEPTMSG "GetMessageLoop" + #include "StdExcept.c" + + CheckProgramErrors(); + } + } + + __try + { + for (n = 0; n < NumberofStreams; n++) + { + BPQStream=Connections[n].BPQStream; + + if (BPQStream) + { + SetAppl(BPQStream, 0, 0); + Disconnect(BPQStream); + DeallocateStream(BPQStream); + } + } + + + hWnd = CreateWindow("STATIC", "Mail Closing - Please Wait", 0, + 150, 200, 350, 40, NULL, NULL, hInstance, NULL); + + ShowWindow(hWnd, nCmdShow); + + Sleep(1000); // A bit of time for links to close + + DestroyWindow(hWnd); + + if (ConsHeader[0]->hConsole) + DestroyWindow(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DestroyWindow(ConsHeader[1]->hConsole); + if (hMonitor) + { + DestroyWindow(hMonitor); + hMonitor = (HWND)1; // For status Save + } + + +// SaveUserDatabase(); + SaveMessageDatabase(); + SaveBIDDatabase(); + + configSaved = 1; + SaveConfig(ConfigName); + + if (cfgMinToTray) + { + DeleteTrayMenuItem(MainWnd); + if (ConsHeader[0]->hConsole) + DeleteTrayMenuItem(ConsHeader[0]->hConsole); + if (ConsHeader[1]->hConsole) + DeleteTrayMenuItem(ConsHeader[1]->hConsole); + if (hMonitor) + DeleteTrayMenuItem(hMonitor); + } + + // Free all allocated memory + + for (n = 0; n <= NumberofUsers; n++) + { + user = UserRecPtr[n]; + + if (user->ForwardingInfo) + { + FreeForwardingStruct(user); + free(user->ForwardingInfo); + } + + free(user->Temp); + + free(user); + } + + free(UserRecPtr); + + for (n = 0; n <= NumberofMessages; n++) + free(MsgHddrPtr[n]); + + free(MsgHddrPtr); + + for (n = 0; n <= NumberofWPrecs; n++) + free(WPRecPtr[n]); + + free(WPRecPtr); + + for (n = 0; n <= NumberofBIDs; n++) + free(BIDRecPtr[n]); + + free(BIDRecPtr); + + if (TempBIDRecPtr) + free(TempBIDRecPtr); + + NNTPREC = FirstNNTPRec; + + while (NNTPREC) + { + SaveNNTPREC = NNTPREC->Next; + free(NNTPREC); + NNTPREC = SaveNNTPREC; + } + + if (BadWords) free(BadWords); + if (BadFile) free(BadFile); + + n = 0; + + if (Aliases) + { + while(Aliases[n]) + { + free(Aliases[n]->Dest); + free(Aliases[n]); + n++; + } + + free(Aliases); + FreeList(AliasText); + } + + n = 0; + + if (NTSAliases) + { + while(NTSAliases[n]) + { + free(NTSAliases[n]->Dest); + free(NTSAliases[n]); + n++; + } + + free(NTSAliases); + } + + FreeOverrides(); + + FreeList(RejFrom); + FreeList(RejTo); + FreeList(RejAt); + FreeList(RejBID); + FreeList(HoldFrom); + FreeList(HoldTo); + FreeList(HoldAt); + FreeList(HoldBID); + FreeList(SendWPAddrs); + + Free_UI(); + + for (n=1; n<20; n++) + { + if (MyElements[n]) free(MyElements[n]); + } + + free(WelcomeMsg); + free(NewWelcomeMsg); + free(ExpertWelcomeMsg); + + free(Prompt); + free(NewPrompt); + free(ExpertPrompt); + + FreeWebMailMallocs(); + + free(CmdLine); + + _CrtDumpMemoryLeaks(); + + } + My__except_Routine("Close Processing"); + + CloseBPQ32(); // Close Ext Drivers if last bpq32 process + + return (int) msg.wParam; +} + + + +// +// FUNCTION: MyRegisterClass() +// +// PURPOSE: Registers the window class. +// +// COMMENTS: +// +// This function and its usage are only necessary if you want this code +// to be compatible with Win32 systems prior to the 'RegisterClassEx' +// function that was added to Windows 95. It is important to call this function +// so that the application will get 'well formed' small icons associated +// with it. +// +// +#define BGCOLOUR RGB(236,233,216) +//#define BGCOLOUR RGB(245,245,245) + +HBRUSH bgBrush; + +ATOM MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + + bgBrush = CreateSolidBrush(BGCOLOUR); + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = DLGWINDOWEXTRA; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(BPQICON)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = bgBrush; + wcex.lpszMenuName = MAKEINTRESOURCE(IDC_BPQMailChat); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(BPQICON)); + + return RegisterClassEx(&wcex); +} + + +// +// FUNCTION: InitInstance(HINSTANCE, int) +// +// PURPOSE: Saves instance handle and creates main window +// +// COMMENTS: +// +// In this function, we save the instance handle in a global variable and +// create and display the main program window. +// + +HWND hWnd; + +int AXIPPort = 0; + +char LOC[7] = ""; + +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + char Title[80]; + WSADATA WsaData; + HMENU hTopMenu; // handle of menu + HKEY hKey=0; + int retCode; + RECT InitRect; + RECT SessRect; + struct _EXCEPTION_POINTERS exinfo; + + HMODULE ExtDriver = LoadLibrary("bpq32.dll"); + + if (ExtDriver) + { + pDllBPQTRACE = GetProcAddress(ExtDriver,"_DllBPQTRACE@8"); + pGetLOC = GetProcAddress(ExtDriver,"_GetLOC@0"); + pRefreshWebMailIndex = GetProcAddress(ExtDriver,"_RefreshWebMailIndex@0"); + pRunEventProgram = GetProcAddress(ExtDriver,"_RunEventProgram@8"); + + if (pGetLOC) + { + char * pLOC = (char *)pGetLOC(); + memcpy(LOC, pLOC, 6); + } + } + + // See if running under WINE + + retCode = RegOpenKeyEx (HKEY_LOCAL_MACHINE, "SOFTWARE\\Wine", 0, KEY_QUERY_VALUE, &hKey); + + if (retCode == ERROR_SUCCESS) + { + RegCloseKey(hKey); + WINE =TRUE; + Debugprintf("Running under WINE"); + } + + + REGTREE = GetRegistryKey(); + REGTREETEXT = GetRegistryKeyText(); + + Sleep(1000); + + { + int n; + struct _EXTPORTDATA * PORTVEC; + + KISSOnly = TRUE; + + for (n=1; n <= GetNumberofPorts(); n++) + { + PORTVEC = (struct _EXTPORTDATA * )GetPortTableEntryFromSlot(n); + + if (PORTVEC->PORTCONTROL.PORTTYPE == 16) // EXTERNAL + { + if (_memicmp(PORTVEC->PORT_DLL_NAME, "TELNET", 6) == 0) + KISSOnly = FALSE; + + if (PORTVEC->PORTCONTROL.PROTOCOL != 10) // Pactor/WINMOR + KISSOnly = FALSE; + + if (AXIPPort == 0) + { + if (_memicmp(PORTVEC->PORT_DLL_NAME, "BPQAXIP", 7) == 0) + { + AXIPPort = PORTVEC->PORTCONTROL.PORTNUMBER; + KISSOnly = FALSE; + } + } + } + } + } + + hInst = hInstance; + + hWnd=CreateDialog(hInst,szWindowClass,0,NULL); + + if (!hWnd) + { + return FALSE; + } + + MainWnd = hWnd; + + GetVersionInfo(NULL); + + sprintf(Title,"G8BPQ Mail Server Version %s", VersionString); + + sprintf(RlineVer, "BPQ%s%d.%d.%d", (KISSOnly) ? "K" : "", Ver[0], Ver[1], Ver[2]); + + SetWindowText(hWnd,Title); + + hWndSess = GetDlgItem(hWnd, 100); + + GetWindowRect(hWnd, &InitRect); + GetWindowRect(hWndSess, &SessRect); + + SessX = SessRect.left - InitRect.left ; + SessY = SessRect.top -InitRect.top; + SessWidth = SessRect.right - SessRect.left; + + // Get handles for updating menu items + + hTopMenu=GetMenu(MainWnd); + hActionMenu=GetSubMenu(hTopMenu,0); + + hFWDMenu=GetSubMenu(hActionMenu,0); + hMenu=GetSubMenu(hActionMenu,1); + hDisMenu=GetSubMenu(hActionMenu,2); + + CheckTimer(); + + cfgMinToTray = GetMinimizetoTrayFlag(); + + if ((nCmdShow == SW_SHOWMINIMIZED) || (nCmdShow == SW_SHOWMINNOACTIVE)) + if (cfgMinToTray) + { + ShowWindow(hWnd, SW_HIDE); + } + else + { + ShowWindow(hWnd, nCmdShow); + } + else + ShowWindow(hWnd, nCmdShow); + + UpdateWindow(hWnd); + + WSAStartup(MAKEWORD(2, 0), &WsaData); + + __try { + + return Initialise(); + + }My__except_Routine("Initialise"); + + return FALSE; +} + +// +// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) +// +// PURPOSE: Processes messages for the main window. +// +// + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + PAINTSTRUCT ps; + HDC hdc; + int state,change; + ConnectionInfo * conn; + struct _EXCEPTION_POINTERS exinfo; + + + if (message == BPQMsg) + { + if (lParam & BPQMonitorAvail) + { + __try + { + DoBBSMonitorData(wParam); + } + My__except_Routine("DoMonitorData"); + + return 0; + + } + if (lParam & BPQDataAvail) + { + // Dont trap error at this level - let Node error handler pick it up +// __try +// { + DoReceivedData(wParam); +// } +// My__except_Routine("DoReceivedData") + return 0; + } + if (lParam & BPQStateChange) + { + // Get current Session State. Any state changed is ACK'ed + // automatically. See BPQHOST functions 4 and 5. + + __try + { + SessionState(wParam, &state, &change); + + if (change == 1) + { + if (state == 1) // Connected + { + GetSemaphore(&ConSemaphore, 0); + __try {Connected(wParam);} + My__except_Routine("Connected"); + FreeSemaphore(&ConSemaphore); + } + else + { + GetSemaphore(&ConSemaphore, 0); + __try{Disconnected(wParam);} + My__except_Routine("Disconnected"); + FreeSemaphore(&ConSemaphore); + } + } + } + My__except_Routine("DoStateChange"); + + } + + return 0; + } + + + switch (message) + { + + case WM_KEYUP: + + switch (wParam) + { + case VK_F2: + CreateConsole(-1); + return 0; + + case VK_F3: + CreateMulticastConsole(); + return 0; + + case VK_F4: + CreateMonitor(); + return 0; + + case VK_TAB: + return TRUE; + + break; + + + + } + return 0; + + case WM_TIMER: + + if (wParam == 1) // Slow = 10 secs + { + __try + { + time_t NOW = time(NULL); + struct tm * tm; + RefreshMainWindow(); + CheckTimer(); + TCPTimer(); + BBSSlowTimer(); + FWDTimerProc(); + if (MaintClock < NOW) + { + while (MaintClock < NOW) // in case large time step + MaintClock += MaintInterval * 3600; + + Debugprintf("|Enter HouseKeeping"); + DoHouseKeeping(FALSE); + } + tm = gmtime(&NOW); + + if (tm->tm_wday == 0) // Sunday + { + if (GenerateTrafficReport && (LastTrafficTime + 86400) < NOW) + { + CreateBBSTrafficReport(); + LastTrafficTime = NOW; + } + } + } + My__except_Routine("Slow Timer"); + } + else + __try + { + TrytoSend(); + TCPFastTimer(); + } + My__except_Routine("TrytoSend"); + + return (0); + + + case WM_CTLCOLORDLG: + return (LONG)bgBrush; + + case WM_CTLCOLORSTATIC: + { + HDC hdcStatic = (HDC)wParam; + SetTextColor(hdcStatic, RGB(0, 0, 0)); + SetBkMode(hdcStatic, TRANSPARENT); + return (LONG)bgBrush; + } + + case WM_INITMENUPOPUP: + + if (wParam == (WPARAM)hActionMenu) + { + if (IsClipboardFormatAvailable(CF_TEXT)) + EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_ENABLED); + else + EnableMenuItem(hActionMenu,ID_ACTIONS_SENDMSGFROMCLIPBOARD, MF_BYCOMMAND | MF_GRAYED ); + + return TRUE; + } + + if (wParam == (WPARAM)hFWDMenu) + { + // Set up Forward Menu + + struct UserInfo * user; + char MenuLine[30]; + + for (user = BBSChain; user; user = user->BBSNext) + { + sprintf(MenuLine, "%s %d Msgs", user->Call, CountMessagestoForward(user)); + + if (ModifyMenu(hFWDMenu, IDM_FORWARD_ALL + user->BBSNumber, + MF_BYCOMMAND | MF_STRING, IDM_FORWARD_ALL + user->BBSNumber, MenuLine) == 0) + + AppendMenu(hFWDMenu, MF_STRING,IDM_FORWARD_ALL + user->BBSNumber, MenuLine); + } + return TRUE; + } + + if (wParam == (WPARAM)hDisMenu) + { + // Set up Disconnect Menu + + CIRCUIT * conn; + char MenuLine[30]; + int n; + + for (n = 0; n <= NumberofStreams-1; n++) + { + conn=&Connections[n]; + + RemoveMenu(hDisMenu, IDM_DISCONNECT + n, MF_BYCOMMAND); + + if (conn->Active) + { + sprintf_s(MenuLine, 30, "%d %s", conn->BPQStream, conn->Callsign); + AppendMenu(hDisMenu, MF_STRING, IDM_DISCONNECT + n, MenuLine); + } + } + return TRUE; + } + break; + + + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + // Parse the menu selections: + + if (wmEvent == LBN_DBLCLK) + + break; + + if (wmId >= IDM_DISCONNECT && wmId < IDM_DISCONNECT+MaxSockets+1) + { + // disconnect user + + conn=&Connections[wmId-IDM_DISCONNECT]; + + if (conn->Active) + { + Disconnect(conn->BPQStream); + } + } + + if (wmId >= IDM_FORWARD_ALL && wmId < IDM_FORWARD_ALL + 100) + { + StartForwarding(wmId - IDM_FORWARD_ALL, NULL); + return 0; + } + + switch (wmId) + { + case IDM_LOGBBS: + + ToggleParam(hMenu, hWnd, &LogBBS, IDM_LOGBBS); + break; + + case IDM_LOGCHAT: + + ToggleParam(hMenu, hWnd, &LogCHAT, IDM_LOGCHAT); + break; + + case IDM_LOGTCP: + + ToggleParam(hMenu, hWnd, &LogTCP, IDM_LOGTCP); + break; + + case IDM_HOUSEKEEPING: + + DoHouseKeeping(TRUE); + + break; + + case IDM_CONSOLE: + + CreateConsole(-1); + break; + + case IDM_MCMONITOR: + + CreateMulticastConsole(); + break; + + case IDM_MONITOR: + + CreateMonitor(); + break; + + case RESCANMSGS: + + ReRouteMessages(); + break; + + case IDM_IMPORT: + + ImportMessages(NULL, "", FALSE); + break; + + case IDM_ABOUT: + DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); + break; + + case ID_HELP_ONLINEHELP: + + ShellExecute(hWnd,"open", + "http://www.cantab.net/users/john.wiseman/Documents/MailServer.html", + "", NULL, SW_SHOWNORMAL); + + break; + + case IDM_CONFIG: + DialogBox(hInst, MAKEINTRESOURCE(IDD_CONFIG), hWnd, ConfigWndProc); + break; + + case IDM_USERS: + DialogBox(hInst, MAKEINTRESOURCE(IDD_USEREDIT), hWnd, UserEditDialogProc); + break; + + case IDM_FWD: + DialogBox(hInst, MAKEINTRESOURCE(IDD_FORWARDING), hWnd, FwdEditDialogProc); + break; + + case IDM_MESSAGES: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGEDIT), hWnd, MsgEditDialogProc); + break; + + case IDM_WP: + DialogBox(hInst, MAKEINTRESOURCE(IDD_EDITWP), hWnd, WPEditDialogProc); + break; + + case ID_ACTIONS_SENDMSGFROMCLIPBOARD: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, ClpMsgDialogProc); + break; + + case ID_ACTIONS_SENDMESSAGE: + DialogBox(hInst, MAKEINTRESOURCE(IDD_MSGFROMCLIPBOARD), hWnd, SendMsgDialogProc); + break; + + case ID_MULTICAST: + + MulticastRX = !MulticastRX; + CheckMenuItem(hActionMenu, ID_MULTICAST, (MulticastRX) ? MF_CHECKED : MF_UNCHECKED); + break; + + case IDM_EXIT: + DestroyWindow(hWnd); + break; + + + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + break; + + case WM_SIZE: + + if (wParam == SIZE_MINIMIZED) + if (cfgMinToTray) + return ShowWindow(hWnd, SW_HIDE); + + return (0); + + + case WM_SIZING: + { + LPRECT lprc = (LPRECT) lParam; + int Height = lprc->bottom-lprc->top; + int Width = lprc->right-lprc->left; + + MoveWindow(hWndSess, 0, 30, SessWidth, Height - 100, TRUE); + + return TRUE; + } + + + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + // TODO: Add any drawing code here... + EndPaint(hWnd, &ps); + break; + + case WM_DESTROY: + + GetWindowRect(MainWnd, &MainRect); // For save soutine + if (ConsHeader[0]->hConsole) + GetWindowRect(ConsHeader[0]->hConsole, &ConsHeader[0]->ConsoleRect); // For save soutine + if (ConsHeader[1]->hConsole) + GetWindowRect(ConsHeader[1]->hConsole, &ConsHeader[1]->ConsoleRect); // For save soutine + if (hMonitor) + GetWindowRect(hMonitor, &MonitorRect); // For save soutine + + KillTimer(hWnd,1); + KillTimer(hWnd,2); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +INT_PTR CALLBACK SendMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "B"); + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "P"); + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) "T"); + + SendDlgItemMessage(hDlg, IDC_MSGTYPE, CB_SETCURSEL, 0, 0); + + return TRUE; + + case WM_SIZING: + { + HWND hWndEdit = GetDlgItem(hDlg, IDC_EDIT1); + + LPRECT lprc = (LPRECT) lParam; + int Height = lprc->bottom-lprc->top; + int Width = lprc->right-lprc->left; + + MoveWindow(hWndEdit, 5, 90, Width-20, Height - 140, TRUE); + + return TRUE; + } + + case WM_COMMAND: + + if (LOWORD(wParam) == IDSEND) + { + char status [3]; + struct MsgInfo * Msg; + char * via = NULL; + char BID[13]; + char FileList[32768]; + BIDRec * BIDRec; + int MsgLen; + char * MailBuffer; + char MsgFile[MAX_PATH]; + HANDLE hFile = INVALID_HANDLE_VALUE; + int WriteLen=0; + char HDest[61]; + char Destcopy[61]; + char * Vptr; + char * FileName[100]; + int FileLen[100]; + char * FileBody[100]; + int n, Files = 0; + int TotalFileSize = 0; + char * NewMsg; + + GetDlgItemText(hDlg, IDC_MSGTO, HDest, 60); + strcpy(Destcopy, HDest); + + GetDlgItemText(hDlg, IDC_MSGBID, BID, 13); + strlop(BID, ' '); + + GetDlgItemText(hDlg, IDC_ATTACHMENTS, FileList, 32767); + + // if there are attachments, check that they can be opened ane read + + n = 0; + + if (FileList[0]) + { + FILE * Handle; + struct stat STAT; + char * ptr1 = FileList, * ptr2; + + while(ptr1 && ptr1[0]) + { + ptr2 = strchr(ptr1, ';'); + + if (ptr2) + *(ptr2++) = 0; + + FileName[n++] = ptr1; + + ptr1 = ptr2; + } + + FileName[n] = 0; + + // read the files + + Files = n; + n = 0; + + while (FileName[n]) + { + if (stat(FileName[n], &STAT) == -1) + { + char ErrorMessage[512]; + sprintf(ErrorMessage,"Can't find file %s", FileName[n]); + MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); + return TRUE; + } + + FileLen[n] = STAT.st_size; + + Handle = fopen(FileName[n], "rb"); + + if (Handle == NULL) + { + char ErrorMessage[512]; + sprintf(ErrorMessage,"Can't open file %s", FileName[n]); + MessageBox(NULL, ErrorMessage, "BPQMail", MB_ICONERROR); + return TRUE; + } + + FileBody[n] = malloc(FileLen[n]+1); + + fread(FileBody[n], 1, FileLen[n], Handle); + + fclose(Handle); + + TotalFileSize += FileLen[n]; + n++; + } + } + + if (strlen(HDest) == 0) + { + MessageBox(NULL, "To: Call Missing!", "BPQMail", MB_ICONERROR); + return TRUE; + } + + if (strlen(BID)) + { + if (LookupBID(BID)) + { + // Duplicate bid + + MessageBox(NULL, "Duplicate BID", "BPQMail", MB_ICONERROR); + return TRUE; + } + } + + Msg = AllocateMsgRecord(); + + // Set number here so they remain in sequence + + Msg->number = ++LatestMsg; + MsgnotoMsg[Msg->number] = Msg; + + strcpy(Msg->from, SYSOPCall); + + Vptr = strlop(Destcopy, '@'); + + if (Vptr == 0 && strchr(Destcopy, '!')) // Bang route without @ + { + Vptr = strchr(Destcopy, '!'); + strcpy(Msg->via, Vptr); + strlop(Destcopy, '!'); + + if (strlen(Destcopy) > 6) + memcpy(Msg->to, Destcopy, 6); + else + strcpy(Msg->to, Destcopy); + goto gotAddr; + } + + if (strlen(Destcopy) > 6) + memcpy(Msg->to, Destcopy, 6); + else + strcpy(Msg->to, Destcopy); + + _strupr(Msg->to); + + if (_memicmp(HDest, "rms:", 4) == 0 || _memicmp(HDest, "rms/", 4) == 0) + { + Vptr = HDest; + memmove(HDest, &HDest[4], strlen(HDest)); + strcpy(Msg->to, "RMS"); + + } + else if (_memicmp(HDest, "smtp:", 5) == 0) + { + if (ISP_Gateway_Enabled) + { + Vptr = HDest; + memmove(HDest, &HDest[5], strlen(HDest)); + Msg->to[0] = 0; + } + } + else if (Vptr) + { + // If looks like a valid email address, treat as such + + int tolen = (Vptr - Destcopy) - 1; + + if (tolen > 6 || !CheckifPacket(Vptr)) + { + // Assume Email address + + Vptr = HDest; + + if (FindRMS() || strchr(Vptr, '!')) // have RMS or source route + strcpy(Msg->to, "RMS"); + else if (ISP_Gateway_Enabled) + Msg->to[0] = 0; + else + { + MessageBox(NULL, "Sending to Internet Email not available", "BPQMail", MB_ICONERROR); + return TRUE; + } + } + } + if (Vptr) + { + if (strlen(Vptr) > 40) + Vptr[40] = 0; + + strcpy(Msg->via, Vptr); + } +gotAddr: + GetDlgItemText(hDlg, IDC_MSGTITLE, Msg->title, 61); + GetDlgItemText(hDlg, IDC_MSGTYPE, status, 2); + Msg->type = status[0]; + Msg->status = 'N'; + + if (strlen(BID) == 0) + sprintf_s(BID, sizeof(BID), "%d_%s", LatestMsg, BBSName); + + strcpy(Msg->bid, BID); + + Msg->datereceived = Msg->datechanged = Msg->datecreated = time(NULL); + + BIDRec = AllocateBIDRecord(); + + strcpy(BIDRec->BID, Msg->bid); + BIDRec->mode = Msg->type; + BIDRec->u.msgno = LOWORD(Msg->number); + BIDRec->u.timestamp = LOWORD(time(NULL)/86400); + + MsgLen = SendDlgItemMessage(hDlg, IDC_EDIT1, WM_GETTEXTLENGTH, 0 ,0); + + MailBuffer = malloc(MsgLen + TotalFileSize + 2000); // Allow for a B2 Header if attachments + + if (Files) + { + char DateString[80]; + struct tm * tm; + + char Type[16] = "Private"; + + // Get Type + + if (Msg->type == 'B') + strcpy(Type, "Bulletin"); + else if (Msg->type == 'T') + strcpy(Type, "Traffic"); + + // Create a B2 Message + + // B2 Header + + NewMsg = MailBuffer + 1000; + + tm = gmtime((time_t *)&Msg->datecreated); + + sprintf(DateString, "%04d/%02d/%02d %02d:%02d", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + + // Remove last Source Route + + if (strchr(HDest, '!')) + { + char * bang = HDest + strlen(HDest); + + while (*(--bang) != '!'); // Find last ! + + *(bang) = 0; // remove it; + } + + NewMsg += sprintf(NewMsg, + "MID: %s\r\nDate: %s\r\nType: %s\r\nFrom: %s\r\nTo: %s\r\nSubject: %s\r\nMbo: %s\r\n", + Msg->bid, DateString, Type, Msg->from, HDest, Msg->title, BBSName); + + + NewMsg += sprintf(NewMsg, "Body: %d\r\n", MsgLen); + + for (n = 0; n < Files; n++) + { + char * p = FileName[n], * q; + + // Remove any path + + q = strchr(p, '\\'); + + while (q) + { + if (q) + *q++ = 0; + p = q; + q = strchr(p, '\\'); + } + + NewMsg += sprintf(NewMsg, "File: %d %s\r\n", FileLen[n], p); + } + + NewMsg += sprintf(NewMsg, "\r\n"); + GetDlgItemText(hDlg, IDC_EDIT1, NewMsg, MsgLen+1); + NewMsg += MsgLen; + NewMsg += sprintf(NewMsg, "\r\n"); + + for (n = 0; n < Files; n++) + { + memcpy(NewMsg, FileBody[n], FileLen[n]); + NewMsg += FileLen[n]; + free(FileBody[n]); + NewMsg += sprintf(NewMsg, "\r\n"); + } + + Msg->length = NewMsg - (MailBuffer + 1000); + NewMsg = MailBuffer + 1000; + Msg->B2Flags = B2Msg | Attachments; + } + + else + { + GetDlgItemText(hDlg, IDC_EDIT1, MailBuffer, MsgLen+1); + Msg->length = MsgLen; + NewMsg = MailBuffer; + } + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number); + + hFile = CreateFile(MsgFile, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + WriteFile(hFile, NewMsg, Msg->length, &WriteLen, NULL); + CloseHandle(hFile); + } + + free(MailBuffer); + + MatchMessagetoBBSList(Msg, 0); + + BuildNNTPList(Msg); // Build NNTP Groups list + + SaveMessageDatabase(); + SaveBIDDatabase(); + + EndDialog(hDlg, LOWORD(wParam)); + + return TRUE; + } + + + if (LOWORD(wParam) == IDSelectFiles) + { + char FileNames[2048]; + char FullFileNames[32768]; + OPENFILENAME Ofn; + int err; + + FileNames[0] = 0; + + memset(&Ofn, 0, sizeof(Ofn)); + + Ofn.lStructSize = sizeof(OPENFILENAME); + Ofn.hInstance = hInst; + Ofn.hwndOwner = hDlg; + Ofn.lpstrFilter = NULL; + Ofn.lpstrFile= FileNames; + Ofn.nMaxFile = 2048; + Ofn.lpstrFileTitle = NULL; + Ofn.nMaxFileTitle = 0; + Ofn.lpstrInitialDir = (LPSTR)NULL; + Ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER; + Ofn.lpstrTitle = NULL;//; + + if (GetOpenFileName(&Ofn)) + { + // if one is selected, a single string is returned, if more than one, a single + // path, followed by all the strings, duuble null terminated. + + char * Names[101]; // Allow up to 100 names + int n = 0; + char * ptr = FileNames; + + while (*ptr) + { + Names[n++] = ptr; + ptr += strlen(ptr); + ptr++; + } + + GetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames, 32768); + + if (strlen(FullFileNames)) + strcat(FullFileNames, ";"); + + if (n == 1) + { + // Single Select + + strcat(FullFileNames, FileNames); + } + else + { + int i = 1; + + while(i < n) + { + strcat(FullFileNames, Names[0]); + strcat(FullFileNames, "\\"); + strcat(FullFileNames, Names[i]); + i++; + if (i < n) + strcat(FullFileNames, ";"); + } + } + SetDlgItemText(hDlg, IDC_ATTACHMENTS, FullFileNames); + } + else + err = GetLastError(); + return (INT_PTR)TRUE; + } + + + if (LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + + return (INT_PTR)TRUE; + + break; + } + return (INT_PTR)FALSE; +} + +INT_PTR CALLBACK ClpMsgDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + HGLOBAL hglb; + LPTSTR lptstr; + + switch (message) + { + case WM_INITDIALOG: + + SetWindowText(hDlg, "Send Message from Clipboard"); + + if (!IsClipboardFormatAvailable(CF_TEXT)) + break; + + if (!OpenClipboard(hDlg)) + break; + + hglb = GetClipboardData(CF_TEXT); + + if (hglb != NULL) + { + lptstr = GlobalLock(hglb); + + if (lptstr != NULL) + { + SetDlgItemText(hDlg, IDC_EDIT1, lptstr); + GlobalUnlock(hglb); + } + } + CloseClipboard(); + } + + return SendMsgDialogProc(hDlg, message, wParam, lParam); + +} + +// Message handler for about box. +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + return (INT_PTR)TRUE; + + break; + } + return (INT_PTR)FALSE; +} + +SMTPMsgs = 0; + +int RefreshMainWindow() +{ + char msg[80]; + CIRCUIT * conn; + int i,n, SYSOPMsgs = 0, HeldMsgs = 0; + time_t now; + struct tm * tm; + char tim[20]; + + SendDlgItemMessage(MainWnd,100,LB_RESETCONTENT,0,0); + + SMTPMsgs = 0; + + for (n = 0; n < NumberofStreams; n++) + { + conn=&Connections[n]; + + if (!conn->Active) + { + strcpy(msg,"Idle"); + } + else + { + { + if (conn->UserPointer == 0) + strcpy(msg,"Logging in"); + else + { + i=sprintf_s(msg, sizeof(msg), "%-10s %-10s %2d %-10s%5d", + conn->UserPointer->Name, conn->UserPointer->Call, conn->BPQStream, + "BBS", conn->OutputQueueLength - conn->OutputGetPointer); + } + } + } + SendDlgItemMessage(MainWnd,100,LB_ADDSTRING,0,(LPARAM)msg); + } + + SetDlgItemInt(hWnd, IDC_MSGS, NumberofMessages, FALSE); + + n = 0; + + for (i=1; i <= NumberofMessages; i++) + { + if (MsgHddrPtr[i]->status == 'N') + { + if (_stricmp(MsgHddrPtr[i]->to, SYSOPCall) == 0 || _stricmp(MsgHddrPtr[i]->to, "SYSOP") == 0) + SYSOPMsgs++; + else + if (MsgHddrPtr[i]->to[0] == 0) + SMTPMsgs++; + } + else + { + if (MsgHddrPtr[i]->status == 'H') + HeldMsgs++; + } + } + + SetDlgItemInt(hWnd, IDC_SYSOPMSGS, SYSOPMsgs, FALSE); + SetDlgItemInt(hWnd, IDC_HELD, HeldMsgs, FALSE); + SetDlgItemInt(hWnd, IDC_SMTP, SMTPMsgs, FALSE); + + SetDlgItemInt(hWnd, IDC_MSGSEM, MsgNoSemaphore.Clashes, FALSE); + SetDlgItemInt(hWnd, IDC_ALLOCSEM, AllocSemaphore.Clashes, FALSE); + SetDlgItemInt(hWnd, IDC_CONSEM, ConSemaphore.Clashes, FALSE); + + now = time(NULL); + + tm = gmtime(&now); + sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); + SetDlgItemText(hWnd, IDC_UTC, tim); + + tm = localtime(&now); + sprintf_s(tim, sizeof(tim), "%02d:%02d", tm->tm_hour, tm->tm_min); + SetDlgItemText(hWnd, IDC_LOCAL, tim); + + + return 0; +} + +#define MAX_PENDING_CONNECTS 4 + +#define VERSION_MAJOR 2 +#define VERSION_MINOR 0 + +SOCKADDR_IN local_sin; /* Local socket - internet style */ + +PSOCKADDR_IN psin; + +SOCKET sock; + + + +BOOL Initialise() +{ + int i, len; + ConnectionInfo * conn; + struct UserInfo * user = NULL; + HKEY hKey=0; + char * ptr1; + int Attrs, ret; + char msg[500]; + TIME_ZONE_INFORMATION TimeZoneInformation; + struct stat STAT; + + GetTimeZoneInformation(&TimeZoneInformation); + + _tzset(); + _MYTIMEZONE = timezone; + _MYTIMEZONE = TimeZoneInformation.Bias * 60; + + // Register message for posting by BPQDLL + + BPQMsg = RegisterWindowMessage(BPQWinMsg); + + // See if we need to warn of possible problem with BaseDir moved by installer + + strcpy(BPQDirectory, GetBPQDirectory()); + + sprintf(BaseDir, "%s/BPQMailChat", BPQDirectory); + + len = strlen(BaseDir); + ptr1 = BaseDir; + + while (*ptr1) + { + if (*(ptr1) == '/') *(ptr1) = '\\'; + ptr1++; + } + + // Make Sure BASEDIR Exists + + Attrs = GetFileAttributes(BaseDir); + + if (Attrs == -1) + { + sprintf_s(msg, sizeof(msg), "Base Directory %s not found - should it be created?", BaseDir); + ret = MessageBox(NULL, msg, "BPQMail", MB_YESNO); + + if (ret == IDYES) + { + ret = CreateDirectory(BaseDir, NULL); + if (ret == 0) + { + MessageBox(NULL, "Failed to created Base Directory - exiting", "BPQMail", MB_ICONSTOP); + return FALSE; + } + } + else + { + MessageBox(NULL, "Can't Continue without a Base Directory - exiting", "BPQMailChat", MB_ICONSTOP); + return FALSE; + } + } + else + { + if (!(Attrs & FILE_ATTRIBUTE_DIRECTORY)) + { + sprintf_s(msg, sizeof(msg), "Base Directory %s is a file not a directory - exiting", BaseDir); + ret = MessageBox(NULL, msg, "BPQMail", MB_ICONSTOP); + + return FALSE; + } + } + + initUTF8(); + + // Set up file and directory names + + strcpy(UserDatabasePath, BaseDir); + strcat(UserDatabasePath, "\\"); + strcat(UserDatabasePath, UserDatabaseName); + + strcpy(MsgDatabasePath, BaseDir); + strcat(MsgDatabasePath, "\\"); + strcat(MsgDatabasePath, MsgDatabaseName); + + strcpy(BIDDatabasePath, BaseDir); + strcat(BIDDatabasePath, "\\"); + strcat(BIDDatabasePath, BIDDatabaseName); + + strcpy(WPDatabasePath, BaseDir); + strcat(WPDatabasePath, "\\"); + strcat(WPDatabasePath, WPDatabaseName); + + strcpy(BadWordsPath, BaseDir); + strcat(BadWordsPath, "\\"); + strcat(BadWordsPath, BadWordsName); + + strcpy(NTSAliasesPath, BaseDir); + strcat(NTSAliasesPath, "/"); + strcat(NTSAliasesPath, NTSAliasesName); + + strcpy(MailDir, BaseDir); + strcat(MailDir, "\\"); + strcat(MailDir, "Mail"); + + CreateDirectory(MailDir, NULL); // Just in case + + strcpy(ConfigName, BaseDir); + strcat(ConfigName, "\\"); + strcat(ConfigName, "BPQMail.cfg"); + + UsingingRegConfig = FALSE; + + // if config file exists use it else try to get from Registry + + if (stat(ConfigName, &STAT) == -1) + { + UsingingRegConfig = TRUE; + + if (GetConfigFromRegistry()) + { + SaveConfig(ConfigName); + } + else + { + int retCode; + + strcpy(BBSName, GetNodeCall()); + strlop(BBSName, '-'); + strlop(BBSName, ' '); + + sprintf(msg, "No configuration found - Dummy Config created"); + + retCode = MessageBox(NULL, msg, "BPQMailChat", MB_OKCANCEL); + + if (retCode == IDCANCEL) + return FALSE; + + SaveConfig(ConfigName); + } + } + + if (GetConfig(ConfigName) == EXIT_FAILURE) + { + ret = MessageBox(NULL, + "BBS Config File seems corrupt - check before continuing", "BPQMail", MB_ICONSTOP); + return FALSE; + } + + // Got a Config File + + if (MainRect.right < 100 || MainRect.bottom < 100) + { + GetWindowRect(MainWnd, &MainRect); + } + + MoveWindow(MainWnd, MainRect.left, MainRect.top, MainRect.right-MainRect.left, MainRect.bottom-MainRect.top, TRUE); + + if (OpenMon) + CreateMonitor(); + + BBSApplMask = 1<<(BBSApplNum-1); + + ShowWindow(GetDlgItem(MainWnd, 901), SW_HIDE); + ShowWindow(GetDlgItem(MainWnd, 902), SW_HIDE); + ShowWindow(GetDlgItem(MainWnd, 903), SW_HIDE); + + // Make backup copies of Databases + + CopyBIDDatabase(); + CopyMessageDatabase(); + CopyUserDatabase(); + CopyWPDatabase(); + + SetupMyHA(); + SetupFwdAliases(); + SetupNTSAliases(NTSAliasesPath); + + GetWPDatabase(); + GetMessageDatabase(); + GetUserDatabase(); + GetBIDDatabase(); + GetBadWordFile(); + GetHTMLForms(); + + UsingingRegConfig = FALSE; + + // Make sure SYSOPCALL is set + + if (SYSOPCall[0] == 0) + strcpy(SYSOPCall, BBSName); + + // Make sure there is a user record for the BBS, with BBS bit set. + + user = LookupCall(BBSName); + + if (user == NULL) + { + user = AllocateUserRecord(BBSName); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + } + + if ((user->flags & F_BBS) == 0) + { + // Not Defined as a BBS + + if (SetupNewBBS(user)) + user->flags |= F_BBS; + } + + // if forwarding AMPR mail make sure User/BBS AMPR exists + + if (SendAMPRDirect) + { + BOOL NeedSave = FALSE; + + user = LookupCall("AMPR"); + + if (user == NULL) + { + user = AllocateUserRecord("AMPR"); + user->Temp = zalloc(sizeof (struct TempUserInfo)); + NeedSave = TRUE; + } + + if ((user->flags & F_BBS) == 0) + { + // Not Defined as a BBS + + if (SetupNewBBS(user)) + user->flags |= F_BBS; + NeedSave = TRUE; + } + + if (NeedSave) + SaveUserDatabase(); + } + + // Allocate Streams + + for (i=0; i < MaxStreams; i++) + { + conn = &Connections[i]; + conn->BPQStream = FindFreeStream(); + + if (conn->BPQStream == 255) break; + + NumberofStreams++; + + BPQSetHandle(conn->BPQStream, hWnd); + + SetAppl(conn->BPQStream, (i == 0 && EnableUI) ? 0x82 : 2, BBSApplMask | ChatApplMask); + Disconnect(conn->BPQStream); + } + + InitialiseTCP(); + + InitialiseNNTP(); + + SetupListenSet(); // Master set of listening sockets + + if (BBSApplNum) + { + SetupUIInterface(); + if (MailForInterval) + _beginthread(SendMailForThread, 0, 0); + } + + if (cfgMinToTray) + { + AddTrayMenuItem(MainWnd, "Mail Server"); + } + + SetTimer(hWnd,1,10000,NULL); // Slow Timer (10 Secs) + SetTimer(hWnd,2,100,NULL); // Send to Node and TCP Poll (100 ms) + + // Calulate time to run Housekeeping + { + struct tm *tm; + time_t now; + + now = time(NULL); + + tm = gmtime(&now); + + tm->tm_hour = MaintTime / 100; + tm->tm_min = MaintTime % 100; + tm->tm_sec = 0; + + MaintClock = _mkgmtime(tm); + + while (MaintClock < now) + MaintClock += MaintInterval * 3600; + + Debugprintf("Maint Clock %lld NOW %lld Time to HouseKeeping %lld", (long long)MaintClock, (long long)now, (long long)(MaintClock - now)); + + if (LastHouseKeepingTime) + { + if ((now - LastHouseKeepingTime) > MaintInterval * 3600) + { + DoHouseKeeping(FALSE); + } + } + } + + if (strstr(CmdLine, "tidymail")) + DeleteRedundantMessages(); + + if (strstr(CmdLine, "nohomebbs")) + DontNeedHomeBBS = TRUE; + + if (strstr(CmdLine, "DontCheckFromCall")) + DontCheckFromCall = TRUE; + + CheckMenuItem(hMenu,IDM_LOGBBS, (LogBBS) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hMenu,IDM_LOGTCP, (LogTCP) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hMenu,IDM_LOGCHAT, (LogCHAT) ? MF_CHECKED : MF_UNCHECKED); + + RefreshMainWindow(); + +// CreateWPReport(); + + CreatePipeThread(); + + return TRUE; +} + +int ConnectState(Stream) +{ + int state; + + SessionStateNoAck(Stream, &state); + return state; +} +UCHAR * EncodeCall(UCHAR * Call) +{ + static char axcall[10]; + + ConvToAX25(Call, axcall); + return &axcall[0]; + +} + +/* +VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo) +{ + struct UserInfo * user; + + int i = FWDInfo->UserIndex; + + if (i == -1) + { + FWDInfo->UserIndex = FWDInfo->UserCall[0] = 0; // Not scanning users + } + + for (i++; i <= NumberofUsers; i++) + { + user = UserRecPtr[i]; + + if (user->flags & F_POLLRMS) + { + FWDInfo->UserIndex = i; + strcpy(FWDInfo->UserCall, user->Call); + FWDInfo->FwdTimer = FWDInfo->FwdInterval - 20; + return ; + } + } + + // Finished Scan + + FWDInfo->UserIndex = FWDInfo->FwdTimer = FWDInfo->UserCall[0] = 0; +} +*/ + +#ifndef NEWROUTING + +VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo) +{ +} +VOID SetupMyHA() +{ +} +VOID SetupFwdAliases() +{ +} + +int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn) +{ + struct UserInfo * bbs; + struct BBSForwardingInfo * ForwardingInfo; + char ATBBS[41]; + char * HRoute; + int Count =0; + + strcpy(ATBBS, Msg->via); + HRoute = strlop(ATBBS, '.'); + + if (Msg->type == 'P') + { + // P messages are only sent to one BBS, but check the TO and AT of all BBSs before routing on HA + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSToList(Msg, bbs, ForwardingInfo)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSAtList(Msg, ForwardingInfo, ATBBS)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckBBSHList(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + return 1; + } + } + + return FALSE; + } + + // Bulls go to all matching BBSs, so the order of checking doesn't matter + + for (bbs = BBSChain; bbs; bbs = bbs->BBSNext) + { + ForwardingInfo = bbs->ForwardingInfo; + + if (CheckABBS(Msg, bbs, ForwardingInfo, ATBBS, HRoute)) + { + if (_stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! + { + if ((conn == NULL) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0)) // Dont send back + { + set_fwd_bit(Msg->fbbs, bbs->BBSNumber); + ForwardingInfo->MsgCount++; + } + } + Count++; + } + } + + return Count; +} +BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) +{ + char ** Calls; + char ** HRoutes; + int i, j; + + if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS + return TRUE; + + // Check TO distributions + + if (ForwardingInfo->TOCalls) + { + Calls = ForwardingInfo->TOCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], Msg->to) == 0) + return TRUE; + + Calls++; + } + } + + // Check AT distributions + + if (ForwardingInfo->ATCalls) + { + Calls = ForwardingInfo->ATCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + if ((HRoute) && (ForwardingInfo->Haddresses)) + { + // Match on Routes + + HRoutes = ForwardingInfo->Haddresses; + + while(HRoutes[0]) + { + i = strlen(HRoutes[0]) - 1; + j = strlen(HRoute) - 1; + + while ((i >= 0) && (j >= 0)) // Until one string rus out + { + if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards + goto next; + } + + return TRUE; + next: + HRoutes++; + } + } + + + return FALSE; + +} + +BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo) +{ + char ** Calls; + + // Check TO distributions + + if (ForwardingInfo->TOCalls) + { + Calls = ForwardingInfo->TOCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], Msg->to) == 0) + return TRUE; + + Calls++; + } + } + return FALSE; +} + +BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS) +{ + char ** Calls; + + // Check AT distributions + + if (strcmp(ATBBS, bbs->Call) == 0) // @BBS = BBS + return TRUE; + + if (ForwardingInfo->ATCalls) + { + Calls = ForwardingInfo->ATCalls; + + while(Calls[0]) + { + if (strcmp(Calls[0], ATBBS) == 0) + return TRUE; + + Calls++; + } + } + return FALSE; +} + +BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute) +{ + char ** HRoutes; + int i, j; + + if ((HRoute) && (ForwardingInfo->Haddresses)) + { + // Match on Routes + + HRoutes = ForwardingInfo->Haddresses; + + while(HRoutes[0]) + { + i = strlen(HRoutes[0]) - 1; + j = strlen(HRoute) - 1; + + while ((i >= 0) && (j >= 0)) // Until one string rus out + { + if (HRoutes[0][i--] != HRoute[j--]) // Compare backwards + goto next; + } + + return TRUE; + next: + HRoutes++; + } + } + return FALSE; +} + +#endif + +char * strlop(char * buf, char delim) +{ + // Terminate buf at delim, and return rest of string + + char * ptr; + + if (buf == NULL) return NULL; // Protect + + ptr = strchr(buf, delim); + + if (ptr == NULL) return NULL; + + *(ptr)++=0; + + return ptr; +} diff --git a/BPQMail.vcxproj b/BPQMail.vcxproj new file mode 100644 index 0000000..2765bdf --- /dev/null +++ b/BPQMail.vcxproj @@ -0,0 +1,346 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3766AA10-C777-4ED8-A83D-F1452DE9B665} + TelnetServer + Win32Proj + + + + Application + v141 + NotSet + true + + + Application + v141 + NotSet + true + + + Application + v141 + false + NotSet + + + Application + v141 + false + NotSet + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>15.0.28307.799 + + + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ + true + + + true + + + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ + false + + + false + + + + + + + Disabled + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + true + + Level3 + EditAndContinue + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + false + LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + $(IntDir)BBSListings\bpqmail.map + true + Windows + MachineX86 + + + + + + + + + Disabled + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + false + LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + $(IntDir)BBSListings\bpqmail.map + true + Windows + + + + + + + + + + + MaxSpeed + false + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + MultiThreaded + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + true + c:\DevProgs\bpq32\BPQMail.pdb + true + c:\DevProgs\bpq32\BPQMail.map + Windows + true + true + + MachineX86 + + + + + + + + + + + + + MaxSpeed + false + ..\CKernel;..\CInclude;..\CommonSource;..\BPQMail;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + ProgramDatabase + CompileAsC + + + ..\Include;%(AdditionalIncludeDirectories) + + + ..\lib\bpq32.lib;wsock32.lib;comctl32.lib;winmm.lib;..\lib\libconfig.lib;DbgHelp.lib;%(AdditionalDependencies) + c:\DevProgs\bpq32\BPQMail.exe + true + c:\DevProgs\bpq32\BPQMail.pdb + true + c:\DevProgs\bpq32\BPQMail.map + Windows + true + true + + + + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + All + All + $(IntDir) + $(IntDir) + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + All + All + $(IntDir) + $(IntDir) + $(IntDir) + $(IntDir) + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.obj + $(IntDir)%(Filename)1.xdc + $(IntDir)%(Filename)1.xdc + + + + + + + + + + + + + \ No newline at end of file diff --git a/BPQMail.vcxproj.filters b/BPQMail.vcxproj.filters new file mode 100644 index 0000000..982128f --- /dev/null +++ b/BPQMail.vcxproj.filters @@ -0,0 +1,113 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/BPQMail.vcxproj.user b/BPQMail.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/BPQMail.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/BPQTermMDI.c b/BPQTermMDI.c index 9626dc2..54ef348 100644 --- a/BPQTermMDI.c +++ b/BPQTermMDI.c @@ -92,7 +92,6 @@ HBRUSH bgBrush; extern BOOL FrameMaximized; char RTFHeader[4000]; - int RTFHddrLen; struct ConsoleInfo * ConsHeader = NULL; @@ -241,11 +240,6 @@ int ClientHeight, ClientWidth; #define MAXLINES 1000 #define LINELEN 200 -char RTFHeader[4000]; - -int RTFHddrLen; - - char FontName[100] = "FixedSys"; int FontSize = 20; int FontWidth = 8; diff --git a/BPQtoAGW.c b/BPQtoAGW.c index 247ee03..3c07458 100644 --- a/BPQtoAGW.c +++ b/BPQtoAGW.c @@ -495,7 +495,7 @@ static int ProcessLine(char * buf, int Port, BOOL CheckPort) BPQport = Port; p_ipad = ptr; } - if(BPQport > 0 && BPQport <33) + if(BPQport > 0 && BPQport < MAXBPQPORTS) { p_udpport = strtok(NULL, " \t\n\r"); diff --git a/Bpq32.c b/Bpq32.c index 463913d..a4d19f6 100644 --- a/Bpq32.c +++ b/Bpq32.c @@ -89,8 +89,8 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // 410c December 2007 -// Fix problem with NT introduced in V410a -// Display location of DLL on Console +// Fix problem with NT introduced in V410a +// Display location of DLL on Console // 410d January 2008 @@ -1165,7 +1165,14 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // Support 64 ports (69) // Fix Node commands for setting UZ7HO Modem (70) // Fix processing SABM on an existing session (71) - +// Extend KISS Node command to send more than one parameter byte (72) +// Add G7TAJ's code to record activity of HF ports for stats display (72) +// Add option to send KISS command to TNC on startup (73) +// Fix Bug in DED Emulator Monitor code (74) +// Add Filters to DED Monitor code (75) +// Detect loss of DED application (76) +// Fix connects to Application Alias with UZ7HO Driver (76) +// Fix Interlock of ports on same UZ7HO modem. (76) #define CKernel diff --git a/CBPQ32.vcproj b/CBPQ32.vcproj index 6a00653..f8e9834 100644 --- a/CBPQ32.vcproj +++ b/CBPQ32.vcproj @@ -50,7 +50,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\CInclude;..\CommonSource;..\CKernel" - PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;BPQ32_EXPORTS;MDIKERNEL;_USE_32BIT_TIME_T" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;BPQ32_EXPORTS;MDIKERNEL;_USE_32BIT_TIME_T;BPQ32" MinimalRebuild="true" BasicRuntimeChecks="3" RuntimeLibrary="1" diff --git a/CHeaders.h b/CHeaders.h index 81d6758..0d6886e 100644 --- a/CHeaders.h +++ b/CHeaders.h @@ -291,7 +291,7 @@ extern int FULL_CTEXT; // CTEXT ON ALL CONNECTS IF NZ // Although externally streams are numbered 1 to 64, internally offsets are 0 - 63 -extern BPQVECSTRUC DUMMY; // Needed to force correct order of following +extern BPQVECSTRUC DUMMYVEC; // Needed to force correct order of following extern BPQVECSTRUC BPQHOSTVECTOR[BPQHOSTSTREAMS + 5]; @@ -370,8 +370,8 @@ extern int MINBUFFCOUNT; extern UCHAR BPQDirectory[]; extern UCHAR BPQProgramDirectory[]; -extern char WINMOR[]; -extern char PACTORCALL[]; +extern UCHAR WINMOR[]; +extern UCHAR PACTORCALL[]; extern UCHAR MCOM; extern UCHAR MUIONLY; diff --git a/ChatUtils.c b/ChatUtils.c index 651ac6c..ce7cc07 100644 --- a/ChatUtils.c +++ b/ChatUtils.c @@ -528,8 +528,8 @@ VOID FreeList(char ** Hddr) #include "libconfig.h" -config_t cfg; -config_setting_t * group; +static config_t cfg; +static config_setting_t * group; extern char ChatWelcomeMsg[1000]; diff --git a/Cmd.c b/Cmd.c index b5d0bf6..617d6e9 100644 --- a/Cmd.c +++ b/Cmd.c @@ -3963,7 +3963,7 @@ VOID ATTACHCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX if (rxInterlock || txInterlock) { - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { OtherTNC = TNCInfo[i]; @@ -5059,7 +5059,6 @@ VOID WL2KSYSOP(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX } VOID CloseKISSPort(struct PORTCONTROL * PortVector); -int OpenConnection(struct PORTCONTROL * PortVector); VOID STOPCMS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) { @@ -5330,7 +5329,7 @@ VOID STARTPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX return; } - if (OpenConnection(PORT), TRUE) + if (OpenConnection(PORT)) Bufferptr = Cmdprintf(Session, Bufferptr, "Port Opened\r"); else Bufferptr = Cmdprintf(Session, Bufferptr, "Port Open Failed\r"); @@ -5355,9 +5354,8 @@ VOID STARTPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX -#define FEND 0xC0 int ASYSEND(struct PORTCONTROL * PortVector, char * buffer, int count); - +int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) { @@ -5365,9 +5363,12 @@ VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * char * ptr, * Context; int portno = 0; - int cmd = 0, val = 0; struct PORTCONTROL * PORT = PORTTABLE; int n = NUMBEROFPORTS; + UCHAR KissString[128]; + UCHAR ENCBUFF[256]; + int KissLen = 0; + unsigned char * Kissptr = KissString; // Send KISS Command to TNC @@ -5380,17 +5381,16 @@ VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * portno = atoi (ptr); ptr = strtok_s(NULL, " ", &Context); - if (ptr) + while (ptr && ptr[0] && KissLen < 120) { - cmd = atoi (ptr); + *(Kissptr++) = atoi (ptr); + KissLen++; ptr = strtok_s(NULL, " ", &Context); - if (ptr) - val = atoi (ptr); } } - if (portno == 0 || cmd == 0) + if (portno == 0 || KissLen == 0) { strcpy(Bufferptr, BADMSG); Bufferptr += (int)strlen(BADMSG); @@ -5403,8 +5403,6 @@ VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * if (PORT->PORTNUMBER == portno) { struct KISSINFO * KISS; - UCHAR ENCBUFF[16]; - unsigned char * ptr = ENCBUFF; if (PORT->PORTTYPE != 0 && PORT->PORTTYPE != 22) { @@ -5424,17 +5422,14 @@ VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * // Send Command - *(ptr++) = FEND; - *(ptr++) = KISS->OURCTRL | cmd; - *(ptr++) = (UCHAR)val; - *(ptr++) = FEND; + KissLen = KissEncode(KissString, ENCBUFF, KissLen); PORT = (struct PORTCONTROL *)KISS->FIRSTPORT; // ALL FRAMES GO ON SAME Q PORT->Session = Session; PORT->LastKISSCmdTime = time(NULL); - ASYSEND(PORT, ENCBUFF, 4); + ASYSEND(PORT, ENCBUFF, KissLen); Bufferptr = Cmdprintf(Session, Bufferptr, "Command Sent\r"); SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); diff --git a/CommonCode.c b/CommonCode.c index 1a1dfd6..7060c77 100644 --- a/CommonCode.c +++ b/CommonCode.c @@ -913,9 +913,8 @@ BOOL ProcessIncommingConnectEx(struct TNCINFO * TNC, char * Call, int Stream, BO PMSGWITHLEN buffptr; int Totallen = 0; UCHAR * ptr; - struct PORTCONTROL * PORT; + struct PORTCONTROL * PORT = TNC->PortRecord; - PORT = &TNC->PortRecord->PORTCONTROL; // Stop Scanner @@ -3264,7 +3263,7 @@ VOID SendReportMsg(char * buff, int txlen) buff[txlen++] = (crc&0xff); buff[txlen++] = (crc>>8); - sendto(ReportSocket, buff, txlen, 0, (LPSOCKADDR)&reportdest, sizeof(reportdest)); + sendto(ReportSocket, buff, txlen, 0, (struct sockaddr *)&reportdest, sizeof(reportdest)); } VOID SendLocation() @@ -4160,7 +4159,7 @@ VOID GetUIConfig() } #else - + int retCode, Vallen, Type, i; char Key[80]; char Size[80]; diff --git a/FLDigi.c b/FLDigi.c index 555c41c..b46ee65 100644 --- a/FLDigi.c +++ b/FLDigi.c @@ -25,7 +25,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "CHeaders.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); @@ -218,6 +218,14 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) // 100 mS Timer. + // 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; + // See if waiting for busy to clear before sending a connect if (TNC->BusyDelay) @@ -1122,7 +1130,7 @@ static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL) return Len; } -VOID FLDIGISuspendPort(struct TNCINFO * TNC) +VOID FLDIGISuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { TNC->FLInfo->CONOK = FALSE; } diff --git a/FreeDATA.c b/FreeDATA.c index 75c7128..7b92f21 100644 --- a/FreeDATA.c +++ b/FreeDATA.c @@ -43,7 +43,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses int KillTNC(struct TNCINFO * TNC); static int RestartTNC(struct TNCINFO * TNC); -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); static int Socket_Data(int sock, int error, int eventcode); VOID MoveWindows(struct TNCINFO * TNC); @@ -73,7 +73,7 @@ int FreeDataDisconnect(struct TNCINFO * TNC); int FreeGetData(struct TNCINFO * TNC); static void SendBeacon(struct TNCINFO * TNC, int Interval); void buildParamString(struct TNCINFO * TNC, char * line); -VOID FreeDataSuspendPort(struct TNCINFO * TNC); +VOID FreeDataSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC); VOID FreeDataReleasePort(struct TNCINFO * TNC); @@ -450,9 +450,17 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) { case 7: - // 100 mS Timer. May now be needed, as Poll can be called more frequently in some circumstances + // 100 mS Timer. May now be needed, as Poll can be called more frequently in some circumstances - SendPoll(TNC); + // 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; + + SendPoll(TNC); // Check for buffered data to send @@ -599,7 +607,7 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) // Stop Listening, and set MYCALL to user's call - FreeDataSuspendPort(TNC); + FreeDataSuspendPort(TNC, TNC); FreeDataChangeMYC(TNC, TNC->Streams[0].MyCall); TNC->SessionTimeLimit = TNC->DefaultSessionTimeLimit; // Reset Limit @@ -1049,7 +1057,7 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) if (TNC->ConnectPending == 0 && TNC->PTTState == 0) { - FreeDataSuspendPort(TNC); + FreeDataSuspendPort(TNC, TNC); TNC->GavePermission = TRUE; return 0; // OK to Change } @@ -1098,7 +1106,7 @@ VOID FreeDataReleaseTNC(struct TNCINFO * TNC) } -VOID FreeDataSuspendPort(struct TNCINFO * TNC) +VOID FreeDataSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { // char CMD[] = "{\"type\" : \"set\", \"command\" : \"listen\", \"state\": \"False\"}\n"; // send(TNC->TCPDataSock, CMD, strlen(CMD), 0); diff --git a/GetVersion.h b/GetVersion.h index 30c0161..c239ae1 100644 --- a/GetVersion.h +++ b/GetVersion.h @@ -4,7 +4,7 @@ char VersionStringWithBuild[50]=""; int Ver[4] = {Vers}; char TextVerstring[50] = ""; -VOID GetVersionInfo(TCHAR * File) +void GetVersionInfo(char * File) { #ifndef LINBPQ diff --git a/HFCommon.c b/HFCommon.c index 295a418..be42afa 100644 --- a/HFCommon.c +++ b/HFCommon.c @@ -1623,7 +1623,7 @@ BOOL InterlockedCheckBusy(struct TNCINFO * ThisTNC) if (rxInterlock == 0 && txInterlock == 0) return ThisTNC->Busy; // No Interlock - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { TNC = TNCInfo[i]; diff --git a/HSMODEM.c b/HSMODEM.c index 0529e4e..5362b87 100644 --- a/HSMODEM.c +++ b/HSMODEM.c @@ -115,7 +115,7 @@ struct HSMODEMINFO int KillTNC(struct TNCINFO * TNC); int RestartTNC(struct TNCINFO * TNC); -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); #include "bpq32.h" @@ -283,7 +283,7 @@ static int ProcessLine(char * buf, int Port) return (TRUE); } -char * Config; +static char * Config; static char * ptr1, * ptr2; int HSMODEMGetLine(char * buf) @@ -413,7 +413,15 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) // 100 mS Timer. May now be needed, as Poll can be called more frequently in some circumstances - if (TNC->CONNECTED) + // 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; + + if (TNC->CONNECTED) { TNC->CONNECTED--; @@ -890,7 +898,7 @@ VOID HSMODEMReleaseTNC(struct TNCINFO * TNC) } -VOID HSMODEMSuspendPort(struct TNCINFO * TNC) +VOID HSMODEMSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { HSMODEMSendCommand(TNC, "CONOK OFF\r"); } diff --git a/HTTPcode.c b/HTTPcode.c index 6a50eb9..7260c8e 100644 --- a/HTTPcode.c +++ b/HTTPcode.c @@ -1382,7 +1382,7 @@ int SetupNodeMenu(char * Buff, int LOCAL) Len = sprintf(Buff, NodeMenuHeader, Mycall); - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { TNC = TNCInfo[i]; if (TNC == NULL) diff --git a/HanksRT.c b/HanksRT.c index e87ce89..98ce668 100644 --- a/HanksRT.c +++ b/HanksRT.c @@ -135,10 +135,10 @@ int AddtoHistory(struct user_t * user, char * text) struct tm * tm; time_t Now = time(NULL); - // Don't want to grow indefinitely and fill memory. We only allow display upt 24 hours back, so if first record is older that that + // Don't want to grow indefinitely and fill memory. We only allow display up to 24 hours back, so if first record is older that that // remove and reuse it - if (History && History->Time < Now - 86400) + if (History && History->Time < Now - 86400) { Rec = History; History = Rec->next; // Remove from front of chain @@ -183,9 +183,6 @@ int AddtoHistory(struct user_t * user, char * text) - - - int ChatIsUTF8(unsigned char *ptr, int len) { int n; @@ -785,9 +782,7 @@ VOID ProcessChatLine(ChatCIRCUIT * conn, struct UserInfo * user, char* OrigBuffe int n = HistoryCount; if (param) - interval = atoi(param) * 60; - - start = time(NULL) - interval; + interval = atoi(param); if (interval < 1) { @@ -802,6 +797,8 @@ VOID ProcessChatLine(ChatCIRCUIT * conn, struct UserInfo * user, char* OrigBuffe interval = 1440; // Limit to 1 day } + start = time(NULL) - (interval * 60); + // Find first record to send while (ptr) diff --git a/KAMPactor.c b/KAMPactor.c index 08e475c..f214f04 100644 --- a/KAMPactor.c +++ b/KAMPactor.c @@ -331,6 +331,14 @@ ok: // 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); diff --git a/KISSHF.c b/KISSHF.c index 884fb28..a8aeb41 100644 --- a/KISSHF.c +++ b/KISSHF.c @@ -30,7 +30,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "CHeaders.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); #include "bpq32.h" @@ -720,7 +720,7 @@ VOID KISSHFReleaseTNC(struct TNCINFO * TNC) ReleaseOtherPorts(TNC); } -VOID KISSHFSuspendPort(struct TNCINFO * TNC) +VOID KISSHFSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { } diff --git a/L2Code.c b/L2Code.c index 8651fdb..1b4d8f7 100644 --- a/L2Code.c +++ b/L2Code.c @@ -37,7 +37,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define REJSENT 1 // SET WHEN FIRST REJ IS SENT IN REPLY // TO AN I(P) -#define RNRSET 2 // RNR RECEIVED FROM OTHER END +#define RNRSET 0x2 // RNR RECEIVED FROM OTHER END #define DISCPENDING 8 // SEND DISC WHEN ALL DATA ACK'ED #define RNRSENT 0x10 // WE HAVE SEND RNR #define POLLSENT 0x20 // POLL BIT OUTSTANDING @@ -127,7 +127,7 @@ UCHAR ALIASMSG = 0; extern UINT APPLMASK; static UCHAR ISNETROMMSG = 0; UCHAR MSGFLAG = 0; -extern UCHAR * ALIASPTR; +extern char * ALIASPTR; UCHAR QSTCALL[7] = {'Q'+'Q','S'+'S','T'+'T',0x40,0x40,0x40,0xe0}; // QST IN AX25 UCHAR NODECALL[7] = {0x9C, 0x9E, 0x88, 0x8A, 0xA6, 0x40, 0xE0}; // 'NODES' IN AX25 FORMAT diff --git a/L4Code.c b/L4Code.c index 6f19652..f9741cd 100644 --- a/L4Code.c +++ b/L4Code.c @@ -42,7 +42,7 @@ VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session); VOID SENDL4DISC(TRANSPORTENTRY * Session); int C_Q_COUNT(VOID * Q); TRANSPORTENTRY * SetupSessionForL2(struct _LINKTABLE * LINK); -VOID InformPartner(struct _LINKTABLE * LINK); +VOID InformPartner(struct _LINKTABLE * LINK, int Reason); VOID IFRM150(TRANSPORTENTRY * Session, PDATAMESSAGE Buffer); VOID SendConNAK(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG); BOOL FINDCIRCUIT(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY ** REQL4, int * NewIndex); @@ -416,7 +416,7 @@ int GETBUSYBIT(TRANSPORTENTRY * L4) return L4->NAKBITS; } -VOID Q_IP_MSG(PDATAMESSAGE Buffer) +VOID Q_IP_MSG(MESSAGE * Buffer) { if (IPHOSTVECTOR.HOSTAPPLFLAGS & 0x80) { @@ -1084,7 +1084,7 @@ VOID ProcessIframe(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer) if (Buffer->PID == 0xCC || Buffer->PID == 0xCD) { - Q_IP_MSG(Buffer); + Q_IP_MSG((MESSAGE *)Buffer); return; } @@ -1100,7 +1100,7 @@ VOID ProcessIframe(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer) { // MUST KILL SESSION - InformPartner(LINK); // CLOSE IT + InformPartner(LINK, NORMALCLOSE); // CLOSE IT LINK->CIRCUITPOINTER = NULL; // AND UNHOOK } LINK->LINKTYPE = 3; // NOW WE KNOW ITS A CROSSLINK @@ -1721,7 +1721,7 @@ VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, if (L3MSG->L4ID == 0x0C && L3MSG->L4INDEX == 0x0C) { - Q_IP_MSG((PDATAMESSAGE)L3MSG); + Q_IP_MSG((MESSAGE *)L3MSG); return; } diff --git a/LinBPQ.c b/LinBPQ.c index 0ea99b0..514c131 100644 --- a/LinBPQ.c +++ b/LinBPQ.c @@ -190,8 +190,8 @@ BOOL ChatInit(); VOID CloseChat(); VOID CloseTNCEmulator(); -config_t cfg; -config_setting_t * group; +static config_t cfg; +static config_setting_t * group; BOOL MonBBS = TRUE; BOOL MonCHAT = TRUE; @@ -257,6 +257,26 @@ int Slowtimer = 0; #define REPORTINTERVAL 15 * 549; // Magic Ticks Per Minute for PC's nominal 100 ms timer int ReportTimer = 0; +// Console Terminal Support + +struct ConTermS +{ + int BPQStream; + BOOL Active; + int Incoming; + + char kbbuf[INPUTLEN]; + int kbptr; + + char * KbdStack[MAXSTACK]; + int StackIndex; + + BOOL CONNECTED; + int SlowTimer; +}; + +struct ConTermS ConTerm = {0, 0}; + VOID CheckProgramErrors() { @@ -486,7 +506,7 @@ int SESSHDDRLEN = 0; UCHAR MCOM; UCHAR MUIONLY; UCHAR MTX; -unsigned long long MMASK; +uint64_t MMASK; UCHAR AuthorisedProgram; // Local Variable. Set if Program is on secure list @@ -516,7 +536,178 @@ UCHAR * GetLogDirectory() { return LogDirectory; } -extern int POP3Timer; +extern int POP3Timer; + +// Console Terminal Stuff + +#ifndef WIN32 + +#define _getch getchar + +/** + Linux (POSIX) implementation of _kbhit(). + Morgan McGuire, morgan@cs.brown.edu + */ + +#include +#include +#include +#include + +int _kbhit() { + static const int STDIN = 0; + static int initialized = 0; + + if (! initialized) { + // Use termios to turn off line buffering + struct termios term; + tcgetattr(STDIN, &term); + term.c_lflag &= ~ICANON; + + tcsetattr(STDIN, TCSANOW, &term); + setbuf(stdin, NULL); + initialized = 1; + } + + int bytesWaiting; + ioctl(STDIN, FIONREAD, &bytesWaiting); + return bytesWaiting; +} + +#endif + +void ConTermInput(char * Msg) +{ + int i; + + if (ConTerm.BPQStream == 0) + { + ConTerm.BPQStream = FindFreeStream(); + + if (ConTerm.BPQStream == 255) + { + ConTerm.BPQStream = 0; + printf("No Free Streams\n"); + return; + } + } + + if (!ConTerm.CONNECTED) + SessionControl(ConTerm.BPQStream, 1, 0); + + ConTerm.StackIndex = 0; + + // Stack it + + if (ConTerm.KbdStack[19]) + free(ConTerm.KbdStack[19]); + + for (i = 18; i >= 0; i--) + { + ConTerm.KbdStack[i+1] = ConTerm.KbdStack[i]; + } + + ConTerm.KbdStack[0] = _strdup(ConTerm.kbbuf); + + ConTerm.kbbuf[ConTerm.kbptr]=13; + + SendMsg(ConTerm.BPQStream, ConTerm.kbbuf, ConTerm.kbptr+1); +} + +void ConTermPoll() +{ + int port, sesstype, paclen, maxframe, l4window, len; + int state, change, InputLen, count; + char callsign[11] = ""; + char Msg[300]; + + // Get current Session State. Any state changed is ACK'ed + // automatically. See BPQHOST functions 4 and 5. + + SessionState(ConTerm.BPQStream, &state, &change); + + if (change == 1) + { + if (state == 1) + { + // Connected + + ConTerm.CONNECTED = TRUE; + ConTerm.SlowTimer = 0; + } + else + { + ConTerm.CONNECTED = FALSE; + printf("*** Disconnected\n"); + } + } + + GetMsg(ConTerm.BPQStream, Msg, &InputLen, &count); + + if (InputLen) + { + char * ptr = Msg; + char * ptr2 = ptr; + Msg[InputLen] = 0; + + while (ptr) + { + ptr2 = strlop(ptr, 13); + + // Replace CR with CRLF + + printf(ptr); + + if (ptr2) + printf("\r\n"); + + ptr = ptr2; + } + } + + if (_kbhit()) + { + unsigned char c = _getch(); + + if (c == 0xe0) + { + // Cursor control + + c = _getch(); + + if (c == 75) // cursor left + c = 8; + } + +#ifdef WIN32 + printf("%c", c); +#endif + if (c == 8) + { + if (ConTerm.kbptr) + ConTerm.kbptr--; + printf(" \b"); // Already echoed bs - clear typed char from screen + return; + } + + if (c == 13 || c == 10) + { + ConTermInput(ConTerm.kbbuf); + ConTerm.kbptr = 0; + return; + } + + ConTerm.kbbuf[ConTerm.kbptr++] = c; + fflush(NULL); + + } + + return; + +} + + +int Redirected = 0; int main(int argc, char * argv[]) { @@ -556,6 +747,12 @@ int main(int argc, char * argv[]) prctl(PR_SET_DUMPABLE, 1); // Enable Core Dumps even with setcap #endif #endif + + // Disable Console Terminal if stdout redirected + + if (!isatty(STDOUT_FILENO)) + Redirected = 1; + #endif printf("G8BPQ AX25 Packet Switch System Version %s %s\n", TextVerstring, Datestring); @@ -1214,6 +1411,9 @@ int main(int argc, char * argv[]) FreeSemaphore(&Semaphore); + if (Redirected == 0) + ConTermPoll(); + if (NUMBEROFTNCPORTS) TNCTimer(); diff --git a/MailDataDefs.c b/MailDataDefs.c index ea3825d..4c177d9 100644 --- a/MailDataDefs.c +++ b/MailDataDefs.c @@ -219,3 +219,4 @@ char ** HoldTo; // Hold on TO Call char ** HoldAt; // Hold on AT Call char ** HoldBID; // Hold on BID +struct ConsoleInfo * ConsHeader[2]; \ No newline at end of file diff --git a/MailNode.vcproj.DESKTOP-TGEL8RC.John.user b/MailNode.vcproj.DESKTOP-TGEL8RC.John.user index 7a49481..6975522 100644 --- a/MailNode.vcproj.DESKTOP-TGEL8RC.John.user +++ b/MailNode.vcproj.DESKTOP-TGEL8RC.John.user @@ -11,7 +11,7 @@ {3766AA10-C777-4ED8-A83D-F1452DE9B666} MailNode Win32Proj - 10.0.17763.0 @@ -68,23 +67,20 @@ <_ProjectFileVersion>15.0.28307.799 - $(SolutionDir)$(Configuration)\ - C:\Dev\Msdev2005\Intermed\LinBPQ\$(Configuration)\ + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ true true - C:\Dev\Msdev2005\Intermed\LinBPQ\$(Configuration)\ - c:\LinBPQ - $(SolutionDir)$(Configuration)\ - C:\Dev\Msdev2005\Intermed\LinBPQ\$(Configuration)\ + C:\Dev\Msdev2005\$(SolutionName)\$(ProjectName)\$(Configuration)\ + C:\Dev\Msdev2005\Intermed\$(SolutionName)\$(ProjectName)\$(Configuration)\ false false - C:\Dev\Msdev2005\Intermed\LinBPQ\$(Configuration)\ @@ -99,7 +95,7 @@ EditAndContinue - kernel32.lib;WS2_32.Lib;C:\Dev\Msdev2005\Projects\BPQ32\CKernel\Debug\libconfig.lib;DbgHelp.lib;setupapi.lib;miniupnpc.lib;zlibstat.lib;%(AdditionalDependencies) + kernel32.lib;WS2_32.Lib;..\lib\libconfigd.lib;DbgHelp.lib;setupapi.lib;miniupnpc.lib;zlibstat.lib;%(AdditionalDependencies) c:\LINBPQ\$(ProjectName).exe true true @@ -120,10 +116,10 @@ Level3 - EditAndContinue + ProgramDatabase - kernel32.lib;WS2_32.Lib;C:\Dev\Msdev2005\Projects\BPQ32\CKernel\x64\Debug\libconfig.lib;DbgHelp.lib;setupapi.lib;C:\Users\John\OneDrive\Dev\Source\miniupnpc-2.2.3\msvc\x64\Debug\miniupnpc.lib;zlibstat64.lib;%(AdditionalDependencies) + kernel32.lib;WS2_32.Lib;C:\OneDrive\Dev\Source\bpq32\libconfig\x64\Release\libconfig.lib;DbgHelp.lib;setupapi.lib;C:\OneDrive\Dev\Source\miniupnpc-2.2.3\msvc\x64\Debug\miniupnpc.lib;C:\Users\johnw\Downloads\zlib-1.2.11-binaries-x64-release\zlib-1.2.11\binaries\x64\Release\zlib.lib;%(AdditionalDependencies) c:\LINBPQ\$(ProjectName).exe true true @@ -131,7 +127,7 @@ Console 4000000 0 - false + false @@ -144,7 +140,7 @@ ProgramDatabase - kernel32.lib;WS2_32.Lib;..\lib\libconfig.lib;DbgHelp.lib;Setupapi.lib;%(AdditionalDependencies) + kernel32.lib;WS2_32.Lib;..\lib\libconfig.lib;DbgHelp.lib;Setupapi.lib;miniupnpc.lib;zlibstat.lib;%(AdditionalDependencies) c:\devprogs\bpq32\LinBPQ.exe true Console @@ -158,7 +154,7 @@ ..\CKernel;..\CommonSource;..\CInclude;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;LINBPQ;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;LINBPQ;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) MultiThreaded @@ -166,7 +162,7 @@ ProgramDatabase - kernel32.lib;WS2_32.Lib;..\lib\libconfig.lib;DbgHelp.lib;Setupapi.lib;%(AdditionalDependencies) + kernel32.lib;WS2_32.Lib;..\lib\libconfig.lib;DbgHelp.lib;Setupapi.lib;miniupnpc.lib;zlibstat.lib;%(AdditionalDependencies) c:\devprogs\bpq32\LinBPQ.exe true Console @@ -215,6 +211,7 @@ + @@ -288,7 +285,6 @@ - diff --git a/MailNode.vcxproj.filters b/MailNode.vcxproj.filters new file mode 100644 index 0000000..23094c6 --- /dev/null +++ b/MailNode.vcxproj.filters @@ -0,0 +1,324 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/MailNode.vcxproj.user b/MailNode.vcxproj.user new file mode 100644 index 0000000..c9aaeaf --- /dev/null +++ b/MailNode.vcxproj.user @@ -0,0 +1,8 @@ + + + + C:\Dev\Msdev2005\projects\bpq32\BPQMail\x64\Debug\LinBPQ.exe + c:\linbpq + WindowsLocalDebugger + + \ No newline at end of file diff --git a/MailTCP.c b/MailTCP.c index b67b839..b26f6da 100644 --- a/MailTCP.c +++ b/MailTCP.c @@ -39,9 +39,8 @@ int CurrentSockets=0; #define VERSION_MAJOR 2 #define VERSION_MINOR 0 -SOCKADDR_IN local_sin; /* Local socket - internet style */ - -PSOCKADDR_IN psin; +static SOCKADDR_IN local_sin; /* Local socket - internet style */ +static PSOCKADDR_IN psin; SOCKET smtpsock, pop3sock; diff --git a/Moncode.c b/Moncode.c index 47de2ce..762fd6d 100644 --- a/Moncode.c +++ b/Moncode.c @@ -60,7 +60,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #define NODES_SIG 0xFF char * strlop(char * buf, char delim); -UCHAR * DisplayINP3RIF(UCHAR * ptr1, UCHAR * ptr2, int msglen); +UCHAR * DisplayINP3RIF(UCHAR * ptr1, UCHAR * ptr2, unsigned int msglen); char * DISPLAY_NETROM(MESSAGE * ADJBUFFER, UCHAR * Output, int MsgLen); UCHAR * DISPLAYIPDATAGRAM(IPMSG * IP, UCHAR * Output, int MsgLen); @@ -145,7 +145,7 @@ int APRSDecodeFrame(MESSAGE * msg, char * buffer, time_t Stamp, uint64_t Mask) { return IntDecodeFrame(msg, buffer, Stamp, Mask, TRUE, FALSE); } -DllExport int APIENTRY DecodeFrame(MESSAGE * msg, char * buffer, int Stamp) +DllExport int APIENTRY DecodeFrame(MESSAGE * msg, char * buffer, time_t Stamp) { return IntDecodeFrame(msg, buffer, Stamp, MMASK, FALSE, FALSE); } diff --git a/MultiConsole.c b/MultiConsole.c index 8b8ec98..81b3530 100644 --- a/MultiConsole.c +++ b/MultiConsole.c @@ -31,6 +31,9 @@ BOOL FlashOnConnect; BOOL WrapInput; BOOL CloseWindowOnBye; +char RTFHeader[4000]; +int RTFHddrLen = 0; + RECT ConsoleRect; char chatMsg[] = "\rSysop wants to chat to you\r"; diff --git a/PG/Loop.c b/PG/Loop.c new file mode 100644 index 0000000..ac753d9 --- /dev/null +++ b/PG/Loop.c @@ -0,0 +1,24 @@ +#include +#include + +/* + * TST_PG.C + * + * Little test program of "PG" command for FBB BBS software. + * + * (C) F6FBB 1991. + * + * FBB software 5.14 and up. + * + * + * This program echoes to the user what he types + * or executes a BBS command preceded by "CMD" + * until "BYE" is received + */ + + +main(int argc, char **argv) +{ + Sleep(10000); + return 0; +} \ No newline at end of file diff --git a/PG/Loop.exe b/PG/Loop.exe new file mode 100644 index 0000000000000000000000000000000000000000..1ddf86e2722fa11b3bfc48b1e26b0d5b6e0930e9 GIT binary patch literal 77824 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 literal 0 HcmV?d00001 diff --git a/PG/PGTest.c b/PG/PGTest.c new file mode 100644 index 0000000..4d3742d --- /dev/null +++ b/PG/PGTest.c @@ -0,0 +1,53 @@ +#include + +/* + * TST_PG.C + * + * Little test program of "PG" command for FBB BBS software. + * + * (C) F6FBB 1991. + * + * FBB software 5.14 and up. + * + * + * This program echoes to the user what he types + * or executes a BBS command preceded by "CMD" + * until "BYE" is received + */ + + +main(int argc, char **argv) +{ + int i; + int level = atoi(argv[2]); /* Get level from argument list */ + + /* and transform it to integer */ + if (level == 0) { /* Is level equal to 0 ? */ + /* This is the first call */ + printf("Hello %s, type BYE when you want to stop !\n", argv[1]); + return(1); /* program will be called again */ + } + else { + strupr(argv[5]); /* Capitalise the first word */ + if (strcmp(argv[5], "BYE") == 0) { /* is BYE received ? */ + printf("Ok, bye-bye\n"); + return(0); /* Yes, go on BBS */ + } + else if (strcmp(argv[5], "CMD") == 0) { /* is CMD received ? */ + for (i = 6 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + for (i = 6 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + return(4); /* Yes, send command */ + } + else { + printf("You told me : "); /* These are other lines */ + for (i = 5 ; i < argc ; i++) /* List line arguments */ + printf("%s ", argv[i]); /* sent by user */ + putchar('\n'); + return(1); /* No, call again program */ + } + } +} \ No newline at end of file diff --git a/PG/PGTest.exe b/PG/PGTest.exe new file mode 100644 index 0000000000000000000000000000000000000000..7f01106893e475b6e0bade6791d6aae1f95f375e GIT binary patch literal 103936 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 T$o{UQU0we-k>B6$Z-;*XDAvp1 literal 0 HcmV?d00001 diff --git a/RigControl.c b/RigControl.c index 090816a..9b33f57 100644 --- a/RigControl.c +++ b/RigControl.c @@ -896,10 +896,10 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR { if (RIG->NumberofBands) { - RIG->ScanStopped &= (0xffffffff ^ (1 << Port)); + RIG->ScanStopped &= (0xffffffffffffffff ^ ((uint64_t)1 << Port)); if (Session != (TRANSPORTENTRY *) -1) // Used for internal Stop/Start - RIG->ScanStopped &= 0xfffffffe; // Clear Manual Stopped Bit + RIG->ScanStopped &= 0xfffffffffffffffe; // Clear Manual Stopped Bit if (n > 2) RIG->ScanCounter = atoi(Mode) * 10; //Start Delay @@ -926,7 +926,7 @@ int Rig_CommandEx(struct RIGPORTINFO * PORT, struct RIGINFO * RIG, TRANSPORTENTR if (_stricmp(FreqString, "SCANSTOP") == 0) { - RIG->ScanStopped |= (1 << Port); + RIG->ScanStopped |= ((uint64_t)1 << Port); if (Session != (TRANSPORTENTRY *) -1) // Used for internal Stop/Start RIG->ScanStopped |= 1; // Set Manual Stopped Bit @@ -2800,7 +2800,7 @@ void CheckRX(struct RIGPORTINFO * PORT) return; if (PORT->RXBuffer[Length-1] != 0xfd) - return; + return; // Echo ProcessICOMFrame(PORT, PORT->RXBuffer, Length); // Could have multiple packets in buffer diff --git a/SCSPactor.c b/SCSPactor.c index 8c2081b..61568fa 100644 --- a/SCSPactor.c +++ b/SCSPactor.c @@ -114,7 +114,7 @@ int DoScanLine(struct TNCINFO * TNC, char * Buff, int Len); VOID SuspendOtherPorts(struct TNCINFO * ThisTNC); VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC); -VOID PTCSuspendPort(struct TNCINFO * TNC); +VOID PTCSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC); VOID PTCReleasePort(struct TNCINFO * TNC); int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); int CheckMode(struct TNCINFO * TNC); @@ -468,6 +468,14 @@ ok: // 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; + SCSCheckRX(TNC); SCSPoll(port); @@ -4147,7 +4155,7 @@ VOID CloseComplete(struct TNCINFO * TNC, int Stream) } -VOID PTCSuspendPort(struct TNCINFO * TNC) +VOID PTCSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { struct STREAMINFO * STREAM = &TNC->Streams[0]; diff --git a/SCSTracker.c b/SCSTracker.c index 8ca38b4..caa6b0e 100644 --- a/SCSTracker.c +++ b/SCSTracker.c @@ -108,7 +108,7 @@ BOOL TrkWriteCommBlock(struct TNCINFO * TNC) } -VOID TRKSuspendPort(struct TNCINFO * TNC) +VOID TRKSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { struct STREAMINFO * STREAM = &TNC->Streams[0]; @@ -367,6 +367,14 @@ ok: // 100 mS Timer. + // 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; + // See if waiting for connect after changing MYCALL if (TNC->SlowTimer) diff --git a/SerialPort.c b/SerialPort.c index 713ab4e..59ab627 100644 --- a/SerialPort.c +++ b/SerialPort.c @@ -39,7 +39,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "CHeaders.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); #include "bpq32.h" @@ -722,7 +722,7 @@ VOID SerialReleaseTNC(struct TNCINFO * TNC) } -VOID SerialSuspendPort(struct TNCINFO * TNC) +VOID SerialSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { SerialSendCommand(TNC, "CONOK OFF\r"); } diff --git a/TNCEmulators.c b/TNCEmulators.c index b8fb2e2..966b785 100644 --- a/TNCEmulators.c +++ b/TNCEmulators.c @@ -55,7 +55,7 @@ VOID GETDATA(struct TNCDATA * TNC); VOID DOCONMODECHANGE(struct TNCDATA * TNC, int Stream); VOID SEND_CONNECTED(struct TNCDATA * TNC, int ToStream); VOID READCHANGE(int Stream); -VOID DOMONITORING(int NeedTrace); +VOID DOMONITORING(int NeedTrace, struct TNCDATA * TNC); int APIENTRY DecodeFrame(MESSAGE * msg, char * buffer, time_t Stamp); time_t APIENTRY GetRaw(int stream, char * msg, int * len, int * count); BOOL TfPut(struct TNCDATA * TNC, UCHAR character); @@ -73,10 +73,11 @@ VOID DisableAppl(struct TNCDATA * TNC); int BPQSerialSetPollDelay(HANDLE hDevice, int PollDelay); int BPQSerialSendData(struct TNCDATA * TNC, UCHAR * Message,int MsgLen); int BPQSerialGetData(struct TNCDATA * TNC, UCHAR * Message, unsigned int BufLen, ULONG * MsgLen); +int SendHostOK(struct TNCDATA * TNC); extern struct TNCDATA * TNCCONFIGTABLE; -struct TNCDATA * TNC2TABLE; // malloc'ed +struct TNCDATA * TNC2TABLE = NULL; // malloc'ed extern int NUMBEROFTNCPORTS; // MODEFLAG DEFINITIONS @@ -229,7 +230,7 @@ typedef struct _SERIAL_STATUS { #ifndef WIN32 - + #ifdef MACBPQ #include #endif @@ -1214,7 +1215,7 @@ VOID TNCTRANS(struct TNCDATA * TNC, char * Tail, CMDX * CMD) TNCStream->MODEFLAG |= TRANS; TNCStream->MODEFLAG &= ~(COMMAND+CONV); } -static VOID RESTART(struct TNCDATA * TNC) +static VOID TNCRESTART(struct TNCDATA * TNC) { // REINITIALISE CHANNEL @@ -1242,12 +1243,12 @@ static VOID RESTART(struct TNCDATA * TNC) } -static VOID UNPROTOCMD(struct TNCDATA * TNC, char * Tail, CMDX * CMD) +static VOID TNCUNPROTOCMD(struct TNCDATA * TNC, char * Tail, CMDX * CMD) { } -CMDX COMMANDLIST[] = +CMDX TNCCOMMANDLIST[] = { "AUTOLF ",2, ONOFF, offsetof(struct TNCDATA, AUTOLF), "BBSMON ",6, ONOFF, offsetof(struct TNCDATA, BBSMON), @@ -1292,15 +1293,15 @@ CMDX COMMANDLIST[] = "PACLEN ",1,VALUE, offsetof(struct TNCDATA, TPACLEN), "PASS ",3,VALHEX, offsetof(struct TNCDATA, PASSCHAR), "RELEASE ",3,TNCRELEASE,0, - "RESTART ",7,RESTART,0, + "RESTART ",7,TNCRESTART,0, "TRANS ",1,TNCTRANS,0, - "UNPROTO ",1,UNPROTOCMD,0, + "UNPROTO ",1,TNCUNPROTOCMD,0, "USERS ",2,VALUE, offsetof(struct TNCDATA, Users), }; -static CMDX * CMD = NULL; -int NUMBEROFTNCCOMMANDS = sizeof(COMMANDLIST)/sizeof(CMDX); + +int NUMBEROFTNCCOMMANDS = sizeof(TNCCOMMANDLIST)/sizeof(CMDX); /*NEWVALUE DW 0 HEXFLAG DB 0 @@ -1449,9 +1450,13 @@ BOOL InitializeTNCEmulator() { // Com Port may be a hardware device (ie /dev/ttyUSB0) COMn or VCOMn (BPQ Virtual COM) - char * Baud = strlop(TNC->PORTNAME, ','); + char * Baud = strlop(TNC->PORTNAME, ':'); char * PNptr; + if (Baud == 0) + Baud = strlop(TNC->PORTNAME, ','); + + PNptr = &TNC->PORTNAME[0]; // Only show last element of name on Streams display @@ -1601,7 +1606,7 @@ BOOL InitializeTNCEmulator() TNC->Speed = atoi(Baud); else TNC->VCOM = TRUE; - + if (_memicmp(TNC->PORTNAME, "COM", 3) == 0) { TNC->VCOM = FALSE; @@ -1750,17 +1755,46 @@ VOID TNCTimer() struct TNCDATA * TNC = TNC2TABLE; struct StreamInfo * channel; int n; - int NeedTrace = 0; + struct TNCDATA * TTNC = 0; // First TNC2 while (TNC) { + if (TNC->LastDEDPollTime && (time(NULL) > TNC->LastDEDPollTime + 30)) // No polls for 30 secs + { + int Len = 0; + int Count; + + TNC->LastDEDPollTime = 0; + + Debugprintf("DED Host Application Lost"); + TNC->MODE = 0; + TNC->HOSTSTATE = 0; + + DisableAppl(TNC); + + // Clear Monitor Q + + GetRaw(TNC->Channels[0]->BPQStream, (char *)&MONITORDATA, &Len, &Count); + + while (Len) + { + GetRaw(TNC->Channels[0]->BPQStream, (char *)&MONITORDATA, &Len, &Count); + } + continue; + } + + if (TNC->Mode != TNC2) goto NotTNC2; - - NeedTrace |= TNC->TRACEFLAG; //SEE IF ANY PORTS ARE MONITORING - // CHECK FOR PACTIMER EXPIRY AND CMDTIME + if (TTNC == 0 && TNC->TNC2Stream[0]) // Save first TNC2 TNC + TTNC = TNC; + + + NeedTrace |= TNC->TRACEFLAG; //SEE IF ANY PORTS ARE MONITORING + + // CHECK FOR PACTIMER EXPIRY AND CMDTIME if (TNC->CMDTMR) { @@ -1809,7 +1843,7 @@ NotTNC2: for (n = 1; n <= TNC->HOSTSTREAMS; n++) { channel = TNC->Channels[n]; - + if (channel->CloseTimer) { channel->CloseTimer--; @@ -1820,7 +1854,8 @@ NotTNC2: NextTNC: TNC = TNC->Next; } - DOMONITORING(NeedTrace); + if (TTNC) // Only if we have TNC2 Streams + DOMONITORING(NeedTrace, TTNC); } /* @@ -2213,35 +2248,28 @@ void CheckForDataFromTerminal(struct TNCDATA * TNC) } -VOID DOMONITORING(int NeedTrace) +VOID DOMONITORING(int NeedTrace, struct TNCDATA * TTNC) { // IF ANY PORTS HAVE MONITOR ENABLED, SET MONITOR BIT ON FIRST PORT - struct TNCDATA * TNC = TNC2TABLE; // malloc'ed int Tracebit = 0, len, count, n; time_t Stamp; uint64_t SaveMMASK = MMASK; BOOL SaveMTX = MTX; BOOL SaveMCOM = MCOM; BOOL SaveMUI = MUIONLY; - int BPQStream = 0; + struct TNCDATA * TNC = TNC2TABLE; + int BPQStream = TTNC->TNC2Stream[0]->BPQPort; if (NeedTrace) Tracebit = 0x80; - if (TNC->Channels[0]) - BPQStream = TNC->Channels[0]->BPQStream; - else if (TNC->TNC2Stream[0]) - BPQStream = TNC->TNC2Stream[0]->BPQPort; - else if (TNC->BPQPort) - BPQStream = TNC->BPQPort; - if (BPQStream) { - if (TNC->CONOK) - SetAppl(BPQStream, TNC->APPLFLAGS | Tracebit, TNC->APPLICATION); + if (TTNC->CONOK && TTNC->Mode == TNC2) + SetAppl(BPQStream, TTNC->APPLFLAGS | Tracebit, TTNC->APPLICATION); else - SetAppl(BPQStream, TNC->APPLFLAGS | Tracebit, 0); + SetAppl(BPQStream, TTNC->APPLFLAGS | Tracebit, 0); } Stamp = GetRaw(BPQStream, (char *)&MONITORDATA, &len, &count); @@ -2250,7 +2278,7 @@ VOID DOMONITORING(int NeedTrace) return; len = DecodeFrame(&MONITORDATA, MONBUFFER, Stamp); - + while (TNC) { if (TNC->Mode == TNC2 && TNC->TRACEFLAG) @@ -2585,7 +2613,7 @@ VOID TNCCOMMAND(struct TNCDATA * TNC) ptr2 = ptr1; // Save - CMD = &COMMANDLIST[0]; + CMD = &TNCCOMMANDLIST[0]; n = 0; for (n = 0; n < NUMBEROFTNCCOMMANDS; n++) @@ -3198,7 +3226,7 @@ VOID DisableAppl(struct TNCDATA * TNC) { int i, Stream; - for (i = 1; i <= TNC->HOSTSTREAMS; i++) + for (i = 0; i <= TNC->HOSTSTREAMS; i++) { Stream = TNC->Channels[i]->BPQStream; @@ -3218,6 +3246,17 @@ VOID EnableAppl(struct TNCDATA * TNC) } } +VOID EnableDEDAppl(struct TNCDATA * TNC) +{ + int i; + + SetAppl(TNC->Channels[0]->BPQStream, 2 | TNC->TRACEFLAG, TNC->APPLICATION); + + for (i = 1; i <= TNC->HOSTSTREAMS; i++) + { + SetAppl(TNC->Channels[i]->BPQStream, TNC->APPLFLAGS, TNC->APPLICATION); + } +} BOOL TfPut(struct TNCDATA * TNC, UCHAR character) { struct StreamInfo * Channel; @@ -3298,8 +3337,8 @@ CHARMODE: TNC->MODE = 1; TNC->CURSOR = (UCHAR *)&TNC->TONODEBUFFER; - EnableAppl(TNC); - + + EnableDEDAppl(TNC); return(TRUE); } @@ -3372,24 +3411,29 @@ NOTDATA: } - if (TNC->DEDTXBUFFER[0] == 1) - goto DUFFHOSTCMD; - // sprintf(msg,"DED CMD: Port %d CMD %c MSGCHANNEL %d\n", TNC->ComPort, TNC->TONODEBUFFER[0], MSGCHANNEL); // OutputDebugString(msg); if (_memicmp(TNC->DEDTXBUFFER, "QRES", 4 == 0)) - goto SENDHOSTOK; + return SendHostOK(TNC); switch (toupper(TNC->DEDTXBUFFER[0])) { - case 1: + case 1: // Recovery - goto DUFFHOSTCMD; + PUTCHARx(TNC, TNC->MSGCHANNEL); + + for (i=0; i < LBADCMDREPLY; i++) + { + PUTCHARx(TNC, BADCMDREPLY[i]); + } + + return TRUE; case 'G': - goto POLL; + PROCESSPOLL(TNC, Channel); + return TRUE; case 'I': goto ICMD; @@ -3399,20 +3443,95 @@ NOTDATA: TNC->MODE = TNC->DEDTXBUFFER[5] & 1; if (TNC->MODE) - EnableAppl(TNC); + EnableDEDAppl(TNC); else DisableAppl(TNC); - goto SENDHOSTOK; + return SendHostOK(TNC); case 'C': goto CCMD; case 'D': - goto DCMD; + + // DISCONNECT REQUEST + + Disconnect(Channel->BPQStream); + return SendHostOK(TNC); case 'L': - goto LCMD; + + // Poll + + PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL + PUTCHARx(TNC, 1); + + // GET STATE AND QUEUED BUFFERS + + if (TNC->MSGCHANNEL) + { + // Data Channel + + LocalSessionState(Channel->BPQStream, &State, &Change, FALSE); + + if (State == 0) + Work = '0'; + else + Work = '4'; // AX.25 STATE + + PUTCHARx(TNC, Change + '0'); // Status Messages + + PUTCHARx(TNC, ' '); + + // GET OTHER QUEUE COUNTS + + Count = RXCount(Channel->BPQStream); + + sprintf(WorkString, "%d", Count); // message count + + PUTSTRING(TNC, WorkString); + PUTCHARx(TNC, ' '); + + // NOT SENT IS NUMBER ON OUR QUEUE, NOT ACKED NUMBER FROM SWITCH + + // SEE HOW MANY BUFFERS ATTACHED TO Q HEADER IN BX + + Count = 0;// C_Q_COUNT(Channel->Chan_TXQ); + + sprintf(WorkString, "%d", Count); // message count + PUTSTRING(TNC, WorkString); + PUTCHARx(TNC, ' '); + + if (Count > 8) + Work = '8'; // Busy + + Count = CountFramesQueuedOnSession(L4); + + sprintf(WorkString, "%d", Count); // message count + PUTSTRING(TNC, WorkString); + PUTCHARx(TNC, ' '); + + } + else + { + //SEE IF MONITORED FRAMES AVAILABLE + + TNC->LastDEDPollTime = time(NULL); + + if (MONCount(TNC->Channels[0]->BPQStream)) + Work = 0x31; + else + Work = 0x30; + + } + + + PUTCHARx(TNC, '0'); + PUTCHARx(TNC, ' '); + PUTCHARx(TNC, Work); + PUTCHARx(TNC, 0); + + return TRUE; case '@': goto ATCOMMAND; @@ -3425,14 +3544,52 @@ NOTDATA: case 'M': + // Support BPQ Extensions IUSC followed by optional port list + if (TNC->DEDTXBUFFER[1] == 'N') - goto DISABLEMONITOR; + TNC->TRACEFLAG = 0; + else + { + char * ptr, * ptr2; + int port; + uint64_t mask = 0; - goto ENABLEMONITOR; + + TNC->DEDTXBUFFER[TNC->MSGLENGTH] = 0; + ptr = strlop(TNC->DEDTXBUFFER, ' '); + _strupr(TNC->DEDTXBUFFER); + + if (strchr(TNC->DEDTXBUFFER, 'U')) + TNC->MUIONLY = 1; + + + // Ptocess Mask + + while (ptr && ptr[0]) + { + ptr2 = strlop(ptr, ','); + port = atoi(ptr); + + if (port) + { + mask |= ((uint64_t)1 << (port - 1)); + ptr = ptr2; + } + } + + if (mask) + TNC->MMASK = mask; + + TNC->TRACEFLAG = 0x80; + } + + SetAppl(TNC->Channels[0]->BPQStream, 2 | TNC->TRACEFLAG, TNC->APPLICATION); + return SendHostOK(TNC); + case 'K': case 'O': - goto SENDHOSTOK; + return SendHostOK(TNC); case 'V': // Vesrion @@ -3444,7 +3601,7 @@ NOTDATA: return TRUE; default: - goto SENDHOSTOK; + return SendHostOK(TNC); ATCOMMAND: @@ -3547,40 +3704,15 @@ ICMD: } ECMD: - goto SENDHOSTOK; + return SendHostOK(TNC); -DUFFHOSTCMD: - - PUTCHARx(TNC, TNC->MSGCHANNEL); - - for (i=0; i < LBADCMDREPLY; i++) - { - PUTCHARx(TNC, BADCMDREPLY[i]); - } - - return TRUE; - -ENABLEMONITOR: - - TNC->TRACEFLAG = 0x80; - goto MONCOM; - -DISABLEMONITOR: - - TNC->TRACEFLAG = 0; - -MONCOM: - - SetAppl(TNC->Channels[0]->BPQStream, 2 | TNC->TRACEFLAG, TNC->APPLICATION); - - goto SENDHOSTOK; CCMD: // CONNECT REQUEST if (TNC->MSGCHANNEL == 0) - goto SENDHOSTOK; // SETTING UNPROTO ADDR - JUST ACK IT + return SendHostOK(TNC); // SETTING UNPROTO ADDR - JUST ACK IT *TNC->DEDCURSOR = 0; @@ -3651,91 +3783,13 @@ REALCALL: // READCHANGE(Channel->BPQStream); // Suppress Connected to Switch - goto SENDHOSTOK; + return SendHostOK(TNC); } } - goto SENDHOSTOK; - -DCMD: - -// DISCONNECT REQUEST - - Disconnect(Channel->BPQStream); - - goto SENDHOSTOK; - -LCMD: - - PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL - PUTCHARx(TNC, 1); - -// GET STATE AND QUEUED BUFFERS - - if (TNC->MSGCHANNEL) - goto NORM_L; - -// TO MONITOR CHANNEL - -// SEE IF MONITORED FRAMES AVAILABLE - - if (MONCount(TNC->Channels[0]->BPQStream)) - Work = 0x31; - else - Work = 0x30; - - goto MON_L; - -NORM_L: - - LocalSessionState(Channel->BPQStream, &State, &Change, FALSE); - - if (State == 0) - Work = '0'; - else - Work = '4'; // AX.25 STATE - - PUTCHARx(TNC, Change + '0'); // Status Messages - - PUTCHARx(TNC, ' '); - -// GET OTHER QUEUE COUNTS - - Count = RXCount(Channel->BPQStream); - - sprintf(WorkString, "%d", Count); // message count - - PUTSTRING(TNC, WorkString); - PUTCHARx(TNC, ' '); - -// NOT SENT IS NUMBER ON OUR QUEUE, NOT ACKED NUMBER FROM SWITCH + return SendHostOK(TNC); -// SEE HOW MANY BUFFERS ATTACHED TO Q HEADER IN BX - - Count = 0;// C_Q_COUNT(Channel->Chan_TXQ); - - sprintf(WorkString, "%d", Count); // message count - PUTSTRING(TNC, WorkString); - PUTCHARx(TNC, ' '); - - if (Count > 8) - Work = '8'; // Busy - - Count = CountFramesQueuedOnSession(L4); - - sprintf(WorkString, "%d", Count); // message count - PUTSTRING(TNC, WorkString); - PUTCHARx(TNC, ' '); - -MON_L: - - PUTCHARx(TNC, '0'); - PUTCHARx(TNC, ' '); - PUTCHARx(TNC, Work); - PUTCHARx(TNC, 0); - - return TRUE; HOSTDATAPACKET: @@ -3763,7 +3817,7 @@ HOSTDATAPACKET: // C_Q_ADD(Channel->Chan_TXQ, COPYMSGTOBUFFERS()); // RETURNS EDI = FIRST (OR ONLY) FRAGMENT -// goto SENDHOSTOK; +// return SendHostOK(TNC); // MAKE SURE NODE ISNT BUSY @@ -3778,17 +3832,13 @@ HOSTDATAPACKET: // OK TO PASS TO NODE SENDENFORCINGPACLEN(Channel, TNC->DEDTXBUFFER, TNC->MSGLENGTH); - goto SENDHOSTOK; + return SendHostOK(TNC); SendUnproto: SendMsg(0, TXBUFFERPTR, TNC->MSGLENGTH); - goto SENDHOSTOK; + return SendHostOK(TNC); -POLL: - - PROCESSPOLL(TNC, Channel); - return TRUE; YCMD: @@ -3835,83 +3885,53 @@ YCMD: TNC->HOSTSTREAMS = Work; } - /* - - Why is this here? - { - int Len=0; - UCHAR Message[1000]; - - while (TNC->RXCOUNT > 0) - { - Message[Len++]= *(TNC->PUTPTR++); - - TNC->RXCOUNT--; - - if (TNC->PUTPTR == &TNC->TOUSERBUFFER[TNCBUFFLEN]) - TNC->PUTPTR = (UCHAR *)&TNC->TOUSERBUFFER; - - if (Len > 900) - { - BPQSerialSendData(TNC, Message, Len); - Len = 0; - } - } - - if (Len > 0) - { - BPQSerialSendData(TNC, Message, Len); - } - } - _asm { - - popad - - - RET -*/ - -SENDHOSTOK: + return SendHostOK(TNC); +} +int SendHostOK(struct TNCDATA * TNC) +{ PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL PUTCHARx(TNC, 0); // NOTHING DOING return TRUE; - } + int PROCESSPOLL(struct TNCDATA * TNC, struct StreamInfo * Channel) -{ - int PollType; - - // ASK SWITCH FOR STATUS CHANGE OR ANY RECEIVED DATA +{ + // DED Mode - ASK SWITCH FOR STATUS CHANGE OR ANY RECEIVED DATA - if (TNC->MSGLENGTH == 1) - goto GENERALPOLL; + if (TNC->MSGLENGTH == 1) // General Poll + { + if (STATUSPOLL(TNC, Channel)) + return TRUE; // Status was reported - PollType = 0; + if (DATAPOLL(TNC, Channel)) + return TRUE; // Data Sent -// HE'S BEING AWKWARD, AND USING SPECIFIC DATA/STATUS POLL + PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL + PUTCHARx(TNC, 0); // NOTHING DOING - if (TNC->TONODEBUFFER[1] == '0') - goto DATAONLY; + return TRUE; + } - STATUSPOLL(TNC, Channel); + // HE'S BEING AWKWARD, AND USING SPECIFIC DATA/STATUS POLL -GENERALPOLL: + if (TNC->TONODEBUFFER[1] == '0') // Data only + { + if (DATAPOLL(TNC, Channel)) + return TRUE; // Data Sent + + PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL + PUTCHARx(TNC, 0); // NOTHING DOING + + return TRUE; + } + + // Must be Status only if (STATUSPOLL(TNC, Channel)) return TRUE; // Status was reported -DATAONLY: - - if (DATAPOLL(TNC, Channel)) - return TRUE; // Data Sent - - goto SENDHOSTOK; // NOTHING DOING - - -SENDHOSTOK: - PUTCHARx(TNC, TNC->MSGCHANNEL); // REPLY ON SAME CHANNEL PUTCHARx(TNC, 0); // NOTHING DOING @@ -3980,13 +4000,15 @@ and then the very next poll to channel 0 will get: */ + Decoded[Len] = 0; + iptr = strchr(&Decoded[10], ':'); // Info if present MONHEADER[0] = 4; // NO DATA FOLLOWS MONCURSOR = &MONHEADER[1]; if (strstr(Decoded, "NET/ROM") || strstr(Decoded, "NODES br") || strstr(Decoded, "INP3 RIF")) - pid = 0xcf; + return 0; //pid = 0xcf; else pid = 0xf0; @@ -4763,21 +4785,21 @@ nochange: return; } -int DOCOMMAND(struct TNCDATA * conn) +int DOCOMMAND(struct TNCDATA * TNC) { char Errbuff[500]; int i; // PROCESS NORMAL MODE COMMAND - sprintf(Errbuff, "BPQHOST Port %d Normal Mode CMD %s\n",conn->ComPort, conn->TONODEBUFFER); - OutputDebugString(Errbuff); + Debugprintf(Errbuff, "BPQHOST Port %d Normal Mode CMD %s\n",TNC->ComPort, TNC->TONODEBUFFER); + // IF ECHO ENABLED, ECHO IT - if (conn->ECHOFLAG) + if (TNC->ECHOFLAG) { - UCHAR * ptr = conn->TONODEBUFFER; + UCHAR * ptr = TNC->TONODEBUFFER; UCHAR c; do @@ -4786,36 +4808,35 @@ int DOCOMMAND(struct TNCDATA * conn) if (c == 0x1b) c = ':'; - PUTCHARx(conn, c); + PUTCHARx(TNC, c); } while (c != 13); } - if (conn->TONODEBUFFER[0] != 0x1b) + if (TNC->TONODEBUFFER[0] != 0x1b) goto NOTCOMMAND; // DATA IN NORMAL MODE - IGNORE - switch (toupper(conn->TONODEBUFFER[1])) + switch (toupper(TNC->TONODEBUFFER[1])) { case 'J': - if (conn->TONODEBUFFER[6] == 0x0d) - conn->MODE = 0; + if (TNC->TONODEBUFFER[6] == 0x0d) + TNC->MODE = 0; else - conn->MODE = conn->TONODEBUFFER[6] & 1; + TNC->MODE = TNC->TONODEBUFFER[6] & 1; - if (conn->MODE) - EnableAppl(conn); - else - DisableAppl(conn); - - if (conn->MODE) + if (TNC->MODE) { // send host mode ack // PUTCHARx(conn, 0); // PUTCHARx(conn, 0); - conn->CURSOR = (UCHAR *)&conn->TONODEBUFFER; + EnableDEDAppl(TNC); + } + else + { + DisableAppl(TNC); return 0; } @@ -4823,20 +4844,20 @@ int DOCOMMAND(struct TNCDATA * conn) case 'E': - conn->ECHOFLAG = conn->TONODEBUFFER[2] & 1; + TNC->ECHOFLAG = TNC->TONODEBUFFER[2] & 1; break; case 'I': { // Save call - char * Call = &conn->TONODEBUFFER[2]; + char * Call = &TNC->TONODEBUFFER[2]; - *(conn->CURSOR - 2) = 0; + *(TNC->CURSOR - 2) = 0; - for (i = 0; i <= conn->HOSTSTREAMS; i++) + for (i = 0; i <= TNC->HOSTSTREAMS; i++) { - strcpy(conn->Channels[i]->MYCall, Call); + strcpy(TNC->Channels[i]->MYCall, Call); } break;; @@ -4845,11 +4866,11 @@ int DOCOMMAND(struct TNCDATA * conn) // PARAMS COMMAND - RETURN FIXED STRING - PARAMPORT = conn->TONODEBUFFER[2]; + PARAMPORT = TNC->TONODEBUFFER[2]; for (i=0; i < LPARAMREPLY; i++) { - PUTCHARx(conn, PARAMREPLY[i]); + PUTCHARx(TNC, PARAMREPLY[i]); } break; @@ -4859,7 +4880,7 @@ int DOCOMMAND(struct TNCDATA * conn) // Return Channel Not Connected - PUTSTRING(conn, "* CHANNEL NOT CONNECTED *\r"); + PUTSTRING(TNC, "* CHANNEL NOT CONNECTED *\r"); default: @@ -4875,7 +4896,7 @@ int DOCOMMAND(struct TNCDATA * conn) NOTCOMMAND: - conn->CURSOR = (UCHAR *)&conn->TONODEBUFFER; + TNC->CURSOR = (UCHAR *)&TNC->TONODEBUFFER; return 0; @@ -4960,7 +4981,7 @@ int DATAPOLL(struct TNCDATA * TNC, struct StreamInfo * Channel) return TRUE; } - OutputDebugString("BPQHOST Mondata Flag Set with no data"); + Debugprintf("BPQHOST Mondata Flag Set with no data"); NOMONITOR: @@ -4968,7 +4989,7 @@ NOMONITOR: stamp = GetRaw(TNC->Channels[0]->BPQStream, (char *)&MONITORDATA, &Len, &Count); - if (Len) + while (Len) { // Use Normal Decode, then reformat to DED standard @@ -4978,14 +4999,18 @@ NOMONITOR: BOOL SaveMUI = MUIONLY; unsigned char Decoded[1000]; - IntSetTraceOptionsEx(TNC->MMASK, TNC->MTX, TNC->MCOM, 0); + IntSetTraceOptionsEx(TNC->MMASK, TNC->MTX, TNC->MCOM, TNC->MUIONLY); Len = IntDecodeFrame(&MONITORDATA, Decoded, stamp, TNC->MMASK, FALSE, FALSE); IntSetTraceOptionsEx(SaveMMASK, SaveMTX, SaveMCOM, SaveMUI); - if (Len) + if (Len) { - return ConvertToDEDMonFormat(TNC, Decoded, Len, &MONITORDATA); + if (ConvertToDEDMonFormat(TNC, Decoded, Len, &MONITORDATA)) + return 1; + + stamp = GetRaw(TNC->Channels[0]->BPQStream, (char *)&MONITORDATA, &Len, &Count); } + } return 0; } diff --git a/TelnetV6.c b/TelnetV6.c index fb55720..4a76bc2 100644 --- a/TelnetV6.c +++ b/TelnetV6.c @@ -99,6 +99,9 @@ extern HWND ClientWnd, FrameWnd; extern HANDLE hInstance; static RECT Rect; + +LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + #endif extern int REALTIMETICKS; @@ -126,9 +129,6 @@ static int ProcessLine(char * buf, int Port); VOID __cdecl Debugprintf(const char * format, ...); char * strlop(char * buf, char delim); -#ifndef LINBPQ -LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); -#endif int DisplaySessions(struct TNCINFO * TNC); int DoStateChange(int Stream); @@ -185,7 +185,9 @@ void DeleteTelnetLogFiles() int DeleteLogFile(char * Log) { - WIN32_FIND_DATA ffd; + + + WIN32_FIND_DATA ffd; char szDir[MAX_PATH]; char File[MAX_PATH]; @@ -290,7 +292,6 @@ int DeleteTelnetLogFiles() - void BuffertoNode(struct ConnectionInfo * sockptr, char * MsgPtr, int InputLen) { // Queue to Node. Data may arrive it large quatities, possibly exceeding node buffer capacity @@ -792,23 +793,12 @@ scanCTEXT: static int MaxStreams = 26; -struct TNCINFO * CreateTTYInfo(int port, int speed); -BOOL OpenConnection(int); -BOOL SetupConnection(int); -BOOL CloseConnection(struct TNCINFO * conn); -BOOL WriteCommBlock(struct TNCINFO * TNC); -BOOL DestroyTTYInfo(int port); void CheckRX(struct TNCINFO * TNC); VOID TelnetPoll(int Port); VOID ProcessTermModeResponse(struct TNCINFO * TNC); VOID DoTNCReinit(struct TNCINFO * TNC); VOID DoTermModeTimeout(struct TNCINFO * TNC); -VOID 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); VOID DoMonitor(struct TNCINFO * TNC, UCHAR * Msg, int Len); @@ -1672,6 +1662,7 @@ BOOL OpenSockets(struct TNCINFO * TNC) SOCKET OpenSocket6(struct TNCINFO * TNC, int port) { + struct sockaddr_in6 local_sin; /* Local socket - internet style */ struct sockaddr_in6 * psin; SOCKET sock; @@ -2774,7 +2765,7 @@ LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara // struct ConnectionInfo * ConnectionInfo; - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { TNC = TNCInfo[i]; if (TNC == NULL) @@ -3122,7 +3113,6 @@ LRESULT CALLBACK TelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara return DefMDIChildProc(hWnd, message, wParam, lParam); } - #endif int Socket_Accept(struct TNCINFO * TNC, SOCKET SocketId, int Port) diff --git a/UIARQ.c b/UIARQ.c index 05eface..6fdc5e3 100644 --- a/UIARQ.c +++ b/UIARQ.c @@ -25,7 +25,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "CHeaders.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); diff --git a/UZ7HODrv.c b/UZ7HODrv.c index 119d48e..90732a2 100644 --- a/UZ7HODrv.c +++ b/UZ7HODrv.c @@ -339,8 +339,13 @@ BOOL UZ7HOStartPort(struct PORTCONTROL * PORT) -VOID UZ7HOSuspendPort(struct TNCINFO * TNC) +VOID UZ7HOSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { + // We don't want to suspend port if on same TNC + + if (MasterPort[TNC->Port] == MasterPort[ThisTNC->Port]) + return; + TNC->PortRecord->PORTCONTROL.PortSuspended = TRUE; RegisterAPPLCalls(TNC, TRUE); } @@ -2315,6 +2320,16 @@ GotStream: if (_stricmp(RXHeader->callto, Appl) == 0) break; + + memcpy(Appl, APPL->APPLALIAS_TEXT, 10); + ptr=strchr(Appl, ' '); + + if (ptr) + *ptr = 0; + + if (_stricmp(RXHeader->callto, Appl) == 0) + break; + } if (App < 32) diff --git a/V4.c b/V4.c index 106446f..5e26ca9 100644 --- a/V4.c +++ b/V4.c @@ -40,7 +40,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "tncinfo.h" #include "bpq32.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); #define WSA_ACCEPT WM_USER + 1 #define WSA_DATA WM_USER + 2 diff --git a/VARA.c b/VARA.c index 1cccc44..cf6dd7d 100644 --- a/VARA.c +++ b/VARA.c @@ -34,7 +34,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #endif -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); #define SD_RECEIVE 0x00 @@ -341,10 +341,17 @@ static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) case 7: // approx 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; // Check session limit timer - if ((STREAM->Connecting || STREAM->Connected) && !STREAM->Disconnecting) { if (TNC->SessionTimeLimit && STREAM->ConnectTime && time(NULL) > (TNC->SessionTimeLimit + STREAM->ConnectTime)) @@ -970,7 +977,7 @@ static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL) return Len; } -VOID VARASuspendPort(struct TNCINFO * TNC) +VOID VARASuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { VARASendCommand(TNC, "LISTEN OFF\r", TRUE); } diff --git a/Versions.h b/Versions.h index 74a69fb..fe9fcb6 100644 --- a/Versions.h +++ b/Versions.h @@ -10,8 +10,8 @@ #endif -#define KVers 6,0,23,71 -#define KVerstring "6.0.23.71\0" +#define KVers 6,0,23,76 +#define KVerstring "6.0.23.76\0" #ifdef CKernel diff --git a/WINMOR.c b/WINMOR.c index 33dd3a6..8bb62ae 100644 --- a/WINMOR.c +++ b/WINMOR.c @@ -76,8 +76,8 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #endif -int (WINAPI FAR *GetModuleFileNameExPtr)(); -int (WINAPI FAR *EnumProcessesPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *EnumProcessesPtr)(); #define SD_RECEIVE 0x00 @@ -1339,7 +1339,7 @@ VOID SuspendOtherPorts(struct TNCINFO * ThisTNC) if (rxInterlock == 0 || txInterlock == 0) return; - for (i=1; i<33; i++) + for (i = 1; i <= MAXBPQPORTS; i++) { TNC = TNCInfo[i]; if (TNC == NULL) @@ -1350,7 +1350,7 @@ VOID SuspendOtherPorts(struct TNCINFO * ThisTNC) if (rxInterlock == TNC->RXRadio || txInterlock == TNC->TXRadio) // Same Group if (TNC->SuspendPortProc) - TNC->SuspendPortProc(TNC); + TNC->SuspendPortProc(TNC, ThisTNC); } } @@ -1366,7 +1366,7 @@ VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC) if (rxInterlock == 0 && txInterlock == 0) return; - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { TNC = TNCInfo[i]; if (TNC == NULL) @@ -1381,7 +1381,7 @@ VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC) } } -VOID WinmorSuspendPort(struct TNCINFO * TNC) +VOID WinmorSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC) { if (TNC->CONNECTED) send(TNC->TCPSock, "CODEC FALSE\r\n", 14, 0); diff --git a/WinRPR.c b/WinRPR.c index 391edc9..f12971e 100644 --- a/WinRPR.c +++ b/WinRPR.c @@ -34,7 +34,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include "CHeaders.h" -int (WINAPI FAR *GetModuleFileNameExPtr)(); +extern int (WINAPI FAR *GetModuleFileNameExPtr)(); extern int (WINAPI FAR *EnumProcessesPtr)(); #include "tncinfo.h" @@ -98,7 +98,7 @@ BOOL KAMStartPort(struct PORTCONTROL * PORT); BOOL KAMStopPort(struct PORTCONTROL * PORT); -extern VOID TRKSuspendPort(struct TNCINFO * TNC); +extern VOID TRKSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC); extern VOID TRKReleasePort(struct TNCINFO * TNC); extern VOID CloseDebugLogFile(int Port); extern BOOL OpenDebugLogFile(int Port); @@ -495,6 +495,14 @@ ok: // 100 mS Timer. + // 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; + // See if waiting for connect after changing MYCALL if (TNC->SlowTimer) diff --git a/asmstrucs.h b/asmstrucs.h index 5d11572..29dcb64 100644 --- a/asmstrucs.h +++ b/asmstrucs.h @@ -730,6 +730,9 @@ typedef struct KISSINFO struct KISSINFO * POLLPOINTER; // LAST GROUP POLLED int POLLED; // SET WHEN POLL RECEIVED + UCHAR * KISSCMD; // Commands to be sent when port opened + int KISSCMDLEN; + // UCHAR WIN32INFO[16]; // FOR WINDOWS DRIVER } *PKISSINFO; @@ -1158,6 +1161,7 @@ struct TNCDATA BOOL MCON; // TRACE MODE FLAGS BOOL MCOM; BOOL MALL; + BOOL MUIONLY; BOOL AUTOLF; // Add LF after CR BOOL BBSMON; // SPECIAL SHORT MONITOR FOR BBS BOOL MTX; // MONITOR TRANSMITTED FRAMES @@ -1210,6 +1214,8 @@ struct TNCDATA int MONLENGTH; int MONFLAG; + time_t LastDEDPollTime; // To detect lost host program + // Kantronics Fields int RXBPtr; diff --git a/bpqaxip.c b/bpqaxip.c index d4f6579..690f96d 100644 --- a/bpqaxip.c +++ b/bpqaxip.c @@ -1344,7 +1344,7 @@ LRESULT FAR PASCAL ConfigWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lPa BOOL UDPFlag, BCFlag; struct AXIPPORTINFO * PORT; - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { PORT = Portlist[i]; if (PORT == NULL) @@ -1648,7 +1648,7 @@ LRESULT CALLBACK MHWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam int i; - for (i=1; i<33; i++) + for (i=1; i <= MAXBPQPORTS; i++) { PORT = Portlist[i]; if (PORT == NULL) diff --git a/bpqchat.c b/bpqchat.c index 04e350d..6739c64 100644 --- a/bpqchat.c +++ b/bpqchat.c @@ -198,6 +198,10 @@ char WindowSize[32]; extern config_setting_t * group; +char RTFHeader[4000]; +int RTFHddrLen; + + int ProgramErrors = 0; // Forward declarations of functions included in this code module: @@ -1255,9 +1259,8 @@ int RefreshMainWindow() #define VERSION_MAJOR 2 #define VERSION_MINOR 0 -SOCKADDR_IN local_sin; /* Local socket - internet style */ - -PSOCKADDR_IN psin; +static SOCKADDR_IN local_sin; /* Local socket - internet style */ +static PSOCKADDR_IN psin; SOCKET sock; diff --git a/bpqchat.h b/bpqchat.h index db46fd3..955ac48 100644 --- a/bpqchat.h +++ b/bpqchat.h @@ -483,9 +483,8 @@ typedef struct user_t #define MAXLINES 1000 #define LINELEN 200 -char RTFHeader[4000]; - -int RTFHddrLen; +extern char RTFHeader[4000]; +extern int RTFHddrLen; struct ConsoleInfo { diff --git a/bpqmail.h b/bpqmail.h index eb7323a..ab1052f 100644 --- a/bpqmail.h +++ b/bpqmail.h @@ -350,6 +350,17 @@ struct B2RestartData int Count; // Give up if too many restarts }; +//------ TAJ ----- +typedef struct PGARGS +{ + CIRCUIT * conn; + struct UserInfo * user; + char InputBuffer[80]; + int Len; +}RUNPGARGS, *RUNPGARGS_PTR; + +//--------------- + #pragma pack(1) struct TempUserInfo @@ -375,7 +386,11 @@ struct TempUserInfo int LLCount; // Number still to send in List Last N int UpdateLatest; // if set, save last listed as latest BOOL IncludeKilled; // Show Killed Messages if SYSOP - + //--- TAJ --- + int PG_INDEX; // current index of PG server + int PG_SERVER; // PG server to run + RUNPGARGS_PTR RUNPGPARAMS; // pointer to RUNPGARGS for dealloc + //----------- }; #define PMSG 1 @@ -759,9 +774,8 @@ struct FBBHeaderLine #define MAXLINES 1000 #define LINELEN 200 -char RTFHeader[4000]; - -int RTFHddrLen; +extern char RTFHeader[4000]; +extern int RTFHddrLen; struct ConsoleInfo { @@ -1577,7 +1591,7 @@ extern BOOL WarnNoRoute; extern BOOL SendPtoMultiple; extern BOOL Localtime; -struct ConsoleInfo * ConsHeader[2]; +extern struct ConsoleInfo * ConsHeader[2]; extern BOOL NeedHomeBBS; extern char ConfigName[250]; diff --git a/bpqmail.h.bak b/bpqmail.h.bak new file mode 100644 index 0000000..a153c3f --- /dev/null +++ b/bpqmail.h.bak @@ -0,0 +1,1604 @@ +#ifndef WINVER // Allow use of features specific to Windows XP or later. +#define WINVER 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. +#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later. +#endif + +#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later. +#define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE. +#endif + + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define _CRT_SECURE_NO_DEPRECATE +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#define LIBCONFIG_STATIC +#include + +#include "compatbits.h" + +#ifndef LINBPQ +#include "bpq32.h" +#include "BPQMailrc.h" +#include "dbghelp.h" +#else +#include "CHeaders.h" +#endif + +#include "asmstrucs.h" + + +#define NEWROUTING + + + +// Standard __except handler for try/except + +VOID CheckProgramErrors(); +VOID WriteMiniDump(); + +extern int ProgramErrors; + +extern struct _EXCEPTION_POINTERS exinfox; + +#ifdef WIN32 +Dump_Process_State(struct _EXCEPTION_POINTERS * exinfo, char * Msg); + +#define My__except_Routine(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + CheckProgramErrors();\ + WriteMiniDump();\ +} + + +/* +#define My__except_Routine(Message) \ +__except(memcpy(&exinfox, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Dump_Process_State(&exinfox, Message);\ + CheckProgramErrors();\ +} + +#define My__except_RoutineWithDisconnect(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + FreeSemaphore(&ChatSemaphore);\ + if (conn->BPQStream < 0)\ + CloseConsole(conn->BPQStream);\ + else\ + Disconnect(conn->BPQStream);\ +} +*/ +#define My_except_RoutineWithDiscBBS(Message) \ +__except(memcpy(&exinfo, GetExceptionInformation(), sizeof(struct _EXCEPTION_POINTERS)), EXCEPTION_EXECUTE_HANDLER)\ +{\ + Debugprintf("MAILCHAT *** Program Error %x at %x in %s EAX %x EBX %x ECX %x EDX %x ESI %x EDI %x",\ + exinfo.ExceptionRecord->ExceptionCode, exinfo.ExceptionRecord->ExceptionAddress, Message,\ + exinfo.ContextRecord->Eax, exinfo.ContextRecord->Ebx, exinfo.ContextRecord->Ecx,\ + exinfo.ContextRecord->Edx, exinfo.ContextRecord->Esi, exinfo.ContextRecord->Edi);\ + if (conn->BPQStream < 0)\ + CloseConsole(conn->BPQStream);\ + else\ + Disconnect(conn->BPQStream);\ + CheckProgramErrors();\ +} +#endif +#define MAXUSERNAMELEN 6 + +#define WSA_ACCEPT WM_USER + 1 +#define WSA_CONNECT WM_USER + 2 +#define WSA_DATA WM_USER + 3 +#define NNTP_ACCEPT WM_USER + 4 +#define NNTP_DATA WM_USER + 5 + +#ifdef _DEBUG + +VOID * _malloc_dbg_trace(int len, int type, char * file, int line); + +#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define calloc(c, s) _calloc_dbg(c, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define realloc(p, s) _realloc_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define _recalloc(p, c, s) _recalloc_dbg(p, c, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define _expand(p, s) _expand_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__) +#define free(p) _free_dbg(p, _NORMAL_BLOCK) +#define _strdup(s) _strdup_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) + + +#define zalloc(s) _zalloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) +#else +#define zalloc(s) _zalloc(s) +#endif + +#ifdef LINBPQ + +#undef zalloc +#define zalloc _zalloc + +#endif + +VOID * _zalloc_dbg(size_t len, int type, char * file, int line); + +#define LOG_BBS 0 +#define LOG_CHAT 1 +#define LOG_TCP 2 +#define LOG_DEBUG_X 3 + + +//Chat Duplicate suppression Code + +#define MAXDUPS 10 // Number to keep +#define DUPSECONDS 5 // TIme to Keep + +struct DUPINFO +{ + time_t DupTime; + char DupUser[10]; + char DupText[100]; +}; + + +struct UserRec +{ + char * Callsign; + char * UserName; + char * Password; +}; + + + +typedef struct ConnectionInfo_S +{ + struct ConnectionInfo_S *next; + PROC *proc; + UCHAR RadioOnlyMode; // T or R flag for Radio Only mode. + + int Number; // Number of record - for Connections display + BOOL Active; + int BPQStream; + int paclen; + UCHAR Callsign[11]; // Station call including SSID + BOOL GotHeader; + UCHAR InputMode; // Line by Line or Binary or YAPP + + UCHAR * InputBuffer; + int InputBufferLen; + int InputLen; // Data we have already = Offset of end of an incomplete packet; + + struct UserInfo * UserPointer; + int Retries; + int LoginState; // 1 = user ok, 2 = password ok + int Flags; + + // Data to the user is kept in a malloc'd buffer. This can be appended to, + // and data sucked out under both terminal and system flow control. PACLEN is + // enfored when sending to node. + + UCHAR * OutputQueue; // Messages to user + int OutputQueueLength; // Total Malloc'ed size. Also Put Pointer for next Message + int OutputGetPointer; // Next byte to send. When Getpointer = Queue Length all is sent - free the buffer and start again. + + int CloseAfterFlush; // Close session when all sent. Set to 100ms intervals to wait. + + int ErrorCount; // Invalid Command count + BOOL Paging; // Set if user wants paging + int LinesSent; // Count when paging + int PageLen; // Lines per page + + UCHAR * MailBuffer; // Mail Message being received + UCHAR * CopyBuffer; // Mail Message being forwarded + int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct + + long lastmsg; // Last Listed. Stored here, updated in user record only on clean close + BOOL sysop; // Set if user is authenticated as a sysop + BOOL Secure_Session; // Set if Local Terminal, or Telnet connect with SYSOP status + UINT BBSFlags; // Set if defined as a bbs and SID received + struct MsgInfo * TempMsg; // Header while message is being received + struct MsgInfo * FwdMsg; // Header while message is being forwarded + + char ** To; // May be several Recipients + int ToCount; + + int BBSNumber; // The BBS number (offset into bitlist of BBSes to forward a message to + int NextMessagetoForward; // Next index to check in forward cycle + BOOL BPQBBS; // Set if SID indicates other end is BPQ + char MSGTYPES[20]; // Any MSGTYPEFLAGS + BOOL SendT; // Send T messages + BOOL SendP; // Send P messages + BOOL SendB; // Send Bulls + BOOL SendWL2KFW; // send ;FW: + int MaxBLen; // Max Size for this session + int MaxPLen; // Max Size for this session + int MaxTLen; // Max Size for this session + BOOL DoReverse; // Request Reverse Forward + char LastForwardType; // Last type of messages forwarded + struct FBBHeaderLine * FBBHeaders; // The Headers from an FFB forward block + char FBBReplyChars[36]; //The +-=!nnnn chars for the 5 proposals + int FBBReplyIndex; // current Reply Pointer + int FBBIndex; // current propopsal number + int RestartFrom; // Restart position + BOOL NeedRestartHeader; // Set if waiting for 6 byte restart header + BOOL DontSaveRestartData; // Set if corrupt data received + BOOL FBBMsgsSent; // Messages need to be maked as complete when next command received + UCHAR FBBChecksum; // Header Checksum + BOOL OpenBCM; // OpenBCM mode (escape -xFF chars) + BOOL InTelnetExcape; // Last Char was 0xff + BOOL LocalMsg; // Set if current Send command is for a local user + BOOL NewUser; // Set if first time user has accessed BBS + BOOL Paclink; // Set if receiving messages from Paclink + BOOL RMSExpress; // Set if receiving messages from RMS Express + BOOL WL2K; // Set if communicating with a CMS + BOOL PAT; // Set if communicating with PAT + char ** PacLinkCalls; // Calls we are getting messages for + BOOL SkipPrompt; // Set if a remote node sends a > at the end of his CTEXT + BOOL SkipConn; // Node sends "connected" in its CTEXT + int Watchdog; // Hung Circuit Detect. + int SessType; // BPQ32 sesstype bits + +#define Sess_L2LINK 1 +#define Sess_SESSION 2 +#define Sess_UPLINK 4 +#define Sess_DOWNLINK 8 +#define Sess_BPQHOST 0x20 +#define Sess_PACTOR 0x40 + + HANDLE DebugHandle; // File Handle for session-based debugging + + char ARQFilename[256]; // Filename from ARQ:FILE:: Header + int ARQClearCount; // To make sure queues are flushed when sending + + int SIDResponseTimer; // Used to detect incomplete handshake + + char PQChallenge[20]; // Secure User logon challange + char SecureMsg[20]; // CMS Secure Signon Response + int MCastListenTime; // Time to run session for + + int YAPPLen; // Bytes sent/received of YAPP Message + long YAPPDate; // Date for received file - if set enables YAPPC + + int SyncCompressedLen; + int SyncXMLLen; + int SyncMsgLen; + char * SyncHost; // Saved so can send "request sync" + int SyncPort; + UCHAR * SyncMessage; // Compressed SYNC message to send + + // These are used to detect CRLF split over a packet boundary + int usingCR; // Session is (normally) using CR as terminator + int lastLineEnd; // Terminator for current line + + struct ConnectionInfo_S * SysopChatStream; // Stream sysop is chatting to + +} ConnectionInfo, CIRCUIT; + +// Flags Equates + +#define GETTINGUSER 1 +#define GETTINGBBS 2 +#define CHATMODE 4 +#define GETTINGTITLE 8 +#define GETTINGMESSAGE 16 +#define CHATLINK 32 // Link to another Chat Node +#define SENDTITLE 64 +#define SENDBODY 128 +#define WAITPROMPT 256 // Waiting for prompt after message +#define PROPOSINGSYNCMSG 512 // Sent proposal to SYNC, waiting response +#define SENDINGSYNCMSG 1024 // Sent message to SYNC, waiting response +#define REQUESTINGSYNC 2048 +#define GETTINGSYNCMESSAGE 4096 // Receiving body of a SYNC message + +// BBSFlags Equates + +#define BBS 1 +#define FBBForwarding 2 +#define FBBCompressed 4 +#define FBBB1Mode 8 +#define FBBB2Mode 16 +#define RunningConnectScript 32 +#define MBLFORWARDING 64 // MBL Style Frwarding- waiting for OK/NO or Prompt following message +#define TEXTFORWARDING 128 // Plain Text forwarding +#define OUTWARDCONNECT 256 // We connected to them +#define FLARQMODE 512 // Message from FLARQ +#define FLARQMAIL 1024 // Sending FLARQ Format Message +#define ARQMAILACK 2048 // Waiting for all data to be acked +#define NEEDLF 4096 // Add LF to forward script commands (fro Telnet +#define MCASTRX 8192 // Stream in Multicast RX Mode +#define DISCONNECTING 16384 // Disconnect sent to Node +#define YAPPTX 0x008000 // Sending YAPP file +#define SYSOPCHAT 0x010000 // Chatting to BBS console +#define WINLINKRO 0x020000 // WL2K RO (no J in SID) +#define SYNCMODE 0x040000 // RMS RELAY SYNC +#define MFJMODE 0x080000 // MFJ PMS +#define NEWPACCOM 0x100000 // PACCOM PMS 3.2 +#define SETCALLTOSENDER 0x200000 // Set calling call to message sender + + +struct FBBRestartData +{ + struct MsgInfo * TempMsg; // Header while message is being received + struct UserInfo * UserPointer; + UCHAR * MailBuffer; // Mail Message being received + int MailBufferSize; // Total Malloc'ed size. Actual size in in Msg Struct + int Count; // Give up if too many restarts +}; + +// We need to keep the B2Message file for B2 messages we are sending until the messages is acked, so +// we can restart it. Otherwise the file may change, resulting in a checksum error + + +struct B2RestartData +{ + int CSize; // Compresses Size (B2 proto) + UCHAR * CompressedMsg; // Compressed Body fo B2 + struct MsgInfo * FwdMsg; + struct UserInfo * UserPointer; + int Count; // Give up if too many restarts +}; + +#pragma pack(1) + +struct TempUserInfo +{ + int LastAuthCode; // Protect against playback attack + + // Fields used to allow interrupting and resuming a paged listing + + BOOL ListActive; // Doing a list + BOOL ListSuspended; // Paused doing a list + int LastListedInPagedMode; + char LastListCommand[80]; + char LastListParams[80]; + int LinesSent; + char SendFullFrom; + char ListType; + char ListDirn; + char ListStatus; + char ListSelector; // < > @ etc + + int ListRangeStart; + int ListRangeEnd; + int LLCount; // Number still to send in List Last N + int UpdateLatest; // if set, save last listed as latest + BOOL IncludeKilled; // Show Killed Messages if SYSOP + +}; + +#define PMSG 1 +#define BMSG 2 +#define TMSG 3 + +struct OldUserInfo +{ + // Old format - without message type specific traffic counts + + char Call[10]; // Connected call without SSID +// indicat relai[8]; /* 64 Digis path */ + int lastmsg; /* 4 Last L number */ + int ConnectsIn; /* 4 Number of connexions in*/ + int TimeLastConnected; //Last connexion date */ +// long lastyap __a2__ ; /* 4 Last YN date */ + ULONG flags ; /* 4 Flags */ + + UCHAR PageLen; // Lines Per Page + UCHAR lang ; /* 1 Language */ + + int Xnewbanner; /* 4 Last Banner date */ + short Xdownload ; /* 2 download size (KB) = 100 */ + char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) + char BBSNumber; // BBS Bitmap Index Number + struct BBSForwardingInfo * ForwardingInfo; + struct UserInfo * BBSNext; // links BBS record + struct TempUserInfo * Temp; // Working Fields - not saved in user file + char xfree[6]; /* 6 Spare */ + char Xtheme; /* 1 Current topic */ + + char Name[18]; /* 18 1st Name */ + char Address[61]; /* 61 Address */ + + // Stats. Was City[31]; /* 31 City */ + + int MsgsReceived; + int MsgsSent; + int MsgsRejectedIn; // Messages we reject + int MsgsRejectedOut; // Messages Rejectd by other end + int BytesForwardedIn; + int BytesForwardedOut; + int ConnectsOut; // Forwarding Connects Out + + USHORT RMSSSIDBits; // SSID's to poll in RMS + + char Spare1; + + char HomeBBS[41]; /* 41 home BBS */ + char QRA[7]; /* 7 Qth Locator */ + char pass[13]; /* 13 Password */ + char ZIP[9]; /* 9 Zipcode */ + BOOL spare; +} ; /* Total : 360 bytes */ + +struct MsgStats +{ + int ConnectsIn; /* 4 Number of connexions in*/ + int ConnectsOut; // Forwarding Connects Out + + // Stats saveed by message type + + int MsgsReceived[4]; + int MsgsSent[4]; + int MsgsRejectedIn[4]; // Messages we reject + int MsgsRejectedOut[4]; // Messages Rejectd by other end + int BytesForwardedIn[4]; + int BytesForwardedOut[4]; +}; + +struct UserInfo +{ + // New Format - with stats maintained by message type and unused fields removed. + + char Call[10]; // Connected call without SSID + + int Length; // To make subsequent format changes easier + + int lastmsg; /* 4 Last L number */ + int xTimeLastConnected; //Last connexion date */ + ULONG flags ; /* 4 Flags */ + + UCHAR PageLen; // Lines Per Page + + char POP3Locked ; // Nonzero if POP3 server has locked this user (stops other pop3 connections, or BBS user killing messages) + unsigned char BBSNumber; // BBS Bitmap Index Number + struct BBSForwardingInfo * ForwardingInfo; + struct UserInfo * BBSNext; // links BBS record + struct TempUserInfo * Temp; // Working Fields - not saved in user file + char Name[18]; /* 18 1st Name */ + char Address[61]; /* 61 Address */ + + USHORT RMSSSIDBits; // SSID's to poll in RMS + + char HomeBBS[41]; /* 41 home BBS */ + char QRA[7]; /* 7 Qth Locator */ + char pass[13]; /* 13 Password */ + char ZIP[9]; /* 9 Zipcode */ + + struct MsgStats Total; + struct MsgStats Last; + + char CMSPass[16]; // For Secure Signon + int WebSeqNo; + + long long TimeLastConnected; //Last connection date */ + + char Filler[44 - 8]; // So we can add a few fields wirhout another resize +}; + +// flags equates + +#define F_Excluded 0x0001 +#define F_GGG 0x0002 +#define F_Expert 0x0004 +#define F_SYSOP 0x0008 +#define F_BBS 0x0010 +#define F_RMSREDIRECT 0x0020 +#define F_BBB 0x0040 +#define F_CCC 0x0080 +#define F_DDD 0x0100 +#define F_EEE 0x0200 +#define F_FFF 0x0400 +#define F_PMS 0x0800 +#define F_EMAIL 0x1000 +#define F_HOLDMAIL 0x2000 +#define F_POLLRMS 0x4000 +#define F_SYSOP_IN_LM 0x8000 +#define F_Temp_B2_BBS 0x00010000 // "Winlink Express User" +#define F_NOWINLINK 0x00020000 // Don't add Winlink.org +#define F_NOBULLS 0x00040000 +#define F_NTSMPS 0x00080000 +#define F_APRSMFOR 0x00100000 // Send APRS message for new mail +#define F_APRSSSID 0xF0000000 // (Top 4 Bits + + +struct Override +{ + char * Call; + int Days; +}; + +struct ALIAS +{ + char * Alias; + char * Dest; +}; + +typedef struct _MESSAGEX +{ +// BASIC LINK LEVEL MESSAGE BUFFER LAYOUT + + struct _MESSAGEX * CHAIN; + + UCHAR PORT; + USHORT LENGTH; + + UCHAR DEST[7]; + UCHAR ORIGIN[7]; + +// MAY BE UP TO 56 BYTES OF DIGIS + + UCHAR CTL; + UCHAR PID; + UCHAR DATA[256]; + UCHAR DIGIS[56]; // Padding in case we have digis + +}MESSAGEX, *PMESSAGEX; + + +#pragma pack() + +// Message Database Entry. Designed to be compatible with FBB + +#define NBBBS 160 // Max BBSes we can forward to. Must be Multiple of 8, and must be 80 for FBB compatibliliy +#define NBMASK NBBBS/8 // Number of bytes in Forward bitlists. + +#pragma pack(1) + +struct OldMsgInfo +{ + char type ; + char status ; + int number ; + int length ; + int datereceived; + char bbsfrom[7] ; // ? BBS we got it from ? + char via[41] ; + char from[7] ; + char to[7] ; + char bid[13] ; + char title[61] ; + char bin; + int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp + + UCHAR B2Flags; + + char free[4]; + unsigned short nblu; + int theme ; + time_t datecreated ; + time_t datechanged ; + char fbbs[10] ; + char forw[10] ; + char emailfrom[41]; +} ; + + +struct MsgInfo +{ + char type; + char status; + int number; + int length; + int xdatereceived; + char bbsfrom[7]; // ? BBS we got it from ? + char via[41]; + char from[7]; + char to[7]; + char bid[13]; + char title[61]; + int nntpnum; // Number within topic (ie Bull TO Addr) - used for nntp + + UCHAR B2Flags; + + #define B2Msg 1 // Set if Message File is a formatted B2 message + #define Attachments 2 // Set if B2 message has attachments + #define FromPaclink 4 + #define FromCMS 8 + #define FromRMSExpress 16 + #define RadioOnlyMsg 32 // Received using call-T + #define RadioOnlyFwd 64 // Received using call-R + + int xdatecreated; + int xdatechanged; + UCHAR fbbs[NBMASK]; + UCHAR forw[NBMASK]; + char emailfrom[41]; + char Locked; // Set if selected for sending (NTS Pickup) + char Defered; // FBB response '=' received + UCHAR UTF8; // Set if Message is in UTF8 (ie from POP/SMTP) + +// For 64 bit time_t compatibility define as long long +// (so struct is same with 32 or 64 bit time_t) + + long long datereceived; + long long datecreated; + long long datechanged; + + char Spare[61 - 24]; // For future use +} ; + +#define MSGTYPE_B 0 +#define MSGTYPE_P 1 + +#define MSGSTATUS_N 0 +#define MSGSTATUS_Y 1 +#define MSGSTATUS_F 2 +#define MSGSTATUS_K 3 +#define MSGSTATUS_H 4 +#define MSGSTATUS_D 5 +#define MSGSTATUS_$ 6 + +struct NNTPRec +{ + // Used for NNTP access to Bulls + + struct NNTPRec * Next; // Record held in chain, so can be held sorted + char NewsGroup[64]; // = Bull TO.at field + int FirstMsg; // Lowest Number + int LastMsg; // Highest Number + int Count; // Active Msgs + time_t DateCreated; // COntains Creation Date of First Bull in Group +}; + + +typedef struct { + char mode; + char BID[13]; + union + { /* array named screen */ + struct + { + unsigned short msgno; + unsigned short timestamp; + }; + CIRCUIT * conn; + } u; +} BIDRec, *BIDRecP; + + +typedef struct WPDBASE{ /* 194 bytes */ + char callsign[7]; + char name[13]; + unsigned char Type; + unsigned char changed; + unsigned short seen; + long long last_modif; + long long last_seen; + char first_homebbs[41]; + char secnd_homebbs[41]; + char first_zip[9]; + char secnd_zip[9]; + char first_qth[31]; + char secnd_qth[31]; +} WPRec, * WPRecP; + +#pragma pack() + +struct FWDBAND +{ + time_t FWDStartBand; + time_t FWDEndBand; +}; + + + +struct BBSForwardingInfo +{ + // Holds info for forwarding + + BOOL Enabled; // Forwarding Enabled + char ** ConnectScript; // Main Connect Script + char ** TempConnectScript; // Used with FWD command. + int ScriptIndex; // Next line in script + BOOL MoreLines; // Set until script is finsihed + + char ** TOCalls; // Calls in to field + char ** ATCalls; // Calls in ATBBS field + char ** HaddressesP; // Heirarchical Addresses for Personals to forward to (as stored) + char *** HADDRSP; // Heirarchical Addresses for Personals to forward to + char ** Haddresses; // Heirarchical Addresses to forward to (as stored) + char *** HADDRS; // Heirarchical Addresses to forward to + int * HADDROffet; // Elements added to complete the HR. At least n+1 must match to forward + char ** FWDTimes; // Time bands to forward + struct FWDBAND ** FWDBands; + int MsgCount; // Messages for this BBS + BOOL ReverseFlag; // Set if BBS wants to poll for reverse forwarding + BOOL Forwarding; // Forward in progress + int MaxFBBBlockSize; + BOOL AllowBlocked; // Allow FBB Blocked + BOOL AllowCompressed; // Allow FBB Compressed + BOOL AllowB1; // Enable B1 + BOOL AllowB2; // Enable B2 + BOOL SendCTRLZ; // Send Ctrl/z instead of /ex + BOOL PersonalOnly; // Only Forward Personals + BOOL SendNew; // Forward new messages immediately + int FwdInterval; + int RevFwdInterval; + int FwdTimer; + time_t LastReverseForward; + char *BBSHA; // HA of BBS + char ** BBSHAElements; // elements of HA of BBS + int ConTimeout; +// char UserCall[10]; // User we are forwarding on behalf of (Currently only for RMS) +// int UserIndex; // index of User we are forwarding on behalf of (Currently only for RMS) +}; + + +struct FBBHeaderLine +{ + // Holds the info from the (up to) 5 headers presented at the start of a Forward Block + + char Format; // Ascii or Binary + char MsgType; // P B etc + char From[7]; // Sender + char ATBBS[41]; // BBS of recipient (@ Field) + char To[7]; // Recipient + char BID[13]; + int Size; + int CSize; // Compresses Size (B2 proto) + BOOL B2Message; // Set if an FC type + UCHAR * CompressedMsg; // Compressed Body fo B2 + struct MsgInfo * FwdMsg; // Header so we can mark as complete +}; + +#define MAXSTACK 20 +//#define MAXLINE 10000 +#define INPUTLEN 512 + +#define MAXLINES 1000 +#define LINELEN 200 + +char RTFHeader[4000]; + +int RTFHddrLen; + +struct ConsoleInfo +{ + struct ConsoleInfo * next; + CIRCUIT * Console; + int BPQStream; + WNDPROC wpOrigInputProc; + HWND hConsole; + HWND hwndInput; + HWND hwndOutput; + HMENU hMenu; // handle of menu + RECT ConsoleRect; + RECT OutputRect; + + int Height, Width, LastY; + + int ClientHeight, ClientWidth; + char kbbuf[INPUTLEN]; + int kbptr; + + char * readbuff; // Malloc'ed + int readbufflen; // Current Length + char * KbdStack[MAXSTACK]; + + int StackIndex; + + BOOL Bells; + BOOL FlashOnBell; // Flash instead of Beep + BOOL StripLF; + + BOOL WarnWrap; + BOOL FlashOnConnect; + BOOL WrapInput; + BOOL CloseWindowOnBye; + + unsigned int WrapLen; + int WarnLen; + int maxlinelen; + + int PartLinePtr; + int PartLineIndex; // Listbox index of (last) incomplete line + + DWORD dwCharX; // average width of characters + DWORD dwCharY; // height of characters + DWORD dwClientX; // width of client area + DWORD dwClientY; // height of client area + DWORD dwLineLen; // line length + int nCaretPosX; // horizontal position of caret + int nCaretPosY; // vertical position of caret + + COLORREF FGColour; // Text Colour + COLORREF BGColour; // Background Colour + COLORREF DefaultColour; // Default Text Colour + + int CurrentLine; // Line we are writing to in circular buffer. + + int Index; + BOOL SendHeader; + BOOL Finished; + + char OutputScreen[MAXLINES][LINELEN]; + + int Colourvalue[MAXLINES]; + int LineLen[MAXLINES]; + + int CurrentColour; + int Thumb; + int FirstTime; + BOOL Scrolled; // Set if scrolled back + int RTFHeight; // Height of RTF control in pixels + +}; + + +struct MSESSION +{ + struct MSESSION * Next; + unsigned int Key; + char * FileName; + char * OrigTimeStamp; + unsigned char * Message; + int MessageLen; + unsigned char * BlockList; + char * ID; + int BlockSize; + int BlockCount; + int BlocksReceived; + BOOL Completed; + time_t Created; + time_t LastUpdated; + int Index; // Line in Display +}; + +VOID __cdecl nprintf(CIRCUIT * conn, const char * format, ...); +char * strlop(char * buf, char delim); +int rt_cmd(CIRCUIT *circuit, char * Buffer); +CIRCUIT *circuit_new(CIRCUIT *circuit, int flags); +VOID BBSputs(CIRCUIT * conn, char * buf); +VOID FBBputs(CIRCUIT * conn, char * buf); +void makelinks(void); +VOID * _zalloc(size_t len); +VOID FreeChatMemory(); +VOID ChatTimer(); +VOID nputs(CIRCUIT * conn, char * buf); +VOID node_close(); +VOID removelinks(); +VOID SetupChat(); +VOID SendChatLinkStatus(); +VOID ClearChatLinkStatus(); +VOID Send_MON_Datagram(UCHAR * Msg, DWORD Len); + +#define Connect(stream) SessionControl(stream,1,0) +#define Disconnect(stream) SessionControl(stream,2,0) +#define ReturntoNode(stream) SessionControl(stream,3,0) +#define ConnectUsingAppl(stream, appl) SessionControl(stream, 0, appl) + +int EncryptPass(char * Pass, char * Encrypt); +VOID DecryptPass(char * Encrypt, unsigned char * Pass, unsigned int len); + +// TCP Connections. FOr the moment SMTP or POP3 + +typedef struct SocketConnectionInfo +{ + struct SocketConnectionInfo * Next; + int Number; // Number of record - for Connections display + SOCKET socket; + SOCKADDR_IN sin; + int Type; // SMTP or POP3 + BOOL AMPR; // Set if sending to an AMPR.ORG server + char FromDomain[50]; // Domain we are sending from + struct UserInfo * bbs; // BBS dor forwarding to AMPR + int State; // Transaction State Machine + UCHAR CallSign[10]; + UCHAR TCPBuffer[3000]; // For converting byte stream to messages + int InputLen; // Data we have alreasdy = Offset of end of an incomplete packet; + + char * MailFrom; // Envelope Sender and Receiver + char ** RecpTo; // May be several Recipients + int Recipients; + + UCHAR * MailBuffer; // Mail Message being received. malloc'ed as needed + int MailBufferSize; // Total Malloc'ed size. Actual size is in MailSize + int MailSize; + int Flags; + + struct UserInfo * POP3User; + struct MsgInfo ** POP3Msgs; // Header List of messages for this uaer + int POP3MsgCount; // No of Messages + int POP3MsgNum; // Sequence number of message being received + + struct MsgInfo * SMTPMsg; // message for this SMTP connection + + UCHAR * SendBuffer; // Message being sent if socket is busy. malloc'ed as needed + int SendBufferSize; // Total Malloc'ed size. Actual size is in MailSize + int SendSize; // Bytes in buffer + int SendPtr; // next byte to send when ready + + struct NNTPRec * NNTPGroup; // Currently Selected Group + int NNTPNum; // Currenrly Selected Msg Number + int Timeout; // Used to close a session that is open too long + +} SocketConn; + +typedef struct KEYVALUES +{ + char * Key; + char * Value; +} KeyValues; + +typedef struct WEBMAILINFO +{ + // Info for HTML Forms Processing + + struct HtmlFormDir * Dir; // HTML Directory + char * txtFileName; // Template Name for current message + char * InputHTMLName; // Template to input message + char * DisplayHTMLName; // Template to display message + char * ReplyHTMLName; // Template for replying to message + char * txtFile; // Template data + char * OrigTo; // To field when template loaded + char * OrigSubject; // Subject field when template loaded + char * OrigBody; // Msg text when template loaded + char * OrigBID; + char OrigType; + char * To; + char * CC; + char * Subject; + char * Body; + char * BID; + char Type; + struct MsgInfo * Msg; // Msg record if replying + KeyValues txtKeys[1000]; // Key/Value pairs for txt template. Used when creating or displaying + KeyValues XMLKeys[1000]; // Key/Value pairs from XML attachment + BOOL isReply; + char * XMLName; + char * XML; // XML attachment + int XMLLen; + int Files; + char * FileName[100]; // Attachments + char * FileBody[100]; + int FileLen[100]; + + char * Header; + int HeaderLen; + + char * Footer; + int FooterLen; + + char * Reply; // put in here to save passing lots of parameters + int * RLen; + + BOOL Winlink; + BOOL P2P; + BOOL Packet; + + int CurrentMessageIndex; // Index of message currently displayed (for Prev and Next) + +}WebMailInfo; + +#define SMTPServer 1 +#define POP3SLAVE 2 +#define SMTPClient 3 +#define POP3Client 4 +#define NNTPServer 5 + +// State Values + +#define GettingUser 1 +#define GettingPass 2 +#define Authenticated 4 + +#define Connecting 8 + +// SMTP Master + +#define WaitingForGreeting 16 +#define WaitingForHELOResponse 32 +#define WaitingForFROMResponse 64 +#define WaitingForTOResponse 128 +#define WaitingForDATAResponse 256 +#define WaitingForBodyResponse 512 +#define WaitingForAUTHResponse 1024 + +// POP3 Master + +#define WaitingForUSERResponse 32 +#define WaitingForPASSResponse 64 +#define WaitingForSTATResponse 128 +#define WaitingForUIDLResponse 256 +#define WaitingForLISTResponse 512 +#define WaitingForRETRResponse 512 +#define WaitingForDELEResponse 1024 +#define WaitingForQUITResponse 2048 + + +#define SE 240 // End of subnegotiation parameters +#define NOP 241 //No operation +//#define DM 242 //Data mark Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. +#define BRK 243 //Break Indicates that the "break" or "attention" key was hi. +#define IP 244 //Suspend Interrupt or abort the process to which the NVT is connected. +#define AO 245 //Abort output Allows the current process to run to completion but does not send its output to the user. +#define AYT 246 //Are you there Send back to the NVT some visible evidence that the AYT was received. +#define EC 247 //Erase character The receiver should delete the last preceding undeleted character from the data stream. +#define EL 248 //Erase line Delete characters from the data stream back to but not including the previous CRLF. +#define GA 249 //Go ahead Under certain circumstances used to tell the other end that it can transmit. +#define SB 250 //Subnegotiation Subnegotiation of the indicated option follows. +#define WILL 251 //will Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. +#define WONT 252 //wont Indicates the refusal to perform, or continue performing, the indicated option. +#define DOx 253 //do Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. +#define DONT 254 //dont Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. +#define IAC 255 + +#define suppressgoahead 3 //858 +//#define Status 5 //859 +//#define echo 1 //857 +#define timingmark 6 //860 +#define terminaltype 24 //1091 +#define windowsize 31 //1073 +#define terminalspeed 32 //1079 +#define remoteflowcontrol 33 //1372 +#define linemode 34 //1184 +#define environmentvariables 36 //1408 + +BOOL Initialise(); +#ifdef WIN32 +INT_PTR CALLBACK ConfigWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +#endif +int DisplaySessions(); +int DoStateChange(int Stream); +int DoReceivedData(int Stream); +int DoBBSMonitorData(int Stream); +int Connected(int Stream); +int Disconnected(int Stream); +//int Socket_Accept(int SocketId); +//int Socket_Data(int SocketId,int error, int eventcode); +int DataSocket_Read(SocketConn * sockptr, SOCKET sock); +int DataSocket_Write(SocketConn * sockptr, SOCKET sock); +int DataSocket_Disconnect(SocketConn * sockptr); +int RefreshMainWindow(); +int Terminate(); +int SendtoSocket(SOCKET sock,char * Msg); +int WriteLog(char * msg); +int ConnectState(int Stream); +UCHAR * EncodeCall(UCHAR * Call); +int ParseIniFile(char * fn); +struct UserInfo * AllocateUserRecord(char * Call); +struct MsgInfo * AllocateMsgRecord(); +BIDRec * AllocateBIDRecord(); +BIDRec * AllocateTempBIDRecord(); +struct UserInfo * LookupCall(char * Call); +BIDRec * LookupBID(char * BID); +BIDRec * LookupTempBID(char * BID); +VOID RemoveTempBIDS(CIRCUIT * conn); +VOID SaveUserDatabase(); +VOID GetUserDatabase(); +VOID GetMessageDatabase(); +VOID SaveMessageDatabase(); +VOID GetBIDDatabase(); +VOID SaveBIDDatabase(); +VOID GetWPDatabase(); +VOID CopyWPDatabase(); +VOID SaveWPDatabase(); +VOID GetBadWordFile(); +WPRec * LookupWP(char * Call); +VOID SendWelcomeMsg(int Stream, ConnectionInfo * conn, struct UserInfo * user); +VOID ProcessLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID ProcessChatLine(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID SendPrompt(ConnectionInfo * conn, struct UserInfo * user); +int QueueMsg( ConnectionInfo * conn, char * msg, int len); +VOID SendUnbuffered(int stream, char * msg, int len); +//int GetFileList(char * Dir); +BOOL ListMessage(struct MsgInfo * Msg, ConnectionInfo * conn, struct TempUserInfo * Temp); +void DoDeliveredCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void DoKillCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void DoListCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, BOOL Resuming, char * Context); +void DoReadCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +void KillMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); +int KillMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call); +int KillMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call); +void DoUnholdCommand(CIRCUIT * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); + +VOID FlagAsKilled(struct MsgInfo * Msg, BOOL SaveDB); +int ListMessagesFrom(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +int ListMessagesTo(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +int ListMessagesAT(ConnectionInfo * conn, struct UserInfo * user, char * Call, BOOL SendFullFrom, int Start); +void ListMessagesInRange(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); +void ListMessagesInRangeForwards(ConnectionInfo * conn, struct UserInfo * user, char * Call, int Start, int End, BOOL SendFullFrom ); +int GetUserMsg(int m, char * Call, BOOL SYSOP); +void Flush(ConnectionInfo * conn); +VOID ClearQueue(ConnectionInfo * conn); +void TrytoSend(); +void ReadMessage(ConnectionInfo * conn, struct UserInfo * user, int msgno); +struct MsgInfo * FindMessage(char * Call, int msgno, BOOL sysop); +char * ReadMessageFile(int msgno); +char * ReadInfoFile(char * File); +char * FormatDateAndTime(time_t Datim, BOOL DateOnly); +int CriticalErrorHandler(char * error); +BOOL DoSendCommand(ConnectionInfo * conn, struct UserInfo * user, char * Cmd, char * Arg1, char * Context); +BOOL CreateMessage(ConnectionInfo * conn, char * From, char * ToCall, char * ATBBS, char MsgType, char * BID, char * Title); +VOID ProcessMsgTitle(ConnectionInfo * conn, struct UserInfo * user, char* Buffer, int len); +VOID ProcessMsgLine(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +VOID CreateMessageFile(ConnectionInfo * conn, struct MsgInfo * Msg); +int ProcessConnecting(CIRCUIT * circuit, char * Buffer, int Len); +VOID SaveConfig(char * ConfigName); +BOOL GetConfig(char * ConfigName); +int GetIntValue(config_setting_t * group, char * name); +BOOL GetStringValue(config_setting_t * group, char * name, char * value); +BOOL GetConfigFromRegistry(); +VOID Parse_SID(CIRCUIT * conn, char * SID, int len); +VOID ProcessMBLLine(CIRCUIT * conn, struct UserInfo * user, UCHAR* Buffer, int len); +VOID ProcessFBBLine(ConnectionInfo * conn, struct UserInfo * user, UCHAR * Buffer, int len); +VOID SetupNextFBBMessage(CIRCUIT * conn); +BOOL DecodeSendParams(CIRCUIT * conn, char * Context, char ** From, char * To, char ** ATBBS, char ** BID); +int PrintMessages(HWND hDlg, int Count, int * Indexes); +int check_fwd_bit(char *mask, int bbsnumber); +void set_fwd_bit(char *mask, int bbsnumber); +void clear_fwd_bit (char *mask, int bbsnumber); +VOID SetupForwardingStruct(struct UserInfo * user); +BOOL Forward_Message(struct UserInfo * user, struct MsgInfo * Msg); +VOID StartForwarding (int BBSNumber, char ** TempScript); +BOOL Reverse_Forward(); +int ProcessBBSConnectScript(CIRCUIT * conn, char * Buffer, int len); +BOOL FBBDoForward(CIRCUIT * conn); +BOOL FindMessagestoForward(CIRCUIT * conn); +BOOL SeeifMessagestoForward(int BBSNumber, CIRCUIT * Conn); +int CountMessagestoForward(struct UserInfo * user); + +VOID * GetMultiLineDialogParam(HWND hDialog, int DLGItem); + +#define LIBCONFIG_STATIC +#include "libconfig.h" +VOID * GetMultiStringValue(config_setting_t * hKey, char * ValueName); +VOID * RegGetMultiStringValue(HKEY hKey, char * ValueName); + +int MultiLineDialogToREG_MULTI_SZ(HWND hWnd, int DLGItem, HKEY hKey, char * ValueName); +int Do_BBS_Sel_Changed(HWND hDlg); +VOID FreeForwardingStruct(struct UserInfo * user); +VOID FreeList(char ** Hddr); +int Do_User_Sel_Changed(HWND hDlg); +int Do_Msg_Sel_Changed(HWND hDlg); +VOID Do_Save_Msg(); +VOID Do_Add_User(HWND hDlg); +VOID Do_Delete_User(HWND hDlg); +VOID FlagSentMessages(CIRCUIT * conn, struct UserInfo * user); +VOID HoldSentMessages(CIRCUIT * conn, struct UserInfo * user); +VOID Do_Save_User(HWND hDlg, BOOL ShowBox); +VOID DeleteBBS(); +VOID AddBBS(); +VOID SaveBBSConfig(); +BOOL GetChatConfig(); +VOID SaveChatConfig(); +VOID SaveISPConfig(); +VOID SaveFWDConfig(); +VOID SaveMAINTConfig(); +VOID SaveWelcomeMsgs(); +VOID SavePrompts(); +VOID ReinitializeFWDStruct(struct UserInfo * user); +VOID CopyBIDDatabase(); +VOID CopyMessageDatabase(); +VOID CopyUserDatabase(); +VOID FWDTimerProc(); +VOID CreateMessageFromBuffer(CIRCUIT * conn); +VOID __cdecl nodeprintf(ConnectionInfo * conn, const char * format, ...); +VOID __cdecl nodeprintfEx(ConnectionInfo * conn, const char * format, ...); +VOID FreeOverrides(); +VOID SendMessageToSYSOP(char * Title, char * MailBuffer, int Length); +struct UserInfo * FindRMS(); +VOID FindNextRMSUser(struct BBSForwardingInfo * FWDInfo); +BOOL ConnecttoBBS (struct UserInfo * user); +BOOL SetupNewBBS(struct UserInfo * user); +VOID CreateRegBackup(); +VOID SaveFilters(HWND hDlg); +BOOL CheckRejFilters(char * From, char * To, char * ATBBS, char * BID, char Type); +BOOL CheckHoldFilters(char * From, char * To, char * ATBBS, char * BID); +BOOL CheckifLocalRMSUser(char * FullTo); +VOID DoWPLookup(ConnectionInfo * conn, struct UserInfo * user, char Type, char *Context); +BOOL wildcardcompare(char * Target, char * Match); +VOID SendWarningToSYSOP(struct MsgInfo * Msg); +VOID DoEditUserCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoPollRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoShowRMSCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoSetIdleTime(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID DoFwdCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID SaveFwdParams(char * Call, struct BBSForwardingInfo * ForwardingInfo); +VOID DoAuthCmd(CIRCUIT * conn, struct UserInfo * user, char * Arg1, char * Context); +VOID ProcessSuspendedListCommand(CIRCUIT * conn, struct UserInfo * user, char* Buffer, int len); +VOID DoReroute(CIRCUIT * conn, struct UserInfo * user); + +// FBB Routines + +VOID SendCompressed(CIRCUIT * conn, struct MsgInfo * FwdMsg); +VOID SendCompressedB2(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); +VOID UnpackFBBBinary(CIRCUIT * conn); +void Decode(CIRCUIT * conn, __int16 DecodeOnly); +//long Encode(char * in, char * out, long inlen, BOOL B1Protocol); + +BOOL CreateB2Message(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader, char * Rline); +VOID SaveFBBBinary(CIRCUIT * conn); +BOOL LookupRestart(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); +BOOL DoWeWantIt(CIRCUIT * conn, struct FBBHeaderLine * FBBHeader); + +// Console Routines + +BOOL CreateConsole(int Stream); +int WritetoConsoleWindow(int Stream, char * Msg, int len); +int ToggleParam(HMENU hMenu, HWND hWnd, BOOL * Param, int Item); +void CopyRichTextToClipboard(HWND hWnd); +void CopyToClipboard(HWND hWnd); +VOID CloseConsole(int Stream); + +// Monitor Routines + +BOOL CreateMonitor(); +int WritetoMonitorWindow(char * Msg, int len); + +BOOL CreateDebugWindow(); +VOID WritetoDebugWindow(char * Msg, int len); +VOID ClearDebugWindow(); +int RemoveLF(char * Message, int len); + +// Utilities + +BOOL isdigits(char * string); +void GetSemaphore(struct SEM * Semaphore, int ID); +void FreeSemaphore(struct SEM * Semaphore); + +VOID __cdecl Debugprintf(const char * format, ...); +VOID __cdecl Logprintf(int LogMode, CIRCUIT * conn, int InOut, const char * format, ...); + +VOID SortBBSChain(); +VOID ExpandAndSendMessage(CIRCUIT * conn, char * Msg, int LOG); +int ImportMessages(CIRCUIT * conn, char * FN, BOOL Nopopup); + +// TCP Routines + +BOOL InitialiseTCP(); +VOID SetupListenSet(); +VOID TCPTimer(); +VOID TCPFastTimer(); +int Socket_Data(int sock, int error, int eventcode); +static int Socket_Accept(SOCKET SocketId); +int Socket_Connect(SOCKET sock, int Error); +VOID ProcessSMTPServerMessage(SocketConn * sockptr, char * Buffer, int Len); +int CreateSMTPMessage(SocketConn * sockptr, int i, char * MsgTitle, time_t Date, char * MsgBody, int Msglen, BOOL B2Flag); +BOOL CreateSMTPMessageFile(char * Message, struct MsgInfo * Msg); +SOCKET CreateListeningSocket(int Port); +int TidyString(char * MailFrom); +VOID ProcessPOP3ServerMessage(SocketConn * sockptr, char * Buffer, int Len); +char *str_base64_encode(char *str); +int b64decode(char *str); +SocketConn * SMTPConnect(char * Host, int Port, BOOL AMPR, struct MsgInfo * Msg, char * MsgBody); +BOOL POP3Connect(char * Host, int Port); +VOID ProcessSMTPClientMessage(SocketConn * sockptr, char * Buffer, int Len); +VOID ProcessPOP3ClientMessage(SocketConn * sockptr, char * Buffer, int Len); +int CreatePOP3Message(char * From, char * To, char * MsgTitle, time_t Date, char * MsgBody, int MsgLen, BOOL B2Flag); +void WriteLogLine(CIRCUIT * conn, int Flag, char * Msg, int MsgLen, int Flags); +int SendSock(SocketConn * sockptr, char * msg); +VOID __cdecl sockprintf(SocketConn * sockptr, const char * format, ...); +VOID SendFromQueue(SocketConn * sockptr); +VOID SendMultiPartMessage(SocketConn * sockptr, struct MsgInfo * Msg, UCHAR * msgbytes); +int CountMessagesTo(struct UserInfo * user, int * Unread); + +BOOL SendtoISP(); + +// NNTP ROutines + +VOID InitialiseNNTP(); +VOID BuildNNTPList(struct MsgInfo * Msg); +int NNTP_Data(int sock, int error, int eventcode); +int NNTP_Accept(SOCKET SocketId); + +VOID * GetOverrides(config_setting_t * group, char * ValueName); +VOID * RegGetOverrides(HKEY hKey, char * ValueName); + +VOID DoHouseKeeping(BOOL Mainual); +VOID ExpireMessages(); +VOID KillMsg(struct MsgInfo * Msg); +BOOL RemoveKilledMessages(); +VOID Renumber_Messages(); +BOOL ExpireBIDs(); +VOID MailHousekeepingResults(); +VOID CreateBBSTrafficReport(); +VOID CreateWPReport(); + +// WP Routines + +VOID ProcessWPMsg(char * MailBuffer, int Size, char * FisrtRLine); +VOID GetWPInfoFromRLine(char * From, char * FirstRLine, time_t RLineTime); +VOID UpdateWPWithUserInfo(struct UserInfo * user); +VOID GetWPBBSInfo(char * Rline); + +// UI Routines + +VOID SetupUIInterface(); +VOID Free_UI(); +VOID SendLatestUI(int Port); +VOID SendMsgUI(struct MsgInfo * Msg); +static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue); +VOID SeeifBBSUIFrame(struct _MESSAGEX * buff, int len); +struct MsgInfo * FindMessageByNumber(int msgno); +int CountConnectionsOnPort(int CheckPort); + +// Message Routing Routtines + +VOID SetupHAElements(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupHAddresesP(struct BBSForwardingInfo * ForwardingInfo); +VOID SetupMyHA(); +VOID SetupFwdAliases(); +struct Continent * FindContinent(char * Name); +int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn); +BOOL CheckABBS(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); +BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo); +BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); +BOOL CheckBBSHList(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char * HRoute); +BOOL CheckBBSHElements(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); +BOOL CheckBBSHElementsFlood(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements); +int CheckBBSToForNTS(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo); +int CheckBBSATListWildCarded(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS); + +VOID ReRouteMessages(); + +VOID initUTF8(); +int Is8Bit(unsigned char *cpt, int len); +int IsUTF8(unsigned char *ptr, int len); +int IsUTF8(unsigned char *ptr, int len); +int WebIsUTF8(unsigned char *ptr, int len); +int Convert437toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1251toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int Convert1252toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF); +int TrytoGuessCode(unsigned char * Char, int Len); + + +VOID FreeWebMailMallocs(); + +extern int _MYTIMEZONE; + +extern HKEY REGTREE; +extern char * REGTREETEXT; + +extern HBRUSH bgBrush; +extern BOOL cfgMinToTray; + +extern CIRCUIT * Console; + +extern ULONG ChatApplMask; +extern char Verstring[]; + +extern char SignoffMsg[]; +extern char AbortedMsg[]; +extern char InfoBoxText[]; // Text to display in Config Info Popup + +extern int LastVer[4]; // In case we need to do somthing the first time a version is run + +extern HWND MainWnd; +extern char BaseDir[]; +extern char BaseDirRaw[]; +extern char MailDir[]; +extern char WPDatabasePath[]; +extern char RlineVer[50]; + +extern BOOL LogBBS; +extern BOOL LogCHAT; +extern BOOL LogTCP; +extern BOOL ForwardToMe; +extern BOOL OnlyKnown; + +extern BOOL AllowAnon; +extern BOOL UserCantKillT; +extern BOOL DontNeedHomeBBS; + +extern int LatestMsg; +extern char BBSName[]; +extern char SYSOPCall[]; +extern char BBSSID[]; +extern char NewUserPrompt[]; + +extern char * WelcomeMsg; +extern char * NewWelcomeMsg; +extern char * ChatWelcomeMsg; +extern char * NewChatWelcomeMsg; +extern char * ExpertWelcomeMsg; + +extern char * Prompt; +extern char * NewPrompt; +extern char * ExpertPrompt; + +// Filter Params + +extern char ** RejFrom; // Reject on FROM Call +extern char ** RejTo; // Reject on TO Call +extern char ** RejAt; // Reject on AT Call +extern char ** RejBID; + +extern char ** HoldFrom; // Hold on FROM Call +extern char ** HoldTo; // Hold on TO Call +extern char ** HoldAt; // Hold on AT Call +extern char ** HoldBID; + +// Send WP Params + +extern BOOL SendWP; +extern char SendWPVIA[81]; +extern char SendWPTO[11]; +extern int SendWPType; + +extern int Ver[4]; + +extern struct MsgInfo ** MsgHddrPtr; + +extern BIDRec ** BIDRecPtr; +extern int NumberofBIDs; + +extern struct NNTPRec * FirstNNTPRec; +//extern int NumberofNNTPRecs; + + +extern int NumberofMessages; +extern int FirstMessageIndextoForward; + +extern WPRec ** WPRecPtr; +extern int NumberofWPrecs; + +extern struct SEM AllocSemaphore; +extern struct SEM ConSemaphore; +extern struct SEM MsgNoSemaphore; + +extern struct MsgInfo * MsgnotoMsg[]; // Message Number to Message Slot List. + + +extern char hostname[]; +extern char RtUsr[]; +extern char RtUsrTemp[]; +extern char RtKnown[]; +extern int AXIPPort; +extern BOOL NeedStatus; + +extern BOOL ISP_Gateway_Enabled; +extern BOOL SMTPAuthNeeded; + + +extern int MaxMsgno; +extern int BidLifetime; +extern int MaxAge; +extern int MaintInterval; +extern int MaintTime; +extern int UserLifetime; + +extern int MaxRXSize; +extern int MaxTXSize; + +extern char OurNode[]; +extern char OurAlias[]; +extern BOOL SMTPMsgCreated; + +extern HINSTANCE hInst; +extern HWND hWnd; +extern RECT MainRect; + +extern char BBSName[]; +extern char HRoute[]; +extern char AMPRDomain[]; +extern BOOL SendAMPRDirect; +extern int BBSApplNum; +extern int SMTPInPort; +extern int POP3InPort; +extern int NNTPInPort; +extern BOOL RemoteEmail; + +extern int MaxStreams; +extern UCHAR * OtherNodes; +extern struct UserInfo * BBSChain; // Chain of users that are BBSes +extern struct UserInfo ** UserRecPtr; +extern int NumberofUsers; +extern struct MsgInfo ** MsgHddrPtr; +extern int NumberofMessages; +extern int HighestBBSNumber; +extern HMENU hFWDMenu; // Forward Menu Handle +extern char zeros[]; // For forward bitmask tests +extern BOOL EnableUI; +extern BOOL RefuseBulls; +extern BOOL SendSYStoSYSOPCall; +extern BOOL SendBBStoSYSOPCall; +extern BOOL DontHoldNewUsers; +extern BOOL DefaultNoWINLINK; +extern BOOL UIEnabled[]; +extern BOOL UINull[]; +extern BOOL UIMF[]; +extern BOOL UIHDDR[]; +extern char * UIDigi[]; +extern int MailForInterval; +extern char MailForText[]; + +extern BOOL ISP_Gateway_Enabled; + +extern char MyDomain[]; // Mail domain for BBS<>Internet Mapping + +extern char ISPSMTPName[]; +extern char ISPEHLOName[]; +extern int ISPSMTPPort; + +extern char ISPPOP3Name[]; +extern int ISPPOP3Port; +extern int ISPPOP3Interval; + +extern char ISPAccountName[]; +extern char ISPAccountPass[]; +extern char EncryptedISPAccountPass[]; +extern int EncryptedPassLen; +extern char *month[]; + +extern HWND hDebug; +extern RECT MonitorRect; +extern RECT DebugRect; +extern HWND hMonitor; +//extern HWND hConsole; +//extern RECT ConsoleRect; +extern int LogAge; +extern BOOL DeletetoRecycleBin; +extern BOOL SuppressMaintEmail; +extern BOOL SaveRegDuringMaint; +extern BOOL SendWP; +extern BOOL OverrideUnsent; +extern BOOL SendNonDeliveryMsgs; +extern BOOL GenerateTrafficReport; + +extern double PR; +extern double PUR; +extern double PF; +extern double PNF; +extern int BF; +extern int BNF; +extern int NTSD; +extern int NTSU; +extern int NTSF; +extern int AP; +extern int AB; + +extern struct Override ** LTFROM; +extern struct Override ** LTTO; +extern struct Override ** LTAT; + +extern time_t LastHouseKeepingTime; +extern time_t LastTrafficTime; + +extern char * MyElements[]; +extern char ** AliasText; +extern struct ALIAS ** Aliases; + +extern BOOL ReaddressLocal; +extern BOOL ReaddressReceived; +extern BOOL WarnNoRoute; +extern BOOL SendPtoMultiple; +extern BOOL Localtime; + +struct ConsoleInfo * ConsHeader[2]; + +extern BOOL NeedHomeBBS; +extern char ConfigName[250]; +extern BOOL UsingingRegConfig; + +extern BOOL MulticastRX; + +extern BOOL FilterWPBulls; +extern BOOL NoWPGuesses; +extern char ** SendWPAddrs; // Replacers WP To and VIA + +extern BOOL DontCheckFromCall; + +// YAPP stuff + +#define SOH 1 +#define STX 2 +#define ETX 3 +#define EOT 4 +#define ENQ 5 +#define ACK 6 +#define DLE 0x10 +#define NAK 0x15 +#define CAN 0x18 diff --git a/cMain.c b/cMain.c index 22a8fa2..1624f80 100644 --- a/cMain.c +++ b/cMain.c @@ -1050,6 +1050,30 @@ BOOL Start() PORT->SmartIDInterval = PortRec->SmartID; + if (PortRec->KissParams && (PORT->PORTTYPE == 0 || PORT->PORTTYPE == 22)) + { + struct KISSINFO * KISS = (struct KISSINFO *)PORT; + UCHAR KissString[128]; + int KissLen = 0; + unsigned char * Kissptr = KissString; + char * ptr; + char * Context; + + ptr = strtok_s(PortRec->KissParams, " ", &Context); + + while (ptr && ptr[0] && KissLen < 120) + { + *(Kissptr++) = atoi (ptr); + KissLen++; + ptr = strtok_s(NULL, " ", &Context); + } + + KISS->KISSCMD = malloc(256); + + KISS->KISSCMDLEN = KissEncode(KissString, KISS->KISSCMD, KissLen); + realloc(KISS->KISSCMD, KISS->KISSCMDLEN); + } + if (PortRec->BBSFLAG) // Appl 1 no permitted - BBSFLAG=NOBBS PORT->PERMITTEDAPPLS &= 0xfffffffe; // Clear bottom bit diff --git a/config.c b/config.c index 5a10ab3..6592b51 100644 --- a/config.c +++ b/config.c @@ -255,6 +255,7 @@ int simple(int i); int C_Q_ADD_NP(VOID *PQ, VOID *PBUFF); int doSerialPortName(int i, char * value, char * rec); int doPermittedAppls(int i, char * value, char * rec); +int doKissCommand(int i, char * value, char * rec); BOOL ProcessAPPLDef(char * rec); BOOL ToLOC(double Lat, double Lon , char * Locator); @@ -360,7 +361,7 @@ static char *pkeywords[] = "BCALL", "DIGIMASK", "NOKEEPALIVES", "COMPORT", "DRIVER", "WL2KREPORT", "UIONLY", "UDPPORT", "IPADDR", "I2CBUS", "I2CDEVICE", "UDPTXPORT", "UDPRXPORT", "NONORMALIZE", "IGNOREUNLOCKEDROUTES", "INP3ONLY", "TCPPORT", "RIGPORT", "PERMITTEDAPPLS", "HIDE", -"SMARTID"}; /* parameter keywords */ +"SMARTID", "KISSCOMMAND"}; /* parameter keywords */ static void * poffset[] = { @@ -374,7 +375,7 @@ static void * poffset[] = &xxp.BCALL, &xxp.DIGIMASK, &xxp.DefaultNoKeepAlives, &xxp.IOADDR, &xxp.DLLNAME, &xxp.WL2K, &xxp.UIONLY, &xxp.IOADDR, &xxp.IPADDR, &xxp.INTLEVEL, &xxp.IOADDR, &xxp.IOADDR, &xxp.ListenPort, &xxp.NoNormalize, &xxp.IGNOREUNLOCKED, &xxp.INP3ONLY, &xxp.TCPPORT, &xxp.RIGPORT, &xxp.PERMITTEDAPPLS, &xxp.Hide, -&xxp.SmartID}; /* offset for corresponding data in config file */ +&xxp.SmartID, &xxp.KissParams}; /* offset for corresponding data in config file */ static int proutine[] = { @@ -388,7 +389,7 @@ static int proutine[] = 0, 1, 2, 18, 15, 16, 2, 1, 17, 1, 1, 1, 1, 2, 2, 2, 1, 1, 19, 2, -1}; /* routine to process parameter */ +1, 20}; /* routine to process parameter */ int PPARAMLIM = sizeof(proutine)/sizeof(int); @@ -2209,7 +2210,11 @@ int decode_port_rec(char * rec) break; case 19: - cn = doPermittedAppls(i,value,rec); // COMPORT + cn = doPermittedAppls(i,value,rec); // Permitted Apps + break; + + case 20: + cn = doKissCommand(i, value, rec); // Permitted Apps break; @@ -2365,7 +2370,7 @@ int doPermittedAppls(int i, char * value, char * rec) char * Context; char * ptr1 = strtok_s(value, " ,=\t\n\r", &Context); - // Param is a comma separated list of Appl Numbers allowes to connect on this port + // Param is a comma separated list of Appl Numbers allowed to connect on this port while (ptr1 && ptr1[0]) { @@ -2378,6 +2383,13 @@ int doPermittedAppls(int i, char * value, char * rec) return 1; } +int doKissCommand(int i, char * value, char * rec) +{ + // Param is kiss command and any operands as decimal bytes + + xxp.KissParams = _strdup(strlop(rec, '=')); + return 1; +} int hwtypes(i, value, rec) @@ -2612,7 +2624,7 @@ static int troutine[] = #define TPARAMLIM 6 -extern CMDX COMMANDLIST[]; +extern CMDX TNCCOMMANDLIST[]; extern int NUMBEROFTNCCOMMANDS; int decode_tnc_rec(char * rec) @@ -2689,7 +2701,7 @@ int decode_tnc_rec(char * rec) // Try process as TNC2 Command int n = 0; - CMDX * CMD = &COMMANDLIST[0]; + CMDX * CMD = &TNCCOMMANDLIST[0]; char * ptr1 = key_word; UCHAR * valueptr; diff --git a/configstructs.h b/configstructs.h index 605d2e1..39fc25b 100644 --- a/configstructs.h +++ b/configstructs.h @@ -1,4 +1,7 @@ +#ifndef CONFIGSTRUCT +#define CONFIGSTRUCT + // MAKE SURE SHORTS ARE CORRECTLY ALLIGNED FOR ARMV5 @@ -73,6 +76,7 @@ struct PORTCONFIG long long txOffset; // Transverter tx offset long long rxOffset; // Transverter rx offset ppa int SmartID; + unsigned char * KissParams; }; struct ROUTECONFIG @@ -177,5 +181,7 @@ struct UPNP char * WANPort; }; +#endif + diff --git a/kiss.c b/kiss.c index 55c8f98..0de9ae9 100644 --- a/kiss.c +++ b/kiss.c @@ -34,12 +34,15 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses #include #include #include +#ifndef RP2040 #include #include +#include +#endif #include #include #include -#include + //#include @@ -113,7 +116,7 @@ int ConnecttoTCP(NPASYINFO ASY); int KISSGetTCPMessage(NPASYINFO ASY); VOID CloseKISSPort(struct PORTCONTROL * PortVector); int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error); -void processDRATSFrame(unsigned char * Message, int Len); +void processDRATSFrame(unsigned char * Message, int Len, struct ConnectionInfo * sockptr); extern struct PORTCONTROL * PORTTABLE; extern int NUMBEROFPORTS; @@ -157,7 +160,7 @@ int ASYSEND(struct PORTCONTROL * PortVector, char * buffer, int count) if (ret == -1) { Debugprintf ("i2c Write Error\r"); - usleep(1000); + Sleep(1); ret = i2c_smbus_write_byte(Port->idComDev, *(ptr)); } ptr++; @@ -306,11 +309,15 @@ int ASYINIT(int comport, int speed, struct PORTCONTROL * PortVector, char Channe } else if (PortVector->PORTIPADDR.s_addr || PortVector->KISSSLAVE) { + +#ifndef RP2040 + SOCKET sock; u_long param=1; BOOL bcopt=TRUE; struct sockaddr_in sinx; + // KISS over UDP or TCP if (PortVector->ListenPort == 0) @@ -423,8 +430,8 @@ int ASYINIT(int comport, int speed, struct PORTCONTROL * PortVector, char Channe npKISSINFO->RXBPTR=&npKISSINFO->RXBUFFER[0]; npKISSINFO->RXMPTR=&npKISSINFO->RXMSG[0]; - OpenConnection(PortVector, comport); - + OpenConnection(PortVector); +#endif } npKISSINFO->Portvector = PortVector; @@ -452,9 +459,10 @@ NPASYINFO CreateKISSINFO( int port,int speed ) -HANDLE OpenConnection(struct PORTCONTROL * PortVector, int port) +HANDLE OpenConnection(struct PORTCONTROL * PortVector) { NPASYINFO npKISSINFO = KISSInfo[PortVector->PORTNUMBER]; + struct KISSINFO * KISS = (struct KISSINFO *) PortVector; HANDLE ComDev = 0 ; if (npKISSINFO == NULL) @@ -474,8 +482,6 @@ HANDLE OpenConnection(struct PORTCONTROL * PortVector, int port) { // RFM22/23 module or TNC-PI- send a reset - struct KISSINFO * KISS = (struct KISSINFO *) PortVector; - ENCBUFF[0] = FEND; ENCBUFF[1] = KISS->OURCTRL | 15; // Action command ENCBUFF[2] = 2; // Reset command @@ -487,13 +493,15 @@ HANDLE OpenConnection(struct PORTCONTROL * PortVector, int port) { // SCS Tracker - Send Enter KISS (CAN)(ESC)@K(CR) - struct KISSINFO * KISS = (struct KISSINFO *) PortVector; - memcpy(ENCBUFF, "\x18\x1b@K\r", 5); // Enter KISS ASYSEND(PortVector, ENCBUFF, 5); } + if (KISS->KISSCMD && KISS->KISSCMDLEN) + ASYSEND(PortVector, KISS->KISSCMD, KISS->KISSCMDLEN); + + return ComDev; } int ReadCommBlock(NPASYINFO npKISSINFO, char * lpszBlock, int nMaxLength ) @@ -509,7 +517,7 @@ int ReadCommBlock(NPASYINFO npKISSINFO, char * lpszBlock, int nMaxLength ) if (npKISSINFO->ReopenTimer > 300) // about 30 secs { - npKISSINFO->idComDev = OpenConnection(npKISSINFO->Portvector, npKISSINFO->bPort); + npKISSINFO->idComDev = OpenConnection(npKISSINFO->Portvector); npKISSINFO->ReopenTimer = 0; } } @@ -1573,7 +1581,7 @@ SeeifMore: VEC = PORT->Session->L4TARGET.HOST; C_Q_ADD(&PORT->Session->L4TX_Q, (UINT *)Buffer); -#ifndef LINBPQ +#ifdef BPQ32 if (VEC) PostMessage(VEC->HOSTHANDLE, BPQMsg, VEC->HOSTSTREAM, 2); #endif @@ -1588,13 +1596,13 @@ SeeifMore: } // checksum if necessary - +#ifndef RP2040 if (KISS->PORT.KISSFLAGS & DRATS) { - processDRATSFrame(&Port->RXMSG[1], len - 2); + processDRATSFrame(&Port->RXMSG[1], len - 2, 0); return 0; } - +#endif if (len < 15) return 0; // too short for AX25 @@ -1821,6 +1829,7 @@ int ConnecttoTCP(NPASYINFO ASY) VOID ConnecttoTCPThread(NPASYINFO ASY) { +#ifndef RP2040 char Msg[255]; int err,i; u_long param=1; @@ -1829,6 +1838,7 @@ VOID ConnecttoTCPThread(NPASYINFO ASY) // struct hostent * HostEnt; SOCKADDR_IN sinx; int addrlen=sizeof(sinx); + struct KISSINFO * KISS = (struct KISSINFO *) ASY->Portvector; sinx.sin_family = AF_INET; sinx.sin_addr.s_addr = INADDR_ANY; @@ -1886,9 +1896,15 @@ VOID ConnecttoTCPThread(NPASYINFO ASY) ASY->Connecting = FALSE; ioctlsocket (sock, FIONBIO, ¶m); + + // If configured send TNC command + + if (KISS->KISSCMD && KISS->KISSCMDLEN) + send(sock, KISS->KISSCMD, KISS->KISSCMDLEN, 0); + continue; } - else + else { err=WSAGetLastError(); @@ -1910,10 +1926,13 @@ VOID ConnecttoTCPThread(NPASYINFO ASY) } Sleep (57000/2); // 1/2 Mins } +#endif } int KISSGetTCPMessage(NPASYINFO ASY) { +#ifndef RP2040 + int index=0; ULONG param = 1; @@ -1989,6 +2008,8 @@ int KISSGetTCPMessage(NPASYINFO ASY) { // Reopen Listening Socket + + SOCKET sock; u_long param=1; BOOL bcopt=TRUE; @@ -2020,7 +2041,7 @@ int KISSGetTCPMessage(NPASYINFO ASY) else ASY->Listening = TRUE; } - } +#endif return 0; } diff --git a/kiss.h b/kiss.h index 8a9aded..b45df2d 100644 --- a/kiss.h +++ b/kiss.h @@ -45,7 +45,7 @@ typedef struct tagASYINFO } ASYINFO, *NPASYINFO ; -NPASYINFO KISSInfo[33] = {0}; +NPASYINFO KISSInfo[MAXBPQPORTS] = {0}; #define _fmemset memset @@ -59,6 +59,6 @@ NPASYINFO CreateKISSINFO( int port, int speed ); BOOL DestroyKISSINFO(NPASYINFO npKISSINFO) ; int ReadCommBlock(NPASYINFO npKISSINFO, char * lpszBlock, int nMaxLength); static BOOL WriteCommBlock(NPASYINFO npKISSINFO, char * lpByte, DWORD dwBytesToWrite); -HANDLE OpenConnection(struct PORTCONTROL * PortVector, int port); +HANDLE OpenConnection(struct PORTCONTROL * PortVector); BOOL SetupConnection(NPASYINFO npKISSINFO); BOOL CloseConnection(NPASYINFO npKISSINFO); diff --git a/makefile b/makefile index a1cc332..184cfbf 100644 --- a/makefile +++ b/makefile @@ -23,7 +23,7 @@ all: CFLAGS = -DLINBPQ -MMD -g -fcommon all: linbpq -noi2c: CFLAGS = -DLINBPQ -MMD -DNOI2C -g +noi2c: CFLAGS = -DLINBPQ -MMD -DNOI2C -g -fcommon noi2c: linbpq diff --git a/rigcontrol.h b/rigcontrol.h index badd22a..bd3c942 100644 --- a/rigcontrol.h +++ b/rigcontrol.h @@ -1,3 +1,6 @@ +#ifndef RIGCONTROL +#define RIGCONTROL + #ifndef LINBPQ #include "Rigresource.h" #endif @@ -75,7 +78,7 @@ struct RIGINFO struct _EXTPORTDATA * PortRecord[32]; // BPQ32 port record(s) for this rig (null terminated list) UCHAR RigAddr; - int ScanStopped; // Scanning enabled if zero. Bits used for interlocked scanning (eg winmor/pactor on same port + uint64_t ScanStopped; // Scanning enabled if zero. Bits used for interlocked scanning (eg winmor/pactor on same port int ScanCounter; int PollCounter; // Don't poll too often; int ScanFreq; // Scan Rate @@ -261,3 +264,5 @@ struct RIGPORTINFO #define W98_SERIAL_GETDATA 0x801 #define W98_SERIAL_SETDATA 0x802 + +#endif diff --git a/telnetserver.h b/telnetserver.h index 072f14c..77da27c 100644 --- a/telnetserver.h +++ b/telnetserver.h @@ -1,3 +1,5 @@ +#ifndef TELNETSERVER +#define TELNETSERVER #ifndef LINBPQ //#include "resource.h" @@ -80,7 +82,7 @@ struct ConnectionInfo #define NOP 241 //No operation #define xDM 242 //Data mark Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. #define BRK 243 //Break Indicates that the "break" or "attention" key was hi. -#define IP 244 //Suspend Interrupt or abort the process to which the NVT is connected. +#define IPx 244 //Suspend Interrupt or abort the process to which the NVT is connected. #define AO 245 //Abort output Allows the current process to run to completion but does not send its output to the user. #define AYT 246 //Are you there Send back to the NVT some visible evidence that the AYT was received. #define EC 247 //Erase character The receiver should delete the last preceding undeleted character from the data stream. @@ -104,3 +106,5 @@ struct ConnectionInfo #define linemode 34 //1184 #define environmentvariables 36 //1408 +#endif + diff --git a/winstdint.h b/winstdint.h index 1e52351..8c44d8e 100644 --- a/winstdint.h +++ b/winstdint.h @@ -76,8 +76,8 @@ typedef unsigned __STDINT_LONGLONG uint_least64_t; */ typedef char int_fast8_t; typedef unsigned char uint_fast8_t; -typedef short int_fast16_t; -typedef unsigned short uint_fast16_t; +//typedef short int_fast16_t; +//typedef unsigned short uint_fast16_t; typedef int int_fast32_t; typedef unsigned int uint_fast32_t; typedef __STDINT_LONGLONG int_fast64_t;