DIRECTORY ArpaMT USING [CheckFromField, Info, ParseHeaders, PrintHeaders, TranslateToArpa, TranslateToGrapevine], ArpaSMTPControl USING [arpaExceptions, deadLetterName, deadLetterSenderName, defaultLogAcceptPriority, longGVMSName, notifyManagerNames], ArpaSMTPDescr USING [CopyForReturn, Create, CreateFailed, Descr, EnumerateRawRecipients, GetArpaReversePath, GetFormat, GetGvSender, GetPrecedeMsgText, GetReturnPathLine, RawRecipProc, RetrieveMsgStream, Unparse, WrongState], ArpaSMTPQueue USING [AddNewMessage], ArpaSMTPSupport USING [LogPriority], ArpaSMTPSyntax USING [EnumerateGVItems, GVItemProc], BasicTime USING [GMT, MonthOfYear, Now, nullGMT, Unpack, Unpacked], Convert USING [RopeFromInt, RopeFromTime], IO USING [BreakProc, Close, CreateStream, CreateStreamProcs, EndOfStream, Error, Flush, GetBlock, GetChar, GetIndex, GetInfo, GetLength, GetTokenRope, int, PutChar, PutFR, PutRope, RIS, rope, RopeFromROS, ROS, SetIndex, STREAM, StreamProcs], GVBasics USING [RName], GVNames USING [IsMemberDirect, Membership], Process USING [Detach], RefText USING [AppendChar, ObtainScratch, ReleaseScratch], Rope USING [Cat, Concat, Equal, Fetch, Find, FromRefText, IsEmpty, Length, ROPE, Substr], SimpleMailer USING [SendMessage], ViewerIO USING [CreateViewerStreams, GetViewerFromStream]; ArpaSMTPSupportImpl: CEDAR MONITOR IMPORTS ArpaMT, ArpaSMTPControl, ArpaSMTPDescr, ArpaSMTPQueue, ArpaSMTPSyntax, BasicTime, Convert, IO, GVNames, Process, RefText, Rope, SimpleMailer, ViewerIO EXPORTS ArpaSMTPSupport = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Descr: TYPE = ArpaSMTPDescr.Descr; currentLogAcceptPriority: PUBLIC ArpaSMTPSupport.LogPriority _ ArpaSMTPControl.defaultLogAcceptPriority; Log: PUBLIC PROC [priority: ArpaSMTPSupport.LogPriority, note1, note2, note3, note4, note5, note6, note7, note8, note9, note10: Rope.ROPE _ NIL] = { now: BasicTime.GMT _ BasicTime.Now[]; IF priority >= currentLogAcceptPriority THEN WriteToLog[priority, now, note1, note2, note3, note4, note5, note6, note7, note8, note9, note10]; IF priority >= ATTENTION THEN { -- notify managers date: ROPE _ RFC822Date[now]; from: ROPE _ ArpaSMTPControl.deadLetterSenderName; to: LIST OF GVBasics.RName _ ArpaSMTPControl.notifyManagerNames; subject: ROPE _ "Confusion in Mail Gateway"; body1: ROPE _ Rope.Cat[note1, note2, note3, note4, note5]; body2: ROPE _ Rope.Cat[note1, note2, note3, note4, note5]; body: ROPE _ Rope.Cat[body1, body2]; [] _ SimpleMailer.SendMessage[from: from, returnTo: from, to: to, subject: subject, body: body] }; }; ForkNewMessage: PROC [descr: Descr, queue: ROPE] = TRUSTED { Process.Detach[FORK ArpaSMTPQueue.AddNewMessage[descr, queue]]; }; HeaderParseError: PUBLIC PROC [recipList: LIST OF ROPE, descr: Descr] = { ENABLE ArpaSMTPDescr.CreateFailed => CONTINUE; now: BasicTime.GMT _ BasicTime.Now[]; date: ROPE _ Rope.Cat["Date: ", RFC822Date[now], "\n"]; from: ROPE _ Rope.Cat["From: ", ArpaSMTPControl.deadLetterSenderName, "\n"]; to: ROPE _ Rope.Cat["To: ", ArpaSMTPControl.arpaExceptions.first, "\n"]; subject: ROPE _ "Subject: Parsing Error in Message Header\n\n"; header: ROPE _ Rope.Cat[date, from, to, subject]; getReturnPathLine: ROPE _ ArpaSMTPDescr.GetReturnPathLine[descr]; getPrecedeMsgText: ROPE _ ArpaSMTPDescr.GetPrecedeMsgText[descr]; msgStream: IO.STREAM; errors: IO.STREAM _ IO.ROS[]; before: IO.STREAM _ IO.ROS[]; after: IO.STREAM _ IO.ROS[]; info: ArpaMT.Info; body: ROPE; new: Descr; msgStream _ ArpaSMTPDescr.RetrieveMsgStream[descr]; SELECT ArpaSMTPDescr.GetFormat[descr] FROM arpa => { info _ ArpaMT.ParseHeaders[file: msgStream, errStream: errors]; msgStream.Close[]; ArpaMT.PrintHeaders[info, before]; ArpaMT.TranslateToGrapevine[info]; ArpaMT.PrintHeaders[info, after]; }; gv => { -- Copied from SMTPSendImpl AssignTextStream: ArpaSMTPSyntax.GVItemProc = { currentIndex: INT; IF itemHeader.type # Text THEN RETURN; currentIndex _ msgStream.GetIndex[]; msgStream _ CreateSubrangeStream[ origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length]; continue _ FALSE; }; ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream]; info _ ArpaMT.ParseHeaders[file: msgStream, errStream: errors]; msgStream.Close[]; ArpaMT.PrintHeaders[info, before]; ArpaMT.TranslateToArpa[info]; ArpaMT.PrintHeaders[info, after]; }; ENDCASE => ERROR; body _ Rope.Cat[body, "Recipients: "]; FOR rest: LIST OF ROPE _ recipList, rest.rest UNTIL rest = NIL DO IF rest # recipList THEN body _ Rope.Cat[body, ", "]; body _ Rope.Cat[body, rest.first]; ENDLOOP; body _ Rope.Cat[body, "\n\n"]; body _ Rope.Cat[body, IO.RopeFromROS[errors], "\n"]; body _ Rope.Cat[body, "Before translation:\n\n", IO.RopeFromROS[before], "\n\n"]; body _ Rope.Cat[body, "After translation:\n\n"]; IF getReturnPathLine # NIL THEN body _ Rope.Cat[body, getReturnPathLine, "\n"]; IF getPrecedeMsgText # NIL THEN body _ Rope.Cat[body, getPrecedeMsgText, "\n"]; body _ Rope.Cat[body, IO.RopeFromROS[after], "\n\n"]; new _ ArpaSMTPDescr.Create[ gvSender: ArpaSMTPControl.deadLetterSenderName, rawRecipients: ArpaSMTPControl.arpaExceptions, format: arpa, -- don't have GV items msgStream: IO.RIS[Rope.Cat[header, body]]]; ForkNewMessage[new, "HeaderParseError"]; }; Undeliverable: PROC [why: ROPE, descr: Descr] = { ENABLE ArpaSMTPDescr.CreateFailed => CONTINUE; now: BasicTime.GMT _ BasicTime.Now[]; date: ROPE _ Rope.Cat["Date: ", RFC822Date[now], "\n"]; from: ROPE _ Rope.Cat["From: ", ArpaSMTPControl.deadLetterSenderName, "\n"]; to: ROPE _ Rope.Cat["To: ", ArpaSMTPControl.deadLetterName, "\n"]; subject: ROPE _ "Subject: Undeliverable mail notification\n\n"; header: ROPE _ Rope.Cat[date, from, to, subject]; getReturnPathLine: ROPE _ ArpaSMTPDescr.GetReturnPathLine[descr]; getPrecedeMsgText: ROPE _ ArpaSMTPDescr.GetPrecedeMsgText[descr]; msgStream: IO.STREAM; errors: IO.STREAM _ IO.ROS[]; before: IO.STREAM _ IO.ROS[]; info: ArpaMT.Info; sender, body: ROPE; new: Descr; msgStream _ ArpaSMTPDescr.RetrieveMsgStream[descr]; SELECT ArpaSMTPDescr.GetFormat[descr] FROM arpa => { sender _ ArpaSMTPDescr.GetArpaReversePath[descr]; info _ ArpaMT.ParseHeaders[file: msgStream, errStream: errors]; msgStream.Close[]; ArpaMT.PrintHeaders[info, before]; }; gv => { -- Copied from SMTPSendImpl AssignTextStream: ArpaSMTPSyntax.GVItemProc = { currentIndex: INT; IF itemHeader.type # Text THEN RETURN; currentIndex _ msgStream.GetIndex[]; msgStream _ CreateSubrangeStream[ origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length]; continue _ FALSE; }; sender _ ArpaSMTPDescr.GetGvSender[descr]; ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream]; info _ ArpaMT.ParseHeaders[file: msgStream, errStream: errors]; msgStream.Close[]; ArpaMT.PrintHeaders[info, before]; }; ENDCASE => ERROR; body _ Rope.Cat[why, "\n\nThe message will be sent to:\n", sender, ".\n\n"]; body _ Rope.Cat[body, "The header of the message was:\n--------------------\n"]; IF getReturnPathLine # NIL THEN body _ Rope.Cat[body, getReturnPathLine, "\n"]; IF getPrecedeMsgText # NIL THEN body _ Rope.Cat[body, getPrecedeMsgText, "\n"]; body _ Rope.Cat[body, IO.RopeFromROS[before], "\n\n"]; new _ ArpaSMTPDescr.Create[ gvSender: ArpaSMTPControl.deadLetterSenderName, rawRecipients: LIST[ArpaSMTPControl.deadLetterName], format: arpa, -- don't have GV items msgStream: IO.RIS[Rope.Cat[header, body]]]; ForkNewMessage[new, "Undeliverable"]; }; WriteToLog: ENTRY PROC [priority: ArpaSMTPSupport.LogPriority, now: BasicTime.GMT, note1, note2, note3, note4, note5, note6, note7, note8, note9, note10: Rope.ROPE _ NIL] = { logViewerOut.PutRope[Convert.RopeFromTime[from: now, start: hours, end: seconds, useAMPM: FALSE, includeZone: FALSE]]; logViewerOut.PutRope[" "]; IF priority > noteworthy THEN { logViewerOut.PutRope[logPriorityNames[priority]]; logViewerOut.PutRope[": "]; }; logViewerOut.PutRope[note1]; logViewerOut.PutRope[note2]; logViewerOut.PutRope[note3]; logViewerOut.PutRope[note4]; logViewerOut.PutRope[note5]; logViewerOut.PutRope[note6]; logViewerOut.PutRope[note7]; logViewerOut.PutRope[note8]; logViewerOut.PutRope[note9]; logViewerOut.PutRope[note10]; logViewerOut.PutChar['\n]; logViewerOut.Flush[]; }; logPriorityNames: PUBLIC ARRAY ArpaSMTPSupport.LogPriority OF ROPE _ ["verbose", "noteworthy", "Important", "ATTENTION", "CRITICAL"]; logViewerIn, logViewerOut: STREAM; -- initialized below letNonMembersPlay: BOOL _ TRUE; goodGuys, badGuys: LIST OF ROPE; AuthorizationCheck: PUBLIC PROC [sender: ROPE] RETURNS [ok: BOOLEAN] = { RETURN[TRUE]}; StripQuotes: PROC [old: ROPE] RETURNS [new: ROPE] = { length: INT _ Rope.Length[old]; new _ old; IF length < 2 THEN RETURN; IF Rope.Fetch[old, 0] # '\" THEN RETURN; BEGIN quoteSeen: BOOLEAN _ FALSE; text: REF TEXT _ RefText.ObtainScratch[length]; FOR i: INT IN [0..length) DO c: CHAR = Rope.Fetch[new, i]; IF c = '\\ AND ~quoteSeen THEN { quoteSeen _ TRUE; LOOP; }; quoteSeen _ FALSE; IF c = '\" THEN LOOP; -- Not quite right, but works for reasonable input text _ RefText.AppendChar[text, c]; ENDLOOP; new _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; END; }; CheckHeader: PUBLIC PROC [sender: ROPE, descr: Descr] RETURNS [ok: BOOLEAN _ TRUE] = { msgStream: IO.STREAM; errors: IO.STREAM _ IO.ROS[]; precede: ROPE _ ArpaSMTPDescr.GetPrecedeMsgText[descr]; errorRope: ROPE; info: ArpaMT.Info; IF Rope.Find[precede, "\n\n"] #-1 THEN RETURN; SELECT ArpaSMTPDescr.GetFormat[descr] FROM arpa => RETURN; -- Don't check ARPA headers (yet) gv => { -- Copied from SMTPSendImpl AssignTextStream: ArpaSMTPSyntax.GVItemProc = { currentIndex: INT; IF itemHeader.type # Text THEN RETURN; currentIndex _ msgStream.GetIndex[]; msgStream _ CreateSubrangeStream[ origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length]; continue _ FALSE; }; ArpaSender: PROC[descr: Descr] RETURNS[BOOLEAN] = { arpaReversePath: Rope.ROPE _ ArpaSMTPDescr.GetArpaReversePath[descr]; IF Rope.Find[ArpaSMTPDescr.GetGvSender[descr], "@"] > -1 THEN RETURN[TRUE]; IF Rope.Find[s1: arpaReversePath, s2: "Owners-"] > -1 THEN RETURN[TRUE]; RETURN[Rope.Find[arpaReversePath, "@"] > -1 AND Rope.Find[s1: arpaReversePath, s2: "@Xerox", case: FALSE] = -1]; }; msgStream _ ArpaSMTPDescr.RetrieveMsgStream[descr]; ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream]; info _ ArpaMT.ParseHeaders[file: msgStream, errStream: errors]; msgStream.Close[]; errorRope _ IO.RopeFromROS[errors]; IF Rope.IsEmpty[errorRope] AND ~ArpaSender[descr] AND ~ArpaMT.CheckFromField[info] THEN errorRope _ "A non-Ascii or unprintable character in the From or Sender fields (even if properly quoted) kills some mailers. If your address (shown below) ends with a \".ns\", check with CINSupport:All Areas to see if your domain should be registered with the Grapevine mail gateways."; IF ~Rope.IsEmpty[errorRope] AND ~ArpaSender[descr] THEN { recipients: ROPE; CopyRawRecipient: ArpaSMTPDescr.RawRecipProc = { IF recipients = NIL THEN recipients _ "Recipients: " ELSE recipients _ Rope.Cat[recipients, ", "]; recipients _ Rope.Cat[recipients, rawRecipient]; }; ArpaSMTPDescr.EnumerateRawRecipients[descr, CopyRawRecipient, NIL]; errorRope _ Rope.Cat["The Arpa Mail Gateway encountered troubles in the header of your message. In order to avoid confusing other ARPA Internet sites, your message was not delivered to the following ARPA Internet recipients: \n\n", recipients, "\n\n", errorRope]; NotifySender[descr, errorRope]; ok _ FALSE; }; }; ENDCASE => ERROR; }; NotifySender: PUBLIC PROC [descr: Descr, why1, why2, why3, why4, why5: ROPE _ NIL] = { ENABLE { ArpaSMTPDescr.CreateFailed => Log[ATTENTION, "Unable to create descriptor to return item ", ArpaSMTPDescr.Unparse[descr], " to sender. Being returned because: \"", why1, why2, why3, why4, why5, "\".\nToo bad!"]; ArpaSMTPDescr.WrongState => Log[noteworthy, "Asked to return item ", ArpaSMTPDescr.Unparse[descr], " to sender, because: \"", why1, why2, why3, why4, why5, "\"\nThe item has a null reverse path (possibly a return message itself). Too bad!"]; }; why: ROPE = Rope.Cat[why1, why2, why3, why4, why5]; new: Descr; Undeliverable[why, descr]; new _ ArpaSMTPDescr.CopyForReturn[descr, Rope.Cat[why, "\n\nThe text of your message was\n--------------------\n"]]; Log[important, ArpaSMTPDescr.Unparse[descr], " is being returned because:\n", why1, why2, why3, why4, why5]; ForkNewMessage[new, "ReturnToSender"]; }; monthText: ARRAY BasicTime.MonthOfYear OF ROPE = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", "???"]; USzones: TYPE ~ [4..10]; zones1stChar: ARRAY USzones OF ROPE = ["A", "E", "C", "M", "P", "Y", "H"]; Now: PUBLIC PROC [compressed: BOOL _ FALSE] RETURNS [rope: ROPE, gmt: BasicTime.GMT] = { unpacked: BasicTime.Unpacked; date, time: ROPE; z: INT; unpacked _ BasicTime.Unpack[gmt _ BasicTime.Now[]]; IF compressed THEN { date _ IO.PutFR["%02G%02G%02G-", IO.int[unpacked.year MOD 100], IO.int[unpacked.month.ORD+1], IO.int[unpacked.day]]; time _ IO.PutFR["%02g%02g%02g", IO.int[unpacked.hour], IO.int[unpacked.minute], IO.int[unpacked.second]]; } ELSE { date _ IO.PutFR["%02g %g %02g ", IO.int[unpacked.day], IO.rope[monthText[unpacked.month]], IO.int[unpacked.year MOD 100]]; time _ IO.PutFR["%02g:%02g:%02g ", IO.int[unpacked.hour], IO.int[unpacked.minute], IO.int[unpacked.second]]; z _ unpacked.zone/60; -- gives zone in range -12 to 12 IF (FIRST[USzones] <= z) AND (z <= LAST[USzones]) THEN time _ Rope.Cat[time, zones1stChar[z], IF unpacked.dst = yes THEN "D" ELSE "S", "T"] ELSE { IF unpacked.dst = yes THEN z _ z - 1; IF z = 0 THEN time _ Rope.Concat[time, "GMT"] ELSE time _ Rope.Cat[ time, IF z > 0 THEN "+" ELSE "-", Convert.RopeFromInt[ABS[z]]]; }; }; RETURN[Rope.Concat[date, time], gmt]; }; -- end Now 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; weekday, 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; SELECT upt.weekday FROM Monday => weekday_ "Monday"; Tuesday => weekday_ "Tuesday"; Wednesday => weekday _ "Wednesday"; Thursday => weekday _ "Thursday"; Friday => weekday _ "Friday"; Saturday => weekday _ "Saturday"; Sunday => weekday _ "Sunday"; 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]]; date _ Rope.Cat[date, " (", weekday, ")"]; END; CreateSubrangeStream: PUBLIC PROC [origStream: STREAM, min, max: INT] RETURNS [STREAM] = { newStreamData: REF SubrangeStreamData; newBackingStream: STREAM; IF origStream.GetInfo[].class = $Subrange THEN { origStreamData: REF SubrangeStreamData = NARROW[origStream.streamData]; origMinIndex: INT = origStreamData.min; origMaxIndex: INT = origStreamData.max; newBackingStream _ origStream.backingStream; newStreamData _ NEW[SubrangeStreamData _ [min: MAX[min, origMinIndex], max: MIN[max, origMaxIndex]]]; } ELSE { newBackingStream _ origStream; newStreamData _ NEW[SubrangeStreamData _ [min: min, max: MIN[max, origStream.GetLength[]]]]; }; RETURN[IO.CreateStream[ backingStream: newBackingStream, streamProcs: SubrangeStreamProcs, streamData: newStreamData]]; }; SubrangeStreamProcs: REF IO.StreamProcs = IO.CreateStreamProcs[ variety: input, class: $Subrange, getChar: SubrangeGetChar, getBlock: SubrangeGetBlock, endOf: SubrangeEndOf, backup: SubrangeBackup, -- Bug in real one setIndex: SubrangeSetIndex, getLength: SubrangeGetLength]; SubrangeStreamData: TYPE = RECORD[min, max: INT]; -- Info is [min..max) SubrangeBackup: PROC [self: STREAM, char: CHAR] = { me: REF SubrangeStreamData = NARROW[self.streamData]; self.SetIndex[self.GetIndex[]-1]; }; SubrangeEndOf: PROC [self: STREAM] RETURNS [BOOL] = { me: REF SubrangeStreamData = NARROW[self.streamData]; RETURN[self.backingStream.GetIndex[] >= me.max]; }; SubrangeGetLength: PROC [self: STREAM] RETURNS [length: INT] = { me: REF SubrangeStreamData = NARROW[self.streamData]; RETURN[me.max]; }; SubrangeSetIndex: PROC [self: STREAM, index: INT] = { me: REF SubrangeStreamData = NARROW[self.streamData]; IF index > me.max OR index < me.min THEN ERROR IO.Error[BadIndex, self]; self.backingStream.SetIndex[index]; }; SubrangeGetBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT _ 0, count: NAT _ NAT.LAST] RETURNS [nBytesRead: NAT] = { me: REF SubrangeStreamData = NARROW[self.streamData]; bytesLeft: INT = me.max - self.backingStream.GetIndex[]; bytesToRead: INT = MIN[count, bytesLeft]; natBytesToRead: NAT = bytesToRead; nBytesRead _ self.backingStream.GetBlock[ block: block, startIndex: startIndex, count: natBytesToRead]; }; SubrangeGetChar: PROC [self: STREAM] RETURNS [CHAR] = { me: REF SubrangeStreamData = NARROW[self.streamData]; fullStream: STREAM ~ self.backingStream; IF fullStream.GetIndex[] >= me.max THEN IO.EndOfStream[self]; RETURN[fullStream.GetChar[]]; }; RopeFromSubrange: PUBLIC PROC [origStream: STREAM, min, max: INT] RETURNS [rope: ROPE] = { EverythingProc: IO.BreakProc = {RETURN[other]}; subrangeStream: STREAM; subrangeStream _ CreateSubrangeStream[origStream: origStream, min: min, max: max]; rope _ subrangeStream.GetTokenRope[EverythingProc ! IO.EndOfStream => CONTINUE ].token; }; [logViewerIn, logViewerOut] _ ViewerIO.CreateViewerStreams[ name: "MailGateway Log", backingFile: "MailGateway.log"]; ViewerIO.GetViewerFromStream[logViewerOut].inhibitDestroy _ TRUE; logViewerOut.PutRope["########## "]; logViewerOut.PutRope[ArpaSMTPControl.longGVMSName]; logViewerOut.PutRope[" Log ##########\n\n"]; END. ArpaSMTPSupportImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: DCraft, December 20, 1983 5:54 pm Last Edited by: Taft, January 23, 1984 1:27:58 pm PST Hal Murray June 17, 1985 11:28:06 am PDT John Larson, August 10, 1988 1:11:36 pm PDT Logging Information Monitored so we don't interleave messages on the log. Housekeeping There is similar code in SMTPQueueImpl membership: GVNames.Membership; IF Rope.Find[sender, "@"] # -1 THEN RETURN[TRUE]; -- Arpa => GV DL => Arpa sender _ StripQuotes[sender]; FOR list: LIST OF ROPE _ goodGuys, list.rest UNTIL list = NIL DO IF Rope.Equal[sender, list.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; FOR list: LIST OF ROPE _ badGuys, list.rest UNTIL list = NIL DO IF Rope.Equal[sender, list.first, FALSE] THEN RETURN[letNonMembersPlay]; ENDLOOP; membership _ GVNames.IsMemberDirect["ArpanetUsers^.X", sender]; SELECT membership FROM yes => { goodGuys _ CONS[sender, goodGuys]; RETURN[TRUE]; }; no => { Log[important, "\"", sender, "\" isn't a member of ArpanetUsers^.X"]; badGuys _ CONS[sender, badGuys]; RETURN[letNonMembersPlay]; }; notGroup => Log[ATTENTION, "GV Claims ArpanetUsers^.X isn't a group"]; allDown => Log[ATTENTION, "GV Claims all servers for the X registry are down"]; ENDCASE => NULL; RETURN[TRUE]; }; JSmith.OSBUNorth => JSmith.OSBUNorth "John Smith".OSBUNorth => John Smith.OSBUNorth Beware: Returned mail uses the precede hackery. The actual descr has the same body and hence looks like it has the same header so it will hit the same problem each time it gets returned. Notification of Undeliverable Mail Current Date and Time (in SMTP format) Returns the current time in format specified for Arpa SMTP. The "compressed" form is suitable for a file name. generates arpa standard time, dd mmm yy hh:mm:ss zzz Subrange Streams Create a new stream encompassing only a subrange of the original one. If the original stream is already a subrange stream, do not layer again. This assumes that GetChar/GetBlock set the index to the length after reading the last char. Misc ΚR– "cedar" style˜head™Icodešœ Οmœ1™ œ˜CMšœ‡˜‡Mšœ˜Mšœ œ˜——Mš œ œ˜——Mšœ"™"š ’ œ œ œ/ œ œ˜Wš œ˜šœ˜Mšœ  œ«˜Έ—šœ˜MšœΨ˜Ψ—M˜—Mšœ œ*˜3Mšœ ˜ Mšœ˜Mšœv˜vMšœn˜nMšœ)˜)—šœΟrΠkr₯™&Mšœ  œ œ œ_˜Mšœ  œ ˜Mšœ œ  œ œ'˜J—š’œ œ œ œ œ œ œ œ˜XMšœ6 œ4™nMšœ˜Mšœ  œ˜Mšœ œ˜Mšœ3˜3š œ  œ˜Mš œ œ œ œ œ œ œ˜wMš œ œ œ œ œ˜n—š œ˜Mš œ œ œ œ# œ œ˜}Mš œ œ œ œ œ˜oMšœ£ ˜6š  œ œ œ œ  ˜6Mšœ' œ œ œ ˜T—š œ˜Mš œ œ ˜%Mš œ œ ˜-š œ˜Mš œ œ œ œ œ ˜E———Mš œ˜%Mšœ£ ˜ —š ’ œ œ œ œ œ œ˜TMšœ4™4Mš œ œ œ˜Mšœ+ œ ˜JMšœ œ˜Mšœ œ˜ Mšœ œ˜!Mšœ  œ£˜:Mšœ  œ£œ˜>Mšœ  œ˜Mšœ œ œ ˜Mšœ œ˜Mšœ œ ˜š œ œ  œ˜$š œ ˜š œ ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mš ˜——š ˜š œ ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mš œ˜ ———š œ œ œ˜Mšœ œ˜Mšœ  œ  œ  œ%˜QM˜—š œ  ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ œ˜Mš œ œ˜M˜—š œ  ˜Mšœ˜Mšœ˜Mšœ#˜#Mšœ!˜!Mšœ˜Mšœ!˜!Mšœ˜Mšœ œ˜Mš œ œ˜—Mšœ œ˜0MšœU˜UMšœK˜KMšœ*˜*Mš œ˜—Mšœ™š’œ œ œ œ  œ œ œ˜ZM™ŽMšœ œ˜&Mšœ œ˜š œ( œ˜0Mšœ œ œ˜GMšœ œ˜'Mšœ œ˜'M˜,Mšœ œ œ œ˜fMšœ˜—š œ˜M˜Mšœ œ( œ ˜^Mšœ˜—š œ œ˜Mšœ ˜ Mšœ!˜!Mšœ˜—M˜—š’œ œ œ œ˜?Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ£˜+Mšœ˜Mšœ˜—Mšœ œ œ  œ£˜Gš’œ œ œ œ˜3Mšœ œ œ˜5Mšœ$˜$—š ’ œ œ œ œ œ˜5Mšœ œ œ˜5Mšœ[™[Mš œ-˜3—š ’œ œ œ œ  œ˜@Mšœ œ œ˜5Mš œ ˜—š’œ œ œ  œ˜5Mšœ œ œ˜5Mš  œ œ œ œ œ˜HM˜&—š’œ œ œ  œ œ œ  œ œ œ œ œ˜€Mšœ œ œ˜5Mšœ  œ*˜8Mšœ  œ œ˜)Mšœ œ˜"šœ)˜)Mšœ@˜@——š ’œ œ œ œ œ˜7Mšœ œ œ˜5Mšœ  œ˜(Mš œ! œ œ˜=Mš œ˜ —š’œ œ œ œ  œ œ œ˜ZMš’œ œ œ ˜/Mšœ œ˜MšœR˜Ršœ3˜3Mš œ œ ˜&——M™šœ;˜;Mšœ˜Mšœ ˜ —Mšœ< œ˜AMšœ$˜$Mšœ3˜3Mšœ,˜,Mš œ˜——…—HζeH