ArpaSMTPSyntaxImpl.mesa
Copyright © 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, July 19, 1988 3:01:41 pm PDT
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["<<<unexpected EOS>>>\n"]; GOTO Return};
SyntaxError => {
out.PutRope["<<<syntax error: "]; out.PutRope[reason]; 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
Beware of bounds fault - count is a NAT
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["<probably not worth printing>\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
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
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];
ArpaName.AliasToName[host].status = bogus => {
ArpaSMTPSupport.Log[important, "BOGUS host name in return path: ", host]; };
ENDCASE => NULL;
SELECT
TRUE
FROM
Rope.Find[raw, ","] # -1 => {
@A,@B:User@Host => comma would be bogus in rejection msgs
arpa ← Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName];
};
ValidDomain[raw] =>
User@Host.ARPA or @Mumble:User@Host.ARPA
arpa ← raw;
raw.Fetch[0] = '@ => {
Can't fixup tail of name: it might be in a different name space
arpa ← Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName];
};
ENDCASE =>
User@Host (no .ARPA)
Somebody fed us an alias rather than the truth. Normalize it.
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 {
Yetch. Somebody fed us a bogus name.
Rejection msgs from GV will probably not work.
ArpaSMTPSupport.Log[important, "Bogus return path: ", raw];
arpa ← Rope.Cat["\"", raw, "\"@", ArpaConfig.ourLocalName];
}
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
Strip "..."@Xerox.ARPA to be kind to other mailers
(the ones that aren't bright enough to process quotes)
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 {
Hackery to translate user@host.CSNet into user%host@relay.cs.net
Beware: There is similar code in MTTReeOpsImpl
raw ← Redirect[raw, ".BITNET", ArpaConfig.bitnetGateway];
raw ← Redirect[raw, ".CSNet", ArpaConfig.csnetGateway];
raw ← Redirect[raw, ".UUCP", ArpaConfig.uucpGateway] };
IF raw.Fetch[0] # '@
THEN {
Without the next lines, mail to ourselves will go around again!!! Good for testing, but..
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 {
Without the next line, mail to ourselves will go around again!!! Good for testing, but..
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
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
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];
NB: Remove following , or : too
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)];
No quote checking on this path
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.