TITLE HSTNAM TOPS-20 host name lookup routines
	SUBTTL Written by Mark Crispin - December 1982/March 1990

; Copyright (C) 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1990 Mark Crispin
; All rights reserved.
;
;  This software, in source and binary form, is distributed free of charge.
; The binary form of this software may be incorporated into public-domain
; software and the source may be used for reference purposes.  Copies may
; be made of the source provided this copyright notice is included.  Wholesale
; copying of the routines in this software or usage of this software in a
; proprietary product without prior permission is prohibited.

;  This module is an attempt to provide a common and consistant host name/host
; address lookup interface for all network software.  For the most part, these
; modules have been designed like jsi.  They take their arguments in AC's in a
; fairly consistant manner.  Only the documented returned value AC's are
; changed; everything else is unaffected.  Note that in a failure return the
; returned value AC's are undefined; software should not be written to assume
; any side-effects of a failure as this may change from release to release.
;
;  The only real difference from a JSYS is that since these are subroutines
; invoked by CALL and use the stack any stack references (e.g. STKVAR) must be
; made absolute prior to using the routines.  For example, assuming FOOSTR is
; a string in a STKVAR:
;  Wrong:
;	MOVE A,[POINT 7,FOOSTR]
;	CALL $xxxxx
;  Right:
;	HRROI A,FOOSTR
;	CALL $xxxxx
;
;  In addition to the individual routines for each network, there are also
; global routines allowing name/address lookups for multiple networks.  In
; general, software should be written to use the global routines rather than
; a specific network's routine if there is any possibility that software will
; ever be used for more than one network.  The additional generality gained
; costs nothing but a minor bit of discipline on the part of the programmer
; and will save future programmers much grief.
;
;  One firm rule: absolutely NO software should do host lookups without going
; through this module.  In particular, no software should be written to access
; "host tables" (e.g. SYSTEM:HOSTSn.BIN).  Any software which knows about the
; format, or depends upon existance, of host tables is guaranteed to break
; without warning.
;
;  This module tries to be "internet" (not to be confused with Internet).  In
; order to provide a means of specifying an explicit name registry, top-level
; domains prefixed with an "#" are used.  These are relative domains, not to
; be confused with Internet domains which are absolute.  Eventually, absolute
; addressing will come into being, but at present that requires considerably
; more cooperation from the various networks than is presently forthcoming.
	SUBTTL Definitions

	SEARCH MACSYM,MONSYM	; system definitions
	SALL			; suppress macro expansions
	.DIRECTIVE FLBLST	; sane listings for ASCIZ, etc.

IFNDEF HSTNML,<HSTNML==^D64>	; length of a host name (64 required minimum)
 HSTNMW==<HSTNML/5>+1		; host name length in words

; AC definitions

A=:1				; JSYS, temporary AC's
B=:2
C=:3
D=:4
P=:17				; stack pointer

; Non-standard operating system definitions


PN%NAM==:1B0
PN%FLD==:1B1
PN%OCT==:1B2

IFNDEF PUPNM%,<
	OPDEF PUPNM% [JSYS 443]

>;IFNDEF PUPNM%

IFNDEF CHANM%,<
	OPDEF CHANM% [JSYS 460]

.CHNPH==:0			; return local site primary name and number
.CHNSN==:1			; Chaosnet name to number
.CHNNS==:2			; Chaosnet number to primary name
>;IFNDEF CHANM%

IFNDEF GTDOM%,<
	OPDEF GTDOM% [JSYS 765]

GD%LDO==:1B0			; local data only (no resolve)
GD%MBA==:1B1			; must be authoritative (don't use cache)
GD%RBK==:1B6			; resolve in background
GD%EMO==:1B12			; exact match only
GD%RAI==:1B13			; uppercase output name
GD%QCL==:1B14			; query class specified
GD%STA==:1B16			; want status code in AC1 for marginal success
  .GTDX0==:0			; total success
  .GTDXN==:1			; data not found in namespace (authoritative)
  .GTDXT==:2			; timeout, any flavor
  .GTDXF==:3			; namespace is corrupt

.GTDWT==:12			; resolver wait function
.GTDPN==:14			; get primary name and IP address
.GTDMX==:15			; get MX (mail relay) data
  .GTDLN==:0			; length of argblk (inclusive)
  .GTDTC==:1			; QTYPE (ignored for .GTDMX),,QCLASS
  .GTDBC==:2			; length of output string buffer
  .GTDNM==:3			; canonicalized name on return
  .GTDRD==:4			; returned data begins here
  .GTDML==:5			; minimum length of argblock (words)
.GTDAA==:16			; authenticate address
.GTDRR==:17			; get arbitrary RR (MIT formatted RRs)
.GTDVN==:20			; validate name for arbitrary QTYPE(s)
  .GTDV0==:1B19			; lowest allowable value
  .GTDVH==:.GTDV0+1		; validate host (A,MX,WKS,HINFO)
  .GTDVZ==:.GTDV0+2		; validate zone (SOA,NS)
>;IFNDEF GTDOM%

	.PSECT CODE		; enter pure CODE PSECT
	SUBTTL Protocol-independent routines

; $GTPRO - Get host address and find protocol supported by host
; Accepts:
;	A/ host name string
;	C/ pointer to protocol list or -1 to try all supported protocols
;	CALL $GTPRO
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B,
;			protocol address in C
;
;  The protocol list is in the form:
;	[ASCIZ/protocol1/],,data1
;	[ASCIZ/protocol2/],,data2
;		...
;	[ASCIZ/protocoln/],,datan
;	0			; end of table

$GTPRO::STKVAR <HSTPTR,PROPTR>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save pointer
	SKIPG C			; user want all known protocols?
	 MOVEI C,$PRTAB		; yes, use our internal table
	DO.
	  SKIPN B,(C)		; get protocol entry
	   RET			; end of list, return failure
	  MOVEM C,PROPTR	; save since TBLUK% clobbers C
	  HLROS B		; make string pointer to name
	  MOVEI A,$PRRTS	; our known table
	  TBLUK%		; see if can find entry in table
	   ERJMP R		; strange failure
	  MOVE C,PROPTR		; get back protocol pointer
	  IFXE. B,TL%NOM!TL%AMB	; found this protocol in table?
	    HRRZ B,(A)		; yes, get pointer to routines to call
	    HLRZ B,(B)		; get string/address routine
	    MOVE A,HSTPTR	; get pointer to host name
	    CALL (B)		; see if name known under this protocol
	    IFSKP. <RETSKP>	; return success
	  ENDIF.
	  AOJA C,TOP.		; not found here, bump pointer and try again
	ENDDO.

	ENDSV.

; $GTNAM - Get name of host given its protocol
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	C/ protocol list item pointer
;	CALL $GTNAM
; Returns +1: Failed
;	  +2: Success, updated pointer in A
;
;  For compatibility with the $GTPRO call and the possible convenience of
; applications programs, a negative argument ("try all protocols") is allowed
; in C.  However, this is only valid if B is also negative ("local host")
; since different networks have different addressing conventions.  If this is
; the case, $GTNAM becomes $GTLCL.

$GTNAM::IFL. C			; caller want to try all protocols?
	  JUMPL B,$GTLCL	; yes, use $GTLCL if local host desired
	  RET			; else fail, meaningless call
	ENDIF.
	SAVEAC <C>
	STKVAR <HSTPTR,HSTNUM>
	TXC A,.LHALF		; is destination pointer's LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save pointer
	MOVEM B,HSTNUM		; save host address
	MOVEI A,$PRRTS		; table of known protocols
	HLRO B,(C)		; protocol to look up
	TBLUK%			; see if can find entry in table
	 ERJMP R		; strange failure
	JXN B,TL%NOM!TL%AMB,R	; fail if protocol not found in table?
	HRRZ C,(A)		; get pointer to routines to call
	HRRZ C,(C)		; get canonicalize,,address/string routines
	HRRZ C,(C)		; get address/string routine
	MOVE A,HSTPTR		; get pointer to host name
	MOVE B,HSTNUM
	CALLRET (C)		; see if name known under this protocol

	ENDSV.

; $GTCAN - Get canonical name for host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	C/ pointer to protocol list
;	   or -1 to try all supported protocols
;	   or 0 to try all supported protocols w/o returning an address
;	CALL $GTCAN
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B
;			if appropriate, protocol address in C

$GTCAN::SKIPN C			; user want mail validation?
	 MOVEI C,$MATAB		; yes, use internal table
	SKIPG C			; user want all known protocols?
	 MOVEI C,$PRTAB		; yes, use our internal table
	CAIN C,$MATAB		; user wants host address returned?
	 SAVEAC <B>		; no - so leave argument untouched
	STKVAR <HSTPTR,DSTPTR,PROPTR>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save pointer
	TXC B,.LHALF		; is destination LH -1?
	TXCN B,.LHALF
	 HRLI B,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM B,DSTPTR		; save pointer
	DO.
	  SKIPN B,(C)		; get protocol entry
	   RET			; end of list, return failure
	  MOVEM C,PROPTR	; save since TBLUK% clobbers C
	  HLROS B		; make string pointer to name
	  MOVEI A,$PRRTS	; our known table
	  TBLUK%		; see if can find entry in table
	   ERJMP R		; strange failure
	  IFXE. B,TL%NOM!TL%AMB	; found this protocol in table?
	    HRRZ C,(A)		; yes, get pointer to routines to call
	    HRRZ C,(C)		; get canonicalize,,address/string routines
	    HLRZ C,(C)		; get canonicalize routine
	    MOVE A,HSTPTR	; get pointer to host name
	    MOVE B,DSTPTR	; and where to stash it
	    CALL (C)		; see if name known under this protocol
	  ANSKP.
	    MOVE C,PROPTR	; get back protocol pointer for return
	    RETSKP		; return success
	  ENDIF.
	  MOVE C,PROPTR		; get back protocol pointer
	  AOJA C,TOP.		; not found here, bump pointer and try again
	ENDDO.

	ENDSV.

; $GTLCL - Get name of local host
; Accepts:
;	A/ pointer to destination host string
;	CALL $GTLCL
; Returns +1: Failed (shouldn't happen)
;	  +2: Success, with updated pointer in A
;  $GTLCL will always return a name, even if there are no networks at
; all.  This means that any software that uses host names that is
; meaningful in a non-network environment (e.g. the mailer) must
; understand the local name as a special concept independent of $GTPRO.

$GTLCL::SAVEAC <B,C,D>
	STKVAR <HSTPTR,HSTNUM>
	TXC A,.LHALF		; is destination pointer's LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save pointer
	MOVEI D,$PRTAB		; our protocol table
	DO.
	  MOVEI A,$PRRTS	; look up protocol
	  SKIPN B,(D)		; get protocol entry
	   EXIT.		; end of list
	  HLROS B		; make string pointer to name
	  TBLUK%
	   ERJMP R		; strange failure
	  JXN B,TL%NOM!TL%AMB,R	; very strange if protocol not found
	  HRRZ C,(A)		; get pointer to routines to call
	  HRRZ C,(C)		; get canonicalize,,address/string routines
	  HRRZ C,(C)		; get address/string routine
	  MOVE A,HSTPTR		; pointer to destination string
	  SETO B,		; translate local host
	  CALL (C)		; see if we're known under this protocol
	  IFSKP. <RETSKP>	; we are, return success
	  AOJA D,TOP.		; try next protocol
	ENDDO.
	MOVE A,HSTPTR		; try a hostname file
	HRROI B,[ASCIZ/SYSTEM:HOSTNAME.TXT/]
	CALL $CPFIL
	IFSKP. <RETSKP>
	MOVE A,HSTPTR		; lose, this is the last resort
	HRROI B,[ASCIZ/TOPS-20/] ; default name string
	SETZ C,			; no limit
	SOUT%			; copy the string
	 ERJMP R		; can't fail
	RETSKP

	ENDSV.
	SUBTTL Protocol-specific routines

; Tables of known protocols

; TBLUK% format table when desired naming registry is given

DEFINE DN (NAME,ADRNAM,NAMADR,CANNAM) <
 [ASCIZ/'NAME'/],,['NAMADR',,['CANNAM',,'ADRNAM']]
>;DEFINE DN

$PRRTS::NPROTS,,NPROTS
	DN Chaos,$CHSNS,$CHSSN,$CHSCA	; Chaosnet
	DN DECnet,$DECNS,$DECSN,$DECCA	; DECnet
	DN Internet,$INTNS,$INTSN,$INTCA ; Internet A/MX/WKS/HINFO (no address)
	DN MX,$MXNS,$MXSN,$MXCA		; MX Internet
	DN Pup,$PUPNS,$PUPSN,$PUPCA	; Pup Ethernet
	DN Special,$SPCNS,$SPCSN,$SPCCA	; Special external network
	DN TCP,$GTHNS,$GTHSN,$GTHCA	; TCP/IP Internet
NPROTS==<.-$PRRTS>-1

;  $PRTAB and $MATAB are default protocol tables; they differ in that the
; address returned by $MATAB is undefined -- this is used by mail and any
; other application that merely want to validate the name.
;  The tables are in the default communication order.  The Special network
; is first so it overrides any other registries  This allows use of the
; Special network to do custom delivery to a defined host, and also prevents
; lossage when some random foreign host comes up with the same name.
;  Note: you should probably set up an appropriate HIGHER-LEVEL-DOMAIN.TXT
; file in at least the MAILS: directory so that a fully-qualified domain name
; appears in local mail.

DEFINE DP (NAME) <
 [ASCIZ/'NAME'/],,0
>;DEFINE DP

$PRTAB::DP Special
	DP MX
	DP TCP
	DP Pup
	DP Chaos
	DP DECnet
	0			; terminate for $GTPRO

$MATAB::DP Special
	DP Internet
	DP Pup
	DP Chaos
	DP DECnet
	0			; terminate for $GTPRO
	SUBTTL Protocol-specific routines - Internet

; $GTHNS - Translate Internet host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $GTHNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$GTHNS::SAVEAC <C,D>
	STKVAR <HSTPTR,HSTNUM>
	TXC A,.LHALF		; is string pointer LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save host pointer
	MOVEM B,HSTNUM		; save host address
	CAME B,[-1]		; want local address?
	IFSKP.
	  MOVX A,.GTHSZ		; yes, get local address so can output
	  CALL $GTHST		;  bracketed if unnamed local host
	   RET			; not on Internet
	  JUMPN A,R		; can't have indeterminate local address!
	  MOVEM D,HSTNUM	; set new host address
	ENDIF.
	MOVX A,.GTHNS		; number to name conversion
	MOVE B,HSTPTR		; destination pointer
	MOVE C,HSTNUM		; host address
	CALL $GTHST
	IFSKP.
	ANDE. A			; must be determinate
	  MOVEM C,HSTNUM	; return host address
	  MOVE A,B		; set up byte pointer for $ARDOM
	ELSE.
	  MOVE A,HSTPTR		; name unknown, output literal
	  MOVE B,HSTNUM
	  CALL $GTHWL
	ENDIF.
	HRROI B,[ASCIZ/Internet/] ; add Internet domain
	CALL $ARDOM		; add domain, leave pointer in A
	MOVE B,HSTNUM		; and host address
	RETSKP

	ENDSV.

; $GTHSN - Translate Internet host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $GTHSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$GTHSN::SAVEAC <C,D>		; preserve these
	STKVAR <HSTPTR,<HSTSTR,HSTNMW>>
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	MOVEM B,HSTPTR		; save pointer
	SETO B,			; back pointer up by one
	ADJBP B,HSTPTR
	MOVEM B,HSTPTR		; save updated pointer
	HRROI A,HSTSTR		; now remove Internet domain
	HRROI B,[ASCIZ/Internet/]
	CALL $RRDOM
	 RET
	HRROI A,HSTSTR		; prepare to read literal
	CALL $GTHRL
	IFNSK.
	  MOVX A,.GTHSN		; translate name to number
	  HRROI B,HSTSTR	; foreign host name
	  CALL $GTHST
	   RET
	  IFN. A		; indeterminate information?
	    MOVE B,$UKHST	; yes, return unknown address
	  ELSE.
	    MOVE B,C		; get host address in proper AC
	  ENDIF.
	ENDIF.
	MOVE A,HSTPTR		; get back updated pointer
	RETSKP

	ENDSV.

$UKHST::BYTE (4) 7 (8) 0,0,0,0	; the "unknown" Internet host address

; $GTHCA - Get canonical name for Internet host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $GTHCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

$GTHCA::SAVEAC <C,D>
	STKVAR <DSTPTR,<HSTSTR,HSTNMW>>
	MOVEM B,DSTPTR		; save destination pointer
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	HRROI A,HSTSTR		; now remove Internet domain
	HRROI B,[ASCIZ/Internet/]
	CALL $RRDOM
	 RET
	HRROI A,HSTSTR		; prepare to read literal
	CALL $GTHRL
	IFSKP.
	  MOVE A,DSTPTR		; get destination pointer
	  CALL $GTHNS		; translate to name for this address
	   RET			; shouldn't ever fail
	  RETSKP
	ENDIF.
	MOVX A,.GTDPN		; get primary name function
	HRROI B,HSTSTR		; source
	MOVE D,DSTPTR		; destination
	CALL $GTHST		; go get the poop
	 RET			; failed
	IFN. A
	  MOVE A,DSTPTR		; copy to canonical name
	  HRROI B,HSTSTR
	  SETZ C,
	  SOUT%
	  MOVE B,$UKHST		; host address is the unknown host
	ELSE.
	  MOVE A,D		; return destination pointer
	  HRROI B,[ASCIZ/Internet/]
	  CALL $ARDOM
	  MOVE B,C		; and host address
	ENDIF.
	RETSKP			; success

	ENDSV.

; $GTHWL - Write host literal
; Accepts:
;	A/ destination string pointer
;	B/ host address
;	CALL $GTHRL
; Returns +1: Always, updated pointer in A

$GTHWL::SAVEAC <B,C,D>
	STKVAR <HSTNUM>
	MOVEM B,HSTNUM
	MOVEI B,"["		; start bracketed number
	IDPB B,A
	LDB B,[POINT 8,HSTNUM,11] ; get first byte
	MOVX C,^D10		; output host parts in decimal
	NOUT%			; output it
	 ERJMP R
	MOVEI D,"."		; delimiting dot
	IDPB D,A		; add delimiting dot
	LDB B,[POINT 8,HSTNUM,19] ; get next byte
	NOUT%			; output it
	 ERJMP R
	IDPB D,A		; add delimiting dot
	LDB B,[POINT 8,HSTNUM,27] ; get next byte
	NOUT%			; output it
	 ERJMP R
	IDPB D,A		; add delimiting dot
	LDB B,[POINT 8,HSTNUM,35] ; get final byte
	NOUT%			; output it
	 ERJMP R
	MOVEI D,"]"		; terminate bracketed number
	IDPB D,A
	RET

	ENDSV.

; $GTHRL - Read host literal
; Accepts:
;	A/ host string pointer
;	CALL $GTHRL
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$GTHRL::SAVEAC <C>
	STKVAR <HSTNUM>
	TXC A,.LHALF		; is destination pointer's LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	ILDB B,A		; get opening character
	CAIE B,"#"		; moby number following?
	IFSKP.
	  MOVX C,^D10		; read number in decimal
	  NIN%			; do it
	   ERJMP R		; failed
	  LDB C,A		; get terminating byte
	  JUMPN C,R		; string has non-numeric text in it
	  RETSKP		; return success
	ENDIF.
	CAIE B,"["		; bracketed host following?
	 RET			; no, fail
	SETZM HSTNUM		; clear out existing crud in number
	MOVEI C,^D10		; in decimal
	NIN%			; input number
	 ERJMP R		; failed
	JXN B,<<MASKB 0,27>>,R	; disallow if not 8-bit number
	DPB B,[POINT 8,HSTNUM,11] ; store byte
	LDB B,A			; get terminating byte
	CAIE B,"."		; proper terminator?
	 RET			; return failure
	NIN%			; input number
	 ERJMP R		; failed
	JXN B,<<MASKB 0,27>>,R ; disallow if not 8-bit number
	DPB B,[POINT 8,HSTNUM,19] ; store byte
	LDB B,A			; get terminating byte
	CAIE B,"."		; proper terminator?
	 RET			; return failure
	NIN%			; input number
	 ERJMP R		; failed
	JXN B,<<MASKB 0,27>>,R	; disallow if not 8-bit number
	DPB B,[POINT 8,HSTNUM,27] ; store byte
	LDB B,A			; get terminating byte
	CAIE B,"."		; proper terminator?
	 RET			; return failure
	NIN%			; input number
	 ERJMP R		; failed
	JXN B,<<MASKB 0,27>>,R	; disallow if not 8-bit number
	DPB B,[POINT 8,HSTNUM,35] ; store final byte
	LDB B,A			; get terminating byte
	CAIE B,"]"		; proper terminator?
	 RET			; return failure
	ILDB B,A		; make sure tied off with null
	JUMPN B,R
	MOVE B,HSTNUM		; return host address
	RETSKP			; return success

	ENDSV.

; $GTHST - Jacket into GTDOM% and GTHST% jsi
; Accepts:
;	A/ function code
;	B-D/ function arguments
;	CALL $GTHST
; Returns +1: Failed
;	  +2: Success, A/ status, updated arguments in B-D

; Control flags

$GTDOK::-1			; non-zero => OK to do GTDOM% 
$GTHOK::-1			; non-zero => OK to do GTHST%
$GTMOK::0			; non-zero => mailer, indeterminate answer OK
$GTFOK::0			; non-zero => finger, don't block on .GTHNS

$GTHST::CALL $DOGTD		; try the domain system first
	IFSKP.
	  CAIN A,.GTDXN		; failure?
	   RET			; yes, return that we have lost
	  RETSKP		; otherwise say we won
	ENDIF.
	CALLRET $DOGTH		; otherwise try the host table

; $DOGTD - Jacket into GTDOM% jsys
; Accepts:
;	A/ function code
;	B-D/ function arguments
;	CALL $DOGTD
; Returns +1: Failed, no AC's clobbered
;	  +2: Success, A/ status, updated arguments in B-D

$DOGTD::SKIPN $GTDOK		; is GTDOM% OK?
	 RET			; no, always fail
	STKVAR <<ACS,4>,STAT>
	DMOVEM A,ACS
	DMOVEM C,2+ACS
	SKIPE $GTFOK		; don't want blocking on address to name?
	 CAIE A,.GTHNS		; yes, is this address to name?
	IFSKP.
	  TXO A,GD%RBK		; resolve in background
	  GTDOM%		; give resolver a kick
	   ERJMP .+1
	  DMOVE A,ACS		; restore the AC's
	  DMOVE C,2+ACS
	  TXO A,GD%LDO		; note we want to use local data only
	ENDIF.
	TXO A,GD%STA		; want status on failure
	GTDOM%			; do the domain thing
	IFNJE.
	  CAIE A,.GTDX0		; total success?
	   CAIN A,.GTDXN	; or total failure?
	    RETSKP		; we have a definite answer
	  SKIPN $GTMOK		; is a "maybe" OK?
	ANSKP.
	  MOVEM A,STAT		; yes, save status code
	  DMOVE A,ACS		; see if host table can help us first
	  DMOVE C,2+ACS
	  CALL $DOGTH		; well, does it?
	   MOVE A,STAT		; if not, get the status code back
	ELSE.
	  DMOVE A,ACS		; domains have failed us, restore AC's
	  DMOVE C,2+ACS		;  so we can try the host table
	  RET	
	ENDIF.
	RETSKP

	ENDSV.

; $DOGTH - Jacket into GTHST% jsys
; Accepts:
;	A/ function code
;	B-D/ function arguments
;	CALL $DOGTH
; Returns +1: Failed
;	  +2: Success, A/ .GTDX0, updated arguments in B-D

$DOGTH::STKVAR <FUNC,HSTPTR,DSTPTR,HSTADR>
	SKIPN $GTHOK		; OK to do GTHST%?
	 RET			; no, always fail
	CAIL A,.GTDPN		; one of the new functions?
	 TXO A,GD%STA		; yes, return status code in A
	MOVEM A,FUNC		; note function code
	GTHST%			; try the montior
	IFNJE.
	  CAME A,FUNC		; won, did it return something?
	   RETSKP		; must be a new monitor
	ELSE.
	  HRRZ A,FUNC		; get back function code
	  CAIE A,.GTDVN		; validate name?
	   CAIN A,.GTDPN	; or primary name translation?
	  IFSKP. <RET>		; no, give up
	  MOVEM D,DSTPTR	; save destination pointer
	  MOVX A,.GTHSN		; translate name to number
	  GTHST%
	   ERJMP R
	  MOVEM B,HSTPTR	; updated source pointer
	  MOVEM C,HSTADR	; host address
	  MOVX A,.GTHNS		; number to name conversion
	  MOVE B,DSTPTR		; destination pointer
	  GTHST%
	  IFNJE.
	    MOVEM B,DSTPTR	; updated destination pointer
	  ELSE.
	    MOVE A,DSTPTR	; name unknown, output literal
	    MOVE B,HSTADR	; host address
	    CALL $GTHWL
	    MOVEM A,DSTPTR	; updated destination pointer
	  ENDIF.
	  MOVE B,HSTPTR		; updated source pointer
	  MOVE C,HSTADR		; host address
	  MOVE D,DSTPTR		; updated destination pointer
	ENDIF.
	MOVX A,.GTDX0		; GTHST% success is always total success
	RETSKP

	ENDSV.

; $MXNS - Translate MX host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $MXNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$MXNS::	CAMN B,[-1]		; want local address?
	IFSKP.
	  TMSG <%HSTNAM: Meaningless call to $MXNS
>				; otherwise this is totally bogus!
	  RET
	ENDIF.
	CALLRET $GTHNS		; yes, perhaps somebody might want this

; $MXSN - Translate MX host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $MXSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$MXSN::	SAVEAC <A>
	STKVAR <<HSTSTR,HSTNMW>>
	HRROI B,HSTSTR		; set up destination as dummy
	CALLRET $MXCA		; enter canonicalization routine

	ENDSV.

; $MXCA - Get canonical name for MX host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $MXCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

MXBLEN==<2*HSTNMW>+1

$MXCA::	SAVEAC <C,D>
	STKVAR <DSTPTR,HSTADR,<HSTSTR,HSTNMW>,<HSTBUF,MXBLEN>,<ARGBLK,.GTDML>>
	MOVEM B,DSTPTR		; save destination pointer
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	HRROI A,HSTSTR		; now remove Internet domain
	HRROI B,[ASCIZ/Internet/]
	CALL $RRDOM
	 RET
	ILDB A,A		; sniff at first character
	CAIE A,"#"		; looks like a literal?
	 CAIN A,"["
	  RET			; yes, can't possibly be MX then!!
	MOVX A,.GTDML		; set up length of argument block
	MOVEM A,.GTDLN+ARGBLK
	SETZM .GTDTC+ARGBLK	; no special query type/class
	MOVX A,<MXBLEN*5>-1	; get length of our buffer
	MOVEM A,.GTDBC+ARGBLK
	SETZM .GTDNM+ARGBLK	; this gets returned
	SETZM .GTDRD+ARGBLK	; so does this
	MOVX A,.GTDMX		; want MX poop
	HRROI B,HSTSTR		; source pointer
	HRROI C,HSTBUF		; destination string buffer
	MOVEI D,ARGBLK		; argument block
	CALL $GTHST
	 RET
	MOVE B,$UKHST		; return the unknown host as default address
	MOVEM B,HSTADR
	IFN. A			; have determinate information?
	  MOVE A,DSTPTR		; indeterminate, just copy the argument
	  HRROI B,HSTSTR
	  SETZ C,
	  SOUT%
	ELSE.
	  MOVE A,DSTPTR		; copy to canonical name
	  MOVE B,.GTDNM+ARGBLK	; get pointer to canonical string
	  MOVX C,HSTNML+1	; up to this many characters
	  SETZ D,		; terminate on null
	  SOUT%
	   ERJMP R		; percolate failure up to caller
	  JUMPE C,R		; string too long if exhausted
	  MOVEM A,DSTPTR	; save updated pointer
	  MOVE A,.GTDRD+ARGBLK	; get pointer to relay
	  CALL $GTHSN		; get its address
	  IFNSK.
	    MOVE A,DSTPTR	; return the correct pointer
	  ELSE.
	    MOVEM B,HSTADR	; save host address
	    SETO A,		; I hate this behavior of SOUT%
	    ADJBP A,DSTPTR
	    HRROI B,[ASCIZ/Internet/]
	    CALL $ARDOM
	  ENDIF.
	ENDIF.
	MOVE B,HSTADR
	RETSKP

	ENDSV.

; $INTNS - Translate Internet mail host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $INTNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$INTNS::TMSG <%HSTNAM: Meaningless call to $INTNS
>				; totally bogus!
	RET

; $INTSN - Translate Internet mail host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $INTSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$INTSN::TMSG <%HSTNAM: Meaningless call to $INTSN
>				; totally bogus!
	RET

; $INTCA - Get canonical name for Internet mail host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $INTCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A

MXBLEN==<2*HSTNMW>+1

$INTCA::SAVEAC <B,C,D>
	TXC A,.LHALF		; is destination pointer's LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVE C,A
	ILDB C,C		; sniff at first character
	CAIE C,"#"		; looks like a literal?
	 CAIN C,"["
	IFNSK. <CALLRET $GTHCA>	; it is, use the physical routine
	STKVAR <DSTPTR,<HSTSTR,HSTNMW>>
	MOVEM B,DSTPTR		; save destination pointer
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	HRROI A,HSTSTR		; now remove Internet domain
	HRROI B,[ASCIZ/Internet/]
	CALL $RRDOM
	 RET
	MOVX A,.GTDVN		; validate name
	HRROI B,HSTSTR		; source pointer
	MOVX C,.GTDVH		; validate host
	MOVE D,DSTPTR		; destination designator
	CALL $GTHST
	 RET
	IFN. A			; have determinate information?
	  MOVE A,DSTPTR		; indeterminate, just copy the argument
	  HRROI B,HSTSTR
	  SETZ C,
	  SOUT%
	ELSE.
	  MOVE A,D		; determinate, put Internet after name
	  HRROI B,[ASCIZ/Internet/]
	  CALL $ARDOM
	ENDIF.
	RETSKP

	ENDSV.
	SUBTTL Protocol-specific routines - DECnet

; $DECNS - Translate DECnet host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $DECNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$DECNS::SAVEAC <C>
	STKVAR <HSTPTR,HSTNUM,<NODBLK,2>>
	TXC A,.LHALF		; is string pointer LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; save destination pointer
	MOVEM B,HSTNUM		; save host "number"
	CAME B,[-1]		; want local address?
	IFSKP.
	  MOVEM A,.NDNOD+NODBLK	; set up string pointer in NODE% block
	  MOVX A,.NDGLN		; get local node name function
	  MOVEI B,NODBLK	; pointer to destination name string
	  NODE%			; get local name
	   ERJMP R		; failed
	  MOVE A,HSTPTR		; now build host "number"
	  CALL $DECSN
	   RET			; NODE%, but no DECnet apparently
	  MOVEM A,HSTPTR	; set as updated host pointer
	  MOVEM B,HSTNUM	; save host "number"
	ELSE.
	  MOVE A,HSTPTR		; get destination string pointer
	  DO.
	    SETZ C,		; prepare for byte
	    ROTC B,6		; get a SIXBIT byte
	    JUMPE C,R		; imbedded space invalid
	    ADDI C,"A"-'A'	; convert to ASCII
	    IDPB C,A		; store in returned string
	    JUMPN B,TOP.	; get next byte
	  ENDDO.
	  MOVE C,A		; tie off string
	  IDPB B,C
	  EXCH A,HSTPTR		; update pointer
	  CALL $DECVY		; try to verify
	   RET
	ENDIF.
	MOVE A,HSTPTR		; return updated pointer
	HRROI B,[ASCIZ/DECnet/]	; add DECnet domain
	CALL $ARDMH
	MOVE B,HSTNUM		; and updated "number"
	RETSKP

	ENDSV.

; $DECSN - Translate DECnet host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $DECSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$DECSN::SAVEAC <C,D>
	STKVAR <HSTPTR,HSTNUM,<HSTSTR,HSTNMW>>
	MOVEM A,HSTPTR		; save host pointer
	HRROI A,HSTSTR		; copy string so we can muck with it
	MOVE B,HSTPTR		; get back host pointer
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	MOVEM B,HSTPTR		; save pointer
	SETO B,			; back pointer up by one
	ADJBP B,HSTPTR
	MOVEM B,HSTPTR		; save updated pointer
	HRROI A,HSTSTR		; now remove DECnet domain
	HRROI B,[ASCIZ/DECnet/]
	CALL $RRDMH
	 RET
	CALL $DECVY		; try to verify
	 RET
	SETZM HSTNUM		; now build host "number"
	MOVE B,[POINT 6,HSTNUM]
	DO.
	  ILDB C,A		; get byte of name
	  CAIG C," "		; has a sixbit representation?
	   EXIT.		; no, done
	  CAIL C,"`"		; lowercase?
	   SUBI C,"a"-"A"	; yes, convert to upper case
	  SUBI C,"A"-'A'	; convert to SIXBIT
	  IDPB C,B		; stash in string
	  TLNE B,770000		; at last byte?
	   LOOP.
	ENDDO.
	MOVE A,HSTPTR		; return updated pointer
	MOVE B,HSTNUM		; and updated "number"
	RETSKP

	ENDSV.

; $DECCA - Get canonical name for DECnet host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $DECCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

$DECCA::STKVAR <HSTPTR>
	MOVEM B,HSTPTR		; save destination pointer
	CALL $DECSN		; get host address
	 RET			; fails
	MOVE A,HSTPTR		; get destination pointer
	CALL $DECNS		; translate to canonical name
	 RET			; shouldn't ever fail
	RETSKP			; success

	ENDSV.

; $DECVY - Verify DECnet node name
; Accepts:
;	A/ pointer to node name string
; Returns +1: Failed
;	  +2: Success, name validated

$DECVY::SAVEAC <A,B>
	STKVAR <<DCNFIL,40>,DCNJFN,NODPTR,<NODBLK,2>>
	MOVEM A,NODPTR		; save pointer for later
	MOVEM A,.NDNOD+NODBLK	; and in NODE% block
	MOVX A,.NDVFY		; validate node name
	MOVEI B,NODBLK
	NODE%
	 ERJMP R		; syntax invalid
	JN ND%EXM,.NDFLG+NODBLK,RSKP ; validated name
	HRROI A,DCNFIL		; syntax valid, but name not, do extra test
	HRROI B,[ASCIZ/DCN:/]
	SETZ C,
	SOUT%
	MOVE B,NODPTR
	SOUT%
	HRROI B,[ASCIZ/-TASK-DCNVFY-TEST/] ; random task name
	SOUT%
	IDPB C,A		; tie off string with null
	MOVX A,GJ%SHT		; see if we can get that name
	HRROI B,DCNFIL
	GTJFN%
	 ERJMP R		; can't get name, no DECnet or something
	MOVEM A,DCNJFN		; save JFN for later
	MOVX B,OF%RD		; open for read
	OPENF%
	IFNJE.
	  CLOSF%		; won, flush the connection
	   ERJMP .+1
	ELSE.
	  EXCH A,DCNJFN		; get back the JFN, save error code
	  RLJFN%		; free it
	   ERJMP .+1		; ignore error here
	  MOVE A,DCNJFN		; get back error code
	  CAIE A,NSPX18		; was it "No path to node"?
	   RET			; no, no such node then
	ENDIF.
	RETSKP			; return success

	ENDSV.
	SUBTTL Protocol-specific routines - Pup

; $PUPNS - Translate Pup Ethernet host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $PUPNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$PUPNS::SAVEAC <C,D>
	STKVAR <HSTPTR,<PUPHSN,2>>
	MOVEM A,HSTPTR		; save host pointer
	CAME B,[-1]		; want local address?
	IFSKP.
	  MOVX A,SIXBIT/PUPROU/	; get GETAB% index of PUPROU table
	  SYSGT%		; B/ -items,,table number
	   ERJMP R		; shouldn't happen
	  JUMPE B,R		; fail if no such table
	  HLLZ C,B		; C/ AOBJN pointer through PUPROU
	  DO.
	    HRR A,B		; table number
	    HRL A,C		; index in table
	    GETAB%		; get table entry
	     ERJMP R		; shouldn't happen
	    IFXE. A,1B0		; network inaccessible?
	      JXN A,.RHALF,ENDLP. ; no, done if have local addr on this network
	    ENDIF.
	    AOBJN C,TOP.	; try next entry
	    RET			; unable to find our host address
	  ENDDO.
	  HRLI B,1(C)		; network # is 1+<PUPROU index>
	  HRR B,A		; host # is in RH of PUPROU entry
	ENDIF.
	MOVEM B,PUPHSN		; save host address argument
	SETZM 1+PUPHSN		; don't want port info
	MOVE A,HSTPTR		; destination string
	MOVX B,PN%FLD!PN%OCT!<FLD 1,.LHALF> ; no defaults, use octal if have to
	HRRI B,PUPHSN		; pointer to host address
	PUPNM%			; call incredibly hairy Pup JSYS
	 ERJMP R		; failed
	HRROI B,[ASCIZ/Pup/]	; add Pup domain
	CALL $ARDMH
	MOVE B,PUPHSN		; return host number too in case argument -1
	RETSKP

	ENDSV.

; $PUPSN - Translate Pup Ethernet host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $PUPSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$PUPSN::SAVEAC <C,D>
	STKVAR <HSTPTR,<HSTSTR,HSTNMW>,<PUPHSN,2>>
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	MOVEM B,HSTPTR		; save pointer
	SETO B,			; back pointer up by one
	ADJBP B,HSTPTR
	MOVEM B,HSTPTR		; save updated pointer
	HRROI A,HSTSTR		; now remove Pup domain
	HRROI B,[ASCIZ/Pup/]
	CALL $RRDMH
	 RET
	MOVX B,PN%NAM!<FLD 1,.LHALF> ; lookup name, return one word
	HRRI B,PUPHSN		; pointer to host address
	PUPNM%			; call incredibly hairy Pup JSYS
	 ERJMP R		; failed
	MOVE A,HSTPTR		; return updated pointer
	MOVE B,PUPHSN		; get host address
	RETSKP

	ENDSV.

; $PUPCA - Get canonical name for Pup host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $PUPCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

$PUPCA::STKVAR <HSTPTR>
	MOVEM B,HSTPTR		; save destination pointer
	CALL $PUPSN		; get host address
	 RET			; fails
	MOVE A,HSTPTR		; get destination pointer
	CALL $PUPNS		; translate to canonical name
	 RET			; shouldn't ever fail
	RETSKP			; success

	ENDSV.
	SUBTTL Protocol-specific routines - Chaosnet

; $CHSNS - Translate Chaosnet host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $CHSNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$CHSNS::SAVEAC <C>
	STKVAR <HSTPTR,HSTNUM>
	MOVEM A,HSTPTR		; save host pointer
	MOVEM B,HSTNUM		; save host number
	CAME B,[-1]		; want local address?
	IFSKP.
	  MOVX A,.CHNPH		; return primary name/address
	  MOVE B,HSTPTR		; pointer to string
	  CHANM%
	   ERJMP R		; failed
	  MOVEM A,HSTNUM	; set returned address
	ELSE.
	  MOVX A,.CHNNS		; return name for this address
	  MOVE B,HSTPTR
	  MOVE C,HSTNUM
	  CHANM%
	   ERJMP R		; failed
	ENDIF.
	MOVE A,B		; updated pointer from CHANM% returned in B
	HRROI B,[ASCIZ/Chaos/]	; add Chaos domain
	CALL $ARDMH
	MOVE B,HSTNUM		; return host number too in case argument -1
	RETSKP

	ENDSV.

; $CHSSN - Translate Chaosnet host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $CHSSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$CHSSN::SAVEAC <C,D>
	STKVAR <HSTPTR,<HSTSTR,HSTNMW>>
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	MOVEM B,HSTPTR		; save pointer
	SETO B,			; back pointer up by one
	ADJBP B,HSTPTR
	MOVEM B,HSTPTR		; save updated pointer
	HRROI A,HSTSTR		; now remove Chaos domain
	HRROI B,[ASCIZ/Chaos/]
	CALL $RRDMH
	 RET
	MOVX A,.CHNSN		; Chaosnet name to number
	HRROI B,HSTSTR		; foreign host name
	CHANM%
	 ERJMP R
	EXCH A,B		; want pointer in A, address in B
	RETSKP

	ENDSV.

; $CHSCA - Get canonical name for Chaosnet host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $CHSCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

$CHSCA::STKVAR <HSTPTR>
	MOVEM B,HSTPTR		; save destination pointer
	CALL $CHSSN		; get host address
	 RET			; fails
	MOVE A,HSTPTR		; get destination pointer
	CALL $CHSNS		; translate to canonical name
	 RET			; shouldn't ever fail
	RETSKP			; success

	ENDSV.
	SUBTTL Protocol-specific routines - "Special" network

; $SPCNS - Translate "Special" host address to host name
; Accepts:
;	A/ pointer to destination host string
;	B/ foreign host address
;	CALL $SPCNS
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$SPCNS::SAVEAC <C,D>
	STKVAR <HSTPTR,HSTNUM,<DIRSTR,20>,TOPDIR,NAMPTR>
	MOVEM A,HSTPTR		; save host pointer
	MOVEM B,HSTNUM		; save host number
	MOVX A,.LNSSY		; get root dir name of special hosts
	HRROI B,[ASCIZ/MAILS/]	; it is called MAILS:
	HRROI C,DIRSTR		; into DIRSTR
	LNMST%
	 ERJMP R		; no such name, no specials!
	MOVX A,RC%EMO		; require exact match
	HRROI B,DIRSTR		; of directory name
	RCDIR%			; see if such a directory exists
	 ERJMP R		; bogus name, barf
	JXN A,RC%NOM,R		; if no match, no special hosts
	MOVEM C,TOPDIR		; save directory number
	HRROI A,DIRSTR		; get canonical name string for MAILS:
	MOVE B,TOPDIR
	DIRST%
	 ERJMP R		; failed
	HRROI A,DIRSTR		; get name string for directory number
	MOVE B,HSTNUM		; get back desired address
	CAME B,[-1]		; want local address?
	IFSKP.
	  MOVE B,TOPDIR		; yes, get our address
	  MOVEM B,HSTNUM	; save for value return
	ENDIF.
	DIRST%			; get the name strig
	 ERJMP R		; failed
	LDB D,A			; get terminator for later
	SETZ B,			; flush terminating brocket
	DPB B,A
	DO.
	  SETO B,		; back up pointer one byte
	  ADJBP B,A
	  MOVE A,B		; update pointer to "host name"
	  LDB C,B		; see if found terminator
	  CAIE C,"["
	   CAIN C,"<"		; if at beginning then top level
	  IFSKP.
	    CAIE C,"."		; else try to find the dot
	     LOOP.		; didn't find it
	  ENDIF.
	ENDDO.
	MOVEM B,NAMPTR		; save name pointer
	MOVE A,HSTNUM		; see if local host
	CAMN A,TOPDIR		; if not we must make sure it's a subdir
	IFSKP.
	  DPB D,B		; stuff terminator
	  ILDB D,B		; get first byte of name
	  SETZ C,		; wipe it for test
	  DPB C,B
	  MOVX A,RC%EMO		; require exact match
	  HRROI B,DIRSTR	; of directory name
	  RCDIR%		; parse the name
	   ERJMP R		; bogus name, barf
	  JXN A,RC%NOM,R	; if no match, barf
	  CAME C,TOPDIR		; is superior the MAILS: directory?
	   RET			; no, lose
	  MOVE B,NAMPTR		; put first byte back again
	  IDPB D,B
	ENDIF.
	MOVE A,HSTPTR		; copy string
	MOVE B,NAMPTR
	SETZ C,			; no limit
	SOUT%
	 ERJMP R		; percolate failure up to caller
	MOVEM A,NAMPTR		; save current pointer in case SPCDOM fails
	MOVEI B,"."		; add domain delimiter
	IDPB B,A
	MOVE B,HSTNUM		; add any higher level domain name
	CALL $ASDOM
	 MOVE A,NAMPTR		; no higher level name
	HRROI B,[ASCIZ/Special/] ; add Special domain
	CALL $ARDOM
	MOVE B,HSTNUM		; return host number too in case argument -1
	RETSKP

	ENDSV.

; $SPCSN - Translate "Special" host name to host address
; Accepts:
;	A/ pointer to host string
;	CALL $SPCSN
; Returns +1: Failed
;	  +2: Success, updated pointer in A, host address in B

$SPCSN::SAVEAC <C,D>
	STKVAR <HSTPTR,<HSTSTR,HSTNMW>,<DIRSTR,HSTNMW>,HSTNUM,NAMPTR,DOMPTR>
	MOVE B,A		; copy string so we can muck with it
	HRROI A,HSTSTR		; into HSTSTR
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	MOVEM B,HSTPTR		; save pointer
	SETO B,			; back pointer up by one
	ADJBP B,HSTPTR
	MOVEM B,HSTPTR		; save updated pointer
	HRROI A,HSTSTR		; now remove Special domain
	HRROI B,[ASCIZ/Special/]
	CALL $RRDOM
	 RET
	SETZM DOMPTR		; no follow-up domain pointer
	DO.
	  ILDB B,A		; see if there's a domain delimiter
	  CAIE B,"."
	   JUMPN B,TOP.		; not yet, keep on going
	  JUMPE B,ENDLP.	; end of string?
	  SETZ B,		; no, tie off string here then
	  DPB B,A
	  MOVEM A,DOMPTR	; remember the pointer to the domain
	ENDDO.
	MOVX A,.LNSSY		; get root dir name of special hosts
	HRROI B,[ASCIZ/MAILS/]	; it is called MAILS:
	HRROI C,DIRSTR		; into DIRSTR
	LNMST%
	 ERJMP R		; no such name, no specials!
	MOVX A,RC%EMO		; require exact match
	HRROI B,DIRSTR		; of directory name
	RCDIR%			; see if such a directory exists
	 ERJMP R		; bogus name, barf
	JXN A,RC%NOM,R		; if no match, no special hosts
	MOVEM C,HSTNUM		; save directory number
	HRROI A,DIRSTR		; get canonical name string for MAILS:
	MOVE B,HSTNUM
	DIRST%
	 ERJMP R		; failed
	MOVEM A,NAMPTR		; save pointer for later
	LDB D,NAMPTR		; get terminator for later
	SETZ B,			; flush terminating brocket
	DPB B,NAMPTR
	DO.
	  SETO B,		; back up pointer one byte
	  ADJBP B,A
	  MOVE A,B		; update pointer to "host name"
	  LDB C,B		; see if found terminator
	  CAIE C,"["
	   CAIN C,"<"		; if at beginning then top level
	  IFSKP.
	    CAIE C,"."		; else try to find the dot
	     LOOP.		; didn't find it
	  ENDIF.
	ENDDO.
	HRROI B,HSTSTR		; see if it matches top directory
	STCMP%
	 ERJMP R
	IFN. A
	  MOVX B,"."		; it didn't, patch in subdir delimeter
	  DPB B,NAMPTR
	  MOVE A,NAMPTR
	  HRROI B,HSTSTR	; now patch in host name
	  SETZ C,
	  SOUT%
	  IDPB D,A		; add on directory delimiter
	  IDPB C,A		; and tie off with null
	  MOVX A,RC%EMO		; require exact match
	  HRROI B,DIRSTR	; of directory name
	  RCDIR%		; see if such a directory exists
	   ERJMP R		; bogus name, barf
	  JXN A,RC%NOM,R	; if no match, no such special host
	  MOVEM C,HSTNUM	; directory number of the "host"
	ENDIF.
	SKIPN DOMPTR		; did user give a domain?
	IFSKP.
	  HRROI A,DIRSTR	; yeah, one last check, get the
	  MOVE B,HSTNUM		;  correct higher-level name
	  CALL $ASDOM
	   RET			; there isn't any for this host!
	  MOVE A,DOMPTR		; compare user's string
	  HRROI B,DIRSTR	; with correct string
	  STCMP%
	   ERJMP R
	  JUMPN A,R		; fail if no match
	ENDIF.
	MOVE A,HSTPTR		; return updated pointer
	MOVE B,HSTNUM		; and "host number"
	RETSKP

	ENDSV.

; $SPCCA - Get canonical name for Special network host
; Accepts:
;	A/ host name string
;	B/ destination host name string
;	CALL $SPCCA
; Returns +1: Failed
;	  +2: Success, updated destination pointer in A, host address in B

$SPCCA::STKVAR <HSTPTR>
	MOVEM B,HSTPTR		; save destination pointer
	CALL $SPCSN		; get host address
	 RET			; fails
	MOVE A,HSTPTR		; get destination pointer
	CALL $SPCNS		; translate to canonical name
	 RET			; shouldn't ever fail
	RETSKP			; success

	ENDSV.

; $ASDOM - Copy higher-level domain name for Special network
; Accepts:
;	A/ pointer to destination string
;	B/ directory number
; Returns +1: No higher level name exists
;	  +2: Success, updated pointer in A

$ASDOM::SAVEAC <B,C>
	STKVAR <DSTPTR,<DOMTXT,HSTNMW>>
	MOVEM A,DSTPTR		; save destination pointer
	HRROI A,DOMTXT		; get directory name
	DIRST%
	 ERJMP R		; ??
	HRROI B,[ASCIZ/HIGHER-LEVEL-DOMAIN.TXT/]
	SETZ C,			; tack on file name
	SOUT%
	MOVE A,DSTPTR		; get destination again
	HRROI B,DOMTXT		; now copy file
	CALLRET $CPFIL

	ENDSV.
	SUBTTL Local domain management routines

; $ADDOM - Add top-level domain name
; Accepts:
;	A/ pointer to host string
;	B/ pointer to domain name string
;	CALL $ADDOM
; Returns +1: Always, updated pointer in A

$ADDOM::SAVEAC <B,C>
	MOVEI C,"."		; add domain delimiter
	IDPB C,A
	SETZ C,			; no limit
	SOUT%
	RET

; $RMDOM - Remove top-level domain name
; Accepts:
;	A/ pointer to host string
;	B/ pointer to domain name string
;	CALL $RMDOM
; Returns +1: Always

$RMDOM::SAVEAC <B>
	STKVAR <HSTPTR,DOMPTR,DOMNAM>
	SETZM DOMPTR		; initially no top-level domain pointer
	MOVEM B,DOMNAM
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	DO.
	  ILDB B,A		; get a byte from name
	  JUMPE B,ENDLP.	; if null, scan done
	  CAIE B,"."		; start of a domain segment?
	   LOOP.		; no
	  MOVEM A,DOMPTR	; yes, remember its pointer
	  MOVE B,DOMNAM		; see if top-level domain is the one we want
	  STCMP%
	  IFN. A		; name match?
	    MOVE A,DOMPTR	; no, keep on looking
	    LOOP.
	  ELSE.
	    SETZ A,		; yes, tie off string before top-level domain
	    DPB A,DOMPTR
	  ENDIF.
	ENDDO.
	MOVE A,HSTPTR
	RET

	ENDSV.

; $ARDOM - Add relative domain by type
; Accepts:
;	A/ pointer to host string
;	B/ pointer to domain type string
;	CALL $ARDOM
; Returns +1: Always, updated pointer in A

$ARDOM::SAVEAC <B>
	STKVAR <HSTPTR,<DOMSTR,HSTNMW>>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	HRROI A,DOMSTR		; get relative name
	CALL $MKREL
	 RET
	MOVE A,HSTPTR		; add the relative name
	HRROI B,DOMSTR
	CALLRET $ADDOM

	ENDSV.

; $ARDMH - Add relative and higher-level domain by type
; Accepts:
;	A/ pointer to host string
;	B/ pointer to domain type string
;	CALL $ARDMH
; Returns +1: Always, updated pointer in A

$ARDMH::SAVEAC <B>
	STKVAR <HSTPTR,DOMTYP,<DOMSTR,HSTNMW>>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	MOVEM B,DOMTYP		; save domain type
	HRROI A,DOMSTR		; make higher level name
	CALL $MKHLN
	IFSKP.
	  MOVE A,HSTPTR		; remove the higher level name
	  HRROI B,DOMSTR
	  CALL $ADDOM
	  MOVEM A,HSTPTR	; save pointer
	ENDIF.
	MOVE A,HSTPTR		; add the relative name
	MOVE B,DOMTYP
	CALLRET $ARDOM

	ENDSV.

; $RRDOM - Remove relative domain by type
; Accepts:
;	A/ pointer to host string
;	B/ pointer to relative domain type string
;	CALL $RRDOM
; Returns +1: Failed (probably some other relative domain)
;	  +2: Success, updated pointer in A

$RRDOM::SAVEAC <B>
	STKVAR <HSTPTR,DOMPTR,DOMNAM>
	SETZM DOMPTR		; initially no top-level domain pointer
	MOVEM B,DOMNAM
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	DO.
	  ILDB B,A		; get a byte from name
	  IFN. B		; if null, scan done
	    CAIN B,"."		; start of a domain segment?
	     MOVEM A,DOMPTR	; yes, remember its pointer
	    LOOP.
	  ENDIF.
	ENDDO.
	SKIPN B,DOMPTR		; have a domain?
	IFSKP.
	  ILDB A,B		; see if it's relative
	  CAIE A,"#"
	ANSKP.
	  MOVE A,DOMNAM		; see if domain matches
	  STCMP%
	   ERJMP R
	  JUMPN A,R		; no match
	  DPB A,DOMPTR		; matched, remove it
	ENDIF.
	MOVE A,HSTPTR		; return pointer
	RETSKP

	ENDSV.

; $RRDMH - Remove relative and higher-level domain by type
; Accepts:
;	A/ pointer to host string
;	B/ pointer to relative domain type string
;	CALL $RRDMH
; Returns +1: Failed (probably some other relative domain)
;	  +2: Success

$RRDMH::SAVEAC <B>
	STKVAR <HSTPTR,DOMNAM,<DOMSTR,HSTNMW>>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	MOVEM B,DOMNAM		; save domain type
	CALL $RRDOM
	 RET
	HRROI A,DOMSTR		; make higher level name
	MOVE B,DOMNAM
	CALL $MKHLN
	IFSKP.
	  MOVE A,HSTPTR		; remove the higher level name
	  HRROI B,DOMSTR
	  CALL $RMDOM
	ENDIF.
	MOVE A,HSTPTR
	RETSKP

	ENDSV.

; $MKHLN - Make a higher level domain name
; Accepts:
;	A/ pointer to destination string
;	B/ pointer to domain type string
;	CALL $MKHLN
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$MKHLN::SAVEAC <B,C,D>
	STKVAR <DSTPTR,DOMTYP>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,DSTPTR
	MOVEM B,DOMTYP
	HRROI B,[ASCIZ/MAIL:/]	; make MAIL:domaintype-HIGHER-LEVEL-DOMAIN.TXT
	SETZ C,
	SOUT%
	 ERJMP R
	MOVE B,DOMTYP
	SOUT%
	 ERJMP R
	HRROI B,[ASCIZ/-HIGHER-LEVEL-DOMAIN.TXT/]
	SOUT%
	 ERJMP R
	MOVE A,DSTPTR		; now get that file if it's there
	MOVE B,DSTPTR
	CALL $CPFIL		; get it
	 RET
	RETSKP

	ENDSV.

; $MKREL - Make a relative domain name
; Accepts:
;	A/ pointer to destination string
;	B/ pointer to domain type string
;	CALL $MKREL
; Returns +1: Failed
;	  +2: Success, updated pointer in A

$MKREL::SAVEAC <B,C,D>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVX C,"#"		; first prepend relative domain
	IDPB C,A
	MOVX C,HSTNML+1		; up to this many characters
	SETZ D,			; terminate on null
	SOUT%
	 ERJMP R		; percolate failure up to caller
	JUMPE C,R		; string too long if exhausted
	RETSKP

; $RMREL - Remove top-level relative domain names
; Accepts:
;	A/ pointer to host string
;	CALL $RMREL
; Returns +1: Always

$RMREL::SAVEAC <B>
	STKVAR <HSTPTR,DOMPTR>
	TXC A,.LHALF		; is source LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,HSTPTR		; set up pointer to return
	DO.
	  SETZM DOMPTR		; initially no top-level domain pointer
	  DO.
	    ILDB B,A		; get a byte from name
	    IFN. B		; if null, scan done
	      CAIN B,"."	; start of a domain segment?
	       MOVEM A,DOMPTR	; yes, remember its pointer
	      LOOP.
	    ENDIF.
	  ENDDO.
	  MOVE A,HSTPTR		; get host pointer for return or loopback
	  SKIPN B,DOMPTR	; get pointer to top-level domain
	  IFSKP.
	    ILDB B,B		; get first byte of domain name
	    CAIE B,"#"		; relative domain?
	  ANSKP.
	    SETZ B,		; yes, tie off string before top-level domain
	    DPB B,DOMPTR
	    LOOP.		; re-do to eliminate other relative domains
	  ENDIF.
	ENDDO.
	RET

	ENDSV.

; $CPFIL - Copy a file into a buffer
; Accepts:
;	A/ pointer to destination buffer
;	B/ pointer to file name
;	CALL $CPFIL
; Returns +1: Failed (e.g. no such file)
;	  +2: Success, with updated pointer in A

$CPFIL::SAVEAC <B,C,D>
	STKVAR <TMPJFN,<TMPBUF,HSTNMW>,DSTPTR>
	TXC A,.LHALF		; is string pointer LH -1?
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)	; yes, set up byte pointer
	MOVEM A,DSTPTR		; save destination pointer
	MOVX A,GJ%SHT!GJ%OLD	; try for the local hostname file
	GTJFN%			; find system file with our name
	 ERJMP R
	MOVEM A,TMPJFN		; save JFN in case OPENF% failure
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD!OF%PDT> ; open in 7-bit ASCII and
	OPENF%			;  don't mangle the FDB
	IFJER.
	  MOVE A,TMPJFN		; get back JFN we got
	  RLJFN%		; free it
	   ERJMP R		; not interested in errors here
	  RET
	ENDIF.
	HRROI B,TMPBUF		; read in string
	MOVX C,HSTNML		; up to this many characters
	MOVX D,.CHLFD		; terminate on a linefeed
	SIN%
	 ERJMP .+1
	CLOSF%			; close off file
	 ERJMP .+1
	MOVEI A,TMPBUF		; now process string a bit
	HRLI A,(<POINT 7,>)
	DO.
	  ILDB B,A		; get byte from string read in
	  CAIE B,.CHLFD		; LF terminates
	   CAIN B,.CHCRT	; CR terminates
	    SETZ B,
	  CAIE B,.CHTAB		; TAB terminates
	   CAIN B,.CHSPC	; space terminates
	    SETZ B,
	  IDPB B,DSTPTR		; return byte to user
	  JUMPN B,TOP.		; if null, done
	ENDDO.
	SETO A,			; back over the null
	ADJBP A,DSTPTR		; return updated pointer
	RETSKP

	ENDSV.

	END