<> <> 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.