/************************************************************************/ /* msg.c */ /* */ /* Message handling for Citadel bulletin board system */ /************************************************************************/ /************************************************************************/ /* history */ /* */ /* 83Mar03 CrT & SB Various bug fixes... */ /* 83Feb27 CrT Save private mail for sender as well as recipient. */ /* 83Feb23 Various. transmitFile() won't drop first char on WC... */ /* 82Dec06 CrT 2.00 release. */ /* 82Nov05 CrT Stream retrieval. Handles messages longer than MAXTEXT.*/ /* 82Nov04 CrT Revised disk format implemented. */ /* 82Nov03 CrT Individual history begun. General cleanup. */ /************************************************************************/ #include "b:210ctdl.h" /************************************************************************/ /* contents */ /* */ /* aideMessage() saves auto message in Aide> */ /* dGetWord() reads a word off disk */ /* dPrintf() printf() that writes to disk */ /* fakeFullCase() converts uppercase message to mixed case*/ /* findPerson() load log record for named person */ /* flushMsgBuf() wraps up message-to-disk store */ /* getMessage() load message into RAM */ /* getMsgChar() returns successive chars off disk */ /* getMsgStr() reads a string out of message.buf */ /* getWord() gets one word from message buffer */ /* mAbort() checks for user abort of typeout */ /* makeMessage() menu-level message-entry routine */ /* mFormat() formats a string to modem and console */ /* mPeek() sysop debugging tool--shows ctdlmsg.sys */ /* mPrintf() writes a line to modem & console */ /* mWCprintf() special mprintf for WC transfers */ /* msgInit() sets up catChar, catSect etc. */ /* noteLogMessage() enter message into log record */ /* noteMessage() enter message into current room */ /* note2Message() noteMessage() local */ /* printMessage() prints a message on modem & console */ /* pullIt() sysop special message-removal routine */ /* putMessage() write message to disk */ /* putMsgChar() writes successive message chars to disk */ /* putWord() writes one word to modem & console */ /* showMessages() menu-level show-roomful-of-messages fn */ /* startAt() setup to read a message off disk */ /* unGetMsgChar() return a char to getMsgChar() */ /* zapMsgFile() initialize ctdlmsg.sys */ /************************************************************************/ /************************************************************************/ /* aideMessage() saves auto message in Aide> */ /************************************************************************/ aideMessage(noteDeletedMessage) char noteDeletedMessage; { int ourRoom; /* message is already set up in msgBuf.mbtext */ putRoom(ourRoom=thisRoom, &roomBuf); getRoom(AIDEROOM, &roomBuf); strCpy(msgBuf.mbauth, "Citadel"); msgBuf.mbto[0] = '\0'; if (putMessage( /* uploading== */ FALSE)) noteMessage(0, ERROR); if (noteDeletedMessage) { note2Message(pulledMId, pulledMLoc); } putRoom(AIDEROOM, &roomBuf); noteRoom(); getRoom(ourRoom, &roomBuf); } /************************************************************************/ /* dGetWord() fetches one word from current message, off disk */ /* returns TRUE if more words follow, else FALSE */ /************************************************************************/ char dGetWord(dest, lim) char *dest; int lim; { char getMsgChar(); char c; --lim; /* play it safe */ /* pick up any leading blanks: */ for (c = getMsgChar(); c == ' ' && c && lim; c = getMsgChar()) { if (lim) { *dest++ = c; lim--; } } /* step through word: */ for ( ; c != ' ' && c && lim; c = getMsgChar()) { if (lim) { *dest++ = c; lim--; } } /* trailing blanks: */ for ( ; c == ' ' && c && lim; c = getMsgChar()) { if (lim) { *dest++ = c; lim--; } } if (c) unGetMsgChar(c); /* took one too many */ *dest = '\0'; /* tie off string */ return c; } /************************************************************************/ /* dPrintf() write from format+args to disk */ /************************************************************************/ dPrintf(format /* plus an unknown #arguments for format */) char *format; #define MAXWORD 256 /* maximum length of a word */ { char *s, string[MAXWORD]; string[0] = 0; _spr(string, &format); s = string; while (*s) putMsgChar(*s++); } /************************************************************************/ /* fakeFullCase() converts a message in uppercase-only to a */ /* reasonable mix. It can't possibly make matters worse... */ /* Algorithm: First alphabetic after a period is uppercase, all */ /* others are lowercase, excepting pronoun "I" is a special case. */ /* We assume an imaginary period preceding the text. */ /************************************************************************/ fakeFullCase(text) char *text; { char toLower(), toUpper(); char *c; char lastWasPeriod; char state; for(lastWasPeriod=TRUE, c=text; *c; c++) { if ( *c != '.' && *c != '?' && *c != '!' ) { if (isAlpha(*c)) { if (lastWasPeriod) *c = toUpper(*c); else *c = toLower(*c); lastWasPeriod = FALSE; } } else { lastWasPeriod = TRUE ; } } /* little state machine to search for ' i ': */ #define NUTHIN 0 #define FIRSTBLANK 1 #define BLANKI 2 for (state=NUTHIN, c=text; *c; c++) { switch (state) { case NUTHIN: if (isSpace(*c)) state = FIRSTBLANK; else state = NUTHIN ; break; case FIRSTBLANK: if (*c == 'i') state = BLANKI ; else state = NUTHIN ; break; case BLANKI: if (isSpace(*c)) state = FIRSTBLANK; else state = NUTHIN ; if (!isAlpha(*c)) *(c-1) = 'I'; break; } } } /************************************************************************/ /* findPerson() loads log record for named person. */ /* RETURNS: ERROR if not found, else log record # */ /************************************************************************/ int findPerson(name, lBuf) char *name; struct logBuffer *lBuf; { int h, i, foundIt, logNo; h = hash(name); for (foundIt=i=0; ilbname) == SAMESTRING) { foundIt = TRUE; } } } if (!foundIt) return ERROR; else return logNo; } /************************************************************************/ /* flushMsgBuf() wraps up writing a message to disk */ /************************************************************************/ flushMsgBuf() { rseek(msgfl, thisSector, 0); crypte(sectBuf, SECTSIZE, 0); if (rwrite(msgfl, sectBuf, 1) != 1) { printf("?ctdlmsg.sys write fail"); } crypte(sectBuf, SECTSIZE, 0); } /************************************************************************/ /* getMessage() reads a message off disk into RAM. */ /* a previous call to setUp has specified the message. */ /************************************************************************/ getMessage() { char c; /* clear msgBuf out */ msgBuf.mbauth[ 0] = '\0'; msgBuf.mbdate[ 0] = '\0'; msgBuf.mborig[ 0] = '\0'; msgBuf.mboname[0] = '\0'; msgBuf.mbroom[ 0] = '\0'; msgBuf.mbsrcId[0] = '\0'; msgBuf.mbtext[ 0] = '\0'; msgBuf.mbto[ 0] = '\0'; do c = getMsgChar(); while (c != 0xFF); /* find start of msg */ msgBuf.mbheadChar = oldChar; /* record location */ msgBuf.mbheadSector = oldSector; getMsgStr(msgBuf.mbId, NAMESIZE); do { c = getMsgChar(); switch (c) { case 'A': getMsgStr(msgBuf.mbauth, NAMESIZE); break; case 'D': getMsgStr(msgBuf.mbdate, NAMESIZE); break; case 'M': /* just exit -- we'll read off disk */ break; case 'N': getMsgStr(msgBuf.mboname, NAMESIZE); break; case 'O': getMsgStr(msgBuf.mborig, NAMESIZE); break; case 'R': getMsgStr(msgBuf.mbroom, NAMESIZE); break; case 'S': getMsgStr(msgBuf.mbsrcId, NAMESIZE); break; case 'T': getMsgStr(msgBuf.mbto, NAMESIZE); break; default: getMsgStr(msgBuf.mbtext, MAXTEXT); /* discard unknown field */ msgBuf.mbtext[0] = '\0'; break; } } while (c != 'M' && isAlpha(c)); } /************************************************************************/ /* getMsgChar() returns sequential chars from message on disk */ /************************************************************************/ char getMsgChar() { char visible(); char toReturn; int mark, val; if (GMCCache) { /* someone did an unGetMsgChar() --return it */ toReturn= GMCCache; GMCCache= '\0'; return toReturn; } oldChar = thisChar; oldSector = thisSector; toReturn = sectBuf[thisChar]; #ifdef XYZZY if (debug) putCh(visible(toReturn)); #endif thisChar = ++thisChar % SECTSIZE; if (thisChar == 0) { /* time to read next sector in: */ thisSector = ++thisSector % maxMSector; rseek(msgfl, thisSector, 0); if (rread(msgfl, sectBuf, 1) >= 1000) { printf("?nextMsgChar-rread fail"); } crypte(sectBuf, SECTSIZE, 0); } return(toReturn); } /************************************************************************/ /* getMsgStr() reads a string from message.buf */ /************************************************************************/ getMsgStr(dest, lim) char *dest; int lim; { char c; while (c = getMsgChar()) { /* read the complete string */ if (lim) { /* if we have room then */ lim--; *dest++ = c; /* copy char to buffer */ } } *dest = '\0'; /* tie string off with null */ } /************************************************************************/ /* getWord() fetches one word from current message */ /************************************************************************/ int getWord(dest, source, offset, lim) char *dest, *source; int lim, offset; { int i, j; /* skip leading blanks if any */ for (i=0; source[offset+i]==' ' && inewestHi) || (hereHi==newestHi && hereLo>newestLo)) { newestHi = hereHi; newestLo = hereLo; printf(" newest=%u %u\n", newestHi, newestLo); /* read rest of message in and remember where it ends, */ /* in case it turns out to be the last message */ /* in which case, that's where to start writing next message*/ while (dGetWord(msgBuf.mbtext, MAXTEXT)); catSector = thisSector; catChar = thisChar; } } } /************************************************************************/ /* noteLogMessage() slots message into log record */ /************************************************************************/ noteLogMessage(lBuf, logNo) struct logBuffer *lBuf; int logNo; { int i; /* store into recipient's log record: */ /* slide message pointers down to make room for this one: */ for (i=0; i room -- 'sysop' is special */ note2Message(newestLo, catSector); /* write it to disk: */ putRoom(AIDEROOM, &roomBuf); noteRoom(); getRoom(MAILROOM, &roomBuf); /* note in ourself if logged in: */ if (loggedIn) noteLogMessage(&logBuf, thisLog); fillMailRoom(); } } /* make message official: */ catSector = thisSector; catChar = thisChar; setUp(FALSE); } /************************************************************************/ /* note2Message() makes slot in current room... called by noteMess */ /************************************************************************/ note2Message(id, loc) int id, loc; { int i; /* store into current room: */ /* slide message pointers down to make room for this one: */ for (i=0; i", msgBuf.mbroom ); } if (msgBuf.mbto[ 0]) mPrintf( " to %s", msgBuf.mbto ); doCR(); do { moreFollows = dGetWord(msgBuf.mbtext, 150); putWord(msgBuf.mbtext); } while (moreFollows && !mAbort()); doCR(); } else { /* networking dump of message: */ /* fill in local node in origin fields if local message: */ if (!msgBuf.mborig[ 0]) strcpy(msgBuf.mborig, nodeId ); if (!msgBuf.mboname[0]) strcpy(msgBuf.mboname, nodeName ); if (!msgBuf.mbsrcId[0]) strcpy(msgBuf.mbsrcId, msgBuf.mbId); /* send header fields out: */ if (msgBuf.mbauth[ 0]) mWCprintf("A%s", msgBuf.mbauth ); if (msgBuf.mbdate[ 0]) mWCprintf("D%s", msgBuf.mbdate ); if (msgBuf.mboname[0]) mWCprintf("N%s", msgBuf.mboname); if (msgBuf.mborig[ 0]) mWCprintf("O%s", msgBuf.mborig ); if (msgBuf.mbroom[ 0]) mWCprintf("R%s", msgBuf.mbroom ); if (msgBuf.mbsrcId[0]) mWCprintf("S%s", msgBuf.mbsrcId); if (msgBuf.mbto[ 0]) mWCprintf("T%s", msgBuf.mbto ); /* send message text proper: */ sendWCChar('M'); do { c = getMsgChar(); if (c=='\n') c='\r'; sendWCChar(c); } while (c); } } /************************************************************************/ /* pullIt() is a sysop special to remove a message from a room */ /************************************************************************/ pullIt(m) int m; { int i, low; /* confirm that we're removing the right one: */ outFlag = OUTOK; printMessage(roomBuf.vp.msg[m].rbmsgLoc, roomBuf.vp.msg[m].rbmsgNo); if (!getYesNo("pull")) return; /* record vital statistics for possible insertion elsewhere: */ pulledMLoc = roomBuf.vp.msg[m].rbmsgLoc; pulledMId = roomBuf.vp.msg[m].rbmsgNo ; if (thisRoom == AIDEROOM) return; /* return emptied slot: */ for (i=m; i>0; i--) { roomBuf.vp.msg[i].rbmsgLoc = roomBuf.vp.msg[i-1].rbmsgLoc; roomBuf.vp.msg[i].rbmsgNo = roomBuf.vp.msg[i-1].rbmsgNo ; } roomBuf.vp.msg[0].rbmsgNo = ERROR; /* mark new slot at end as free */ /* store revised room to disk before we forget... */ noteRoom(); putRoom(thisRoom, &roomBuf); /* note in Aide>: */ sPrintf(msgBuf.mbtext, "Following message deleted by %s:", logBuf.lbname); aideMessage( /* noteDeletedMessage== */ TRUE); } /************************************************************************/ /* putMessage() stores a message to disk */ /* Always called before noteMessage() -- newestLo not ++ed yet. */ /* Returns: TRUE on successful save, else FALSE */ /************************************************************************/ char putMessage(uploading) char uploading; /* true to get text via WC modem input, not RAM */ { char *s, allOk; int putWCChar(); startAt(catSector, catChar); /* tell putMsgChar where to write */ putMsgChar(0xFF); /* start-of-message */ /* write message ID */ dPrintf("%u %u", newestHi, newestLo+1); putMsgChar(0); /* write date: */ dPrintf("D%d%s%02d", interpret(pGetYear), monthTab[interpret(pGetMonth)], interpret(pGetDay) ); putMsgChar(0); /* write room name out: */ dPrintf("R%s", roomBuf.rbname); putMsgChar(0); if (loggedIn) { /* write author's name out: */ dPrintf("A%s", msgBuf.mbauth); putMsgChar(0); /* null to end string */ } if (msgBuf.mbto[0]) { /* private message -- write addressee */ dPrintf("T%s", msgBuf.mbto); putMsgChar(0); } /* write message text by hand because it would overrun dPrintf buffer: */ putMsgChar('M'); /* M-for-message. */ if (!uploading) { for (s=msgBuf.mbtext; *s; s++) putMsgChar(*s); allOk = TRUE; } else { outFlag = FALSE; /* setup for putWCChar() */ allOk = readFile(putWCChar); } if (allOk) { putMsgChar(0); /* null to end text */ flushMsgBuf(); } else { flushMsgBuf(); /* so message count is ok */ /* erase start-of-message indicator: */ startAt(catSector, catChar); putmsgChar(0); /* overwrite 0xFF byte */ } return allOk; } /************************************************************************/ /* putMsgChar() writes successive message chars to disk */ /* Globals: thisChar= thisSector= */ /* Returns: ERROR if problems else TRUE */ /************************************************************************/ int putMsgChar(c) char c; { char visible(); int toReturn; toReturn = TRUE; #ifdef XYZZY if (debug) putch(visible(c)); #endif if (sectBuf[thisChar] == 0xFF) { /* obliterating a msg */ if (!++oldestLo) ++oldestHi; /* 32-bit increment by hand */ logBuf.lbvisit[(MAXVISIT-1)] = oldestLo; } sectBuf[thisChar] = c; thisChar = ++thisChar % SECTSIZE; if (thisChar == 0) { /* time to write sector out a get next: */ rseek(msgfl, thisSector, 0); crypte(sectBuf, SECTSIZE, 0); if (rwrite(msgfl, sectBuf, 1) != 1) { printf("?putMsgChar-rwrite fail"); toReturn = ERROR; } thisSector = ++thisSector % maxMSector; rseek(msgfl, thisSector, 0); if (rread(msgfl, sectBuf, 1) >= 1000) { printf("?putMsgChar-rread fail"); toReturn = ERROR; } crypte(sectBuf, SECTSIZE, 0); } return toReturn; } /************************************************************************/ /* putWord() writes one word to modem & console */ /************************************************************************/ putWord(st) char *st; { char *s; int newColumn; for (newColumn=crtColumn, s=st; *s; s++) { if (*s != TAB) ++newColumn; else while (++newColumn % 8); } if (newColumn > termWidth) doCR(); for (; *st; st++) { if (*st != TAB) ++crtColumn; else while (++crtColumn % 8); /* worry about words longer than a line: */ if (crtColumn > termWidth) doCR(); if (prevChar!=NEWLINE || (*st > ' ')) oChar(*st); else { /* end of paragraph: */ if (outFlag == OUTPARAGRAPH) { outFlag = OUTOK; } doCR(); oChar(*st); } } } /************************************************************************/ /* showMessages() is routine to print roomful of msgs */ /************************************************************************/ showMessages(whichMess, revOrder) char whichMess, revOrder; { char toUpper(), iChar(); char c; int i; int start, finish, increment, msgNo; unsigned lowLim, highLim; setUp(FALSE); /* Allow for reverse retrieval: */ if (!revOrder) { start = 0; finish = MSGSPERRM; increment = 1; } else { start = (MSGSPERRM -1); finish = -1; increment = -1; } switch (whichMess) { case NEWoNLY: lowLim = logBuf.lbvisit[ logBuf.lbgen[thisRoom] & CALLMASK]+1; highLim = newestLo; break; case OLDaNDnEW: lowLim = oldestLo; highLim = newestLo; break; case OLDoNLY: lowLim = oldestLo; highLim = logBuf.lbvisit[ logBuf.lbgen[thisRoom] & CALLMASK]; break; } /* stuff may have scrolled off system unseen, so: */ /* was "if (lowLim < oldestLo)...", rigged for wraparound: */ if (oldestLo-lowLim < 0x8000) { lowLim = oldestLo; } if (!expert && !usingWCprotocol) { mPrintf("\n ump ext

ause top"); } for (i=start; i!=finish; i+=increment) { if (outFlag) { if ( outFlag == OUTNEXT || outFlag == OUTPARAGRAPH ) outFlag = OUTOK; else if (outFlag == OUTSKIP) { echo = BOTH; return; } } /* "<" comparison with 64K wraparound in mind: */ msgNo = roomBuf.vp.msg[i].rbmsgNo; if ( msgNo - lowLim < 0x8000 && highLim - msgNo < 0x8000 ) { printMessage(roomBuf.vp.msg[i].rbmsgLoc, msgNo); /* Pull current message from room if flag set */ if (pullMessage) { pullMessage = FALSE; pullIt(i); if (revOrder) i++; } if ( thisRoom == MAILROOM && whichMess == NEWoNLY && getYesNo("respond") ) { if (makeMessage( /* uploading== */ FALSE)) i--; } } } } /************************************************************************/ /* startAt() sets location to begin reading message from */ /************************************************************************/ startAt(sect, byt) int sect; int byt; { GMCCache = '\0'; /* cache to unGetMsgChar() into */ if (sect >= maxMSector) { printf("?startAt s=%d,b=%d", sect, byt); return; } thisChar = byt; thisSector = sect; rseek(msgfl, sect, 0); if (rread(msgfl, sectBuf, 1) >= 1000) { printf("?startAt rread fail"); } crypte(sectBuf, SECTSIZE, 0); } /************************************************************************/ /* unGetMsgChar() returns (at most one) char to getMsgChar() */ /************************************************************************/ unGetMsgChar(c) char c; { GMCCache = c; } /************************************************************************/ /* zapMsgFl() initializes message.buf */ /************************************************************************/ zapMsgFile() { char getCh(), toUpper(); int i, sect, val; printf("\nDestroy all current messages? "); if (toUpper(getCh()) != 'Y') return; /* put null message in first sector... */ sectBuf[0] = 0xFF; /* \ */ sectBuf[1] = '0'; /* \ */ sectBuf[2] = ' '; /* > Message ID "0 1" */ sectBuf[3] = '1'; /* / */ sectBuf[4] = '\0'; /* / */ sectBuf[5] = 'M'; /* \ Null messsage */ sectBuf[6] = '\0'; /* / */ for (i=7; i