DIRECTORY GVAnswer USING [MakeHeader], GVBasics USING [ItemType, RName], GVMailParse, GVNames, GVSend USING [AddRecipient, AddToItem, CheckValidity, Close, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo], List USING [Append, Reverse], InputFocus USING [GetInputFocus], Menus USING [MouseButton], IO, Rope, TextNode, TiogaOps, ViewerOps, ViewerTools, ViewerClasses, UserExec USING [GetNameAndPassword], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc], PeanutSendMail, PeanutWindow, PeanutParse; PeanutSendMailImpl: CEDAR MONITOR IMPORTS GVAnswer, GVMailParse, GVNames, GVSend, List, IO, Rope, TiogaOps, ViewerTools, TextNode, UserExec, UserProfile, PeanutWindow, ViewerOps EXPORTS PeanutSendMail, PeanutParse SHARES Rope = BEGIN OPEN PeanutSendMail, PeanutParse; TiogaCTRL: GVBasics.ItemType; Viewer: TYPE = ViewerClasses.Viewer; ROPE: TYPE = Rope.ROPE; RName: TYPE = GVBasics.RName; userRName: PUBLIC ROPE_ NIL; -- user name with registry simpleUserName: PUBLIC ROPE_ NIL; -- user name without registry defaultRegistry: PUBLIC ROPE_ "pa"; arpaGatewayHosts: PUBLIC LIST OF ROPE_ LIST["PARC-MAXC", "PARC", "MAXC"]; needToAuthenticate: BOOL_ TRUE; subTocc: ROPE_ "Subject: \001Topic\002\nTo: \001Recipients\002\ncc: \001Copies To\002"; messageRope: ROPE_ "\n\n\001Message\002\n"; answRope: ROPE_ "\n\n\001Message\002\n"; fwdRope: ROPE_ "\n\n\001CoveringMessage\002\n\n-------------------------------------\n"; fwdRope2: ROPE_ "\n------------------------------------------------------------\n"; messageParseArray: PUBLIC ARRAY MessageFieldIndex OF 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] ]; AuthenticateUser: PUBLIC PROC RETURNS [BOOL] = BEGIN uN: ROPE_ UserExec.GetNameAndPassword[].name; proc: PROC[r: ROPE] = {InternalReport[r]}; pos: INT; auth: GVNames.AuthenticateInfo; IF Rope.Length[uN] = 0 THEN {proc["Please Login"]; RETURN[FALSE]}; IF userRName = NIL THEN IF Rope.SkipTo[s: uN, skip: "."] = Rope.Length[uN] THEN {simpleUserName_ uN; userRName_ Rope.Cat[uN, ".", defaultRegistry]} ELSE {simpleUserName_ Rope.Substr[uN, 0, pos]; userRName_ uN}; SELECT auth_ GVNames.Authenticate[userRName, UserExec.GetNameAndPassword[].password] FROM group => proc["... Can't login as group"]; individual => {needToAuthenticate_ FALSE; RETURN[TRUE]}; notFound => {proc[uN]; proc[" is invalid - please Login"]}; allDown => proc["... No server responded"]; badPwd => proc["... Your Password is invalid - please Login"]; ENDCASE; RETURN[FALSE]; END; PeanutSendInit: UserProfile.ProfileChangedProc = { needToAuthenticate_ TRUE }; ObjectRope: TYPE = object node Rope.RopeRep; CreateMessageRope: PUBLIC PROC [firstMessageNode: TextNode.Ref] RETURNS [r: Rope.ROPE] = { RETURN [NEW[ObjectRope _ [node[object[2048,firstMessageNode,FetchFromMessageRope,NIL,NIL]]]]] }; FetchFromMessageRope: PROC [data: REF, index: INT] RETURNS [CHAR] = { firstMessageNode: TextNode.Ref _ NARROW[data]; loc: TextNode.Location = TextNode.LocRelative[[firstMessageNode,0], index]; n: TextNode.RefTextNode = TextNode.NarrowToTextNode[loc.node]; IF n=NIL THEN ERROR; IF loc.where >= Rope.Size[n.rope] THEN RETURN ['\n]; RETURN [Rope.Fetch[n.rope, loc.where]] }; TopParent: PROC [node, root: TiogaOps.Ref _ NIL] RETURNS [parent: TiogaOps.Ref] = { IF node=NIL THEN RETURN [NIL]; IF root=NIL THEN root _ TiogaOps.Root[node]; DO parent _ TiogaOps.Parent[node]; IF parent=root THEN RETURN [node]; node _ parent; ENDLOOP }; AnswerMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = BEGIN notOk: BOOL; txt, answer: ROPE; messageNode: TiogaOps.Ref; AnswerGetChar: PROC[pos: INT] RETURNS[CHAR] = {RETURN[txt.Fetch[pos]]}; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; messageNode _ TopParent[TiogaOps.GetSelection[].start.node]; IF messageNode = NIL THEN { InternalReport["\nSelect message to be answered."]; RETURN }; TRUSTED {txt _ CreateMessageRope[LOOPHOLE[TiogaOps.FirstChild[messageNode]]] }; [notOk, answer]_ GVAnswer.MakeHeader[AnswerGetChar, txt.Length[], simpleUserName, defaultRegistry, arpaGatewayHosts]; IF notOk THEN { InternalReport["\nFailed to parse message header."]; RETURN }; NewForm[Rope.Cat[answer, answRope]]; END; ForwardMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = { sourceViewer: Viewer; lockedPrimary, lockedSecondary, lockedDest, lockedSource: BOOL _ FALSE; start, end: TiogaOps.Location; sourceDoc, destDoc, messageHeader: TiogaOps.Ref; singleNode: BOOL; Simple: PROC [node: TiogaOps.Ref] RETURNS [BOOL] = { n: TextNode.RefTextNode; TRUSTED { n _ TextNode.NarrowToTextNode[LOOPHOLE[node]] }; IF n=NIL THEN RETURN [FALSE]; singleNode _ n.child=NIL AND n.last; IF ~singleNode THEN RETURN [FALSE]; RETURN [n.runs=NIL] }; Cleanup: PROC = { IF lockedPrimary THEN TiogaOps.UnlockSel[primary]; IF lockedSecondary THEN TiogaOps.UnlockSel[secondary]; IF lockedDest THEN TiogaOps.Unlock[destDoc]; IF lockedSource THEN TiogaOps.Unlock[sourceDoc]; }; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; TiogaOps.LockSel[primary]; lockedPrimary _ TRUE; [sourceViewer, start, end, ----, ----, ----] _ TiogaOps.GetSelection[]; IF sourceViewer = NIL THEN { TiogaOps.UnlockSel[primary]; InternalReport["\nSelect message to be forwarded."]; RETURN }; IF start.node=end.node AND Simple[start.node] THEN -- send without Tioga formatting NewForm[Rope.Cat["Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: ", simpleUserName, "\ncc: \001Copies To\002", fwdRope, TiogaOps.GetRope[start.node], fwdRope2]] ELSE { -- forward with Tioga formatting OPEN TiogaOps; destViewer: Viewer; sourceDoc _ ViewerDoc[sourceViewer]; messageHeader _ TopParent[start.node, sourceDoc]; IF TopParent[end.node, sourceDoc] # messageHeader THEN { UnlockSel[primary]; InternalReport["\nSelect single message to be forwarded."]; RETURN }; destViewer _ ViewerTools.MakeNewTextViewer[info: [name: "Message", iconic: FALSE]]; destDoc _ ViewerDoc[destViewer]; LockSel[secondary]; lockedSecondary _ TRUE; Lock[destDoc]; lockedDest _ TRUE; IF sourceDoc#destDoc THEN { Lock[sourceDoc]; lockedSource _ TRUE }; SelectPoint[destViewer, [FirstChild[destDoc],0], primary]; InsertRope["Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: "]; InsertRope[simpleUserName]; InsertRope["\ncc: \001Copies To\002"]; InsertRope[fwdRope]; IF ~singleNode THEN BackSpace[]; -- get rid of CR at end SelectBranches[ -- source viewer: sourceViewer, level: IF singleNode THEN char ELSE branch, caretBefore: FALSE, pendingDelete: FALSE, which: secondary, start: FirstChild[messageHeader], end: LastChild[messageHeader]]; ToPrimary[]; SelectPoint[destViewer, [FirstChild[destDoc],0], primary]; [] _ NextPlaceholder[gotoend: TRUE]; }; Cleanup[]; }; NewMsgForm: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = { NewForm[Rope.Cat[ "Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: ", simpleUserName, "\ncc: \001Copies To\002", ", ", simpleUserName, messageRope]]}; NewForm: PROC [r: ROPE] = { newForm: Viewer; ViewerTools.SetContents[ newForm _ ViewerTools.MakeNewTextViewer[info: [name: "Message", iconic: FALSE]], r]; ViewerTools.SetSelection[newForm, NEW[ViewerTools.SelPosRec_ [0, 0]]]; newForm.class.notify[newForm, LIST[$NextPlaceholder]]; }; InternalReport: PROC [r: ROPE] = INLINE { PeanutWindow.OutputRope[r] }; CheckForAbort: PROC RETURNS [BOOL] = INLINE { RETURN [PeanutWindow.abortFlag] }; SendMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = BEGIN status: SendParseStatus; sPos, mPos: INT; specialTxt, formatting: ROPE; fullSender, oldName: ROPE; smr: SendingRec; senderV: Viewer; contents: ViewerTools.TiogaContents; Restore: PROC = { senderV.name _ oldName; ViewerOps.OpenIcon[senderV] }; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; senderV _ ViewerTools.GetSelectedViewer[]; IF senderV = NIL THEN { InternalReport["\nSelect message to be sent."]; RETURN }; oldName _ senderV.name; senderV.name _ "Sending"; ViewerOps.CloseViewer[senderV]; TRUSTED {smr_ NEW[SendMsgRecObject_ [fullText: CreateMessageRope[LOOPHOLE[TiogaOps.ViewerDoc[senderV]]]]] }; InternalReport["\nParsing..."]; [status, sPos, mPos]_ ParseTextToBeSent[smr]; IF (status # ok) AND (status # includesPublicDL) THEN BEGIN SELECT status FROM fieldNotAllowed => IF sPos # mPos THEN { InternalReport[Rope.Substr[smr.fullText, sPos, mPos-sPos-1]]; InternalReport[" field is not allowed."]} ELSE InternalReport[IO.PutFR[" field at pos %g is not allowed", IO.int[sPos]]]; syntaxError => IF sPos # mPos THEN { InternalReport["\nSyntax error on line beginning with "]; InternalReport[Rope.Substr[smr.fullText, sPos, mPos-sPos-1]]} ELSE InternalReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]]; includesPrivateDL => InternalReport[" Private dl's are not yet implemented"]; ENDCASE => ERROR; Restore; RETURN; END; IF CheckForAbort[] THEN { Restore; RETURN }; IF smr.arpaRecipient THEN fullSender_ Rope.Concat[simpleUserName, "@Parc"] ELSE fullSender_ userRName; specialTxt_ Rope.Cat["Date: ", IO.PutFR[NIL, IO.time[]], IF smr.from = NIL THEN "\nFrom: " ELSE "\nSender: ", fullSender, "\n"]; InsertIntoSender[senderV, specialTxt, 0]; contents_ ViewerTools.GetTiogaContents[senderV]; IF (formatting_ contents.formatting).Length[] = 0 THEN smr.fullText_ contents.contents ELSE { -- check for null at end of contents; move it to formatting 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] }; InternalReport["... Sending message..."]; IF Send[smr, formatting] THEN { InternalReport[" ... Message has been delivered"]; ViewerOps.DestroyViewer[senderV] } ELSE { Restore; InternalReport["... Message NOT sent."] }; END; InsertIntoSender: PROC[v: Viewer, what: ROPE, where: INT] = BEGIN OPEN TiogaOps; thisV: Ref_ ViewerDoc[v]; 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]; InsertRope[what]; ViewerTools.InhibitUserEdits[v]; IF (prevV # v) AND (prevV#NIL) THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd]; END; CallWithLocks[InsertChars, thisV]; END; Send: PROC[smr: SendingRec, formatting: ROPE] RETURNS[ sent: BOOLEAN] = { sendHandle: GVSend.Handle _ GVSend.Create[]; sent _ SendMessage[smr, formatting, sendHandle, TRUE ! GVSend.SendFailed => IF notDelivered THEN { InternalReport["\nCommunication failure during send."]; sent _ FALSE; CONTINUE } ELSE { InternalReport["\nCommunication failure, but message delivered."]; sent _ TRUE; CONTINUE; }; ]; IF sendHandle#NIL THEN GVSend.Close[sendHandle]; }; SendMessage: PROC[smr: SendingRec, formatting: ROPE, h: GVSend.Handle, validateFlag: BOOL] RETURNS[ sent: BOOLEAN ] = { DO ENABLE GVSend.SendFailed => CONTINUE; -- try again if SendFailed startInfo: GVSend.StartSendInfo; stepper: LIST OF RName; numRecips: INT _ 0; numValidRecips: INT; firstInvalidUser: BOOL_ TRUE; numInvalidUsers: INTEGER_ 0; ReportFromSend: PROC[r: ROPE] = { InternalReport[r]}; InvalidUserProc: PROC [ userNum: INT, userName: RName ] = { IF firstInvalidUser THEN {ReportFromSend["\nInvalid user(s): "]; firstInvalidUser_ FALSE}; SELECT numInvalidUsers _ numInvalidUsers + 1 FROM 1 => ReportFromSend[userName]; IN [2..5] => {ReportFromSend[", "]; ReportFromSend[userName]}; 6 => ReportFromSend[", ..."]; ENDCASE; } ; sent _ FALSE ; startInfo _ GVSend.StartSend[ handle: h, senderPwd: UserExec.GetNameAndPassword[].password, sender: userRName, returnTo: userRName, validate: validateFlag ] ; SELECT startInfo FROM badPwd => {ReportFromSend["\nInvalid password"]; RETURN}; badSender => {ReportFromSend["\nInvalid sender name"]; RETURN}; badReturnTo => {ReportFromSend["\nBad return-to field"]; RETURN}; allDown => {ReportFromSend["\nAll servers are down"]; RETURN}; ok => { stepper _ smr.to; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ]; numRecips _ numRecips + 1; stepper _ stepper.rest; ENDLOOP; IF CheckForAbort[] THEN RETURN; stepper _ smr.cc; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ] ; numRecips _ numRecips + 1 ; stepper _ stepper.rest ; ENDLOOP ; IF CheckForAbort[] THEN RETURN; IF validateFlag THEN { IF (numValidRecips_ GVSend.CheckValidity[ h, InvalidUserProc]) = 0 THEN { ReportFromSend["\nThere were NO valid recipients."]; RETURN }; IF numValidRecips # numRecips THEN { tempInteger: INT _ numRecips-numValidRecips; ReportFromSend[IO.PutFR["\nThere were %g invalid recipients,", IO.int[tempInteger] ]]; RETURN; }; }; IF CheckForAbort[] THEN RETURN; ReportFromSend[IO.PutFR["..sending to %g recipients", IO.int[numValidRecips]]]; 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, TiogaCTRL]; GVSend.AddToItem[h, formatting] }; IF CheckForAbort[] THEN RETURN; GVSend.Send[ h ] ; sent_ TRUE; RETURN; } ; ENDCASE ; ENDLOOP; } ; CanonicalizeName: PUBLIC PROC[r: ROPE] RETURNS[name: ROPE] = BEGIN OPEN GVMailParse; mPos: INT_ 0; lastCharPos: INT_ r.Length[] - 1; GetNextChar: PROC RETURNS [ch: CHAR] = { IF mPos > lastCharPos THEN ch_ endOfInput ELSE ch_ Rope.Fetch[r, mPos]; mPos_ mPos + 1; -- always increment mPos }; BackupChar: PROC = {mPos_ mPos - 1}; ProcessName: PROC[simpleName, registry, arpaHost: ROPE, nameInfo: NameInfo] RETURNS [accept: BOOLEAN] = BEGIN IF nameInfo.type = msgDL THEN name_ simpleName ELSE name_ CanonicalName[simpleName, registry, arpaHost, nameInfo, FALSE].name; RETURN[FALSE]; END; pH: ParseHandle_ InitializeParse[next: GetNextChar, backup: BackupChar]; ParseNameList[pH, ProcessName ! ParseError => {name_ r; CONTINUE}]; FinalizeParse[pH] END; CanonicalName: PUBLIC PROC [simpleName, registry, arpaHost: ROPE, n: GVMailParse.NameInfo, sending: BOOL] RETURNS[name: ROPE, arpaSeen: BOOL] = BEGIN arpaSeen_ FALSE; IF arpaHost.Length[] = 0 OR ((arpaHost.Length[] # 0) AND LocalArpaSite[arpaHost]) THEN { IF registry.Length[] = 0 THEN registry_ defaultRegistry; arpaHost_ NIL}; name_ simpleName; IF registry.Length[] > 0 THEN name_ name.Cat[".", registry]; IF arpaHost.Length[] > 0 THEN {name_ name.Cat["@", arpaHost]; IF sending THEN name_ name.Concat[".ArpaGateway"]; arpaSeen_ TRUE}; END; LocalArpaSite: PUBLIC PROC[host: ROPE] RETURNS [BOOL] = BEGIN FOR l: LIST OF ROPE_ arpaGatewayHosts, l.rest UNTIL l=NIL DO IF Rope.Equal[host, l.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE] END; -- of LocalArpaSite -- ParseTextToBeSent: PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] = BEGIN OPEN GVMailParse; mLF: MessageInfo; tHeaders: LIST OF ROPE_ NIL; msgText: ROPE_ msg.fullText; lastCharPos: INT_ msgText.Length[] - 1; arpaSeen: BOOL_ FALSE; countOfRecipients, dlCount: INT_ 0; GetNextMsgChar: PROC[] RETURNS [ch: CHAR] = { IF mPos > lastCharPos THEN ch_ endOfInput ELSE ch_ Rope.Fetch[msgText, mPos]; mPos_ mPos + 1 }; BackupMsgChar: PROC[] = {mPos_ mPos - 1}; RNameListField: PROC[index: MessageFieldIndex] = BEGIN fieldBody: LIST OF RName_ NIL; AnotherRName: PROC[r1, r2, r3: ROPE, n: NameInfo] RETURNS [BOOLEAN] = BEGIN name: ROPE; sawArpa: BOOL; [name, sawArpa]_ CanonicalName[r1, r2, r3, n, TRUE]; arpaSeen_ arpaSeen OR sawArpa; fieldBody_ CONS[name, fieldBody]; SELECT n.type FROM normal => countOfRecipients_ countOfRecipients + 1; quotedString, file => status_ includesPrivateDL; publicDL => { IF status # includesPrivateDL THEN status_ includesPublicDL; dlCount_ dlCount + 1 }; ENDCASE; RETURN[FALSE]; END; ParseNameList[pH, AnotherRName, NIL, TRUE]; TRUSTED {fieldBody_ LOOPHOLE[List.Reverse[LOOPHOLE[fieldBody]]]}; 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]]]}; ENDCASE => ERROR; END; pH: ParseHandle; field: ROPE_ NIL; fieldNotRecognized: BOOL; mPos_ 0; -- where we are in the fulltext, for parsing status_ ok; -- start with good status pH_ InitializeParse[next: GetNextMsgChar, backup: BackupMsgChar]; IF msgText.Fetch[lastCharPos] # '\n THEN { msg.fullText_ msgText_ Rope.Concat[msgText, "\n"]; lastCharPos_ lastCharPos+1}; DO sPos_ mPos; [fieldNotRecognized, field]_ GetFieldName[pH ! 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: MessageFieldIndex IN MessageFieldIndex DO { mLF_ messageParseArray[i]; IF Rope.Equal[messageParseArray[i].name, field, FALSE] THEN { -- ignore case fieldNotRecognized_ FALSE; SELECT mLF.fType FROM simpleRope => IF i = fromF THEN msg.from_ GetFieldBody[pH] ELSE {IF i = replyToF THEN msg.replyTo_ TRUE; []_ GetFieldBody[pH]}; rCatList => []_ GetFieldBody[pH]; rNameList => RNameListField[i ! ParseError => GOTO errorExit]; ENDCASE => ERROR; EXIT }; }; ENDLOOP; IF fieldNotRecognized THEN []_ GetFieldBody[pH]; -- skip anything not recognized ENDLOOP; FinalizeParse[pH]; msg.endHeadersPos_ mPos - 1; IF arpaSeen THEN msg.arpaRecipient_ TRUE; msg.numRecipients_ countOfRecipients; msg.numDLs_ dlCount; EXITS errorExit => RETURN[syntaxError, sPos, mPos]; END; TRUSTED { TiogaCTRL _ LOOPHOLE[1013B] }; UserProfile.CallWhenProfileChanges[PeanutSendInit]; END. ÒFile: PeanutSendMailImpl.mesa April 1, 1983 3:02 pm Global types & variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * check if userRName is already set * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This probably should be revised to work if selection is in the message header rather than the message body. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * adds registry if r does not include one; canonicalizes " at " to "@", etc make sure msgText ends in a CR 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˜šÏk ˜ Jšœ œ˜Jšœ œ˜!J˜ J˜Jšœœx˜„Jšœœ˜Jšœ œ˜!Jšœœ˜Jšœ˜J˜J˜ J˜ J˜ Jšœ ˜ J˜Jšœ œ˜$Jšœ œ.˜?J˜J˜ J˜ J˜—šœ ˜!š˜Jšœ.œW˜‡—Jšœ˜#Jšœ˜ —Jšœœ˜'J˜Jšœ™J˜Jšœ˜Jšœœ˜$Jšœœœ˜Jšœœ˜J˜Jšœ œœœÏc˜9Jšœœœœž˜@J˜Jšœœœ˜#Jš œœœœœœ˜IJ˜Jšœœœ˜J˜šœ œ˜J˜H—Jšœ œ˜+Jšœ œ˜(Jšœ œK˜XJšœ œE˜SJ˜šœœœœ˜Bšœž/˜LJ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜——JšœU™Uš Ïnœœœœœ˜.Jš˜Jšœœ%˜-Jšœœœ˜*Jšœœ˜ J˜šœœœœ˜BJ˜—Jšœ!™!šœ œœœ0˜JJšœD˜HJšœ:˜>J˜—š˜JšœN˜RJ˜*Jšœ#œœœ˜8J˜;J˜+J˜>Jšœ˜—J˜Jšœœ˜Jšœ˜J˜—JšœU™UJ˜šœGœ˜NJ˜—Jšœ œ˜,J˜šŸœ œ"œ œ˜Zšœœ ˜Jšœ8œœ˜G——J˜š Ÿœœœ œœœ˜EJšœ!œ˜.JšœK˜KJ˜>Jšœœœœ˜Jšœ œœ˜4Jšœ#˜)J˜—šŸ œœœœ˜SJš œœœœœ˜Jšœœœ˜,š˜J˜Jšœ œœ˜"J˜Jšœ˜ J˜——šŸ œœœ2œ˜OJš˜Jšœœ˜ Jšœ œ˜Jšœ˜Jš Ÿ œœœœœœ˜GJ˜Jšœœœœ˜:Jšœ<˜<šœœœ˜Jšœ4œ˜=—Jšœœ&˜OJ˜šœA˜AJ˜3J˜—šœœ8œ˜NJ˜—Jšœ$˜$Jšœ˜J˜—šŸ œœœ2œ˜RJšœ˜Jšœ:œœ˜GJ˜Jšœ0˜0Jšœ œ˜J˜J™J™kJ™J™šŸœœœœ˜4J˜Jšœ!œ ˜:Jš œœœœœ˜Jšœœœ˜$Jšœ œœœ˜#Jšœ œ˜—šŸœœ˜Jšœœ˜2Jšœœ˜6Jšœ œ˜,Jšœœ˜0J˜—J˜Jšœœœœ˜:Jšœ+œ˜0JšœG˜Gšœœœ˜JšœRœ˜[—šœœœž ˜SJšœ¬˜¬—šœž ˜'Jšœ ˜Jšœ˜Jšœ$˜$Jšœ1˜1šœ0œ˜8JšœPœ˜Y—JšœKœ˜SJšœ ˜ Jšœ&œ˜+Jšœœ˜!Jšœœ#œ˜CJšœ:˜:JšœI˜IJšœ˜Jšœ&˜&Jšœ˜Jšœ œž˜9šœž ˜Jš œœ œœœ˜UJšœœ˜'JšœA˜A—J˜ Jšœ:˜:Jšœœ˜$J˜—J˜ J˜J˜—šŸ œ œ2œ˜Ršœ˜Jšœ=˜=Jšœ@˜@Jšœ˜—J˜—šŸœœœ˜Jšœ˜šœ˜JšœHœ˜T—Jšœ"œ!˜FJšœœ˜9J˜—šŸœœœœ ˜GJ˜—š Ÿ œœœœœœ˜PJ˜—JšœU™UJ˜šŸœ œ2œ˜MJš˜J˜Jšœ œ˜Jšœœ˜Jšœœ˜J˜Jšœ˜J˜$JšŸœœ;˜HJ˜Jšœœœœ˜:J˜Jšœ*˜*Jšœ œœ3œ˜QJšœ˜Jšœ˜Jšœ˜J˜šœœ˜#Jšœœ#˜H—J˜J˜-J˜šœœ ˜;šœ˜˜šœ ˜˜?J˜)—Jšœœ+œ ˜P——˜šœ ˜˜;J˜=—Jšœœ.œ ˜S——J˜MJšœœ˜—J˜Jšœ˜Jšœ˜J˜—šœœ œ˜,J˜—Jšœœ1˜JJšœ˜Jšœœœœ ˜8Jšœ œœ œ!˜GJšœ)˜)Jšœ0˜0Jšœ0œ ˜Všœž;˜BJšœœ!˜*šœ'œž˜CJ˜8J˜6—šœ3˜7J˜——J˜)šœœ˜Jšœ2˜2J˜"—Jšœ6˜:J˜Jšœ˜J˜—šŸœœœ œ˜;Jšœœ ˜J˜šŸ œœ ˜$J˜J˜J˜J˜Jšœœ˜ Jšœ œ,˜=šœ7˜;J˜—J˜FJ˜J˜#J˜J˜ šœ œœ˜#J˜=—Jšœ˜—J˜"Jšœ˜J˜J˜—JšœO™Oš Ÿœœœœœ˜IJ˜,šœ.œ˜Kšœœ˜Jšœ7˜7Jšœœœ˜—šœ˜JšœB˜BJšœœ œ˜—J˜—Jšœ œœ˜0˜J˜——šŸ œœœ"œ˜ZJšœœ˜Jšœœœž˜DJ˜ Jšœ œœ˜Jšœ œ˜Jšœœ˜Jšœœœ˜Jšœœ˜J˜JšŸœœœ˜5šŸœœ œ˜;Jšœœ;œ˜Zšœ'˜1J˜Jšœ<˜>J˜—Jšœ˜J˜J˜—Jšœœ˜šœ(˜(J˜2J˜J˜J˜J˜—šœ ˜Jšœ1œ˜9Jšœ7œ˜NJšœ9œ˜AJšœ6œ˜>˜J˜šœ œœ˜J˜(J˜J˜Jšœ˜—Jšœœœ˜J˜šœ œœ˜J˜)J˜J˜Jšœ˜ —Jšœœœ˜šœœ˜šœAœ˜IJšœ5œ˜>—šœœ˜%Jšœ œ˜,Jšœœ.œ˜VJšœ˜J˜—J˜J˜—Jšœœœ˜Jšœœ%œ˜OJšœœž.˜DJ˜J˜"šœ œœž,˜EJšœB˜B—Jšœœœ˜J˜Jšœœ˜ Jšœ˜J˜—Jšœ˜ —Jšœ˜J˜—J˜JšœO™Oš Ÿœœœœœœ˜J˜—J˜——Jšœ˜Jšœœ˜Jšœ˜J˜—Jšœ œœ˜+Jšœ œœ˜Ašœ˜šœœ œœ˜-šœœ œ ˜"Jšœ œ œ œ˜G——šœœ œœ˜7šœœ œ ˜"Jšœ œ œ œ˜G——Jšœœ˜—Jšœ˜J˜—J˜Jšœœœ˜Jšœœ˜Jšœ ž,˜6Jšœ ž˜&J˜J˜AJ˜Jšœ™šœ"œ˜*JšœO˜OJ˜—š˜J˜ ˜Jšœœ˜Jš˜J˜—J˜—Jšœ˜—Jšœœž˜PJšœ˜—J˜JšœA™AJ˜J˜J˜JšœQ™QJšœN™N—˜Jšœ œœ˜)J˜&J˜š˜Jšœ œ˜-—Jšœ˜J˜—Jšœœ ˜(J˜Jšœ3˜3J˜Jšœ˜J˜J˜—…—G*_‹