<> <> <> <> <> DIRECTORY ArpaSMTPControl USING [deadLetterSenderName, deadLetterPath, itemExpiryTimeout], ArpaSMTPDescr USING [Descr, Format, HostProc, InitialItemProc, PrintForm, RawRecipProc], ArpaSMTPSupport USING [CreateSubrangeStream, Log, LogPriority, Now, RFC822Date, RopeFromSubrange], BasicTime USING [GMT, Now, Period, Update], Convert USING [Error, IntFromRope, RopeFromInt, RopeFromRope, TimeFromRope], FS USING [Copy, Delete, EnumerateForInfo, Error, InfoProc, Rename, StreamOpen], IO USING [BreakProc, Close, EndOfStream, GetBlock, GetIndex, GetLineRope, GetTokenRope, int, Put, PutBlock, PutChar, PutF, PutFR, PutRope, rope, RopeFromROS, ROS, SetIndex, SetLength, STREAM, time], RefText USING [ObtainScratch], Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, Substr]; ArpaSMTPDescrImpl: CEDAR MONITOR IMPORTS ArpaSMTPControl, ArpaSMTPSupport, BasicTime, Convert, FS, IO, RefText, Rope EXPORTS ArpaSMTPDescr = BEGIN <<---- Descriptors ----->> <> Descr: TYPE = REF DescrRep; DescrRep: PUBLIC TYPE = RECORD[ userHandle: INT, source: ROPE, format: ArpaSMTPDescr.Format, arpaReversePath: ROPE, gvSender: ROPE, recipients: RecipientList, createDate: BasicTime.GMT, expiryDate: BasicTime.GMT, precedeMsgText: ROPE, returnPathLine: ROPE, fileName: ROPE, infoStartIndex: INT]; RecipientList: TYPE = REF RecipientListRep; RecipientState: TYPE = {raw, processed}; RecipientListRep: TYPE = RECORD[ val: SELECT state: RecipientState FROM raw => [raw: LIST OF ROPE], processed => [processed: LIST OF HostAndUsers], ENDCASE]; HostAndUsers: TYPE = RECORD[host: ROPE, users: LIST OF ROPE]; valuesRecipientState: ARRAY RecipientState OF ROPE = [raw: "raw", processed: "processed"]; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; twoDays: INT = INT[2]*24*60*60; Create: PUBLIC PROC [ arpaReversePath: ROPE _ NIL, gvSender: ROPE _ NIL, rawRecipients: LIST OF ROPE, source: ROPE _ NIL, format: ArpaSMTPDescr.Format, msgStream: STREAM, precedeMsgText: ROPE _ NIL, returnPathLine: ROPE _ NIL] RETURNS [descr: Descr] = TRUSTED { expiryDate: BasicTime.GMT; expiryDate _ BasicTime.Update[BasicTime.Now[], ArpaSMTPControl.itemExpiryTimeout]; descr _ NEW[DescrRep _ [ userHandle: UniqueUserHandle[], arpaReversePath: arpaReversePath, gvSender: gvSender, recipients: NEW[raw RecipientListRep _[raw[rawRecipients]]], createDate: BasicTime.Now[], expiryDate: expiryDate, source: source, format: format, precedeMsgText: precedeMsgText, returnPathLine: returnPathLine, fileName: UniqueFileName[], infoStartIndex: -1 --undefined--]]; StoreEntireItem[descr, msgStream -- includes sucking message from msgStream ! BadItemFile => ERROR CreateFailed]; }; -- end Create CopyForReturn: PUBLIC PROC [old: Descr, reason: ROPE] RETURNS [new: Descr] = { newFileName: ROPE = UniqueFileName[]; expiryDate: BasicTime.GMT; sender: ROPE; date: ROPE _ Rope.Cat["Date: ", ArpaSMTPSupport.RFC822Date[BasicTime.Now[]], "\n"]; from: ROPE; to: ROPE; subject: ROPE _ "Subject: Undeliverable mail\n\n"; header: ROPE; arpaReversePath: ROPE; <> [] _ FS.Copy[old.fileName, newFileName ! FS.Error => { ArpaSMTPSupport.Log[ important, "Failed to copy file ", old.fileName, " to ", newFileName, "\nwhen trying to create new descriptor to return item to sender.\n", "FS said ", error.explanation, "."]; ERROR CreateFailed}]; <> SELECT old.format FROM gv => { sender _ old.gvSender; from _ ArpaSMTPControl.deadLetterSenderName; -- Mailer.PA IF Rope.Find[sender, "@"] # -1 THEN <> from _ ArpaSMTPControl.deadLetterPath; -- Mailer.PA@Xerox.ARPA arpaReversePath _ ArpaSMTPControl.deadLetterPath; }; arpa => { sender _ old.arpaReversePath; from _ ArpaSMTPControl.deadLetterPath; arpaReversePath _ ArpaSMTPControl.deadLetterSenderName; }; -- SMTPSendImpl will add @Xerox ENDCASE => ERROR; IF sender.Fetch[0] # '@ THEN to _ Rope.Cat["To: ", sender, "\n"] ELSE to _ Rope.Cat["To: RFC822.Requires.Something.Here <", sender, ">\n"]; from _ Rope.Cat["From: ", from, "\n"]; header _ Rope.Cat[date, from, to, subject, reason]; IF old.returnPathLine # NIL THEN header _ Rope.Cat[header, old.returnPathLine, "\n"]; expiryDate _ BasicTime.Update[BasicTime.Now[], ArpaSMTPControl.itemExpiryTimeout]; new _ NEW[DescrRep _ [ userHandle: UniqueUserHandle[], arpaReversePath: arpaReversePath, gvSender: ArpaSMTPControl.deadLetterSenderName, recipients: NEW[raw RecipientListRep _ [raw[LIST[sender]]]], createDate: BasicTime.Now[], expiryDate: expiryDate, source: NIL, format: old.format, precedeMsgText: Rope.Cat[header, old.precedeMsgText], returnPathLine: NIL, fileName: newFileName, infoStartIndex: old.infoStartIndex]]; StoreItemInfo[new]; }; -- end CopyForReturn <> GetUserHandle: PUBLIC PROC [self: Descr] RETURNS [INT] = { RETURN[self.userHandle] }; GetFileName: PUBLIC PROC [self: Descr] RETURNS [ROPE] = { RETURN[self.fileName]; }; GetExpiryDate: PUBLIC PROC [self: Descr] RETURNS [BasicTime.GMT] = { RETURN[self.expiryDate] }; GetArpaReversePath: PUBLIC PROC [self: Descr] RETURNS [path: ROPE] = { RETURN[self.arpaReversePath]; }; SetArpaReversePath: PUBLIC PROC [self: Descr, path: ROPE] = { self.arpaReversePath _ path; }; GetGvSender: PUBLIC PROC [self: Descr] RETURNS [rName: ROPE] = { RETURN[self.gvSender]; }; SetGvSender: PUBLIC PROC [self: Descr, rName: ROPE] = { self.gvSender _ rName; }; EnumerateRawRecipients: PUBLIC PROC [self: Descr, proc: ArpaSMTPDescr.RawRecipProc, procData: REF ANY _ NIL] = { recipients: LIST OF ROPE; IF NOT ISTYPE[self.recipients, REF raw RecipientListRep] THEN ERROR WrongState; recipients _ NARROW[self.recipients, REF raw RecipientListRep].raw; FOR restRecips: LIST OF ROPE _ recipients, restRecips.rest UNTIL restRecips = NIL DO IF NOT proc[restRecips.first, procData] THEN RETURN; ENDLOOP; }; RemoveRawRecipients: PUBLIC PROC [self: Descr] = { IF NOT ISTYPE[self.recipients, REF raw RecipientListRep] THEN ERROR WrongState; NARROW[self.recipients, REF raw RecipientListRep].raw _ NIL; -- test this StoreItemInfo[self]; }; AddProcessedRecipient: PUBLIC PROC [self: Descr, hostName: ROPE, userName: ROPE, checkDuplicate: BOOL] = { WITH self.recipients SELECT FROM raw: REF raw RecipientListRep => <> self.recipients _ NEW[processed RecipientListRep _ [processed[LIST[HostAndUsers[hostName, LIST[userName]]]]]]; processed: REF processed RecipientListRep => { <> hostList: LIST OF HostAndUsers _ processed.processed; FOR restHosts: LIST OF HostAndUsers _ hostList, restHosts.rest UNTIL restHosts = NIL DO hostAndUsers: HostAndUsers = restHosts.first; IF Rope.Equal[hostAndUsers.host, hostName, FALSE] THEN { <> userNames: LIST OF ROPE _ hostAndUsers.users; IF checkDuplicate THEN <> FOR userList: LIST OF ROPE _ userNames, userList.rest UNTIL userList = NIL DO IF Rope.Equal[userList.first, userName] THEN EXIT; REPEAT FINISHED => userNames _ CONS[userName, userNames]; ENDLOOP ELSE -- just add this user name at end userNames _ CONS[userName, userNames]; restHosts.first.users _ userNames; EXIT; }; REPEAT FINISHED => -- not yet a HostAndUsers element for this host; create one processed.processed _ CONS[HostAndUsers[hostName, LIST[userName]], hostList]; ENDLOOP; }; ENDCASE; }; -- end AddProcessedRecipient RemoveRecipientHost: PUBLIC PROC [self: Descr, hostName: ROPE] = { recipients: REF processed RecipientListRep; hostList: LIST OF HostAndUsers; IF NOT ISTYPE[self.recipients, REF processed RecipientListRep] THEN ERROR WrongState; recipients _ NARROW[self.recipients, REF processed RecipientListRep]; hostList _ recipients.processed; IF Rope.Equal[hostList.first.host, hostName, FALSE] THEN hostList _ hostList.rest ELSE { lastList: LIST OF HostAndUsers _ hostList; FOR list: LIST OF HostAndUsers _ hostList.rest, list.rest UNTIL list = NIL DO IF Rope.Equal[list.first.host, hostName, FALSE] THEN {lastList.rest _ list.rest; EXIT}; lastList _ list; ENDLOOP; }; recipients.processed _ hostList; self.recipients _ recipients; StoreItemInfo[self]; }; EnumerateRecipientHosts: PUBLIC PROC [self: Descr, proc: ArpaSMTPDescr.HostProc, procData: REF ANY _ NIL] = { recipients: LIST OF HostAndUsers; IF NOT ISTYPE[self.recipients, REF processed RecipientListRep] THEN ERROR WrongState; recipients _ NARROW[self.recipients, REF processed RecipientListRep].processed; FOR restHostRecipients: LIST OF HostAndUsers _ recipients, restHostRecipients.rest UNTIL restHostRecipients = NIL DO IF NOT proc[restHostRecipients.first.host, restHostRecipients.first.users, procData] THEN RETURN; ENDLOOP; }; MoreRecipients: PUBLIC PROC [self: Descr] RETURNS [BOOL] = { WITH self.recipients SELECT FROM raw: REF raw RecipientListRep => RETURN[raw.raw # NIL]; processed: REF processed RecipientListRep => RETURN[processed.processed # NIL]; ENDCASE => ERROR; }; <> GetFormat: PUBLIC PROC [self: Descr] RETURNS [ArpaSMTPDescr.Format] = { RETURN[self.format]; }; GetPrecedeMsgText: PUBLIC PROC [self: Descr] RETURNS [ROPE] = { RETURN[self.precedeMsgText]; }; SetPrecedeMsgText: PUBLIC PROC [self: Descr, new: ROPE] = { self.precedeMsgText _ new; }; GetReturnPathLine: PUBLIC PROC [self: Descr] RETURNS [ROPE] = { RETURN[self.returnPathLine]; }; GetSource: PUBLIC PROC [self: Descr] RETURNS [ROPE] = { IF self.source.Fetch[0] = '[ THEN RETURN[Rope.Cat[self.source, ".ARPA"]]; RETURN[self.source]; }; RetrieveMsgStream: PUBLIC PROC [self: Descr] RETURNS [STREAM] = { RETURN[OpenMsgFileStream[self.fileName, self.infoStartIndex ! BadItemFile => ERROR PutOnBadQueue]]; }; UniqueID: PUBLIC PROC [self: Descr] RETURNS [rope: ROPE] = { bang: INT; rope _ self.fileName; rope _ Rope.Substr[rope, itemFileNamePrefixLength]; -- Strip off leading "[]<>MG>Q>Item-" bang _ Rope.Find[rope, "!"]; IF bang = -1 THEN RETURN; rope _ Rope.Substr[rope, 0, bang-1]; }; Destroy: PUBLIC PROC[self: Descr] = { FS.Delete[self.fileName]; }; Print: PUBLIC PROC [descr: Descr, out: STREAM, form: ArpaSMTPDescr.PrintForm _ long] = { PutLine: PROC [r1, r2, r3, r4: ROPE _ NIL] = { out.PutRope[r1]; out.PutRope[r2]; out.PutRope[r3]; out.PutRope[r4]; out.PutChar['\n]; }; out.PutF["#%g (%g)\n", IO.int[descr.userHandle], IO.rope[descr.fileName]]; <<===== Recipients =====>> WITH descr.recipients SELECT FROM raw: REF raw RecipientListRep => { out.PutRope["Recipients (raw): "]; FOR names: LIST OF ROPE _ raw.raw, names.rest UNTIL names = NIL DO out.PutRope[names.first]; IF names.rest # NIL THEN out.PutRope[", "]; ENDLOOP; out.PutChar['\n]; }; processed: REF processed RecipientListRep => { out.PutRope["Recipients (processed): "]; FOR hosts: LIST OF HostAndUsers _ processed.processed, hosts.rest UNTIL hosts = NIL DO hostAndUsers: HostAndUsers = hosts.first; out.PutRope[hostAndUsers.host]; out.PutRope[": "]; FOR users: LIST OF ROPE _ hostAndUsers.users, users.rest UNTIL users = NIL DO out.PutRope[users.first]; IF users.rest # NIL THEN out.PutRope[", "]; ENDLOOP; IF hosts.rest # NIL THEN out.PutRope["; "]; ENDLOOP; out.PutChar['\n]; }; ENDCASE; <<===== Return Info =====>> PutLine["Arpa Reverse Path: ", descr.arpaReversePath]; PutLine["GV Sender: ", descr.gvSender]; <<===== Dates =====>> out.PutF["Create Date: %g\n", IO.time[descr.createDate]]; out.PutF["Expiry Date: %g\n", IO.time[descr.expiryDate]]; <<===== Item Format =====>> PutLine["Item Format: ", valuesFormat[descr.format]]; <<===== Precede Message Text =====>> PutLine["Precede Message Text: ", Convert.RopeFromRope[descr.precedeMsgText, TRUE]]; <<===== Return Path Line =====>> PutLine["Return Path Line: ", Convert.RopeFromRope[descr.returnPathLine, TRUE]]; }; -- end Print Unparse: PUBLIC PROC [descr: Descr, form: ArpaSMTPDescr.PrintForm _ short] RETURNS [ROPE] = { IF form = short THEN RETURN[IO.PutFR["Msg #%g (%g)", IO.int[descr.userHandle], IO.rope[descr.fileName]]] ELSE { printStream: STREAM = IO.ROS[]; Print[descr, printStream, long]; RETURN[IO.RopeFromROS[printStream]]; }; }; CreateFailed: PUBLIC ERROR = CODE; WrongState: PUBLIC ERROR = CODE; PutOnBadQueue: PUBLIC ERROR = CODE; <<----- Filing System ----->> itemFileNamePrefix: ROPE = "[]<>MG>Q>Item-"; itemFileNamePrefixLength: INT = Rope.Length[itemFileNamePrefix]; <> EnumerateInitialItems: PUBLIC PROC [proc: ArpaSMTPDescr.InitialItemProc] = { ForEachItemFileDo: FS.InfoProc = { <> fileOK: BOOL _ TRUE; simpleStart: INT; fileName: ROPE; descr: Descr; continue _ TRUE; <> IF BasicTime.Period[from: enumerationStarted, to: created] >= 0 THEN RETURN; IF Rope.Find[fullFName, "$"] > 0 THEN { -- Crash during reception FS.Delete[fullFName]; RETURN; }; <> simpleStart _ Rope.Find[fullFName, itemFileNamePrefix, 0, FALSE]; IF simpleStart < 0 THEN RETURN; -- shouldn't occur fileName _ Rope.Substr[fullFName, simpleStart]; descr _ RetrieveItemInfo[fileName ! BadItemFile => {fileOK _ FALSE; CONTINUE}]; IF fileOK THEN proc[descr]; RETURN; }; enumerationStarted: BasicTime.GMT = BasicTime.Now[]; FS.EnumerateForInfo[Rope.Concat[itemFileNamePrefix, "*!H"], ForEachItemFileDo]; }; ReadItemFile: PUBLIC PROC [fileName: ROPE] RETURNS [Descr] = { RETURN[RetrieveItemInfo[fileName ! BadItemFile => ERROR Failed[Rope.Cat["Unable to read ", fileName, " as a valid item file. (See log for more details.)"]]]]; }; Failed: PUBLIC ERROR [reason: ROPE] = CODE; <> titleInfoStartIndex: ROPE = "-------- Item Info starts at Position --------"; titleMsgBody: ROPE = "-------- Message Body --------"; titleRecipients: ROPE = "-------- Recipients --------"; titleInfoState: ROPE = "State:"; titleRecipientNames: ROPE = "Names:"; titleRecipientHost: ROPE = "Host:"; titleRecipientUsers: ROPE = "Users:"; titleReturnInfo: ROPE = "-------- Return Info --------"; titleReversePathVal: ROPE = "Path:"; titleDates: ROPE = "-------- Dates --------"; titleFormat: ROPE = "-------- Source and Format --------"; titlePrecedeMsgText: ROPE = "-------- Precede Message Text --------"; titleReturnPathLine: ROPE = "-------- Return Path Line --------"; valueStateRaw: ROPE = "raw"; valueStateProcessed: ROPE = "processed"; numPlaceHolder: ROPE = " "; -- Big enough for length of longest message placeHolderFormat: ROPE = IO.PutFR["%%%dd", IO.int[Rope.Length[numPlaceHolder]]]; valuesFormat: ARRAY ArpaSMTPDescr.Format OF ROPE _ [gv: "GV", arpa: "ARPA"]; StoreEntireItem: PROC [descr: Descr, msgStream: STREAM] = { tempName: ROPE = Rope.Cat[descr.fileName, "$"]; itemFile: STREAM _ NIL; BEGIN ENABLE { FS.Error => { ArpaSMTPSupport.Log[important, "Unable to open stream/store entire item to \"", descr.fileName, "\".\nFS reported \"", error.explanation, "\".\nI will reject the incoming mail item."]; ERROR BadItemFile; }; UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; FS.Delete[tempName]}; }; PutLine: PROC [line: ROPE] = INLINE {itemFile.PutRope[line]; itemFile.PutChar['\n]}; indexIndex, nBytesRead: INT; buffer: REF TEXT; itemFile _ FS.StreamOpen[fileName: tempName, accessOptions: create]; <<===== Info Start Index =====>> PutLine[titleInfoStartIndex]; indexIndex _ itemFile.GetIndex[]; PutLine[numPlaceHolder]; <<===== Message Body =====>> PutLine[titleMsgBody]; buffer _ RefText.ObtainScratch[512]; DO nBytesRead _ msgStream.GetBlock[buffer]; -- catch stream failure signal (globally?) itemFile.PutBlock[block: buffer, count: nBytesRead]; IF nBytesRead < buffer.maxLength THEN EXIT; ENDLOOP; <<===== Info Start Index (continued) =====>> descr.infoStartIndex _ itemFile.GetIndex[]; itemFile.SetIndex[indexIndex]; itemFile.PutF[placeHolderFormat, IO.int[descr.infoStartIndex]]; itemFile.SetIndex[descr.infoStartIndex]; StoreItemInfoOnFileStream[descr, itemFile]; itemFile.Close[]; END; FS.Rename[from: tempName, to: descr.fileName]; }; -- end StoreEntireItem LookAtThis: SIGNAL = CODE; StoreItemInfo: PUBLIC PROC [descr: Descr] = { itemFile: STREAM _ NIL; BEGIN ENABLE { FS.Error => { IF error.group = lock THEN { ArpaSMTPSupport.Log[ important, "Unable to update item file (lock conflict): ", descr.fileName, ".\nFS reported: ", error.explanation, "This happens when we send a msg to GV and ARPA at the same time. Plunging on."]; CONTINUE; }; SIGNAL LookAtThis; ArpaSMTPSupport.Log[ ATTENTION, "Unable to open/update item file: ", descr.fileName, ".\nFS reported: ", error.explanation, "\nContinuing without saving the change (message may be multiply sent)."]; IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; CONTINUE; }; UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; }; <<>> itemFile _ FS.StreamOpen[fileName: descr.fileName, accessOptions: write]; StoreItemInfoOnFileStream[descr, itemFile]; END; }; StoreItemInfoOnFileStream: PROC [descr: Descr, itemFile: STREAM] = { Newline: PROC = INLINE {itemFile.PutChar['\n]}; PutLine: PROC [line: ROPE] = INLINE {itemFile.PutRope[line]; Newline[]}; endIndex, textEndIndexIndex: INT; itemFile.SetIndex[descr.infoStartIndex]; <<>> <<===== Recipients =====>> PutLine[NIL]; -- separate from msg w/newline PutLine[titleRecipients]; PutLine[titleInfoState]; WITH descr.recipients SELECT FROM raw: REF raw RecipientListRep => { numNames: INT _ 0; numNamesIndex: INT; PutLine[valuesRecipientState[raw]]; PutLine[titleRecipientNames]; numNamesIndex _ itemFile.GetIndex[]; PutLine[numPlaceHolder]; FOR names: LIST OF ROPE _ raw.raw, names.rest UNTIL names = NIL DO PutLine[names.first]; numNames _ numNames + 1; ENDLOOP; endIndex _ itemFile.GetIndex[]; itemFile.SetIndex[numNamesIndex]; itemFile.PutF[placeHolderFormat, IO.int[numNames]]; itemFile.SetIndex[endIndex]; }; processed: REF processed RecipientListRep => { numHosts: INT _ 0; numHostsIndex: INT; PutLine[valuesRecipientState[processed]]; numHostsIndex _ itemFile.GetIndex[]; PutLine[numPlaceHolder]; FOR hosts: LIST OF HostAndUsers _ processed.processed, hosts.rest UNTIL hosts = NIL DO numUsers: INT _ 0; numUsersIndex: INT; hostAndUsers: HostAndUsers = hosts.first; PutLine[titleRecipientHost]; PutLine[hostAndUsers.host]; PutLine[titleRecipientUsers]; numUsersIndex _ itemFile.GetIndex[]; PutLine[numPlaceHolder]; FOR users: LIST OF ROPE _ hostAndUsers.users, users.rest UNTIL users = NIL DO PutLine[users.first]; numUsers _ numUsers + 1; ENDLOOP; endIndex _ itemFile.GetIndex[]; itemFile.SetIndex[numUsersIndex]; itemFile.PutF[placeHolderFormat, IO.int[numUsers]]; itemFile.SetIndex[endIndex]; numHosts _ numHosts + 1; ENDLOOP; endIndex _ itemFile.GetIndex[]; itemFile.SetIndex[numHostsIndex]; itemFile.PutF[placeHolderFormat, IO.int[numHosts]]; itemFile.SetIndex[endIndex]; }; ENDCASE; <<===== Return Info =====>> PutLine[titleReturnInfo]; PutLine[descr.arpaReversePath]; PutLine[descr.gvSender]; <<===== Dates =====>> PutLine[titleDates]; itemFile.Put[IO.time[descr.createDate]]; Newline[]; itemFile.Put[IO.time[descr.expiryDate]]; Newline[]; <<===== Source and Format =====>> PutLine[titleFormat]; PutLine[descr.source]; PutLine[valuesFormat[descr.format]]; <<===== Precede Message Text =====>> PutLine[titlePrecedeMsgText]; textEndIndexIndex _ itemFile.GetIndex[]; PutLine[numPlaceHolder]; itemFile.PutRope[descr.precedeMsgText]; -- may contain newlines endIndex _ itemFile.GetIndex[]; itemFile.SetIndex[textEndIndexIndex]; itemFile.PutF[placeHolderFormat, IO.int[endIndex]]; itemFile.SetIndex[endIndex]; PutLine[NIL]; -- ensure separated from next field by newline <<>> <<===== Return Path Line =====>> PutLine[titleReturnPathLine]; itemFile.PutRope[descr.returnPathLine]; -- may contain newlines itemFile.SetLength[itemFile.GetIndex[]]; itemFile.Close[]; }; -- end StoreItemInfo <> OpenMsgFileStream: PROC [fileName: ROPE, maxMsgIndex: INT] RETURNS [STREAM] = { itemFile: STREAM _ NIL; BEGIN ENABLE { FS.Error => { ArpaSMTPSupport.Log[ATTENTION, "Unable to open stream/read message body from \"", fileName, "\".\nFS reported \"", error.explanation, "\".\nI will place descriptor on BadQueue. ", "Edit file and Requeue descriptor."]; IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; ERROR BadItemFile; }; BadItemFile => { ArpaSMTPSupport.Log[ATTENTION, "Corrupt item file \"", fileName, "\", at Position ", Convert.RopeFromInt[position], ".\nExpected \"", expected, "\", but found \"", found, "\".\nI will place descriptor on BadQueue. Edit file and Requeue descriptor."]; IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; ERROR BadItemFile; }; UNWIND => IF itemFile # NIL THEN itemFile.Close[]; }; GetLine: PROC [expected: ROPE] RETURNS [ROPE] = { RETURN[itemFile.GetLineRope[! IO.EndOfStream => ERROR BadItemFile[expected, "EndOfFile", itemFile.GetIndex[]]]] }; -- catches all EOFs since all reading done here CheckLine: PROC [expected: ROPE] = { found: ROPE = GetLine[expected]; IF NOT Rope.Equal[expected, found] THEN ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]; }; itemFile: IO.STREAM _ FS.StreamOpen[fileName]; <<>> <<===== Info Start Index =====>> CheckLine[titleInfoStartIndex]; [] _ GetLine["Info Start Index"]; <<===== Message Body =====>> CheckLine[titleMsgBody]; RETURN[ArpaSMTPSupport.CreateSubrangeStream[ origStream: itemFile, min: itemFile.GetIndex[], max: maxMsgIndex]]; END; }; -- end OpenMsgFileStream RetrieveItemInfo: PUBLIC PROC [fileName: ROPE] RETURNS [descr: Descr] = { itemFile: STREAM _ NIL; BEGIN ENABLE { FS.Error => { ArpaSMTPSupport.Log[ATTENTION, "Unable to open/read item file \"", fileName, "\".\nFS reported \"", error.explanation, "\".\nEdit file and InitRead descriptor."]; IF itemFile # NIL THEN itemFile.Close[]; ERROR BadItemFile; -- caught by EnumerateInitialItems and ReadItemFile }; BadItemFile => { ArpaSMTPSupport.Log[ATTENTION, "Corrupt item file \"", fileName, "\", at Position ", Convert.RopeFromInt[position], ".\nExpected \"", expected, "\", but found \"", found, "\".\nIgnoring it; edit it and reread."]; IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; ERROR BadItemFile; -- caught by EnumerateInitialItems and ReadItemFile }; UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; itemFile _ NIL}; }; GetLine: PROC [expected: ROPE] RETURNS [ROPE] = { RETURN[itemFile.GetLineRope[! IO.EndOfStream => ERROR BadItemFile[expected, "EndOfFile", itemFile.GetIndex[]]]] }; -- catches all EOFs since all reading done here CheckLine: PROC [expected: ROPE] = { found: ROPE = GetLine[expected]; IF NOT Rope.Equal[expected, found] THEN ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]; }; GetInt: PROC [intName: ROPE] RETURNS [INT] = { found: ROPE = GetLine[intName]; RETURN[Convert.IntFromRope[found ! Convert.Error => ERROR BadItemFile[intName, found, itemFile.GetIndex[]-(found.Length[]+1)]]]; }; GetRecipientState: PROC RETURNS [RecipientState] = { expected: ROPE = "info state value (\"raw\" or (\"processed\")"; state: ROPE = GetLine[expected]; IF Rope.Equal[state, valuesRecipientState[raw]] THEN RETURN[raw]; IF Rope.Equal[state, valuesRecipientState[processed]] THEN RETURN[processed]; ERROR BadItemFile[expected, state, itemFile.GetIndex[]-(found.Length[]+1)]; }; EverythingProc: IO.BreakProc = {RETURN[other]}; recipients: RecipientList; arpaReversePath, gvSender: ROPE; createDate, expiryDate: BasicTime.GMT; source: ROPE; format: ArpaSMTPDescr.Format; returnPathLine: ROPE; precedeMsgText: ROPE; infoStartIndex: INT; textEndIndex: INT; expected, found: ROPE; itemFile _ FS.StreamOpen[fileName: fileName]; <<===== Info Start Index =====>> itemFile.SetIndex[0]; CheckLine[titleInfoStartIndex]; infoStartIndex _ GetInt["Info Start Index"]; itemFile.SetIndex[infoStartIndex]; <<===== Recipients =====>> CheckLine[NIL]; -- separated from msg w/newline CheckLine[titleRecipients]; CheckLine[titleInfoState]; SELECT GetRecipientState[] FROM raw => { names: LIST OF ROPE _ NIL; CheckLine[titleRecipientNames]; THROUGH [0..GetInt["number of recipient names (INT)"]) DO names _ CONS[GetLine["recipient name"], names]; ENDLOOP; recipients _ NEW[raw RecipientListRep _ [raw[names]]]; }; processed => { hosts: LIST OF HostAndUsers _ NIL; THROUGH [0..GetInt["number of recipient hosts (INT)"]) DO host: ROPE; users: LIST OF ROPE _ NIL; CheckLine[titleRecipientHost]; host _ GetLine["recipient host name"]; CheckLine[titleRecipientUsers]; THROUGH [0..GetInt["number of users for given host (INT)"]) DO users _ CONS[GetLine["recipient user name"], users]; ENDLOOP; hosts _ CONS[HostAndUsers[host, users], hosts]; ENDLOOP; recipients _ NEW[processed RecipientListRep _ [processed[hosts]]]; }; ENDCASE; <<===== Return Info =====>> CheckLine[titleReturnInfo]; expected _ "ARPA Return Path"; arpaReversePath _ GetLine[expected]; IF arpaReversePath.Length[] = 0 THEN arpaReversePath _ NIL; expected _ "GV Sender"; gvSender _ GetLine[expected]; IF gvSender.Length[] = 0 THEN gvSender _ NIL; <<===== Dates =====>> CheckLine[titleDates]; expected _ "Create date"; found _ GetLine[expected]; createDate _ Convert.TimeFromRope[found ! Convert.Error => ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]]; expected _ "Expiry date"; found _ GetLine[expected]; expiryDate _ Convert.TimeFromRope[found ! Convert.Error => ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]]; <<>> <<===== Item Format =====>> CheckLine[titleFormat]; expected _ "Source IP Address ([nnn.nnn.nnn.nnn])"; source _ GetLine[expected]; expected _ "item format (\"ARPA\" or \"GV\")"; found _ GetLine[expected]; SELECT TRUE FROM Rope.Equal[found, valuesFormat[arpa]] => format _ arpa; Rope.Equal[found, valuesFormat[gv]] => format _ gv; ENDCASE => ERROR BadItemFile[expected, found, itemFile.GetIndex[]-found.Length[]-1]; <<===== Precede Message Text =====>> CheckLine[titlePrecedeMsgText]; textEndIndex _ GetInt["Precede Msg Text end index"]; precedeMsgText _ ArpaSMTPSupport.RopeFromSubrange[ -- Empty => NIL origStream: itemFile, min: itemFile.GetIndex[], max: textEndIndex]; CheckLine[NIL]; <<===== Return Path Line =====>> CheckLine[titleReturnPathLine]; returnPathLine _ NIL; -- In case of empty returnPathLine _ itemFile.GetTokenRope[EverythingProc ! IO.EndOfStream => CONTINUE].token; itemFile.Close[]; descr _ NEW[DescrRep _ [ userHandle: UniqueUserHandle[], arpaReversePath: arpaReversePath, gvSender: gvSender, recipients: recipients, createDate: createDate, expiryDate: expiryDate, source: source, format: format, precedeMsgText: precedeMsgText, returnPathLine: returnPathLine, fileName: fileName, infoStartIndex: infoStartIndex]]; RETURN[descr]; END; }; BadItemFile: ERROR [expected, found: ROPE _ NIL, position: INT _ 0] = CODE; <> UniqueUserHandle: ENTRY PROC RETURNS [INT] = { nextUserHandle _ nextUserHandle + 1; RETURN[nextUserHandle]; }; UniqueFileName: ENTRY PROC RETURNS [ROPE] = { timeLiteral: ROPE; [timeLiteral, ] _ ArpaSMTPSupport.Now[compressed: TRUE]; timeStampPostfix _ timeStampPostfix + 1; RETURN[Rope.Cat[ itemFileNamePrefix, timeLiteral, "-", Convert.RopeFromInt[timeStampPostfix]]]; }; timeStampPostfix: INT _ 1000; nextUserHandle: INT _ 1000; END.