/***************************************************************************** * * $Id$ * Purpose ...............: JAM message base functions * ***************************************************************************** * * Original written in C++ by Marco Maccaferri for LoraBBS and was * distributed under GNU GPL. This version is modified for use with * MBSE BBS and utilities. * ***************************************************************************** * Copyright (C) 1997-2004 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS 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 2, or (at your option) any * later version. * * MBSE BBS 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 MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "../config.h" #include "mbselib.h" #include "msgtext.h" #include "msg.h" #include "jam.h" #include "jammsg.h" #define MAX_TEXT 2048 int fdHdr = -1; int fdJdt = -1; int fdJdx = -1; int fdJlr = -1; char *pSubfield; char BaseName[PATH_MAX]; char *pLine, *pBuff; char szBuff[MAX_LINE_LENGTH + 1]; char szLine[MAX_LINE_LENGTH + 1]; JAMHDRINFO jamHdrInfo; JAMHDR jamHdr; unsigned long LastReadRec; unsigned long AddSubfield(unsigned int, char *); unsigned long AddSubfield(unsigned int JamSFld, char *SubStr) { JAMSUBFIELD jamSubfield; unsigned long Len; jamSubfield.HiID = 0; jamSubfield.LoID = JamSFld; jamSubfield.DatLen = strlen(SubStr); /* + 1; */ Len = jamSubfield.DatLen + sizeof(JAMBINSUBFIELD); write(fdHdr, &jamSubfield, sizeof(JAMBINSUBFIELD)); write(fdHdr, SubStr, strlen(SubStr)); /* + 1); */ return Len; } void JAMset_flags(void); void JAMset_flags() { jamHdr.Attribute |= (Msg.Local) ? MSG_LOCAL : 0; jamHdr.Attribute |= (Msg.Intransit) ? MSG_INTRANSIT : 0; jamHdr.Attribute |= (Msg.Private) ? MSG_PRIVATE : 0; jamHdr.Attribute |= (Msg.Received) ? MSG_READ : 0; jamHdr.Attribute |= (Msg.Sent) ? MSG_SENT : 0; jamHdr.Attribute |= (Msg.KillSent) ? MSG_KILLSENT : 0; jamHdr.Attribute |= (Msg.ArchiveSent) ? MSG_ARCHIVESENT : 0; jamHdr.Attribute |= (Msg.Hold) ? MSG_HOLD : 0; jamHdr.Attribute |= (Msg.Crash) ? MSG_CRASH : 0; jamHdr.Attribute |= (Msg.Immediate) ? MSG_IMMEDIATE : 0; jamHdr.Attribute |= (Msg.Direct) ? MSG_DIRECT : 0; jamHdr.Attribute |= (Msg.Gate) ? MSG_GATE : 0; jamHdr.Attribute |= (Msg.FileRequest) ? MSG_FILEREQUEST : 0; jamHdr.Attribute |= (Msg.FileAttach) ? MSG_FILEATTACH : 0; jamHdr.Attribute |= (Msg.TruncFile) ? MSG_TRUNCFILE : 0; jamHdr.Attribute |= (Msg.KillFile) ? MSG_KILLFILE : 0; jamHdr.Attribute |= (Msg.ReceiptRequest) ? MSG_RECEIPTREQ : 0; jamHdr.Attribute |= (Msg.ConfirmRequest) ? MSG_CONFIRMREQ : 0; jamHdr.Attribute |= (Msg.Orphan) ? MSG_ORPHAN : 0; jamHdr.Attribute |= (Msg.Encrypt) ? MSG_ENCRYPT : 0; jamHdr.Attribute |= (Msg.Compressed) ? MSG_COMPRESS : 0; jamHdr.Attribute |= (Msg.Escaped) ? MSG_ESCAPED : 0; jamHdr.Attribute |= (Msg.ForcePU) ? MSG_FPU : 0; jamHdr.Attribute |= (Msg.Localmail) ? MSG_TYPELOCAL : 0; jamHdr.Attribute |= (Msg.Echomail) ? MSG_TYPEECHO : 0; jamHdr.Attribute |= (Msg.Netmail) ? MSG_TYPENET : 0; jamHdr.Attribute |= (Msg.Nodisplay) ? MSG_NODISP : 0; jamHdr.Attribute |= (Msg.Locked) ? MSG_LOCKED : 0; jamHdr.Attribute |= (Msg.Deleted) ? MSG_DELETED : 0; jamHdr.ReplyTo = Msg.Original; jamHdr.ReplyNext = Msg.Reply; jamHdr.DateReceived = Msg.Read; jamHdr.MsgIdCRC = Msg.MsgIdCRC; jamHdr.ReplyCRC = Msg.ReplyCRC; } /* * Add a message, the structure msg must contain all needed * information. */ int JAM_AddMsg() { int i, RetVal = TRUE; unsigned long ulMsg = JAM_Highest() + 1L; char *pszText, *Sign= (char *)HEADERSIGNATURE; JAMIDXREC jamIdx; int Oke; memset(&jamHdr, 0, sizeof(JAMHDR)); jamHdr.Signature[0] = Sign[0]; jamHdr.Signature[1] = Sign[1]; jamHdr.Signature[2] = Sign[2]; jamHdr.Signature[3] = Sign[3]; jamHdr.Revision = CURRENTREVLEV; jamHdr.MsgNum = ulMsg; jamHdr.DateWritten = Msg.Written; jamHdr.DateProcessed = Msg.Arrived; JAMset_flags(); lseek(fdHdr, 0L, SEEK_END); jamIdx.UserCRC = 0; jamIdx.HdrOffset = tell(fdHdr); lseek(fdJdx, 0L, SEEK_END); write(fdJdx, &jamIdx, sizeof(JAMIDXREC)); write(fdHdr, &jamHdr, sizeof(JAMHDR)); jamHdr.SubfieldLen += AddSubfield(JAMSFLD_SENDERNAME, Msg.From); if (Msg.To[0]) jamHdr.SubfieldLen += AddSubfield(JAMSFLD_RECVRNAME, Msg.To); jamHdr.SubfieldLen += AddSubfield(JAMSFLD_SUBJECT, Msg.Subject); if (Msg.FromAddress[0] != '\0') jamHdr.SubfieldLen += AddSubfield(JAMSFLD_OADDRESS, Msg.FromAddress); if (Msg.ToAddress[0] != '\0') jamHdr.SubfieldLen += AddSubfield(JAMSFLD_DADDRESS, Msg.ToAddress); Msg.Id = jamHdr.MsgNum; lseek(fdJdt, 0L, SEEK_END); jamHdr.TxtOffset = tell(fdJdt); jamHdr.TxtLen = 0; /* * Read message text from memory, this also contains kludges. * Extract those that are defined by the JAMmb specs, except * the AREA: kludge. This one is only present in bad and dupe * echomail areas and is present for tossbad and tossdupe. */ if ((pszText = (char *)MsgText_First ()) != NULL) do { if ((pszText[0] == '\001') || (!strncmp(pszText, "SEEN-BY:", 8))) { Oke = FALSE; if (!strncmp(pszText, "\001PID: ", 6)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_PID, pszText + 6); Oke = TRUE; } if (!strncmp(pszText, "\001MSGID: ", 8)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_MSGID, pszText + 8); Oke = TRUE; } if (!strncmp(pszText, "\001REPLY: ", 8)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_REPLYID, pszText + 8); Oke = TRUE; } if (!strncmp(pszText, "\001PATH: ", 7)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_PATH2D, pszText + 7); Oke = TRUE; } if (!strncmp(pszText, "\001Via", 4)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_TRACE, pszText + 5); Oke = TRUE; } if (!strncmp(pszText, "SEEN-BY: ", 9)) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_SEENBY2D, pszText + 9); Oke = TRUE; } /* * Other non-JAM kludges */ if ((!Oke) && (pszText[0] == '\001')) { jamHdr.SubfieldLen += AddSubfield(JAMSFLD_FTSKLUDGE, pszText + 1); Oke = TRUE; } if (!Oke) { for (i = 0; i < strlen(pszText); i++) { if (pszText[i] < 32) printf("<%x>", pszText[i]); else printf("%c", pszText[i]); } } } else { write(fdJdt, pszText, strlen (pszText)); jamHdr.TxtLen += strlen (pszText); write(fdJdt, "\r", 1); jamHdr.TxtLen += 1; } } while ((pszText = (char *)MsgText_Next ()) != NULL); /* * Write final message header */ lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); write(fdHdr, &jamHdr, sizeof (JAMHDR)); /* * Update area information */ lseek(fdHdr, 0L, SEEK_SET); read(fdHdr, &jamHdrInfo, sizeof (JAMHDRINFO)); jamHdrInfo.ActiveMsgs++; jamHdrInfo.ModCounter++; lseek(fdHdr, 0L, SEEK_SET); write(fdHdr, &jamHdrInfo, sizeof (JAMHDRINFO)); return RetVal; } /* * Close current message base */ void JAM_Close(void) { if (fdJdx != -1) close(fdJdx); if (fdJdt != -1) close(fdJdt); if (fdHdr != -1) close(fdHdr); if (fdJlr != -1) close(fdJlr); if (pSubfield != NULL) free(pSubfield); fdHdr = fdJdt = fdJdx = fdJlr = -1; pSubfield = NULL; Msg.Id = 0L; } /* * Delete message number */ int JAM_Delete(unsigned long ulMsg) { int RetVal = FALSE; JAMIDXREC jamIdx; if (JAM_ReadHeader(ulMsg) == TRUE) { jamHdr.Attribute |= MSG_DELETED; lseek(fdJdx, tell(fdJdx) - sizeof(jamIdx), SEEK_SET); if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); write(fdHdr, &jamHdr, sizeof(JAMHDR)); lseek(fdHdr, 0L, SEEK_SET); read(fdHdr, &jamHdrInfo, sizeof (JAMHDRINFO)); jamHdrInfo.ActiveMsgs--; lseek(fdHdr, 0L, SEEK_SET); write(fdHdr, &jamHdrInfo, sizeof (JAMHDRINFO)); RetVal = TRUE; } } return RetVal; } /* * Delete JAM area files */ void JAM_DeleteJAM(char *Base) { char *temp; temp = calloc(PATH_MAX, sizeof(char)); sprintf(temp, "%s%s", Base, EXT_HDRFILE); unlink(temp); sprintf(temp, "%s%s", Base, EXT_IDXFILE); unlink(temp); sprintf(temp, "%s%s", Base, EXT_TXTFILE); unlink(temp); sprintf(temp, "%s%s", Base, EXT_LRDFILE); unlink(temp); free(temp); Syslog('+', "JAM deleted %s", Base); } /* * Search for requested LastRead record. */ int JAM_GetLastRead(lastread *LR) { lastread lr; LastReadRec = 0L; lseek(fdJlr, 0, SEEK_SET); while (read(fdJlr, &lr, sizeof(lastread)) == sizeof(lastread)) { if (lr.UserID == LR->UserID) { LR->LastReadMsg = lr.LastReadMsg; LR->HighReadMsg = lr.HighReadMsg; return TRUE; } LastReadRec++; } return FALSE; } /* * Get highest message number */ unsigned long JAM_Highest(void) { unsigned long RetVal = 0L; JAMIDXREC jamIdx; if (jamHdrInfo.ActiveMsgs > 0L) { lseek(fdJdx, filelength(fdJdx) - sizeof(jamIdx), SEEK_SET); if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof(JAMHDR)); RetVal = jamHdr.MsgNum; } } Msg.Id = RetVal; return RetVal; } int JAM_Lock(unsigned long ulTimeout) { int rc, Tries = 0; struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0L; fl.l_len = 1L; /* GoldED locks 1 byte as well */ fl.l_pid = getpid(); while ((rc = fcntl(fdHdr, F_SETLK, &fl)) && ((errno == EACCES) || (errno == EAGAIN))) { if (++Tries >= (ulTimeout * 4)) { fcntl(fdHdr, F_GETLK, &fl); WriteError("JAM messagebase is locked by pid %d", fl.l_pid); return FALSE; } msleep(250); Syslog('m', "JAM messagebase lock attempt %d", Tries); } if (rc) { WriteError("$%s lock error", BaseName); return FALSE; } return TRUE; } /* * Get lowest message number */ unsigned long JAM_Lowest(void) { unsigned long RetVal = 0L; JAMIDXREC jamIdx; if (jamHdrInfo.ActiveMsgs > 0L) { lseek(fdJdx, 0L, SEEK_SET); if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof(JAMHDR)); RetVal = jamHdr.MsgNum; } } Msg.Id = RetVal; return RetVal; } void JAM_New(void) { memset(&Msg, 0, sizeof(Msg)); MsgText_Clear(); } int JAM_NewLastRead(lastread LR) { lseek(fdJlr, 0, SEEK_END); return (write(fdJlr, &LR, sizeof(lastread)) == sizeof(lastread)); } int JAM_Next(unsigned long * ulMsg) { int RetVal = FALSE, MayBeNext = FALSE; JAMIDXREC jamIdx; unsigned long _Msg; _Msg = *ulMsg; if (jamHdrInfo.ActiveMsgs > 0L) { // -------------------------------------------------------------------- // The first attempt to retrive the next message number suppose that // the file pointers are located after the current message number. // Usually this is the 99% of the situations because the messages are // often readed sequentially. // -------------------------------------------------------------------- if (tell(fdJdx) >= sizeof (jamIdx)) lseek(fdJdx, tell(fdJdx) - sizeof(jamIdx), SEEK_SET); do { if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof (JAMHDR)); if (MayBeNext == TRUE) { if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum > _Msg) { _Msg = jamHdr.MsgNum; RetVal = TRUE; } } if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == _Msg) MayBeNext = TRUE; } } while (RetVal == FALSE && tell(fdJdx) < filelength(fdJdx)); if (RetVal == FALSE && MayBeNext == FALSE) { // -------------------------------------------------------------------- // It seems that the file pointers are not located where they should be // so our next attempt is to scan the database from the beginning to // find the next message number. // -------------------------------------------------------------------- lseek(fdJdx, 0L, SEEK_SET); do { if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum > _Msg) { _Msg = jamHdr.MsgNum; RetVal = TRUE; } } } while (RetVal == FALSE && tell(fdJdx) < filelength(fdJdx)); } Msg.Id = 0L; if (RetVal == TRUE) Msg.Id = _Msg; } memcpy(ulMsg, &_Msg, sizeof(unsigned long)); return RetVal; } /* * Return number of messages */ unsigned long JAM_Number(void) { return jamHdrInfo.ActiveMsgs; } /* * Open specified JAM message base */ int JAM_Open(char *Msgbase) { int RetVal = FALSE; char *File; char *Signature = (char *)HEADERSIGNATURE; fdJdt = fdJdx = fdJlr = -1; pSubfield = NULL; File = calloc(PATH_MAX, sizeof(char)); sprintf(File, "%s%s", Msgbase, EXT_HDRFILE); if ((fdHdr = open(File, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)) != -1) { if (read(fdHdr, &jamHdrInfo, sizeof(JAMHDRINFO)) != sizeof(JAMHDRINFO)) { memset(&jamHdrInfo, 0, sizeof(JAMHDRINFO)); jamHdrInfo.Signature[0] = Signature[0]; jamHdrInfo.Signature[1] = Signature[1]; jamHdrInfo.Signature[2] = Signature[2]; jamHdrInfo.Signature[3] = Signature[3]; jamHdrInfo.DateCreated = time(NULL); jamHdrInfo.BaseMsgNum = 1; lseek(fdHdr, 0, SEEK_SET); write(fdHdr, &jamHdrInfo, sizeof(JAMHDRINFO)); Syslog('+', "JAM created %s", Msgbase); } if (jamHdrInfo.Signature[0] == Signature[0] && jamHdrInfo.Signature[1] == Signature[1] && jamHdrInfo.Signature[2] == Signature[2] && jamHdrInfo.Signature[3] == Signature[3]) { sprintf(File, "%s%s", Msgbase, EXT_TXTFILE); fdJdt = open(File, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); sprintf(File, "%s%s", Msgbase, EXT_IDXFILE); fdJdx = open(File, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); sprintf(File, "%s%s", Msgbase, EXT_LRDFILE); fdJlr = open(File, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); RetVal = TRUE; strcpy(BaseName, Msgbase); } else { close(fdHdr); fdHdr = -1; } } else memset(&jamHdrInfo, 0, sizeof(JAMHDRINFO)); Msg.Id = 0L; free(File); if (!RetVal) WriteError("JAM error open %s", Msgbase); return RetVal; } /* * Pack deleted messages from the message base. The messages are * renumbered on the fly and the LastRead pointers are updated. */ void JAM_Pack(void) { int fdnHdr, fdnJdx, fdnJdt, fdnJlr; int ToRead, Readed, i, count; char *File, *New, *Subfield, *Temp; JAMIDXREC jamIdx; unsigned long NewNumber = 0, RefNumber = 0, Written = 0; lastread LR; File = calloc(PATH_MAX, sizeof(char)); New = calloc(PATH_MAX, sizeof(char)); sprintf(File, "%s%s", BaseName, ".$dr"); fdnHdr = open(File, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); sprintf(File, "%s%s", BaseName, ".$dt"); fdnJdt = open(File, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); sprintf(File, "%s%s", BaseName, ".$dx"); fdnJdx = open(File, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); sprintf(File, "%s%s", BaseName, ".$lr"); fdnJlr = open(File, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); /* * Get the number of LastRead records, this number is needed to prevent * that FreeBSD makes garbage of the LastRead pointers for some reason. */ count = lseek(fdJlr, 0, SEEK_END) / sizeof(lastread); if (fdnHdr != -1 && fdnJdt != -1 && fdnJdx != -1 && fdnJlr != -1) { lseek(fdHdr, 0L, SEEK_SET); if (read(fdHdr, &jamHdrInfo, sizeof(JAMHDRINFO)) == sizeof(JAMHDRINFO)) { write(fdnHdr, &jamHdrInfo, sizeof(JAMHDRINFO)); while (read(fdHdr, &jamHdr, sizeof(JAMHDR)) == sizeof(JAMHDR)) { RefNumber++; if (strncmp(jamHdr.Signature, "JAM", 3)) { WriteError("jamPack: %s headerfile corrupt", BaseName); lseek(fdJdx, (RefNumber -1) * sizeof(JAMIDXREC), SEEK_SET); read(fdJdx, &jamIdx, sizeof(JAMIDXREC)); lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof(JAMHDR)); if ((strncmp(jamHdr.Signature, "JAM", 3) == 0) && (jamHdr.MsgNum == RefNumber)) WriteError("jamPack: corrected the problem"); else { WriteError("jamPack: PANIC, problem cannot be solved, skipping this area"); Written = 0; break; } } if (jamHdr.Attribute & MSG_DELETED) { if (jamHdr.SubfieldLen > 0L) lseek (fdHdr, jamHdr.SubfieldLen, SEEK_CUR); } else { jamIdx.UserCRC = 0; jamIdx.HdrOffset = tell(fdnHdr); write(fdnJdx, &jamIdx, sizeof(JAMIDXREC)); lseek(fdJdt, jamHdr.TxtOffset, SEEK_SET); jamHdr.TxtOffset = tell(fdnJdt); NewNumber++; Written++; lseek(fdJlr, 0, SEEK_SET); for (i = 0; i < count; i++) { if ((read(fdJlr, &LR, sizeof(lastread)) == sizeof(lastread))) { /* * Test if one of the lastread pointer is the current * old message number. */ if ((LR.LastReadMsg == jamHdr.MsgNum) || (LR.HighReadMsg == jamHdr.MsgNum)) { /* * Adjust the matching numbers */ if (LR.LastReadMsg == jamHdr.MsgNum) LR.LastReadMsg = NewNumber; if (LR.HighReadMsg == jamHdr.MsgNum) LR.HighReadMsg = NewNumber; lseek(fdJlr, - sizeof(lastread), SEEK_CUR); write(fdJlr, &LR, sizeof(lastread)); } } } jamHdr.MsgNum = NewNumber; write(fdnHdr, &jamHdr, sizeof(JAMHDR)); if (jamHdr.SubfieldLen > 0L) { if ((Subfield = (char *)malloc ((size_t)(jamHdr.SubfieldLen + 1))) != NULL) { read (fdHdr, Subfield, (size_t)jamHdr.SubfieldLen); write (fdnHdr, Subfield, (size_t)jamHdr.SubfieldLen); free(Subfield); } } if ((Temp = (char *)malloc (MAX_TEXT)) != NULL) { do { if ((ToRead = MAX_TEXT) > jamHdr.TxtLen) ToRead = (int)jamHdr.TxtLen; Readed = (int)read (fdJdt, Temp, ToRead); write (fdnJdt, Temp, Readed); jamHdr.TxtLen -= Readed; } while (jamHdr.TxtLen > 0); free(Temp); } } } } /* * Correct any errors in the header */ if (Written) { lseek(fdnHdr, 0, SEEK_SET); if (read(fdnHdr, &jamHdrInfo, sizeof(JAMHDRINFO)) == sizeof(JAMHDRINFO)) { if (jamHdrInfo.ActiveMsgs != Written) { WriteError("jamPack: repair msgs %lu to %lu area %s", jamHdrInfo.ActiveMsgs, Written, BaseName); jamHdrInfo.ActiveMsgs = Written; lseek(fdnHdr, 0, SEEK_SET); write(fdnHdr, &jamHdrInfo, sizeof(JAMHDRINFO)); } } } /* * Now copy the lastread file, reset LastRead pointers if area is empty. */ lseek(fdJlr, 0, SEEK_SET); for (i = 0; i < count; i++) { if (read(fdJlr, &LR, sizeof(lastread)) == sizeof(lastread)) { if (jamHdrInfo.ActiveMsgs == 0 && (LR.LastReadMsg || LR.HighReadMsg)) { Syslog('-', "jamPack: reset LR pointer index %d, area %s", i, BaseName); LR.LastReadMsg = 0; LR.HighReadMsg = 0; } write(fdnJlr, &LR, sizeof(lastread)); } } /* * Close all files */ close(fdnHdr); close(fdnJdt); close(fdnJdx); close(fdnJlr); fdnHdr = fdnJdt = fdnJdx = fdnJlr = -1; close(fdHdr); close(fdJdt); close(fdJdx); close(fdJlr); fdHdr = fdJdt = fdJdx = fdJlr = -1; sprintf(File, "%s%s", BaseName, ".$dr"); sprintf(New, "%s%s", BaseName, EXT_HDRFILE); unlink(New); rename(File, New); sprintf(File, "%s%s", BaseName, ".$dt"); sprintf(New, "%s%s", BaseName, EXT_TXTFILE); unlink(New); rename(File, New); sprintf(File, "%s%s", BaseName, ".$dx"); sprintf(New, "%s%s", BaseName, EXT_IDXFILE); unlink(New); rename(File, New); sprintf(File, "%s%s", BaseName, ".$lr"); sprintf(New, "%s%s", BaseName, EXT_LRDFILE); unlink(New); rename(File, New); sprintf(File, "%s", BaseName); JAM_Open(File); } if (fdnHdr != -1) close(fdnHdr); sprintf(File, "%s%s", BaseName, ".$dr"); unlink(File); if (fdnJdt != -1) close(fdnJdt); sprintf(File, "%s%s", BaseName, ".$dt"); unlink(File); if (fdnJdx != -1) close(fdnJdx); sprintf(File, "%s%s", BaseName, ".$dx"); unlink(File); if (fdnJlr != -1) close(fdnJlr); sprintf(File, "%s%s", BaseName, ".$lr"); unlink(File); free(File); free(New); } int JAM_Previous (unsigned long *ulMsg) { int RetVal = FALSE, MayBeNext = FALSE; long Pos; JAMIDXREC jamIdx; unsigned long _Msg; _Msg = *ulMsg; if (jamHdrInfo.ActiveMsgs > 0L) { // -------------------------------------------------------------------- // The first attempt to retrive the next message number suppose that // the file pointers are located after the current message number. // Usually this is the 99% of the situations because the messages are // often readed sequentially. // -------------------------------------------------------------------- if (tell (fdJdx) >= sizeof (jamIdx)) { Pos = tell (fdJdx) - sizeof (jamIdx); do { lseek (fdJdx, Pos, SEEK_SET); if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (MayBeNext == TRUE) { if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum < _Msg) { _Msg = jamHdr.MsgNum; RetVal = TRUE; } } if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == _Msg) MayBeNext = TRUE; } Pos -= sizeof (jamIdx); } while (RetVal == FALSE && Pos >= 0L); } if (RetVal == FALSE && MayBeNext == FALSE) { // -------------------------------------------------------------------- // It seems that the file pointers are not located where they should be // so our next attempt is to scan the database from the end to find // the next message number. // -------------------------------------------------------------------- Pos = filelength (fdJdx) - sizeof (jamIdx); do { lseek (fdJdx, Pos, SEEK_SET); if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum < _Msg) { _Msg = jamHdr.MsgNum; RetVal = TRUE; } } Pos -= sizeof (jamIdx); } while (RetVal == FALSE && Pos >= 0L); } Msg.Id = 0L; if (RetVal == TRUE) Msg.Id = _Msg; } memcpy(ulMsg, &_Msg, sizeof(unsigned long)); return (RetVal); } int JAM_ReadHeader (unsigned long ulMsg) { int i, RetVal = FALSE; unsigned char *pPos; unsigned long ulSubfieldLen, tmp; JAMIDXREC jamIdx; JAMBINSUBFIELD *jamSubField; tmp = Msg.Id; JAM_New (); Msg.Id = tmp; if (Msg.Id == ulMsg) { // -------------------------------------------------------------------- // The user is requesting the header of the last message retrived // so our first attempt is to read the last index from the file and // check if this is the correct one. // -------------------------------------------------------------------- lseek (fdJdx, tell (fdJdx) - sizeof (jamIdx), SEEK_SET); if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == ulMsg) RetVal = TRUE; } } if (((Msg.Id + 1) == ulMsg) && (RetVal == FALSE)) { //--------------------------------------------------------------------- // If the user is requesting the header of the next message we attempt // to read the next header and check if this is the correct one. // This is EXPERIMENTAL //--------------------------------------------------------------------- if (read(fdJdx, &jamIdx, sizeof(jamIdx)) == sizeof(jamIdx)) { lseek(fdHdr, jamIdx.HdrOffset, SEEK_SET); read(fdHdr, &jamHdr, sizeof(JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == ulMsg) RetVal = TRUE; } } if (RetVal == FALSE) { // -------------------------------------------------------------------- // The message request is not the last retrived or the file pointers // are not positioned where they should be, so now we attempt to // retrieve the message header scanning the database from the beginning. // -------------------------------------------------------------------- Msg.Id = 0L; lseek (fdJdx, 0L, SEEK_SET); do { if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == ulMsg) RetVal = TRUE; } } while (RetVal == FALSE && tell (fdJdx) < filelength (fdJdx)); } if (RetVal == TRUE) { Msg.Current = Msg.Id = ulMsg; Msg.Local = (unsigned char)((jamHdr.Attribute & MSG_LOCAL) ? TRUE : FALSE); Msg.Intransit = (unsigned char)((jamHdr.Attribute & MSG_INTRANSIT) ? TRUE : FALSE); Msg.Private = (unsigned char)((jamHdr.Attribute & MSG_PRIVATE) ? TRUE : FALSE); Msg.Received = (unsigned char)((jamHdr.Attribute & MSG_READ) ? TRUE : FALSE); Msg.Sent = (unsigned char)((jamHdr.Attribute & MSG_SENT) ? TRUE : FALSE); Msg.KillSent = (unsigned char)((jamHdr.Attribute & MSG_KILLSENT) ? TRUE : FALSE); Msg.ArchiveSent = (unsigned char)((jamHdr.Attribute & MSG_ARCHIVESENT) ? TRUE : FALSE); Msg.Hold = (unsigned char)((jamHdr.Attribute & MSG_HOLD) ? TRUE : FALSE); Msg.Crash = (unsigned char)((jamHdr.Attribute & MSG_CRASH) ? TRUE : FALSE); Msg.Immediate = (unsigned char)((jamHdr.Attribute & MSG_IMMEDIATE) ? TRUE : FALSE); Msg.Direct = (unsigned char)((jamHdr.Attribute & MSG_DIRECT) ? TRUE : FALSE); Msg.Gate = (unsigned char)((jamHdr.Attribute & MSG_GATE) ? TRUE : FALSE); Msg.FileRequest = (unsigned char)((jamHdr.Attribute & MSG_FILEREQUEST) ? TRUE : FALSE); Msg.FileAttach = (unsigned char)((jamHdr.Attribute & MSG_FILEATTACH) ? TRUE : FALSE); Msg.TruncFile = (unsigned char)((jamHdr.Attribute & MSG_TRUNCFILE) ? TRUE : FALSE); Msg.KillFile = (unsigned char)((jamHdr.Attribute & MSG_KILLFILE) ? TRUE : FALSE); Msg.ReceiptRequest = (unsigned char)((jamHdr.Attribute & MSG_RECEIPTREQ) ? TRUE : FALSE); Msg.ConfirmRequest = (unsigned char)((jamHdr.Attribute & MSG_CONFIRMREQ) ? TRUE : FALSE); Msg.Orphan = (unsigned char)((jamHdr.Attribute & MSG_ORPHAN) ? TRUE : FALSE); Msg.Encrypt = (unsigned char)((jamHdr.Attribute & MSG_ENCRYPT) ? TRUE : FALSE); Msg.Compressed = (unsigned char)((jamHdr.Attribute & MSG_COMPRESS) ? TRUE : FALSE); Msg.Escaped = (unsigned char)((jamHdr.Attribute & MSG_ESCAPED) ? TRUE : FALSE); Msg.ForcePU = (unsigned char)((jamHdr.Attribute & MSG_FPU) ? TRUE : FALSE); Msg.Localmail = (unsigned char)((jamHdr.Attribute & MSG_TYPELOCAL) ? TRUE : FALSE); Msg.Echomail = (unsigned char)((jamHdr.Attribute & MSG_TYPEECHO) ? TRUE : FALSE); Msg.Netmail = (unsigned char)((jamHdr.Attribute & MSG_TYPENET) ? TRUE : FALSE); Msg.Nodisplay = (unsigned char)((jamHdr.Attribute & MSG_NODISP) ? TRUE : FALSE); Msg.Locked = (unsigned char)((jamHdr.Attribute & MSG_LOCKED) ? TRUE : FALSE); Msg.Deleted = (unsigned char)((jamHdr.Attribute & MSG_DELETED) ? TRUE : FALSE); Msg.Written = jamHdr.DateWritten; Msg.Arrived = jamHdr.DateProcessed; Msg.Read = jamHdr.DateReceived; Msg.Original = jamHdr.ReplyTo; Msg.Reply = jamHdr.ReplyNext; if (pSubfield != NULL) free (pSubfield); pSubfield = NULL; if (jamHdr.SubfieldLen > 0L) { ulSubfieldLen = jamHdr.SubfieldLen; pPos = pSubfield = (unsigned char *)malloc ((size_t)(ulSubfieldLen + 1)); if (pSubfield == NULL) return (FALSE); read (fdHdr, pSubfield, (size_t)jamHdr.SubfieldLen); while (ulSubfieldLen > 0L) { jamSubField = (JAMBINSUBFIELD *)pPos; pPos += sizeof (JAMBINSUBFIELD); /* * The next check is to prevent a segmentation * fault by corrupted subfields. */ if ((jamSubField->DatLen < 0) || (jamSubField->DatLen > jamHdr.SubfieldLen)) return FALSE; switch (jamSubField->LoID) { case JAMSFLD_SENDERNAME: if (jamSubField->DatLen > 100) { memcpy (Msg.From, pPos, 100); Msg.From[100] = '\0'; } else { memcpy (Msg.From, pPos, (int)jamSubField->DatLen); Msg.From[(int)jamSubField->DatLen] = '\0'; } break; case JAMSFLD_RECVRNAME: if (jamSubField->DatLen > 100) { memcpy (Msg.To, pPos, 100); Msg.To[100] = '\0'; } else { memcpy (Msg.To, pPos, (int)jamSubField->DatLen); Msg.To[(int)jamSubField->DatLen] = '\0'; } break; case JAMSFLD_SUBJECT: if (jamSubField->DatLen > 100) { memcpy (Msg.Subject, pPos, 100); Msg.Subject[100] = '\0'; } else { memcpy (Msg.Subject, pPos, (int)jamSubField->DatLen); Msg.Subject[(int)jamSubField->DatLen] = '\0'; } break; case JAMSFLD_OADDRESS: if (jamSubField->DatLen > 100) { memcpy (Msg.FromAddress, pPos, 100); Msg.FromAddress[100] = '\0'; } else { memcpy (Msg.FromAddress, pPos, (int)jamSubField->DatLen); Msg.FromAddress[(int)jamSubField->DatLen] = '\0'; } break; case JAMSFLD_DADDRESS: if (jamSubField->DatLen > 100) { memcpy(Msg.ToAddress, pPos, 100); Msg.ToAddress[100] = '\0'; } else { memcpy (Msg.ToAddress, pPos, (int)jamSubField->DatLen); Msg.ToAddress[(int)jamSubField->DatLen] = '\0'; } break; case JAMSFLD_MSGID: memcpy (Msg.Msgid, pPos, (int)jamSubField->DatLen); Msg.Msgid[(int)jamSubField->DatLen] = '\0'; break; case JAMSFLD_REPLYID: memcpy (Msg.Replyid, pPos, (int)jamSubField->DatLen); Msg.Replyid[(int)jamSubField->DatLen] = '\0'; break; default: break; } ulSubfieldLen -= sizeof (JAMBINSUBFIELD) + jamSubField->DatLen; if (ulSubfieldLen > 0) pPos += (int)jamSubField->DatLen; } } /* * In the original BBS we found that GEcho was not * setting the FromAddress. We take it from the MSGID * if there is one. */ if ((!strlen(Msg.FromAddress)) && (strlen(Msg.Msgid))) { for (i = 0; i < strlen(Msg.Msgid); i++) { if ((Msg.Msgid[i] == '@') || (Msg.Msgid[i] == ' ')) break; Msg.FromAddress[i] = Msg.Msgid[i]; } } } return (RetVal); } /* * Read message */ int JAM_Read(unsigned long ulMsg, int nWidth) { int RetVal = FALSE, SkipNext; int i, nReaded, nCol, nRead; unsigned char *pPos; unsigned long ulTxtLen, ulSubfieldLen; JAMIDXREC jamIdx; JAMBINSUBFIELD *jamSubField; LDATA *Bottom = NULL, *New; MsgText_Clear(); if ((RetVal = JAM_ReadHeader(ulMsg)) == TRUE) { lseek (fdJdx, tell (fdJdx) - sizeof (jamIdx), SEEK_SET); read (fdJdx, &jamIdx, sizeof (jamIdx)); lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (pSubfield != NULL) free (pSubfield); pSubfield = NULL; if (jamHdr.SubfieldLen > 0L) { ulSubfieldLen = jamHdr.SubfieldLen; pPos = pSubfield = (unsigned char *)malloc ((size_t)(ulSubfieldLen + 1)); if (pSubfield == NULL) return (FALSE); read (fdHdr, pSubfield, (size_t)jamHdr.SubfieldLen); while (ulSubfieldLen > 0L) { jamSubField = (JAMBINSUBFIELD *)pPos; pPos += sizeof (JAMBINSUBFIELD); /* * Check for corrupted subfields */ if ((jamSubField->DatLen < 0) || (jamSubField->DatLen > jamHdr.SubfieldLen)) return FALSE; switch (jamSubField->LoID) { case JAMSFLD_MSGID: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; memset(&Msg.Msgid, 0, sizeof(Msg.Msgid)); sprintf(Msg.Msgid, "%s", szBuff); sprintf (szLine, "\001MSGID: %s", szBuff); MsgText_Add2(szLine); break; case JAMSFLD_REPLYID: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "\001REPLY: %s", szBuff); MsgText_Add2(szLine); break; case JAMSFLD_PID: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "\001PID: %s", szBuff); MsgText_Add2(szLine); break; case JAMSFLD_TRACE: memcpy(szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "\001Via %s", szBuff); MsgText_Add2(szLine); break; case JAMSFLD_FTSKLUDGE: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; if (!strncmp(szBuff, "AREA:", 5)) sprintf(szLine, "%s", szBuff); else { sprintf (szLine, "\001%s", szBuff); if (strncmp(szLine, "\001REPLYADDR:", 11) == 0) { sprintf(Msg.ReplyAddr, "%s", szLine+12); } if (strncmp(szLine, "\001REPLYTO:", 9) == 0) { sprintf(Msg.ReplyTo, "%s", szLine+10); } if (strncmp(szLine, "\001REPLYADDR", 10) == 0) { sprintf(Msg.ReplyAddr, "%s", szLine+11); } if (strncmp(szLine, "\001REPLYTO", 8) == 0) { sprintf(Msg.ReplyTo, "%s", szLine+9); } } MsgText_Add2(szLine); break; case JAMSFLD_SEENBY2D: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "SEEN-BY: %s", szBuff); if ((New = (LDATA *)malloc(sizeof(LDATA))) != NULL) { memset(New, 0, sizeof(LDATA)); New->Value = strdup(szLine); if (Bottom != NULL) { while (Bottom->Next != NULL) Bottom = Bottom->Next; New->Previous = Bottom; New->Next = Bottom->Next; if (New->Next != NULL) New->Next->Previous = New; Bottom->Next = New; } Bottom = New; } break; case JAMSFLD_PATH2D: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "\001PATH: %s", szBuff); if ((New = (LDATA *)malloc(sizeof(LDATA))) != NULL) { memset(New, 0, sizeof(LDATA)); New->Value = strdup(szLine); if (Bottom != NULL) { while (Bottom->Next != NULL) Bottom = Bottom->Next; New->Previous = Bottom; New->Next = Bottom->Next; if (New->Next != NULL) New->Next->Previous = New; Bottom->Next = New; } Bottom = New; } break; case JAMSFLD_FLAGS: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szLine, "\001FLAGS %s", szLine); MsgText_Add2(szLine); break; case JAMSFLD_TZUTCINFO: memcpy (szBuff, pPos, (int)jamSubField->DatLen); szBuff[(int)jamSubField->DatLen] = '\0'; sprintf (szBuff, "\001TZUTC %s", szLine); MsgText_Add2(szLine); break; default: break; } ulSubfieldLen -= sizeof (JAMBINSUBFIELD) + jamSubField->DatLen; if (ulSubfieldLen > 0) pPos += (int)jamSubField->DatLen; } } lseek (fdJdt, jamHdr.TxtOffset, SEEK_SET); ulTxtLen = jamHdr.TxtLen; pLine = szLine; nCol = 0; SkipNext = FALSE; do { if ((unsigned long)(nRead = sizeof (szBuff)) > ulTxtLen) nRead = (int)ulTxtLen; nReaded = (int)read (fdJdt, szBuff, nRead); for (i = 0, pBuff = szBuff; i < nReaded; i++, pBuff++) { if (*pBuff == '\r') { *pLine = '\0'; if (pLine > szLine && SkipNext == TRUE) { pLine--; while (pLine > szLine && *pLine == ' ') *pLine-- = '\0'; if (pLine > szLine) MsgText_Add3(szLine, (int)(strlen (szLine) + 1)); } else if (SkipNext == FALSE) MsgText_Add2(szLine); SkipNext = FALSE; pLine = szLine; nCol = 0; } else if (*pBuff != '\n') { *pLine++ = *pBuff; nCol++; if (nCol >= nWidth) { *pLine = '\0'; if (strchr (szLine, ' ') != NULL) { while (nCol > 1 && *pLine != ' ') { nCol--; pLine--; } if (nCol > 0) { while (*pLine == ' ') pLine++; strcpy (szWrp, pLine); } *pLine = '\0'; } else szWrp[0] = '\0'; MsgText_Add2(szLine); strcpy (szLine, szWrp); pLine = strchr (szLine, '\0'); nCol = (int)strlen (szLine); SkipNext = TRUE; } } } ulTxtLen -= nRead; } while (ulTxtLen > 0); if (Bottom != NULL) { while (Bottom->Previous != NULL) Bottom = Bottom->Previous; MsgText_Add2(Bottom->Value); while (Bottom->Next != NULL) { Bottom = Bottom->Next; MsgText_Add2(Bottom->Value); } while (Bottom != NULL) { if (Bottom->Previous != NULL) Bottom->Previous->Next = Bottom->Next; if (Bottom->Next != NULL) Bottom->Next->Previous = Bottom->Previous; New = Bottom; if (Bottom->Next != NULL) Bottom = Bottom->Next; else if (Bottom->Previous != NULL) Bottom = Bottom->Previous; else Bottom = NULL; free(New->Value); free(New); } } } return (RetVal); } int JAM_SetLastRead(lastread LR) { if (lseek(fdJlr, LastReadRec * sizeof(lastread), SEEK_SET) != -1) if (write(fdJlr, &LR, sizeof(lastread)) == sizeof(lastread)) return TRUE; return FALSE; } /* * Unlock the message base */ void JAM_UnLock(void) { struct flock fl; fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; fl.l_start = 0L; fl.l_len = 1L; /* GoldED locks 1 byte as well */ fl.l_pid = getpid(); if (fcntl(fdHdr, F_SETLK, &fl)) { WriteError("$Can't unlock JAM message base %s", BaseName); } } /* * Write message header */ int JAM_WriteHeader (unsigned long ulMsg) { int RetVal = FALSE; JAMIDXREC jamIdx; if (Msg.Id == ulMsg) { // -------------------------------------------------------------------- // The user is requesting to write the header of the last message // retrived so our first attempt is to read the last index from the // file and check if this is the correct one. // -------------------------------------------------------------------- lseek (fdJdx, tell (fdJdx) - sizeof (jamIdx), SEEK_SET); if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == ulMsg) RetVal = TRUE; } } if (RetVal == FALSE) { // -------------------------------------------------------------------- // The message requested is not the last retrived or the file pointers // are not positioned where they should be, so now we attempt to // retrive the message header scanning the database from the beginning. // -------------------------------------------------------------------- Msg.Id = 0L; lseek (fdJdx, 0L, SEEK_SET); do { if (read (fdJdx, &jamIdx, sizeof (jamIdx)) == sizeof (jamIdx)) { lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); read (fdHdr, &jamHdr, sizeof (JAMHDR)); if (!(jamHdr.Attribute & MSG_DELETED) && jamHdr.MsgNum == ulMsg) RetVal = TRUE; } } while (RetVal == FALSE && tell (fdJdx) < filelength (fdJdx)); } if (RetVal == TRUE) { Msg.Id = jamHdr.MsgNum; jamHdr.Attribute &= MSG_DELETED; JAMset_flags(); lseek (fdHdr, jamIdx.HdrOffset, SEEK_SET); write (fdHdr, &jamHdr, sizeof (JAMHDR)); } return RetVal; }