-- Copyright (C) 1981, 1982, 1983, 1984, 1985 by Xerox Corporation. All rights reserved. -- ReturnMail.mesa, Transport Mechanism Mail Server - Return Undeliverable Mail -- -- HGM, 15-Sep-85 3:41:57 -- Randy Gobbel 19-May-81 23:38:50 -- -- Andrew Birrell 4-Mar-82 14:42:33 -- -- Michael Schroeder 25-Jan-83 15:54:32 -- DIRECTORY Ascii USING [CR], BodyDefs USING [ItemHeader, ItemLength, maxRNameLength, RName, Timestamp], Heap USING [systemZone], HeapDefs USING [ GetReaderOffset, HeapAbandonWrite, HeapEndRead, HeapEndWrite, HeapReadData, HeapReadRName, HeapStartRead, ObjectNumber, ObjectOffset, objectStart, ReaderHandle, SetReaderOffset, WriterHandle], Inline USING [LowHalf], LocalNameDefs USING [ReadMSName], LogDefs USING [ShowLine], NameInfoDefs USING [IsMemberDirect, Membership], ProtocolDefs USING [AppendTimestamp], ReturnDefs USING [], SendDefs USING [ Abort, AddRecipient, AddToItem, Create, Destroy, CheckValidity, Handle, StartSend, StartText, Send], String USING [AppendChar, AppendDecimal, AppendNumber, AppendString], Time USING [Append, Current, Packed, Unpack], VMDefs USING [AllocatePage, Page, pageSize, Release]; ReturnMail: PROGRAM IMPORTS Heap, HeapDefs, Inline, LocalNameDefs, LogDefs, NameInfoDefs, ProtocolDefs, SendDefs, String, Time, VMDefs EXPORTS ReturnDefs = BEGIN ParseBody: PUBLIC PROCEDURE [ reader: HeapDefs.ReaderHandle, stamp: POINTER TO BodyDefs.Timestamp ← NIL, sender: BodyDefs.RName ← NIL, returnTo: BodyDefs.RName ← NIL] RETURNS [BodyDefs.ItemLength] = BEGIN textStart: HeapDefs.ObjectOffset; textFound: BOOLEAN ← FALSE; textLength: BodyDefs.ItemLength; header: BodyDefs.ItemHeader; found: CARDINAL ← 0; UNTIL found = 4 -- postmark, sender, returnto, text/end -- DO [] ← HeapDefs.HeapReadData[reader, [@header, SIZE[BodyDefs.ItemHeader]]]; BEGIN itemStart: HeapDefs.ObjectOffset = HeapDefs.GetReaderOffset[reader]; found ← found + 1; SELECT header.type FROM PostMark => IF stamp # NIL THEN [] ← HeapDefs.HeapReadData[reader, [stamp, SIZE[BodyDefs.Timestamp]]]; Sender => IF sender # NIL THEN [] ← HeapDefs.HeapReadRName[reader, sender]; ReturnTo => IF returnTo # NIL THEN [] ← HeapDefs.HeapReadRName[reader, returnTo]; Text => BEGIN textStart ← itemStart; textLength ← header.length; textFound ← TRUE; EXIT END; LastItem => NULL; ENDCASE => found ← found - 1; --skip past item-- HeapDefs.SetReaderOffset[reader, itemStart + (1 + header.length) / 2]; END ENDLOOP --each item of a message-- ; IF textFound THEN BEGIN HeapDefs.SetReaderOffset[reader, textStart]; RETURN[textLength] END ELSE RETURN[0] END; CopyItem: PUBLIC PROCEDURE [ reader: HeapDefs.ReaderHandle, length: BodyDefs.ItemLength, sendBlock: PROCEDURE [SendDefs.Handle, LONG POINTER, CARDINAL], sendBlockData: SendDefs.Handle] = BEGIN data: VMDefs.Page = VMDefs.AllocatePage[]; WHILE length > 0 DO BEGIN ENABLE UNWIND => VMDefs.Release[data]; --messy because we're reading words and sending bytes-- wanted: CARDINAL; given: CARDINAL; used: CARDINAL; wanted ← IF VMDefs.pageSize * 2 < length THEN VMDefs.pageSize * 2 ELSE Inline.LowHalf[length]; [, used] ← HeapDefs.HeapReadData[reader, [data, (1 + wanted) / 2]]; given ← MIN[used * 2, wanted]; sendBlock[sendBlockData, data, given]; length ← length - given; END; ENDLOOP; VMDefs.Release[data]; END; AppendOwner: PROC [s: LONG STRING, group: LONG STRING] = BEGIN String.AppendString[s, "Owners-"L]; FOR i: CARDINAL IN [0..MIN[s.maxlength - s.length, group.length]) DO s[s.length] ← group[i]; s.length ← s.length + 1; ENDLOOP; END; BadRecipients: PUBLIC PROC [ list: HeapDefs.WriterHandle, body: HeapDefs.ObjectNumber] = { Return[list, body, user, NIL, NIL, NIL]}; BadGroup: PUBLIC PROC [ list: HeapDefs.WriterHandle, body: HeapDefs.ObjectNumber, group: BodyDefs.RName] = {Return[list, body, owner, group, NIL, NIL]}; LongTerm: PUBLIC PROC [ list: HeapDefs.WriterHandle, body: HeapDefs.ObjectNumber] = { Return[list, body, longTerm, NIL, NIL, NIL]}; NoDeadLetterRName: SIGNAL = CODE; LooksLikeImDown: ERROR = CODE; MyNameIsWrong: ERROR = CODE; maxReturnDays: CARDINAL = 7; -- discard messages older than this -- Return: PROCEDURE [ list: HeapDefs.WriterHandle, body: HeapDefs.ObjectNumber, case: {user, owner, longTerm}, group: BodyDefs.RName, server, text: LONG STRING] = BEGIN OPEN SendDefs; bodyReader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[body]; sendHandle: Handle = Create[]; wsBuffer: STRING = [64]; ws: PROCEDURE [s: LONG STRING] = BEGIN OPEN Ascii; FOR index: CARDINAL IN [0..s.length) DO IF wsBuffer.length = wsBuffer.maxlength --must be even-- THEN BEGIN AddToItem[sendHandle, DESCRIPTOR[@(wsBuffer.text), wsBuffer.length]]; wsBuffer.length ← 0; END; wsBuffer[wsBuffer.length] ← IF s[index] = '~ THEN CR ELSE s[index]; wsBuffer.length ← wsBuffer.length + 1; ENDLOOP; END; FlushWS: PROC = BEGIN IF wsBuffer.length MOD 2 # 0 THEN ws[" "L]; AddToItem[sendHandle, DESCRIPTOR[@(wsBuffer.text), wsBuffer.length]]; wsBuffer.length ← 0; END; wt: PROCEDURE [t: Time.Packed] = BEGIN s: STRING = [30]; Time.Append[s, Time.Unpack[t], TRUE]; ws[s]; END; textLength: BodyDefs.ItemLength; badCount: CARDINAL ← 0; stamp: BodyDefs.Timestamp; returnTo: BodyDefs.RName = [BodyDefs.maxRNameLength]; sender: BodyDefs.RName = [BodyDefs.maxRNameLength]; textLength ← ParseBody[ bodyReader, @stamp, sender, IF case = owner THEN NIL ELSE returnTo]; IF stamp.time + maxReturnDays * 24 * LONG[60 * 60] < Time.Current[] THEN BEGIN HeapDefs.HeapAbandonWrite[list]; LogDiscard[stamp, sender]; END ELSE BEGIN textStart: HeapDefs.ObjectOffset = HeapDefs.GetReaderOffset[bodyReader]; myName: BodyDefs.RName; password: LONG STRING; dead: BodyDefs.RName = "DeadLetter.MS"L; badReader: HeapDefs.ReaderHandle; Start: PROC [validate: BOOLEAN] = BEGIN SELECT StartSend[ handle: sendHandle, sender: myName, senderPwd: password, returnTo: dead, validate: validate] FROM ok => NULL; badReturnTo => ERROR NoDeadLetterRName[]; badSender, badPwd => ERROR MyNameIsWrong[]; allDown => ERROR LooksLikeImDown[] --silly: I'm not down!-- ; ENDCASE => ERROR; END; [myName, password, ] ← LocalNameDefs.ReadMSName[]; IF case = owner THEN AppendOwner[returnTo, group]; Start[TRUE]; AddRecipient[sendHandle, returnTo]; BEGIN bad: BOOLEAN ← FALSE; Notify: PROCEDURE [number: CARDINAL, who: LONG STRING] = BEGIN bad ← TRUE; END; [] ← CheckValidity[sendHandle, Notify]; IF bad THEN BEGIN Abort[sendHandle]; returnTo.length ← 0; String.AppendString[returnTo, dead]; Start[TRUE]; AddRecipient[sendHandle, returnTo]; bad ← FALSE; [] ← CheckValidity[sendHandle, Notify]; IF bad THEN ERROR NoDeadLetterRName[]; END; END; StartText[sendHandle]; ws["Subject: Undelivered mail~From: "L]; ws[myName]; ws[" (a Grapevine mail server)~To: "L]; ws[returnTo]; ws["~Date: "L]; wt[Time.Current[]]; ws["~~The message sent by "L]; ws[sender]; ws[" at "L]; wt[LOOPHOLE[stamp.time]]; ws[" could not be delivered to the following recipients "L]; SELECT case FROM user => ws["because their names are invalid."L]; owner => BEGIN ws["because they are invalid names in the distribution list """L]; ws[group]; ws[""". Please remove them from that list."L]; END; longTerm => ws[ "within a reasonable time, because of network failures or the unavailability of other servers."L]; ENDCASE => ERROR; ws["~~"L]; BEGIN name: BodyDefs.RName = [BodyDefs.maxRNameLength]; ended: BOOLEAN; GetReader: PROCEDURE [obj: HeapDefs.ObjectNumber] = BEGIN badReader ← HeapDefs.HeapStartRead[obj]; END; HeapDefs.HeapEndWrite[list, GetReader]; [ended] ← HeapDefs.HeapReadData[badReader, [name, 0]]; UNTIL ended DO ended ← HeapDefs.HeapReadRName[badReader, name]; badCount ← badCount + 1; ws[name]; IF NOT ended THEN ws[", "L]; ENDLOOP; END; ws["~~"L]; IF case = owner THEN FlushWS[] ELSE BEGIN IF textLength > 0 THEN BEGIN SillySend: PROCEDURE [u: SendDefs.Handle, p: LONG POINTER, c: CARDINAL] = { AddToItem[u, DESCRIPTOR[p, c]]}; ws["----------------~~"L]; IF wsBuffer.length MOD 2 # 0 THEN ws["~"L]; FlushWS[]; CopyItem[bodyReader, textLength, SillySend, sendHandle]; END ELSE {ws["There was no message text."L]; FlushWS[]}; END; LogReturn[stamp, returnTo, badCount]; Send[sendHandle]; BEGIN who: LONG STRING ← "GGWMsgs↑.MS"; IF case # user OR Filter[stamp] THEN who ← dead; Start[FALSE]; AddRecipient[sendHandle, who]; StartText[sendHandle]; ws["Subject: Undelivered mail notification~From: "L]; ws[myName]; ws["~To: "L]; ws[who]; ws["~Date: "L]; wt[Time.Current[]]; ws["~~Reason: "L]; ws[ SELECT case FROM user => "Invalid recipients(s)"L, owner => "Invalid recipients(s) in DL"L, longTerm => "Message time-out"L, ENDCASE => ERROR]; ws["~Notification sent to: "L]; ws[returnTo]; ws["~Original postmark: "L]; wt[LOOPHOLE[stamp.time]]; ws[" = "L]; { s: STRING = [18]; ProtocolDefs.AppendTimestamp[s, stamp]; ws[s]}; ws["~Sender: "L]; ws[sender]; SELECT case FROM user => NULL; owner => {ws["~Group: "L]; ws[group]}; longTerm => NULL; ENDCASE => ERROR; ws["~Undeliverable recipient(s): "L]; HeapDefs.SetReaderOffset[badReader, HeapDefs.objectStart]; FOR i: CARDINAL IN [1..badCount] DO name: BodyDefs.RName = [BodyDefs.maxRNameLength]; IF i # 1 THEN ws[", "L]; [] ← HeapDefs.HeapReadRName[badReader, name]; ws[name]; ENDLOOP; IF textLength > 0 THEN BEGIN length: BodyDefs.ItemLength ← textLength; crSeen: BOOLEAN ← FALSE; -- end on double CR -- ws["~Text header:~~"L]; HeapDefs.SetReaderOffset[bodyReader, textStart]; WHILE length > 0 DO buff: STRING = [64]; wanted: CARDINAL = IF buff.maxlength < length THEN buff.maxlength ELSE Inline.LowHalf[length]; [] ← HeapDefs.HeapReadData[bodyReader, [@buff.text, (wanted + 1) / 2]]; length ← length - wanted; buff.length ← wanted; FOR i: CARDINAL IN [0..buff.length) DO IF buff[i] = Ascii.CR THEN { IF crSeen THEN {buff.length ← i; ws[buff]; GOTO ended} ELSE crSeen ← TRUE} ELSE crSeen ← FALSE; ENDLOOP; ws[buff]; REPEAT ended => NULL; ENDLOOP; END ELSE ws["~No text~"L]; FlushWS[]; Send[sendHandle]; END; HeapDefs.HeapEndRead[badReader]; END; HeapDefs.HeapEndRead[bodyReader]; Destroy[sendHandle]; END; -- Hack to filter out crap because GGW injects invalid recipients Filter: PROC [stamp: BodyDefs.Timestamp] RETURNS [go: BOOLEAN] = BEGIN group: BodyDefs.RName = "GGWFilter↑.MS"L; source: STRING = [8]; m: NameInfoDefs.Membership; String.AppendNumber[source, stamp.net, 8]; String.AppendChar[source, '#]; String.AppendNumber[source, stamp.host, 8]; String.AppendChar[source, '#]; m ← NameInfoDefs.IsMemberDirect[group, source]; IF m = yes THEN RETURN[FALSE]; RETURN[TRUE]; END; LogDiscard: PROC [stamp: BodyDefs.Timestamp, sender: BodyDefs.RName] = BEGIN len: CARDINAL = sender.length; log: LONG STRING ← Heap.systemZone.NEW[StringBody[128]]; String.AppendString[log, "Discarded old "L]; ProtocolDefs.AppendTimestamp[log, stamp]; String.AppendString[log, " Sender="L]; sender.length ← MIN[len, log.maxlength - log.length]; String.AppendString[log, sender]; sender.length ← len; LogDefs.ShowLine[log]; Heap.systemZone.FREE[@log]; END; LogReturn: PROC [ stamp: BodyDefs.Timestamp, returnTo: BodyDefs.RName, badCount: CARDINAL] = BEGIN len: CARDINAL = returnTo.length; log: LONG STRING ← Heap.systemZone.NEW[StringBody[128]]; String.AppendString[log, "Returned "L]; ProtocolDefs.AppendTimestamp[log, stamp]; String.AppendString[log, " to "L]; returnTo.length ← MIN[len, log.maxlength - log.length - 10]; String.AppendString[log, returnTo]; returnTo.length ← len; String.AppendString[log, ": "L]; String.AppendDecimal[log, badCount]; String.AppendString[log, " bad"L]; LogDefs.ShowLine[log]; Heap.systemZone.FREE[@log]; END; END.