DIRECTORY
BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds],
Convert USING [RopeFromInt],
FS USING [Delete, Error, StreamOpen],
IO USING [Close, EndOf, EndOfStream, Error, Flush, GetLength, GetLine, GetChar, GetIndex, GetLineRope, int, PutChar, PutF, PutRope, PutText, RIS, rope, RopeFromROS, ROS, STREAM],
RefText USING [Fetch, Length, ObtainScratch],
Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, Substr],
TypeScript USING [ChangeLooks],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
IPDefs USING [Address],
IPName USING [AddressToRope, LoadCacheFromName, NameState, NameToAddress, Source],
IPRouter USING [BestAddress],
MT USING [TranslateMessage],
SMTPControl USING [arpaMSPort, xeroxDomain],
SMTPDescr USING [Descr, GetFormat, GetArpaReversePath, GetPrecedeMsgText, RetrieveMsgStream, UniqueID, Unparse],
SMTPSend USING [WithItemAction],
SMTPSupport USING [CreateSubrangeStream, HeaderParseError, Log],
SMTPSyntax USING [EnumerateGVItems, GVItemProc],
SMTPQueue USING [CountQueue],
TCP USING [AbortTCPStream, CreateTCPStream, Error, ErrorFromStream, Reason, TCPInfo, Timeout];
SMTPSendImpl: CEDAR PROGRAM
IMPORTS
BasicTime, Convert, FS, IO, IPName, IPRouter, RefText, Rope, TypeScript, ViewerIO,
MT, SMTPControl, SMTPDescr, SMTPSupport, SMTPSyntax, SMTPQueue, TCP
EXPORTS SMTPSend =
BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Descr: TYPE = SMTPDescr.Descr;
Connection: TYPE = REF ConnectionRep; -- so it can be opaque
ConnectionRep: PUBLIC TYPE = RECORD[
stream: STREAM, 
start: BasicTime.Pulses,
used: BOOLEAN,
bytes: INT,
addr: IPDefs.Address,
name: ROPE];

timeOutSwitch: INT _ 5;  -- number of hosts in ARPA queue for long timeout
timeOut: INT _ shortTimeOut;
shortTimeOut: INT _ 30000;  --  30 seconds
longTimeOut: INT _ 120000;  --  2 minutes
totalArpaMsgsSent: PUBLIC INT _ 0;
totalArpaBytesSent: PUBLIC INT _ 0;


Open: PUBLIC PROC [hostName: ROPE] RETURNS [hostStream: Connection] = {
hostStr: STREAM;
hello: ROPE;
busy: BOOLEAN _ FALSE;
hostAddrList: LIST OF IPDefs.Address;
state: IPName.NameState _ IPName.LoadCacheFromName[hostName, FALSE, TRUE];

IF SMTPQueue.CountQueue["ARPA"] > timeOutSwitch THEN {timeOut _ shortTimeOut; busy _ TRUE} ELSE {timeOut _ longTimeOut; busy _ FALSE};

hostAddrList _ IPName.NameToAddress[hostName, TRUE];

IF state = down OR (hostAddrList = NIL AND state ~= bogus) THEN {
SMTPSupport.Log[
noteworthy, "TCP open failed: Can't load cache for ", hostName, "."];
ERROR Failed[
withItem: retryLater,
reason: Rope.Cat["Unable to load name cache for ", hostName, "."],
problemWithHost: TRUE]; };

IF state = bogus THEN {
host: Rope.ROPE;
serverName: Rope.ROPE;
contactMsg: Rope.ROPE;
host _ IF Rope.Find[hostName, "."] = -1 THEN Rope.Concat[hostName, ".ARPA"] ELSE hostName;
serverName _ IPName.Source[host, bogusNameCache];
IF serverName # NIL THEN {
contactMsg _ Rope.Cat[serverName, " is the name server which gave us this information.  \n\nIf you are sure that the unknown host above is actually a valid Arpanet name, there may be a problem with the name server at ", serverName, "  If the problem does not go away after a few days, forward the header of this message to Postmaster.pa@Xerox.COM and the maintainers of this name server will be notified.\n"]}
ELSE {
serverName _ "???";
contactMsg _ "If you are sure that the unknown host above is actually a valid Arpanet name, there may be a problem with one of name servers for this host's domain.  If the problem does not go away after a few days, forward the header of this message to Postmaster.pa@Xerox.COM and the maintainers of the relevant name server will be notified.\n"};

SMTPSupport.Log[
noteworthy, "TCP open failed: unknown host ", hostName, ".  Loaded from:  ", serverName, "."];
ERROR Failed[
withItem: returnToSender,
reason: Rope.Cat["Unable to deliver msg to unknown host: ", hostName, ".\n\n", contactMsg],
problemWithHost: TRUE]; };

IF busy THEN hostAddrList _ LIST[IPRouter.BestAddress[hostAddrList]];

FOR addrList: LIST OF IPDefs.Address _ hostAddrList, addrList.rest UNTIL addrList = NIL DO
ENABLE {
TCP.Timeout => {
SMTPSupport.Log[
noteworthy,
"TCP.Timeout during SMTP open to ", hostName,
" = ", IPName.AddressToRope[addrList.first]];
CONTINUE; }; -- i.e., go around the loop again (until exhausted)
IO.Error => {
SMTPSupport.Log[
noteworthy,
"IO.Error during SMTP open to ", hostName,
" = ", IPName.AddressToRope[addrList.first],
".\nTCP reason: \"", TCPErrorText[TCP.ErrorFromStream[stream]],
"\". Will retry later."];
CONTINUE; };
IO.EndOfStream => {
SMTPSupport.Log[
noteworthy,
"IO.EndOfStream during SMTP open to ", hostName,
" = ", IPName.AddressToRope[addrList.first],
".\nTCP reason: \"", TCPErrorText[TCP.ErrorFromStream[stream]],
"\". Will retry later."];
CONTINUE; };
FailureReply => {
SMTPSupport.Log[
noteworthy,
"FailureReply during SMTP open to ", hostName,
" = ", IPName.AddressToRope[addrList.first],
".\nReason: \"", reason,
"\". Will retry later."];
ERROR Failed[
withItem: retryLater,
reason: reason,
problemWithHost: TRUE]; };
};

addr: IPDefs.Address = addrList.first;
tcpInfo: TCP.TCPInfo = [
matchForeignAddr: TRUE,
foreignAddress: addr,
matchForeignPort: TRUE,
foreignPort: SMTPControl.arpaMSPort,
active: TRUE, -- i.e. establish connection
timeout: timeOut,
matchLocalPort~FALSE];
SMTPSupport.Log[verbose, "Opening SMTP connection to ", hostName,
" = ", IPName.AddressToRope[addr], "."];
hostStr _ TCP.CreateTCPStream[tcpInfo !
TCP.Error => {
SMTPSupport.Log[
ATTENTION, -- this shouldn't occur
 "TCP.Error opening stream to ", hostName, ", ",
" = ", IPName.AddressToRope[addr],
TCPErrorText[reason],
".\nIt is possibly a program bug.\n",
"Will try again later, though intervention is probably required."];
CONTINUE}]; -- i.e. try next addr
hostStream _ NEW[ConnectionRep _ [
stream: hostStr,
start: BasicTime.GetClockPulses[],
used: FALSE,
bytes: 0,
addr: addr,
name: hostName]];
hello _ CheckReplyTo[hostStream]; -- check initial connection reply
HELOcmd[hostStream];
EXIT;
REPEAT
FINISHED => ERROR Failed[
withItem: retryLater,
reason: Rope.Cat["Failed to connect to ", hostName],
problemWithHost: TRUE];
ENDLOOP;

SMTPSupport.Log[verbose, "Opened SMTP connection to ", hostName,
" = ", IPName.AddressToRope[hostStream.addr], ".\n", hello];
}; -- end Open
SendItem: PUBLIC PROC [descr: Descr, recipList: LIST OF ROPE, hostStream: Connection] = {
ENABLE {
TCP.Timeout => {
SMTPSupport.Log[
noteworthy,
 "TCP.Timeout during SMTP conversation with ", hostStream.name,
" = ", IPName.AddressToRope[hostStream.addr],
"\nwhile trying to send item ", descr.Unparse[],
". Will try again later."];
ERROR Failed[
withItem: retryLater,
reason: "TCP.Timeout during conversation",
problemWithHost: TRUE];
};
IO.EndOfStream => {
SMTPSupport.Log[
noteworthy,
"IO.EndOfStream during SMTP conversation with ", hostStream.name,
" = ", IPName.AddressToRope[hostStream.addr],
"\nwhile trying to send item ", descr.Unparse[],
".\nTCP reason: ", TCPErrorText[TCP.ErrorFromStream[stream]], ". Will retry later."];
ERROR Failed[
withItem: retryLater,
reason: "IO.EndOfStream during conversation",
problemWithHost: TRUE]; };
IO.Error => {
SMTPSupport.Log[
noteworthy,
"IO.Error during SMTP conversation with ", hostStream.name,
" = ", IPName.AddressToRope[hostStream.addr],
"\nwhile trying to send item ", descr.Unparse[],
".\nTCP reason: ", TCPErrorText[TCP.ErrorFromStream[stream]], ". Will retry later."];
ERROR Failed[
withItem: retryLater,
reason: "IO.Error during conversation",
problemWithHost: TRUE]; };
};

stop: BasicTime.Pulses;
seconds: INT;
precedeMsgText: ROPE _ descr.GetPrecedeMsgText[];
badRecipients: ROPE _ NIL;
good: INT _ 0;
msgStream, textStream: STREAM;
buffer: REF TEXT;
from: ROPE _ descr.GetArpaReversePath[];

IF descr.GetFormat[] = arpa THEN {
glue: ROPE _ IF from.Fetch[0] = '@ THEN "," ELSE ":";
IF Rope.Find[from, "@"] = -1 THEN
from _ Rope.Cat[from, "@", SMTPControl.xeroxDomain] -- Rejection messages
ELSE
from _ Rope.Cat["@", SMTPControl.xeroxDomain, glue, from]; }; -- Relay mode

IF hostStream.used THEN RSETcmd[hostStream]; -- ensure clean state
hostStream.used _ TRUE;
hostStream.start _ BasicTime.GetClockPulses[];
hostStream.bytes _ 0;
MAILcmd[hostStream, from];

FOR restRecips: LIST OF ROPE _ recipList, restRecips.rest UNTIL restRecips = NIL DO
good _ good + 1;
RCPTcmd[hostStream, restRecips.first ! UnknownUser => {
good _ good - 1;
IF badRecipients # NIL THEN badRecipients _ Rope.Concat[badRecipients, ",\n"];
badRecipients _ Rope.Cat[badRecipients, "\t", restRecips.first, " => ", reason];
CONTINUE}];
ENDLOOP;

IF good # 0 THEN
BEGIN
StartDATAcmd[hostStream];
buffer _ RefText.ObtainScratch[512];

IF precedeMsgText # NIL THEN {
textStream _ IO.RIS[precedeMsgText];
UNTIL textStream.EndOf[] DO
buffer _ textStream.GetLine[buffer];
SendDataBuffer[hostStream, buffer];
ENDLOOP; };

msgStream _ descr.RetrieveMsgStream[];
SELECT descr.GetFormat[] FROM
arpa => textStream _ msgStream;
gv => { -- Bletch, we really should handle more than 1 text block
AssignTextStream: SMTPSyntax.GVItemProc = {
currentIndex: INT;
IF itemHeader.type # Text THEN RETURN;
currentIndex _ msgStream.GetIndex[];
msgStream _ SMTPSupport.CreateSubrangeStream[
origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length];
continue _ FALSE; };
DeleteVersions: PROC[name: Rope.ROPE, nVersions: CARDINAL] = {FOR i: CARDINAL IN [0..nVersions) DO FS.Delete[name]; ENDLOOP;};

errors: IO.STREAM _ IO.ROS[];
tempName: ROPE = "///MG/ToArpa";
keep: CARDINAL = 5;
SMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream];
textStream _ FS.StreamOpen[fileName: tempName, accessOptions: $create, keep: keep ! FS.Error => {IF error.code = $noMoreVersions THEN {DeleteVersions[tempName, keep]; RETRY}}];
MT.TranslateMessage[in: msgStream, out: textStream, error: errors, direction: toArpa, id: SMTPDescr.UniqueID[descr] ];
msgStream.Close[];
textStream.Close[];
textStream _ FS.StreamOpen[tempName, $read];
IF errors.GetLength[] # 0 THEN {
IF FALSE THEN {
SendDataBuffer[
hostStream,
"Comment: ***** Troubles parsing header. Fixups may look strange.\n"];
errors _ IO.RIS[IO.RopeFromROS[errors]];
UNTIL errors.EndOf[] DO
buffer _ errors.GetLine[buffer];
SendDataBuffer[hostStream, buffer];
ENDLOOP;
errors.Close[]; };
SMTPSupport.HeaderParseError[recipList, descr]; }; };
ENDCASE => ERROR;

UNTIL textStream.EndOf[] DO
buffer _ textStream.GetLine[buffer];
SendDataBuffer[hostStream, buffer];
ENDLOOP;
textStream.Close[];
EndDATAcmd[hostStream];
END;
stop _ BasicTime.GetClockPulses[];
seconds _ BasicTime.PulsesToMicroseconds[stop-hostStream.start]/1000000;

IF badRecipients = NIL THEN {
SMTPSupport.Log[
noteworthy, SMTPDescr.Unparse[descr], Bytes[hostStream.bytes], hostStream.name, "."];
totalArpaMsgsSent _ totalArpaMsgsSent +1;
totalArpaBytesSent _ totalArpaBytesSent + hostStream.bytes;
}
ELSE {
reason: ROPE _ Rope.Cat[
"Unable to deliver msg to the following recipient(s) at ", hostStream.name, ":\n",
badRecipients, "."];

IF good > 0 THEN reason _ Rope.Cat[
reason, "\nSuccessfully delivered to other recipient(s)."];
SMTPSupport.Log[
noteworthy, SMTPDescr.Unparse[descr], " will be returned because:\n", reason];
ERROR Failed[withItem: returnToSender, reason: reason, problemWithHost: FALSE]; };
}; -- end SendItem
Bytes: PROC [bytes: INT] RETURNS [rope: ROPE] = {
rope _ Rope.Cat[" sent ", Convert.RopeFromInt[bytes], " bytes to "]; };
Close: PUBLIC PROC [hostStream: Connection, trouble: BOOL] = {
BEGIN ENABLE {
TCP.Timeout => GOTO Abort;
IO.EndOfStream, IO.Error => GOTO Return;
};
IF trouble THEN GOTO Abort;
QUITcmd[hostStream ! Failed => GOTO Abort];
hostStream.stream.Close[];
TCP.AbortTCPStream[hostStream.stream];
EXITS
Abort => TCP.AbortTCPStream[hostStream.stream];
Return => NULL;
END;
IF out # NIL THEN out.PutText["*** Closed.\n\n\n"];
SMTPSupport.Log[
verbose, "Outgoing SMTP conversation with ", hostStream.name, " closed."]; };
SendDataBuffer: PROC [hostStream: Connection, line: REF TEXT] = {
him: IO.STREAM = hostStream.stream;
length: INT = RefText.Length[line];
IF out # NIL THEN out.PutText["     "];
IF RefText.Length[line] > 0 AND RefText.Fetch[line, 0] = '. THEN him.PutChar['.];
FOR i: INT IN [0..length) DO
hostStream.bytes _ hostStream.bytes + 1;
IF out # NIL THEN out.PutChar[RefText.Fetch[line, i]];
him.PutChar[RefText.Fetch[line, i]];
ENDLOOP;
hostStream.bytes _ hostStream.bytes + 2;
IF out # NIL THEN out.PutChar['\n]; 
him.PutText["\n\l"];

};
GetLineRope: PROC [hostStr: STREAM] RETURNS [rope: ROPE] = {
length: INT;
rope _ hostStr.GetLineRope[];
length _ rope.Length[];
IF length > 0 AND rope.Fetch[length-1] = '\l THEN { -- NRL-CSS LFCR Krock
rope _ Rope.Substr[rope, 0, length-1]; RETURN; };
[] _ hostStr.GetChar[]; }; -- Discard LF
Failed: PUBLIC ERROR [withItem: SMTPSend.WithItemAction, reason: ROPE, problemWithHost: BOOL] = CODE;
TCPErrorText: PROC [why: TCP.Reason] RETURNS [ROPE] = {
RETURN[SELECT why FROM
localConflict => "local conflict",
unspecifiedRemoteEnd => "unspecified remote end",
neverOpen => "never open",
localClose => "local close",
localAbort => "local abort",
remoteClose => "remote close",
remoteAbort => "remote abort",
transmissionTimeout => "transmission timeout",
protocolViolation => "protocol violation",
ENDCASE => "???"]; };
RC: TYPE = { rc050, rc211, rc214, rc220, rc221, rc250, rc251,
 rc354,
 rc421, rc450, rc451, rc452,
 rc500, rc501, rc502, rc503, rc504, rc550, rc551, rc552, rc553, rc554,
 unknown};
Analysis: TYPE = RECORD [asLiteral: ROPE,
 success: BOOL,
 withItem: SMTPSend.WithItemAction _ irrelevant,
 problemWithHost: BOOL _ FALSE,
 logEvokingCmdLine: BOOL _ FALSE];
Replies: ARRAY RC[RC.FIRST .. RC.LAST) OF Analysis = [
rc050: Analysis["050", TRUE], -- krock/bug
rc211: Analysis["211", TRUE], -- system status
rc214: Analysis["214", TRUE], -- help msg
rc220: Analysis["220", TRUE], -- ready
rc221: Analysis["221", TRUE], -- closing channel
rc250: Analysis["250", TRUE], -- ok, completed
rc251: Analysis["251", TRUE], -- user not local, forwarding
rc354: Analysis["354", TRUE], -- start mail text
rc421: Analysis["421", FALSE, retryLater, TRUE, FALSE], -- service not avail
rc450: Analysis["450", FALSE, retryLater, FALSE, TRUE], -- mailbox unavail
rc451: Analysis["451", FALSE, retryLater, TRUE, FALSE], -- host error
rc452: Analysis["452", FALSE, retryLater, TRUE, FALSE], -- out of store
rc500: Analysis["500", FALSE, returnToSender, FALSE, TRUE], -- cmd unrecognized
rc501: Analysis["501", FALSE, returnToSender, FALSE, TRUE], -- syntax error in args
rc502: Analysis["502", FALSE, returnToSender, TRUE, TRUE], -- cmd unimplemented
rc503: Analysis["503", FALSE, retryLater, TRUE, TRUE], -- bad cmd sequence
rc504: Analysis["504", FALSE, returnToSender, TRUE, TRUE], -- cmd param unimpl
rc550: Analysis["550", FALSE, returnToSender, FALSE, FALSE], -- unknown rcpt
rc551: Analysis["551", FALSE, returnToSender, FALSE, FALSE], -- user not local
rc552: Analysis["552", FALSE, returnToSender, FALSE, TRUE], -- exceeded store alloc
rc553: Analysis["553", FALSE, returnToSender, FALSE, TRUE], -- bad mailbox name
rc554: Analysis["554", FALSE, returnToSender, TRUE, TRUE] ]; -- transaction failed
AnalyzeUnknownRC: PROC [asLiteral: ROPE] RETURNS [analysis: Analysis] = {
analysis _ [asLiteral: "*** Too Short", success: FALSE, withItem: retryLater];
IF asLiteral.Length[] < 3 THEN RETURN; -- Avoid BoundsFalut
analysis.asLiteral _ asLiteral;
SELECT Rope.Fetch[asLiteral, 0] FROM
'0 => -- bug/krock
 {analysis.success _ TRUE; analysis.withItem _ irrelevant};
'1, '2, '3 => -- positive preliminary/completion/intermediate reply (respectively)
 {analysis.success _ TRUE; analysis.withItem _ irrelevant};
'4 => -- transient negative completion reply
 {analysis.success _ FALSE; analysis.withItem _ retryLater};
'5 => -- permanent negative completion reply
 {analysis.success _ FALSE; analysis.withItem _ returnToSender};
ENDCASE => -- ??? shouldn't occur
 {analysis.success _ FALSE; analysis.withItem _ returnToSender};
SELECT Rope.Fetch[asLiteral, 1] FROM
'0 => -- syntax
 {analysis.problemWithHost _ FALSE; analysis.logEvokingCmdLine _ TRUE};
'1 => -- information
 {analysis.problemWithHost _ FALSE; analysis.logEvokingCmdLine _ FALSE};
'2 => -- connections
 {analysis.problemWithHost _ TRUE; analysis.logEvokingCmdLine _ FALSE};
'3, '4 => -- unspecified as yet
 NULL;
'5 => -- mail system
 {analysis.problemWithHost _ TRUE; analysis.logEvokingCmdLine _ FALSE};
ENDCASE => -- ??? shouldn't occur
 {analysis.problemWithHost _ FALSE; analysis.logEvokingCmdLine _ TRUE}; };
CheckReplyTo: PROC [hostStream: Connection, send1, send2, send3: ROPE _ NIL] RETURNS [hostResponse: ROPE] = {
hostStr: STREAM = hostStream.stream;
replyText, rcLiteral: ROPE;
rcCode: RC; rcAnalysis: Analysis;
start, stop: BasicTime.Pulses;
IF send1 = NIL THEN { -- check "reply" to initial connection only, nothing to send
IF out # NIL THEN {
out.PutText["\n\n\n*** Initial Connection to "];
out.PutRope[hostStream.name];
out.PutText["\n"];
out.Flush[]; };
send1 _ "initial connection"; }
ELSE {
IF out # NIL THEN {
out.PutRope[send1]; out.PutRope[send2]; out.PutRope[send3];
out.PutRope["\n"]; out.Flush[]; };
hostStr.PutRope[send1]; hostStr.PutRope[send2]; hostStr.PutRope[send3];
hostStr.PutRope["\n\l"];
hostStr.Flush[]; };
start _ BasicTime.GetClockPulses[];
replyText _ GetLineRope[hostStr];
stop _ BasicTime.GetClockPulses[];
IF out # NIL THEN {
seconds: INT _ BasicTime.PulsesToMicroseconds[stop-start]/1000000;
out.PutF["%03G: %G\n", IO.int[seconds], IO.rope[replyText]]; };
IF replyText.Length[] > 3 AND replyText.Fetch[3] = '- THEN {  -- xxxxx
DO
temp: ROPE;
start _ BasicTime.GetClockPulses[];
temp _ GetLineRope[hostStr];
stop _ BasicTime.GetClockPulses[];
IF out # NIL THEN {
seconds: INT _ BasicTime.PulsesToMicroseconds[stop-start]/1000000;
out.PutF["%03G: %G\n", IO.int[seconds], IO.rope[temp]]; };
replyText _ Rope.Cat[replyText, "\n", temp];
IF temp.Length[] > 3 AND temp.Fetch[3] # '-  THEN EXIT;
ENDLOOP; };
rcLiteral _ Rope.Substr[replyText, 0, 3];
FOR rc: RC IN [RC.FIRST..RC.LAST) DO
IF Rope.Equal[Replies[rc].asLiteral, rcLiteral] THEN {
rcCode _ rc;
rcAnalysis _ Replies[rc];
EXIT; };
REPEAT FINISHED => {rcCode _ unknown; rcAnalysis _ AnalyzeUnknownRC[replyText]};
ENDLOOP;

IF rcAnalysis.success THEN RETURN[replyText];

SIGNAL FailureReply[rcCode, replyText];
SMTPSupport.Log[IF rcCode = rc503 OR rcCode = rc552 THEN ATTENTION -- possible code bug
 ELSE IF rcAnalysis.problemWithHost THEN important
 ELSE noteworthy,
 "Error reply from ", hostStream.name, ": \"", replyText,
 IF rcAnalysis.logEvokingCmdLine
 THEN Rope.Cat["\"\nin response to: \"", send1, send2, send3, "\"."]
 ELSE "\"."];
ERROR Failed[
withItem: rcAnalysis.withItem,
reason: Rope.Cat[hostStream.name, " said ", replyText],
problemWithHost: rcAnalysis.problemWithHost]; };
HELOcmd: PROC [hostStream: Connection] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "HELO ", SMTPControl.xeroxDomain]; };
MAILcmd: PROC [hostStream: Connection, reversePath: ROPE] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "MAIL FROM:<", reversePath, ">"]; };
RCPTcmd: PROC [hostStream: Connection, recipient: ROPE] = { -- may raise UnknownUser
ENABLE FailureReply =>
IF rcCode = rc550 OR rcCode = rc551 THEN ERROR UnknownUser[reason] ELSE RESUME;
[] _ CheckReplyTo[hostStream, "RCPT TO:<", recipient, ">"]; };
StartDATAcmd: PROC [hostStream: Connection] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "DATA"]; };
EndDATAcmd: PROC [hostStream: Connection] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "."]; };
RSETcmd: PROC [hostStream: Connection] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "RSET"]; };
QUITcmd: PROC [hostStream: Connection] = {
ENABLE FailureReply => RESUME;
[] _ CheckReplyTo[hostStream, "QUIT"]; };
FailureReply: SIGNAL [rcCode: RC, reason: ROPE] = CODE;
UnknownUser: ERROR [reason: ROPE] = CODE;
MakeViewer: PROC = {
[in: in, out: out] _ ViewerIO.CreateViewerStreams[
name: "SMTPSend.log", viewer: NIL, backingFile: "SMTPSend.log", editedStream: FALSE];
TypeScript.ChangeLooks[ViewerIO.GetViewerFromStream[out], 'f]; };
showThings: BOOL _ FALSE;
in, out: IO.STREAM _ NIL;
IF showThings THEN MakeViewer[];
END.
��
ž��SMTPSendImpl.mesa
Copyright c 1985 by Xerox Corporation.  All rights reserved.
Last Edited by: DCraft, December 21, 1983 1:09 pm
Last Edited by: Taft, January 23, 1984 4:37:03 pm PST
Hal Murray June 1, 1985 8:02:58 pm PDT
John Larson, July 23, 1987 12:15:10 pm PDT
Open, SendItem, and Close
probably during SMTP initial handshake (HELOcmd); this is what happens if the host cannot be reached (I think!)
Dies if this error isn't from a TCP stream
Send the given mail item down the host stream.
Dies if this error isn't from a TCP stream
This path is also used when sending error messages
Send all the recipients, constructing a BadRecipients rope.
Now send the message body.
First, what we have added.
Obtain a stream containing only the message text.
Copy the data from the text stream to the host stream, handling transparency of lines beginning with dot.
him.Flush[]; 

Reply codes
Actions resulting from reply codes are largely table driven. AnalyzeUnknownRC will attempt to provide similar information for unknown reply codes. The intended use of the analysis is as follows: For successful replies, the remaining text of the reply line will be the result of CheckReplyTo. For unsuccessful replies, the Failed error will be raised with the arguments withItem and problemWithHost, and the remaining text of the reply line will be included in the reason. (See the comment with Failed for intended results of its parameters.) Also, a log entry will be made, including the command line if indicated. Because this generality does not properly handle all cases, there are a few statements in CheckReplyTo which deal with exceptions.
503, and 552 will be logged with priority ATTENTION
Make as best a guess as possible based on the "Theory of Reply Codes" (appendix E). This wouldn't completely correctly analyze the known reply codes, but it would come fairly close.
Sends the given cmd line, awaits the reply and analyzes it, returning the text message if successful or signalling FailureReply and (upon resumption) logging the error and raising Failed.
Send [non-empty] command line. Analyze the reply rc.
Read the remaining reply text and return it if cmd successful. (Assumption: The success reply was the expected success reply. I don't think it's worth checking this.)
Otherwise, log failure and raise the Failed error, except...
SMTP commands
All of the following commands may raise the Failure error. All of them should catch the FailureReply signal, the resumption of which will invoke the standard reply error handling (logging and raising the Failure error). Any of them wishing to intervene in the standard handling may act on the rcCode, and possibly not resume.
should possibly be done some other way
ÊÓ��–
"cedar" style˜�headšœ™codešœ
Ïmœ1™<Jšœ1™1Jšœ5™5J™&J™*—code2šÏk	˜	Mšœ
žœ0˜?Mšœžœ˜Mšžœžœ˜%Mš
žœžœ…žœžœžœ˜²Mšœžœ ˜-Mšœžœ+žœ
˜CMšœžœ˜Mšœ	žœ,˜:Mšœžœ˜MšœžœF˜RMšœ	žœ˜Mšžœžœ˜Mšœžœ˜,Mšœ
žœa˜pMšœ	žœ˜ Mšœžœ/˜@Mšœžœ ˜0Mšœ
žœ˜MšžœžœU˜^——šÐlnœžœž˜šž˜Mšœžœžœ8˜RMšžœ<ž˜C—Mšžœ˜Mšž˜Mšžœžœžœ˜Mšžœžœžœžœ˜Mšœžœ˜MšœžœžœÏc˜<šœžœžœžœ˜$Mšœžœ˜Mšœ˜Mšœžœ˜Mšœžœ˜Mšœ˜Mšœžœ˜M˜�—Mšœžœ 1˜JMšœ	žœ˜Mšœžœ ˜*Mšœ
žœ 
˜)Mšœž
œ˜"Mšœž
œ˜#M˜�M˜�M™š
ÏnœžœžœÏr
Ðkrœžœ¢
œ˜GMšœ	žœ˜Mšœžœ˜Mšœžœžœ˜Mšœžœžœ˜%Mšœ3¢œžœ˜JM˜�Mš
žœ.žœ!žœžœ žœ˜†M˜�Mšœ.žœ˜4M˜�š
žœžœžœžœžœ˜Ašœ˜MšœE˜Ešžœ˜
Mšœ˜MšœB˜BMšœžœ˜M˜�———šžœžœ˜Mšœžœ˜Mšœžœ˜Mšœžœ˜Mš	œžœ¢œžœ žœ
˜ZMšœ¢œ˜1šžœžœžœ˜Mšœ™˜™—šžœ˜Mšœ˜MšœÛ˜ÛM˜�—šœ˜Mšœ^˜^šžœ˜
Mšœ˜Mšœ[˜[Mšœžœ˜M˜�———šžœžœžœ%˜EM˜�—šžœžœžœ.žœžœž˜Zšžœ˜šžœ
˜Mšœo™ošœ˜Mšœ˜MšœÏlœ)˜-Mšœ-˜-—Mšžœ 3˜@—šžœ˜
˜M˜M˜*Mšœ,˜,M™*Mšœ"žœ˜?M˜—Mšžœ˜—šžœ˜˜M˜Mšœ0˜0Mšœ,˜,Mšœ"žœ˜?M˜—Mšžœ˜—šœ˜˜M˜Mšœ.˜.Mšœ,˜,Mšœ˜M˜—šžœ˜
Mšœ˜Mšœ˜Mšœžœ˜——M˜M˜�—Mšœ&˜&šœ	žœ˜Mšœžœ˜Mšœ˜Mšœžœ˜Mšœ$˜$Mšœžœ ˜*Mšœ˜Mšœžœ˜—šœA˜AMšœ(˜(—šœ
žœ˜'šžœ˜šœ˜Mšž	œ ˜"Mšœ0˜0Mšœ"˜"Mšœ˜Mšœ%˜%MšœC˜C—Mšžœ ˜!——šœ
žœ˜"Mšœ˜Mšœ"˜"Mšœžœ˜Mšœ	˜	Mšœ˜Mšœ˜—Mšœ" !˜CM˜Mšžœ˜šž˜šžœžœ˜Mšœ˜Mšœ4˜4Mšœžœ˜——Mšžœ˜M˜�šœ@˜@Mšœ<˜<—Mšœ ˜——š¡œžœžœ¢
£¢£¢£¢œ˜YM™.šžœ˜šžœ
˜˜M˜M˜?Mšœ-˜-M˜0M˜—šžœ˜
Mšœ˜Mšœ*˜*Mšœžœ˜—Mšœ˜—šžœ˜˜M˜M˜AMšœ-˜-M˜0Mšœ žœ2˜U—šžœ˜
Mšœ˜Mšœ-˜-Mšœžœ˜——šžœ˜
˜M˜M˜;Mšœ-˜-M˜0M™*Mšœ žœ2˜U—šžœ˜
Mšœ˜Mšœ'˜'Mšœžœ˜——M˜—M˜�Mšœ˜Mšœ	žœ˜
Mšœžœ˜1Mšœžœžœ˜Mšœžœ˜Mšœžœ˜Mšœžœžœ˜Mšœžœ˜(M˜�šžœžœ˜"Mšœ2™2Mš	œžœžœžœžœ˜5šžœž˜!Mšœ4 ˜I—šž˜Mšœ> 
˜K——M˜�Mšžœžœ ˜BMšœžœ˜Mšœ.˜.Mšœ˜Mšœ˜M˜�M™;š
žœ
žœžœžœžœžœž˜SM˜˜7M˜Mšžœžœžœ3˜NMšœP˜PMšžœ˜—Mšžœ˜—M˜�šžœ
ž˜Mšž˜M™M˜Mšœ$˜$M˜�M™šžœžœžœ˜Mšœ
žœžœ˜$šžœž˜Mšœ$˜$Mšœ#˜#Mšžœ˜——M˜�M™1Mšœ&˜&šžœž˜Mšœ˜š¤œ 9˜Aš¡œ˜+Mšœžœ˜Mšžœžœžœ˜&M˜$˜-M˜O—Mšœžœ˜—š¡œžœžœ
žœžœžœžœžœžœ˜~M˜�—Mš	œžœžœžœžœ˜Mšœ
žœ˜ Mšœžœ˜MšœI˜IMšœ
žœEžœžœžœ"žœ˜°Mšžœt˜vMšœ˜Mšœ˜Mšœ
žœ˜,šžœžœ˜ šžœžœžœ˜šœ˜Mšœ˜MšœF˜F—Mšœ	žœžœžœ˜(šžœž˜Mšœ ˜ Mšœ#˜#Mšžœ˜—Mšœ˜—Mšœ5˜5——Mšžœžœ˜—M˜�Mšœi™išžœž˜Mšœ$˜$Mšœ#˜#Mšžœ˜—M˜M˜Mšžœ˜—Mšœ"˜"MšœH˜HM˜�šžœžœžœ˜˜M˜UMšœ)˜)Mšœ;˜;M˜——šžœ˜šœžœ˜MšœR˜RMšœ˜M˜�—šžœ
žœ˜#M˜;—šœ˜MšœN˜N—MšžœCžœ˜R—Mšœ ˜—š
¡œžœ	žœžœžœ˜1MšœG˜G—š¡œžœžœ#žœ˜>šžœžœ˜Mšžœžœ˜Mšžœžœ
žœ˜(M˜—Mšžœ	žœžœ˜Mšœžœ˜+M˜Mšžœ#˜&šž˜Mšœ	žœ#˜/Mšœ
žœ˜—Mšžœ˜Mšžœžœžœ"˜3˜M˜M——š
¡œžœ¢œžœžœ˜AMšœžœžœ˜#Mšœžœ˜#Mšžœžœžœ˜'Mšžœžœžœ˜Q𣢣¢£¢
£˜Mš¢
œ	¢
œ˜(Mšžœžœžœ%˜6Mšœ$˜$Mšžœ˜—Mš¢
œ	¢
œ˜(Mšžœžœžœ˜$Mšœ˜M˜�M™
M™�M˜—š¡œžœ	¢žœžœžœ˜<Mšœžœ˜Mšœ˜Mšœ˜šžœžœžœ ˜IMšœ'žœ˜1—Mšœ 
˜(—Mš¡œžœžœ-žœžœžœ˜eš
¡œžœžœ	žœžœ˜7šžœžœž˜M˜"Mšœ1˜1M˜M˜M˜M˜M˜M˜.M˜*Mšžœ˜——M™Mšœ=¢œÉ¢œ ¢œ)¢œ¢œC¢œ¢œÍ¢œ™éMšžœžœ¬˜´Mšœ
žœžœ
žœžœEžœžœžœžœ˜­š¡œžœžœžœžœžœžœžœ
˜6Mšœžœ ˜*Mšœžœ ˜.Mšœžœ ˜)Mšœžœ ˜&Mšœžœ ˜0Mšœžœ ˜.Mšœžœ ˜;Mšœžœ ˜0Mšœžœžœžœ ˜LMšœžœžœžœ ˜JMšœžœžœžœ 
˜EMšœžœžœžœ ˜GMšœ3™3Mšœžœžœžœ ˜OMšœžœžœžœ ˜SMšœžœžœžœ ˜OMšœžœžœžœ ˜JMšœžœžœžœ ˜NMšœžœžœžœ ˜LMšœžœžœžœ ˜NMšœžœžœžœ ˜SMšœžœžœžœ ˜OMšœžœžœžœ ˜R—š¡œžœ
žœžœ˜IMšœµ™µMšœ1žœ˜NMšžœžœžœ ˜;M˜šžœž˜$šœ ˜Mšœžœ"˜;—šœ D˜RMšœžœ"˜;—šœ &˜,Mšœžœ"˜<—šœ &˜,Mšœžœ&˜@—šžœ ˜!Mšœžœ&˜@——šžœž˜$šœ 	˜Mšœžœžœ˜G—šœ ˜Mšœžœžœ˜H—šœ ˜Mšœžœžœ˜G—šœ
 ˜Mšœžœ˜—šœ ˜Mšœžœžœ˜G—šžœ ˜!Mšœžœžœ˜J———š¡œžœ/žœžœžœžœ˜mMšœ»™»Mšœ	žœ˜$Mšœžœ˜Mšœžœ˜!Mšœ˜M™4šžœ	žœžœ <˜Ršžœžœžœ˜M˜0Mšœ˜Mšœ˜Mšœ˜—M˜—šžœ˜šžœžœžœ˜M˜;Mšœ"˜"—M˜GM˜M˜—Mšœ#˜#Mšœ!˜!Mšœ"˜"šžœžœžœ˜Mšœ	žœ6˜BMšœžœžœ˜?—šžœžœžœ ˜Fšž˜Mšœžœ˜Mšœ#˜#Mšœ˜Mšœ"˜"šžœžœžœ˜Mšœ	žœ6˜B—Mšœžœžœ˜:Mšœ,˜,Mšžœžœžœžœ˜7Mšžœ˜——Mšœ)˜)šžœžœžœžœžœžœžœž˜$šžœ.žœ˜6Mšœ˜Mšœ˜Mšžœ˜—MšžœžœA˜PMšžœ˜—M˜�M™¦Mšžœžœžœ˜-M˜�M™<Mšžœ!˜'Mšœžœžœžœž	œ œžœžœžœžœHžœžœAžœ˜Êšžœ˜
Mšœ˜Mšœ7˜7Mšœ0˜0——M™
Mš	œ,¢œ%¢œh¢œR¢œ™Åš¡œžœ˜*Mšžœžœ˜M˜C—š¡œžœ'žœ˜=Mšžœžœ˜M˜B—š¡œžœ%žœ ˜Tšžœ˜Mšžœžœžœžœžœžœ˜O—M˜>—š¡œžœ˜/Mšžœžœ˜M˜)—š¡
œžœ˜-Mšœ&™&Mšžœžœ˜Mšœ&˜&—š¡œžœ˜*Mšžœžœ˜M˜)—š¡œžœ˜*Mšžœžœ˜M˜)—Mš
¡œžœ
žœ
žœžœ˜7Mš¡œžœ
žœžœ˜)š¡
œžœ˜šœ2˜2Mšœžœ-žœ˜U—JšœA˜A—Mšœžœžœ˜Mšœ	žœžœžœ˜Mšžœžœ˜ Mšžœ˜——�…—����NÞ��oO��