DIRECTORY BasicTime USING [GMT, nullGMT, Unpacked, Now, Unpack], 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, ViewerClasses USING [Viewer], ViewerOps, ViewerTools, UserCredentials USING [Get], WalnutSendOps, WalnutSendInternal, WalnutParseMsg USING [MessageFieldIndex, MessageInfo, messageParseArray, CanonicalName]; WalnutSendMailImpl: CEDAR MONITOR IMPORTS BasicTime, GVMailParse, GVNames, GVSend, List, IO, Rope, RuntimeError, WalnutDocumentRope, TiogaOps, ViewerOps, ViewerTools, UserCredentials, WalnutParseMsg, WalnutSendInternal, WalnutSendOps EXPORTS WalnutSendInternal, WalnutSendOps = BEGIN OPEN WalnutSendInternal; ROPE: TYPE = Rope.ROPE; RName: TYPE = GVBasics.RName; TiogaContents: TYPE = ViewerTools.TiogaContents; Viewer: TYPE = ViewerClasses.Viewer; 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; } ; ParseTextToBeSent: PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] = BEGIN OPEN GVMailParse; mLF: WalnutParseMsg.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: WalnutParseMsg.MessageFieldIndex] = BEGIN fieldBody, fbEnd: LIST OF RName_ NIL; AnotherRName: PROC[r1, r2: ROPE, isFile, isNested: BOOL] RETURNS [ROPE, BOOLEAN] = BEGIN name: ROPE; name_ WalnutParseMsg.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: WalnutParseMsg.MessageFieldIndex IN WalnutParseMsg.MessageFieldIndex DO { mLF_ WalnutParseMsg.messageParseArray[i]; IF Rope.Equal[WalnutParseMsg.messageParseArray[i].name, field, FALSE] THEN { 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. `WalnutSendMailImpl.mesa - Implementation of the WalnutMsg Send operation Copyright c 1984, 1985 by Xerox Corporation. All rights reserved. Willie-Sue, December 6, 1984 2:08:01 pm PST Donahue, October 6, 1983 1:05 pm Russ Atkinson (RRA) March 21, 1985 0:40:15 am PST ************************************************************************ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 ΚZ˜codešœH™HKšœ Οmœ7™BKšœ+™+Kšœ ™ K™1—K˜šΟk ˜ Kšœ žœžœ"˜6Kšœ žœ˜!K˜ K˜Kšœžœx˜„K˜Kšœžœ ˜K˜Kšžœ˜K˜Kšœ žœ˜!Kšœžœ ˜#K˜ Kšœžœ ˜K˜ K˜ Kšœžœ˜K˜K˜KšœžœD˜XK˜—šœžœž˜!Kšžœ0žœŽ˜ΗKšžœ$˜+Kšžœžœ˜K˜Kšžœžœžœ˜Kšœžœ˜Kšœžœ˜0Kšœžœ˜$—K˜K˜KšœH™HšΟnœžœžœžœžœžœžœ˜JKšœ˜Kšœžœ˜šžœžœžœžœ˜ K˜"šžœžœ˜Kšœ(˜(šžœžœ˜Kšœžœ žœžœ˜2K˜Kšœžœ˜Kšžœžœ˜ K˜—K˜K˜—K˜"šž˜Kšœžœ˜ —Kšžœ˜—K˜Kšœžœ˜K˜K˜—KšœU™Uš Ÿœžœžœžœžœžœ˜Ušžœžœžœ˜3Kšœžœ˜ Kšœžœ˜ Kšœžœ˜ K˜Kšžœ"žœ˜LšžœΟc/˜3Kšžœžœ žœ˜5Kšžœ"žœžœ˜.—šžœ˜K˜GK˜—šž˜Kšœžœ3žœ˜Z——Kšžœ˜K˜—KšœU™UšŸœžœžœžœ˜'šžœžœ˜K˜Kšžœžœ"žœžœ˜OK˜šž˜˜Kšœ@ž˜DK˜4Kšœ#žœžœžœ˜8K˜TK˜5K˜H——Kšžœ˜K˜Kšžœžœ˜—Kšžœ˜K˜—š Ÿœžœžœžœ žœ˜MKšž˜K˜Kšœ žœ˜Kšœ žœ˜K˜Kšœžœ˜Kšœžœ˜ K˜1Kšœžœ,˜JK˜Kšœžœ˜Kšœžœ˜—˜šžœžœž˜;˜CKšžœž˜!˜!Kšœžœ˜#Kšžœ˜—K˜K˜——Kšœžœ˜#K˜K˜K˜Kšœžœ˜šž˜˜Kšœžœ!˜C—šžœžœžœž˜.Kšœ=žœ˜E—K˜-—˜šžœžœž˜5šž˜šžœž˜˜šžœ ž˜˜)Kšœ'žœ˜CK˜(—Kšžœžœ-žœ ˜P——˜šžœ ž˜˜)K˜7Kšœ'žœ˜C—Kšžœžœ.žœ ˜Q——K˜MKšžœžœ˜Kšœžœžœžœ˜>—Kšžœ˜—Kšžœ˜K˜Kšžœžœžœ˜-K˜—šžœžœ'ž˜JKšœ ž˜˜!šžœž˜˜!Kšžœžœžœ˜-˜(˜ šžœR˜TKšžœžœ˜0——˜ K˜O—K˜(K˜!—K˜——šžœž˜˜MK˜,—Kšžœž˜˜CKšž˜K˜K˜——Kšžœžœžœ˜3—K˜K˜Kšžœ žœžœ ˜CK˜——Kšœ!™!K˜3˜Kš œ žœ žœžœ žœ&˜S˜!K˜>Kšœ"™"K˜-K˜6K˜>K˜K˜1Kšœ[™[šœžœ!˜,šžœ'žœ ˜B˜:K˜6——šžœ˜˜2K˜%K˜———šžœž˜!K˜B—K˜1K˜——K˜8šžœI˜Kšž˜˜CK˜ —K˜—šž˜˜'K˜7K˜———Kšžœ˜K˜—Kš Ÿ œžœžœžœžœžœ˜Tšœ4™4šžœžœžœ˜˜Kšœžœžœžœ˜K—Kšœžœ˜ Kšœžœ˜Kšœ žœ ˜:Kšœ žœ ˜>K˜Kšœ žœ˜Kšœžœžœ ˜Kšœžœ ˜Kšœžœ ˜šžœžœ ž˜"šœžœž˜šžœž˜K˜K˜K˜K˜K˜———šž˜šž˜šžœž˜K˜K˜K˜K˜K˜Kšžœ˜—K˜K˜Kšžœžœž˜šœžœ˜Kšœ žœ žœ žœ"˜NK˜K˜—šžœ ž˜K˜K˜K˜K˜K˜K˜K˜K˜K˜K˜K˜K˜Kšœžœ˜Kšžœžœ˜K˜—Kšœžœ˜0K˜UK˜KK˜———Kšžœ˜K˜—šŸœžœžœžœ˜šœDžœ˜IKšžœ žœžœžœ#˜NK˜——Kšžœ˜K˜—š Ÿ œžœžœžœžœ˜6šžœžœžœžœ˜Kšžœ žœ˜*—Kšžœ˜K˜—šŸœžœžœžœ˜8šžœžœ ˜K˜šŸœžœ ˜šž˜K˜K˜K˜Kšœžœ˜ K˜+K˜-K˜FK˜K˜6K˜ K˜K˜šžœ žœžœž˜#K˜;——Kšžœ˜Kšžœ žœžœžœ ˜H——Kšžœ˜K˜—KšœO™Oš Ÿœžœ6žœžœžœ˜ašœŸ œžœžœ˜šœžœžœžœ˜K˜'Kšœžœ˜Kšœžœ˜K˜—šŸœžœžœ˜šœžœžœžœ˜Kšœž˜K˜——K˜˜Ešœžœž˜˜AKšžœžœžœ˜'Kšžœ žœžœ˜—K˜šžœ˜K˜AKšœžœ˜ Kšžœ˜ K˜——K˜K˜—K˜K˜——šŸ œžœ6žœ˜LKšžœžœ˜Kšžœžœžœ ˜DK˜)K˜ Kšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœžœžœ˜Kšœžœ˜K˜šŸœžœ žœ˜;Kšžœžœ9žœ˜Xšžœ'ž˜1K˜Kšžœ8˜:K˜—Kšžœ˜K˜K˜—Kšœžœ˜˜<˜*K˜#K˜#K˜"K˜——šžœ ž˜Kšœ1žœ˜9Kšœ7žœ˜NKšœ9žœ˜AKšœ6žœ˜>˜K˜šžœ žœžœ˜K˜(K˜K˜—Kšžœ˜Kšžœžœžœ˜-K˜šžœ žœžœ˜K˜)K˜K˜—Kšžœ˜ šžœžœžœ˜-Kšžœž˜šœžœAž˜I˜Ršžœžœž˜BKšœžœ˜——K˜K˜——šžœž˜"˜)Kšœ žœ.žœ˜TK˜/šžœžœž˜BKšœžœ˜—K˜K˜˜Kšžœžœžœ˜-Kšœ žœ'žœ˜OKšœžœ .˜OK˜K˜"Kšžœ žœžœ ,˜EK˜P—šžœž˜ K˜AK˜—K˜1———Kšžœžœžœ˜-K˜Kšœžœ˜ Kšžœ˜K˜—Kšžœ˜ —Kšžœ˜K˜K˜KšœO™OšŸœžœžœ&žœ˜\šžœžœ ˜K˜ Kš œ žœžœžœžœ˜Kšœ žœ˜Kšœ žœ˜'Kšœžœ%˜7Kšœžœ˜#K˜KšŸœžœžœžœ˜+šœžœžœ˜;šžœžœžœžœ˜;Kšžœ˜—K˜—K˜K˜šŸœžœ+˜?Kšž˜Kšœžœžœžœ˜%šŸ œžœ žœžœžœžœžœ˜Ršž˜Kšœžœ˜ K˜+š žœžœžœžœžœ˜3Kšžœžœžœ˜(—šžœžœ˜(šžœžœžœ)˜Hšž˜šœžœžœ˜>K˜—K˜———Kšžœžœžœ˜Kšžœ˜K˜——šœ0žœ˜5šžœž˜šœžœ žœžœ˜-šžœžœ žœž˜šž˜Kšœ žœ žœ žœ˜G———šœžœ žœžœ˜7šžœžœ žœž˜šž˜Kšœ žœ žœ žœ˜G———Kšœ% ˜;—Kšžœžœ˜—Kšžœ˜K˜—K˜Kšœžœžœ˜Kšœžœ˜Kšœ  ˜)Kšœ  ˜&K˜K˜K˜šž˜K˜ ˜LKšœžœ ˜&—šžœžœžœ˜!š žœžœžœžœž˜NKšžœ˜$K˜—Kšžœ%žœ"ž˜Nšœ+˜+šžœ=žœž˜Jšœžœ˜šžœ ž˜˜ šžœž˜ Kšœ*žœ ˜:Kšœžœ'žœ˜LK˜:K˜8—Kšžœ)žœ˜6K˜—Kšœ1žœ˜7Kšœ.žœ ˜>Kšžœžœ˜Kšž˜K˜———K˜—Kšžœ˜šžœž˜Kšœ& ˜E———Kšžœ˜K˜——šœA™AK˜K˜K˜—KšœQ™QKšœN™N˜K˜&K˜šž˜Kšœ žœ˜-—Kšžœ˜K˜—Kšžœ˜K˜—…—DŒ\F