DIRECTORY BasicTime USING [GMT, nullGMT, Unpacked, Now, Unpack], DefaultRegistry USING [UsersRegistry], GVBasics USING [ItemType, RName], GVMailParse, GVNames, GVSend USING [Abort, AddRecipient, AddToItem, CheckValidity, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo], Icons, List USING [Append], Menus, IO, Rope, RuntimeError USING [BoundsFault], WalnutDocumentRope USING[ Create ], TiogaOps, ViewerOps, ViewerTools, UserCredentials USING [Get], WalnutSendOps, WalnutSendInternal, WalnutParse USING [MessageFieldIndex, MessageInfo]; WalnutSendMailImpl: CEDAR MONITOR IMPORTS BasicTime, DefaultRegistry, GVMailParse, GVNames, GVSend, List, IO, Rope, RuntimeError, WalnutDocumentRope, TiogaOps, ViewerOps, ViewerTools, UserCredentials, WalnutSendInternal, WalnutSendOps EXPORTS WalnutSendInternal, WalnutSendOps, WalnutParse = BEGIN OPEN WalnutSendInternal; defaultRegistry: PUBLIC ROPE_ DefaultRegistry.UsersRegistry[]; messageParseArray: PUBLIC ARRAY WalnutParse.MessageFieldIndex OF WalnutParse.MessageInfo _ [ ["Reply-to", simpleRope], -- this is really wrong, a special case for now ["Sender", simpleRope], ["From", simpleRope], ["To", rNameList], ["cc", rNameList], ["c", rNameList], ["bcc", rNameList], ["Date", simpleRope], ["Subject", simpleRope], ["Categories", rCatList], ["In-reply-to", simpleRope], ["VoiceFileID", simpleRope] ]; Send: PUBLIC PROC[v: Viewer, doClose: BOOL_ FALSE] RETURNS[sent: BOOL] = { oldMenu: Menus.Menu = v.menu; v.inhibitDestroy_ TRUE; BEGIN ENABLE UNWIND => GOTO out; ViewerOps.SetMenu[v, sendingMenu]; IF needToAuthenticate THEN { SenderReport["Authenticating user ..."]; IF ~AuthenticateUser[] THEN {ViewerOps.BlinkIcon[v, IF v.iconic THEN 0 ELSE 1]; ViewerOps.SetMenu[v, oldMenu]; v.inhibitDestroy_ FALSE; RETURN[FALSE] }; SenderReport[" ...ok\n"]; }; sent_ InternalSendMsg[v, doClose]; EXITS out => NULL; END; ViewerOps.SetMenu[v, oldMenu]; v.inhibitDestroy_ FALSE; }; AppendHeaderLine: PUBLIC PROC[v: Viewer, line: ROPE, changeSelection: BOOL _ FALSE] = BEGIN ENABLE RuntimeError.BoundsFault => GOTO exit; text: ROPE; i: INT_ 0; ch: CHAR; TRUSTED {text_ WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[v]]]}; DO -- find the double CR at the end of the headers UNTIL (ch_ text.Fetch[i]) = '\n DO i_ i + 1; ENDLOOP; IF (ch_ text.Fetch[i_ i + 1]) = '\n THEN EXIT; ENDLOOP; InsertIntoViewer[v, line, i, WalnutSendOps.labelFont, changeSelection]; ViewerTools.EnableUserEdits[v]; EXITS exit => SenderReport[IO.PutFR["Malformed headers; append of &g not done", IO.rope[line]]]; END; AuthenticateUser: PROC RETURNS [BOOL] = BEGIN OPEN WalnutSendOps; auth: GVNames.AuthenticateInfo; IF userRName.Length[] = 0 THEN {SenderReport["Please Login\n"]; RETURN[FALSE]}; SELECT auth_ GVNames.Authenticate[userRName, UserCredentials.Get[].password] FROM group => SenderReport["... Can't login as group\n"]; individual => {needToAuthenticate_ FALSE; RETURN[TRUE]}; notFound => {SenderReport[userRName]; SenderReport[" is invalid - please Login\n"]}; allDown => SenderReport["... No server responded\n"]; badPwd => SenderReport["... Your Password is invalid - please Login\n"]; ENDCASE; RETURN[FALSE]; END; InternalSendMsg: PROC[senderV: Viewer, doClose: BOOL] RETURNS[sendOk: BOOL] = BEGIN status: SendParseStatus; sPos, mPos: INT; formatting: ROPE; smr: SendingRec; addedTxtLength: INT; newTxt: ROPE; contents, currentText: ViewerTools.TiogaContents; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]]; sendOk_ FALSE; senderInfo.aborted_ FALSE; IF senderInfo.successfullySent AND ~senderV.newVersion THEN { SenderReport["\nDo you really want to send this message again?"]; IF ~Confirmation[senderInfo] THEN { SenderReport[" .. Not sent\n"]; senderInfo.successfullySent_ FALSE; RETURN} }; senderInfo.successfullySent_ FALSE; SenderReport["... Parsing..."]; smr_ NEW[SendMsgRecObject]; TRUSTED {smr.fullText_ WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[senderV]]]}; IF smr.fullText.Length[] > LAST[CARDINAL] THEN { SenderReport["\nMessage is too long to send; NOT sent\n"]; RETURN}; [status, sPos, mPos]_ ParseTextToBeSent[smr]; IF (status # ok) AND (status # includesPublicDL) THEN BEGIN SELECT status FROM fieldNotAllowed => IF sPos # mPos THEN { ShowErrorFeedback[senderV, sPos, mPos]; SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]]; SenderReport[" field is not allowed\n"]} ELSE SenderReport[IO.PutFR[" field at pos %g is not allowed\n", IO.int[sPos]]]; syntaxError => IF sPos # mPos THEN { ShowErrorFeedback[senderV, sPos, mPos]; SenderReport["\nSyntax error on line beginning with "]; SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]]} ELSE SenderReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]]; includesPrivateDL => SenderReport[" Private dl's are not yet implemented\n"]; ENDCASE => ERROR; ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1]; RETURN; END; IF CheckForAbortSend[senderInfo] THEN RETURN; IF (status = includesPublicDL OR smr.numRecipients > maxWithNoReplyTo) AND ~smr.replyTo THEN { howToReply: HowToReplyTo_ self; IF ~replyToSelf THEN { oldM: Menus.Menu_ senderV.menu; IF CheckForAbortSend[senderInfo] THEN RETURN; ViewerOps.SetMenu[senderV, replyToMenu]; SenderReport[ IO.PutFR["... %g public DLs and %g other recipients; please choose Reply-To option", IO.int[smr.numDLs], IO.int[smr.numRecipients]]]; SenderReport[ "\nClick Self to reply-to self, All to reply-to all, Cancel to cancel Send\n"]; howToReply_ ReplyToResponse[senderInfo]; ViewerOps.SetMenu[senderV, oldM]; }; IF howToReply # all THEN InsertIntoViewer[senderV, Rope.Concat["Reply-to: ", WalnutSendOps.userRName], smr.endHeadersPos, WalnutSendOps.labelFont]; IF howToReply = cancel THEN {SenderReport["\nDelivery cancelled. Reply-to: has been added\n"]; RETURN }; IF CheckForAbortSend[senderInfo] THEN RETURN; }; IF doClose AND ~senderV.iconic THEN ViewerOps.CloseViewer[senderV]; currentText_ ViewerTools.GetTiogaContents[senderV]; newTxt _ Rope.Cat[IF smr.from = NIL THEN "From: " ELSE "Sender: ", WalnutSendOps.userRName]; addedTxtLength _ newTxt.Length[]; InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont]; newTxt _ Rope.Concat["Date: ", RFC822Date[]]; addedTxtLength _ newTxt.Length[] + addedTxtLength + 1; InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont]; contents_ ViewerTools.GetTiogaContents[senderV]; { last: INT_ contents.contents.Length[] - 1; IF contents.contents.Fetch[last] = '\000 THEN -- NULL for padding { smr.fullText_ Rope.Substr[contents.contents, 1, last-1]; formatting_ Rope.Concat["\000", contents.formatting] } ELSE { smr.fullText_ Rope.Substr[contents.contents, 1]; formatting _ contents.formatting } }; IF smr.subject.Length[] > 40 THEN smr.subject_ Rope.Concat[Rope.Substr[smr.subject, 0, 35], " ..."]; smr.subject_ Rope.Cat[" \"", smr.subject, "\" "]; SenderReport["... Sending "]; SenderReport[smr.subject]; IF senderInfo.successfullySent_ sendOk_ SendIt[smr, senderInfo, formatting] THEN { SenderReport[smr.subject]; SenderReport[" has been delivered\n"]; senderInfo.prevMsg_ currentText; } ELSE { DeleteChars[senderV, addedTxtLength]; SenderReport[smr.subject]; SenderReport[" NOT sent\n"]; }; END; RFC822Date: PUBLIC PROC[gmt: BasicTime.GMT_ BasicTime.nullGMT] RETURNS[date: ROPE] = BEGIN OPEN IO; upt: BasicTime.Unpacked_ BasicTime.Unpack[IF gmt = BasicTime.nullGMT THEN BasicTime.Now[] ELSE gmt]; zone: ROPE; month, tyme, year: ROPE; timeFormat: ROPE = "%02g:%02g:%02g %g"; -- "hh:mm:ss zzz" dateFormat: ROPE = "%2g %g %g %g"; -- "dd mmm yy timeFormat" arpaNeg: BOOL_ upt.zone > 0; aZone: INT_ ABS[upt.zone]; zDif: INT_ aZone / 60; zMul: INT_ zDif * 60; IF (zMul = aZone) AND arpaNeg THEN { IF upt.dst = yes THEN SELECT zDif FROM 0 => zone_ "UT"; 4 => zone_ "EDT"; 5 => zone_ "CDT"; 6 => zone_ "MDT"; 8 => zone_ "PDT"; ENDCASE ELSE SELECT zDif FROM 0 => zone_ "UT"; 5 => zone_ "EST"; 6 => zone_ "CST"; 7 => zone_ "MST"; 8 => zone_ "PST"; ENDCASE; }; IF zone = NIL THEN { mm: INT_ aZone - zMul; zone_ PutFR[IF arpaNeg THEN "-%02g%02g" ELSE "+%02g%02g", int[zDif], int[mm]]; }; SELECT upt.month FROM January => month_ "Jan"; February => month_ "Feb"; March => month_ "Mar"; April => month_ "Apr"; May => month_ "May"; June => month_ "Jun"; July => month_ "Jul"; August => month_ "Aug"; September => month_ "Sep"; October => month_ "Oct"; November => month_ "Nov"; December => month_ "Dec"; unspecified => ERROR; ENDCASE => ERROR; year_ Rope.Substr[PutFR[NIL, int[upt.year]], 2]; tyme_ PutFR[timeFormat, int[upt.hour], int[upt.minute], int[upt.second], rope[zone]]; date_ PutFR[dateFormat, int[upt.day], rope[month], rope[year], rope[tyme]]; END; ShowErrorFeedback: PUBLIC PROC[v: Viewer, start, end: INT] = BEGIN OPEN TiogaOps; ENABLE UNWIND => GOTO exit; startLoc, endLoc: Location; thisV: Ref = ViewerDoc[v]; beginning: Location_ [FirstChild[thisV], 0]; startLoc_ LocRelative[beginning, start]; endLoc_ LocRelative[beginning, end]; SetSelection[viewer: v, start: startLoc, end: endLoc, which: feedback]; EXITS exit => NULL; END; InsertIntoViewer: PUBLIC PROC [v: Viewer, what: ROPE, where: INT, labelFont: ROPE _ NIL, changeSelection: BOOL _ FALSE] = BEGIN OPEN TiogaOps; thisV: Ref = ViewerDoc[v]; prefix: ROPE; InsertChars: PROC[root: Ref] = BEGIN insertLoc: Location; prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; IF where < 0 THEN insertLoc_ LastLocWithin[LastChild[thisV]] ELSE insertLoc_ LocRelative[[FirstChild[thisV], 0], where]; [prevV, prevStart, prevEnd, prevLevel, cb, pd]_ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SelectPoint[v, insertLoc, primary]; IF labelFont # NIL THEN TiogaOps.SetLooks[labelFont, caret]; TiogaOps.InsertRope[prefix]; IF labelFont # NIL THEN TiogaOps.ClearLooks[caret]; TiogaOps.InsertRope[what]; TiogaOps.Break[]; -- make a new node ViewerTools.InhibitUserEdits[v]; IF ~changeSelection AND (prevV # v) AND (prevV#NIL) AND ~prevV.destroyed THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd ! TiogaOps.SelectionError => CONTINUE]; END; LockViewerOpen[v]; prefix _ Rope.Substr[ what, 0, Rope.Find[s1: what, s2: ":"] ]; what _ Rope.Replace[base: what, start: 0, len: prefix.Length, with: NIL]; IF thisV = NIL THEN InsertChars[thisV] ELSE CallWithLocks[InsertChars, thisV]; ReleaseOpenViewer[v]; END; DeleteChars: PUBLIC ENTRY PROC[v: Viewer, num: INT] = BEGIN ENABLE UNWIND => NULL; IF num # 0 THEN DeleteLeadingChars[v, num] END; DeleteLeadingChars: INTERNAL PROC[v: Viewer, num: INT] = BEGIN OPEN TiogaOps; thisV: Ref_ ViewerDoc[v]; DelChars: PROC[root: Ref] = BEGIN prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; startLoc: Location_ [FirstChild[thisV], 0]; endLoc: Location_ LocRelative[startLoc, num]; [prevV, prevStart, prevEnd, prevLevel, cb, pd]_ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SetSelection[viewer: v, start: startLoc, end: endLoc]; Delete[]; GoToNextNode[]; Join[]; IF (prevV # v) AND (prevV#NIL) THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd]; END; IF thisV = NIL THEN DelChars[thisV] ELSE CallWithLocks[DelChars, thisV]; END; SendIt: PROC[smr: SendingRec, senderInfo: SenderInfo, formatting: ROPE] RETURNS[ sent: BOOLEAN] = { InitSending: ENTRY PROC = { ENABLE UNWIND => NULL; senderInfo.sendHandle_ GVSend.Create[]; senderInfo.validateFlag_ TRUE; senderInfo.aborted_ FALSE; }; FinishedSending: ENTRY PROC = { ENABLE UNWIND => NULL; senderInfo.sendHandle_ NIL }; InitSending[]; sent _ SendMessage[smr, senderInfo, formatting ! GVSend.SendFailed => { IF notDelivered THEN { SenderReport["\nCommunication failure during send. Retry?\n"]; IF Confirmation[senderInfo] THEN RETRY ELSE { sent _ FALSE; CONTINUE } } ELSE { SenderReport["\nCommunication failure, but message delivered\n"]; sent _ TRUE; CONTINUE; }; }]; FinishedSending[]; } ; SendMessage: PROC[smr: SendingRec, senderInfo: SenderInfo, formatting: ROPE] RETURNS[ sent: BOOLEAN ] = { DO ENABLE GVSend.SendFailed => CONTINUE; -- try again if SendFailed h: GVSend.Handle _ senderInfo.sendHandle; startInfo: GVSend.StartSendInfo; stepper: LIST OF RName; tempInteger: INT; numRecips: INT _ 0; numValidRecips: INT; firstInvalidUser: BOOL_ TRUE; numInvalidUsers: INTEGER_ 0; InvalidUserProc: PROC [ userNum: INT, userName: RName ] = { IF firstInvalidUser THEN {SenderReport["\nInvalid user(s): "]; firstInvalidUser_ FALSE}; SELECT numInvalidUsers _ numInvalidUsers + 1 FROM 1 => SenderReport[userName]; IN [2..5] => {SenderReport[", "]; SenderReport[userName]}; 6 => SenderReport[", ...\n"]; ENDCASE; } ; sent _ FALSE ; startInfo _ GVSend.StartSend[ handle: senderInfo.sendHandle, senderPwd: UserCredentials.Get[].password, sender: WalnutSendOps.userRName, returnTo: WalnutSendOps.userRName, validate: senderInfo.validateFlag ] ; SELECT startInfo FROM badPwd => {SenderReport["\nInvalid password\n"]; RETURN}; badSender => {SenderReport["\nInvalid sender name\n"]; RETURN}; badReturnTo => {SenderReport["\nBad return-to field\n"]; RETURN}; allDown => {SenderReport["\nAll servers are down\n"]; RETURN}; ok => { stepper _ smr.to; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ]; numRecips _ numRecips + 1; stepper _ stepper.rest; ENDLOOP; IF CheckForAbortSend[senderInfo] THEN RETURN; stepper _ smr.cc; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ] ; numRecips _ numRecips + 1 ; stepper _ stepper.rest ; ENDLOOP ; IF CheckForAbortSend[senderInfo] THEN RETURN; IF senderInfo.validateFlag THEN { IF (numValidRecips_ GVSend.CheckValidity[ h, InvalidUserProc]) = 0 THEN { SenderReport["\nThere were NO valid recipients; do you wish to send anyway?\n"]; IF CheckForAbortSend[senderInfo] OR ~Confirmation[senderInfo] THEN {GVSend.Abort[h]; RETURN}; }; IF numValidRecips # numRecips THEN { tempInteger _ numRecips-numValidRecips; SenderReport[IO.PutFR["\nThere were %g invalid recipients,", IO.int[tempInteger] ]]; SenderReport[" do you wish to send anyway?\n"]; IF CheckForAbortSend[senderInfo] OR ~Confirmation[senderInfo] THEN {GVSend.Abort[h]; RETURN}; }; }; IF CheckForAbortSend[senderInfo] THEN RETURN; SenderReport[IO.PutFR["..sending to %g recipients\n", IO.int[numValidRecips]]]; senderInfo.validateFlag_ FALSE; -- if sending fails, don't need to re-validate GVSend.StartItem[h, Text]; GVSend.AddToItem[h, smr.fullText]; IF formatting#NIL THEN { -- send the formatting info as a second item GVSend.StartItem[h, WalnutSendOps.TiogaCTRL]; GVSend.AddToItem[h, formatting] }; IF smr.voiceID.Length[] # 0 THEN { GVSend.StartItem[h, Audio]; GVSend.AddToItem[h, smr.voiceID] }; ViewerOps.SetMenu[senderInfo.senderV, blankMenu]; IF CheckForAbortSend[senderInfo] THEN RETURN; GVSend.Send[ h ] ; sent_ TRUE; RETURN; } ; ENDCASE ; ENDLOOP; } ; CanonicalName: PUBLIC PROC [simpleName, registry: ROPE] RETURNS[name: ROPE] = BEGIN name_ simpleName; IF registry.Length[] = 0 THEN name_ name.Cat[".", defaultRegistry] ELSE name_ name.Cat[".", registry]; END; ParseTextToBeSent: PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] = BEGIN OPEN GVMailParse; mLF: WalnutParse.MessageInfo; tHeaders: LIST OF ROPE_ NIL; msgText: ROPE_ msg.fullText; lastCharPos: INT_ msgText.Length[] - 1; lastCharIsCR: BOOL_ (msgText.Fetch[lastCharPos] = '\n); countOfRecipients, dlCount: INT_ 0; GetNextMsgChar: PROC[] RETURNS [ch: CHAR] = { IF mPos <= lastCharPos THEN ch_ Rope.Fetch[msgText, mPos] ELSE IF (mPos=lastCharPos+1) AND ~lastCharIsCR THEN ch_ '\n ELSE ch_ endOfInput; mPos_ mPos + 1; }; RNameListField: PROC[index: WalnutParse.MessageFieldIndex] = BEGIN fieldBody, fbEnd: LIST OF RName_ NIL; AnotherRName: PROC[r1, r2: ROPE, isFile, isNested: BOOL] RETURNS [ROPE, BOOLEAN] = BEGIN name: ROPE; name_ CanonicalName[r1, r2]; IF fbEnd=NIL THEN fbEnd_ fieldBody_ CONS[name, NIL] ELSE fbEnd_ fbEnd.rest_ CONS[name, NIL]; IF isFile THEN status_ includesPrivateDL ELSE IF name.Find["^"] < 0 THEN countOfRecipients_ countOfRecipients + 1 ELSE { IF status # includesPrivateDL THEN status_ includesPublicDL; dlCount_ dlCount + 1 }; RETURN[NIL, FALSE]; END; ParseNameList[pH, GetNextMsgChar, AnotherRName, NIL]; SELECT index FROM toF => IF msg.to = NIL THEN msg.to_ fieldBody ELSE IF fieldBody#NIL THEN TRUSTED {msg.to_ LOOPHOLE[List.Append[LOOPHOLE[msg.to], LOOPHOLE[fieldBody]]]}; ccF, cF, bccF => IF msg.cc = NIL THEN msg.cc_ fieldBody ELSE IF fieldBody#NIL THEN TRUSTED {msg.cc_ LOOPHOLE[List.Append[LOOPHOLE[msg.cc], LOOPHOLE[fieldBody]]]}; fromF => msg.from_ fieldBody.first; -- needs to be non-NIL ENDCASE => ERROR; END; pH: ParseHandle; field: ROPE_ NIL; fieldNotRecognized: BOOL; mPos_ 0; -- where we are in the fulltext status_ ok; -- start with good status pH_ InitializeParse[]; DO sPos_ mPos; [field, fieldNotRecognized]_ GetFieldName[pH, GetNextMsgChar ! ParseError => { FinalizeParse[pH]; GOTO errorExit}]; IF ~fieldNotRecognized THEN EXIT; IF Rope.Equal[field, "Sender", FALSE] OR Rope.Equal[field, "Date", FALSE] THEN RETURN[fieldNotAllowed, sPos, mPos]; FOR i: WalnutParse.MessageFieldIndex IN WalnutParse.MessageFieldIndex DO { mLF_ messageParseArray[i]; IF Rope.Equal[messageParseArray[i].name, field, FALSE] THEN -- ignore case { fieldNotRecognized_ FALSE; SELECT mLF.fType FROM simpleRope => SELECT i FROM fromF => RNameListField[i ! ParseError => GOTO errorExit]; replyToF => {msg.replyTo_ TRUE; []_ GetFieldBody[pH, GetNextMsgChar, TRUE]}; subjectF => msg.subject_ GetFieldBody[pH, GetNextMsgChar]; voiceF => msg.voiceID_ GetFieldBody[pH, GetNextMsgChar]; ENDCASE => []_ GetFieldBody[pH, GetNextMsgChar, TRUE]; rCatList => []_ GetFieldBody[pH, GetNextMsgChar, TRUE]; rNameList => RNameListField[i ! ParseError => GOTO errorExit]; ENDCASE => ERROR; EXIT }; }; ENDLOOP; IF fieldNotRecognized THEN []_ GetFieldBody[pH, GetNextMsgChar]; -- skip anything not recognized ENDLOOP; FinalizeParse[pH]; msg.endHeadersPos_ mPos - 1; msg.numRecipients_ countOfRecipients; msg.numDLs_ dlCount; EXITS errorExit => RETURN[syntaxError, sPos, mPos]; END; END. 4File: WalnutSendMailImpl.mesa Contents: Implementation of the WalnutMsg Send operation Last Edited by: Willie-Sue, September 19, 1984 1:00:20 pm PDT Last Edited by: Donahue, October 6, 1983 1:05 pm Global types & variables ************************************************************************ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * first add the From:/Sender: line now put the date at the very front note that there is guaranteed to be formatting since we added some to make things look nice generates arpa standard time, dd mmm yy hh:mm:ss zzz * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * now we are positioned at the beginning of the body of the message if any recipient is at another arpa site, all recipients should be arpa qualified however, like Laurel, we'll only do the From/Sender field in full arpa regalia Êà˜J˜Jšœ™Jšœ8™8J˜Jšœ=™=Jšœ0™0J˜šÏk ˜ Jšœ œœ"˜6Jšœœ˜&Jšœ œ˜!J˜ J˜šœœ@˜LJ˜7—J˜Jšœœ ˜J˜Jšœ˜J˜Jšœ œ˜!Jšœœ ˜#J˜ J˜ J˜ Jšœœ˜J˜J˜Jšœ œ"˜3J˜—šœœ˜!š˜J˜Jšœ$œ˜;J˜J˜J˜J˜J˜ —š˜J˜0J˜——Jšœœ˜J˜Jšœ™J˜Jšœœœ"˜>J˜šœœœœ˜ZšœÏc/˜LJ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜——JšœH™HšÏnœœœœœœœ˜Hšœ˜Jšœœ˜Jšœœœœ˜ ˜"šœ˜˜*šœ˜šœœ œœ˜3J˜Jšœœ˜Jšœœ˜ J˜——J˜J˜—J˜"——š˜Jšœœ˜ —Jšœ˜J˜Jšœœ˜J˜J˜——JšœU™Uš Ÿœœœœœœ˜Ušœœœ˜3Jšœœ˜ Jšœœ˜ Jšœœ˜ J˜Jšœ"œ˜Lšœž/˜3Jšœœ œ˜5Jšœ"œœ˜.—šœ˜J˜GJ˜—š˜Jšœœ3œ˜Z——Jšœ˜J˜—JšœU™UšŸœœœœ˜'šœœ˜J˜Jšœœ"œœ˜OJ˜š˜˜Jšœ@˜DJ˜4Jšœ#œœœ˜8J˜TJ˜5J˜H——Jšœ˜J˜Jšœœ˜—Jšœ˜J˜—š Ÿœœœœ œ˜MJš˜J˜Jšœ œ˜Jšœ œ˜J˜Jšœœ˜Jšœœ˜ J˜1Jšœœ,˜JJ˜Jšœœ˜Jšœœ˜—˜šœœ˜;˜CJšœ˜!˜!Jšœœ˜#Jšœ˜—J˜J˜——Jšœœ˜#J˜J˜J˜Jšœœ˜š˜˜Jšœœ!˜C—šœœœ˜.Jšœ=œ˜E—J˜-—˜šœœ˜5š˜šœ˜˜šœ ˜˜)Jšœ'œ˜CJ˜(—Jšœœ-œ ˜P——˜šœ ˜˜)J˜7Jšœ'œ˜C—Jšœœ.œ ˜Q——J˜MJšœœ˜Jšœœœœ˜>—Jšœ˜—Jšœ˜J˜Jšœœœ˜-J˜—šœœ'˜JJšœ ˜˜!šœ˜˜!Jšœœœ˜-˜(˜ šœR˜TJšœœ˜0——˜ J˜O—J˜(J˜!—J˜——šœ˜˜MJ˜,—Jšœ˜˜CJš˜J˜J˜——Jšœœœ˜3—J˜J˜Jšœ œœ ˜CJ˜——Jšœ!™!J˜3˜Jš œ œ œœ œ&˜S˜!J˜>Jšœ"™"J˜-J˜6J˜>J˜J˜1Jšœ[™[šœœ!˜,šœ'œž˜B˜:J˜6——šœ˜˜2J˜%J˜———šœ˜!J˜B—J˜1J˜——J˜8šœI˜Kš˜˜CJ˜ —J˜—š˜˜'J˜7J˜———Jšœ˜J˜—Jš Ÿ œœœœœœ˜Tšœ4™4šœœœ˜˜Jšœœœœ˜K—Jšœœ˜ Jšœœ˜Jšœ œž˜:Jšœ œž˜>J˜Jšœ œ˜Jšœœœ ˜Jšœœ ˜Jšœœ ˜šœœ ˜"šœœ˜šœ˜J˜J˜J˜J˜J˜———š˜š˜šœ˜J˜J˜J˜J˜J˜Jšœ˜—J˜J˜Jšœœ˜šœœ˜Jšœ œ œ œ"˜NJ˜J˜—šœ ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜Jšœœ˜Jšœœ˜J˜—Jšœœ˜0J˜UJ˜KJ˜———Jšœ˜J˜—šŸœœœœ˜šœDœ˜IJšœ œœœ#˜NJ˜——Jšœ˜J˜—š Ÿ œœœœœ˜6šœœœœ˜Jšœ œ˜*—Jšœ˜J˜—šŸœœœœ˜8šœœ ˜J˜šŸœœ ˜š˜J˜J˜J˜Jšœœ˜ J˜+J˜-J˜FJ˜J˜6J˜ J˜J˜šœ œœ˜#J˜;——Jšœ˜Jšœ œœœ ˜H——Jšœ˜J˜—JšœO™Oš Ÿœœ6œœœ˜ašœŸ œœœ˜šœœœœ˜J˜'Jšœœ˜Jšœœ˜J˜—šŸœœœ˜šœœœœ˜Jšœ˜J˜——J˜˜Ešœœ˜˜AJšœœœ˜'Jšœ œœ˜—J˜šœ˜J˜AJšœœ˜ Jšœ˜ J˜——J˜J˜—J˜J˜——šŸ œœ6œ˜LJšœœ˜Jšœœœž˜DJ˜)J˜ Jšœ œœ˜Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœœœ˜Jšœœ˜J˜šŸœœ œ˜;Jšœœ9œ˜Xšœ'˜1J˜Jšœ8˜:J˜—Jšœ˜J˜J˜—Jšœœ˜˜<˜*J˜#J˜#J˜"J˜——šœ ˜Jšœ1œ˜9Jšœ7œ˜NJšœ9œ˜AJšœ6œ˜>˜J˜šœ œœ˜J˜(J˜J˜—Jšœ˜Jšœœœ˜-J˜šœ œœ˜J˜)J˜J˜—Jšœ˜ šœœœ˜-Jšœ˜šœœA˜I˜Ršœœ˜BJšœœ˜——J˜J˜——šœ˜"˜)Jšœ œ.œ˜TJ˜/šœœ˜BJšœœ˜—J˜J˜˜Jšœœœ˜-Jšœ œ'œ˜OJšœœž.˜OJ˜J˜"Jšœ œœž,˜EJ˜P—šœ˜ J˜AJ˜—J˜1———Jšœœœ˜-J˜Jšœœ˜ Jšœ˜J˜—Jšœ˜ —Jšœ˜J˜J˜JšœO™Oš Ÿ œœœœœœ˜Mš˜J˜šœœ%˜BJšœ˜#——Jšœ˜J˜—˜šœœ&œ˜Išœœ ˜J˜Jš œ œœœœ˜Jšœ œ˜Jšœ œ˜'Jšœœ%˜7Jšœœ˜#J˜JšŸœœœœ˜+šœœœ˜;šœœœœ˜;Jšœ˜—J˜—J˜J˜šŸœœ(˜J˜—J˜———Jšœœœ˜Jšœ˜J˜——šœ0œ˜5šœ˜šœœ œœ˜-šœœ œ˜š˜Jšœ œ œ œ˜G———šœœ œœ˜7šœœ œ˜š˜Jšœ œ œ œ˜G———Jšœ%ž˜;—Jšœœ˜—Jšœ˜J˜——J˜Jšœœœ˜Jšœœ˜Jšœ ž˜)Jšœ ž˜&J˜J˜J˜š˜J˜ ˜LJšœœ ˜&—šœœœ˜!š œœœœ˜NJšœ˜$J˜—Jšœ"œ˜H˜šœ.œœž˜Jšœœ˜šœ ˜˜ šœ˜ Jšœ*œ ˜:Jšœœ'œ˜LJ˜:J˜8—Jšœ)œ˜6J˜—Jšœ1œ˜7Jšœ.œ ˜>Jšœœ˜Jš˜J˜———J˜—Jšœ˜šœ˜Jšœ&ž˜E———Jšœ˜J˜——šœA™AJ˜J˜J˜—JšœQ™QJšœN™N˜J˜&J˜š˜Jšœ œ˜-—Jšœ˜J˜—Jšœ˜J˜—…—F„^˜