-- Transport Mechanism Mail Server - Return Undeliverable Mail --

-- [Indigo]<Grapevine>MS>ReturnMail.mesa

-- 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 ],
HeapDefs	USING[ GetReaderOffset, HeapAbandonWrite, HeapEndRead,
                       HeapEndWrite, HeapReadData, HeapReadRName,
                       HeapStartRead, ObjectNumber, ObjectOffset,
                       objectStart, ReaderHandle, SetReaderOffset,
		       WriterHandle ],
Inline		USING[ LowHalf ],
LocalNameDefs	USING[ ReadMSName ],
LogDefs		USING[ WriteLogEntry ],
ProtocolDefs	USING[ AppendTimestamp ],
ReturnDefs	USING[] --EXPORT only--,
SendDefs	USING[ Abort, AddRecipient, AddToItem, Create, Destroy,
                       CheckValidity, Handle, StartSend, StartText, Send ],
String		USING[ AppendDecimal, AppendString ],
Time		USING[ Append, Current, Packed, Unpack ],
VMDefs		USING[ AllocatePage, Page, pageSize, Release ];

ReturnMail: PROGRAM
   IMPORTS HeapDefs, Inline, LocalNameDefs, LogDefs,
           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[UNSPECIFIED, POINTER, CARDINAL],
             sendBlockData: UNSPECIFIED ] =
   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: STRING, group: 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] };

RejectedByMTP: PUBLIC PROC[list: HeapDefs.WriterHandle,
                           body: HeapDefs.ObjectNumber,
                           server: STRING,
                           text: STRING] =
   { Return[list, body, mtp, NIL, server, text] };

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, mtp, longTerm },
                   group: BodyDefs.RName,
                   server, text: STRING ] =
   BEGIN OPEN SendDefs;
   bodyReader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[body];
   sendHandle: Handle = Create[];
   wsBuffer: STRING = [64];
   ws: PROCEDURE[s: 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: 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: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;
          mtp =>
            BEGIN
            ws["because they were rejected by the MTP server """L];
            ws[server]; ws[""".  "L];
            IF text = NIL
            THEN ws["No reason was given."L]
            ELSE { ws["The reason given was: "L]; ws[text] };
            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:UNSPECIFIED, p: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
           -- Send summary to DeadLetter.ms --
           Start[FALSE];
           AddRecipient[sendHandle,dead];
           StartText[sendHandle];
           ws["Subject: Undelivered mail notification~From: "L];
           ws[myName];
           ws["~To: "L];
           ws[dead];
           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,
                mtp => "Name(s) rejected by MTP server"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] }; 
             mtp => { ws["~MTP host: "L]; ws[server];
                      ws["~MTP complaint: "L];
                      ws[IF text = NIL THEN "none"L ELSE text] };
             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;

LogDiscard: PROC[stamp: BodyDefs.Timestamp, sender: BodyDefs.RName] =
   BEGIN
   len: CARDINAL = sender.length;
   log: STRING = [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.WriteLogEntry[log];
   END;

LogReturn: PROC[stamp: BodyDefs.Timestamp, returnTo: BodyDefs.RName,
                badCount: CARDINAL] =
   BEGIN
   len: CARDINAL = returnTo.length;
   log: STRING = [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.WriteLogEntry[log];
   END;

END.