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