DIRECTORY ArpaConfig USING [bitnetGateway, uucpGateway, csnetGateway, ourLocalName, ourLocalAddress, specialDomains, validDomains, resolv], ArpaName USING [AliasToName], ArpaSMTPControl USING [defaultRegistry], ArpaSMTPSupport USING [CreateSubrangeStream, Log], ArpaSMTPSyntax USING [GVItemProc], Basics USING [bytesPerWord], ConvertExtras USING [RopeFromArpaAddress], GVBasics USING [ItemHeader, RopeFromTimestamp, Timestamp], GVProtocol USING [Failed, ReceiveCount, ReceiveItemHeader, ReceiveRName, ReceiveTimestamp], IO USING [EndOfStream, GetBlock, GetIndex, PutBlock, PutChar, PutF, PutRope, SetIndex, STREAM], RefText USING [AppendChar, ObtainScratch, ReleaseScratch], Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, FromRefText, IsEmpty, Length, ROPE, Substr, Translate]; ArpaSMTPSyntaxImpl: CEDAR PROGRAM IMPORTS ArpaConfig, ArpaName, ArpaSMTPControl, ArpaSMTPSupport, ConvertExtras, GVBasics, GVProtocol, IO, RefText, Rope EXPORTS ArpaSMTPSyntax = BEGIN STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; possibleOldNames: LIST OF ROPE _ LIST["Xerox.ARPA", "[10.2.0.32].ARPA"]; possibleCurrentNames: LIST OF ROPE _ LIST["Xerox.COM", "PARC.Xerox.COM", "[13.0.12.232].ARPA", "mailhost.Xerox.COM", "mailhost.parc.Xerox.COM"]; arpaSpecialRegistries: LIST OF ROPE _ LIST["AG", "ArpaGateway", "NotArpa"]; EnumerateGVItems: PUBLIC PROC [GVStream: STREAM, proc: ArpaSMTPSyntax.GVItemProc, procData: REF ANY _ NIL] = { nextItemIndex: INT; continue: BOOL; itemHeader: GVBasics.ItemHeader; DO itemHeader _ GVProtocol.ReceiveItemHeader[GVStream]; nextItemIndex _ GVStream.GetIndex[] + (itemHeader.length+ bpw-1)/bpw*bpw --word boundary--; continue _ proc[itemHeader, GVStream, procData]; IF (itemHeader.type = LastItem) OR (NOT continue) THEN EXIT; GVStream.SetIndex[nextItemIndex]; ENDLOOP; }; bpw: INT = Basics.bytesPerWord; ReceiveRName: PUBLIC PROC[GVStream: STREAM] RETURNS [ROPE] = { ENABLE GVProtocol.Failed => IF why = protocolError THEN ERROR SyntaxError[Rope.Concat["failed to read RName: ", text]]; RETURN[GVProtocol.ReceiveRName[GVStream]]; }; ReceiveCount: PUBLIC PROC[GVStream: STREAM] RETURNS [CARDINAL] = { ENABLE GVProtocol.Failed => IF why = protocolError THEN ERROR SyntaxError[Rope.Concat["failed to read count: ", text]]; RETURN[LOOPHOLE[GVProtocol.ReceiveCount[GVStream]]]; }; SyntaxError: PUBLIC ERROR [reason: ROPE] ~ CODE; PrintGVItem: PUBLIC ArpaSMTPSyntax.GVItemProc = { out: STREAM ~ NARROW[procData]; BEGIN ENABLE { IO.EndOfStream => {out.PutRope["<<>>\n"]; GOTO Return}; SyntaxError => { out.PutRope["<<>>\n"]; GOTO Return; }; }; PutHeader: PROC [type: ROPE, raw: BOOL] = TRUSTED { out.PutF["----- %g (%bB), %g bytes", [rope[type]], [integer[LOOPHOLE[itemHeader.type, CARDINAL]]], [integer[itemHeader.length]] ]; out.PutRope[IF raw THEN " (raw format) -----\n" ELSE " -----\n"]; IF itemHeader.length <= 0 THEN ERROR SyntaxError["length <= 0"]; }; PutRaw: PROC [] = { -- somewhat inefficient, but infrequently used currentIndex: INT = itemStream.GetIndex[]; nBytesLeft: INT _ itemHeader.length; itemRestrictedStream: STREAM = ArpaSMTPSupport.CreateSubrangeStream[itemStream, currentIndex, currentIndex + nBytesLeft]; buffer: REF TEXT = RefText.ObtainScratch[100]; WHILE nBytesLeft > 0 DO nBytesRead: INT _ itemRestrictedStream.GetBlock[ buffer, 0, MIN[nBytesLeft, buffer.maxLength]]; IF nBytesRead = 0 THEN ERROR IO.EndOfStream[itemRestrictedStream]; out.PutBlock[buffer, 0, nBytesRead]; nBytesLeft _ nBytesLeft - nBytesRead; ENDLOOP; out.PutChar['\n]; RefText.ReleaseScratch[buffer]; }; SELECT itemHeader.type FROM PostMark => { PutHeader["PostMark", FALSE]; out.PutRope[GVBasics.RopeFromTimestamp[GVProtocol.ReceiveTimestamp[itemStream]]]; out.PutChar['\n];}; Sender => { PutHeader["Sender", FALSE]; out.PutRope[ReceiveRName[itemStream]]; out.PutChar['\n];}; ReturnTo => { PutHeader["ReturnTo", FALSE]; out.PutRope[ReceiveRName[itemStream]]; out.PutChar['\n];}; Recipients => { numRecips: INT = ReceiveCount[itemStream]; PutHeader["Recipients", FALSE]; THROUGH [1..numRecips] DO out.PutRope[ReceiveRName[itemStream]]; out.PutChar['\n] ENDLOOP;}; Text => {PutHeader["Text", FALSE]; PutRaw[]}; Capability => {PutHeader["Capability", TRUE]; PutRaw[]}; Audio => {PutHeader["Audio", TRUE]; out.PutRope["\n"]}; LastItem => {PutHeader["LastItem", FALSE]; out.PutChar['\n]}; ENDCASE => {PutHeader["", TRUE]; PutRaw[]}; EXITS Return => NULL; END; }; BlessReturnPath: PUBLIC PROC [raw: ROPE] RETURNS [arpa: ROPE] = BEGIN length: INT = Rope.Length[raw]; host: ROPE; IF Rope.Fetch[raw, 0] = '@ THEN { -- @Foo:X@Y case FOR i: INT IN [1..length) DO char: CHAR = Rope.Fetch[raw, i]; SELECT char FROM ',, ': => { host _ Rope.Substr[raw, 1, i-1]; EXIT; }; ENDCASE => NULL; REPEAT FINISHED => ArpaSMTPSupport.Log[important, "Invalid syntax in return path: ", raw]; ENDLOOP; } ELSE { --Foo@Bar FOR i: INT DECREASING IN [0..length) DO c: CHAR = Rope.Fetch[raw, i]; IF c = '@ THEN { host _ Rope.Substr[raw, i + 1, (length-i-1)]; EXIT; }; ENDLOOP; }; SELECT TRUE FROM (host = NIL) => NULL; ~CheckHostName[host] => ArpaSMTPSupport.Log[important, "Invalid character in first return return host: ", raw]; ENDCASE => NULL; SELECT TRUE FROM Rope.Find[raw, ","] # -1 => { }; ValidDomain[raw] => arpa _ raw; raw.Fetch[0] = '@ => { }; ENDCASE => BEGIN length: INT _ Rope.Length[raw]; user, host, newHost: ROPE; FOR i: INT DECREASING IN [0..length) DO c: CHAR = raw.Fetch[i]; IF c = '@ THEN { user _ Rope.Substr[raw, 0, i]; host _ Rope.Substr[raw, i + 1, (length-i-1)]; IF ~Rope.IsEmpty[host] THEN IF host.Fetch[0] = '[ THEN host _ Rope.Cat[host, ".ARPA"]; -- [36,1,2,6] newHost _ NormalizeName[host]; IF Rope.IsEmpty[newHost] THEN { ArpaSMTPSupport.Log[important, "Bogus return path: ", raw]; } ELSE arpa _ Rope.Cat[user, "@", newHost]; EXIT; }; REPEAT FINISHED => arpa _ raw; ENDLOOP; END; IF arpa = NIL THEN arpa _ raw; IF arpa # raw THEN ArpaSMTPSupport.Log[important, "Return path fixup: ", raw, " => ", arpa]; END; ValidDomain: PROC [raw: ROPE] RETURNS [BOOLEAN] = { FOR list: LIST OF Rope.ROPE _ ArpaConfig.validDomains, list.rest UNTIL list = NIL DO domain: Rope.ROPE _ list.first; IF DotTailed[raw, domain] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE];}; UnBlessReturnPath: PUBLIC PROC [raw: ROPE] RETURNS [arpa: ROPE] = BEGIN IF raw = NIL THEN RETURN[NIL]; IF Rope.Fetch[raw, 0] #'" THEN RETURN[raw]; IF ~Tailed[raw, ArpaConfig.ourLocalName] THEN RETURN[raw]; RETURN[Rope.Substr[raw, 1, Rope.Length[raw]-3-Rope.Length[ArpaConfig.ourLocalName]]]; END; ReversePath: PUBLIC PROC [gv: ROPE] RETURNS [arpa: ROPE] = BEGIN length: INT _ gv.Length[]; FOR i: INT DECREASING IN [0..length) DO c: CHAR = gv.Fetch[i]; IF c = '\" THEN { -- "Foo" or "Foo".OSBUNorth IF gv.Fetch[0] # '\" THEN EXIT; IF i = length-1 THEN gv _ Rope.Substr[gv, 1, length-2] ELSE gv _ Rope.Cat[Rope.Substr[gv, 1, i-1], Rope.Substr[gv, i+1, length-i-1]]; EXIT; }; IF c = '@ THEN { glue: ROPE = IF gv.Fetch[0] = '@ THEN "," ELSE ":"; arpa _ Rope.Cat["@", ArpaConfig.ourLocalName, glue, gv]; RETURN; }; ENDLOOP; gv _ FixupSpaces[gv]; gv _ MaybeAddQuotes[gv]; arpa _ Rope.Cat[gv, "@", ArpaConfig.ourLocalName]; -- Normal GV case END; HostAndUser: PUBLIC PROC [raw: ROPE] RETURNS [host, user: ROPE] = { FOR list: LIST OF ROPE _ arpaSpecialRegistries, list.rest UNTIL list = NIL DO raw _ FixupTail[raw, Rope.Cat[".", list.first]]; raw _ FixupTail[raw, Rope.Cat[".", list.first, ".ARPA"]]; ENDLOOP; raw _ FixupTail[raw, ".ARPA.ARPA"]; FOR list: LIST OF Rope.ROPE _ ArpaConfig.validDomains, list.rest UNTIL list = NIL DO domain: Rope.ROPE _ list.first; rope: Rope.ROPE _ Rope.Cat[".", domain, ".ARPA"]; raw _ TruncateTail[raw, rope]; ENDLOOP; IF Rope.Find[raw, "].ARPA", 0, FALSE] # -1 THEN raw _ TruncateTail[raw, ".ARPA"]; IF raw.Fetch[0] # '@ THEN { raw _ Redirect[raw, ".BITNET", ArpaConfig.bitnetGateway]; raw _ Redirect[raw, ".CSNet", ArpaConfig.csnetGateway]; raw _ Redirect[raw, ".UUCP", ArpaConfig.uucpGateway] ; raw _ Redirect[raw, "xait.xerox.com", ArpaConfig.uucpGateway]; }; IF raw.Fetch[0] # '@ THEN { raw _ StripTail[raw, Rope.Cat["@", ArpaConfig.ourLocalName]]; raw _ StripTail[raw, Rope.Cat["@", ConvertExtras.RopeFromArpaAddress[ArpaConfig.ourLocalAddress]]]; raw _ StripTail[raw, Rope.Cat["@", ConvertExtras.RopeFromArpaAddress[ArpaConfig.ourLocalAddress], ".ARPA"]]; FOR list: LIST OF ROPE _ possibleOldNames, list.rest UNTIL list = NIL DO raw _ StripTail[raw, Rope.Cat["@", list.first]]; ENDLOOP; FOR list: LIST OF ROPE _ possibleCurrentNames, list.rest UNTIL list = NIL DO raw _ StripTail[raw, Rope.Cat["@", list.first]]; ENDLOOP; } ELSE { raw _ StripHead[raw, Rope.Cat["@", ArpaConfig.ourLocalName]]; raw _ StripHead[raw, Rope.Cat["@", ConvertExtras.RopeFromArpaAddress[ArpaConfig.ourLocalAddress]]]; raw _ StripHead[raw, Rope.Cat["@", ConvertExtras.RopeFromArpaAddress[ArpaConfig.ourLocalAddress], ".ARPA"]]; FOR list: LIST OF ROPE _ possibleOldNames, list.rest UNTIL list = NIL DO raw _ StripHead[raw, Rope.Cat["@", list.first]]; ENDLOOP; FOR list: LIST OF ROPE _ possibleCurrentNames, list.rest UNTIL list = NIL DO raw _ StripHead[raw, Rope.Cat["@", list.first]]; ENDLOOP; }; raw _ StripQuotes[raw]; [host: host, user: user] _ FindHostName[raw]; IF host = NIL THEN { user _ StripTail[user, ".ARPA"]; -- Hack for testing by sending to Foo.PA.Arpa user _ ForceRegistry[user]; user _ FixupUnderbars[user]; }; }; Redirect: PROC [old, domain, relay: ROPE] RETURNS [new: ROPE] = BEGIN tail: ROPE _ Rope.Cat[domain, ".ARPA"]; IF Tailed[old, tail] THEN old _ StripTail[old, ".ARPA"]; IF Tailed[old, domain] THEN { length: INT; old _ StripTail[old, domain]; length _ Rope.Length[old]; FOR i: INT DECREASING IN [0..length) DO IF Rope.Fetch[old, i] = '@ THEN { name: ROPE _ Rope.Substr[old, 0, i]; name _ StripQuotes[name]; -- "Joe User"@Host.xx old _ Rope.Cat[name, "%", Rope.Substr[old, i + 1, (length-i-1)], domain]; old _ MaybeAddQuotes[old]; -- "Joe User%Host.xx" EXIT; }; ENDLOOP; old _ Rope.Cat[old, "@", relay]; }; RETURN[old]; END; StripQuotes: PROC [old: ROPE] RETURNS [new: ROPE] = { length: INT _ old.Length[]; new _ old; IF length < 2 THEN RETURN; IF old.Fetch[0] # '\" THEN RETURN; SELECT TRUE FROM old.Fetch[length-1] = '\" => new _ old.Substr[1, length-1-1]; Tailed[old, "\".ARPA"] => new _ old.Substr[1, length-1-6]; ENDCASE => RETURN; length _ new.Length[]; FOR i: INT IN [0..length) DO IF new.Fetch[i] = '\\ THEN EXIT; REPEAT FINISHED => RETURN; -- No \ inside the string ENDLOOP; BEGIN quoteSeen: BOOLEAN _ FALSE; text: REF TEXT _ RefText.ObtainScratch[length]; FOR i: INT IN [0..length) DO c: CHAR = new.Fetch[i]; IF c = '\\ AND ~quoteSeen THEN { quoteSeen _ TRUE; LOOP; }; text _ RefText.AppendChar[text, c]; quoteSeen _ FALSE; ENDLOOP; new _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; END; }; MaybeAddQuotes: PROC [old: ROPE] RETURNS [new: ROPE] = BEGIN length: INT _ old.Length[]; new _ old; IF Rope.IsEmpty[new] THEN RETURN; IF Rope.Fetch[new, 0] = '" THEN RETURN; -- Assume already quoted correctly FOR i: INT IN [0..length) DO SELECT Rope.Fetch[new, i] FROM > 177C => EXIT; -- Funny characters. What should happen to these?? '(, '), '<, '>, '@, '<, ';, ':, '\\, '", '[, '] => EXIT; -- Specials EXCEPT PERIOD! ' => EXIT; -- Space < 040C => EXIT; -- CTL ENDCASE => NULL; -- Includes underbar REPEAT FINISHED => RETURN; -- Nothing fancy inside the string ENDLOOP; new _ Rope.Cat["\"", old, "\""]; END; FixupTail: PROC [old, tail: ROPE] RETURNS [new: ROPE] = { new _ old; IF Tailed[old, tail] THEN { new _ StripTail[old, tail]; new _ Rope.Concat[new, ".ARPA"]; }; }; TruncateTail: PROC [old, tail: ROPE] RETURNS [new: ROPE] = { new _ old; IF Tailed[old, tail] THEN new _ StripTail[old, ".ARPA"]; }; Tailed: PROC [body, tail: ROPE] RETURNS [match: BOOL] = { bodyLength: INT = body.Length[]; tailLength: INT = tail.Length[]; back: ROPE; IF bodyLength <= tailLength THEN RETURN[FALSE]; back _ Rope.Substr[body, bodyLength-tailLength, tailLength]; IF Rope.Equal[back, tail, FALSE] THEN RETURN[TRUE]; RETURN[FALSE]; }; DotTailed: PROC [body, tail: ROPE] RETURNS [match: BOOL] = { IF ~Tailed[body, tail] THEN RETURN[FALSE]; IF Rope.Fetch[body, Rope.Length[body]-Rope.Length[tail]-1] # '. THEN RETURN[FALSE]; RETURN[TRUE]; }; StripTail: PROC [body, tail: ROPE] RETURNS [new: ROPE] = { bodyLength: INT = body.Length[]; tailLength: INT = tail.Length[]; IF ~Tailed[body, tail] THEN RETURN[body]; RETURN[Rope.Substr[body, 0, bodyLength - tailLength]]}; Headed: PROC [body, head: ROPE] RETURNS [match: BOOL] = { bodyLength: INT = body.Length[]; headLength: INT = head.Length[]; char: CHAR; IF bodyLength <= headLength+1 THEN RETURN[FALSE]; char _ Rope.Fetch[body, headLength]; IF char = ', OR char = ': THEN { front: ROPE _ Rope.Substr[body, 0, headLength]; IF Rope.Equal[front, head, FALSE] THEN RETURN[TRUE]; }; RETURN[FALSE]; }; StripHead: PROC [body, head: ROPE] RETURNS [new: ROPE] = { bodyLength: INT = body.Length[]; headLength: INT = head.Length[]; IF ~Headed[body, head] THEN RETURN[body]; RETURN[Rope.Substr[body, headLength+1, bodyLength - headLength -1]]}; FindHostName: PROC [raw: ROPE] RETURNS [host, user: ROPE] = { length: INT _ raw.Length[]; newHost: ROPE; user _ raw; IF Rope.Fetch[raw, 0] = '@ THEN { -- @Foo:X@Y case FOR i: INT IN [1..length) DO char: CHAR = Rope.Fetch[raw, i]; SELECT char FROM ',, ': => { host _ Rope.Substr[raw, 1, i-1]; IF ~CheckHostName[host] THEN RETURN["Invalid character in host name", raw]; newHost _ NormalizeName[host]; IF ~Rope.IsEmpty[newHost] THEN host _ newHost; user _ Rope.Substr[raw, i + 1, (length-i-1)]; user _ Rope.Cat["@", host, Rope.FromChar[char], user]; RETURN; }; ENDCASE => NULL; REPEAT FINISHED => RETURN["Invalid Syntax", raw]; ENDLOOP; }; FOR i: INT DECREASING IN [0..length) DO c: CHAR = raw.Fetch[i]; IF c = '\" THEN EXIT; IF c = '@ THEN { --Foo@Bar user _ Rope.Substr[raw, 0, i]; host _ Rope.Substr[raw, i + 1, (length-i-1)]; IF ~CheckHostName[host] THEN RETURN["Invalid character in host name", raw]; newHost _ NormalizeName[host]; IF ~Rope.IsEmpty[newHost] THEN host _ newHost; user _ MaybeAddQuotes[user]; -- "Foo Foo"@Bar user _ Rope.Cat[user, "@", host]; RETURN; }; ENDLOOP; FOR i: INT DECREASING IN [0..length) DO c: CHAR = raw.Fetch[i]; IF c = '\" THEN EXIT; IF c = '% THEN { -- Hackery: foo%bar user _ Rope.Substr[raw, 0, i]; host _ Rope.Substr[raw, i + 1, (length-i-1)]; IF ~CheckHostName[host] THEN RETURN["Invalid character in host name", raw]; newHost _ NormalizeName[host]; IF ~Rope.IsEmpty[newHost] THEN host _ newHost; user _ MaybeAddQuotes[user]; user _ Rope.Cat[user, "@", host]; RETURN; }; ENDLOOP; host _ NIL; }; -- No @, Must be GV CheckHostName: PROC [host: ROPE] RETURNS [ok: BOOLEAN] = BEGIN length: INT _ Rope.Length[host]; FOR i: INT IN [1..length) DO char: CHAR = Rope.Fetch[host, i]; SELECT char FROM '(, '), '<, '>, '@, '<, ';, ':, '\\, '" => RETURN[FALSE]; -- Specials EXCEPT PERIOD and []! ' => RETURN[FALSE]; -- Space < 040C => RETURN[FALSE]; -- CTL ENDCASE => NULL; ENDLOOP; RETURN[TRUE]; END; NormalizeName: PUBLIC PROC [raw: ROPE] RETURNS [host: ROPE] = BEGIN IF Rope.IsEmpty[raw] THEN RETURN[NIL]; raw _ StripTail[raw, ".ARPA"]; -- fix foo.EDU.ARPA case IF raw.Fetch[0] = '[ THEN IF ~DotTailed[raw, "ARPA"] THEN RETURN[Rope.Cat[raw, ".ARPA"]]; IF Rope.Find[raw, ".",, FALSE] = -1 THEN raw _ Rope.Cat[raw, ".ARPA"]; IF SpecialDomain[raw] THEN RETURN[raw]; host _ ArpaName.AliasToName[raw, ArpaConfig.resolv^].name; RETURN[host]; END; SpecialDomain: PROC [raw: ROPE] RETURNS [BOOLEAN] = { FOR list: LIST OF Rope.ROPE _ ArpaConfig.specialDomains, list.rest UNTIL list = NIL DO domain: Rope.ROPE _ list.first; IF DotTailed[raw, domain] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE];}; ForceRegistry: PROC [raw: ROPE] RETURNS [user: ROPE] = BEGIN length: INT _ raw.Length[]; user _ raw; FOR i: INT DECREASING IN [0..length) DO IF raw.Fetch[i] = '. THEN RETURN; ENDLOOP; user _ Rope.Concat[raw, ArpaSMTPControl.defaultRegistry]; END; FixupSpaces: PROC [raw: ROPE] RETURNS [user: ROPE] = BEGIN length: INT _ raw.Length[]; user _ raw; FOR i: INT IN [0..length) DO IF raw.Fetch[i] = ' THEN EXIT; REPEAT FINISHED => RETURN; ENDLOOP; user _ Rope.Translate[base: user, translator: SpaceToUnderbar]; END; FixupUnderbars: PROC [raw: ROPE] RETURNS [user: ROPE] = BEGIN length: INT _ raw.Length[]; name, registry: ROPE; user _ raw; FOR i: INT IN [0..length) DO IF raw.Fetch[i] = '_ THEN EXIT; REPEAT FINISHED => RETURN; ENDLOOP; FOR i: INT DECREASING IN [0..length) DO IF raw.Fetch[i] = '. THEN { name _ Rope.Substr[raw, 0, i]; registry _ Rope.Substr[raw, i, (length-i)]; -- Registry includes the dot EXIT; }; REPEAT FINISHED => name _ user; -- No registry (?) ENDLOOP; name _ Rope.Translate[base: name, translator: UnderbarToSpace]; user _ Rope.Cat["\"", name, "\"", registry]; END; UnderbarToSpace: PROC [old: CHAR] RETURNS [new: CHAR] = BEGIN IF old = '_ THEN RETURN[' ] ELSE RETURN[old]; END; SpaceToUnderbar: PROC [old: CHAR] RETURNS [new: CHAR] = BEGIN IF old = ' THEN RETURN['_] ELSE RETURN[old]; END; END. าArpaSMTPSyntaxImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: HGM, May 8, 1985 0:30:18 am PDT Last Edited by: DCraft, November 22, 1983 1:59 pm Last Edited by: Taft, February 3, 1984 1:17:33 pm PST Hal Murray July 2, 1985 2:31:00 am PDT John Larson, September 29, 1988 0:25:11 am PDT Beware of bounds fault - count is a NAT 1) Bitch if name of first host on return path isn't recognized by name servers 2) Make sure it ends in .ARPA (or such) so GV will send rejections back via us ArpaName.AliasToName[host].status = bogus => { ArpaSMTPSupport.Log[important, "BOGUS host name in return path: ", host]; }; @A,@B:User@Host => comma would be bogus in rejection msgs arpa _ Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName]; User@Host.ARPA or @Mumble:User@Host.ARPA Can't fixup tail of name: it might be in a different name space arpa _ Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName]; User@Host (no .ARPA) Somebody fed us an alias rather than the truth. Normalize it. Yetch. Somebody fed us a bogus name. Rejection msgs from GV will probably not work. arpa _ Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName]; Strip "..."@Xerox.ARPA to be kind to other mailers (the ones that aren't bright enough to process quotes) Hackery to translate user@host.CSNet into user%host@relay.cs.net Beware: There is similar code in MTTReeOpsImpl Without the next lines, mail to ourselves will go around again!!! Good for testing, but.. Without the next line, mail to ourselves will go around again!!! Good for testing, but.. See pg 10 of RFC 822. An Atom is anything except specials, SPACE, and CTLs. This won't do the right things with Foo..bar NB: Remove following , or : too No quote checking on this path สw˜head™Icodešœ ฯmœ1™šžœ˜šžœž˜Mšžœ:˜?——Mšžœ'˜-—š ก œžœžœ žœžœžœ˜Bšžœ˜šžœž˜Mšžœ:˜?——Mšžœžœ(˜7—Mš œ žœžœ žœžœ˜0šก œžœ˜1Mšœžœžœ ˜Mšž˜šžœ˜Mšžœ8žœ ˜Gšœ˜MšœL˜LMšžœ˜——š ก œžœžœžœžœ˜3Mšœ<žœžœ$˜‚Mšœ žœžœžœ ˜AMšžœžœžœ˜C—šกœžœข.˜BMšœžœ˜*Mšœ žœ˜$Mšœžœ]˜yMšœžœžœ˜.šžœž˜Mšœ'™'šœ žœ!˜0Mšœ žœ ˜.—Mšžœžœžœžœ#˜BM˜$M˜%Mšžœ˜—M˜M˜"—šžœž˜šœ ˜ Mšœžœ˜MšœQ˜QMšœ˜—šœ ˜ Mšœžœ˜Mšœ&˜&Mšœ˜—šœ ˜ Mšœžœ˜Mšœ&˜&Mšœ˜—˜Mšœ žœ˜*Mšœžœ˜šžœž˜Mšœ8žœ˜B——Mšœžœ ˜-Mšœ'žœ ˜8Mšœžœ3˜TMšœ#žœ˜=Mšžœžœ ˜+—Mšžœ ž˜Mšžœ˜—š กœžœžœžœžœžœ˜?Mšž˜M™NM™NMšœžœ˜Mšœžœ˜ šžœžœข˜2šžœžœžœ ž˜Mšœžœ˜ šžœž˜Mšœ-žœ˜5Mšžœžœ˜—šžœžœ˜MšœG˜G—Mšžœ˜ ——šžœข ˜š žœžœž œžœ ž˜'Mšœžœ˜Mšžœžœ1žœ˜GMšžœ˜ M˜——šžœžœž˜Mšžœžœžœ˜šœ˜MšœW˜W—šœ.™.MšœL™L—Mšžœžœ˜—šžœžœž˜šœ˜Mšœ9™9Mšœ;™;M˜—šœ˜M™(Mšœ ˜ —šœ˜M™?Mšœ;™;M˜—šžœ˜ M™M™=Mšž˜Mšœžœ˜Mšœžœ˜š žœžœž œžœ ž˜'Mšœžœ˜šžœžœ˜M˜M˜-šžœž˜Mšžœžœ!ข ˜H—Mšœ˜šžœžœ˜Mšœ%™%Mšœžœ™.Mšœ;˜;Mšœ<™žœžœžœ˜SJšžœžœ˜J˜M˜—š ก œžœžœžœžœ˜:Mšœ žœ˜ Mšœ žœ˜ Mšžœžœžœ˜)Mšžœ1˜7—š กœžœ žœžœ žœ˜9Mšœ žœ˜ Mšœ žœ˜ Mšœžœ˜ Mšžœžœžœžœ˜1Mšœ$˜$šžœ žœ žœ˜ Mšœžœ$˜/Mš žœžœžœžœžœ˜7—Mšžœžœ˜—š ก œžœžœžœžœ˜:Mšœ žœ˜ Mšœ žœ˜ Mšžœžœžœ˜)M™Mšžœ?˜E—š ก œžœžœžœžœ˜=Mšœžœ˜Mšœ žœ˜M˜ šžœžœข˜2šžœžœžœ ž˜Mšœžœ˜ šžœž˜šœ ˜ M˜ Mšžœžœžœ(˜KMšœ˜Mšžœžœ˜.M˜-Mšœ™Mšœ6˜6Mšžœ˜ —Mšžœžœ˜—Mšžœžœžœ˜1Mšžœ˜ ——š žœžœž œžœ ž˜'Mšœž œ ž˜Mšžœ žœžœ˜šžœžœข ˜M˜M˜-Mšžœžœžœ(˜KMšœ˜Mšžœžœ˜.Mšœข˜-M˜!Mšžœ˜ —Mšžœ˜—š žœžœž œžœ ž˜'Mšœž œ ž˜Mšžœ žœžœ˜šžœžœข˜%M˜M˜-Mšžœžœžœ(˜KMšœ˜Mšžœžœ˜.M˜M˜!Mšžœ˜ —Mšžœ˜—Mšœžœฯiะci˜#—š ก œžœžœžœžœ˜8Mšž˜Mšœžœ˜ šžœžœžœ ž˜Mšœž œž˜!šžœž˜Mšœ+žœžœžœข!˜[Mšœžœžœžœข˜Mšœ žœžœžœข˜Mšžœžœ˜—Mšžœ˜—Mšžœžœ˜ Mšžœ˜—š ก œž œžœžœžœ˜=Mšž˜Mšžœžœžœžœ˜&Mšœ ข˜8šžœžœ˜Mšžœžœžœ˜@—Mšžœžœžœ ˜HMšžœžœžœ˜'Mšœ:˜:Mšžœ˜ Mšžœ˜—š ก œžœžœžœžœ˜5š žœžœžœžœ(žœžœž˜VJšœ žœ˜Jšžœžœžœžœ˜,Jšžœ˜—Jšžœžœ˜J˜J˜—M˜š ก œžœžœžœžœ˜6Mšž˜Mšœžœ˜M˜ šžœžœž œ ž˜'Mšžœ žœ˜!Mšžœ˜—Mšœ9˜9Mšžœ˜—š ก œžœžœžœžœ˜4Mšž˜Mšœžœ˜M˜ šžœžœ ž˜Mšžœžœžœ˜Mšžœžœžœ˜Mšžœ˜—Mšœ?˜?Mšžœ˜—J˜š กœžœžœžœžœ˜7Mšž˜Mšœžœ˜Mšœž˜M˜ šžœžœ ž˜Mšžœžœžœ˜Mšžœžœžœ˜Mšžœ˜—šžœžœž œ ž˜'šžœ ž ˜Mšœ˜Mšœ,ข˜HMšžœ˜—Mšžœžœ#˜2Mšžœ˜—Mšœ?˜?Mšœ,˜,Mšžœ˜—J˜š กœžœžœžœžœ˜7Jšž˜Jš žœ žœžœžœžœ˜-Jšžœ˜J˜—š กœžœžœžœžœ˜7Jšž˜Jš žœ žœžœžœžœ˜-Jšžœ˜—Mšžœ˜——…—Bž`็