Trailing-Edge
-
PDP-10 Archives
-
bb-ev83b-bm
-
tcpip-sources/mmailr.mac
There are 3 other files named mmailr.mac in the archive. Click here to see a list.
TITLE MMailr -- System Mailer Daemon for MM Mailsystem
SUBTTL Mike McMahon & Mark Crispin/TCR/DT/DE/CLH/yduJ/GZ/SRA/WD/LeL
;Version components
MMLWHO==0 ;Who last edited MMAILR (0=developers)
MMLVER==6 ;MMAILR's release version (matches monitor's)
MMLMIN==1 ;MMAILR's minor version
MMLEDT==^D522 ;MMAILR's edit version
SEARCH MACSYM,MONSYM ;System definitions
SEARCH SNDDEF ;Definitions for terminal messages
SALL ;Suppress macro expansions
.DIRECTIVE FLBLST ;Sane listings for ASCIZ, etc.
.TEXT "/NOINITIAL" ;Suppress loading of JOBDAT
.TEXT "MMAILR/SAVE" ;Save as MMAILR.EXE
.TEXT "/SYMSEG:PSECT:CODE" ;Put symbol table and patch area in CODE
.REQUIRE HSTNAM ;Host name routines
.REQUIRE WAKEUP ;MMailr wakeup routines - make LINK happy
.REQUIRE SNDMSG ;Terminal message support
.REQUIRE SYS:MACREL ;MACSYM support routines
.REQUIRE RELAY ;RELAY code
; *******************************************************************
; * *
; * MMailr is a multiple network mailer program for TOPS-20. Like *
; * most fine software, it is the result of several individuals' *
; * work. *
; * It was originally conceived as XMAILR about January 1980 by *
; * Mike McMahon (MIT Artificial Intelligence Lab) and jointly *
; * developed for TOPS-20 with Mark Crispin (Stanford Computer *
; * Science Dept.). *
; * The TENEX version of XMAILR was developed by Tom Rindfleisch *
; * (Stanford SUMEX Project) and Mike McMahon in January 1981. *
; * MMailr was developed from XMAILR version 524 for TCP/IP and *
; * SMTP by Mark Crispin in September 1982. Dan Tappan (BBN) *
; * assisted in the development and debugging of the new host name *
; * lookup technology, including eliminating the need for HOSTS2. *
; * David Eppstein (Stanford) wrote the interface into the send *
; * system, which in turn was written by Kirk Lougheed (Stanford) *
; * et. al. Charles Hedrick (Rutgers) wrote the new relaying code. *
; * Ken Rossman (Columbia) wrote the first DECnet support code. *
; * Willis Dair (Santa Clara Univ) wrote the new multi-hop *
; * Mark Crispin wrote the HSTNAM module and SMTP support, lots of *
; * miscellaneous code, specified the other modules noted above, *
; * and generally guided MMailr through its long evolution. *
; * *
; *******************************************************************
; Routines invoked externally
EXTERN $GTPRO,$GTNAM,$GTCAN,$GTLCL,$GTHST
EXTERN $ADDOM,$RMREL,$RRDOM,$UKHST
EXTERN $GTHNS,$PUPNS,$CHSNS,$DECNS,$SPCNS
EXTERN $PUPSN
EXTERN $SEND,$WTRCP,$SSTAT
EXTERN $GTRLY,$INRLY,DM%TRN,DM%RLY
SUBTTL Conditional Assembly
; Following are assembly switches and functions
IFNDEF DATORG,<DATORG==1000> ;Data on page 1
IFNDEF CODORG,<CODORG==10000> ;Code on page 10
IFNDEF PAGORG,<PAGORG==50000> ;Paged data on page 50
IFNDEF FREORG,<FREORG==100000> ;Free storage starts at page 100
IFNDEF NTDAYS,<NTDAYS==1> ;Default sender status period, 1 day
IFNDEF DEDAYS,<DEDAYS==3> ;Default dead letter period, 3 days
IFNDEF MAXTMT,<MAXTMT==^D15*60> ;Max time for Daemon to transmit whole message
IFNDEF MAXTMC,<MAXTMC==^D15*60> ;Max time for Daemon to transmit one copy
IFNDEF MAXTMB,<MAXTMB==^D2*60> ;Max time to transmit 1000 chars
IFNDEF INTRXM,<INTRXM==^D30> ;Number of minutes between retransmit scans
IFNDEF INTSCN,<INTSCN==^D5> ;Number of minutes between file scans
SUBTTL Definitions
F==:0 ;Flags
A=:1 ;JSYS/argument passing
B=:2 ;...
C=:3 ;...
D=:4 ;...
E=:5
T=:6 ;Scratch
TT=:7 ;Ditto
M=:10 ;Holds current message
N=:11 ;Current host block when sending
O=:12 ;Current recipient block ""
X=:14
Y=:15
CX=:16 ;Used by MACREL
;P=:17 ;Stack pointer
; Character definitions
.CHDQT=="""" ;Double quote
; Local UUO's
OPDEF UTYPE [1B8]
OPDEF UETYPE [2B8]
OPDEF UERR [3B8]
; Macros for initializing and disabling timer
TMRTCK==^D5 ;Timer tick interval in seconds
; intvl = time-out interval in seconds
; retad = time-out error return address
DEFINE TMOSET (INTVL,RETAD) <
SETZM INTOK ;An interrupt here could be embarrassing
MOVEM P,TIMRTP ;Save the stack ptr for return
PUSH P,[PC%USR+RETAD] ;Set the return address
POP P,TIMLOC
PUSH P,[-<INTVL/TMRTCK>] ;Set the time-out interval in ticks
POP P,INTOK
>;DEFINE TMOSET
DEFINE TMOCLR <
SETZM INTOK ;Turn off time-out counter
SETZM TIMLOC ;And the return adr
>;DEFINE TMOCLR
; The following print macros do output only if PRINTP is set
DEFINE TYPE (X)
< UTYPE [ASCIZ/X/] ;Just type string
>
DEFINE CTYPE (X)
< UTYPE 10,[ASCIZ/X/] ;Do crlf and type string
>
DEFINE CITYPE (X)
< UTYPE 1,[ASCIZ/X/] ;Conditional crlf and type string
>
DEFINE ETYPE (X)
< UETYPE [ASCIZ/X/] ;Type string (fmt codes)
>
DEFINE CETYPE (X)
< UETYPE 10,[ASCIZ/X/] ;Do crlf and type string (fmt codes)
>
DEFINE CIETYP (X)
< UETYPE 1,[ASCIZ/X/] ;Conditional crlf and type str (fmt codes)
>
DEFINE DEFERR (X,Y) <
DEFINE X (Z) <
IFB <Z>,<UERR Y,0>
IFNB <Z>,<UERR Y,[ASCIZ/Z/]>>
OPDEF %'X [UERR Y,]>
DEFERR WARN,0
DEFERR JWARN,4
DEFERR FATAL,10
DEFERR JFATAL,14
IFNDEF OT%822,OT%822==:1
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)
>;IFNDEF GTDOM%
SUBTTL Flags
; Beware! Flags are local, not global. Consequently, they shouldn't be
;referenced outside of their defined context. Each return from a SAVACS
;context will restore the flags to their prior context.
;
; There are a number of other flags in various location, this page is only
;for the flags in F.
;;; Parser flags
FP%FF== 1B0 ;Formfeed seen at start of line
FP%CLN==1B1 ;Colon seen
FP%EOL==1B2 ;Blank line (after any formfeed, that is)
FP%DEL==1B3 ;Rubout on line
FP%EQU==1B4 ;Equal sign seen (control parameter)
FP%BKA==1B5 ;Backarrow seen (sender spec)
FP%WSP==1B6 ;Whitespace at start
;;; Following used in parsing sender addresses from msg headers
FP%LBK==1B7 ;Left angle bracket seen
FP%RBK==1B8 ;Right angle bracket seen
FP%HST==1B9 ;Collecting host
FP%SEP==1B10 ;"Separator" at end of sender adr field
FP%DQT==1B11 ;" seen to start quoted field
;;; Delivery flags
FM%FAI==1B18 ;Failing message
FM%RLY==1B19 ;Current transaction is being relayed
FM%HDR==1B20 ;Headers already generated
FM%FLO==1B21 ;Addressee is a file
FM%VRC==1B22 ;Valid recipient seen
FM%QOT==1B23 ;Must quote this address in protocol
;;; Requeue flags
FQ%DON==1B26 ;"Host done" set on entry
FQ%XER==1B27 ;Discard msg on failure
FQ%XNT==1B28 ;Don't send non-delivery notifications
FQ%RNM==1B29 ;Rename file to have RETRANSMIT ext
FQ%SXX==1B30 ;Failure notice rerouted to mail agent
FQ%SDR==1B31 ;Mail failed to sender
FQ%MLA==1B32 ;Mail failed to mail agent
FQ%OMF==1B33 ;Old style mail queue file
FQ%ALL==1B34 ;Output all of this host
FQ%HST==1B35 ;Host already output
SUBTTL Paged storage
.PSECT DATPAG,PAGORG ;Enter paged data
DEFINE DEFPAG (ADDR,LENGTH) <
ADDR: IFB <LENGTH>,<BLOCK 1000>
IFNB <LENGTH>,<BLOCK 1000*LENGTH>
>;DEFINE DEFPAG
DEFPAG IPCPAG,1 ;Junk page for IPCF
DEFPAG HSTTBL,4 ;Internal table of hosts
HTBLSZ==<4*1000>-1 ;Length of table in TBLUK% format
DEFPAG FLGPAG ;For MAILER.FLAGS if needed
DEFPAG TMPBUF,2 ;Temporary storage
DEFPAG FWDWIN,2 ;Forwarding string window
.ENDPS
.PSECT FRESTG,FREORG
FSPAG==<FREORG/1000> ;First free storage page
.ENDPS
SUBTTL Impure storage
LOC 20 ;Low memory
FATACS: BLOCK 20 ;AC's saved on crash
UUOLOC: BLOCK 1 ;LUUO saved here
JSR UUOH ;Set up UUO handler
FHTAB: BLOCK 3 ;Start of daughter fork handle table
FORKX: BLOCK 1 ;Logical fork number
NEWF: BLOCK 1 ;Non-zero to scan new mail
NETF: BLOCK 1 ;Non-zero to deliver to network recipients
RXMF: BLOCK 1 ;Non-zero to scan retransmit mail
FSTF: BLOCK 1 ;Non-zero to cache dead hosts
DAEMNP: BLOCK 1 ;If running as system job
WOPRP: BLOCK 1 ;If WHEEL or OPERATOR
MYUSRN: BLOCK 1 ;User number
MYDIRN: BLOCK 1 ;Connected directory number
MYJOBN: BLOCK 1 ;Job number
MYLDIR: BLOCK 1 ;Logged-in directory
RELOC
.PSECT DATA,DATORG ;Enter data area
NPDL==500 ;Size of stack
PDL: BLOCK NPDL ;Pushdown list
MEMBEG==. ;Start of memory initialized at startup
IPCFON: BLOCK 1 ;Non-zero if IPCF is set up
LOGJFN: BLOCK 1 ;Log file when Daemon
STAJFN: BLOCK 1 ;Statistics file when Daemon
SEGSIZ: BLOCK 1 ;Size of segments we'll send
MPP: BLOCK 1 ;Saved stack ptr for SAVACS/RSTACS
SAVEN: BLOCK 1 ;Place to save recipient host ptr
SAVEP: BLOCK 1 ;For Pup abort returns
DODJFN: BLOCK 1 ;DODIR's current JFN
FRNHST: BLOCK 1 ;Address of foreign host string
FRNADR: BLOCK 1 ;Foreign host address
PGTBLL==<1000-FSPAG+^D35>/^D36
PAGTBL: BLOCK PGTBLL ;Bit table
FREPTR: BLOCK 1 ;Tail,,head for free block list
PLINBP: BLOCK 2 ;Start of line in parser
PWSPBP: BLOCK 2 ;Byte pointer of start of line after whitespace
PCLNBP: BLOCK 2 ;Where there was a colon
PDELBP: BLOCK 2 ;Where there was a rubout
PDELB2: BLOCK 2 ;Where it ends
SDRHST: BLOCK 1 ;Sender host site
SDRNAM: BLOCK 2 ;Ptr/cnt to sender name
NXTSEQ: BLOCK 1 ;Ascending number in sequence for uniqueness
NETJFN: BLOCK 1 ;Network JFN
REQJFN: BLOCK 1 ;Requeue output JFN
FAIJFN: BLOCK 1 ;Failure message JFN
NTFJFN: BLOCK 1 ;Sender notify message JFN
HSHPAG: BLOCK 1 ;Page it is mapped into
HSHSIZ: BLOCK 1 ;Size of hash file
SITHSH: BLOCK 1 ;Hash for this site
TXTJFN: BLOCK 1 ;JFN for text file
CURDTM: BLOCK 1 ;Date/time when MMailr scan started
SCNTIM: BLOCK 1 ;Time to do file scan
SYSDIR: BLOCK 1 ;SYSTEM: directory
MLQDIR: BLOCK 1 ;MAILQ: directory
DIRNUM: BLOCK 1 ;Directory being hacked
MFLAGP: BLOCK 1 ;Are mailer flags mapped in?
TIMKIL: BLOCK 1 ;-1 if clock should be killed
TIMLOC: BLOCK 1 ;PC to go to on time-out
TIMRTP: BLOCK 1 ;Stack ptr for time-out return
INTOK: BLOCK 1 ;Neg if time-out interrupt active
INTPC: BLOCK 1 ;Interrupt PC
CTGCNT: BLOCK 1 ;# of ^G's typed
ICPTIM: BLOCK 1 ;ICP time-out countdown
HDRLEN: BLOCK 1 ;Number of characters in current header block
FILIDX: BLOCK 1 ;File tbl index for queued file type
OMLRBF: BLOCK 20 ;Buffer for address strings (old MAILER)
MBXFK: BLOCK 1 ;MMAILBOX.EXE fork handle
INUUO: BLOCK 1 ;Safety check to prevent recursive UUO's
NUPDL==100 ;Size of UUO PDL
UUOPDL: BLOCK NUPDL ;Pushdown list for processing UUO's
UUOACS: BLOCK 20 ;ACs saved over UUO
INTACS: BLOCK 20 ;ACs saved over level 1 interrupt
HSTBFL==^D30
HSTBUF: BLOCK HSTBFL ;Put string of a host here
AUTLEN==20 ;Length of author strings
FILAUT: BLOCK AUTLEN ;Place for msg file's author string
ORGAUT: BLOCK AUTLEN ;Vanilla author string
GTINF: BLOCK <.JIBAT-.JITNO+1> ;GETJI% stores data here
GTDLEN==.GTDML+10
GTDBLK: BLOCK GTDLEN+1 ;GTDOM% argument block
RLYBFL==5*HSTBFL
RLYBUF: BLOCK RLYBFL ;MX relays buffer
USRNUM: BLOCK 1
NTDEQF: BLOCK 1 ;Pos -- Notify sender if undeliverable
;Zero -- No action
;Neg -- Dequeue msg if undeliverable
IPCNT: BLOCK 1 ;Count of times we've MSEND%'d
IPCFOK: BLOCK 1 ;Non-zero if okay to bump interrupted PC
NOSLEP: BLOCK 1 ;Non-zero if we should skip DISMS
DOMTBL: BLOCK 1 ;Table of domains created by relay code
SNRLYS: BLOCK 1
SRLYTB: BLOCK 20 ;Table of domain block pointers
DNRLYS: BLOCK 1 ;In TRNMGR a call is used to build a path
DRLYTB: BLOCK 20 ; back to the host given a domain
;The destination domain is at offest 0
; will all the domain blocks back to our
; neighbor
PTHEND: BLOCK 1 ;The offset off of PTHLST containing the
; last host in the path
PTHLST: BLOCK 40 ;List of host relays that are in the path
STRBSZ==1000 ;Length of string buffers
STRBUF: BLOCK STRBSZ ;String buffer, used globally
STRBF1: BLOCK STRBSZ ;Alternative string buffer, used locally
STRBF2: BLOCK STRBSZ ;Another alternate buffer used locally
FRMMSG=STRBF2+<STRBSZ/2>
MEMEND==.-1 ;End of memory initialized at startup
PIDGET: IP%CPD ;Create a PID
0 ;Where the PID goes
0 ;For <SYSTEM>INFO
ENDPID-.,,.+1 ;Length,,address of message block
1,,.IPCII ;Ask to associate a name
0 ;No PID for copy
ASCIZ/[SYSTEM]MMAILR/ ;The name
ENDPID==.
IPCFMS: 0 ;Flags
0 ;Sender
0 ;Receiver
IPCFBL,,IPCFBF ;Length,,address of message block
IPCFBL==10 ;Size of IPCF buffer
IPCFBF: BLOCK IPCFBL ;Place for MRECV%/MUTIL% to write to
SDBLOK: 0 ;.SDPID - PID for local sends
T%RSYS!T%HDR ;.SDFLG - We build the header, obey REF SYS
; Site-selectable runtime flags
TRALLP: 0 ;-1 if transmogrification should always be done
; when crossing network registries even if the
; name is a domain name. However, Internet
; names are never transmogrified.
; 0 if transmogrification is suppressed if the
; name is a domain name.
PRINTP: 0 ;-1 to print activity messages
DEBUGP: 0 ;-1 if debugging network protocol
LOGP: 0 ;-1 if should make logs
STATP: 0 ;-1 if should keep statistics
;;;Non-zero pure data
UUOH: 0 ;UUO handler
JRST UUOH0
SAVACS: 0 ;AC save routine
JRST SAVAC0
LCLNAM: ASCIZ/TOPS-20/ ;Gets clobbered at initialization time
BLOCK LCLNAM+20-.
LCLNME==. ;End of local name (for padding purposes)
LCLNCN: BLOCK 20 ;Local name for current network
CHNTAB::PHASE 0
1,,TIMINT ;Time-out
1,,CTGINT ;^G typed
IPCHAN::!1,,IPCINT ;Handle IPCF interrupt
WAKCHN::!1,,WAKINT ;Process interrupt wakeup channel
REPEAT <^D36-.>,<0>
DEPHASE
; Sending protocol information
;
; SNDRT0 contains all the routines that MMailr might use.
;
; SNDRTS is a table (built from SNTRT0) of the routines
; it can use (because the monitor knows about them)
;
DEFINE DEFNT(PROT,NTDEV,SNDRTN)<
[[ASCIZ/PROT/],,SNDRTN],,[ASCIZ/NTDEV/]
>;DEFINE DEFNT
; These should be ordered by prefered priority of use
SNDRT0: DEFNT(Special,MAILS,SPCSND) ;Special (non-MMailr) network
DEFNT(TCP,TCP,INTSND) ;Internet
DEFNT(Chaos,CHA,CHASND) ;Chaosnet
DEFNT(Pup,PUP,PUPSND) ;Pup Ethernet
DEFNT(DECnet,DCN,DCNSND) ;DECnet
NSNDRS==.-SNDRT0
; Format of a SNDRTS table entry is <Protocol name>,,<routine>
;
SNDRTS: BLOCK NSNDRS ;Where we build the table
0 ;End of table marker
.ENDPS
SUBTTL Pure storage
.PSECT CODE,CODORG ;Enter code
LEVTAB::INTPC ;Priority level table
0
0
BITS:
...BIT==0
REPEAT <^D36>,<
1B<...BIT>
...BIT==...BIT+1
>;REPEAT <^D36>
;;; Various timer value definitions
RXMINT: INTRXM*^D<60*1000> ;RETRANSMIT file scan interval
SCNINT: INTSCN*^D<60*1000> ;File scan interval
NTFINT: NTDAYS,,0 ;Sender notify interval (internal fmt)
MAXQUE: DEDAYS,,0 ;Maximum time in the queue (internal fmt)
TMTINT: MAXTMT*^D1000 ;Max total transmission time (msec)
TMCINT: MAXTMC*^D1000 ;Max transmission time/copy (msec)
DAEDIR: ASCIZ/OPERATOR/ ;Directory DAEMON runs out of
MLAGNT: ASCIZ/Mailer/ ;Person handling mail problems
; Following are definitions and a table of file names/processing
; functions to handle delivery of various queued mail formats:
DEFINE FILXX(GSTR,BSTR,PRCHDR,PRCTXT,FLGS)<
%FLSTR==0
[ASCIZ `GSTR`],,[ASCIZ `BSTR`] ;File group name string
%FLPRC==1
PRCHDR,,PRCTXT ;Setup routines for processing
;header/text
%FLFLG==2
FLGS
%FLLEN==3
>;DEFINE FILXX
; Control flags for processing names
FF%OML==1B0 ;Old style queue file (adr in extension)
FF%RNM==1B1 ;Rename file with RETRANSMIT ext if requeued
FF%RXM==1B2 ;Only scan this file type every RXMINT minutes
FF%XNT==1B3 ;Don't notify sender of failures
FF%NEW==1B4 ;This is a new file with possible local recipients
FF%NET==1B5 ;This file is requeued from NEW
FILTBL: FILXX(<[--QUEUED-MAIL--].NEW*>,<[--BAD-QUEUED-MAIL--].>,GQUEQM,GQUEH1,FF%RNM!FF%NEW)
FILXX(<[--QUEUED-MAIL--].NETWORK>,<[--BAD-QUEUED-MAIL--].NETWORK>,GQUEQM,GQUEH1,FF%RNM!FF%NET)
FILXX(<[--QUEUED-MAIL--].RETRANSMIT>,<[--BAD-QUEUED-MAIL--].RETRANSMIT>,GQUEQM,GQUEH1,FF%RXM)
FILXX(<[--RETURNED-MAIL--].NEW*>,<[--BAD-RETURNED-MAIL--].>,GQUEQM,GQUEH1,FF%RNM!FF%XNT!FF%NEW)
FILXX(<[--RETURNED-MAIL--].NETWORK>,<[--BAD-RETURNED-MAIL--].>,GQUEQM,GQUEH1,FF%RNM!FF%XNT!FF%NET)
FILXX(<[--RETURNED-MAIL--].RETRANSMIT>,<[--BAD-RETURNED-MAIL--].RETRANSMIT>,GQUEQM,GQUEH1,FF%XNT!FF%RXM)
FILXX(<[--UNSENT-MAIL--].*>,</UNDELIVERABLE-MAIL/.>,GQUEUN,GQUEH0,FF%OML!FF%NEW)
FILXX(<]--UNSENT-NEGATIVE-ACKNOWLEDGEMENT--[.*>,</UNDELIVERABLE-MAIL/.>,GQUEUN,GQUEH0,FF%OML!FF%XNT)
NFTBL==<.-FILTBL>/%FLLEN
SUBTTL Main program
IFNDEF VI%DEC,< ;In case MACSYM is prior to release 6
VI%DEC==1B18
>;IFNDEC VI%DEC
; Program entry vector
ENTVEC: JRST MMAILR ;START
JRST MMAILR ;REENTER
VI%DEC!<FLD MMLWHO,VI%WHO>!<FLD MMLVER,VI%MAJ>!<FLD MMLMIN,VI%MIN>!<FLD MMLEDT,VI%EDN>
FRKTAB: PHASE 1
NEWFRK:!JRST MMLNLF ;Fork 1: First time deliver to local recipients
NETFRK:!JRST MMLNNF ;Fork 2: New network mail, fast scan
RXMFRK:!JRST MMLRXM ;Fork 3: Retransmitted mail, slow scan
DEPHASE
NFRKS==.-FRKTAB ;Number of forks
ENTVCL==.-ENTVEC ;Length of entry vector
;;;Fork 1: First time delivery to local recipients
MMLNLF: MOVEI A,NEWFRK ;Set logical fork number
MOVEM A,FORKX
SETOM NEWF ;Scan new mail
SETZM NETF ;Don't deliver to network recipients
SETZM RXMF ;Don't scan retransmit mail
SETOM FSTF ;Cache dead hosts (doesn't matter here)
SETOM DAEMNP ;We are the daemon
SETOM WOPRP ;Also, we must have been WHEEL or OPERATOR
JRST MAILR1 ;Enter main program
;;;Fork 2: First time delivery to network recipients
MMLNNF: MOVEI A,NETFRK ;Set logical fork number
MOVEM A,FORKX
SETZM NEWF ;Don't scan new mail
SETOM NETF ;Deliver to network recipients
SETZM RXMF ;Don't scan retransmit mail
SETOM FSTF ;Cache dead hosts
SETOM DAEMNP ;We are the daemon
SETOM WOPRP ;Also, we must have been WHEEL or OPERATOR
JRST MAILR1 ;Enter main program
;;;Fork 3: Slow scan through the RETRANSMIT queue
MMLRXM: MOVEI A,RXMFRK ;Set logical fork number
MOVEM A,FORKX
SETZM NEWF ;Don't scan new mail
SETOM NETF ;Deliver to network recipients
SETOM RXMF ;Scan retransmit mail
SETZM FSTF ;Don't cache dead hosts
SETOM DAEMNP ;We are the daemon
SETOM WOPRP ;Also, we must have been WHEEL or OPERATOR
JRST MAILR1 ;Enter main program
;;;Mother fork start
MMAILR: DO.
GTAD% ;a =: date/time
AOSE A ;Set yet?
IFSKP.
MOVEI A,^D5000 ;No, wait 5 sec
DISMS%
LOOP. ;And try again
ENDIF.
ENDDO.
SETZM FORKX ;This is top fork
SETOM NEWF ;Assume scan new mail
SETOM NETF ;Assume deliver to network recipients
SETOM RXMF ;Assume scan retransmit mail
SETOM FSTF ;Assume cache dead hosts
SETZM DAEMNP ;Assume not the Daemon
SETOM PRINTP ;Assume print all messages
JSP CX,INIT ;Init the world
MOVX A,.FHSLF
RPCAP% ;Get our capabilities
IFXN. B,SC%WHL!SC%OPR ;WHEEL or OPERATOR?
SETOM WOPRP ;Yes, flag so
IOR C,B ;Enable everything we've got
EPCAP%
MOVX A,RC%EMO ;Now see if we're the Daemon (must be priv'd)
HRROI B,DAEDIR ;b =: dir Daemon runs out of
RCUSR%
MOVE T,C
GJINF%
DAEPAT:! ;;;Patch this location to NOP to force Daemon
CAMN A,T ;Are we logged in as the Daemon user?
SETOM DAEMNP ;Yes, we're the Daemon
ENDIF.
SKIPN DAEMNP ;Are we the daemon?
JRST MAILR2 ;No - run main program
;;; Mother fork
CALL WAKTOP ;Set up for passing on wakeup interrupts
MOVSI X,-NFRKS ;Set up fork count
DO.
MOVX A,CR%CAP ;Make an inferior fork, pass down capabilities
CFORK%
IFJER.
JFATAL <?Can't create MMailr daughter fork>
HALTF% ;Punt
JRST MMAILR ;Restart on CONTINUE
ENDIF.
MOVEM A,FHTAB(X) ;Save daughter's fork handle
SETZ T, ;Reset page index
DO.
MOVE A,T ;Get the page number
HRLI A,.FHSLF ;This fork
RMAP% ;Read page access
IFXN. B,RM%PEX ;Does page exist?
MOVE C,B ;Yes, get its access bits
ANDX C,RM%RD!RM%WR!RM%EX!RM%CPY ;Turn off unwanted bits
TXZE C,RM%WR ;Does this page have write access?
TXO C,RM%CPY ;Yes, set copy-on-write for daughters
MOVE A,T ;Get page number
HRLI A,.FHSLF ;This fork
MOVE B,T ;For destination also
HRL B,FHTAB(X) ;New fork handle
PMAP% ;Map the page
ENDIF.
CAIGE T,777 ;At last page?
AOJA T,TOP. ;No so keep going
ENDDO.
MOVE A,FHTAB(X) ;Start daughter fork
MOVEI B,FRKTAB(X) ;At specified address
SFORK%
AOBJN X,TOP. ;Start next fork
ENDDO.
DO.
MOVSI X,-NFRKS ;Set up
DO.
MOVE A,FHTAB(X) ;Get fork handle
RFSTS% ;Check its status
LOAD A,RF%STS,A ;Not interested in PSI or frozen flag
CAIE A,.RFHLT ;If HALTF%, treat like blew up
CAIN A,.RFFPT ;Forced process termination?
IFNSK.
MOVEI A,1(X) ;Get fork index
CETYPE <Fork %1O halted at >
MOVEI T,-1(B) ;Get PC
CALL SYMOUT ;Output symbolically
MOVE A,FHTAB(X) ;Get fork handle
GETER% ;Get last error of this process
ETYPE <, last error: %2E, ...restarting
>
MOVE A,FHTAB(X) ;Get fork handle again
MOVEI B,CRASH ;Get it to dump and reboot
SFORK%
ENDIF.
AOBJN X,TOP. ;Otherwise looks good, try next
ENDDO.
MOVX A,^D<5*60*1000> ;Wait five minutes between checks
DISMS%
LOOP.
ENDDO.
MAILR1: JSP CX,INIT ;Initialize the world
MOVX A,^D<2*60*1000> ;Wait two minutes for the network to stabilize
DISMS%
MAILR2: MOVEI A,.FHSLF ;Set up PSI
MOVE B,[LEVTAB,,CHNTAB]
SIR%
EIR%
MOVX B,1B0 ;Set up for channel 0 to interrupt
AIC%
TMOCLR ;No time-out interrupts, please
;
; Place initial entries in our host table
;
MOVEI A,HTBLSZ ;Maximum number of hosts we can handle at once
MOVEM A,HSTTBL ;Init the table
CALL INICNX ;Figure out the protocols we speak
HRROI A,LCLNAM ;Try to get local host name for Internet
CALL $GTLCL ;Get local host name
FATAL <Can't get local host name>
MOVEI A,HSTTBL ;Add it to our host table
MOVSI B,LCLNAM
TBADD%
MOVX B,HF%PRM ;Mark it permanent
IORM B,(A)
MOVEI A,ALCBLK ;Set up routines for use by relay code
MOVEI B,PRMHST
CALL $INRLY ;Init relay tables
MOVEM A,DOMTBL ;Save table of domains it made
JSP CX,SETTIM ;Set the timer up
SKIPE DAEMNP ;Are we the Daemon?
IFSKP.
MOVEI A,.FHSLF ;No, set up ^G interrupt
MOVX B,1B1
AIC%
MOVE A,[.TICCG,,1]
ATI%
SETOM PRINTP ;Print all messages
GTAD% ;Log current date/time
MOVEM A,CURDTM
MOVE B,MYDIRN ;Get connected directory
CAMN B,MYLDIR ;Login same as connected?
IFSKP.
CALL DODIR ;Do connected first
CALL CRIF
MOVE B,MYLDIR ;Get login directory
ENDIF.
CALL DODIR ;Do login
HALTF%
JRST MMAILR ;Restart totally if continue
ENDIF.
; falls through
SUBTTL Background operator task
; drops in
SETZM PRINTP ;Don't print detailed logs
SKIPE DEBUGP ;Unless debugging
SETOM PRINTP ;Want detailed logs
MOVX A,RC%EMO ;No MAILQ:, use SYSTEM:
HRROI B,[ASCIZ/SYSTEM:/]
RCDIR%
TXNE A,RC%NOM!RC%AMB ;Anything go wrong?
SETZ C, ;This shouldn't happen
MOVEM C,SYSDIR ;Save SYSTEM: directory
MOVX A,RC%EMO ;Look up MAILQ:
HRROI B,[ASCIZ/MAILQ:/]
RCDIR%
TXNE A,RC%NOM!RC%AMB ;Anything go wrong?
MOVE C,SYSDIR ;Yes, use SYSTEM: directory instead
MOVEM C,MLQDIR ;Set directory to check every time
MOVEI A,.FHSLF
SETOB C,B
EPCAP%
CALL MAPFLG ;Map in the mailer flags
JWARN <Failed to map MAILER flags>
; falls through
; drops in
;;;This is the main daemon loop
DO.
SKIPN LOGP ;Should make logs?
IFSKP. ;Yes
SETOM PRINTP ;Want details
DO.
MOVE A,[POINT 7,STRBUF]
MOVEI B,[ASCIZ/MAIL:/]
CALL MOVSTR
MOVE B,FORKX ;Fork handle
MOVX C,^D8
NOUT%
JFATAL
MOVEI B,[ASCIZ/-MMAILR.LOG/]
CALL MOVST0
HRROI B,STRBUF
MOVX A,GJ%SHT
GTJFN%
IFJER.
CAIE A,GJFX24 ;Work around monitor bug
JWARN <Cannot get LOG file>
MOVX A,^D5000 ;Wait 5 seconds
DISMS%
LOOP.
ENDIF.
MOVEM A,LOGJFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%APP>
OPENF%
IFJER.
PUSH P,A ;Save error code
MOVE A,LOGJFN ;Recover JFN
RLJFN% ;Release it
JWARN
SETZM LOGJFN ;Clear log JFN
MOVX A,^D5000 ;Wait a few seconds
DISMS%
POP P,A ;Recover error code
CAIN A,OPNX9 ;No error if file just busy
LOOP.
CAIE A,OPNX2 ;File disappeared?
WARN <Cannot open log file - %1E>
LOOP.
ENDIF.
ENDDO.
MOVEI B,(A) ;B := Nul,,log
HRLI B,.NULIO
MOVX A,.FHSLF ;Set primary JFNs for this fork
SPJFN%
ENDIF.
SKIPN STATP ;Taking statistics?
IFSKP.
DO.
MOVE A,[POINT 7,STRBUF]
MOVEI B,[ASCIZ/MAIL:/]
CALL MOVSTR
MOVE B,FORKX ;Fork handle
MOVX C,^D8
NOUT%
JFATAL
MOVEI B,[ASCIZ/-MMAILR.STAT/]
CALL MOVST0
HRROI B,STRBUF
MOVX A,GJ%SHT
GTJFN%
IFJER.
CAIE A,GJFX24 ;Work around monitor bug
JWARN <Cannot get STAT file>
MOVX A,^D5000 ;Wait 5 seconds
DISMS%
LOOP.
ENDIF.
MOVEM A,STAJFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%APP>
OPENF%
IFJER.
PUSH P,A ;Save error code
MOVE A,STAJFN ;Recover JFN
RLJFN% ;Release it
JWARN
SETZM STAJFN ;Clear STAT JFN
MOVEI A,^D5000 ;Wait a few seconds
DISMS%
POP P,A ;Recover error code
CAIN A,OPNX9 ;No error if file just busy
LOOP.
CAIE A,OPNX2 ;File disappeared?
WARN <Cannot open STAT file - %1E>
LOOP.
ENDIF.
ENDDO.
ENDIF.
; falls through
; drops in
CITYPE <Daemon wakeup>
CALL NDHOST ;Clear dead host list
AOSE TIMKIL ;If clock got killed restart it
JSP CX,SETTIM
CALL WAKINI ;Set up wakeup interrupt
SKIPE A,FORKX ;Initialize IPCF if fork 0 (single fork) or
CAIN A,1 ; fork 1 (first time requests). This is here
CALL IPCINI ; so we retry every scan if failed
SKIPN IPCFON ;IPCF on?
IFSKP.
JSP C,IPCHEK ;Yes, check the queue
IFSKP.
CIETYP <Clearing IPCF queue...> ;Log this
MOVEI A,.FHSLF ;Now fake an IPCF delivery
MOVX B,1B<IPCHAN>
IIC%
ENDIF.
ENDIF.
GTAD% ;Log current date/time
MOVEM A,CURDTM
TIME% ;Get time
SKIPN RXMF ;Scanning retransmit files?
IFSKP.
ADD A,RXMINT ;Yes, wait longer between wakeups
ELSE.
ADD A,SCNINT ;Normal scan interval
ENDIF.
MOVEM A,SCNTIM ;Set time to scan again
; falls through
; drops in
SKIPL MFLAGP ;Have mailer flags to do?
IFSKP.
MOVSI A,-1000
DO.
SKIPN B,FLGPAG(A) ;Find a word with bit set
IFSKP.
DO.
JFFO B,.+2 ;Get bit position
EXIT. ;Last bit in this word
PUSH P,A ;Found a directory, do it
PUSH P,B
MOVNI D,(C) ;Negative bit number
MOVX B,1B0
LSH B,(D) ;Make bit to clear
ANDCAM B,FLGPAG(A) ;Clear it in flag page
ANDCAM B,(P) ;And in saved word
MOVEI B,(A)
IMULI B,^D36
ADDI B,(C) ;Compute directory to do
HLL B,MYLDIR
CAME B,MLQDIR ;We'll do MAILQ: below
CAMN B,SYSDIR ;Ditto SYSTEM:
CAIA
CALL DODIR
POP P,B
POP P,A
LOOP.
ENDDO.
ENDIF.
AOBJN A,TOP.
ENDDO.
ENDIF.
; falls through
; drops in
SKIPN B,MLQDIR ;Scan the MAILQ: directory
IFSKP.
CALL DODIRX
MOVX A,DD%DTF+DD%DNF ;Deleting ;T and non-existent files
MOVE B,MLQDIR ;Now, expunge the directory
DELDF%
IFJER.
JWARN <Expunging MAILQ: failed>
ENDIF.
ENDIF.
SKIPE B,SYSDIR ;Scan the SYSTEM: directory
CAMN B,MLQDIR ;Only if it is different from MAILQ:
IFSKP.
CALL DODIRX ;It is, scan it
MOVX A,DD%DTF+DD%DNF ;Deleting ;T and non-existent files
MOVE B,SYSDIR ;Now, expunge the directory
DELDF%
IFJER.
JWARN <Expunging SYSTEM: failed>
ENDIF.
ENDIF.
MOVX A,.FHSLF ;Restore primaries
SETO B,
SPJFN%
SKIPN A,LOGJFN ;Close log file
IFSKP.
CLOSF%
JFATAL <Unable to close log file>
SETZM LOGJFN
ENDIF.
SKIPN A,STAJFN ;Close statistics file
IFSKP.
CLOSF%
JFATAL <Unable to close STAT file>
SETZM STAJFN
ENDIF.
TIME% ;Current time
EXCH A,SCNTIM ;Time to do scan
SUB A,SCNTIM
IFG. A ;Sleep only if time left in this interval
SKIPN RXMF ;Scanning retransmit files?
IFSKP.
CAMLE A,RXMINT ;Paranoia
MOVE A,RXMINT
ELSE.
CAMLE A,SCNINT ;Paranoia
MOVE A,SCNINT
ENDIF.
SETOM TIMKIL ;Kill the clock
SETOM IPCFOK ;Indicate IPCF interrupts are OK to grant
SKIPN NOSLEP ;Okay to sleep?
DISMS%
NOP ;In case of interrupts
SETZM IPCFOK ;Indicate IPCF interrupts not allowed
SETZM NOSLEP ;Allowed to DISMS% now
ENDIF.
LOOP.
ENDDO.
; Here to process files in a directory
DODIR: CIETYP <Trying %2U...>
DODIRX: MOVEM B,DIRNUM ;Save directory number
MOVE A,[-NFTBL,,FILTBL] ;Init file type index
SETZM DODJFN ;Initially no current group JFN
DO. ;For each group
SKIPE DODJFN ;Have a current JFN defined?
IFSKP. ;No current JFN defined
MOVEM A,FILIDX ;Save file flags index
HRROI A,STRBUF ;Build filename here
MOVE B,DIRNUM ;Start with desired directory
DIRST%
ERJMP ENDLP. ;No such directory, can't do anything
MOVE B,FILIDX ;b =: ptr to current file type string
HLRZ B,%FLSTR(B)
CALL MOVST0
MOVE A,[GJ%IFG!GJ%OLD!GJ%SHT+.GJALL]
HRROI B,STRBUF
GTJFN% ;See if file group found
IFNJE.
MOVEM A,DODJFN ;Save JFN
DO.
MOVE A,FILIDX ;Get pointer to file type string
MOVE A,%FLFLG(A) ;Get flags for this group
IFXN. A,FF%NEW ;Is this a new file?
SKIPE NEWF ;Allowed to do new files?
EXIT. ;Yes, do it
ELSE. ;Not new file
SKIPN NETF ;Allowed to do network I/O?
IFSKP. ;Network I/O ok
IFXN. A,FF%RXM ;Is this a retransmit file?
SKIPE RXMF ;Allowed to do retransmit files?
EXIT. ;Yes, do it
ELSE. ;Not retransmit file, assume 1st time net file
SKIPE FSTF ;Doing fast 1st time net mail delivery?
EXIT. ;Yes, do it
ENDIF. ;End retransmit file test
ENDIF. ;End network I/O okay
ENDIF. ;End test of group type
CALL MAIFLG ;Not allowed to do it, make sure mailer knows
HRRZ A,DODJFN ;Now flush this JFN
RLJFN%
NOP
SETZM DODJFN ;Don't try to do this group
ENDDO. ;End validate need to do this group
ENDIF. ;End found files matching this group
ENDIF. ;End no current JFN defined
SKIPN A,DODJFN ;Current JFN defined
IFSKP. ;Process current file for this JFN
DO.
HRRZS A
CALL GETQUE
JRST [TYPE <...queue map failed...requeued>
CALL MAIFLG ;Make sure mailer knows
EXIT.]
JRST [TYPE <...bad file format>
CALL MAIFLG ;Make sure mailer knows
EXIT.]
SETZM NTDEQF ;Clear dequeue flag
MOVE B,FILIDX ;Notify sender about this file type?
MOVE B,%FLFLG(B)
IFXE. B,FF%XNT
SKIPN A,MSGNTF(M) ;Sender notify time given?
IFSKP.
CAMGE A,CURDTM ;Yes, time to squawk if undeliverable?
AOS NTDEQF ;Yes, flag to send notification
ENDIF.
ENDIF.
SKIPN A,MSGDEQ(M) ;Dequeue time given?
IFSKP.
CAML A,MSGAFT(M) ;Yes, dequeue time before after time?
IFSKP.
MOVE A,MSGAFT(M) ;Yes, don't be absurd! Use after time
CAMG A,CURDTM ;Unless it's before now
MOVE A,CURDTM ;In which case we'll use the time now
ADD A,MAXQUE ;Plus interval
MOVEM A,MSGDEQ(M) ;Set corrected dequeue time
ENDIF.
CAMGE A,CURDTM ;Time to dequeue this file?
SETOM NTDEQF ;One more try, then dequeue failures
ENDIF.
CALL FWDLCL
MOVE A,MSGAFT(M) ;Get after parameter, if any
CAMLE A,CURDTM ;Time to do this message yet?
IFSKP.
PUSH P,MSGTMT(M) ;Yes, no overall time limits on locals
SETZM MSGTMT(M)
CALL SNDLCL ;Always try local recipients
IFNSK.
ADJSP P,-1 ;Reset stack
TYPE <...bad file format>
CALL MAIFLG ;Make sure mailer knows
EXIT.
ENDIF.
POP P,MSGTMT(M) ;Restore global delivery timeout
CALL SNDMSG ;Deliver the message
IFNSK.
TYPE <...bad file format>
CALL MAIFLG ;Make sure mailer knows
EXIT.
ENDIF.
SKIPE NETF ;If no net sends hold off on this
SETZM MSGDOP(M) ;Next time use MAIL to deliver this message
ELSE.
CIETYP < Processing of recipients deferred until %1T>
MOVEI A,MSGLCL(M) ;Pointer to local mail
DO. ;Flag "temporary" failure to fake out REMAIL
HRRZ B,(A)
IFN. B
MOVX C,FR%TMP
IORM C,RCPFLG(B)
MOVEI A,(B)
LOOP.
ENDIF.
ENDDO.
ENDIF.
CALL REMAIL ;Requeue or send failure
CALL RELQUE
CITYPE < Done, >
SKIPN REQJFN ;Was something requeued?
IFSKP.
TYPE <requeued>
CALL MAIFLG ;Make sure mailer knows
MOVE A,FILIDX ;Was the file renamed too?
MOVE A,%FLFLG(A)
IFXN. A,FF%RNM!FF%OML
HRRZ A,DODJFN ;Yes. GNJFN% fails if current file renamed
RLJFN% ;Release this jfn
JWARN
SETZM DODJFN
MOVE A,FILIDX ;Get current group
ADJSP A,-1 ;Back up group so iteration redos this one
SUBI A,%FLLEN-1
MOVEM A,FILIDX ;Now store it
ENDIF.
ELSE.
TYPE <deleting>
HRRZ A,DODJFN
TXO A,DF%NRJ
DELF%
JWARN <DELETE failed>
ENDIF.
CALL HSTCLR ;Clean up the host table
ENDDO.
ENDIF. ;End processing for this file
SKIPN A,DODJFN ;Get JFN back
IFSKP.
GNJFN% ;See if another file in this group
IFNJE.
LOOP. ;Another file, do it
ENDIF.
SETZM DODJFN ;No more JFNs in this group
ENDIF.
MOVE A,FILIDX ;a =: current file type index
ADDI A,%FLLEN-1 ;Step to next one
AOBJN A,TOP. ;And do next group if more to do
ENDDO. ;End of per-group processing
RET
INIT: RESET% ;Flush all I/O
MOVE P,[IOWD NPDL,PDL] ;Establish stack
SETZB F,MEMBEG ;Clear out impure storage
MOVE A,[MEMBEG,,MEMBEG+1]
BLT A,MEMEND
SETOM INUUO ;Init recursive UUO flag
GJINF%
MOVEM A,MYUSRN ;Save user number
MOVEM B,MYDIRN ;Save connected directory number
MOVEM C,MYJOBN ;Save job number
SETZ A, ;Get login directory
MOVE B,MYUSRN ;My user number
RCDIR%
MOVEM C,MYLDIR ;My logged-in directory
HRROI A,[ASCIZ/POBOX:/] ;Get post office box structure
STDEV%
IFJER.
HRROI A,STRBUF ;Failed, get logged-in directory string
MOVE B,MYLDIR ;From logged-in directory
DIRST%
JFATAL
HRROI A,STRBUF ;Now get its device designator
STDEV%
JFATAL
DEVST% ;Now get just its device name
JFATAL
MOVX B,":" ;Append the device delimiter
IDPB B,A
SETZ B, ;Now null-terminate it
IDPB B,A
MOVX A,.CLNSY ;Create systemwide logical name
HRROI B,[ASCIZ/POBOX/] ; for POBOX:
HRROI C,STRBUF ;From login structure
CIETYP <[POBOX: not found, defining as %3W]
>
CRLNM%
JFATAL
ENDIF.
JRST (CX)
SUBTTL Get atom from file routine
;;; Read atom into string buffer in C, from open JFN in A.
;;; Always pads to word boundaries, uppercasing.
FILATM: BIN%
ERJMP FILAT1 ;Done on EOF
JUMPE B,FILAT1 ; or on NUL
CAIE B,.CHLFD ; or LF
CAIN B,.CHSPC ; or space
JRST FILAT1
CAIN B,.CHCRT ; or CR
JRST FILAT3
CAIL B,"a"
CAILE B,"z"
CAIA
SUBI B,"a"-"A"
IDPB B,C ;Else, add it
JRST FILATM
FILAT3: BIN% ;CR, flush LF too
FILAT1: SETZ B, ;Tie off local name
FILAT2: IDPB B,C
TXNE C,76B4
JRST FILAT2
RET
; Routine to scan the possible sending routines, and remove
; those that the monitor doesn't know about.
; Create a protocol table for later use in mail sending
;
; Return: +1
INICNX: MOVX T,<-NSNDRS,,SNDRT0> ;Number of possible sending routines
MOVEI TT,SNDRTS ;Table of allowed sending routines
DO.
HRRO A,(T) ;a := ptr to dev name for this net
STDEV% ;Local system know about it?
IFNJE.
HLRZ A,(T) ;Get the data address
MOVE A,(A) ;And the data
MOVEM A,(TT) ;Save
AOS TT ;Increment table
ENDIF.
AOBJN T,TOP.
ENDDO.
SETZM (TT) ;End of table marker
RET ;Yes
SUBTTL Memory allocation
;;; Bit table hacking, page number in A for all
PAGSBT: PUSH P,[IORM B,(A)] ;Set bit
JRST PAGHBT
PAGCBT: PUSH P,[ANDCAM B,(A)] ;Clear bit
JRST PAGHBT
PAGTBT: PUSH P,[TDNE B,(A)] ;Skip if bit clear
PAGHBT: PUSH P,A
PUSH P,B
SUBI A,FSPAG ;Make relative to start of bit table
IDIVI A,^D36
MOVEI A,PAGTBL(A) ;Point to right word
MOVE B,BITS(B) ;Get right bit
XCT -2(P)
SKIPA
AOS -3(P)
POP P,B
POP P,A
ADJSP P,-1
RET
;;; Allocate number of pages in A, returns +1 failure, +2 page number in B
PAGAL1: MOVEI A,1 ;Allocate one page
PAGALC: PUSH P,C
PUSH P,A ;Save number of pages we need
MOVEI B,FSPAG ;Starting free page
PAGALB: CALL PAGFFP ;Fast search for first free page
JRST POPACJ ;Failure, just return
MOVEI A,1(B)
MOVE C,(P) ;Get number of pages to hack again
PAGALL: SOJLE C,PAGALW ;Got enough, return address from b
CAIL A,1000 ;Page number too big?
JRST POPACJ ;Yes, fail
CALL PAGTBT ;Is this bit set?
IFNSK.
MOVEI B,1(A) ;Try for next free page
JRST PAGALB
ENDIF.
AOJA A,PAGALL ;Try for next match
PAGALW: MOVE C,(P)
MOVEI A,(B)
PAGAW1: CALL PAGSBT ;Allocate one page
SOJLE C,POPAC1
AOJA A,PAGAW1
POPAC1: AOS -2(P) ;Winning return
POPACJ: POP P,A
POP P,C
RET
;;; Deallocate pages, number in A, starting page in B
PAGDA1: MOVEI A,1 ;Deallocate one page
PAGDAL: PUSH P,A
PUSH P,B
PUSH P,C
EXCH A,B ;Setup for page number in A
PAGDA2: SOJL B,PAGDA3
CALL PAGCBT ;Clear one bit
AOJA A,PAGDA2
PAGDA3: SETO A,
MOVE B,-1(P) ;Starting page
HRLI B,.FHSLF
HRRZ C,-2(P) ;Count
TXO C,PM%CNT
PMAP% ;Flush those pages
POP P,C
POPBAJ: POP P,B
CPOPAJ: POP P,A
RET
;;; Fast search for the first free bit, starting page in B
;;; Returns +1 failure, +2 with page number in B
PAGFFP: SUBI B,FSPAG ;Make relative to start of bit table
IDIVI B,^D36
SETCM A,PAGTBL(B) ;Get first word to check
LSH A,(C)
MOVNI C,(C)
LSH A,(C) ;Clear out random bits to left
SKIPA C,B ;Starting word index
PAGFF1: SETCM A,PAGTBL(C) ;Get word to check
JFFO A,PAGFF2 ;Got any ones?
CAIL C,PGTBLL ;No - beyond last word?
RET ;Failed
AOJA C,PAGFF1 ;No, search for next word
PAGFF2: IMULI C,^D36 ;Number of bits passed
ADDI B,FSPAG(C) ;Final winning page number
CAIL B,1000 ;Was page valid?
RET ;No
RETSKP
; Routine to unmap memory buffer pages currently in use
; Entry: pagtbl = bitmap for pages in use
; Call: CALL CLRPTB
; Return: +1
CLRPTB: SETO A, ;Unmap special prebuffer pages
MOVSI B,.FHSLF
SETZ C,
HRRI B,<FLGPAG/1000> ;Do FLAGS page
PMAP%
HRRI B,<TMPBUF/1000> ;Do MMAILBOX buffer page
MOVX C,PM%CNT!2 ;Unmap both temp pages
PMAP%
HRRI B,<FWDWIN/1000>
PMAP%
MOVSI T,-PGTBLL ;t =: aobjn ptr to PAGTBL
CLRPT0: SKIPE A,PAGTBL(T) ;Any bits in this entry?
JFFO A,CLRPT1 ;Yes, scan for 1st one
AOBJN T,CLRPT0 ;No more, try next word
RET ;Done
; Here to unmap a page flagged in PAGTBL
; Entry: t = ptr to PAGTBL word for page
; b = count of flag bit position for page
CLRPT1: MOVEI C,0(T) ;c =: PAGTBL word index
IMULI C,^D36 ;c =: page count for prior wds in table
ADDI B,FSPAG(C) ;b =: memory page number
CAIL B,1000 ;Legal page?
FATAL <CLRPTB: Invalid page table bit set>
CALL PAGDA1 ;Deallocate this page
JRST CLRPT0 ;Look for more to do
;;; Map in a file, given name in B,
;;; Returns +1 failure, +2 success, starting address in B,
;;; number of bytes in C, start,,count in D
MAPQFL: PUSH P,[OF%RD!OF%WR!OF%PDT]
SKIPA ;Try for write too first, save dates for queue
MAPFIL: PUSH P,[OF%RD] ;Normally try just read
MOVX A,GJ%OLD!GJ%SHT
GTJFN%
IFJER.
ADJSP P,-1
RET
ENDIF.
CIETYP < File %1J:>
MOVE B,(P) ;Get OPENF% flags
PUSH P,A ;Save the jfn
OPENF%
ERJMP MPFLOE
MAPFL1: SIZEF%
ERJMP MPFLE1
PUSH P,B ;Save number of bytes
MOVEI A,(C) ;Number of pages needed for whole file
CALL PAGALC ;Allocate them
IFNSK.
MOVE B,-2(P) ;Get starting OPENF% bits
TXNN B,OF%PDT ;From MAPQFL call?
JRST MAPFLE ;No, just fail return
JRST MAPQFE ;Make "Bad Mail" file
ENDIF.
HRLZ A,-1(P) ;Start with page 0 of file
HRLI B,.FHSLF
HRLI C,(PM%CNT!PM%RD!PM%CPY)
PMAP%
ERJMP MAPFLE
HRLI C,(B)
MOVS D,C ;Count,,start
LSH B,9 ;Make page number into address
POP P,C ;Count of bytes
POP P,-1(P) ;Move the jfn down on the stack
POPA1J: POP P,A
RETSKP
;; Here on error mapping file
MAPFLE: ADJSP P,-1 ;Clear byte count
MPFLE1: POP P,A ;Recover JFN
CLOSF%
JWARN
ADJSP P,-1 ;Clear OPENF% bits
RET
;; Here when mail file is too big. C = # of pages
MAPQFE: ADJSP P,-1 ;Clear byte count
POP P,A ;Recover JFN
ADJSP P,-1 ;Clear OPENF% bits
MOVE B,DIRNUM ;Directory number
WARN <MAPQFL: %2U%1J too big - %4D pgs.>
TXO A,CO%NRJ ;Close it but keep the JFN
CLOSF%
JFATAL
HRRZS A ;Just JFN again
CALL RENBAX ;Rename to bad mail file
MOVEI B,STRBUF ;Ptr to name of new file
WARN < Renamed to %2W>
RET
;; Here if OPENF% fails for file
MPFLOE: CAIE A,OPNX9 ;If not invalid simultaneous access
TXNN B,OF%WR ;And asking for write
JRST MPFOE1
MOVE A,(P) ;Try once more
MOVEI B,OF%RD ;With just read
OPENF%
ERJMP MPFOE1
JRST MAPFL1 ;Succeeded this way, use it
MPFOE1: POP P,A
RLJFN%
JWARN
ADJSP P,-1 ;Clear OPENF% bits
RET
;;; Free storage
;;; Format of free list is FREHDR,,forward-link ? size,,backward-link ...
;;; ... FRETAI,,0
;;; format of allocated entry is ALCHDR,,size ? ... ? ALCTAI,,0
FREHDR==<SIXBIT / FRE/>
FRETAI==<SIXBIT / ERF/>
ALCHDR==<SIXBIT / ALC/>
ALCTAI==<SIXBIT / CLA/>
;;; Routine to check the integrity of a free space block. Requires the
;;; header and tail to match and the tail to point to the header
; Entry: b = adr of block to check
; Call: CALL CHKBLK
; Return: +1, block format is bad
; +2, format OK - allocated block
; +3, format OK - free block
CHKBLK: HLRZ T,(B) ;t =: block header type
CAIN T,FREHDR ;Free block?
JRST CHKBLF ;Yes, check the rest
CAIE T,ALCHDR ;Allocated block?
RET ;No???
HRRZ T,0(B) ;t =: size of allocated block
ADDI T,1(B) ;t =: adr of tail word
HLRZ TT,0(T) ;tt =: block tail type
HRRZ T,0(T) ;t =: ptr to head
CAIN TT,ALCTAI ;Allocated block tail?
CAIE T,0(B) ;And ptr really to head of block?
RET ;No???
RETSKP ;Good allocated block, return +2
;;; Here to check out a free block tail
CHKBLF: HLRZ T,1(B) ;t =: size of free block
ADDI T,1(B) ;t =: adr of tail word
HLRZ TT,0(T) ;tt =: block tail type
HRRZ T,0(T) ;t =: ptr to head
CAIN TT,FRETAI ;Free block tail?
CAIE T,0(B) ;And ptr really to head of block?
RET ;No???
R2SKP: AOS (P) ;Do one skip
JRST RSKP ;and then a normal skip return
;;; Allocate a block, given size in A,
;;; Returns +1 failure, +2 address of block in B, real size in A
ALCBLK: JSR SAVACS ;Save all ACs
CAIGE A,5 ;Minimum size
MOVEI A,5
MOVEI C,FREPTR ;Start by pointing to free list
ALCBLL: HRRZ B,(C) ;Get link word
JUMPE B,ALCBPG ;End of list, need a whole new page
HLRZ D,1(B) ;Size of free block
CAIL D,(A) ;Large enough?
JRST ALCBLF ;Yes, found winner
MOVEI C,(B) ;Too small, setup to try next one
JRST ALCBLL
;; Now have block in B, previous in C, size in D, user's size still in A
ALCBLF: CALL CHKBLK ;Check block integrity
NOP ;+1, block type bad
FATAL <ALCBLK: Free list screwed up> ;+2, allocated block
CAIG D,5(A) ;Size close enough to desired?
JRST ALCBLR ;Yes, no need to split
MOVEI E,(B) ;Get copy of address of block
HRLM A,1(B) ;Store new size of block to be returned
ADDI E,2(A) ;Address of start of other block
HRRZ T,(B) ;Old forward link
HRRM E,(B) ;Second is forward link for first one
IFE. T
HRLM E,FREPTR
ELSE.
HRRM E,1(T)
ENDIF.
HRLI T,FREHDR
MOVEM T,(E) ;Old forward is forward link of second block
MOVSI T,FRETAI
HRRI T,(B)
MOVEM T,-1(E) ;Store end of first block
SUBI D,2(A) ;New size of rest of block
EXCH D,A ;D should have size of block we are returning
HRLI A,(B)
MOVSM A,1(E) ;Backward link of second block is first block
ADDI A,1(E)
HRRM E,(A) ;Update pointer to start of block
ALCBLR: HRRZ T,(B) ;Forward link of this block
HRRM T,(C) ;Becomes forward link of our backward link
IFE. T
HRLM C,FREPTR
ELSE.
HRRM C,1(T) ;Its backward link is our former backward link
ENDIF.
MOVEM D,A-ACBASE(P) ;Return real size in A
MOVSI T,ALCHDR
HRRI T,(D)
MOVEM T,(B)
ADDI B,1 ;User should see block, not header
MOVEM B,B-ACBASE(P) ;Return address in B
MOVSI A,0(B) ;Compose BLT pointer to clear block
HRRI A,1(B)
SETZM 0(B) ;Clear first word
ADDI B,(D) ;Address of end
CAIL D,2 ;If multiple words,
BLT A,-1(B) ; clear rest of block
MOVEI T,ALCTAI
HRLM T,(B) ;Mark end as used too
RETSKP ;Skip return
;; Need to allocate a whole other page
ALCBPG: PUSH P,A ;Save desired size
ADDI A,1003 ;Round to page and have room for headers
LSH A,-9 ;Get number of pages needed
CALL PAGALC ;Get that many
JRST CPOPAJ ;Failed, return failure to whole thing
LSH B,9 ;Make address out of it
HRRM B,(C) ;Link onto end of list
HRLM B,FREPTR ;And save end of free list
MOVSI T,FREHDR ;Setup header of block and forward link
MOVEM T,(B)
LSH A,9 ;Number of words we asked for
MOVEI D,-2(A) ;This is the created size
HRLM D,1(B) ;Store it
HRRM C,1(B) ;Store backward link
ADDI A,-1(B) ;End of page
MOVSI T,FRETAI
HRRI T,(B)
MOVEM T,(A) ;Mark end of block
POP P,A ;Get back size user requested
JRST ALCBLF ;Go return this one
;;; Deallocate a block, address in B
FREBLK: JSR SAVACS ;Save all ACs
SETO X, ;Flag if link into list someway
SUBI B,1 ;Point to real block
CALL CHKBLK ;Check block integrity
SKIPA ;+1, block type bad
SKIPA ;+2, good allocated block
FATAL <FREBLK: Attempt to deallocate bad block> ;+3, free blk
HRRZ A,(B) ;Get size of block
HLRZ T,-1(B) ;End of previous block, maybe
CAIE T,FRETAI ;Check for free entry
IFSKP.
MOVE C,-1(B) ;Yes, get start of block then
PUSH P,B ;Save input block adr
HRRZ B,C ;b =: ptr to preceding free block
CALL CHKBLK ;Check its integrity
NOP ;+1, Bad block
FATAL <FREBLK: Prior free blk screwed up> ;+2, Allocated block
POP P,B
HLRZ D,1(C) ;Get size of previous block
ADDI A,2 ;Freeing headers
ADDB D,A ;Get new total size
HRLM D,1(C) ;Store that
ADDI D,1(C) ;End of new big block
MOVEM C,(D) ;Store tail there
MOVEI B,(C) ;This is the block to use now
ADDI X,1
ENDIF.
MOVEI C,(A)
ADDI C,2(B) ;Address of start of next block, maybe
HLRZ T,(C)
CAIE T,FREHDR ;Is it?
JRST FREBL3 ;No
PUSH P,B ;Save input block adr
HRRZ B,C ;b =: ptr to preceding free block
CALL CHKBLK ;Check its integrity
NOP ;+1, Bad block
FATAL <FREBLK: Next free blk screwed up> ;+2, Allocated block
POP P,B
AOJE X,FREBL2 ;Was it linked to previous?
HRRZ D,(C) ;Forward link of block
HRRZ E,1(C) ;Backward link
IFE. E
HRRM D,FREPTR
ELSE.
HRRM D,(E) ;Splice out this entry since already there
ENDIF.
IFE. D
HRLM E,FREPTR
ELSE.
HRRM E,1(D) ;Backward link
ENDIF.
HLRZ D,1(C) ;Get size of block
ADDI A,2
ADDB D,A
HRLM D,1(B) ;Update size
ADDI D,1(B) ;End of new big block
HRRM B,(D) ;Store correct starting address
JRST FREBLR ;That's all there is to it
FREBL2: DMOVE T,(C) ;Start of second block
HLRZ D,TT ;Size of block
ADDI A,2(D)
HRL TT,A ;Update total size
DMOVEM T,(B) ;Store as start of this entry
TXNN TT,.RHALF
HRRI TT,FREPTR
HRRM B,(TT) ;Update forward link of backward link
IFXE. T,.RHALF
HRLM B,FREPTR
ELSE.
HRRM B,1(T) ;And vice versa
ENDIF.
ADDI C,1(D) ;End of large block
HRRM B,(C) ;Store pointer to start
FREBL3: IFL. X ;Already linked in?
HRLZM A,1(B) ;Clear backward link, store size
HRRZ T,FREPTR ;Old beginning of free list
HRRM T,(B)
IFE. T
HRLM B,FREPTR
ELSE.
HRRM B,1(T) ;Update backward link of old beginning
ENDIF.
HRRM B,FREPTR ;New beginning
ENDIF.
FREBLR: MOVEI T,FREHDR ;Free header
HRLM T,(B)
ADDI A,1(B) ;End of block
MOVEI B,FRETAI
HRLM B,(A) ;Free tail
RET ;Return
;;; Make a block bigger, address of block in B, length in A
;;; Returns with new address and length
GROBLK: JSR SAVACS
HLRZ T,-1(B) ;t =: old block header
CAILE A,0 ;New length reasonable?
CAIE T,ALCHDR ;Old block type right?
FATAL <Attempt to grow bad block>
;;;*** This should try to steal from next block ***
CALL ALCBLK ;Get a new block
RET
DMOVE T,A ;Save new results
EXCH A,A-ACBASE(P) ;This is what we return
EXCH B,B-ACBASE(P)
HRLI TT,(B) ;Old,,new
ADDI T,(TT) ;End of new block
BLT TT,-1(T) ;Transfer data into new block
CALL FREBLK ;Release the old block now
RETSKP
;;; Set the bit for a particular directory
MAIFLG: HLLZ A,DIRNUM ;Get str #
HLLZ B,MYLDIR ;Compare with login str #
CAMN A,B ;Same?
CALL MAPFLG ;No, map flags if not mapped
RET ;Non-login str or can't map flags
HRRZ A,DIRNUM ;Get directory number
IDIVI A,^D36
MOVNI B,(B)
MOVX C,1B0
LSH C,(B)
IORM C,FLGPAG(A)
RET
;;; Map in the mailer flags
MAPFLG: SKIPGE A,MFLAGP ;Have the mailer flags already?
RETSKP ;Yes, don't bother
JUMPG A,R ;Cannot get them
MOVX A,GJ%OLD!GJ%SHT
HRROI B,[ASCIZ/MAIL:MAILER.FLAGS.1/]
GTJFN%
IFJER.
MOVX A,GJ%OLD!GJ%SHT ;Failed, try on SYSTEM:
HRROI B,[ASCIZ/SYSTEM:MAILER.FLAGS.1/]
GTJFN%
IFJER.
AOS MFLAGP ;Flag that we can't get the flags
RET
ENDIF.
ENDIF.
MOVEI B,OF%RD!OF%WR!OF%THW
MOVE C,A ;Save JFN away in case OPENF% loses
OPENF%
IFJER.
AOS MFLAGP
MOVE A,C ;Get rid of the JFN we got
RLJFN%
JWARN
RET
ENDIF.
HRLZ A,A
MOVE B,[.FHSLF,,FLGPAG/1000]
MOVX C,PM%RD!PM%WR
PMAP%
SETOM MFLAGP ;Flag that we have the flags in
RETSKP
SUBTTL Host name routines
; The host table is a TBLUK% format table, with the left half of
;each entry pointing to the host name string (in fully expanded
;format) and the right half holding flags
;
; Currently defined flags are
HF%PRM==1 ;Permanent table entry
HF%DED==2 ;Host was dead recently
; Parse a host name
; Call: CALL HSTNAM
; B/ Pointer to host name
; Returns:
; +1 Host not known
; +2 Success
; B/ Host pointer
HSTNAM: SAVEAC <A,C,D>
STKVAR <HSTPTR,<HSTTMP,HSTBFL>,<HSTCAN,HSTBFL>>
HRROI A,HSTTMP ;Make a copy of the host name
MOVX C,5*<HSTBFL-1> ;Up to this many characters
SETZ D, ;Terminate on null
SOUT%
JUMPE C,R ;If ran out of space just die
MOVEI A,HSTTBL ;Point to our table
HRROI B,HSTTMP
TBLUK% ;Look it up in the cache
IFXN. B,TL%EXM ;Found it?
HLRZ B,(A) ;Great, get the string address
RETSKP ;Return success
ENDIF.
HRROI A,HSTTMP ;Name to canonicalize
HRROI B,HSTCAN ;Where to put the name
CALL MXNAME ;Do the canonicalization
IFSKP.
IFLE. A ;Did we get a relay list?
IFE. A ;No, was it indeterminate?
HRROI A,HSTTMP ;If so, see if protocols can help
HRROI B,HSTCAN ;Canonical name from MXNAME was just a copy
ELSE. ;Otherwise we are the relay for this host
HRROI A,HSTCAN ;So sniff at that name
HRROI B,HSTTMP ;We don't care what protocols say is canonical
ENDIF.
CALL HSNAME ;Look up the name through protocols
ANSKP.
JUMPE A,RSKP ;Handle the local name case
ENDIF.
MOVEI A,HSTCAN ;Make pointer to canonical name
HRLI A,(<POINT 7,>)
ELSE.
HRROI A,HSTTMP ;Get the string pointer
HRROI B,HSTCAN ;Where to put canonical name
CALL HSNAME
IFSKP.
JUMPE A,RSKP ;Handle the local name case
MOVEI A,HSTCAN ;Make pointer to canonical name
HRLI A,(<POINT 7,>)
ELSE.
HRROI A,HSTTMP ;Try for a relay, return canonical name in A
CALL $GTRLY
RET
ENDIF.
ENDIF.
MOVEM A,HSTPTR ;Save pointer to canonical name
MOVEI A,HSTTBL ;Cache header
MOVE B,HSTPTR ;Pointer to possible name to add
TBLUK%
IFXE. B,TL%EXM ;Found it?
MOVE A,HSTPTR
CALL CPYSTR ;Copy the string
HRLZS B ;RH 0 means temporary table entry
MOVEI A,HSTTBL ;Point to the table
TBADD% ;Add it to table
ENDIF.
HLRZ B,(A) ;Get the string address
RETSKP ;Return success
ENDSV.
; GETPRO - 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 GETPRO
; Returns +1: Failed
; +2: Success, updated pointer in A, host address in B,
; protocol address in C
GETPRO: STKVAR <HSTPTR,HSTPT1,<HSTTMP,HSTBFL>>
MOVEM A,HSTPTR ;Save host pointer
HRROI B,HSTTMP ;See if an MX entry for this guy
CALL MXNAME ;Well, is there?
IFSKP.
ANDG. A ;Must have a relay list
MOVE A,(A) ;Get CAR of relay list
MOVEM A,HSTPT1 ;Get name of first relay
MOVE B,HSTPTR ;Compare with name user wants
STCMP%
IFXN. A,SC%SUB ;Is relay name a subset name user wants?
ILDB A,B ;Yes, see what follows
CAIE A,"." ;Relative domain delimiter?
ANSKP.
ILDB A,B ;If we have a relative domain, it means the
CAIN A,"#" ; relay is really the host itself, so we must
SETZ A, ; skip all the MX games
ENDIF.
ANDN. A ;Relay must be different from host
MOVE A,HSTPT1 ;Get back relay name
ELSE.
MOVE A,HSTPTR ;Get back host pointer
SETZM GTDBLK+.GTDRD ;Note no MX in progress in case optional %<host>
ENDIF.
CALLRET $GTPRO ;Now do the normal $GTPRO
ENDSV.
; HSNAME - Get canonical name and relays for physical host
; Accepts:
; A/ host name string
; B/ destination host name string
; CALL HSNAME
; Returns +1: Failed
; +2: Success, A/ 0 and B/ LCLNAM if local host, A/ non-zero otherwise
HSNAME: SAVEAC <C>
STKVAR <HSTADR,<HSTTMP,HSTBFL>>
MOVEI C,SNDRTS ;Check all protocols known at this point
CALL $GTCAN ;Get canonical name, address, and registry
RET ;Fails
MOVEM B,HSTADR ;Success, save host address
HRROI A,HSTTMP ;Where to store name
SETO B, ;Local host address for this protocol
CALL $GTNAM ;Canonicalize the name
IFSKP. ;Can't fail most places
CAME B,HSTADR ;Is this our local host?
ANSKP.
SETZ A, ;Yes, flag as such
MOVEI B,LCLNAM ;Return the local name pointer here
ENDIF.
RETSKP
ENDSV.
; MXNAME - Get canonical name and relays for MX host
; Accepts:
; A/ host name string
; B/ destination host name string
; CALL MXNAME
; Returns +1: Failed
; +2: Success, A/ pointer to relay list
; 0 if indeterminate, -1 if we are the relay
MXNAME: SAVEAC <B,C,D>
STKVAR <DSTPTR,<HSTTMP,HSTBFL>>
MOVEM B,DSTPTR ;Save destination pointer
MOVE B,A ;Copy string so we can muck with it
HRROI A,HSTTMP ;Into HSTTMP
MOVX C,5*<HSTBFL-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,HSTTMP ;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,GTDLEN ;Set up length of argument block
MOVEM A,GTDBLK+.GTDLN
SETZM GTDBLK+.GTDTC ;No special query type/class
MOVX A,<RLYBFL*5>-1 ;Length of relay buffer
MOVEM A,GTDBLK+.GTDBC ;Save relay buffer length
SETZM GTDBLK+.GTDNM ;This gets returned
SETZM GTDBLK+.GTDRD ;So does this
MOVX A,.GTDMX ;Want MX poop
HRROI B,HSTTMP ;Source pointer
HRROI C,RLYBUF ;Destination string buffer
MOVEI D,GTDBLK ;Argument block
CALL $GTHST
RET
IFN. A ;Have determinate information?
MOVE A,DSTPTR ;Indeterminate, just copy the argument
HRROI B,HSTTMP ;As the canonical name
SETZ C,
SOUT%
SETZ A, ;No relay pointer
ELSE.
MOVE A,DSTPTR ;Copy to canonical name
MOVE B,GTDBLK+.GTDNM ;Get pointer to canonical string
MOVX C,5*<HSTBFL-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
MOVEI D,GTDBLK+.GTDRD ;Scan relay list
DO.
SKIPN A,(D) ;Get item from relay list
EXIT.
HRROI B,LCLNAM ;Compare with local name
STCMP%
IFE. A ;Handle even the unlikely case
SETO A, ;So flag that
RETSKP ;And return success
ENDIF.
IFXN. A,SC%SUB ;Is relay name a subset of our name?
ILDB A,B ;Yes, see what follows
CAIE A,"." ;Relative domain delimiter?
ANSKP.
ILDB A,B
CAIE A,"#"
ANSKP. ;We are the relay to this MX!
SETO A, ;So flag that
RETSKP ;And return success
ENDIF.
AOJA D,TOP. ;Else consider next relay
ENDDO.
MOVEI A,GTDBLK+.GTDRD ;Return pointer to relay list
ENDIF.
RETSKP
ENDSV.
; Make a host a permanent table entry
; Call: CALL HSTPRM
; B/ Host pointer
; Returns: +1 always.
HSTPRM: SAVEAC <A,B>
MOVEI A,HSTTBL
TBLUK%
TXNE B,TL%NOM!TL%AMB
FATAL <HSTPRM - Impossible TBLUK failure>
MOVX B,HF%PRM
IORM B,(A) ;Set the right flag
RET
; Combination of HSTNAM and HSTPRM.
; Call: CALL PRMHST
; B/ Host string
; returns +1 or +2, like HSTNAM, but also marks host perm if
; it works.
PRMHST: CALL HSTNAM
RET ;Fail if HSTNAM does
SAVEAC <B>
HRRO B,B
CALL HSTPRM ;Mark it permanent
RETSKP
; Clear the table of all temporary entries.
; Call: CALL HSTCLR
; Returns: +1 always
HSTCLR: SAVEAC <A,B,C>
HLRZ C,HSTTBL ;number of entries
MOVNS C
MOVSS C
HRRI C,HSTTBL+1 ;Make an AOBJN pointer
MOVEI A,HSTTBL
DO.
HRRZ B,(C) ;get entries flag
IFE. B ;0 = temp entry
HLRZ B,(C) ;Get name string block
CALL FREBLK ;release the storage
MOVEI B,(C)
TBDEL%
SOS C ;correct pointer for deleted entry
ENDIF.
AOBJN C,TOP.
ENDDO.
RET
; Routine to check if a host is known to be dead
; Entry: b = host pointer
; Call: CALL HSTDED
; Return: +1, host dead
; +2, host is alive
HSTDED: SKIPN NETF ;Allowed to scan network mail?
RET ;No, pretend host is dead
SKIPN FSTF ;Slow scan fork?
RETSKP ;Yes, no need to scan dead host table
SAVEAC <A,B,C>
MOVEI A,HSTTBL ;Look this one up
HRROS B ;Make sure byte pointer
TBLUK%
TXNE B,TL%NOM!TL%AMB ;Paranoia
FATAL <HSTDED - Impossible TBLUK failure>
HRRZ A,(A) ;Get flags
JXN A,HF%DED,R ;Dead?
RETSKP ;Else return success
; Routine to add a host to the dead list.
; Entry: FRNHST = host pointer
; Call: CALL ADEADH
; Return: +1 always
ADEADH: SKIPN FSTF ;Slow scan?
RET ;Yes, no need to do this
SAVEAC <A,B>
MOVEI A,HSTTBL
HRRO B,FRNHST
TBLUK% ;Look it up
TXNE B,TL%NOM!TL%AMB
FATAL <ADEADH - Impossible TBLUK failure>
MOVX B,HF%DED
IORM B,(A) ;Set the right flag
RET
; Routine to remove all dead host flags from the list
; Call: CALL NDHOST
; Return: +1 always
NDHOST: HLRZ A,HSTTBL ;Get length
MOVNS A ;(Better be at least one)
MOVSS A
HRRI A,HSTTBL+1 ;Make an AOBJN pointer
MOVX B,HF%DED
DO.
ANDCAM B,(A) ;Clear the flag
AOBJN A,TOP. ;and loop
ENDDO.
RET
SUBTTL Parser
;;; Initialize parser, called with starting address in B, byte count in C
PARINI: HRLI B,(<POINT 7,0>)
DMOVE X,B
RET
;;; Parse a single line
PARLIN: TXZ F,FP%FF!FP%CLN!FP%EOL!FP%DEL!FP%WSP
SETZM PDELB2 ;Filter for malformed <del> pairs
DO.
DMOVEM X,PLINBP ;Save start of line
DO.
DMOVEM X,PWSPBP
SOJL Y,R
ILDB D,X ;Get first character
CAIE D,.CHTAB ;Leading whitespace?
CAIN D,.CHSPC
IFNSK.
TXO F,FP%WSP ;Yes, note it
LOOP. ;And continue
ENDIF.
ENDDO.
IFXE. F,FP%FF ;Seen formfeed yet?
CAIE D,.CHFFD ;No, is there one now?
IFSKP.
TXO F,FP%FF
TXZ F,FP%BKA!FP%EQU ;Clear special flags
LOOP.
ENDIF.
ELSE.
IFXE. F,FP%EQU!FP%BKA ; Seen one of these yet?
CAIE D,"=" ;Equal sign?
IFSKP.
TXO F,FP%EQU ;Yes
LOOP.
ENDIF.
CAIE D,"_" ;Backarrow?
IFSKP.
TXO F,FP%BKA ;Yes
LOOP.
ENDIF.
ENDIF.
ENDIF.
ENDDO.
CAIN D,.CHCRT ;End of line?
IFSKP.
DO.
CAIE D,.CHDEL
IFSKP.
TXON F,FP%DEL ;Rubout within line is start of host
IFSKP.
SKIPN PDELB2 ;Matching pair?
IFSKP.
SETOM PDELB2 ;No, flag error
ELSE.
DMOVEM X,PDELB2
ENDIF.
ELSE.
DMOVEM X,PDELBP
ENDIF.
ELSE.
CAIN D,":"
TXOE F,FP%CLN
IFSKP.
DMOVEM X,PCLNBP ;Save pointers when got to colon
ENDIF.
ENDIF.
SOJL Y,R
ILDB D,X
CAIE D,.CHCRT
LOOP.
ENDDO.
ELSE.
TXO F,FP%EOL
ENDIF.
SOJL Y,R
ILDB D,X ;Skip lf too
SKIPG PDELB2 ;Matching <del> set?
TXZ F,FP%DEL ;No, ignore any seen
RETSKP
;;; Parse a keyword from table in A
;;; Returns +1 failure, else calls routine pointed to by table
PARKEY: IFXN. F,FP%CLN ;Line had a colon in it?
MOVE D,PCLNBP ;Yes, use byte pointer of colon then
ELSE.
SETO D,
ADJBP D,X
ENDIF.
LDB TT,D ;Get character that terminates atom
SETZ T,
DPB T,D ;Replace it with null
MOVE T,0(A) ;t := aobjn ptr to lookup table
PARKY2: HLRZ A,0(T) ;a := ptr to next table entry
HRLI A,(<POINT 7,0>)
MOVE B,PLINBP ;Start of line
CALL STRCMP ;Match?
AOBJN T,PARKY2 ;No, try the next
DPB TT,D ;Replace character
JUMPGE T,R ;If no match, return
HRRZ A,(T) ;Get entry
JRST (A) ;Go call that routine
;;; Get pointers for this line
PARSTR: DMOVE C,PLINBP
PARST1: SUB D,Y
SUBI D,2 ;Number of chars less CRLF
RET
;;; Make lengths of fields in line with rubout relative
PARDEL: MOVE T,PLINBP+1 ;Start of line
MOVE TT,PDELBP+1
SUB T,TT
SUBI T,1 ;Less rubout itself
MOVEM T,PLINBP+1
MOVE T,PWSPBP+1
SUB T,TT
SUBI T,1
MOVEM T,PWSPBP+1
MOVE T,PDELB2+1
SUB TT,T
SUBI TT,1
MOVEM TT,PDELBP+1
SUB T,Y
SUBI T,2 ;Less CRLF
MOVEM T,PDELB2+1
RET
;;; Return a host index for string in C and D, returns as HSTNAM
PARHLN: CALL PARSTR ;Get pointers for this line
PARHST: MOVE B,[POINT 7,HSTBUF]
DO.
ILDB A,C ;Copy string
IDPB A,B
CAIE A,.CHNUL ;Quit on null
SOJG D,TOP. ;Or count
ENDDO.
SETZ A, ;Fill out with nulls
DO.
IDPB A,B
TXNE B,76B4
LOOP.
ENDDO.
MOVE B,[POINT 7,HSTBUF]
CALLRET HSTNAM ;Go try to parse host name
SUBTTL Queue file handling
;;; Structure of a queue file entry:
MSGPAG==0 ;Count,,starting page mapped into
MSGJFN==1 ;Flags,,JFN for it
MSGFHS==2 ;Foreign host
MSGHDR==3 ;Byte pointer of start of headers
MSGHCN==4 ;Count of bytes in that
MSGTXT==5 ;Byte pointer of start of text
MSGTCN==6 ;Count of bytes in that
MSGNHD==7 ;Count,,addr of headers for this network
MSGRCP==10 ;Network recipients
MSGLCL==11 ;Local recipients
MSGSDR==12 ;Sender of msg
MSGWRT==13 ;Time msg was queued
MSGAFT==14 ;Time to start attempting message delivery
MSGNTF==15 ;Time to tell sender of delivery status
MSGDEQ==16 ;Time to dequeue the msg -- dead letter
MSGTMT==17 ;Time limit for sending whole msg (msec)
MSGTMC==20 ;Time limit for sending one copy (msec)
MSGDOP==21 ;Delivery options
MSGRPT==22 ;Return path
MSGLEN==23 ;Length of entry
;;; Global flags for msg handling (lh of MSGJFN)
FG%XER==1B0 ;Discard file on error (hard failure or
;dequeue time-out)
;;; Structure of host entry:
HSTFLG==0 ;Flags,,link to next
FH%DON==1B0 ;Host done
FH%DN1==1B1 ;Host about to be done
;;; Flags for "sender" specification (used in sender host block)
FS%BKA==1B2 ;Sender specified in mail file preamble
FS%RMF==1B3 ;Sender from "ReSent-From:" line
FS%SDR==1B4 ;Sender from "Sender:" line
FS%FRM==1B5 ;Sender from "From:" line
FS%RPL==1B6 ;Sender from "Reply-to:" line
FS%NTM==1B7 ;"Mail-from:" net host line seen
FS%MLA==1B8 ;"Mail Agent" is the default sender
HSTHST==1 ;Host pointer
HSTRCP==2 ;Recipients
HSTLEN==3 ;Length of entry
;;; Structure of recipient entry:
RCPFLG==0 ;Flags,,link to next
FR%FAI==1B0 ;Hard failure
FR%TMP==1B1 ;Temporary failure
FR%ERM==1B2 ;There is a consed up error
FR%STR==1B3 ;Name is consed locally
FR%MLA==1B4 ;Recip = mail agent and failed
FR%SDR==1B5 ;Recip = sender and failed
RCPBPT==1 ;Byte pointer to name
RCPCNT==2 ;Byte count
RCPERR==3 ;Error message
RCPLEN==4 ;Length of entry
;;; Get a queue file JFN in A, returns +1 if failure, +2 with file entry in M
GETQUE: JSR SAVACS ;Save all ACs
MOVEI B,(A)
HRROI A,STRBUF
SETZ C,
JFNS%
HRROI B,STRBUF ;Must get another JFN
CALL MAPQFL
RET ;Failed, return
CALL PARINI ;Initialize parser
PUSH P,A ;Save JFN
MOVEI A,MSGLEN
CALL ALCBLK ;Allocate a block for message
IFNSK.
POP P,A ;Restore JFN
CALL UNMQU0 ;Unmap file and return
NOP
RET
ENDIF.
MOVEI M,(B) ;Pointer to block
POP P,MSGJFN(M) ;Save JFN
MOVEM M,M-ACBASE(P) ;Return that too
MOVEM D,MSGPAG(M) ;Page info
SETZM MSGFHS(M)
SETZM MSGNHD(M)
SETZM MSGRCP(M) ;Initialize recipient pointers
SETZM MSGLCL(M)
SETZM MSGSDR(M)
SETZM MSGAFT(M) ;Clear default after interval
SETZM MSGNTF(M) ;Clear delivery status notification time
SETZM MSGDEQ(M) ;Clear default dequeue time for msg
SETZM MSGDOP(M) ;Clear delivery options
SETZM MSGRPT(M) ;Clear return path
SKIPN A,DAEMNP ;Running as daemon?
IFSKP.
SKIPE RXMF ;Doing a retransmission?
IFSKP.
TIME% ;No, log xmit time limit for whole msg
ADD A,TMTINT
ELSE.
SETZ A, ;No overall time limit for retransmissions
ENDIF.
ENDIF.
MOVEM A,MSGTMT(M) ;Record it
SETZM MSGTMC(M) ;Clear xmit time limit/msg copy
HRRZ A,MSGJFN(M) ;Get file write date
CALL .GFWDT
MOVEM B,MSGWRT(M)
CALL GDFSDR ;Set up the default sender
FATAL <GETQUE: Error setting up default sender>
MOVE A,MPP ;From here on, return +2 on error
AOS (A)
MOVE A,FILIDX ;a := current file type index
HLRZ A,%FLPRC(A) ;a := processing dispatch for header
JRST 0(A) ;Do it
;; Here to fake a header for xxx.<addressee> files
GQUEUN: PUSH P,X ;Save the current msg string info
PUSH P,Y
HRROI A,STRBUF ;a := buffer for the extension info
HRRZ B,MSGJFN(M) ;b := msg file JFN
MOVSI C,000100 ;Print extension only
JFNS%
MOVE A,[POINT 7,STRBUF] ;Now scan the string for the host name
MOVE B,A
SETZB X,Y ;Init host ptr and string length
DO.
ILDB C,B ;c := next char
IFN. C ;While non-null
CAIN C,.CHCNV ;^V?
LOOP. ;Yes, ignore it
CAIE C,"@" ;Start of host?
IFSKP.
SETZ C, ;Yes, clobber the "@" with a null
IDPB C,A
MOVE X,A ;Save start of string
LOOP.
ENDIF.
IDPB C,A ;Store the char
AOJA Y,TOP. ;Count the char and do the next
ENDIF.
SKIPN X ;"@" seen?
MOVE X,A ;No, update host ptr
CAME A,X ;Is host null?
IFSKP.
MOVE B,[POINT 7,LCLNAM] ;No, use local name
LOOP.
ENDIF.
ENDDO.
MOVE B,A ;OK, terminate edited string
IDPB C,B
;;;Now we create a fake header (as if [--QUEUED-MAIL--])
MOVE A,[POINT 7,OMLRBF] ;a := place to build it
MOVEI B,.CHFFD ;Start with ^L<host><crlf>
IDPB B,A
MOVE B,X ;b := ptr to host string
SETZ C,
SOUT% ;(Have to SOUT% - not word boundary)
MOVEI B,CRLF0
CALL MOVSTR
MOVEI B,STRBUF ;Add <addressee><crlf>
CALL MOVSTR
MOVEI B,CRLF0
CALL MOVSTR
MOVEI B,.CHFFD ;And finish with ^L<CRLF>
IDPB B,A
MOVEI B,CRLF0
CALL MOVST0
MOVE X,[POINT 7,OMLRBF] ;Now set to scan the string
ADDI Y,^D8+1 ;Account ^L's and <crlf>'s in length
;(and 1 so PARLIN thinks a msg follows)
; JRST GQUEQM ;Drop into common code
;; Parse the head of the file
GQUEQM: CALL PARLIN ;Get a line from the file
JRST QUEEOF ;Premature eof
IFXE. F,FP%FF ;Was a formfeed seem?
CALL QUEBAD ;No, bad format file
HRROI B,[ASCIZ/Invalid queued mail file format in line "/]
JRST QUEBP0 ;Toss the losing file out
ENDIF.
;; Now parse the message recipients
GQUERC: IFXN. F,FP%EOL ;Empty line?
JXN F,FP%EQU,QUEBPM ;Error if control parameter specification
JXE F,FP%BKA,GQUEHD ;If not sender, must be start of actual msg
MOVEI B,LCLNAM ;Default sender host to us
JRST GQUSDR ;Set up new sender spec
ENDIF.
TXNE F,FP%EQU ;Control parameter specification?
JRST GQUPRM ;Yes, decode it
CALL PARHLN ;Get host from name
IFNSK.
JXE F,FP%BKA,QUEBHS ;If not sender spec, can't win...
DO. ;Yes, ignore it
CALL PARLIN ;Eat line
JRST QUEEOF ;Premature EOF
TXNE F,FP%FF ;Started with form?
JRST GQUERC ;Yes, done with this
LOOP. ;Otherwise eat remainder of specification
ENDDO.
ENDIF.
JXN F,FP%BKA,GQUSDR ;Set up if sender spec
SKIPN WOPRP ;WHEEL or OPERATOR?
IFSKP.
CAIE B,LCLNAM ;Yes, deliver directly if local host
IFSKP.
MOVEI O,MSGLCL(M) ;Point to local entry
JRST GQURC5
ENDIF.
ENDIF.
PUSH P,B ;Save site entry
HRROS B ;Set to check if this host already seen
MOVEI N,MSGRCP(M) ;Starting pointer for linked host list
GQURC2: HRRZ A,(N) ;a := next host entry on list
JUMPE A,GQURC3 ;Quit at end of list
MOVEI N,(A) ;n := adr of this host block
CAME B,HSTHST(N) ;Host already on list?
JRST GQURC2 ;No, check next block
POP P,B ;Yes, recover site entry
JRST GQURC4 ;Append these users
;; Here when the new host is not already on the recipient list
GQURC3: MOVEI A,HSTLEN ;Get a host entry
CALL ALCBLK
JRST QUEBRT ;Failed, free what we used and return
HRRM B,(N) ;Link it in
MOVEI N,(B) ;Now the end of the list
SETZM HSTFLG(N)
POP P,HSTHST(N) ;Save host pointer
SETZM HSTRCP(N) ;Init recipient list
GQURC4: MOVEI O,HSTRCP(N) ;This is the start of the recipients
GQURC5: HRRZ A,(O) ;a := next recipient entry on list
JUMPE A,GQURC1 ;Quit at end of the list
MOVEI O,(A) ;o := adr of this recipient block
JRST GQURC5 ;Try another
;; Here to process the next input line...
GQURC1: CALL PARLIN ;Get a line
JRST QUEEOF ;Premature eof
TXNE F,FP%FF ;Started with form?
JRST GQUERC ;Yes, next host then
TXNE F,FP%EOL ;End of line?
JRST GQURC1 ;Yes, ignore it and try another
MOVEI A,RCPLEN ;Get block for this recipient
CALL ALCBLK
JRST QUEBRT ;Failed, return
HRRM B,(O) ;Link it in
MOVEI O,(B) ;Now the end of the list
SETZM RCPFLG(O) ;Clear flags
CALL PARSTR ;Limits of string
DMOVEM C,RCPBPT(O) ;Save them
JRST GQURC1
;; Here when sender spec encountered. b = host site tbl adr
GQUSDR: PUSH P,[0] ;Save place for user ptr
PUSH P,[0]
PUSH P,B ;Save host adr (until we have a user)
GQUSD0: CALL PARLIN ;Get a line
IFNSK.
ADJSP P,-3 ;Premature eof
JRST QUEEOF
ENDIF.
TXNE F,FP%FF ;Started with form?
JRST GQUSD1 ;Yes, record what we have
TXNE F,FP%EOL ;End of line?
JRST GQUSD0 ;Yes, ignore it and try another
CALL PARSTR ;OK, get limits of string
DMOVEM C,-2(P) ;Save them
TXZE F,FP%BKA ;First user entry?
JRST GQUSD0 ;Yes, see if there are anymore
JRST GQUSDB ;Too many, bad sender spec
;; Here when new line starting with FF
GQUSD1: JXN F,FP%BKA,GQUSDB ;Exactly one sender?
REPEAT 0,< ;; This needs more thought for Cafard, etc.
DMOVE A,[POINT 7,ORGAUT ;File's last writer
POINT 7,DAEDIR] ;Daemon directory
CALL STRCMP ;Match?
IFNSK.
ADJSP P,-3 ;Reset stack
JRST GQUERC ;See about next host
ENDIF.
>;REPEAT 0
HRRZ B,MSGSDR(M) ;OK, b := adr of host entry block
MOVX A,FS%MLA ;Clear "mlagnt" bit if on
ANDCAM A,HSTFLG(B)
MOVX A,FS%BKA ;Set "_sender" bit
IORM A,HSTFLG(B)
POP P,HSTHST(B) ;Install new sender host
HRRZ B,HSTRCP(B) ;b := adr of recipient entry block
POP P,RCPCNT(B) ;Install new byte count
POP P,RCPBPT(B) ;and byte ptr
SETZM RCPERR(B) ;Clear error
JRST GQUERC ;Now see about the next host
;; Now finish up, remembering where the headers start
GQUEHD: MOVE A,FILIDX ;a := index to current file type
HRRZ A,%FLPRC(A) ;a := processing dispatch for msg
JRST 0(A) ;Do it
GQUEH0: POP P,Y ;Recover ptr info for msg text itself
POP P,X
GQUEH1: DMOVEM X,MSGHDR(M)
CALL FNDSDR ;Find sender by parsing msg headers
MOVE P,MPP ;Undo extra pushes
RETSKP ;Skip return from it all
;;; Here to process file processing parameter specifications. These are
;;; of the form <ff>=<keyword>:<value>
GQUPRM: MOVEI A,QUEPTB ;Lookup in parameter keyword table
CALL PARKEY
JRST QUEBPM ;Bad luck...
JRST GQURC1 ;Got it, continue processing
;;; Here to fetch return path
QUERPT: DMOVE C,PCLNBP ;Rest of line after colon
CALL PARST1
SKIPN A,D ;Length of string
RETSKP ;Return path null? Ignore it I guess
IDIVI A,5 ;Size in words
ADDI A,1 ;Add an extra word for remainder and null pad
CALL ALCBLK
RETSKP ;Don't care all that much
MOVEM B,MSGRPT(M) ;Save pointer to block
HRLI B,(<POINT 7,>) ;Make byte pointer
QUERP1: ILDB A,C ;Copy string
IDPB A,B
SOJG D,QUERP1 ;Continue until count exhausted
IDPB D,B ;Tie off string with null
RETSKP
;;; Here to fetch delivery options
QUEDEL: DMOVE C,PCLNBP ;Rest of line after colon
CALL PARST1
CAIE D,4 ;Is string 4 characters precisely?
RET ;No, can't be valid
ADJBP D,C ;Pointer to delimeter byte
ILDB TT,D ;Get delimiter byte
SETZ T, ;Make it null-terminated
DPB T,D
MOVEI A,QUEDOP ;Lookup in parameter keyword table
MOVE B,C
TBLUK%
DPB TT,D ;Put delimiter back
TXNE B,TL%NOM!TL%AMB ;Bad delivery option?
RET
HRRZ B,(A) ;Get delivery options table code
MOVEM B,MSGDOP(M)
RETSKP
QUEDOP: NQDOPS,,NQDOPS
DOPTAB: PHASE 0
[ASCIZ/MAIL/],,. ;Mail (MUST BE FIRST IN TABLE!!!!!!!!)
D%SAML:![ASCIZ/SAML/],,. ;Send and mail
D%SEND:![ASCIZ/SEND/],,. ;Send
D%SOML:![ASCIZ/SOML/],,. ;Send or mail
DEPHASE
NQDOPS=.-DOPTAB
;;; Here to fetch physical host that connected to us
QUEHST: DMOVE C,PCLNBP ;Rest of line after colon
CALL PARST1
CALL PARHST ;Parse the host name
SETZ B, ;Failed, ignore it (shouldn't happen)
MOVEM B,MSGFHS(M)
RETSKP
;;; Here to fetch time to attempt network retransmissions
QUEAFT: CALL GQUTIM ;Decode the time value
RET ;No go
MOVEM B,MSGAFT(M) ;Save it
RETSKP ;And success return
;;; Here to fetch time to notify sender of transmission status
QUENTF: CALL GQUTIM ;Decode the time value
RET ;No go
MOVEM B,MSGNTF(M) ;Save it
RETSKP ;And success return
;;; Here to fetch time to notify sender of transmission status
QUEDEQ: CALL GQUTIM ;Decode the time value
RET ;No go
MOVEM B,MSGDEQ(M) ;Save it
RETSKP ;And success return
;;; Here to set flag for discarding msg without notifying sender if
;;; failed or dequeued.
QUEDER: MOVX A,FG%XER ;Set flag
IORM A,MSGJFN(M)
RETSKP ;And success return
;;; Routine to decode a time value for a control parameter
;;; Return: +1, error
;;; +2, success - value in b
GQUTIM: DMOVE C,PCLNBP ;Rest of line after colon
CALL PARST1
MOVE A,[POINT 7,STRBF1] ;Temp buffer for time string
GQUTI0: ILDB B,C
CAIE B,.CHSPC ;Skip starting spaces and tabs
CAIN B,.CHTAB
IFNSK.
SOJG D,GQUTI0 ;Look some more
RET ;Unless string exhausted
ENDIF.
SKIPA
GQUTI1: ILDB B,C ;Next char
IDPB B,A ;Copy it
CAIN B,.CHNUL ;Quit on null
JRST GQUTI2
SOJG D,GQUTI1 ;If not end of string, continue
MOVEI B,0 ;Else end with null
IDPB B,A
GQUTI2: HRROI A,STRBF1 ;Now convert the time string
IDTIM%
RET
RETSKP
;;; Table of parameter keywords and processing routines
QUEPTB: -NQPRMS,,.+1
[ASCIZ/AFTER/],,QUEAFT ;Formerly RETRANSMIT
; [ASCIZ/DATA/],,QUEDAT
[ASCIZ/DELIVERY-OPTIONS/],,QUEDEL
[ASCIZ/DEQUEUE/],,QUEDEQ
[ASCIZ/DISCARD-ON-ERROR/],,QUEDER
; [ASCIZ/ERROR/],,QUEERR
[ASCIZ/NET-MAIL-FROM-HOST/],,QUEHST
[ASCIZ/NOTIFY/],,QUENTF
[ASCIZ/RETURN-PATH/],,QUERPT
NQPRMS=.-QUEPTB-1
; Routine to set up the default sender for a msg
; Entry: queue file mapped
; Call: CALL GDFSDR
; Return: +1, failure
; +2, OK
GDFSDR: HRRZ A,MSGJFN(M) ;a := queue file JFN
HRLI A,.GFLWR ;Get its author string
HRROI B,FILAUT ;Into filaut buffer
GFUST%
MOVE A,[FILAUT,,ORGAUT] ;Save original in ORGAUT
BLT A,ORGAUT+AUTLEN-1
MOVE N,[POINT 7,MLAGNT] ;Set up mail agent as default author
DMOVE A,[POINT 7,FILAUT ;See if it was written by system server
POINT 7,DAEDIR]
CALL STRCMP ;Was it?
IFNSK.
MOVX A,RC%EMO ;No, see if looks like a local user name
HRROI B,FILAUT
RCUSR% ;Parse user name
IFNJE.
TXNN A,RC%NOM!RC%AMB ;Parsed, does it exist?
MOVE N,[POINT 7,FILAUT] ;Yes, set local user as default author
ENDIF.
ENDIF.
PUSH P,N ;Save author on stack
MOVEI N,MSGSDR(M) ;n := root for sender host entry blk
MOVEI A,HSTLEN ;Get a host entry
CALL ALCBLK
JRST GDFSDX ;Failed, return +1
HRRM B,0(N) ;Link it in
MOVEI N,(B) ;Now the end of the list
SETZM B,HSTFLG(N)
MOVX A,FS%MLA ;Check if dflt sender = mail agent
HRRZ B,(P)
CAIN B,MLAGNT ;Is it?
IORM A,HSTFLG(N) ;Yes, set the flag
MOVEI B,LCLNAM ;b := host site tbl adr
MOVEM B,HSTHST(N) ;Save site entry
MOVEI O,HSTRCP(N) ;o := start of the sender recipient
MOVEI A,RCPLEN ;Get block for this recipient
CALL ALCBLK
JRST GDFSDX ;Failed, return +1
HRRZM B,(O) ;Link it in
MOVEI O,(B) ;Now the end of the list
SETZM RCPFLG(O) ;Clear flags
MOVE A,(P) ;a := ptr to dflt sender string
SETZ B, ;b := str length
ILDB C,A ;c := next char
CAIE C,.CHNUL ;Quit on null
AOJA B,.-2 ;Otherwise count it
POP P,A ;a := fresh ptr to sender string
DMOVEM A,RCPBPT(O) ;Install the sender name
RETSKP ;Return +2
; Here if error allocating blocks
GDFSDX: ADJSP P,-1 ;Reset the stack
RET ;Fail return +1
;;; The following code is to parse the msg headers to find the msg
;;; sender if none was specified by "_sender" in the msg preamble and
;;; the msg file author was DAEDIR.
; Keyword table for locating msg header lines possible containing a
; sender address.
FSDRTB: -NFSDR,,.+1
[ASCIZ/RESENT-FROM/],,SDRRMF
[ASCIZ/REMAILED-FROM/],,SDRRMF
[ASCIZ/REDISTRIBUTED-FROM/],,SDRRMF
[ASCIZ/SENDER/],,SDRSDR
[ASCIZ/FROM/],,SDRFRM
[ASCIZ/REPLY-TO/],,SDRRPL
[ASCIZ/MAIL-FROM/],,SDRNTM
NFSDR==.-FSDRTB-1
; Find sender name by parsing message header. Message file mapped
; Entry: m = adr of message block
; x,y = ptr/cnt to start of msg headers
; Call: CALL FNDSDR
; Returns +1 always
FNDSDR: HRRZ N,MSGSDR(M) ;n := adr of "sender" recip host block
MOVX A,FS%BKA
MOVX B,FS%MLA
TDNN A,HSTFLG(N) ;Sender from file preamble?
TDNN B,HSTFLG(N) ;No, sender = non-DAEDIR file author?
RET ;Yes, don't supersede that
HRRZ O,HSTRCP(N) ;o := adr of "sender" recipient block
SETZM SDRHST ;Init sender temp locs
SETZM SDRNAM
FNDSD0: CALL PARLIN ;Get a line from the msg text
JRST FNDSD1 ;EOF, check out sender
TXNE F,FP%EOL ;Empty line?
JRST FNDSD1 ;No more header lines, check out sender
MOVEI A,FSDRTB ;a := sender spec line keywords
TXNE F,FP%CLN ;Colon seen?
CALL PARKEY ;Yes, look up this line's keyword
JRST FNDSD0 ;+1, no go, move on to next line
HRRM B,SDRHST ;Save the new host
DMOVEM C,SDRNAM ;Install the new recipient name ptr
JRST FNDSD0 ;Loop through rest of headers
; Here when finished with msg headers
FNDSD1: DMOVE C,SDRNAM ;c/d := new recipient name ptr/cnt
JUMPE C,R ;If highest priority spec failed, quit
DMOVEM C,RCPBPT(O) ;Install the new recipient name ptr
SKIPN B,SDRHST ;b := sender host site
MOVEI B,LCLNAM ;Yes
HRRZM B,HSTHST(N) ;Install it
RET ;Done
; Following are the routines to check out various "sender"
; specification lines.
; Return: +1, No sender found
; +2, Sender address found
; b = host site tbl entry adr
; c = ptr to sender name string
; d = byte count for sender name
; Here to process "ReSent-From:" line
SDRRMF: MOVX A,FS%RMF ;a := flag for this line type
IORM A,SDRHST ;Show we've seen one
SDRRM0: CALL GTSNDR ;Go scan for the sender
JRST SDRXXX ;Error
RETSKP ;Success, return +2
; Here to process "Sender:" line
SDRSDR: MOVX A,FS%SDR ;a := flag for this line type
IORM A,SDRHST ;Show we've seen one
MOVX A,FS%RMF ;Already have higher priority spec?
TDNE A,SDRHST
RET ;Yes
CALLRET SDRRM0 ;Go scan for the sender
; Here to process "From:" line
SDRFRM: MOVX A,FS%FRM ;a := flag for this line type
IORM A,SDRHST ;Show we've seen one
MOVX A,FS%RMF!FS%SDR ;Already have higher priority spec?
TDNE A,SDRHST
RET ;Yes
CALLRET SDRRM0 ;No, go scan for the sender
; Here to process "Reply-to:" line
SDRRPL: MOVX A,FS%RPL ;a := flag for this line type
IORM A,SDRHST ;Show we've seen one
MOVX A,FS%RMF!FS%SDR!FS%FRM ;Already have higher priority spec?
TDNE A,SDRHST
RET ;Yes
CALLRET SDRRM0 ;No, go scan for the sender
; Here to process "Mail-from:" line
SDRNTM: MOVX A,FS%NTM ;a := flag for this line type
IORM A,SDRHST ;Show we've seen one
RET
; Here on error in parsing sender address line
SDRXXX: HLLZS SDRHST ;Clear the sender address stuff
SETZM SDRNAM
RET
; Parse a line for sender's name and host
; Entry: Input line set up to parse
; Call: CALL GTSNDR
; Return: +1, error, no valid address
; +2, success, b = host site, c/d = sender name ptr/cnt
GTSNDR: STKVAR <SDRHSP,SDRNPT,SDRNCT,SAVEB,SAVEC,SAVED>
TXZ F,FP%LBK!FP%RBK!FP%DQT ;Clear flags
DMOVE C,PCLNBP ;Set to scan from ":"
CALL PARST1 ;Adjust counts
GTSND0: SETZM SDRHSP ;Reset host/name
SETZM SDRNPT
TXZ F,FP%HST ;Not collecting host yet
CALL GTSFLD ;Scan a field of the input string
JUMPL B,R ;If questionable char, do error return
MOVEM T,SDRNPT ;Save the name ptr/cnt
MOVEM TT,SDRNCT
TXNN F,FP%SEP ;Special char term?
JRST GTSND3 ;Yes
; Here to check for "at" field signalling host name
GTSND1: CALL GTSFLD ;Get the next field
JUMPL B,R ;Quit on questionable char
IFXE. F,FP%SEP ;This field end with separator?
SETZM SDRNPT ;No, bad syntax
JRST GTSND4 ;Try to make sense of spec char
ENDIF.
TXZ A,10040 ;Capitalize last two small letters
CAIN A,"AT" ;Is it "at"?
JRST GTSND5 ;Yes, process host name
SETZM SDRNPT ;Random string format, flush ptr
GTSND2: CALL GTSFLD ;Look for field ending with a spec char
JUMPL B,R ;Quit on error
TXNN F,FP%SEP ;This field term with separator?
JRST GTSND4 ;No, better be eol or bracket
JRST GTSND2 ;Scan further
; Here when hit special char
GTSND3: CAIN B,"@" ;At-sign?
JRST GTSND5 ;Yes, end name and start host
GTSND4: CAIN B,.CHCRT ;End of line?
JRST GTSND6 ;Yes
CAIE B,.CHDQT ;Start of quoted string?
IFSKP.
TXOE F,FP%DQT ;Yes, set flag and check for error
RET ;Shouldn't be here then
JRST GTSND0 ;Start collection over
ENDIF.
CAIE B,"<" ;Left angle-bracket?
IFSKP.
TXOE F,FP%LBK ;Yes, mark it and check for earlier one
RET ;Can't have more than one
JRST GTSND0 ;OK, start over
ENDIF.
CAIE B,">" ;Right angle-bracket?
IFSKP.
TXO F,FP%RBK ;Yes, set flag
JRST GTSND6 ;Check it out
ENDIF.
RET ;No, can't make sense of it, bomb!
; Here when saw "@" or "at". Should get host name next
GTSND5: CALL GTSFLD ;Get the next field
JUMPL B,R ;Quit on weird char
JUMPE TT,GTSND4 ;If null string, check terminator
MOVEM B,SAVEB ;Save current field info
MOVEM C,SAVEC
MOVEM D,SAVED
DMOVE C,T ;Get ptr to this field
CALL PARHST ;Lookup the host name
RET ;No go, punt
TXON F,FP%HST ;Good host, already have one?
MOVEM B,SDRHSP ;No, save this host site entry
MOVE D,SAVED ;Restore field scanning information
MOVE C,SAVEC
MOVE B,SAVEB
TXNN F,FP%SEP ;Last field end with separator?
JRST GTSND3 ;No, check out special char
JRST GTSND1 ;Better be more host stuff!
; Here when done processing line
GTSND6: SKIPN SDRNPT ;Find a name?
RET ;No
TXCE F,FP%LBK!FP%RBK ;Either no <>
TXCN F,FP%LBK!FP%RBK ;Or matching set?
TRNA ;OK
RET ;Bad news
MOVE D,SDRNCT ;b,c,d := host site and ptr/cnt
MOVE C,SDRNPT
MOVE B,SDRHSP
RETSKP ;Return +2 - sender found
ENDSV.
; Routine to scan for next field in sender address
; Entry: c/d = ptr/cnt to remainder of line
; Call: CALL GTSFLD
; Return: +1, always
; t = starting ptr, tt = char count for field
; a = last 5 chars of field
; b = terminating char
; fp%sep set if terminated by special char
GTSFLD: SETZB T,TT ;Clear field string ptr/cnt
SETZ A, ;Clear shift reg for last chars in field
TXZ F,FP%SEP ;Reset separator flag
GTSFL0: CALL GTSCHR ;Get a char
JRST GTSFL0 ;+1, ignore leading separators
RET ;+2, special char - return
MOVE T,C ;+3, regular char - save starting ptr
ADD T,[7B5]
GTSFL1: ADDI TT,1 ;Bump char counter
LSH A,7 ;Accumulate last chars of field
IORI A,0(B)
CALL GTSCHR ;Get next character
TXO F,FP%SEP ;+1, separator - set flag
RET ;+2, special char - return
JRST GTSFL1 ;+3, regular char - continue collecting
; Get next input character in scanning for sender address. Skips over
; multiple blanks, tabs, and comments (...), checks for allowed special
; chars: "@" "<", ">", or <crlf>. Other special chars abort the parsing
; and require human intervention to decode the address: ",", ";", or ":".
; Entry: c/d = source byte ptr/cnt
; Call: CALL GTSCHR
; Return: +1, separator seen, b = space
; +2, special character, b = character
; +3, normal character, b = character
; Updates c/d appropriately
GTSCHR: CALL GTSLDB ;Fetch a byte
JRST GTSCH4 ;eol
IFXN. F,FP%DQT ;Quoted string?
CAIE B,.CHDQT ;Yes, ending now?
JRST R2SKP ;No, take char as is
TXZ F,FP%DQT ;Turn off quote flag
JRST GTSCH1 ;And make like it is a separator
ENDIF.
CAIE B,.CHSPC ;Space?
CAIN B,.CHTAB ;Tab?
JRST GTSCH1 ;Yes
CAIN B,"(" ;Start of comment?
JRST GTSCH2 ;Yes
CALL CHKSPC ;Address punctuation?
RETSKP ;Yes, return +2
JRST R2SKP ;No, treat as regular char, return +3
; Here to process separators
GTSCH1: CALL GTSLDB ;Fetch a byte
JRST GTSCH4 ;EOL
CAIE B,.CHSPC ;Space or tab?
CAIN B,.CHTAB
JRST GTSCH1 ;Yes, skip over it
CAIE B,"(" ;Start of comment?
JRST GTSCH3 ;No, end of separator
; Here to skip over a comment (...)
GTSCH2: CALL GTSLDB ;Fetch a byte
IFNSK.
SETO B, ;eol before matching ")", fail
RETSKP ;Return +2 (special char)
ENDIF.
CAIN B,")" ;End of comment?
JRST GTSCH1 ;Yes, back to skipping separtors
JRST GTSCH2 ;Find end of comment
; Here on end of a separator
GTSCH3: CALL CHKSPC ;Special char after the separator?
RETSKP ;Yes, return it +2
MOVEI B,.CHSPC ;Return " " for separator
ADD C,[7B5] ;Back up input ptr/cnt
AOJA D,R
; Here on end of line
GTSCH4: MOVEI B,.CHCRT ;b := <cr>
RETSKP ;Return +2 (special char)
; Routine to fetch a byte from a sender line. Ignores null's and del's.
; Entry: c/d = ptr/cnt to input line
; Call: CALL GTSLDB
; Return: +1, eol encountered
; +2, b = next char
GTSLDB: SOJL D,R ;EOL if count exhausted
ILDB B,C ;b := next char
TXNE F,FP%DQT ;Quoted string?
RETSKP ;Yes, return whatever it is
CAIE B,.CHNUL ;Null?
CAIN B,.CHDEL ;Or DEL
JRST GTSLDB ;Yes, ignore it
RETSKP ;Got a char, return +2
; Routine to categorize special chars
; Entry: b = char
; Call: CALL CHKSPC
; Return: +1, char part of address punctuation
; +2, char not part of punctuation
CHKSPC: TXNE F,FP%DQT ;Quoted string?
RETSKP ;Yes, char can't be special
CAIN B,.CHDQT ;Start of quoted string?
RET ;Yes
CAIE B,"<" ;Part of <> address subfield?
CAIN B,">"
RET ;Yes
CAIN B,"@" ;Start of host field?
RET ;Yes
CAIE B,"," ;Human intervention required?
CAIN B,";"
JRST CHKSP0 ;Yes
CAIN B,":" ;Human intervention required?
JRST CHKSP0 ;Yes
RETSKP
; Here char is not a recognized punctuation char but is not part of
; regular name either..
CHKSP0: SETO B,
RET
;; Premature EOF
QUEEOF: CALL QUEBAD ;Setup message back to luser
HRROI B,[ASCIZ/Premature end of file, /]
SOUT%
JRST QUEBDR ;Finish up
;; Bad control parameter specification
QUEBPM: CALL QUEBAD
HRROI B,[ASCIZ/Bad control parameter in line "/]
QUEBP0: SOUT%
CALL PARSTR
MOVE B,C
MOVN C,D
SOUT%
SETZ C,
JRST QUEBH1
;; Here on invalid sender spec
GQUSDB: CALL QUEBAD ;Too many, set up neg ack file
HRROI B,[ASCIZ/Invalid sender specification.
/]
SETZ C, ;Print the bad news
SOUT%
JRST QUEBDF ;Abort
;; Bad host
QUEBHS: CALL QUEBAD
HRROI B,[ASCIZ/No such host as "/]
SOUT%
HRROI B,HSTBUF
SOUT%
QUEBH1: HRROI B,[ASCIZ/",
/]
SOUT%
QUEBDR: SKIPE MSGJFN(M)
SKIPN MSGPAG(M)
IFSKP.
HRROI B,[ASCIZ/bad queue file follows:
-------
/]
SETZ C,
SOUT%
PUSH P,A
HRRZ A,MSGJFN(M)
SIZEF%
IFNSK.
HLRZ B,MSGPAG(M)
IMULI B,5000
ENDIF.
POP P,A
MOVN C,B
HRRZ B,MSGPAG(M)
IMULI B,1000
HRLI B,(<POINT 7,0>)
SKIPGE C
SOUT%
HRROI B,[ASCIZ/
-------
/]
SETZ C,
SOUT%
CLOSF%
JFATAL <Could not close queue file>
HRRZ A,MSGJFN(M) ;Get back file jfn
PUSH P,A ;Save it
TXO A,CO%NRJ
CALL UNMQUF ;Unmap
NOP
POP P,A ;And get rid of it
DELF%
JWARN <Could not delete bad queue file>
JRST QUEBRT
ENDIF.
HRROI B,[ASCIZ/ file renamed to /]
SOUT%
QUEBDF: CALL RENBAD ;Rename file as bad
HRROI B,STRBUF
SETZ C,
SOUT%
HRROI B,[ASCIZ/
-------
/]
SOUT%
CLOSF%
JFATAL <Could not close queue file>
;; Bad return
QUEBRT: CALL RELQUE ;Free entry
MOVE P,MPP ;Undo excess pushes
RET ;Single return
;;; Release storage from queue entry in M
RELQUE: PUSH P,A
PUSH P,B
PUSH P,N
PUSH P,O
HRRZ B,MSGNHD(M) ;Are there any headers allocated?
SKIPE B
CALL FREBLK
HRRZ A,MSGJFN(M)
CALL UNMQUF ;Unmap queue
NOP ;Can't happen
SKIPE N,MSGRCP(M) ;Any network recipients?
CALL RELQHS ;Yes, release the list buffers
SKIPE O,MSGLCL(M) ;Local recipients?
CALL RELQLS ;Yes, release them
SKIPE N,MSGSDR(M) ;Any "sender" specification?
CALL RELQHS ;Yes, release it
SKIPE B,MSGRPT(M) ;Any return path specification?
CALL FREBLK ;Free the return path
MOVEI B,(M) ;Release the message block itself
CALL FREBLK
POP P,O
POP P,N
JRST POPBAJ
; Routine to chase down a list of hosts/recipients, releasing the
; free space blocks in use.
; Entry: n = adr of first host entry
; Call: CALL RELQHS
; Return: +1
RELQHS: DO.
SKIPE O,HSTRCP(N) ;Any recipients for this host?
CALL RELQLS ;Yes, release them
MOVEI B,(N)
HRRZ N,HSTFLG(N) ;Link to next
CALL FREBLK ;Free this host block
JUMPN N,TOP. ;Do them all
ENDDO.
RET
; Routine to chase down a list of recipients, releasing the free space
; blocks in use for names and error msgs
; Entry: o = adr of first recipient entry
; Call: CALL RELQLS
; Return: +1
RELQLS: DO.
MOVX B,FR%ERM ;Consed error message
TDNN B,RCPFLG(O)
IFSKP.
MOVE B,RCPERR(O) ;b := error message block adr
CALL FREBLK ;Free it up
ENDIF.
MOVX B,FR%STR ;Locally generated string for name?
TDNN B,RCPFLG(O)
IFSKP.
HRRZ B,RCPBPT(O) ;Yes, can free it then
CALL FREBLK
ENDIF.
MOVEI B,(O)
HRRZ O,RCPFLG(O) ;Link to next one
CALL FREBLK ;Free this recipient block
JUMPN O,TOP. ;Do them all
ENDDO.
RET
; Routine to reset the error flags for a recipient
; Entry: o = adr of recipient block
; Call: CALL RSTRCP
; Return: +1, flags cleared and error msg block freed
; No AC's clobbered
RSTRCP: SAVEAC <B>
MOVX B,FR%ERM ;Consed error message?
TDNN B,RCPFLG(O)
IFSKP.
MOVE B,RCPERR(O) ;b := error message?
CALL FREBLK ;Free it up
ENDIF.
MOVX B,FR%FAI!FR%TMP!FR%ERM ;Clear the error flags
ANDCAM B,RCPFLG(O)
RET
; Routine to update error information for all recipients at a given
; host. If error message is already present, it is left as is unless
; the severity of the error increases from TMP to FAI.
; Entry: b = error flags
; strbuf = error msg
; saven = ptr to host block
; Call: CALL STUMSG
; Return: +1 always
STUMSG: SKIPG N,SAVEN ;n := ptr to starting recipient host
RET ;None
MOVEI O,HSTRCP(N) ;o := recipient list adr for this host
STUMS0: DO.
CALL NXTRCP ;Get the next recipient
RET ;No more, quit
JN FR%FAI,RCPFLG(O),TOP. ;Leave alone if recipient already lost hard
TXNE B,FR%FAI ;Increasing soft to hard?
CALL RSTRCP ;Yes, clear out the old stuff
CALL STEMSG ;Install new failure flags and msg
LOOP. ;Do next recipient
ENDDO.
; Routine to install failure information for addressee
; Entry: b = error flags
; strbuf = error msg (attached to user if FR%ERM on in b)
; o = adr of recipient block
; Call: CALL STEMSG
; Return: +1 always
STEMSG: SAVEAC <A>
JN FR%FAI,RCPFLG(O),R ;Leave alone if recipient already lost hard
IFXN. B,FR%ERM ;Append error msg now?
ANDQE. FR%ERM,RCPFLG(O) ;Yes, but not if a message installed already
MOVEI A,STRBUF ;a := ptr to last response
PUSH P,B ;Save flags
CALL CPYSTR ;Get a copy
MOVEM B,RCPERR(O) ;Install it
POP P,B
ENDIF.
IORM B,RCPFLG(O) ;Flag failure type
RET
; Routine to set up an appropriate failure msg for all hosts/recipients
; using the information already collected for hosts that were processed.
; If this is to dequeue the msg file, all errors become hard. If it is
; just to notify the sender, temporary errors are conjured up. Default
; errors are used when none came out of the processing.
; Entry: m = adr of message block
; Call: CALL SERRCP
; Return: +1
SERRCP: JSR SAVACS ;Save the ac's
MOVE A,[POINT 7,STRBUF] ;Set up default error msg
MOVEI B,[ASCIZ/Cannot append to mailbox/]
CALL MOVST0
MOVEI O,MSGLCL(M) ;Do locals first
TXO F,FQ%DON ;We must have done the locals
CALL SERRLS ;Hack this list
MOVE A,[POINT 7,STRBUF] ;Set up default error msg
MOVEI B,[ASCIZ/Cannot connect to host/]
CALL MOVST0
MOVEI N,MSGRCP(M) ;Now scan net recipients
DO.
HRRZ N,(N) ;n := next host block adr
JUMPE N,R ;Quit on 0
MOVX B,FH%DON ;"Host done" set?
TDNN B,HSTFLG(N)
TXZA F,FQ%DON ;No, clear flag
TXO F,FQ%DON ;Yes, record fact
SKIPG NTDEQF ;Dequeueing msg?
IORM B,HSTFLG(N) ;Yes, always show host done
MOVEI O,HSTRCP(N) ;Do recipients for this host
CALL SERRLS
LOOP. ;Do all hosts
ENDDO.
; Routine to scan a list of recipients and install failure/error
; Entry: o = adr of recipient list
; strbuf = default error string if none already given
; Call: CALL SERRLS
; Return: +1
SERRLS: DO.
HRRZ O,(O) ;o := adr of next recipient
JUMPE O,R ;Done with list
MOVE A,RCPFLG(O) ;Fetch recipient flags
JXN A,FR%FAI,TOP. ;Ignore if hard error already seen
IFXE. A,FR%TMP ;Any temporary error seen?
JXN F,FQ%DON,TOP. ;No, if host processed, assume recipients ok
ENDIF.
MOVX B,FR%ERM!FR%TMP ;If notifying sender, leave error temporary
SKIPL NTDEQF ;Dequeueing msg?
IFSKP.
ANDCAM B,RCPFLG(O) ;Yes, clear "temporary" error indicators
MOVX B,FR%ERM!FR%FAI ;And make error hard
ENDIF.
CALL STEMSG ;Set the error message
LOOP. ;Do all recipients at this host
ENDDO.
; Here to unmap a queued msg file
UNMQUF: MOVE D,MSGPAG(M)
CALL UNMQU0
SKIPA
AOS (P)
SETZM MSGJFN(M)
SETZM MSGPAG(M)
RET
UNMQU0: JUMPE D,UNMQU1
PUSH P,A
HLRZ A,D
HRRZ B,D
CALL PAGDAL
POP P,A
UNMQU1: JUMPE A,R
TXZN A,CO%NRJ ;Don't release JFNs?
IFSKP.
PUSH P,A ;Yes, save JFN
HRROI A,STRBF1 ;Buffer to put filename string into
HRRZ B,(P) ;JFN to release
MOVE C,[111110,,JS%PAF] ;Dev/dir/nam/ext/gen, punctuate
JFNS% ;Get string for this file
IFJER.
ADJSP P,-1
RET ;In case JFN already released somehow
ENDIF.
MOVX A,GJ%SHT!GJ%OLD!GJ%DEL ;Now get another JFN
HRROI B,STRBF1 ;On the same filename
GTJFN% ;Get virgin JFN in A
IFJER.
POP P,A ;Get back JFN
CLOSF% ;Flush it
NOP ;Don't care if it failed
RET
ENDIF.
POP P,B ;Old JFN in B
SWJFN% ;Make old JFN caller know about virgin JFN
ENDIF.
CLOSF% ;Flush the JFN
JWARN <Error closing queue file in UNMQUF>
RETSKP
;;; Create a response queue file for a bad one
QUEBAD: CALL RESPQF ;Initialize the file
CALL SDRADR ;Addressee = sender
CALL RESPQB ;Finish up the file
HRRZ B,MSGJFN(M)
MOVE C,[111110,,1]
JFNS%
HRROI B,[ASCIZ/
/]
SETZ C,
SOUT%
RET
;;; Rename a bad file
RENBAX: PUSH P,A ;Save a
PUSH P,A ;Save the JFN
JRST RENBA0
RENBAD: PUSH P,A ;Save present JFN
HRRZ A,MSGJFN(M)
PUSH P,A
TXO A,CO%NRJ
CALL UNMQUF ;Unmap, leave JFN
IFNSK.
ADJSP P,-1
JRST CPOPAJ
ENDIF.
RENBA0: HRROI A,STRBUF
HRRZ B,(P)
MOVE C,[110000,,1]
JFNS%
MOVE B,FILIDX ;b := index to current file type
HRRZ B,%FLSTR(B) ;b := ptr to "bad file" name
CALL MOVSTR
HRROI B,[ASCIZ/;P770000/]
SETZ C,
SOUT%
DO.
MOVX A,GJ%NEW!GJ%FOU!GJ%SHT
HRROI B,STRBUF
GTJFN%
IFJER.
CAIE A,GJFX24 ;Work around monitor bug
JWARN <Cannot get BAD file>
MOVEI A,^D5000 ;Wait 5 seconds
DISMS%
LOOP.
ENDIF.
ENDDO.
MOVE B,A
POP P,A
CALL RNMFIL ;Rename the file
IFNSK.
JWARN <Cannot rename BAD file>
EXCH A,B ;A:=existing JFN, B:=JFN we failed to rename to
RLJFN% ;Flush the failing JFN
NOP
ENDIF.
HRROI A,STRBUF
MOVE C,[111110,,1]
JFNS%
MOVE A,B
RLJFN%
JWARN
JRST CPOPAJ
;;; Create a response queue file
RESPQN: SKIPA A,[[ASCIZ/[--RETURNED-MAIL--].NEW-NOTIFY-/]]
RESPQF: MOVEI A,[ASCIZ/[--RETURNED-MAIL--].NEW-FAILURE-/]
STKVAR <<GTJARG,2>,TMPJFN,RESPQT>
MOVEM A,RESPQT ;Save queue type
HRROI A,STRBUF ;Put this file where msg file came from
HRRZ B,MSGJFN(M)
MOVE C,[110000,,1]
JFNS%
MOVE B,RESPQT
CALL MOVSTR
MOVE B,FORKX
MOVX C,^D8
NOUT%
JFATAL
MOVEI B,[ASCIZ/;P770000/]
CALL MOVST0
MOVX A,GJ%NEW!GJ%FOU!GJ%SHT
HRROI B,STRBUF
SETZ C,
DMOVEM A,GTJARG ;Save the args
DO.
DMOVE A,GTJARG ;Install args
GTJFN%
IFJER.
CAIE A,GJFX24 ;Work around monitor bug
JWARN <Cannot get queue file>
MOVEI A,^D5000 ;Wait 5 seconds
DISMS%
LOOP.
ENDIF.
MOVEM A,TMPJFN ;Save the JFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%WR>
OPENF%
IFJER.
EXCH A,TMPJFN ;Recover JFN, save error code
RLJFN% ;Release it
JWARN
MOVEI A,^D5000 ;Wait a few seconds
DISMS%
MOVE A,TMPJFN ;Recover error code
CAIE A,OPNX9 ;No error if file just busy
CAIN A,OPNX2 ;File disappeared?
LOOP. ;Yes, try again
WARN <Cannot open queue file - %1E>
LOOP.
ENDIF.
ENDDO.
HRLI A,.FBBYV ;Set to retain infinite versions
MOVX B,FB%RET
SETZ C,
CHFDB%
HRRZS A ;a := output JFN
CALLRET SDRMLA ;Write the sender header = mail agent
ENDSV.
;; Here to set up "DISCARD-ON-ERROR" parameter
; Entry: a = output jfn
DSCRDE: MOVEI B,.CHFFD ;Signal parameter start
BOUT%
HRROI B,[ASCIZ/=DISCARD-ON-ERROR
/]
SETZ C,
SOUT%
RET
; Here to finish up reply file header
RESPQB: MOVEI B,.CHFFD ;Terminate addressee headers
BOUT%
HRROI B,[ASCIZ/
Date: /]
SOUT%
SETO B, ;Now
MOVX C,OT%DAY!OT%SPA!OT%TMZ!OT%SCL!OT%822 ;RFC 822 standard date/time
ODTIM%
HRROI B,[ASCIZ/
From: The Mailer Daemon </] ;> -- so MACRO doesn't fail
SETZ C,
SOUT%
HRROI B,MLAGNT ;Use MLAGNT so user can reply
SOUT%
MOVEI B,"@"
BOUT%
MOVEI B,.CHDEL
BOUT%
HRROI B,LCLNAM ;Get local host name string
SOUT%
MOVEI B,.CHDEL
BOUT%
HRROI B,[ASCIZ/>
To: /]
SOUT%
MOVE D,MSGSDR(M) ;d := entry adr for sender
HRRZ C,HSTRCP(D)
MOVE B,RCPBPT(C) ;b,c := ptr,-cnt to sender name string
MOVN C,RCPCNT(C)
SOUT% ;write the sender's address
MOVEI B,"@"
BOUT%
MOVEI B,.CHDEL
BOUT%
HRRO B,HSTHST(D) ;Get the host pointer
SOUT%
MOVEI B,.CHDEL
BOUT%
HRROI B,[ASCIZ/
Subject: /]
SOUT%
RET
; Routine to output the sender as "sender" or "addressee" in mail file
; header
; Entry: a = output JFN
; m = ptr to queued msg block
; Call: CALL SDRHDR ("sender" = sender)
; CALL SDRADR ("addressee" = sender)
; Return: +1, b = ptr to sender host string
SDRHDR: MOVEI B,.CHFFD ;Do ff to signal host
BOUT%
MOVX B,"_" ;Flag "sender" header
SKIPA
SDRADR: MOVX B,.CHFFD ;Do ff to signal host
BOUT%
PUSH P,C ;Save ac's
PUSH P,D
MOVE D,MSGSDR(M) ;d := hst entry adr for sender
HRRO B,HSTHST(D) ;b := file site tbl adr for host
SETZ C,
SOUT%
HRROI B,CRLF0 ;Terminate line
SOUT%
HRRZ C,HSTRCP(D) ;d := adr of sender recipient list
MOVE B,RCPBPT(C) ;b,c := ptr,-cnt to sender name string
MOVN C,RCPCNT(C)
SOUT%
HRROI B,CRLF0 ;Terminate line
SOUT%
POP P,D ;Recover working ac's
POP P,C
RET
; Routine to output a "sender" = mail agent header
; Entry: a = output JFN
; Call: CALL SDRMLA ("sender" = mail agent)
; CALL MLAADR ("addressee" = mail agent)
; Return: +1
SDRMLA: MOVEI B,.CHFFD ;Do ff to signal host
BOUT%
MOVX B,"_" ;Flag "sender" header
SKIPA
MLAADR: MOVX B,.CHFFD ;Do ff to signal host
BOUT%
HRROI B,LCLNAM ;Get local name string
SETZ C,
SOUT%
HRROI B,CRLF0
SOUT%
HRROI B,MLAGNT ;Now the mail agent's name
SOUT%
HRROI B,CRLF0
SOUT%
RET
;;; Generate headers for message in M to host in A
; B has the ultimate host pointer while A has the "neighbor" host
; host pointer
GENHDL: SETZ A, ;Local host; no special transmogrification
SKIPA E,[LCLNAM] ;Don't convert LCLNAM to LCLNCN
GENHDR: MOVEI E,LCLNCN ;Convert LCLNAM to LCLNCN
JSR SAVACS ;Save all AC's
STKVAR <LCLHPT,DSTHPT,<HSTTMP,^D52>,LINCNT,ULTHPT>
MOVEM B,ULTHPT ;Save ultimate destination host pointer
MOVEM A,DSTHPT ;Save destination host pointer
MOVEM E,LCLHPT ;Save local name pointer
DMOVE X,MSGHDR(M) ;Start of headers of message
SKIPN O,MSGNHD(M) ;Was there a block from last time?
IFSKP.
HRRZ A,-1(O) ;Get size of block
ELSE.
MOVEI A,100 ;Nominal block to allocate
CALL ALCBLK
FATAL <Memory exhausted>
MOVEI O,(B)
MOVEM O,MSGNHD(M)
ENDIF.
HRLI O,(<POINT 7,0>)
MOVEI N,(A)
IMULI N,5 ;Number of bytes available
MOVEM N,HDRLEN ;Save it in case we grow
DO. ;Output BP in O, free byte count in N
DMOVEM X,MSGTXT(M)
CALL PARLIN ;Read a line
IFNSK.
MOVE C,[POINT 7,CRLF0] ;Failed, just write CRLF
MOVEI D,2
EXIT.
ENDIF.
IFXN. F,FP%EOL ;Blank line?
DMOVEM X,MSGTXT(M) ;Update start of actual message text
MOVE C,[POINT 7,[BYTE (7) .CHCRT,.CHLFD,.CHCRT,.CHLFD,.CHNUL]]
MOVEI D,4
EXIT. ;Yes, finish up then
ENDIF.
IFXE. F,FP%CLN!FP%WSP ;Looks like a valid line?
MOVE C,[POINT 7,CRLF0] ;No, just write CRLF
MOVEI D,2
EXIT.
ENDIF.
IFXE. F,FP%DEL ;Is this a special line?
CALL OUHNWL ;New line
CALL PARSTR ;Get whole line
CALL OUHSTR ;Finish
LOOP. ;And go hack next line
ENDIF.
MOVE T,PLINBP+1 ;Save line context (may get host error)
MOVEM T,LINCNT
CALL PARDEL ;Canonicalize lengths
DMOVE C,PDELBP ;Start of host
CALL PARHST ;Parse it
IFNSK.
MOVE T,LINCNT ;Bad host! Restore line context
MOVEM T,PLINBP+1
CALL OUHNWL ;Make like never saw <del>'s
CALL PARSTR ;Get whole line
CALL OUHSTR ;Output it
LOOP. ;And go hack next line
ENDIF.
MOVEI A,HSTTMP ;Copy returned string so we can muck it
HRLI A,(<POINT 7,>) ;Make string pointer
MOVEM A,PDELBP ;Save pointer
CAIN B,LCLNAM ;Local host name returned?
MOVE B,LCLHPT ;Yes, use local name for this network
MOVE C,ULTHPT ;Ultimate destination host pointer
MOVE D,DSTHPT ;Destination host pointer
CALL TRNMGR ;Transmogrify host
IFSKP.
SOS PLINBP+1 ;Flush "@" preceeding
SOS PWSPBP+1
ENDIF.
SETZ C, ;Now count its length
DO.
ILDB B,A ;Get byte
CAIE B,.CHNUL ;Null?
AOJA C,TOP. ;No, count it and do another
ENDDO.
MOVEM C,PDELBP+1 ;Save length too
IFXN. F,FP%WSP ;Is this a continuation line?
MOVEI T,1(E) ;Length of line so far, plus a new space
ADD T,PWSPBP+1 ;Plus line without whitespace
ADD T,PDELBP+1 ;Plus start of host
ADD T,PDELB2+1 ;Plus end of host
CAIL T,^D79 ;Is that a reasonable length line?
IFSKP.
MOVEI T,.CHSPC ;Yes, put in a space
CALL OUHCHR
DMOVE C,PWSPBP ;And use start of stuff after whitespace
ELSE.
CALL OUHNWL ;New line
DMOVE C,PLINBP ;Use start of line
ENDIF.
ELSE.
CALL OUHNWL ;New line
DMOVE C,PLINBP ;Use start of line
ENDIF.
CALL OUHSTR ;Output it
DMOVE C,PDELBP ;First part of host
CALL OUHSTR ;Output that
DMOVE C,PDELB2 ;Rest of line
CALL OUHSTR ;Finish
LOOP. ;And go hack next line
ENDDO.
CALL OUHSTR
MOVE T,MSGNHD(M)
HRRZ T,-1(T) ;Length of block
IMULI T,5 ;Total bytes
SUB T,N ;Less bytes left is bytes used
HRLM T,MSGNHD(M)
RET
ENDSV.
;TRNMGR - transmogrify host name for destination host
; A/ output byte pointer
; B/ host pointer
; C/ ultimate destination host pointer
; D/ destination host pointer
; Returns +1 if no transmogrification is needed
; +2 if transmogrified so preceeding "@" should be flushed.
;
TRNMGR: SAVEAC <A,B,C,D> ;Don't clobber invoker's context
STKVAR <BUFPTR,SRCPTR,DSTPTR,DOMPTR,ULTPTR,UPPLIM,INTDOM,ATPTR>
MOVEM A,BUFPTR ;Save the output buffer pointer
HRRZM B,SRCPTR ;Save source pointer
MOVEM C,ULTPTR ;Ultimate destination pointer
HRRZM D,DSTPTR ;Save destination pointer
CALL MOVST0 ;Make copy of src to output buffer
MOVE A,BUFPTR ;Remove relative domains
CALL $RMREL
; Don't transmogrify if the source and destination are on the same network
; providing that network is a full-connectivity net. At the present time,
; only Special is not (or rather is not guaranteed to be such). This tries
; to avoid unnecessary transmogrification.
MOVE A,SRCPTR ;Check source
HRLI A,(<POINT 7,>)
SETZM DOMPTR ;Look for relative domain
DO.
ILDB B,A
IFN. B
CAIN B,"."
MOVEM A,DOMPTR
LOOP.
ENDIF.
ENDDO.
ILDB A,DOMPTR ;Now see if it's really relative
CAIE A,"#"
IFSKP.
MOVE A,DOMPTR ;It is, see if it's a full-connectivity net
HRROI B,[ASCIZ/Special/] ;"Special" is the only one that isn't
STCMP%
ANDN. A ;Full-connectivity net?
MOVE A,ULTPTR ;Check destination
HRLI A,(<POINT 7,>)
SETZM ATPTR ;Look for relative domain in destination
DO.
ILDB B,A
IFN. B
CAIN B,"."
MOVEM A,ATPTR
LOOP.
ENDIF.
ENDDO.
ILDB A,ATPTR ;Now see if it's really relative
CAIE A,"#"
ANSKP.
MOVE A,DOMPTR ;It is, see if it's the same net
MOVE B,ATPTR
STCMP%
JUMPE A,R ;If the same, then no transmogrification
ENDIF.
SETZM DOMPTR ;See if there is a real domain
MOVE A,BUFPTR
DO.
ILDB B,A
IFN. B
CAIN B,"." ;Domain separator?
MOVEM A,DOMPTR ;Save the pointer for later
LOOP.
ENDIF.
ENDDO.
SKIPN B,DOMPTR ;Is there a domain?
IFSKP.
MOVE A,DOMTBL ;Yes, it one of the pseudo-domains?
TBLUK%
IFXE. B,TL%EXM ;Found it?
SKIPN TRALLP ;No, do we always transmogrify?
RET ;No, no transmogrification needed then
ELSE.
SETZ C,
DPB C,DOMPTR ;Remove pseudo-domain
MOVE A,DOMPTR ;Pointer to pseudo-domain
HRROI B,[ASCIZ/$Internet/]
STCMP% ;See if going to Internet
JUMPE A,R ;Yes, so don't bother transmogrifying
ENDIF.
ENDIF.
;Try to transmogrify the source so that the destination will know about it
SKIPN DSTPTR ;Local delivery?
RET ;Yes, return
MOVE A,SRCPTR ;The source host
MOVE B,ULTPTR ;This destination host
CALL TRNBLD ;Build relay tables, SRLYTB, DRLYTB
SETZM PTHLST ;Set the first element of the path 0 to start
;Find the Internet domain block address; save it in INTDOM
MOVE A,DOMTBL ;Yes, is the domain relayed to?
HRROI B,[ASCIZ/$Internet/]
TBLUK%
TXNE B,TL%NOM ;Find it?
TDZA B,B ;Didn't find it, Internet not defined here
HRRZ B,(A) ;Yes, get domain block address in B
MOVEM B,INTDOM ;Internet domain block address
;Add the source host to our path first
SKIPN A,INTDOM ;A/ domain block; is it in the Internet domain?
IFSKP.
HRRZ B,DM%RLY(A) ;Get the relay pointer
CAME B,SRCPTR ;Is source host in Internet?
ANSKP.
MOVEI B,DM%TRN ;Yes, it is Internet use transmog. string
CALL PTHADD ;Put it in the path
JRST BLDPTH ;Since Internet, jump directly to build path
ENDIF.
MOVE D,DOMTBL ;Set up aobjn pointer to domain table
HLL D,(D)
TXC D,.LHALF
DO. ;Look for destination host
AOBJP D,ENDLP. ;Next domain
HRRZ A,(D) ;Get domain block
HRRZ C,DM%RLY(A) ;Get the host pointer
CAME C,SRCPTR ;Is it the same as the source host?
LOOP. ;No, go for more
ENDDO.
IFGE. D ;Is host a relay?
MOVE A,SRCPTR ;No
SETZ B,
ELSE. ;Yes it is host relay
MOVEI B,DM%RLY ;Not Internet, use relay string
ENDIF.
CALL PTHADD ;Add this host
;One last chance to check if we really need to transmogrify
MOVE A,SRCPTR
CAMN A,ULTPTR ;If source and destinations are the same
RET ;Then no need to do anything!
;Ascend the source table
SKIPN SNRLYS ;Any relays in source?
IFSKP. ;Yes, let's process
SETZ D, ;Start at the bottom
DO.
MOVE A,SRLYTB(D) ;Get the domain block pointer
MOVEI B,DM%RLY ;Which transmogrification string to use
CALL PTHADD ;Add this relay to the path construct
CAMN A,INTDOM ;Is it magic Internet domain?
JRST BLDPTH ;Yes, jump out
ADDI D,1 ;Increment index
CAMGE D,SNRLYS ;Less than the number of relays?
LOOP. ;Yes, loop around
ENDDO.
ENDIF.
;Add our local host here
MOVEI A,LCLNCN ;Our local name
SETZ B, ;Only a string
CALL PTHADD ;Add it to path
;now descend destination table
SKIPN D,DNRLYS ;Any relays in destination?
IFSKP. ;Yes, let's process
SUBI D,1 ;Index to start with
DO.
MOVE A,DRLYTB(D) ;Get the domain block pointer
MOVEI B,DM%TRN ;Which transmogrification string to use
CALL PTHADD ;Add this relay to the path construct
CAMN A,INTDOM ;Is it magic Internet domain?
JRST BLDPTH ;Yes, jump out
SOJGE D,TOP. ;If not bottom of the table, loop.
ENDDO.
ENDIF.
;Build the transmogified path using PTHLST
BLDPTH: SKIPN DNRLYS ;From destination to source?
SKIPN PTHEND ;More than one in the path?
IFSKP.
MOVE D,PTHEND ;Yes, get the offet of the last entry
DO.
HLRZ C,PTHLST(D) ;Get the domain flags
IFE. C ;Is it a plain string?
HRRZ A,PTHLST(D) ;Yes, get the string address
ELSE. ;Not a string, it is a domain block
HRRZ B,PTHLST(D) ;Get the domain block
HRRZ A,DM%RLY(B) ;Get a string pointer
ENDIF.
CAME A,DSTPTR ;Is it the same as the destination
IFSKP.
SETZM PTHLST(D) ;Yes, zap it from the list
EXIT. ;And done
ENDIF.
SOJG D,TOP. ;Otherwise loop until done
ENDDO.
ENDIF.
MOVE B,BUFPTR
SETZ A,
IDPB A,B ;Re-init output string by putting a zero
MOVEI D,PTHLST ;Start at the beginning of the path list
DO.
HLRZ C,(D) ;Get the flag of the entry
IFE. C ;Is it a string pointer?
HRRZ B,(D) ;Yes, get the address
MOVE A,[POINT 7,STRBF2]
CALL MOVST0 ;Make a copy of the string
MOVEI A,STRBF2
CALL RMDOM1 ;Remove the pseudo-domain
MOVE B,[POINT 7,STRBF2]
MOVEI C,"%" ;Use a % for relaying
ELSE. ;Not a string pointer, but a domain pointer
HRRZ B,(D) ;Get the domain block pointer
CAIE C,DM%TRN ;Use transmog. string as host name relay?
IFSKP. ;Yes, no need to fool around with domains
MOVE B,DM%TRN(B)
HRLI B,(<POINT 7,>) ;Point to the transmog. string
ILDB C,B ;Get the relay character
ELSE. ;Use relay string as host name relay
PUSH P,B ;Save the domain pointer
MOVE B,DM%TRN(B)
HRLI B,(<POINT 7,>) ;Point to the transmogrification string
ILDB C,B ;And get the relay character
POP P,B ;Now get the domain block pointer back
MOVE B,DM%RLY(B)
HRLI B,(<POINT 7,>) ;Point to the relay string instead
MOVE A,[POINT 7,STRBF2]
CALL MOVST0 ;Make a copy of the relay string
MOVEI A,STRBF2
CALL RMDOM1 ;Get rid of the pseudo-domain
MOVE B,[POINT 7,STRBF2]
ENDIF.
ENDIF.
;A/ output buffer B/ string to append C/ prepend character
MOVE A,BUFPTR
CALL HSTAPP ;Append this host to path
MOVEM B,ATPTR ;Save the byte pointer to the last @ sign
ADDI D,1 ;Look at next element in path list
SKIPE (D) ;End of list?
LOOP. ;No, loop
ENDDO.
MOVEI A,"@" ;The last relay character must be @ sign
DPB A,ATPTR ;Put it there
RETSKP ;Say we did a transmogrification
ENDSV.
;A/ byte pointer to host string to tweak
;
;Returns +1 always
; no change to ACS; string should be tweaked
;
RMDOM1: SAVEAC <A,B,C>
STKVAR <DOMPTR>
HRLI A,(<POINT 7,>)
CALL $RMREL
SETZM DOMPTR ;See if there is a real domain
DO.
ILDB B,A ;Get a character from the string
IFN. B ;Null (end of string)?
CAIN B,"." ;Nope, check if domain separator
MOVEM A,DOMPTR ;Yes, save the pointer for later
LOOP. ;Back for more
ENDIF.
ENDDO.
SKIPN B,DOMPTR ;See a domain?
IFSKP.
MOVE A,DOMTBL ;Look at know domains
TBLUK% ;Is it one of ours?
JXE B,TL%EXM,R ;No, don't do anything
SETZ A, ;Yes, remove pseudo-domain
DPB A,DOMPTR
ENDIF.
RET
ENDSV.
;A/ output byte pointer
;B/ string pointer
;C/ prepend character
;
; Returns +1 always
; B has byte pointer where prepend character was put
;
HSTAPP: SAVEAC <A,C,D>
STKVAR <STRPTR>
MOVEM B,STRPTR ;Save string pointer
DO. ;Look for null at end of string
ILDB B,A ;Get a character
JUMPN B,TOP. ;If not null step through string
ENDDO.
MOVE D,A ;Save the atsign pointer
DPB C,A ;Put the prepend character into string
MOVE B,STRPTR ;Get the string pointer again
CALL MOVST2 ;Append the string
MOVE B,D ;Here is the atsign pointer
RET
ENDSV.
;A/ byte pointer to the source host
;B/ byte pointer to the ultimate destination host
;
; Returns +1 always
; This routine builds the relay tables SRLYTB and DRLYTB.
; SNRLYS and DNRLYS are updated to reflect the number of relay entries
; in the respective tables.
;
TRNBLD: SAVEAC <A,B>
STKVAR <DSTPTR>
MOVEM B,DSTPTR ;Save destination pointer
CALL SRCPTH ;Build source table
MOVE A,DSTPTR ;Get the destination pointer back
CALLRET DSTPTH ;Build destination table
ENDSV.
;A/ host pointer to source host
; Returns +1 always
SRCPTH: SAVEAC <A,B,C,D>
STKVAR <SRCPTR>
MOVEM A,SRCPTR
SETZM SNRLYS ;No relays yet
;Test for local host here if source is local return
HRRZ A,SRCPTR ;Get source pointer
CAIN A,LCLNCN ;Local host
RET
;First do source. Find a path from the source host to us
DO.
HRRO A,SRCPTR ;Get name of host to check
MOVEI C,SNDRTS ;Try direct protocols first
CALL GETPRO ;Is it directly connected to us?
IFSKP.
CAME B,$UKHST ;Do the relay thing if we really don't know
RET ;Looks good, return
ENDIF.
HRRO A,SRCPTR ;Get the host to find relay for
CALL $GTRLY ;Get the relay
RET
MOVE A,DM%RLY(B) ;Get the pointer
MOVEM A,SRCPTR ;Save it as the next host pointer
MOVE A,SNRLYS ;Get the number of relays
MOVEM B,SRLYTB(A) ;Save the domain block pointer
AOS SNRLYS ;Increment number of relays we saw
LOOP. ;Go up and try again
ENDDO.
ENDSV.
;A/ pointer to destination host pointer
; Returns +1 always
;Now do destination. Find a path from the destination host to us
DSTPTH: SAVEAC <A,B,C,D>
STKVAR <DSTPTR>
MOVEM A,DSTPTR
SETZM DNRLYS
HRRZ A,DSTPTR ;Get destination pointer
CAIN A,LCLNCN ;Is it local?
RET ;Yes, return
DO.
HRRO A,DSTPTR ;Get name of host to check
MOVEI C,SNDRTS ;Try direct protocols first
CALL GETPRO ;Is it directly connected to us?
IFSKP.
CAME B,$UKHST ;Do the relay thing if we really don't know
RET ;Looks good, return
ENDIF.
HRRO A,DSTPTR ;Get the host to find relay for
CALL $GTRLY ;Get the relay
RET ;Probably local host
MOVE A,DM%RLY(B) ;Get the pointer
MOVEM A,DSTPTR ;Save it as the next host pointer
MOVE A,DNRLYS ;Get the number of relays
MOVEM B,DRLYTB(A) ;Save the domain block pointer
AOS DNRLYS ;Increment number of relays we saw
LOOP. ;Go up and try again
ENDDO.
ENDSV.
;A/ domain block pointer or string pointer
;B/ if 0, A is string pointer
; if non-zero, A is a domain block pointer and the value of B
; is the offset into the domain block for transmogrification string
PTHADD: SAVEAC <A,B,C,D>
SETZ D,
HRRZ A,A ;Only address, just in case
DO. ;Step through list looking for duplicates
SKIPN C,PTHLST(D) ;Get element from path list
IFSKP.
HRRZ C,C ;Only the address
CAMN C,A ;Are the 2 domains the same?
EXIT. ;Yes, out of loop
ADDI D,1 ;No, incr. index
LOOP.
ENDIF.
ENDDO.
;D/ where to put the domain or string pointer
HRL A,B ;Move the flag bits to LH of A
MOVEM A,PTHLST(D) ;Save the next path
MOVEM D,PTHEND ;Save the end of the list
ADDI D,1 ;Next location
SETZM PTHLST(D) ;Zero the next location to end list
RET
;;; Header string output routines, byte pointer is in O,
;;; count of bytes left is in N, length of line is in E
OUHNWL: DMOVE C,[POINT 7,CRLF0
2]
TDZA E,E ;Init to 0
OUHSTR: ADDI E,(D) ;Update length of line
JUMPE D,R ;Nothing if empty string
SAVEAC <C,D>
DO.
ILDB T,C
CALL OUHCHR
SOJG D,TOP.
ENDDO.
RET
OUHCHG: MOVE B,MSGNHD(M)
HRRZ A,-1(B) ;Length of block now
ADDI A,100 ;Increment by this much
SUBI O,(B) ;Make pointer relative in case relocated
CALL GROBLK
FATAL <Memory exhausted>
MOVEM B,MSGNHD(M)
ADDI O,(B) ;Make pointer absolute again
IMULI A,5 ;Number of bytes total available
MOVE N,HDRLEN ;Get previous size of block
SUBM A,N ;Update now available
MOVEM A,HDRLEN ;Update for current size
OUHCHR: SOJL N,OUHCHG ;Room left in buffer?
IDPB T,O ;Yes, just stick it in
RET
SUBTTL Sending routines
;;; Send the message in M
SNDMSG: JSR SAVACS ;I don't know why, but it's necessary
STKVAR <RLYLST>
SETZM RLYLST
TXZ F,FM%RLY ;Not relaying here
MOVEI N,MSGRCP(M) ;Start of recipient list
DO.
SKIPN MSGTMT(M) ;Total timeout for msg?
IFSKP.
TIME% ;Yes, elapsed yet?
CAML A,MSGTMT(M)
RETSKP ;Yes, quit on this round
ENDIF.
;The following loop looks for the next physical host. If we are in the
;middle of relaying, it will try the next host in the list of possible
;relays. Otherwise, it will try the next host in the list of recipient
;hosts. The only exit from this loop is the success return from GETPTH.
;So after this loop, the AC's will be set as in GETPTH, for some
;physical host (i.e. if we have to relay, the relay host).
DO. ;Look for a host to send to
IFXE. F,FM%RLY ;Have we been relaying?
HRRZ N,(N) ;No, get next host
JUMPE N,RSKP ;None, done for now
MOVX TT,FH%DON ;Already done this one?
TDNE TT,HSTFLG(N)
LOOP. ;Yes, look at the next
HRRZ B,HSTHST(N) ;Get host pointer
CALL GETPTH ;Do we have a direct path?
IFSKP. <EXIT.> ;Yes, do it then
HRRO A,HSTHST(N) ;Get back the host
CALL $GTRLY ;See if we can relay to it
LOOP. ;No, so much for that host...
SKIPN B,DM%RLY(B) ;Get list of relays
LOOP. ;None
MOVEM B,RLYLST ;Initial current list pointer
TXO F,FM%RLY ;Note that we are relaying
ENDIF.
; Try to find physical host to send to. This will recurse as necessary.
;Someday this routine needs to be rewritten to be somewhat more general and
;allow more flexibility in MAILER-RELAY-INFO.TXT.
DO.
MOVE B,RLYLST ;Get current relay list pointer
CALL GETPTH ;Have a path to this relay?
IFSKP. <EXIT.>
HRRO A,RLYLST ;Let's see if we can relay to it
CALL $GTRLY ;Well?
IFSKP.
MOVE B,DM%RLY(B) ;Yes, get host we can relay to
ELSE.
HLRZ B,RLYLST ;Get pointer to more
SKIPE B ;Is there?
MOVE B,(B) ;Yes, go get it
ENDIF.
MOVEM B,RLYLST ;Save current pointer
JUMPN B,TOP. ;Try again if any more to go
ENDDO.
IFE. B ;Found a host to send this to?
TXZ F,FM%RLY ;No, fail utterly
LOOP. ;Do next host
ENDIF.
ENDDO.
MOVX TT,FH%DN1 ;Mark that we are trying to do this one
IORM TT,HSTFLG(N)
MOVEI O,HSTRCP(N) ;Point to start of recipients
MOVEM C,FRNADR ;Save returned host address
MOVEM B,FRNHST ;Remember the host we're connecting to
HRRO B,HSTHST(N) ;Get final destination
CIETYPE < Queued mail for %2W>
HLRZ T,E ;Get protocol name
IFXN. F,FM%RLY ;If relaying
HRRO B,FRNHST ;Get back immediate destination
ETYPE < routing via %2W using %6W>
ELSE.
ETYPE < using %6W>
ENDIF.
TXZ F,FM%FAI ;Haven't failed
MOVEM N,SAVEN ;Save the position in the host list
HRRZ A,HSTHST(N) ;Get final destination
MOVE B,FRNHST ;Get back host pointer
MOVE C,FRNADR ;Get the address back
CALL (E) ;Call the routine
IFNSK.
TXO F,FM%FAI ;Failed
TYPE < failed.>
IFXN. F,FM%RLY ;If relaying
HLRZ T,RLYLST ;Then go to next possible host
SKIPE T ;If zero, no more relays
SKIPN T,(T) ;Else get next relay
TXZ F,FM%RLY ;Note we're no longer relaying
MOVEM T,RLYLST
ENDIF.
ELSE. ;If it succeeded
SETZM RLYLST ;Forget any further possible relay hosts
TXZ F,FM%RLY ;Note we're no longer relaying
SKIPN A,STAJFN ;Doing statistics?
ANSKP.
HRRO B,FRNHST ;Get back host pointer
SETZ C, ;Null-terminated
SOUT%
ERJMP .+1
MOVX B,"," ;Delimiter
BOUT%
ERJMP .+1
HLRZ B,MSGNHD(M) ;Length of headers generated
ADD B,MSGTCN(M)
MOVX C,^D10 ;In decimal
NOUT%
ERJMP .+1
HRROI B,CRLF0 ;Finally output CRLF
SETZ C,
SOUT%
ERJMP .+1
ENDIF.
MOVE T,SAVEN ;Recover starting recipient host
DO.
MOVX TT,FH%DN1 ;Check if "about to be done"
TDNN TT,HSTFLG(T)
IFSKP.
ANDCAM TT,HSTFLG(T) ;If so, clear that
MOVX TT,FH%DON
TXNN F,FM%FAI ;Unless it failed
IORM TT,HSTFLG(T)
ENDIF.
CAIN T,(N) ;Reached host we just processed?
EXIT. ;Yes
HRRZ T,(T) ;May have sent more, check them out
JUMPN T,TOP.
ENDDO.
MOVE N,SAVEN ;Recover starting host
LOOP. ;Loop
ENDDO.
ENDSV.
; Get the next recipient for this route, skip if success
; Call: CALL NXTRCP
; N/ Current host block
; O/ Current recipient block
; FRNHST: The current host we have a connection to
; Returns:
; +1 if no more possible recipients
; +2 new recipient
; N/ Host block (possibly changed if relaying)
; O/ Recipient block (definitely changed)
;
NXTRCP: SAVEAC <A,B,C>
HRRZ O,(O) ;Next recipient
JUMPN O,RSKP ;Found one
RET ;Don't - old optimization code is history since
; often the headers were wrong
; Find the path to a given host
; Call: CALL GETPTH
; B/ Host pointer
; Returns:
; +1 No path to host
; +2 path found
; E/ Protocol name,,routine
; B/ Host pointer
; C/ Numeric address to use for this protocol
;
GETPTH: STKVAR <HSTPTR>
MOVEM B,HSTPTR ;Set up pointer
CALL HSTDED ;Is host up?
RET ;No, no path
MOVEI C,SNDRTS ;Try direct protocols first
HRRO A,HSTPTR ;Get name
CALL GETPRO ;Try to find a protocol
RET ;None
MOVE E,(C) ;Get protocol data
MOVE C,B ;Get foreign host address for this protocol
MOVE B,HSTPTR ;Get foreign host pointer
RETSKP
ENDSV.
;;; Output host in B in absolute form to the output designator in A
HSTTSZ==^D40
OUTAHS: SAVEAC <C,D>
STKVAR <HSTPTR,<HSTTMP,HSTTSZ>>
MOVEM A,HSTPTR ;Save output designator
MOVEI A,HSTTMP ;Get copy of host name in HSTTMP
HRLI A,(<POINT 7,>)
HRLI B,(<POINT 7,>)
MOVX D,<5*HSTTSZ>-1 ;Up to this many bytes
DO.
ILDB C,B
JUMPE C,ENDLP.
IDPB C,A
SOJG D,TOP.
SETZ C, ;Tie off string
ENDDO.
IDPB C,A
HRROI A,HSTTMP ;Remove relative domains
CALL $RMREL
MOVE A,HSTPTR ;Restore output designator
HRROI B,HSTTMP ;B := host in absolute form
SETZ C,
SOUT%
RET
ENDSV.
;;; Output host in B in absolute form to the pointer in A with quoting
OUTAHQ: STKVAR <HSTPTR,<HSTTMP,^D13>>
MOVEM A,HSTPTR ;Save output designator
MOVEI A,HSTTMP ;Get copy of host name in HSTTMP
HRLI A,(<POINT 7,>)
CALL MOVST0
HRROI A,HSTTMP ;Remove relative domains
CALL $RMREL
MOVEI A,HSTTMP ;B := host in absolute form
HRLI A,(<POINT 7,>)
MOVX C,.CHCNV
DO.
ILDB B,A ;Get next byte
JUMPE B,ENDLP. ;Punt if null
CAIN B,"." ;Period that needs quoting?
IDPB C,HSTPTR ;Yes, quote it
IDPB B,HSTPTR ;Store the byte
LOOP. ;Loop for more
ENDDO.
MOVE A,HSTPTR ;Return updated pointer
IDPB B,HSTPTR ;Terminate with null
RET
ENDSV.
;;; Output this recipient to designator in A, also to terminal if appropriate
OUTRCP: STKVAR <OTRJFN,OTRHPT,OTRHCT,<HSTTMP,^D13>,UPPLIM,BUFPTR>
MOVEM A,OTRJFN ;Save JFN
MOVE C,[POINT 8,STRBF1]
DMOVE T,RCPBPT(O)
MOVEM TT,OTRHCT ;Save count before relaying
DO.
ILDB D,T
IDPB D,C ;Copy recipient to STRBF1
SOJG TT,TOP.
ENDDO.
IFXN. F,FM%RLY ;Are we relaying?
MOVEM C,BUFPTR ;Save the pointer to add transmogification
SETZM STRBF2 ;Clear the buffer
MOVEI A,HSTTMP
HRLI A,(<POINT 7,0>) ;Point to the temporary host buffer
MOVEM A,OTRHPT ;Save the pointer for later
HRRZ B,HSTHST(N) ;Get the destination host
CALL MOVST0 ;Make a copy of it
MOVE A,OTRHPT
CALL RMDOM1 ;Rip out the domain
MOVE A,[POINT 7,STRBF2] ;A/ is output buffer
MOVE B,OTRHPT ;B/ host string to add
MOVEI C,"%" ;C/ prepend char
CALL HSTAPP ;Append this host to the path
HRRZ A,HSTHST(N) ;From site entry
CALL SRCPTH ;Build a destination path
MOVE D,SNRLYS ;Get number of relays
SUBI D,2 ;Don't include our neighbor in the list
MOVEM D,UPPLIM ;Save the upper limit
IFGE. D ;Less than 0?
SETZ D, ;No, start at the bottom
DO.
MOVE B,SRLYTB(D) ;Get the domain block pointer
PUSH P,B ;Save the pointer
MOVE B,DM%TRN(B) ;Point to the relay character
HRLI B,(<POINT 7,>)
ILDB C,B ;Get the relay character
POP P,B ;Get domain block back again
MOVE B,DM%RLY(B) ;Get the relay host's name
HRLI B,(<POINT 7,>)
MOVE A,OTRHPT
CALL MOVST0 ;Make a copy of the host name
MOVE A,OTRHPT
CALL RMDOM1 ;Rip out the domain
MOVE A,[POINT 7,STRBF2] ;A/ is output buffer
MOVE B,OTRHPT ;B/ host string to add, C/ prepend char
CALL HSTAPP ;Append this host to the path
ADDI D,1 ;Increment index
CAMG D,UPPLIM ;Less than the upper limit?
LOOP. ;Yes, loop around
ENDDO.
ENDIF.
;Now to build the whole thing together
MOVE A,BUFPTR ;Where to add the host path
MOVE B,[POINT 7,STRBF2] ;Where to get the host path
DO.
ILDB D,B ;Get a character
IFN. D ;Is it null (end of string)?
IDPB D,A ;No, put the char in the output buffer
AOS OTRHCT ;Inc. the character count
LOOP.
ENDIF.
ENDDO.
ENDIF.
CITYPE < >
MOVX A,.PRIOU
MOVE B,[POINT 8,STRBF1]
MOVN C,OTRHCT ;Updated count
SKIPE PRINTP
SOUT%
TYPE <: >
MOVE A,OTRJFN ;Restore JFN
MOVE B,[POINT 8,STRBF1]
MOVN C,OTRHCT ;Updated count
SOUT%
ERJMP .+1
RET
ENDSV.
;;; Output only message headers to JFN in A
;;; Returns: +1, transmission error
;;; +2, successful
OUTMSH: STKVAR <OUTMSD>
MOVEM A,OUTMSD ;Save designator
MOVEI A,^D1000 ;Transmit 1000 bytes at a time
MOVEM A,SEGSIZ ;Set segment size
SKIPN A,MSGTMT(M) ;Overall delivery timeout in effect?
IFSKP.
TIME% ;Yes, compute time limit for this copy
ADD A,TMCINT
CAMLE A,MSGTMT(M) ;Beyond total delivery timeout?
MOVE A,MSGTMT(M) ;Yes, use that
ENDIF.
MOVEM A,MSGTMC(M) ;Record copy timeout
MOVE A,OUTMSD ;Restore designator
MOVE B,MSGNHD(M) ;Headers we generated
HLRZ C,B ;Length
HRLI B,(<POINT 7,0>) ;Build byte pointer to message
MOVNI C,(C) ;And byte count
ADDI C,2 ;Skip over the CRLF at the start
IBP B
IBP B
CALL OUTMST ;Check copy timer
JRST OUTMSF
CALL $SOUT ;If no timeout, output the headers
JRST OUTMSF
OUTMDN: AOS (P) ;Set success (+2)
OUTMSF: TMOCLR ;Disallow timer interrupts now
RET
ENDSV.
;;; Output whole text of message and headers to JFN in A
;;; Returns: +1, transmission error
;;; +2, successful
OUTMSG: CALL OUTMSH ;Output headers
RET ;+1 Transmission error
SKIPE D,MSGTCN(M) ;+3 Success. Is message body empty?
IFSKP.
HRROI B,CRLF0 ;Yes, must output at least a CRLF
SETZ C,
CALL $SOUT
JRST OUTMSF
ELSE.
MOVE B,MSGTXT(M) ;Message non-empty, get pointer to message text
DO. ;No, here with message pointer in B, count in D
TMOCLR ;Disallow timer interrupts now
CAIG D,^D1000 ;Do 1000 characters at a time
SKIPA C,D
MOVEI C,^D1000
SUBI D,(C) ;Account for this many characters output
MOVNS C ;Negative byte count for SOUT%
CALL OUTMST ;Check copy timer
JRST OUTMSF ;Timed out
CALL $SOUT ;Output the string
JRST OUTMSF
JUMPG D,TOP. ;Continue output if more bytes to go
ENDDO.
ENDIF.
JRST OUTMDN ;Message output done
;;; Output whole text of message and headers to JFN in A with period checking
;;; Returns: +1, transmission error
;;; +2, successful
MSGOUT: STKVAR <BUFPTR>
CALL OUTMSH ;Output headers
RET ;+1 Transmission error
SKIPN D,MSGTCN(M) ;Get text count or flag text empty
IFSKP. ;Message non-empty with count in D
MOVE B,MSGTXT(M) ;Get pointer to message text
ILDB B,B ;Get first byte of message
CAIE B,"." ;Is it a period?
IFSKP.
CALL $BOUT ;Yes, double it in transmission
JRST OUTMSF
ENDIF.
MOVE B,MSGTXT(M) ;Get pointer to message body again
DO. ;Do 1000-bytes at a time with period checking
TMOCLR ;Disallow timer interrupts
MOVEM B,BUFPTR ;Save pointer to start of buffer
SETZB C,TT ;Character count zero, no doubled dot
DO. ;Search for "<CRLF>." sequence within buffer
CAILE D,2(C) ;Possible at all for "<CRLF>." sequence?
IFSKP. ;No, too near end of message
MOVE C,D ;Set to output rest of message
EXIT. ;And be done with this
ENDIF.
CAMLE C,SEGSIZ ;Buffer filled?
EXIT. ;Yes, output it
ILDB T,B ;Get byte from buffer
ADDI C,1 ;Count this character
CAIE T,.CHCRT ;Is it a CR?
LOOP. ;No, continue scan
ILDB T,B ;Saw CR, get possible LF
ADDI C,1 ;Count this character
CAIE T,.CHLFD ;Have we gotten a <CRLF>?
LOOP. ;No, continue scan
MOVE T,B ;Saw <CRLF>, get pointer to peek at next byte
ILDB T,T ;Peek at next byte
CAIE T,"." ;Have we gotten a line starting with period?
LOOP. ;No, continue scan
SETO TT, ;Yes, end buffer here, flag must double dot
IBP B ;Advance pointer beyond the dot
ADDI C,1 ;And count it
ENDDO. ;End scan through message for <CRLF>.
MOVE B,BUFPTR ;Get back pointer to start of buffer
SUBI D,(C) ;Account for this many characters output
MOVNS C ;Negative byte count for SOUT%
CALL OUTMST ;Check copy timer
JRST OUTMSF ;Timed out
CALL $SOUT ;Output the string
JRST OUTMSF
IFN. TT ;Do we have to double dot?
MOVEM B,BUFPTR ;Yes, save pointer to buffer
MOVEI B,"." ;Output the extra period
CALL $BOUT
JRST OUTMSF
MOVE B,BUFPTR ;Retrieve pointer
ENDIF.
JUMPG D,TOP. ;Continue output if more bytes to go
ENDDO.
SETO T, ;Back up pointer to last two bytes in buffer
ADJBP T,B
LDB D,T ;Get next to last byte
CAIE D,.CHCRT ;Was it a CR?
TDZA D,D ;No, can't be a CRLF sequence
ILDB D,T ;Yes, possible CRLF, get last byte
ENDIF.
CAIN D,.CHLFD ;Here D has either: the last byte output from
IFSKP. ; the message, or zero. D can be zero if the
HRROI B,CRLF0 ; message body is empty or if the next to the
SETZ C, ; last byte wasn't a CR. We can suppress
CALL $SOUT ; outputting the CRLF before the EOM only if
JRST OUTMSF ; D has a "last byte" of line feed
ENDIF.
HRROI B,[ASCIZ/.
/] ;Send End-Of-Message signal
SETZ C,
CALL $SOUTR
JRST OUTMSF
JRST OUTMDN
ENDSV.
;;; Routine to check timer for this msg copy
; Entry: MSGTMC(M) = time limit for transmitting this copy
; Call: CALL OUTMST
; Return: +1, timeout expired
; +2, ready to send next block of text
OUTMST: SKIPN MSGTMC(M) ;Copy timeout in effect?
IFSKP.
SAVEAC <A,B> ;Save ACs
TIME% ;Time limit up?
CAML A,MSGTMC(M)
CALL TIMOUT ;Timer expired
ENDIF.
RETSKP
SUBTTL Process local mail
SNDLCL: SKIPN MSGLCL(M) ;Any local mail?
RETSKP ;No
JSR SAVACS ;Yes, save all ACs
MOVEI X,MSGLCL(M) ;Pointer to local mail
SKIPE MSGDOP(M) ;If sending, do this another way
JRST SNDLCT
CITYPE < Processing local mail>
CALL GENHDL ;Build local headers
DO.
HRRZ O,(X) ;Get next recipient
JUMPE O,RSKP ;All done
MOVE B,RCPFLG(O) ;Get address flags
IFXE. B,FR%FAI!FR%TMP ;Forwarding errors on this address?
CALL SNDLCF ;No, try to send to file
IFSKP.
TYPE <OK> ;Success, log it
ELSE.
CALL CHKSFT ;Failed, was it a soft error?
IFSKP.
SKIPE NTDEQF ;Soft error, has message expired?
ANSKP.
MOVX B,FR%TMP ;No, just record soft failure
IORM B,RCPFLG(O)
CIETYP < %1E> ;JSYS error message
ELSE.
MOVE B,A ;Dequeueing, get a copy of the JSYS error text
HRROI A,STRBF1
HRLI B,.FHSLF
SETZ C,
ERSTR%
ERJMP .+1
ERJMP .+1
MOVEI A,STRBF1
MOVX B,FR%ERM!FR%TMP ;Assume sender notify and requeue
SKIPG NTDEQF
MOVX B,FR%ERM!FR%FAI ;No, dequeueing
CALL RCPLCX ;Save the error string
ENDIF.
ENDIF.
ENDIF.
MOVEI X,(O)
LOOP.
ENDDO.
;;;Skip if error code in A is soft
CHKSFT: CAIE A,OPNX6 ;Append access required means no WOPR or file
CAIN A,OPNX23 ;Quota exceeded (all cases -- see OVRQTA)
RETSKP
CAIE A,GJFX16 ;If POBOX: went away consider it temporary too
CAIN A,OPNX9 ;Let invalid simultaneous access through too
RETSKP ; OVRQTA and this is soft
;;;Maybe some others need adding here?
RET
; Here when address forwards to bad host, it is HSTBUF
RCPLXH: MOVE A,[POINT 7,STRBF1] ;a := buffer to construct msg
MOVEI B,[ASCIZ/Can't forward - unknown host "/]
CALL MOVSTR
MOVEI B,HSTBUF
CALL MOVSTR
MOVEI B,.CHDQT
IDPB B,A
SETZ B,
IDPB B,A
MOVEI A,STRBF1 ;Now give him the bad news
MOVX B,FR%ERM!FR%FAI ;Hard failure
;;; JRST RCPLCX
; Set error message for a recipient
; a = address of error string
; b = error bits for user block
RCPLCX: CALL RSTRCP ;Clear error msgs for this recipient
IORM B,RCPFLG(O)
CALL CPYSTR
MOVEM B,RCPERR(O)
UTYPE (B) ;Print the reason
RET
; Here to do SNDLCL processing for terminal messages
; returns +2/always
; messages to be sent as mail requeued with temporary error flag
; failed messages that can't be remailed flagged as permanent errors
SNDLCT: MOVE A,MSGDOP(M) ;Point to delivery-options
HLRO A,DOPTAB(A) ;Get delivery option string
CIETYP < Processing %1S terminal message>
;; Build message text to send
HRROI A,STRBF1 ;We build the message into STRBUF
SKIPN D,MSGSDR(M) ;d := adr of sender host entry block
FATAL <No sender block set up>
HRRZ C,HSTRCP(D) ;Get pointer to recipient entry block
MOVE B,RCPBPT(C) ;Point to sender user name
MOVN C,RCPCNT(C) ;And sender count
SOUT% ;Add it in
FMSG <@> ;Add atsign
HRRO B,HSTHST(D) ;Now get name for host
CALL OUTAHS ;Add host name
FMSG <, > ;Comma
SETO B, ;Current time
MOVX C,OT%NSC!OT%12H!OT%SCL ;C/Format flags: no seconds, 12 hour time
ODTIM% ;Write it
HRROI A,STRBUF ;Into normal place to make send
HRROI B,STRBF1 ;From header we just made
MOVEI C,STRBSZ*5-1 ;With number of chars allowed in buffer
SETZ D, ;To a null
SOUT% ;String-to-string copy
MOVEI B,.CHCRT ;Now another CR
DPB B,A ;Write over null with it
MOVEI B,.CHLFD ;And a linefeed
IDPB B,A ;To finish the header line
CAML C,MSGHCN(M) ;See how much space we have
IFSKP.
HRROI TT,[ASCIZ/Message text much too long/]
CIETYP < All sends failed: %7S>
DO.
HRRZ O,(X) ;Get next recipient
JUMPE O,ENDLP. ;If zero, done flagging them
CALL SERMRK ;Set error flags and message
MOVEI X,(O) ;Move on to next recipient
LOOP.
ENDDO.
ELSE.
MOVE B,MSGHDR(M) ;Point to message header start
MOVN C,MSGHCN(M) ;And get count of letters
SOUT% ;Copy message text across to finish message
;; Message built. Now make a list of recipients.
SETZB T,TT ;No first block, no latest block
DO.
HRRZ O,(X) ;Get next recipient
JUMPE O,ENDLP.
MOVE A,[POINT 7,STRBF1] ;Get pointer to random string buffer
DMOVE B,RCPBPT(O) ;Point to recipient name, byte count
DO.
ILDB D,B ;Get a byte
IDPB D,A ;And drop it in
SOJG C,TOP. ;Until there are no more bytes left
ENDDO.
IDPB C,A ;Drop in a null to terminate
;; Have name for recipient. Try looking up as a local user
MOVX A,RC%EMO ;Forcing exact match
HRROI B,STRBF1 ;With string we made
RCUSR% ;Read user name
IFNJE. ;If we succeeded
ANDXE. A,RC%NOM ;And got a match
PUSH P,C ;Save user number
CALL GSRCPT ;Get recipient block in TT
MOVSI A,RC.USR ;This is a user number
MOVEM A,(TT) ;Save as block header
POP P,1(TT) ;Save user number as data
ELSE.
HRROI A,STRBF1 ;That failed, point to buffer again
MOVEI C,^D8 ;Terminal numbers are octal
NIN% ;Try to read one in
IFNJE.
LDB C,A ;Read terminator byte
ANDE. C ;Must be null
PUSH P,B ;Is, save terminal number
CALL GSRCPT ;Get recipient block for it
MOVSI A,RC.TTY ;This is a terminal number
MOVEM A,(TT) ;Save as block header
POP P,1(TT) ;Save terminal number as data
ELSE.
MOVX A,FR%TMP ;Couldn't translate, want to send as mail
IORM A,RCPFLG(O) ;So requeue with a "temporary error"
ENDIF.
ENDIF.
MOVEI X,(O) ;Move on to next recipient
LOOP.
ENDDO.
ANDN. T ;If nobody left, give up in disgust
;; Here to attempt to send to rcpt list pointed to by T
DO.
HRROI A,STRBUF ;From string buffer where we built message
MOVE B,T ;Starting at the first send
MOVEI C,SDBLOK ;With send state block
CALL $SEND ;Send it off
NOP ;We can tell if it succeeded by looking at B
;; Message has been sent. Loop through rcpts until we find one
;; that failed, logging and freeing blocks as we go.
EXCH B,T ;Get starting recipient block in a useful place
MOVE TT,A ;Save error pointer if we have any
DO.
HRROI A,STRBF1 ;Into alternate buffer
CALL $WTRCP ;Write recipient name for strings
CAMN B,T ;Are we where we left off yet?
IFSKP.
HRROI A,STRBF1 ;No, rcpt succeeded, get recipient name string
CIETYP < %1S: Sent> ;Say we delivered it
MOVE A,MSGDOP(M) ;Get delivery options
CAIE A,D%SAML ;Send and mail?
IFSKP.
MOVX A,FR%TMP ;Yes, we need to send it as mail too
MOVE O,2(B) ;Point back to recipient block
IORM A,RCPFLG(O) ;Requeue with a "temporary error"
ENDIF.
LOAD O,RC%NXT,(B) ;Point to next recipient
CALL FREBLK ;Free this one
MOVE B,O ;Get next block pointer back
JUMPN B,TOP. ;Got someone, go on
SETZ T, ;Break out of outer loop
ELSE.
HRROI A,STRBF1 ;Point to recipient name
CIETYP < %1S: %7S>
MOVE O,2(T) ;Point back to recipient block
CALL SERMRK ;Set error flags for that recipient
MOVE B,T ;Get pointer to this block
LOAD T,RC%NXT,(T) ;And move on to the next
CALL FREBLK ;Free this one
ENDIF.
ENDDO.
JUMPN T,TOP. ;If we have more to do, go do it
ENDDO.
ENDIF.
RETSKP
; Here with a bad recipient, error string in TT.
SERMRK: MOVE A,MSGDOP(M) ;Get message delivery options
CAIE A,D%SOML ;If SOML, just set temporary failure
CAIN A,D%SAML ;Ditto for SAML
IFSKP.
HRROI A,STRBF1 ;Into random string buffer
MOVE B,TT ;From error string
SETZ C, ;No limit (short string, don't worry about it)
SOUT% ;String-to-string copy
HRROI A,STRBF1 ;Now point to start of string again
CALL CPYSTR ;Copy into safer string space
MOVEM B,RCPERR(O) ;Save error message with recipient
MOVX A,FR%ERM!FR%FAI ;Hard failure
ELSE.
MOVX A,FR%TMP ;Get flag for temporary error
ENDIF.
IORM A,RCPFLG(O) ;Set error flags in recipient block
RET
; Here to make a recipient block
GSRCPT: MOVEI A,3 ;Need: recipient type and data, copy of O
CALL ALCBLK ;Allocate block
FATAL <Memory exhausted>
MOVEM O,2(B) ;Save recipient pointer for flagging
SKIPN T ;If we don't have a first block yet
MOVEM B,T ;This is it
SKIPE TT ;If we had a previous block
STOR B,RC%NXT,(TT) ;Link through for $SEND
MOVEM B,TT ;In any case save this as the previous block
RET
; Mail failed. Check to see if the addressee is the mail agent.
; If so set the FR%MLA bit in RCPFLG(O).
; Entry: n = adr of host block
; o = adr of recipient block
; mlagnt = mail agent name string
; Call: CALL MMLGTL (check addressee assuming local host)
; CALL MMLGT (check addressee on network host)
; Return: +1, always
MMLGT: MOVE A,HSTHST(N) ;a := host site
CAIE A,LCLNAM ;Local?
RET ;No, can't be mail agent
MMLGTL: MOVE A,[POINT 7,MLAGNT] ;a := ptr to mail agent name
DMOVE B,RCPBPT(O) ;b,c := ptr/ctr to recipient name
CALL STRCAL ;Compare the strings
RET ;Not same
MOVX A,FR%MLA ;Same, flag mail agent failure
IORM A,RCPFLG(O)
RET
; Mail failed. Check to see if the addressee is the sender.
; If so set the FR%SDR bit in RCPFLG(O).
; Entry: n = adr of host block
; o = adr of recipient block
; msgsdr = message sender
; Call: CALL MSNDRL (check addressee on local host)
; CALL MSNDR (check addressee on network host)
; Return: +1, always
MSNDR: SKIPA C,HSTHST(N) ;c := addressee host
MSNDRL: MOVEI C,LCLNAM ;c := addressee host = local host
MOVE A,MSGSDR(M) ;a := adr of sender host block
MOVE B,HSTHST(A) ;b := sender host
CAME B,C ;Same host?
RET ;No, addressee neq sender
HRRZ B,HSTRCP(A) ;a/b := ptr/len of sender name
DMOVE A,RCPBPT(B)
DMOVE C,RCPBPT(O) ;c/d := ptr/len of recipient name
CALL STRCLL ;Compare the strings
RET ;Not same
MOVX A,FR%SDR ;Same, flag sender failure
IORM A,RCPFLG(O)
RET
; Routine to check forwarding address.
; Entry: strbuf = new addressee name
; hstbuf = new host
; Call: CALL CKFWDL
; Return: +1, host not recognized
; +2, new addressee = old one
; +3, forwarding OK, b = host site address
CKFWDL: MOVE B,[POINT 7,HSTBUF] ;b := ptr to host name
CALL HSTNAM ;Look it up
RET ;No go, return +1
CAIE B,LCLNAM ;Still to local host?
JRST R2SKP ;No, return +3
AOS 0(P) ;Return at least +2 from here
SAVEAC <B>
MOVE A,[POINT 7,STRBUF] ;a := ptr to new user name
DMOVE B,RCPBPT(O) ;b/c := ptr/len of old name
CALL STRCAL ;Compare them (upper case)
RETSKP ;No match, return +3
RET
;;; Add a forwarding address
;;; O/ ptr to recipient block
;;; B/ host index
ADDRCP: MOVEI N,MSGRCP(M)
ADDRC7: HRRZ T,HSTFLG(N) ;n := adr of next host block
JUMPE T,ADDR11 ;This host not on list
MOVE TT,HSTHST(T)
CAME TT,B ;Same host
JRST [ MOVEI N,(T)
JRST ADDRC7]
MOVEI N,(T)
ADDRC8: MOVEI T,HSTRCP(N)
ADDRC9: HRRZ TT,RCPFLG(T) ;Reached end?
JUMPE TT,ADDR10
MOVEI T,(TT)
JRST ADDRC9
ADDR10: HRRM O,(T) ;Link onto end
HRRZ T,(O) ;Get old end
HRRM T,(X) ;Link to previous
HLLZS (O) ;This is the new end of its list
MOVEI O,(T)
RET
ADDR11: PUSH P,B ;Save host
MOVEI A,HSTLEN ;Make a new host block
CALL ALCBLK
FATAL <Memory exhausted>
HRRM B,(N)
MOVEI N,(B)
POP P,HSTHST(N)
SETZM HSTFLG(N)
SETZM HSTRCP(N)
JRST ADDRC8
; Try to send local mail to addressee
; Returns: +1: Failure, JSYS error in A
; +2: Success, message delivered
SNDLCF: STKVAR <LCFJFN,<FILSIZ,2>,SDRPTR,FILPTR>
SKIPE WOPRP ;Must be WOPR to run here (checked earlier)
IFSKP.
MOVEI A,OPNX6 ;Pick a convincing error code
RET ;And return
ENDIF.
TXZ F,FM%FLO ;Assume addressee is not a file
MOVE A,RCPBPT(O) ;a := ptr to recipient name
ILDB B,A ;b := 1st char
CAIE B,"*" ;File address designator?
IFSKP.
TXO F,FM%FLO ;Yes
CALL SNLFAD ;Prepare file name string
IFNSK.
MOVEI A,GJFX33 ;Failed, pick a convincing error code
RET ;And return
ENDIF.
ELSE.
MOVE A,[POINT 7,STRBUF] ;Start filename string
MOVEI B,[ASCIZ/POBOX:</]
CALL MOVSTR
MOVEM A,FILPTR ;Save pointer for typing out
DMOVE B,RCPBPT(O)
ILDB D,B ;Get first byte of user string
CAIE D,"&" ;Was it the special local user hack?
SKIPA B,RCPBPT(O) ;No, use existing pointer/counter
SUBI C,1 ;Otherwise skip over and decrement count
DO.
ILDB D,B
IDPB D,A
SOJG C,TOP.
ENDDO.
MOVE B,A
IDPB C,B ;Terminate it for now
EXCH A,FILPTR
CIETYPE < %1W: >
MOVE B,[POINT 7,[ASCIZ/SYSTEM/]] ;Check if SYSTEM mail
CALL STRCMP
SKIPA
TXO F,FM%FLO ;SYSTEM mail, treat as output to file
MOVE A,FILPTR
MOVEI B,[ASCIZ/>MAIL.TXT.1/]
CALL MOVST0
ENDIF.
;;; The need for two GTJFN% calls is to work around a long-standing monitor
;;;bug in DIRECT -- GT%FOU!GJ%OLD will cause an empty mail file to go away.
;;;This bug is fixed at Stanford, but not in DEC TOPS-20 as of 5.1.
MOVX A,GJ%OLD!GJ%DEL!GJ%SHT ;Verify there is a mail file there
HRROI B,STRBUF
GTJFN%
ERJMP R ;Return JSYS error
IFXN. F,FM%FLO ;OK, output to file?
MOVEM A,LCFJFN ;Special-case NUL: device
;;;Actually, need some general tests for non-disk devices. For now, only disk
;;;and NUL: can possibly work.
DVCHR% ;Get characteristics
IFNJE.
LOAD B,DV%TYP,B ;Get device type
CAIE B,.DVNUL ;NUL:?
ANSKP.
MOVE A,LCFJFN ;Yes, all done here
RLJFN%
JWARN
RETSKP
ENDIF.
MOVE A,LCFJFN
CALL SNLFCK ;Yes, check for append access
ANNSK.
RLJFN% ;No go, release the JFN
JWARN
MOVEI A,OPNX6 ;Convincing error code
RET ;And fail return
ENDIF.
MOVE B,[1,,.FBDRN]
MOVEI C,C
GTFDB%
ERJMP .+1
RLJFN% ;Now get rid of this JFN
JWARN
MOVX A,GJ%FOU!GJ%DEL!GJ%SHT ;Get the JFN again (note: no GJ%OLD!!)
HLR A,C ;Default version number from old
HRROI B,STRBUF
GTJFN% ;Try to get guys mail file
ERJMP R ;This shouldn't have happened, oh well
MOVEM A,LCFJFN ;Save JFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%RD!OF%WR> ;Open for read/write
OPENF%
IFJER.
EXCH A,LCFJFN ;JSYS error, save error code
RLJFN% ;Flush the JFN
JWARN
MOVE A,LCFJFN ;Now return error to caller
RET
ENDIF.
SKIPN DAEMNP ;Allow enabled wheel to circumvent quota check
IFSKP.
MOVX A,.FHSLF ;Get our capabilities
RPCAP%
TXZ C,SC%WHL!SC%OPR ;Disable them
EPCAP%
ENDIF.
MOVE A,LCFJFN ;Get JFN
MOVE B,[2,,.FBBYV] ;Get two words of file size
MOVEI C,FILSIZ ;Into FILSIZ
GTFDB%
LDB C,[POINT 6,FILSIZ,11] ;Get file byte size
CAIN C,7 ;Already the right byte size?
IFSKP.
MOVEI B,^D36 ;Ugh, compute total bytes per word
IDIVI B,(C)
EXCH B,1+FILSIZ
IDIV B,1+FILSIZ ;Compute number of words
IMULI B,5 ;Compute # of characters
ELSE.
MOVE B,1+FILSIZ ;Use exact byte count if 7 bit bytes
ENDIF.
MOVEM B,FILSIZ ;Save prior file size
SFPTR% ;Set this as the place to write to
JFATAL
SETO B, ;Now
MOVX C,OT%TMZ
ODTIM%
IFNJE.
MOVEI B,","
BOUT%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
SETZM STRBUF ;Assume nothing needed
DMOVE A,[POINT 7,ORGAUT ;See if it was written by system server
POINT 7,DAEDIR]
CALL STRCMP ;Strings match?
IFNSK.
HRROI A,STRBUF
HRROI B,[ASCIZ/Mail-From: /]
SETZ C,
SOUT%
HRROI B,ORGAUT
SOUT% ;Give him the author
HRROI B,[ASCIZ/ created at /]
SOUT%
HRRZ B,MSGJFN(M) ;Date of queue file
MOVEI C,JS%LWR ;Last write
JFNS%
HRROI B,CRLF0
SETZ C,
SOUT% ;And crlf
ELSE.
HRROI A,STRBUF
ENDIF.
SKIPN MSGRPT(M) ;Return path specified?
IFSKP.
HRROI B,[ASCIZ/Return-Path: </] ;Yes, output it
SETZ C,
SOUT%
HRRO B,MSGRPT(M) ;Now output the path
SOUT%
MOVEI B,">"
BOUT%
HRROI B,CRLF0 ;Terminating CRLF
SOUT%
ENDIF.
SKIPN STRBUF
IFSKP.
LDB B,[POINT 6,A,5] ;High order 2 octal digits
ADDI B,3 ;High order digit is now 4,3,2,1,or 0
LSH B,-3 ;Get 4 - 0
TXZ A,.LHALF ;Clear left half of ptr
SUBI A,STRBUF-1 ;Number of words
IMULI A,5 ;Number of chars
SUB A,B ;Adjust by number not used in last word
ELSE.
SETZ A, ;Nothing to be done
ENDIF.
;;;Note that B is off by 2, since it includes a CRLF in front of the message.
;;; In most cases, we compensate by subtracting 2. If the message is null,
;;; however, we will generate a free CRLF so we don't compensate
HLRZ B,MSGNHD(M) ;Length of headers
ADD B,A ;Add the MAIL-FROM/RETURN-PATH headers
SKIPE C,MSGTCN(M) ;Is there a message body?
SUBI B,2 ;Yes, adjust count
MOVE A,LCFJFN ;Get back JFN
ADD B,MSGTCN(M) ;Plus text
MOVEI C,^D10 ;Decimal
NOUT%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
HRROI B,[ASCIZ/;000000000000
/]
SETZ C,
SOUT%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
HRROI B,STRBUF ;Output the Mail-From: line
SOUT%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
CALL OUTMSG ;Now output message for real
ANSKP.
MOVX A,.FHSLF ;Get our capabilities
RPCAP%
IOR C,B ;Re-enable them
EPCAP%
ELSE.
;;; Here when destination directory appears to be over quota. Back out of
;;;sending the message.
MOVX A,.FHSLF ;Get our capabilities
RPCAP%
IOR C,B ;Re-enable them
EPCAP%
MOVE A,LCFJFN
RFBSZ% ;Get current byte size
ERJMP .+1
MOVEI C,^D36
IDIVI C,(B) ;Compute bytes per word
MOVE D,C ;Save this for later
RFPTR% ;Get current EOF pointer
ERJMP .+1
IDIVI B,(D) ;Compute words
LSH B,-11 ;Make it a page number
MOVE C,FILSIZ ;Get original EOF pointer
IDIVI C,(D) ;Compute word #
LSH C,-11 ;Get page number
SUB B,C ;Compute # of pages added
IFN. B
EXCH B,C ;Get args in proper regs
TXO C,PM%CNT
SETO A, ;Delete pages
HRL B,LCFJFN ;JFN
ADDI B,1 ;Starting page
PMAP% ;Zap the extra file pages
MOVE A,LCFJFN ;JFN again
ENDIF.
HRLI A,.FBBYV ;Make sure byte size is correct
MOVX B,FB%BSZ ;Set byte size
MOVX C,<FLD 7,FB%BSZ> ;Set it to 7-bit bytes
CHFDB% ;Do it
IFNJE.
HRLI A,.FBSIZ ;Now set the size
SETO B, ;Set entire word
MOVE C,FILSIZ ;And back to original count
CHFDB% ;Do it
ERJMP .+1
ENDIF.
MOVE A,LCFJFN ;Get JFN again
HRROI B,[ASCIZ/somebody pending because of disk quota/] ;39 chrs max!
CALL .SFUST ;Set as writer
MOVE A,LCFJFN ;Get JFN one last time
CLOSF% ;Close the file
JWARN
MOVX A,OPNX23 ;Disk quota exceeded
RET ;JSYS error return
ENDIF.
;;;Make sure the message just delivered has made it to the disk, otherwise
;;;if the system crashes before DDMP runs it will be lost.
MOVE A,LCFJFN ;Get back JFN
RFPTR% ;Get pointer to last byte we wrote
JFATAL <Can't get local mail file size>
MOVEM B,FILSIZ
IDIVI B,5*^D512 ;Convert to number of pages
SKIPE C ;Was there a remainder?
ADDI B,1 ;Yes, a partially written page exists
HRL A,LCFJFN ;JFN in LH
HRRI A,1 ;Start with page 1
UFPGS% ;Drop the pages and wait until it happens
JWARN <Can't update local mail file>
MOVE A,LCFJFN
HRLI A,.FBBYV ;Make sure byte size is correct
MOVX B,FB%BSZ ;Set byte size
MOVX C,<FLD 7,FB%BSZ> ;Set it to 7-bit bytes
CHFDB% ;Do it
IFNJE.
HRLI A,.FBSIZ ;Now set the size
SETO B, ;Set entire word
MOVE C,FILSIZ ;Make damn sure FDB is updated
CHFDB% ;Do it
ERJMP .+1
ENDIF.
MOVE A,LCFJFN ;Get back JFN
TXO A,CO%NRJ ;Close file w/o releasing JFN
CLOSF%
JFATAL <Can't close local mail file>
MOVE D,MSGSDR(M) ;d := sender host block adr
HRRZ C,HSTRCP(D) ;c := sender recipient block adr
HRRZ B,RCPBPT(C) ;b := ptr to sender name
CAIN B,MLAGNT ;Our mail agent?
SKIPN B,MSGFHS(M) ;Yes, any "Net-mail-from-host" spec?
IFNSK.
HRROI A,STRBUF ;a := ptr to temp buffer for author name
MOVE B,RCPBPT(C) ;b/c := ptr/-cnt to name field
MOVN C,RCPCNT(C)
SOUT%
MOVE D,HSTHST(D) ;d := sender host site tbl entry
CAIN D,LCLNAM ;Local host?
IFSKP.
MOVEI B,"@" ;Add on host name
BOUT%
HRRO B,D ;Pointer to host name
SETZ C,
SOUT%
ENDIF.
HRROI B,STRBUF ;b := author string ptr
ENDIF.
MOVEM B,SDRPTR ;And string pointer
MOVE C,RCPCNT(O) ;Length of receiver's name
ADJBP C,RCPBPT(O) ;Pointer to receiver's name
SETZ D, ;Tie off name string
IDPB D,C
MOVE B,RCPBPT(O) ;Pointer to receiver's name
ILDB A,B ;Get first byte
CAIE A,"&" ;Was it special force local user hack?
MOVE B,RCPBPT(O) ;No, use it as is
MOVX A,RC%EMO ;Match string exactly
RCUSR% ;Get user number
IFNJE.
ANDN. C
MOVEM C,USRNUM ;Save user number
HRROI A,FRMMSG ;Create output msg in FRMMSG
HRROI B,[ASCIZ/
[You have a message from /]
SETZ C,
SOUT%
HRRO B,SDRPTR ;Get back sender name string pointer
CALL OUTAHS ;Output absolute host
HRROI B,[ASCIZ/ on /] ;Tell him where he has new mail
SOUT% ; since he may have TELNETed somewhere else
HRROI B,LCLNAM
CALL OUTAHS
HRROI B,[ASCIZ/]
/]
SOUT%
IDPB C,A ;Tie off with null
SETZ D, ;Init job number for scan
DO.
MOVEI A,(D) ;Job number
MOVE B,[-<.JIBAT-.JITNO+1>,,GTINF] ;Get values from monitor
MOVX C,.JITNO ;Get term # and logged in dir
GETJI% ;Get them
IFNJE.
SKIPE GTINF+<.JIBAT-.JITNO> ;Is this a batch job?
ANSKP.
DMOVE A,GTINF ;No, get GETJI% data in regs
ANDGE. A ;Detached?
CAME B,USRNUM ;Logged into the user number we want?
ANSKP.
IORX A,.TTDES ;Make it a device designator
MOVX B,.MORNT ;Does user want system messages?
MTOPR%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
ANDE. C ;Ignore if refusing system messages
HRROI B,FRMMSG ;Get message block
TTMSG% ;Send to this user
ERJMP ENDLP. ;Ignore failure
ELSE.
CAIN A,GTJIX3 ;"Invalid job number"?
EXIT. ;Yes, all done
ENDIF.
AOJA D,TOP. ;Do all jobs
ENDDO.
ENDIF.
MOVE A,LCFJFN ;Get back JFN
MOVE B,SDRPTR ;Restore string pointer
SKIPE DAEMNP ;Daemon running?
CALL .SFUST ;Yes, set the author
ANDX A,.RHALF ;Isolate file JFN
RLJFN% ;Release it
JWARN
RETSKP ;Return success
ENDSV.
; Here to set up for sending mail to a file specification, defaulting the
; device and directory from the msg file JFN.
; Entry: o = adr of recipient buffer
; Call: CALL SNLFAD
; Return: +1, failure (bad string)
; +2, OK, name string set up in STRBUF
SNLFAD: STKVAR <FILPTR,<RCPPTR,2>>
MOVE A,[POINT 7,STRBUF] ;a := buffer for name string
DMOVE B,RCPBPT(O) ;b,c := ptr/ctr to file name string
IBP B ;Step over "*"
SOJLE C,R ;And decrement count (if null str, quit)
MOVEM A,FILPTR ;Save buffer pointer
DMOVEM B,RCPPTR ;Save recipient pointer and counter
DO.
ILDB D,B ;Look for device delimiter
IDPB D,A ;Stick character in buffer in case
CAIE D,.CHCNV ;CTRL-V?
IFSKP.
SOJLE C,R ;Yes, next character doesn't count
ILDB D,B
IDPB D,A
ELSE.
CAIN D,":" ;Found one?
SOJA C,ENDLP. ;Yes, no need to default device
ENDIF.
SOJG C,TOP. ;Look for device delimiter until exhausted
MOVE A,FILPTR ;Device not specified, must default it
HRRZ B,MSGJFN(M) ;b := JFN for this queued file
MOVE C,[100000,,1] ;Print the device part (assumed)
JFNS%
DMOVE B,RCPPTR ;Retrieve pointer/count to start over
ENDDO.
MOVEM A,FILPTR ;Update buffer pointer
DMOVEM B,RCPPTR ;Update saved pointer/count
JUMPE C,R ;In case no more text
DO.
ILDB D,B ;Search for directory delimiter
IDPB D,A ;Stick character in buffer in case
CAIE D,.CHCNV ;CTRL-V?
IFSKP.
SOJLE C,R ;Yes, next character doesn't count
ILDB D,B
IDPB D,A
ELSE.
CAIE D,"[" ;This is a directory delimiter too
CAIN D,"<" ;Found it?
SOJA C,ENDLP. ;Yes, no need to default directory
ENDIF.
SOJG C,TOP. ;Look for directory delimiter until exhausted
MOVE A,FILPTR ;Directory not specified, must default it
HRRZ B,MSGJFN(M) ;b := JFN for this queued file
MOVE C,[010000,,1] ;Print the directory part (assumed)
JFNS%
DMOVE B,RCPPTR ;Retrieve pointer/count to start over
ENDDO.
JUMPE C,R ;In case no more text
DO.
ILDB D,B ;d := next char
IDPB D,A
SOJG C,TOP. ;Do the whole string
ENDDO.
IDPB C,A ;Terminate the string
MOVE A,[POINT 7,STRBUF] ;a := ptr to start of buffer
CIETYP < %1W: > ;Print it if needed
RETSKP ;Return +2
ENDSV.
; Routine to check for append access to a file
; Entry: a = JFN to file
; strbuf = file name string (must not clobber it)
; Call: CALL SNLFCK
; Return: +1, access not allowed
; +2, append access OK
SNLFCK: SKIPL DAEMNP ;Running as daemon?
RETSKP ;No, system will take care of access chk
PUSH P,A ;Save the JFN
DMOVE A,[POINT 7,ORGAUT ;See if it was written by system server
POINT 7,DAEDIR]
CALL STRCMP ;Strings match?
JRST SNLFC1 ;No, do CHKAC% to validate access
SNLFC0: POP P,A ;Random source, check for world append access
MOVE B,[1,,.FBPRT] ;Want protection code for file
MOVEI C,C ;Into C
GTFDB%
ERJMP R ;Can't get protection, deny
TXNE C,FP%APP ;Append access for the world?
RETSKP ;Yes, allow access
RET ;No, deny access
CKABLK==<STRBF1+20> ;CHKAC% argument
SNLFC1: HRROI A,STRBF1 ;a := ptr for file directory string
HRRZ B,MSGJFN(M) ;b := queue file JFN
MOVE C,[010000,,1] ;Set STRBF1 to "connected directory", or some
JFNS% ;suitable approximation
MOVEI A,CKABLK-1 ;Area to store CHKAC% argument block
PUSH A,[.CKAAP] ;Tbl wd 0: append access
PUSH A,[POINT 7,ORGAUT] ;Tbl wd 1: user name string
PUSH A,[POINT 7,STRBF1] ;Tbl wd 2: conn dir string
PUSH A,[0] ;Tbl wd 3: enabled privileges
PUSH A,(P) ;Tbl wd 4: JFN for file to be accessed
MOVE A,[CK%JFN+5] ;a := JFN flag,,tbl length
MOVEI B,CKABLK ;b := adr of table on stack
CHKAC% ;Check for access rights
ERJMP SNLFC0 ;JSYS failed, check for world access
MOVE B,A ;Get CHKAC% result in B
POP P,A ;a := file JFN
JUMPN B,RSKP ;Skip return if access allowed
RET ;Else fail return
; Routine to run MMailbox program to lookup forwarding address or mailing list
; Entry: a = ptr to user name
; Call: CALL MLFWRD
; Return: +1, No forwarding
; +2, forwarding found
MLFWRD: SAVEAC <A,B> ;Save calling args
STKVAR <MBXJFN,MBXPTR>
MOVEM A,MBXPTR ;Save mailbox pointer
SKIPE MBXFK ;Fork already existing?
IFSKP.
MOVX A,GJ%OLD!GJ%SHT ;No, get JFN of forwarder
HRROI B,[ASCIZ/SYS:MMAILBOX.EXE/]
GTJFN%
ERJMP R ;Not there.
MOVEM A,MBXJFN ;Save JFN
MOVX A,CR%CAP ;Create an inferior fork
CFORK%
IFJER.
MOVEI A,^D5000 ;Failed get fork, wait 5 sec
DISMS%
MOVX A,CR%CAP
CFORK%
IFJER.
MOVE A,MBXJFN ;Failed again, quit
RLJFN% ;Punt the JFN
JWARN ;Don't care
RET ;Return to caller
ENDIF.
ENDIF.
MOVEM A,MBXFK ;Save fork handle
RPCAP% ;TOPS-20 will not let you do anything
TXO B,SC%SUP ; to a superior (ie IIC it) unless you
TXO C,SC%SUP ; have the cap to map it.
EPCAP% ;So enable that capability
MOVE A,MBXJFN ;Get back JFN
HRL A,MBXFK ;a := fork handle,,JFN
GET% ;Get pgm into fork
ERJMP CLRMLF
ENDIF.
HRLZ A,MBXFK ;a := inferior fork,,page 0
DMOVE B,[.FHSLF,,<TMPBUF/1000> ;b := our fork,,shared page
PM%RD!PM%WR!PM%CNT+2]
PMAP%
ERJMP CLRMLF
MOVE A,[POINT 7,TMPBUF+200] ;a := ptr to shared page (200)
MOVE B,MBXPTR ;b := ptr to address user name
CALL MOVST0 ;Copy string and terminating null
MOVX A,.FHSLF ;Get our primary JFN's
GPJFN%
ERJMP CLRMLF
MOVE A,MBXFK ;Set MMailbox's to match
SPJFN%
ERJMP CLRMLF
MOVE A,MBXFK ;a := fork handle again
MOVX B,3 ;MMailr entry
SFRKV%
ERJMP CLRMLF
WFORK% ;Wait for it to halt
ERJMP CLRMLF
RFSTS% ;Read status
ERJMP CLRMLF
HLRZS A ;a := termination code
CAIN A,.RFHLT ;Normal HALTF%?
IFSKP.
CALL CLRMLF ;No, better clean it up
MOVEI A,[ASCIZ/Forwarding program error/]
MOVX B,FR%ERM!FR%TMP ;Temporary failure
CALLRET RCPLCX ;Set recipient error message
ENDIF.
SKIPL A,TMPBUF+177 ;Check success flag
IFSKP.
MOVE A,[POINT 7,STRBUF]
MOVEI B,[ASCIZ/Forwarding error: /]
CALL MOVSTR
HRRZ B,TMPBUF+177 ;Get from inferior
CALL FWDCPY ;Copy here
SETZ B, ;Tie off string
DPB B,A ;Not IDPB! FWDCPY uses MOVST0
MOVE A,[POINT 7,STRBUF] ;Point to error string
SKIPE TMPBUF+176 ;Auxillary value returned?
SKIPA B,[FR%ERM!FR%FAI] ;Yes, failure is hard then
MOVX B,FR%ERM!FR%TMP ;Otherwise temporary failure
CALLRET RCPLCX ;Set recipient error message
ENDIF.
IFE. A
MOVEI A,[ASCIZ/No such mailbox/]
MOVX B,FR%ERM!FR%FAI ;Failure is hard here
CALLRET RCPLCX ;Set recipient error message
ENDIF.
CAIL A,3 ;Valid local entry?
IFSKP.
HRRZ B,(O) ;Temporarily link it out of the list
HRRM B,(X)
CALL UNQRCP ;Is it unique?
IFSKP.
HRRM O,(X) ;Yes, put it back
ELSE.
CALL FREDUP
MOVEI O,(X)
ENDIF.
RET
ENDIF.
RETSKP
ENDSV.
; Routine to clear up the MMAILBOX.EXE fork
; Entry: MBXFK = frk handle
; frk pg 0 possibly mapped to TMPBUF in our space
CLRMLF: SKIPN MBXFK ;a := fork handle
RET ;If none, nothing to do
SETO A, ;Unmap shared page
DMOVE B,[.FHSLF,,<TMPBUF/1000>
PM%CNT+2]
PMAP%
ERJMP .+1
HRRI B,<FWDWIN/1000>
MOVE C,[PM%CNT+2]
PMAP%
ERJMP .+1
MOVE A,MBXFK ;a := fork handle
KFORK% ;Get rid of fork
ERJMP .+1
SETZM MBXFK ;Show fork gone
RET ;Return
;;; Forward local mail
;;; CALL FWDLCL
;;; Returns +1 always
FWDLCL: SKIPN MSGDOP(M) ;Delivering as mail?
SKIPN MSGLCL(M) ;Any local mail?
RET ;Terminal message or nothing local, stop now
JSR SAVACS ;Got something to do, save all ACs
CITYPE < Checking local mail for mailing lists>
MOVEI X,MSGLCL(M) ;Pointer to local mail
DO.
HRRZ O,(X) ;Current message pointer in O, previous in X
JUMPE O,R ;If done, just return
CALL FWDLCF ;Try to forward it
MOVEI X,(O) ;Set current as previous
LOOP. ;Try next message
ENDDO.
;;; Try to forward a single local recipient
;;; O/ Current recipient
;;; X/ Previous recipient (in case of relinking)
FWDLCF: MOVE A,[POINT 7,STRBUF] ;a := ptr for copy of the addressee name
DMOVE B,RCPBPT(O) ;b,c := ptr/ctr to name
DO.
ILDB D,B ;d := next char
IDPB D,A
SOJG C,TOP. ;Copy all chars in name
ENDDO.
IDPB C,A ;Terminate with null
MOVE A,[POINT 7,STRBUF] ;a := ptr to user name
CIETYPE < %1W: >
CALL MLFWRD ;Look up forwarding address
RET ;No forwarding, all done
;; A valid forwarding has been found, get it out of the inferior
MOVX T,FR%STR
HRRZ B,RCPBPT(O)
TDNE T,RCPFLG(O) ;Generated recipient string?
CALL FREBLK ;Yes, deallocate
HRRZ B,O ;Get pointer to old block
HRRZ O,(O) ;Get forward pointer for relinking
CALL FREBLK ;Deallocate recipient block
HRRM O,(X) ;Link out current block
MOVEI Y,TMPBUF+300 ;Where the expansion was put
DO.
SKIPE T,(Y) ;End of addresses?
IFSKP.
MOVEI O,(X) ;Get current pointer again (O had forward ptr)
RET ;Go back and do next local address
ENDIF.
PUSH P,O ;Save next address
CALL FWDRCP ;Make recipient block
CAIN B,LCLNAM ;Local host?
IFSKP.
CALL ADDRCP ;No, add another recipient
ELSE.
CALL UNQRCP ;Yes, unique local recipient?
IFNSK.
CALL FREDUP ;No
POP P,O ;Leave O and X the same
AOJA Y,TOP.
ENDIF.
HRRM O,(X) ;Yes, link to previous address
HRRZ X,O ;Make it be previous address
ENDIF.
POP P,O ;Get back next address
HRRM O,(X) ;Set as next on list
AOJA Y,TOP. ;And try for rest of recipient
ENDDO.
;Free duplicate recipient
FREDUP: CIETYP <FREDUP: Duplicate recipient deleted: >
MOVX A,.PRIOU
MOVE B,RCPBPT(O)
MOVN C,RCPCNT(O)
SKIPN PRINTP
IFSKP.
SOUT%
CALL CRLF
ENDIF.
MOVX T,FR%STR
HRRZ B,RCPBPT(O)
TDNE T,RCPFLG(O) ;Generated recipient string?
CALL FREBLK ;Yes, deallocate
HRRZ B,O
CALLRET FREBLK
;;; Skip if this recipient (O) is unique among local recipients
UNQRCP: PUSH P,X ;Preserve caller's X
CALL UNQRCX ;Call worker routine
SKIPA ;Non-skip return from worker
AOS -1(P) ;Skip return from worker
POP P,X ;Restore caller's X
RET
UNQRCX: MOVEI X,MSGLCL(M) ;Head of local recipient list
DO.
HRRZ X,(X) ;Next local rcpt
JUMPE X,RSKP ;It's unique
DMOVE A,RCPBPT(O) ;Compare them
DMOVE C,RCPBPT(X)
CALL STRCLL
LOOP. ;Different, try next
ENDDO.
RET ;Identical, string not unique
;;; Copy a string from the forwarding inferior
;;; A/ output string
;;; B/ address in inferior
FWDCPY: STKVAR <FWDSTR,FWDADR>
MOVEM A,FWDSTR ;Save parameters
MOVEM B,FWDADR
LSH B,-<^D9> ;Get inferior page number
HRL A,MBXFK
HRR A,B
MOVX C,PM%CNT!PM%RD!PM%CPY!2
CAIN B,777 ;Is inferior page page 777?
SUBI C,1 ;Yes, only map 1 page then
MOVE B,[.FHSLF,,FWDWIN/1000]
PMAP%
MOVE A,FWDSTR
LDB B,[POINT 9,FWDADR,35]
ADDI B,FWDWIN
CALLRET MOVST0
ENDSV.
;;; Make a new recipient block from forwarded address
;;; T/ host,,name
;;; Returns O/ standard recipient block
FWDRCP: PUSH P,T
MOVEI A,RCPLEN ;Get block for this recipient
CALL ALCBLK
FATAL (Memory exhausted)
MOVEI O,(B)
MOVX B,FR%STR
MOVEM B,RCPFLG(O) ;Initialize flags
MOVE A,[POINT 7,STRBUF]
HRRZ B,(P)
CALL FWDCPY ;Copy string from inferior
HRROI A,STRBUF
CIETYP < %1W>
CALL CPYSTR ;Get byte pointer and count
HRLI B,(<POINT 7,0>)
DMOVEM B,RCPBPT(O) ;Save them
POP P,T
HLRZ B,T ;Get host address
JUMPE B,FWDRC1 ;Local
MOVE A,[POINT 7,HSTBUF]
CALL FWDCPY ;Copy host name from inferior
DO.
TXNN A,76B4 ;Filled to word boundary?
EXIT.
IDPB D,A ;No, do another null
LOOP.
ENDDO.
HRROI B,HSTBUF
ETYPE <@%2W>
CALL HSTNAM
SKIPA
RET
CALL RCPLXH ;Put in error for no such host
FWDRC1: MOVEI B,LCLNAM ;And store as local
RET
SUBTTL Requeue or send failure message for message in M
REMAIL: JSR SAVACS ;Save all ACs
STKVAR <RMLJFN>
TXZ F,FQ%SXX ;Clear flags
SETZM MSGTMT(M) ;No more timeouts when requeueing
SKIPE NTDEQF ;Dequeueing file or notifying sender?
CALL SERRCP ;Yes, finalize errors
REMAI0: SETZM FAIJFN ;Reset output jfn's
SETZM NTFJFN
SETZB N,REQJFN ;Do local mail
TXZ F,FQ%OMF!FQ%MLA!FQ%SDR!FQ%RNM!FQ%XNT!FQ%XER ;Clear flags
MOVE A,FILIDX ;a := flags for current queue file type
MOVE A,%FLFLG(A)
TXNE A,FF%OML ;Old style?
TXO F,FQ%OMF ;Yes
TXNE A,FF%RNM ;Rename to add RETRANSMIT extension?
TXO F,FQ%RNM ;Yes
TXNE A,FF%XNT ;Suppress non-delivery notifications?
TXO F,FQ%XNT ;Yes
MOVX A,FG%XER ;Discard on error?
TDNE A,MSGJFN(M)
TXO F,FQ%XER ;Yes
;;; I think it's probably all right to allow local mail here, even if not WOPR
MOVEI O,MSGLCL(M)
TXZ F,FQ%ALL
CALL REMALS ;Hack this list
MOVEI N,MSGRCP(M)
DO.
HRRZ N,(N)
JUMPE N,ENDLP.
MOVX T,FH%DON ;This host got done?
TDNN T,HSTFLG(N)
TXOA F,FQ%ALL ;No, output it all
TXZ F,FQ%ALL
MOVEI O,HSTRCP(N)
CALL REMALS
LOOP.
ENDDO.
SKIPN NTFJFN ;Sender notification?
SKIPE FAIJFN ;Or failure file?
IFNSK.
CALL GENHDL ;Build local headers
SKIPN A,FAIJFN ;Failure file?
IFSKP.
MOVEI B,OUTMSG ;Routine to output headers/text
CALL REMHTX ;Do it with punctuation
TXNN F,FQ%SXX ;Processing rerouted failure msg?
TXNN F,FQ%SDR ;No, fail on sender?
IFSKP.
IFXE. F,FQ%MLA ;Also fail on mail agent?
TXO F,FQ%SXX ;Divert failure msg to mail agent
DELF% ;Delete current reply file
JFATAL
CLOSF% ;Close it
JFATAL
SKIPN A,REQJFN ;Also requeue file?
IFSKP.
CLOSF% ;Yes, close it
JFATAL
SETZM REQJFN
ENDIF.
SKIPN A,NTFJFN ;Also notification file?
IFSKP.
DELF% ;Delete it
JFATAL
CLOSF% ;And close it
JFATAL
SETZM NTFJFN
ENDIF.
JRST REMAI0
ENDIF.
TXO A,CO%NRJ ;Close fail msg file and keep JFN
CLOSF%
JFATAL
MOVEI A,0(A) ;Now rename the file to "bad mail"
CALL RENBAX
ELSE.
CLOSF% ;Close out failure file
JFATAL
SKIPN NTFJFN ;Only set flags once
SKIPE REQJFN
SKIPA
CALL MAIFLG
ENDIF.
ENDIF.
SKIPN A,NTFJFN ;Notification file pending?
IFSKP.
MOVEI B,OUTMSH ;Routine to output headers and no text
CALL REMHTX ;Do it with punctuation
CLOSF% ;Close out notification file
JFATAL
SKIPN REQJFN ;Only set flags once
CALL MAIFLG
ENDIF.
ENDIF.
SKIPN A,REQJFN ;Have a requeue file?
RET ;No, all done
MOVEI B,.CHFFD ;No, must end addressee specs
BOUT%
HRROI B,CRLF0
SETZ C,
SOUT%
DMOVE B,MSGHDR(M) ;Finish off file
MOVNI C,(C)
SOUT%
TXO A,CO%NRJ ;Close file, preserve JFN
CLOSF%
JFATAL
HRRZ A,MSGJFN(M) ;Get back JFN of original file
MOVEM A,RMLJFN
TXO A,CO%NRJ
CALL UNMQUF ;Unmap, leave JFN
RET ;Percolate error up
MOVE A,RMLJFN
HRLI A,.GFLWR ;Save file writer
HRROI B,STRBUF
GFUST%
ERJMP .+1
IFXN. F,FQ%RNM!FQ%OMF ;Rename file extension or old mail first?
HRROI A,STRBF1 ;Yes, construct new name
MOVE B,RMLJFN ;From original file's JFN
IFXN. F,FQ%OMF
MOVX C,JS%DEV!JS%DIR!JS%PAF
JFNS%
TXNN F,FQ%XNT ;Notify about errors?
SKIPA B,[[ASCIZ/[--QUEUED-MAIL--]/]]
MOVEI B,[ASCIZ/[--RETURNED-MAIL--]/]
CALL MOVSTR
ELSE.
MOVX C,JS%DEV!JS%DIR!JS%NAM!JS%PAF
JFNS%
ENDIF.
SKIPN NETF ;Were we allowed to deliver network mail?
SKIPA B,[[ASCIZ/.NETWORK;P770000/]] ;No, use alternate name
MOVEI B,[ASCIZ/.RETRANSMIT;P770000/] ;Yes, use standard name
CALL MOVST0
DO.
MOVX A,GJ%NEW!GJ%FOU!GJ%ACC!GJ%SHT ;And rename the file
HRROI B,STRBF1
GTJFN%
IFJER.
CAIE A,GJFX24 ;Work around monitor bug
JWARN <Cannot get RETRANSMIT file>
MOVEI A,^D5000 ;Wait 5 seconds
DISMS%
LOOP.
ENDIF.
MOVE B,A ;JFN of name we will rename to
ENDDO.
EXCH A,RMLJFN ;Set original file JFN, get former one
CALL RNMFIL
IFNSK.
JWARN <Unable to rename to RETRANSMIT extension>
MOVEM A,RMLJFN ;Rename failed, restore former name
MOVE A,B ;JFN we tried to use
RLJFN% ;Flush this useless JFN
ERJMP .+1 ;Don't care if it fails
ENDIF.
ENDIF.
MOVE A,REQJFN ;Requeue file we just made
MOVE B,RMLJFN ;Original file JFN
CALL RNMFIL
IFNSK.
JWARN <Cannot rename requeue file>
EXCH A,RMLJFN ;A:=existing JFN, RMLJFN:=JFN failed to rename
RLJFN% ;Flush the failing JFN
NOP
ENDIF.
MOVE A,RMLJFN ;JFN we ended up with
MOVEI B,MSGWRT(M) ;Set its write date
MOVEI C,1
SFTAD%
ERJMP .+1
HRROI B,STRBUF
CALL .SFUST ;Set its writer
MOVE B,RMLJFN
RLJFN%
JWARN
CALL MAIFLG ;Set flags unless already did
IFXN. F,FQ%RNM!FQ%OMF ;Rename file extension or old mail first?
SKIPN NETF ;Did we queue something for the network fork?
CALL WAKNET ;Yes, go wake it up
ENDIF.
RET
ENDSV.
;; Routine to output msg headers and text with punctuation to a
;; notification or error file
; Entry: a = output jfn
; b = message output routine
REMHTX: PUSH P,B ;Save output routine
HRROI B,[ASCIZ/ ------------
/]
SETZ C,
SOUT% ;Do starting punctuation
POP P,B ;Execute output routine
CALL (B)
JFATAL <Local message output lost> ;+1, error???
HRROI B,[ASCIZ/-------
/]
SETZ C,
SOUT% ;Add trailing punctuation
RET
;; Check one list of recipients
REMALS: TXZ F,FQ%HST ;Host not yet output
REMLS1: HRRZ O,(O)
JUMPE O,R ;Done with list
DO.
IFXE. F,FQ%ALL ;Output all of this host?
MOVE A,RCPFLG(O) ;a := recipient flags,,link to next
TXNN A,FR%FAI ;Permanent failure?
TXNN A,FR%TMP ; or no errors?
EXIT. ;Then don't requeue this one
ENDIF.
TXON F,FQ%HST ;Already got host?
CALL REMLHS ;No, output it
HRRZ A,REQJFN ;a := requeue file JFN
MOVE B,RCPBPT(O)
MOVN C,RCPCNT(O)
SOUT%
HRROI B,CRLF0
SETZ C,
SOUT%
SKIPG NTDEQF ;Notifying sender of status?
IFSKP.
SKIPN A,NTFJFN ;Yes, JFN already set up?
CALL REMNTF ;No, do it
CALL APPERM ;Now append error msg
ENDIF.
ENDDO.
MOVX T,FR%FAI
TXNN F,FQ%ALL ;Outputing all of this host?
TDNN T,RCPFLG(O) ;Or not permanent failure?
IFSKP.
IFN. N ;If not local mail,
CALL MMLGT ;Check for mail agent failure
CALL MSNDR ;And sender failure
ENDIF.
MOVE A,RCPFLG(O) ;a := recip flags,,link to next recip
IFXN. A,FR%MLA ;Is this a failure for mail agent?
TXON F,FQ%MLA ;Yes
WARN <Failed sending msg to Mail Agent>
ENDIF.
TXNE A,FR%SDR ;Is this a failure for the sender?
TXO F,FQ%SDR ;Yes
IFXN. F,FQ%XER ;Discard this file on error?
MOVEI A,[ASCIZ/ Message queued too long, file purged/]
SKIPL NTDEQF ;Dequeueing file?
MOVEI A,[ASCIZ/ Message file purged/] ;No, must be error
UTYPE 1,(A) ;Type appropriate msg
ELSE.
SKIPE A,FAIJFN
IFSKP.
SKIPGE NTDEQF ;Dequeue this file?
CITYPE < Message queued too long, sender notified>
CALL REMLFA ;Init failure file
ENDIF.
CALL APPERM ;Append the name and error msg
ENDIF.
ENDIF.
JRST REMLS1
;; Routine to append recipient name and error msg to a sender
;; notification or error file.
; a = output jfn
; o = adr of recipient block
APPERM: MOVE B,RCPBPT(O) ;b/c := recipient name ptr
MOVN C,RCPCNT(O)
SOUT%
MOVEI B,"@"
BOUT%
IFE. N ;Output host
HRROI B,LCLNAM
ELSE.
HRRO B,HSTHST(N)
ENDIF.
SOUT%
HRROI B,[ASCIZ/: /]
SOUT%
HRRO B,RCPERR(O) ;And the error msg
TXNN B,.RHALF ;Given?
HRROI B,[ASCIZ/No error msg given./]
SOUT%
HRROI B,CRLF0 ;Append a CRLF
SOUT%
RET
;; Output host first time
REMLHS: SKIPN A,REQJFN
CALL REMLRQ
MOVEI B,.CHFFD
BOUT%
IFE. N
HRROI B,LCLNAM
ELSE.
HRRO B,HSTHST(N)
ENDIF.
SETZ C,
SOUT%
HRROI B,CRLF0
SOUT%
RET
;; Start of requeue file
REMLRQ: HRROI A,STRBF1 ;As good a place as any I guess
HRRZ B,MSGJFN(M) ;JFN for queued file
MOVE C,[110000,,1] ;Print device and directory
JFNS%
HRROI B,[ASCIZ/-REQUEUED-MAIL/]
SETZ C,
SOUT% ;Append our filename to it
MOVEI B,"-"
IDPB B,A
MOVE B,MYJOBN ;Set up job number
MOVEI C,^D10 ;Output in decimal
NOUT%
JFATAL
MOVEI B,"-"
IDPB B,A
MOVE B,FORKX ;Tack in fork number
NOUT%
JFATAL
HRROI B,[ASCIZ/.TMP.-1/]
SETZ C,
SOUT% ;Append our filename to it
MOVX A,GJ%FOU!GJ%NEW!GJ%SHT
HRROI B,STRBF1
GTJFN%
IFJER.
CAIN A,GJFX24 ;Somebody's DELDF% screwed us? (monitor bug)
IFSKP.
MOVEI A,STRBF1 ;No, set up name for warning
JWARN <Can't get %1W in REMLRQ>
ENDIF.
MOVEI A,^D5000 ;Wait 5 seconds
DISMS%
JRST REMLRQ ;Try again
ENDIF.
MOVEM A,REQJFN ;Save the JFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%WR>
OPENF%
IFJER.
CAIN A,OPNX2 ;Somebody's DELDF% screwed us? (monitor bug)
IFSKP.
MOVE B,REQJFN ;Get JFN for message
JWARN <Can't open %2J in REMLRQ>
ENDIF.
MOVE A,REQJFN ;Flush JFN
RLJFN%
JWARN
MOVEI A,^D5000 ;Wait 5 seconds
DISMS%
JRST REMLRQ ;Try again
ENDIF.
MOVX B,.CHFFD ;Output delivery option
BOUT%
HRROI B,[ASCIZ/=DELIVERY-OPTIONS:/]
SOUT%
MOVE B,MSGDOP(M)
HLRO B,DOPTAB(B) ;Get delivery option string
SOUT%
HRROI B,CRLF0
SOUT%
SKIPN D,MSGFHS(M) ;Net host spec?
IFSKP.
MOVEI B,.CHFFD ;Output keyword part
BOUT%
HRROI B,[ASCIZ/=NET-MAIL-FROM-HOST:/]
SOUT%
HRRO B,D
SOUT%
HRROI B,CRLF0
SOUT%
ENDIF.
SKIPN MSGRPT(M) ;Return path specified?
IFSKP.
MOVEI B,.CHFFD ;Yes, copy it to output
BOUT%
HRROI B,[ASCIZ/=RETURN-PATH:/] ;Yes, output it
SETZ C,
SOUT%
HRRO B,MSGRPT(M) ;Now output the path
SOUT%
HRROI B,CRLF0 ;Terminating CRLF
SOUT%
ENDIF.
SKIPN C,MSGAFT(M) ;After specified?
IFSKP.
CAMG C,CURDTM ;Yes, before current time?
IFSKP.
HRROI B,[ASCIZ/=AFTER: /] ;No, write new after period
CALL OUDTIM ;Output after parameter
ELSE.
SETZM MSGAFT(M) ;Set no after parameter
ENDIF.
ENDIF.
IFXE. F,FQ%XNT ;Suppress non-delivery notifications?
SKIPE C,MSGNTF(M) ;No, sender notification time set?
IFSKP.
SKIPN C,MSGAFT(M) ;Must compute it, have an After time?
SKIPA C,CURDTM ;No, start with current time then
ADD C,NTFINT ;Otherwise use After time plus notify interval
ENDIF.
DO.
CAMLE C,CURDTM ;Past current time?
IFSKP.
ADD C,NTFINT ;No, bump an interval
LOOP. ;And try again
ENDIF.
ENDDO.
HRROI B,[ASCIZ/=NOTIFY: /]
CALL OUDTIM ;Use previous notification time
ENDIF.
SKIPE C,MSGDEQ(M) ;Dequeue time set?
IFSKP.
MOVE C,MSGWRT(M) ;No, get write time
CAMG C,MSGAFT(M) ;Is an after time specified that's greater?
MOVE C,MSGAFT(M) ;Yes, use after time as base
ADD C,MAXQUE ;Plus interval
ENDIF.
HRROI B,[ASCIZ/=DEQUEUE: /]
CALL OUDTIM ;Use previous dequeue time
TXNE F,FQ%XER ;Discard on error?
CALL DSCRDE ;Yes, retain that property
CALLRET SDRHDR ;Write the sender spec
;; Routine to output a time difference (t1 - t2) in days.
; Entry: a = output jfn
; b = t1 (internal date/time format)
; c = t2 (internal date/time format)
OTMDIF: SUB B,C ;Compute time difference
CAIGE B,0 ;Set neg value to 0
SETZ B,
ADDI B,400000 ;Round to nearest day
HLRZS B
MOVEI C,^D10 ;Print it in decimal
NOUT%
JFATAL
MOVE C,B ;Save the value
HRROI B,[ASCIZ/ days/]
CAIN C,1 ;Exactly one?
HRROI B,[ASCIZ/ day/]
SETZ C,
SOUT%
RET
;;; Routine to compute internal date/time after given delay
; Entry: b = delay in seconds
; curdtm = current date/time
; Call: CALL DLYTIM
; Return: +1, c = new date/time
DLYTIM: HRLZ C,B ;Normalize delay to internal std
IDIVI C,^D<24*60*60>
ADD C,CURDTM ;Add on current time
RET
;;; Routine to output a date/time control parameter
; Entry: b = ptr to parameter keyword
; c = internal time value
; Call: CALL OUDTIM
; Return: +1
OUDTIM: PUSH P,C ;Save the time
PUSH P,B ;And the text ptr
MOVEI B,.CHFFD ;Output keyword part
BOUT%
POP P,B
SETZ C,
SOUT%
POP P,B ;Now the time
MOVX C,OT%NSC!OT%SCL
ODTIM%
HRROI B,CRLF0 ;End line
SETZ C,
SOUT%
RET
;; Init failure file
REMLFA: CALL RESPQF ;Initialize the file
IFXE. F,FQ%SXX ;Divert reply to mail agent?
CALL SDRADR ;Addressee = sender
ELSE.
CALL MLAADR ;Addressee = mail agent
ENDIF.
CALL RESPQB ;Finish up the file
MOVEM A,FAIJFN
HRROI B,[ASCIZ/Message of /]
SETZ C,
SOUT%
MOVE B,MSGWRT(M) ;b := file write date/time
MOVX C,OT%SCL
ODTIM%
SKIPGE NTDEQF ;Last try?
IFSKP.
HRROI B,[ASCIZ/
Message failed for the following:
/]
SETZ C,
ELSE.
HRROI B,[ASCIZ/
Message undeliverable and dequeued after /]
SETZ C,
SOUT%
MOVE B,CURDTM ;Compute time in queue so far
MOVE C,MSGWRT(M)
CALL OTMDIF ;And output it
HRROI B,[ASCIZ/:
/] ;Finish punctuation
ENDIF.
SOUT%
RET
;; Routine to initialize a response file to notify sender that msg has
;; not been sent.
REMNTF: CALL RESPQN ;Initialize the file
CALL SDRADR ;Addressee = sender
CALL DSCRDE ;Set discard parameter
CALL RESPQB ;Finish up the file
MOVEM A,NTFJFN
HRROI B,[ASCIZ/Message of /]
SETZ C,
SOUT%
MOVE B,MSGWRT(M) ;b := file write date/time
MOVX C,OT%SCL
ODTIM%
HRROI B,[ASCIZ/
Message undelivered after /]
SETZ C,
SOUT%
MOVE B,CURDTM ;Output time in queue
MOVE C,MSGWRT(M)
CALL OTMDIF
HRROI B,[ASCIZ/ -- will try for another /]
SOUT%
MOVE B,MSGDEQ(M) ;Output remaining time in queue
MOVE C,CURDTM
CALL OTMDIF
HRROI B,[ASCIZ/:
/] ;Finish punctuation
SOUT%
RET
;;; Routine to rename a file
; Entry: a = source file jfn
; b = destination file JFN
; Call: CALL RNMFIL
; Return: +1, error
; +2, success
RNMFIL: SAVEAC <A,B>
STKVAR <SRC,DST>
MOVEM A,SRC ;Save source/destination JFNs
MOVEM B,DST
DO.
RNAMF% ;Rename, superceding
IFJER.
CAIE A,RNAMX5 ;File busy?
RET
MOVEI A,^D5000 ;Yes, wait 5 seconds and try again
DISMS%
MOVE A,SRC ;Get back source
LOOP.
ENDIF.
ENDDO.
MOVE A,DST ;Get destination JFN
HRLI A,.FBBYV ;Set to retain infinite versions
MOVX B,FB%RET
SETZ C,
CHFDB%
ERJMP .+1 ;Ignore failure
RETSKP
ENDSV.
SUBTTL Internet routines
; B/ Host name to connect to
; C/ Host number to connect to
INTSND: CAMN C,$UKHST ;Unknown host address?
JRST ADEADH ;Yes, fail right away
STKVAR <INTDST,INTADR,INTTRY,INTERR,DSTHPT>
MOVEM A,DSTHPT ;Save the ultimate destination
MOVEM B,INTDST ;Save destination
MOVEM C,INTADR ;Save destination address
MOVX A,^D10 ;Don't loop more than 10 times
MOVEM A,INTTRY
HRROI A,LCLNCN ;Local name for this network
SETO B, ;Output local host
CALL $GTHNS
FATAL (Can't get Internet local host name)
MOVE A,INTDST ;Get immediate destination
MOVE B,DSTHPT ;Ultimate destination host
CALL GENHDR ;Generate headers
MOVE N,SAVEN ;n := starting recipient host
MOVEI O,HSTRCP(N) ;o := start of recipient list
MOVE A,[POINT 7,STRBUF] ;a := ptr to net file name str
DO.
MOVEI B,[ASCIZ/TCP:/] ;Build device
CALL MOVSTR
;;; By default, DEC uses a port number of 100000+<job#>_6+<JFN#>
;;;For most applications, this is alright. It is not good enough
;;;for us, however. We open lots of connections, and are quite
;;;likely to get the same JFN each time. Because of this, any time
;;;we open to the same host in succession we're in danger of getting
;;;the same TCB before it's been fully flushed. What we'll do is use
;;;a slightly smarter version of DEC's algorithm, keeping within the
;;;reserved port number space if possible.
PUSH P,A
GJINF% ;Get our job number for local port
POP P,A
SKIPN C ;Job 0?
MOVEI C,377 ;Yes, do not use a small port number!
LSH C,6 ;Put job # where DEC expects it
AOS B,NXTSEQ ;Get next number in sequence
ANDI B,37 ;Cycle through 5 bits
IOR B,C ;Merge in job number
MOVE C,FORKX ;Get our fork ID
CAIN C,NETFRK ;Net fork?
TXO B,40 ;Yes, distinguish between it and rxmfrk
SKIPN WOPRP ;Privileged?
TXZA B,100000 ;Yes, make sure an unprivileged port
TXO B,100000 ;Yes, make like we're using a DEC port!
MOVX C,^D10 ;Ports are decimal
NOUT%
ERJMP R ;Failed
MOVEI B,[ASCIZ/#./] ;Privileged use of absolute local port
SKIPN WOPRP ;Privileged?
MOVEI B,[ASCIZ/./] ;No, just delimit to foreign port
CALL MOVSTR
MOVE B,INTADR ;Destination host number
MOVX C,^D8 ;TCP: hosts are in octal
NOUT% ;Output to file string
ERJMP R ;Shouldn't fail
MOVEI B,[ASCIZ/-25;CONNECTION:ACTIVE/] ;Port 25
CALL MOVST0
SETOM INTERR ;No default "OPENF% error code"
MOVX A,GJ%SHT ;Short form
HRROI B,STRBUF ;Pointer to file string we made
GTJFN% ;Make a JFN on it
ERJMP ADEADH ;Failed so mark dead
MOVEM A,NETJFN ;Save JFN
MOVX B,<<FLD ^D8,OF%BSZ>!<FLD .TCMWH,OF%MOD>!OF%RD!OF%WR>
DO. ;Begin timed control block
TMOSET (^D30,ENDLP.) ;Quit after 30 seconds
OPENF% ;Open 8 read/write buffered and wait
IFNJE.
TMOCLR ;Got it, clear timer
CALL SMTSND ;Call SMTP worker routine
DO.
TMOSET (^D60,ENDLP.) ;Don't wait too long for the FIN to happen
MOVE A,NETJFN ;Send a FIN to the other end
MOVX B,.TCSFN
TCOPR% ;Send the FIN
IFNJE.
DO. ;Now go into a loop slurping bytes from
BIN% ; the other end
ERJMP ENDLP. ;Closed, JFN close okay now
LOOP. ;Keep going until slurped up last byte
ENDDO.
ENDIF.
ENDDO.
TMOCLR
CALL $CLOSF ;Close the connection
RETSKP ;Success return
ELSE.
MOVEM A,INTERR ;Save last error code if OPENF% failed
ENDIF.
ENDDO. ;End of timed control block
TMOCLR ;Clear timer
MOVE A,NETJFN ;Get Internet JFN back
RLJFN% ;Release it
JWARN
SETZM NETJFN
MOVE A,INTERR ;Get back last error
CAIN A,TCPX19 ;Connection already exists?
SOSLE INTTRY ;Yes, have any more retries?
JRST ADEADH ;Other error or out of retries
LOOP. ;Yes to both, try next port up
ENDDO.
ENDSV.
;;; SMTP routines, independent of Internet
; SMTP command reply summary
; ^D220 ;Server greeting
; ^D250 ;OK
; ^D251 ;OK, but will forward
; ^D354 ;Ready for message
; ^D4xx ;Soft failure
; ^D5xx ;Hard failure
; ^D500 ;Unrecognized command
; ^D501 ;Unimplemented command
; ^D550 ;No such mailbox
SMTSND: STKVAR <SMTDOP,SMTHPT,DOMPTR,<HSTTMP,^D13>,<HSTLCL,^D13>>
HRROI A,HSTLCL ;Make absolute copy of local name string
HRROI B,LCLNCN
CALL OUTAHS
MOVE A,MSGDOP(M) ;Get message's delivery option
MOVEM A,SMTDOP ;And save as a temporary here
CALL SMRPLY ;Get greeting message
JRST SMTJER
CAIE B,^D220 ;Success reply is 220
JRST SMTSMF
MOVE A,NETJFN ;Negotiate HELO command
HRROI B,[ASCIZ/HELO /]
SETZ C,
CALL $SOUT
JRST SMTJER
HRROI B,HSTLCL ;Absolute form of local host
CALL SMMESG
JRST SMTJER
CAIE B,^D250 ;Success reply is 250
JRST SMTSMF
MOVE A,NETJFN ;Negotiate MAIL FROM command
MOVE B,SMTDOP ;Get delivery option index
HLRO B,DOPTAB(B) ;Get delivery option string
SETZ C,
CALL $SOUT
JRST SMTJER
HRROI B,[ASCIZ/ FROM:</]
DO.
CALL $SOUT
JRST SMTJER
SKIPN D,MSGRPT(M) ;Have a return path?
IFSKP.
MOVEI B,"@" ;Yes, must prepend local host as part
CALL $BOUT ;of source route. Output an at
JRST SMTJER
HRROI B,HSTLCL ;Local host name
CALL $SOUT
JRST SMTJER
MOVE B,MSGRPT(M) ;Make pointer to return path
HRLI B,(<POINT 7,>)
ILDB B,B ;Get first character of return path
CAIE B,"@" ;Additional source routing specification seen?
SKIPA B,[":"] ;No, use colon to terminate source routing
MOVEI B,"," ;Else must use comma for continuation
CALL $BOUT ;Output the character
JRST SMTJER
MOVE D,B ;Last delimiter
MOVE B,MSGRPT(M) ;Now output return path
HRLI B,(<POINT 7,>)
SETZ C, ;Terminate on null
CALL $SOUT
JRST SMTJER
ELSE. ;Return path not known, create one using sender
ANDQE. FG%XER,MSGJFN(N) ;But not if discarding errors!
MOVE D,MSGSDR(M) ;D := addr of sender host entry block
HRRZ C,HSTRCP(D) ;C := adr of recipient entry block
HRRZ B,RCPBPT(C) ;B := ptr to sender name
CAIN B,MLAGNT ;Only do this if not mail agent
ANSKP.
HRROI A,STRBUF ;Output to recipient buffer
MOVE B,RCPBPT(C) ;B,C := sender name ptr/byte count
MOVN C,RCPCNT(C) ;C := neg byte count
SOUT%
HRRZ B,HSTHST(D) ;B := sender host pointer
CAIN B,LCLNAM ;Is it our host?
MOVEI B,HSTLCL ;Yes, use canonical form
MOVEM B,SMTHPT ;Save host pointer
CAIN B,HSTLCL ;Is it me?
IFSKP.
MOVEI B,"%" ;Punctuate
IDPB B,A
MOVEI B,HSTLCL ;Set up local name
EXCH B,SMTHPT ;Restore host
HRROS B
SOUT%
ENDIF.
MOVE C,A ;Save termination
MOVE A,NETJFN ;Restore JFN
MOVE B,[POINT 7,STRBUF]
CALL QOTSTR ;Output it quoted
JRST SMTJER
MOVEI B,"@" ;Punctuate
CALL $BOUT
JRST SMTJER
HRRO B,SMTHPT ;Restore host
CALL $SOUT ;Output host name
JRST SMTJER
ENDIF. ;End of return-path output conditional
HRROI B,[ASCIZ/>/]
CALL SMMESG
JRST SMTJER
CAIN B,^D250 ;Success reply is 250
IFSKP.
MOVE A,NETJFN ;Failed, restore JFN
MOVE B,SMTDOP ;Get delivery option index
HLRO B,DOPTAB(B) ;Get delivery option string
SETZ C,
CALL $SOUT ;Output delivery option
JRST SMTJER
HRROI B,[ASCIZ/ FROM:<>/] ;Output null return path in case the SMTP
CALL SMMESG ; server didn't like its syntax...
JRST SMTJER
CAIN B,^D250 ;Did it win this time?
IFSKP.
SKIPN SMTDOP ;No, non-MAIL delivery option?
IFSKP.
SETZM SMTDOP ;Yes, convert to MAIL delivery option
MOVE A,NETJFN ;Restore JFN
LOOP. ;and try again
ENDIF.
JRST SMTSMF ;Treat as failure of entire message
ENDIF.
ENDIF.
ENDDO.
TXZ F,FM%VRC ;Initially no valid recipient seen
DO.
CALL NXTRCP ;Get next recipient
IFSKP.
CALL RSTRCP ;Reset error flags from other tries
MOVE A,NETJFN ;Start transaction
HRROI B,[ASCIZ/RCPT TO:</]
SETZ C,
CALL $SOUT
JRST SMTJER
MOVE A,[POINT 7,STRBUF]
CALL OUTRCP ;Output recipient name to STRBUF
MOVE C,A ;End of string pointer
MOVE A,NETJFN
MOVE B,[POINT 7,STRBUF] ;Recipient name to output
CALL QOTSTR ;Output it, quoted
JRST SMTJER ;Output failed
MOVE A,[POINT 7,STRBUF]
MOVX B,"@"
IDPB B,A
HRRO B,FRNHST ;Get site we are talking to
CALL OUTAHS ;Output it
MOVEI B,">"
IDPB B,A
SETZ B,
IDPB B,A
HRROI B,STRBUF
CALL SMMESG
JRST SMTJER
ETYPE <%1W> ;Type reply for user
CAILE B,^D299 ;Valid recipient?
IFSKP.
TXO F,FM%VRC ;Flag a valid recipient seen
ELSE.
CAIGE B,^D500 ;Hard fail code?
SKIPA B,[FR%TMP!FR%ERM] ;No, temporary error
MOVX B,FR%FAI!FR%ERM ;Yes, permanent
CALL STEMSG ;Flag the user failure
ENDIF.
LOOP.
ELSE.
ANDXN. F,FM%VRC ;A valid recipient seen?
CITYPE < > ;Yes, indicate sending the message text
HRROI B,[ASCIZ/DATA/]
CALL SMMESG ;Get reply
JRST SMTJER
CAIE B,^D354 ;Good reply?
JRST SMTSMF ;No, whole message fails
MOVE A,NETJFN ;Get output designator
CALL MSGOUT ;Output message, checking for periods
JRST SMTJER ;+1 Network error
CALL SMRPLY ;Get a reply
JRST SMTJER
ETYPE <%1W> ;Type reply
CAIE B,^D250 ;250 is success reply
JRST SMTSMF ;Whole message fails
ENDIF.
ENDDO.
SMTQIT: HRROI B,[ASCIZ/QUIT/] ;Negotiate QUIT command
CALL SMMESG
NOP ;Don't care
RET
ENDSV.
;;;JSYS error in SMTP dialog
SMTJER: TMOCLR ;No more interrupts
; CALLRET NETJER
NETJER: HRROI A,STRBUF ;Create error string
HRLOI B,.FHSLF ;This fork,,last error
SETZ C,
ERSTR%
ERJMP .+1
ERJMP .+1
HRROI A,STRBUF ;Set up string for SMTSMF
CETYPE <%1W> ;Type error msg for user
MOVX B,FR%TMP!FR%ERM ;Yes, save error info for dequeue
CALLRET STUMSG ;Update user errors
;;;Entire message fails due to SMTP error reply
SMTSMF: CETYPE <%1W> ;Type error msg for user
CAIGE B,^D500 ;Hard fail code?
SKIPA B,[FR%TMP!FR%ERM] ;No, mark as soft
MOVX B,FR%ERM!FR%FAI ;Otherwise hard
CALL STUMSG ;Update user errors
JRST SMTQIT
;;; SMTP quoting
;Accepts:
; A/ Destination designator
; B/ Source pointer - may not be to STRBF1!!!!!!!
; C/ End of source string pointer or 0 to terminate on null
; CALL QOTSTR
;Returns +1: JSYS error
; +2: success
; Clobbers STRBUF, STRBF1
QOTSTR: SAVEAC <A,D,T,TT>
STKVAR <QOTDES,QOTSRC,QOTTMP,QOTCNT>
MOVEM A,QOTDES ;Save output designator
MOVEM B,QOTSRC ;Save source pointer
MOVE A,[POINT 7,STRBF1] ;Pointer to temporary buffer
MOVEM A,QOTTMP ;Save temporary buffer pointer
MOVE A,C ;End of string pointer
SETZM QOTCNT ;Initial number of copied bytes count
TXZ F,FM%QOT ;Initially require no quoting
MOVX B,"\" ;Quote for wierd characters
DO. ;Copy to STRBF1 with \ insert and " need check
IFN. A ;If end of string pointer exists
CAMN A,QOTSRC ;Reached end of buffer?
EXIT. ;Yes, leave now
ENDIF.
ILDB C,QOTSRC ;Get character in buffer
IFE. A ;If terminate on null
JUMPE C,ENDLP. ;Terminate on null
ENDIF.
MOVEI T,(C) ;Make a copy of it to hack
IDIVI T,^D32 ;T := word to check, TT := bit to check
MOVNS TT
MOVX D,1B0 ;D := bit to check
LSH D,(TT)
TDNE D,QOTMSK(T) ;Is it a special character?
TXO F,FM%QOT ;Yes, note
TDNN D,QT1MSK(T) ;Is it an wierd character?
IFSKP.
IDPB B,QOTTMP ;Yes, put in wierd character quote
SOS QOTCNT ;Count the quoting character
ENDIF.
IDPB C,QOTTMP ;Now copy character
SOS QOTCNT
LOOP. ;Count and continue
ENDDO.
MOVE A,[POINT 8,STRBUF]
MOVX T,.CHDQT
TXNE F,FM%QOT ;Need to do atomic quoting?
IDPB T,A ;Yes, insert it
MOVE B,[POINT 7,STRBF1]
MOVE D,QOTCNT ;Count of bytes in recipient string
DO.
ILDB C,B ;Copy recipient string to command buffer
IDPB C,A
AOJL D,TOP.
ENDDO.
TXNE F,FM%QOT ;Need to do atomic quoting?
IDPB T,A ;Yes, insert it
HRRZ T,A ;Last word written
SUBI T,STRBUF-1 ;Number of words written
LSH T,2 ;Number of bytes in those words
LDB TT,[POINT 3,A,2] ;Number of padding bytes
SUBI T,(TT) ;Number of bytes in string
MOVE A,QOTDES
MOVE B,[POINT 8,STRBUF]
MOVN C,T
CALL $SOUT ;Output buffer
RET
RETSKP
ENDSV.
;;;If any of these characters are seen, the entire string must be
;;;quoted within double quotes
BRINI. ;Initialize break mask
BRKCH. (.CHNUL,.CHTAB) ;CTRL/@ through CTRL/I
BRKCH. (.CHVTB,.CHFFD) ;CTRL/K, CTRL/L
BRKCH. (.CHCNN,.CHSPC) ;CTRL/N through space
BRKCH. (050,051) ;"(", ")"
BRKCH. (054) ;","
BRKCH. (072,074) ;":", ";", "<"
BRKCH. (076) ;">"
BRKCH. (100) ;"@"
BRKCH. (133) ;"["
BRKCH. (135) ;"]"
QOTMSK: EXP W0.,W1.,W2.,W3. ;Form table
;;;If any of these characters are seen, they must be quoted with backslash
BRINI. ;Initialize break mask
BRKCH. (.CHLFD) ;Line feed
BRKCH. (.CHCRT) ;Carriage return
BRKCH. (.CHDQT) ;"
BRKCH. (134) ;"\"
QT1MSK: EXP W0.,W1.,W2.,W3. ;Form table
;;; Send a line and get response
SMMESG: MOVE A,NETJFN
SETZ C,
CALL $SOUT
RET
HRROI B,CRLF0
SETZ C,
CALL $SOUTR ;Output buffer
RET
;;; CALLRET SMRPLY ;Get a reply and return
;;; Get a reply, return text starting pointer in A, number in B
SMRPLY: STKVAR <TXTPTR>
DO.
TMOSET(^D300,TIMOUT) ;Wait 5 minutes before giving up
MOVE A,NETJFN
MOVE B,[POINT 7,STRBUF]
MOVEM B,TXTPTR
MOVX C,<5*STRBSZ>-1
MOVEI D,.CHLFD ;Terminate on line feed
SIN% ;Read a line
IFJER.
TMOCLR
RET
ENDIF.
TMOCLR ;No more interrupts...
LDB C,B ;Sniff at last byte of text
CAIN C,.CHLFD ;Ended in LF? (should have)
IFSKP.
WARN <SMRPLY didn't get full text of SMTP reply>
ELSE.
MOVNI C,2 ;Yes, back up over CRLF
ADJBP C,B ;C := backed over byte pointer
MOVE B,C ;Update copy in B for tie-off below
ILDB C,C ;Get expected CR
CAIN C,.CHCRT ;Was it?
ANSKP.
WARN <SMRPLY got an SMTP reply that ended with LF, not CRLF>
IBP B ;No, don't wipe the whatever it was out
ENDIF.
SETZ C, ;Make sure string is properly tied off
IDPB C,B
SKIPN DEBUGP ;Debugging SMTP replies?
IFSKP.
MOVEI A,STRBUF ;Print the whole buffer
CIETYP < SMTP: %1W
> ;CRLF and text
ENDIF.
SETZ B, ;Accumulate number here
DO.
ILDB C,TXTPTR ;Get byte
CAIE C,177 ;IAC? (Some cretin sending TELNET protocol!)
IFSKP.
ILDB C,TXTPTR ;Sigh, get command byte
CAIL C,173 ;WILL/WONT/DO/DONT?
ILDB C,TXTPTR
LOOP. ;Having ignored this IAC, try again
ENDIF.
CAIL C,"0" ;Is this character a digit?
CAILE C,"9"
EXIT. ;End of number
IMULI B,^D10 ;Else add in the new digit
ADDI B,-"0"(C)
LOOP. ;Get another digit
ENDDO.
CAIE C,"-" ;Continuation line?
CAIGE B,^D100 ;Some silly message we don't care about?
LOOP. ;Yes to either, get a new line
ENDDO.
MOVE A,TXTPTR
RETSKP
ENDSV.
SUBTTL DECnet Routines
;
; Try to connect and deliver a message to a remote DECnet host.
; Deliver using SMTP (object #125) if possible. If nobody answers,
; try using Mail-11 (object #27) instead. If this fails too,
; we're out of luck (it's a tough life).
;
; Entry: A/ Name of ultimate destination host
; B/ Name of DECnet host to connect to
; Call: CALL DCNSND
; Return: +1 -- Failure, error message printed using SMTJER
; +2 -- Success, connection JFN in NETJFN
DCNSND: STKVAR <DCNNAM,DSTHST,OBJIX>
MOVEM A,DSTHST ;Save ultimate destination host
MOVEM B,DCNNAM ;Save remote DECnet host name
HRROI A,LCLNCN ;Storage for local name for this network
SETO B, ;Output local host
CALL $DECNS
FATAL (Can't get DECnet local host name)
MOVE A,DCNNAM ;Immediate destination host
MOVE B,DSTHST ;Ultimate destination host
CALL GENHDR ;Generate headers
MOVEI A,DCNTBL ;Set up pointer to object table
MOVEM A,OBJIX
DO.
HLRZ A,@OBJIX ;Get object spec
JUMPE A,ADEADH ;Mark host as dead if no more specs
MOVE B,DCNNAM ;Name of remote host
CALL DCNCON ;Try to connect
IFSKP.
HRRZ A,@OBJIX ;Call transport routine
MOVE B,DCNNAM ;Get remote name agatin
MOVE N,SAVEN ;N := starting recipient host
MOVEI O,HSTRCP(N) ;O := start of recipient list
CALL (A) ;Call the proper worker routine
CALL $CLOSF ;Close the connection
RETSKP ;Success return
ENDIF.
AOS OBJIX
LOOP.
ENDDO.
ENDSV.
DCNTBL: [ASCIZ/-125/],,SMTSND
[ASCIZ/-TASK-MX-LISTENER/],,SMTSND
[ASCIZ/-27/],,VAXSND
0
; Connect to a DECnet host
;
; Entry: A/ Remote object name
; B/ Remote host name
; Call: CALL DCNCON
; Return: +1 -- Failure, couldn't connect
; +2 -- Success, connection JFN in NETJFN
DCNTIM==^D30000 ;DECnet user time-out interval (msec)
DCNDTM==^D60000 ;DECnet daemon time-out interval (msec)
DCNCON: STKVAR <DCNNAM,DCNOBJ>
MOVEM A,DCNOBJ ;Save DECnet object and
MOVEM B,DCNNAM ;Save DECnet host name for later
MOVE A,[POINT 7,STRBUF] ;a := ptr to net file name str
MOVEI B,[ASCIZ/DCN:/] ;Build device spec
CALL MOVSTR
HRRO B,DCNNAM ;Pick up our remote host name again
CALL OUTAHS ;Drop it in without the relative domain
MOVE B,DCNOBJ ;Add DECnet object spec
CALL MOVST0
MOVX A,GJ%OLD!GJ%SHT ;Old, short form, name from string
HRROI B,STRBUF
GTJFN% ;Get a JFN for our connection
ERJMP R ;Failed, so fail-return
MOVEM A,NETJFN ;Else, save our network JFN
MOVX B,<FLD(^D8,OF%BSZ)!FLD(1,OF%MOD)!OF%RD!OF%WR>
OPENF% ;Open the connection
IFJER.
MOVE A,NETJFN ;Get our DECnet JFN back
RLJFN% ;Release it
JWARN
SETZM NETJFN
RET ;Return lossage
ENDIF.
MOVX B,DCNTIM ;Set timeout interval (assume user)
SKIPE DAEMNP ;Are we the daemon?
MOVX B,DCNDTM ;Yes, so get different timeout interval
MOVEM B,ICPTIM
DO.
MOVE A,NETJFN
MOVX B,.MORLS ;Read link status
SETZ C, ;No addresses returned
MTOPR% ;Check our status
IFNJE.
JXN C,MO%CON,RSKP ;Exit if connected
TXNN C,MO%ABT ;Did the other end abort the connection?
SKIPE CTGCNT ;Or, did we see a ^G abort?
ANSKP.
MOVX A,^D100 ;No, still looking for connect confirm
MOVNI B,(A)
ADDB B,ICPTIM ;Have we timed out?
ANDG. B
DISMS% ;No, wait another 100 msec
LOOP. ;Go check again
ENDIF.
ENDDO.
CALLRET $CLOSF ;Lossage, close connection
ENDSV.
;;; Mail-11 DECnet Routines
; Send the message to a Mail-11 listener.
;
; Entry: NETJFN/ connection JFN
; Call: CALL VAXSND
; Return: +1 -- Always, via VAXJER if an error occurred
VAXSND: STKVAR <SMTDOP,SMTHPT,DOMPTR,<HSTTMP,^D13>,<HSTLCL,^D13>>
HRROI A,HSTLCL ;Make absolute copy of local name string
HRROI B,LCLNCN
CALL OUTAHS
MOVE A,MSGDOP(M) ;Get message's delivery option
MOVEM A,SMTDOP ;And save as a temporary here
MOVE A,[POINT 7,STRBUF] ;We'll put the sender's name here
SKIPN D,MSGRPT(M) ;Have a return path?
IFSKP.
MOVEI B,.CHDQT ;Quote it
IDPB B,A
HRRO B,MSGRPT(M) ;Now output return path
SETZ C, ;Terminate on null
SOUT%
MOVEI B,.CHDQT ;And add an ending quote
IDPB B,A
SETZ B,
IDPB B,A
ELSE. ;Return path not known, create one using sender
MOVE D,MSGSDR(M) ;D := addr of sender host entry block
HRRZ C,HSTRCP(D) ;C := adr of recipient entry block
HRRZ B,RCPBPT(C) ;B := ptr to sender name
CAIN B,MLAGNT ;Only do this if not mail agent
IFSKP.
HRRZ B,HSTHST(D) ;B := sender host pointer
CAIN B,LCLNAM ;Is it our host? (Local user)
IFSKP.
MOVEM B,SMTHPT ;No, add host and quote all of it
MOVEI B,.CHDQT ;Start with a quote
IDPB B,A
MOVE B,RCPBPT(C) ;B,C := sender name ptr/byte count
MOVN C,RCPCNT(C) ;C := neg byte count
SOUT%
MOVEI B,"@" ;Separate user/host with an atsign
IDPB B,A
HRRO B,SMTHPT ;Add host
SOUT%
MOVEI B,.CHDQT ;Finish with an ending quote
IDPB B,A
SETZ B, ;And a null, of course
IDPB B, A
ELSE. ;It's a local sender -- just name is sufficient
MOVE B,RCPBPT(C) ;B,C := sender name ptr/byte count
MOVN C,RCPCNT(C) ;C := neg byte count
SOUT%
ENDIF. ;End of local sender conditional
ENDIF. ;End of origin not mail agent conditional
ENDIF. ;End of return-path output conditional
HRROI B,STRBUF ;Send sender to the vax
CALL VAXLIN
JRST VAXJER
TXZ F,FM%VRC ;Initially no valid recipient seen
DO.
CALL NXTRCP ;Get next recipient
EXIT.
CALL RSTRCP ;Reset error flags from other tries
MOVE A,[POINT 7,STRBUF]
CALL OUTRCP ;Output recipient name to STRBUF
SKIPN GTDBLK+.GTDRD ;Doing MX?
IFSKP.
MOVX B,"%" ;Yes, shove in relay poop
BOUT% ;Probably this should have been done better
HRRO B,FRNHST
CALL OUTAHS
ENDIF.
SETZ B, ;Mark EOS
IDPB B,A
HRROI A,STRBUF ;Get recepient
CALL UCASE ;And turn it to upper case
HRROI A,STRBUF ;Double colonize address
CALL VAXTRN
HRROI B,STRBUF ;Send receiver to the VAX
CALL VAXLIN
JRST VAXJER
CALL VAXVRF ;Valid recipient?
IFSKP.
ANDE. B ;Single losers make whole message fail
ELSE.
MOVX B,FR%TMP ;Whole message lost, mark as soft error
CALLRET STUMSG ;Update user errors
ENDIF.
TYPE <Recepient accepted> ;Yes, tell user
TXO F,FM%VRC ;Flag a valid recipient seen
LOOP.
ENDDO.
JXE F,FM%VRC,R ;Punt now if no valid recipients
CITYPE < > ;Yes, indicate sending the message text
CALL VAXNIL ;Mark end of recepient list
JRST VAXJER
MOVEI A,[ASCIZ "TO"]
CALL FNDHEA ;Find recepients
HRROI B,[ASCIZ ""] ;Null string in case of none
CALL VAXLIN ;Send it
JRST VAXJER
MOVEI A,[ASCIZ "SUBJECT"]
CALL FNDHEA ;Find subject
HRROI B,[ASCIZ ""] ;In case of none
CALL VAXLIN ;And send it
JRST VAXJER
MOVE A,NETJFN ;Get output designator
CALL VAXMSG ;Output message, checking for CRLFs
JRST VAXJER ;+1 Network error
CALL VAXNIL ;Indicate end of message
JRST VAXJER
;;;Go through each recepient and verify that he/she really got the message
MOVE N,SAVEN ;N := starting recipient host
MOVEI O,HSTRCP(N) ;O := start of recipient list
DO. ;DO for each recepient
CALL NXTRCP ; Get next recipient
IFSKP. ; IF got another?
JN FR%FAI!FR%TMP,RCPFLG(O),TOP. ;Leave alone if already failed
CALL VAXVRF ; Verify this one
RET ; Whole message lost
LOOP. ; LOOP for each recepient
ENDIF. ; ENDIF got another
ENDDO. ;ENDDO for each recepient
RET
ENDSV.
;;; Transmogrify address to VMS double colon format (A/ address string)
;;; eg. a%b@c => c::b::a a%b.dom@c => c::dom%b::a (using VMS Foreign Protocol)
VAXTRN: TXC A,.LHALF ;Is str pnt LH -1?
TXCN A,.LHALF
HRLI A,(<POINT 7,>) ;Set up byte pointer
MOVE T,A ;T := start of string
SETZ TT, ;TT: = non-zero if quote seen
PUSH P,A ;Push pnt of beg of string
DO. ;Now find all %-routes
ILDB C,A
JUMPE C,ENDLP. ;End if null
CAIN C,.CHDQT ;Start/end of quoted material?
SETCA TT, ;Toggle quote flag
JUMPN TT,TOP. ;Don't check for %'s inside quoted text
CAIN C,"%" ;Is it percent kludge?
PUSH P,A ;Yes, push pointer
LOOP. ;Go for next char
ENDDO.
MOVE D,[POINT 7,TMPBUF] ;Temporary storage
DO. ;Next change them into :: route
POP P,B ;Check what we've found
CAMN B,T ;Back to user part (beg of string)?
EXIT. ;Yes, don't process, just copy
PUSH P,B ;No, save pointer again
SETZ TT, ;Outside of quoted material
DO. ;Search for .pseudoDomain (*%*.x*)
ILDB C,B
JUMPE C,ENDLP.
CAIN C,.CHDQT ;Start/end of quoted material?
SETCA TT, ;Toggle quote flag
JUMPN TT,TOP. ;Don't check for %'s or .'s inside quoted text
CAIN C,"%" ;End on %
EXIT.
CAIE C,"." ;Found domain?
LOOP. ;No, check next char
DO. ;Yes, move it + % sign
ILDB C,B
JUMPE C,ENDLP.
CAIN C,.CHDQT ;Start/end of quoted material?
SETCA TT, ;Toggle quote flag
IFE. TT ;Inside quoted text?
CAIN C,"%" ;No, end on %
EXIT.
ENDIF.
IDPB C,D ;Copy char
LOOP.
ENDDO.
MOVEI C,"%" ;Add % sign (VMS Foreign Protocol)
IDPB C,D
ENDDO.
POP P,B ;Get string pointer again
SETZ TT, ;Outside quoted text again
DO. ;Now move host name (*%x.*)
ILDB C,B
JUMPE C,ENDLP.
CAIN C,.CHDQT ;Start/end of quoted material?
SETCA TT, ;Toggle quote flag
IFE. TT ;Inside quoted text?
CAIE C,"%" ;No, end on %
CAIN C,"." ;..or "."
EXIT.
ENDIF.
IDPB C,D ;Move it
LOOP.
ENDDO.
MOVEI C,":" ;Append double colon
IDPB C,D
IDPB C,D
LOOP.
ENDDO.
SETZ TT, ;Clear quote flag
DO. ;Move user part (x*)
ILDB C,B
JUMPE C,ENDLP.
CAIN C,.CHDQT ;Start/end of quoted material?
SETCA TT, ;Toggle quote flag
IFE. TT ;Inside quoted text?
CAIN C,"%" ;No, end on %
EXIT.
ENDIF.
IDPB C,D ;Move it
LOOP.
ENDDO.
SETZ C, ;Mark null
IDPB C,D
MOVE A,T ;Move string back again
HRROI B,TMPBUF
SETZ C,
SOUT%
RET
;;; Send a line in B to VAX but don't wait for response
VAXLIN: MOVE A,NETJFN
SETZ C,
CALLRET $SOUTR
;;;JSYS error in MAIL-11 dialog
VAXJER: CALLRET SMTJER
;;; Mark end of recepeint list by sending a NULL
VAXNIL: MOVE A,NETJFN
HRROI B,[0]
MOVEI C,1
SETZ D,
CALLRET $SOUTR
;;; Verify a recepient by an acknowledge from the VAX.
;;; Returns +1 if whole message lost, +2 if message either succeded
;;; (with B/ 0) or only lost for this user (with B/ error flags)
VAXVRF: TMOSET(^D120,TIMOUT) ;Wait 2 minutes before giving up
SETZM STRBUF ;Clear STRBUF
MOVE A,NETJFN ;Get network JFN
HRROI B,STRBUF ;Set destination to STRBUF
MOVX C,-4 ;Want 4 bytes
SINR%
ERJMP VAXJER ;Couldn't get it -- report total soft error
HLRZ A,STRBUF ;What did the VAX say?
SETZ B, ;Reset error flags in B
CAIN A,4000 ;Good acknowledgement?
IFSKP.
HRROI B,STRBUF ;No, put error message in STRBUF
DO.
MOVE A,B ;Destination in A (STRBUF)
HRROI B,CRLF0 ;Start it with a CRLF
SETZ C, ;(Including the NULL)
SOUT%
MOVE B,A ;Destination in B (STRBUF)
MOVE A,NETJFN ;What went wrong?
SINR% ;Go get it
ERJMP VAXJER ;Couldn't get it -- report total soft error
LDB D,B ;Got a null string (= end of error msg)?
CAIE D,.CHLFD ;Then, we're still pointing on the last LF
LOOP. ;Otherwise get next line
ENDDO.
MOVX D,-2 ;Backup before last CRLF
ADJBP D,B
SETZ C,
IDPB C,D ;Smash last CR with NULL
HRROI A,STRBUF ;Point to the string
ETYPE <%1W> ;Type message for user
MOVX B,FR%ERM!FR%FAI ;Mark as hard error
CALL STEMSG ;Record error for user
ENDIF.
RETSKP
; Find the value of a certain header
;
; Entry: A/ mem addrs of asciz header key string
; Call: CALL FNDHEA
; Return: +1 for Failure
; +2 for Success with B/ asciz pnt to header value string
FNDHEA: HRLM A,HEATAB+1 ;Save header key
MOVE X,MSGNHD(M) ;Count,,byte-> to headers for this net
HLRZ Y,X ;Put count in Y
SUBI Y,2 ;Subtrace first CRLF
HRLI X,220700 ;And fill LR of X with a byte-> to 3rd byte
FNDSB0: CALL PARLIN ;Parse another line
RET ;End of file
JXN F,FP%EOL,R ;Empty line?
MOVEI A,HEATAB ;Point to header table
TXNE F,FP%CLN ;Ended by a colon?
CALL PARKEY ;Yes, check if subject
JRST FNDSB0 ;Either not colon or not subject -- try next
MOVE B,PCLNBP ;Got one!
IBP B ;Skip colon
CALL CPYHEA ;Copy the header
RETSKP
HEATAB: -1,,.+1
0,,[RETSKP]
; Copy a header value into STRBUF
;
; Entry: B -- Byte pointer to header value
; Call: CALL CPYHEA
; Return: +1 with B/ byte pnt asciz string in STRBUF
;
CPYHEA: MOVE A,[POINT 7,STRBUF]
DO.
ILDB C,B ;Copy a byte
IDPB C,A
CAIE C,.CHCRT ;Found CR?
LOOP. ;No, move next
SETZ C, ;Mark possible EOS
DPB C,A
ILDB C,B ;1st char on next line
CAIN C,.CHLFD ;(Skip LF)
ILDB C,B ;(Get real 1st char)
CAIE C,.CHTAB ;Tab? Then continue
CAIN C," " ;Space? Also continue
IFSKP. <EXIT.> ;Neither, done
IDPB C,A ;Copy this byte
LOOP.
ENDDO.
MOVE B,[POINT 7,STRBUF] ;Done copying, exit with B byte-> STRBUF
RET
; Turn a string into upper case
;
; Entry: A/ Pnt to asciz string
; Call: CALL UCASE
; Return: +1 always with string changed to uc and updated byte pnt in a
UCASE: SAVEAC <B>
TXC A,.LHALF ;Is str pnt LH -1?
TXCN A,.LHALF
HRLI A,(<POINT 7,>) ;Set up byte pointer
DO.
ILDB B,A ;Get next char
JUMPE B,R ;Return if done
CAIL B,"a" ;Turn into UC if >= "a" and <= "z"
CAILE B,"z"
CAIA
SUBI B,"a"-"A"
DPB B,A ;Put char back again
LOOP.
ENDDO.
;;; Output only message headers to JFN in A
;;; Returns: +1, transmission error
;;; +2, successful
VAXHEA: STKVAR <OUTMSD,BUFPTR>
MOVEM A,OUTMSD ;Save designator
;;; MOVEI A,^D256 ;Transmit 256 bytes at a time
MOVEI A,^D199 ;VMAIL can't handle more than 199 bytes, sigh!
MOVEM A,SEGSIZ ;Set segment size
SKIPN A,MSGTMT(M) ;Overall delivery timeout in effect?
IFSKP.
TIME% ;Yes, compute time limit for this copy
ADD A,TMCINT
CAMLE A,MSGTMT(M) ;Beyond total delivery timeout?
MOVE A,MSGTMT(M) ;Yes, use that
ENDIF.
MOVEM A,MSGTMC(M) ;Record copy timeout
MOVE A,OUTMSD ;Restore designator
MOVE B,MSGNHD(M) ;Headers we generated
HLRZ D,B ;Length
HRLI B,(<POINT 7,0>) ;Build byte pointer to message
SUBI D,2 ;Skip over the CRLF at the start
IBP B
IBP B
IFN. D ;Message non-empty with count in D
DO. ;Do 256-bytes at a time with CRLF checking
TMOCLR ;Disallow timer interrupts
MOVEM B,BUFPTR ;Save pointer to start of buffer
SETZB C,TT ;Character count zero, no doubled dot
DO. ;Search for "<CRLF>" sequence within buffer
CAMLE C,SEGSIZ ;Buffer filled?
EXIT. ;Yes, output it
ILDB T,B ;Get byte from buffer
ADDI C,1 ;Count this character
CAIE T,.CHCRT ;Is it a CR?
LOOP. ;No, continue scan
ILDB T,B ;Saw CR, get possible LF
ADDI C,1 ;Count this character
CAIE T,.CHLFD ;Have we gotten a <CRLF>?
LOOP. ;No, continue scan
ENDDO. ;End scan through message for <CRLF>.
MOVE B,BUFPTR ;Get back pointer to start of buffer
SUBI D,(C) ;Account for this many characters output
MOVNS C ;Negative byte count for SOUT%
ADDI C,2 ;Don't send CRLF
CALL OUTMST ;Check copy timer
JRST OUTMSF ;Timed out
IFE. C ;A null line?
HRROI B,[ASCIZ ""] ;Yes, send a NULL terminated null string
CALL $SOUTR
JRST OUTMSF
MOVE B,BUFPTR ;Then restore text pointer
ELSE.
CALL $SOUTR ;No, output the string as usual
JRST OUTMSF
ENDIF.
ILDB T,B ;Skip CRLF we didn't send
ILDB T,B
JUMPG D,TOP. ;Continue output if more bytes to go
ENDDO.
ENDIF.
AOS (P) ;Set success (+2)
TMOCLR ;Disallow timer interrupts now
RET
ENDSV.
;;; Output whole text of message and headers to JFN in A with CRLF checking
;;; Returns: +1, transmission error
;;; +2, successful
VAXMSG: STKVAR <BUFPTR>
CALL VAXHEA ;Output headers
RET ;+1 Transmission error
MOVEI B,^D256 ;Transmit 256 bytes at a time
MOVEM B,SEGSIZ ;Set segment size
MOVE B,MSGTXT(M) ;Get pointer to message text
MOVE D,MSGTCN(M) ;Get text count
DO. ;Do 256-bytes at a time with CRLF checking
JUMPLE D,OUTMDN ;Quit if no more bytes to do
TMOCLR ;Disallow timer interrupts
MOVEM B,BUFPTR ;Save pointer to start of buffer
SETZ C, ;Character count zero
DO. ;Search for "<CRLF>" sequence within buffer
CAMLE C,SEGSIZ ;Buffer filled?
EXIT. ;Yes, output it
ILDB T,B ;Get byte from buffer
ADDI C,1 ;Count this character
CAIE T,.CHCRT ;Is it a CR?
LOOP. ;No, continue scan
ILDB T,B ;Saw CR, get possible LF
ADDI C,1 ;Count this character
CAIE T,.CHLFD ;Have we gotten a <CRLF>?
LOOP. ;No, continue scan
ENDDO. ;End scan through message for <CRLF>
MOVE B,BUFPTR ;Get back pointer to start of buffer
SUBI D,(C) ;Account for this many characters output
MOVNS C ;Negative byte count for SOUT%
ADDI C,2 ;Don't send <CRLF> itself
CALL OUTMST ;Check copy timer
JRST OUTMSF ;Timed out
IFE. C ;A null line?
HRROI B,[ASCIZ ""] ;Yes, send a NULL terminated null string
CALL $SOUTR
JRST OUTMSF
MOVE B,BUFPTR ;Then restore text pointer
ELSE.
CALL $SOUTR ;No, output the string as usual
JRST OUTMSF
ENDIF.
ILDB T,B ;Skip CRLF we didn't send
ILDB T,B
LOOP.
ENDDO.
ENDSV.
SUBTTL Chaosnet routines
;;; Chaos specific symbols, etc
;Timeouts
CHATIM==^D7000 ;User time-out
CHADTM==^D20000 ;Daemon time-out
;Connection states
;IFNDEF .CSCLS,<.CSCLS==0> ;Closed
;IFNDEF .CSLSN,<.CSLSN==1> ;Listening
;IFNDEF .CSRFC,<.CSRFC==2> ;RFC received
IFNDEF .CSRFS,<.CSRFS==3> ;RFC sent
IFNDEF .CSOPN,<.CSOPN==4> ;Opened
;IFNDEF .CSLOS,<.CSLOS==5> ;LOS-ing
IFNDEF .CSINC,<.CSINC==6> ;Incomplete transmission (no response to SNS)
IFNDEF .MOPKR,<.MOPKR==27> ;MTOPR% code to read a packet
;Packet description
$CPKOP==<POINT 8,Z,7> ;Opcode
$CPKNB==<POINT 12,Z,31> ;Number of bytes
CHPKDT==4 ;First word of data
CHPMXC==^D488 ;Maximum number of characters of data
;Packet opcodes
;IFNDEF .CORFC,<.CORFC==1> ;Request for connect
;IFNDEF .COOPN,<.COOPN==2> ;Open
IFNDEF .COCLS,<.COCLS==3> ;Close
;IFNDEF .COFWD,<.COFWD==4> ;Forward
;IFNDEF .COANS,<.COANS==5> ;Answer
;IFNDEF .COSNS,<.COSNS==6> ;Sense status
;IFNDEF .COSTS,<.COSTS==7> ;Report status
;IFNDEF .CORUT,<.CORUT==10> ;Routing info (not used)
IFNDEF .COLOS,<.COLOS==11> ;You are losing
;IFNDEF .COLSN,<.COLSN==12> ;Listen (never used)
;IFNDEF .COMNT,<.COMNT==13> ;Maintenance
;IFNDEF .COEOF,<.COEOF==14> ;EOF connection stream
;IFNDEF .COMAX,<.COMAX==15> ;Maximum opcode+1
;IFNDEF .CODAT,<.CODAT==200> ;Random data opcode
;;; Send message in M to Chaosnet host in E
; B/ Host name to connect to
; C/ Host number to use
CHASND: STKVAR <HSTPTR,DSTHPT>
MOVEM A,DSTHPT ;Save ultimate host
MOVEM B,HSTPTR ;Save host pointer
HRROI A,LCLNCN ;Local name for this network
SETO B, ;Output local host
CALL $CHSNS
FATAL (Can't get Chaosnet local host name)
MOVE A,HSTPTR ;Get immediate destination
MOVE B,DSTHPT ;Get ultimate destination
CALL GENHDR ;Generate headers
SETZM NETJFN ;No MAIL connection yet
DO.
CALL NXTRCP ;Get next recipient
EXIT. ;No, done with recipients
CALL RSTRCP ;Reset error flags from other tries
SKIPN MSGDOP(M) ;Want some kind of send?
IFSKP. ;Guess so...
MOVE C,HSTPTR ;Need name back
PUSH P,NETJFN ;Save jfn we're using for MAIL
CALL CHSEND ;Try a chaos SEND
IFSKP. ;Did it win?
POP P,NETJFN ;This MUST happen on all paths through here!!
MOVE B,MSGDOP(M) ;Yup, it won, see what we were doing
CAIE B,D%SAML ;Want mail even when send won?
LOOP. ;Nope, done with this recipient
ELSE. ;Send lost
POP P,NETJFN ;This MUST happen on all paths through here!!
MOVE B,MSGDOP(M) ;See what we were doing
CAIN B,D%SEND ;Send only?
LOOP. ;Yup, really lost, next recipient
ENDIF. ;Going on to do MAIL if we get here
ENDIF. ;Or here
CALL RSTRCP ;Reset error flags again
SETZM TMPBUF ;Clear reply string buffer
SKIPE A,NETJFN ;Net mail jfn
IFSKP. ;Don't have one yet
MOVE A,[POINT 7,STRBUF] ;Construct contact name
MOVEI B,[ASCIZ/CHA:/] ;Chaos
CALL MOVSTR
MOVE B,HSTPTR ;Host name
CALL OUTAHQ ;Add it, in absolute form
MOVEI B,[ASCIZ/.MAIL/] ;Contact name is MAIL
CALL MOVST0 ;Tack it on, end with null
HRROI B,STRBUF ;Point at filename
SETZ C, ;No third arg for OPENF%
CALL CHAOPN ;Go open the connection
CALLRET $CLOSF ;Couldn't, host is dead, out of here
MOVE A,NETJFN ;Get jfn we just opened
ENDIF. ;Have a net jfn in A
CALL CHARCP ;Output this name
TYPE <(MAIL) > ;Say we are trying MAIL
MOVEI B,<200+.CHCRT> ;Newline
BOUT%
IFNJE.
MOVEI B,.MOSND
MTOPR%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
CALL CHAREP ;Get reply
ANSKP.
CAIN D,"+" ;Address ok?
LOOP. ;Yes, flag as such
CAIN D,"%" ;Temporary error?
ANSKP.
CALL CHAECP ;No, hard error, copy error string
MOVX B,FR%FAI!FR%ERM ;Record failure
CALL STEMSG
LOOP. ;Try next recipient
ELSE.
CALL CHAECP ;Set up error string
MOVX B,FR%TMP!FR%ERM
CALL STEMSG ;Set error information
LOOP.
ENDIF.
ENDDO.
CITYPE < > ;Indicate sending message text
SETZM TMPBUF ;Clear network reply buffer
SKIPN A,NETJFN ;Are we doing mail at all?
RETSKP ;No, bye
MOVE C,MSGNHD(M)
HLRZ D,C
HRLI C,(<POINT 7,0>)
CALL CHOSTR ;Dump out headers
IFSKP.
DMOVE C,MSGTXT(M) ;Okay, now the message
CALL CHOSTR
ANSKP.
MOVEI B,.MOEOF
MTOPR%
..TAGF (ERJMP,) ;I sure wish ANNJE. existed!
CALL CHAREP ;Get reply
ANSKP.
CAIE D,"+" ;Ok?
ANSKP.
ELSE.
CALL CHAECP ;Yes, copy error string
MOVX B,FR%TMP!FR%ERM ;Save error info for dequeue
CALL STUMSG ;Update user errors
ENDIF.
CALL $CLOSF ;Close it - take care of data error
RETSKP
ENDSV.
;Open a chaos connection, returns +1 on failure, +2 on success
;NETJFN might be open even if connection didn't, so you can get the error msg.
;B/ Filespec for connection
;C/ Zero or contact name word for OPENF%
CHAOPN: MOVX A,GJ%SHT ;Generic
GTJFN% ;B already points to filespec
ERJMP R ;Failed completely, host dead or something
MOVEM A,NETJFN ;Save the jfn
MOVEI A,CHATIM ;Set timer
SKIPE DAEMNP
MOVEI A,CHADTM
MOVEM A,ICPTIM
SETZM CTGCNT
MOVE A,NETJFN ;Open 8-bit, mode 6 (don't wait for OPN)
MOVX B,<<FLD ^D8,OF%BSZ>!<FLD 6,OF%MOD>!OF%RD!OF%WR>
OPENF% ;There may be a contact name in C
IFJER. ;Lost completely
MOVE A,NETJFN
RLJFN%
JWARN
SETZM NETJFN ;Be paranoid
RET ;It's dead, give up
ENDIF.
DO. ;Wait for the OPN
MOVE A,NETJFN
GDSTS% ;Get connection status
ERJMP R ;Give up
ANDI B,17 ;Just the state bits
CAIN B,.CSOPN ;OPN ?
RETSKP ;Yup, we won
CAIN B,.CSRFS ;RFS ?
SKIPE CTGCNT ;User requested abort?
EXIT. ;Out of here
MOVX A,-^D100 ;Still RFS and no abort, wait a while
ADDB A,ICPTIM ;Count off time to wait
JUMPLE A,ENDLP. ;Timeout, B has state
MOVX A,^D100
DISMS% ;Time left, dally on it
LOOP. ;Go try again
ENDDO. ;We've lost if we get here
CAIE B,.CSINC ;Not responding?
CAIN B,.CSRFS ;or timeout on RFS?
CALL ADEADH ;If either, mark as dead
RET ;Return failure
; Do a chaos SEND, return +1 on failure, +2 on sucess
;C/ Host name
CHSEND: MOVE A,[POINT 7,TMPBUF+1000] ;Build filename for connection
MOVEI B,[ASCIZ/CHA:/] ;Chaos
CALL MOVSTR
MOVE B,C ;Host name
CALL OUTAHQ ;Add it, in absolute form
MOVEI B,[ASCIZ/./] ;No contact name yet, easier to do in OPENF%
CALL MOVST0 ;Tack it on with a null
MOVE A,[POINT 8,TMPBUF] ;Cons up RFC packet
MOVEI B,[ASCIZ/SEND /] ;Contact name
CALL MOVSTR
CALL CHARCP ;The recipient
TYPE <(SEND) > ;Log that we are sending
IFXN. F,FM%RLY ;Are we relaying?
MOVEI B," " ;Yes, add space
IDPB B,A
SKIPN D,MSGSDR(M) ;and the sender
FATAL <No sender block set up>
HRRZ C,HSTRCP(D) ;Get pointer to sender's recipient entry block
MOVE B,RCPBPT(C) ;Point to sender user name
SKIPN C,RCPCNT(C) ;Have a recipient?
HRROI B,[ASCIZ/Unknown user/] ;No, make pretty name
SOUT% ;Write it
MOVEI B,"@" ;Add atsign
IDPB B,A
HRRO B,HSTHST(D) ;Now get name for host
CALL OUTAHS ;Add host name
ENDIF.
MOVEI C,-TMPBUF+1(A) ;Find length
IMULI C,4
LSH A,-41
SUB C,A
CAILE C,CHPMXC
MOVEI C,CHPMXC
HRLI C,TMPBUF
MOVSS C ;C/ length,,buffer (contact name)
HRROI B,TMPBUF+1000 ;B/ filespec (no contact name)
CALL CHAOPN ;Open the connection
IFSKP. ;Won, user available
MOVE A,NETJFN ;Output reply-parsable header:user<sp>date<nl>
SKIPN D,MSGSDR(M) ;d := adr of sender host entry block
FATAL <No sender block set up>
HRRZ C,HSTRCP(D) ;Get pointer to recipient entry block
MOVE B,RCPBPT(C) ;Point to sender user name
SKIPN C,RCPCNT(C) ;Have a recipient?
HRROI B,[ASCIZ/Unknown user/] ;No, make pretty name
SOUT% ;Write it
IFNJE.
MOVEI B,"@" ;Add atsign
BOUT%
..TAGF (ERJMP,) ;ANNJE.
HRRO B,HSTHST(D) ;Now get name for host
CALL OUTAHS ;Add host name
MOVEI B,.CHSPC ;Space
BOUT%
..TAGF (ERJMP,) ;ANNJE.
SETO B, ;Current time
MOVX C,OT%NSC!OT%12H!OT%SCL
ODTIM%
..TAGF (ERJMP,) ;ANNJE.
MOVE C,MSGNHD(M) ;Dump out headers (start with a newline)
HLRZ D,C
HRLI C,(<POINT 7,0>)
CALL CHOSTR
ANSKP.
DMOVE C,MSGTXT(M) ;And now the message
CALL CHOSTR
ANSKP.
MOVEI B,.MOEOF ;Send EOF
MTOPR%
..TAGF (ERJMP,) ;ANNJE.
MOVEI B,.MONOP ;Wait til it is ack'd
MTOPR%
..TAGF (ERJMP,) ;ANNJE.
TXO A,CO%WCL
CLOSF%
..TAGF (ERJMP,) ;ANNJE.
TYPE <OK>
SETZM NETJFN ;Be paranoid
RETSKP ;Won, return success
ENDIF.
;here if jsys error sending message, could get the emsg but most
;likely it's just 'data error' or something equally uninformative
MOVE TT,[POINT 7,[ASCIZ/SEND connection not completed/]]
ELSE. ;Here if couldn't even open a connection
MOVE TT,[POINT 7,[ASCIZ/Couldn't get a SEND connection to host/]]
SKIPN NETJFN
ANSKP.
DO.
MOVE A,NETJFN
GDSTS%
ERJMP ENDLP.
JXE C,.RHALF,ENDLP. ;No more packets, punt
MOVEI B,.MOPKR ;Else get a packet
MOVEI C,TMPBUF
MTOPR%
ERJMP ENDLP.
LDB C,[$CPKOP+TMPBUF]
CAIE C,.COLOS ;LOS packet?
CAIN C,.COCLS ;CLS packet?
IFSKP. <LOOP.> ;Neither, get another one
LDB C,[$CPKNB+TMPBUF]
IFG. C ;Ok, have a reply
MOVE TT,[POINT 8,TMPBUF+CHPKDT]
ADJBP C,TT ;Tie it off
SETZ A,
IDPB A,C
ENDIF.
ENDDO.
ENDIF.
ETYPE <failed - %7W>
CALL SERMRK ;Mark the error
CALLRET $CLOSF ;Done
;;Output recipient name for chaos with quoting, sigh. Apparently Unix servers
;;can't handle "user%host", they want "user"%host.... Everybody else seems to
;;be able to handle either, so we do it the Unix way.
CHARCP: MOVE A,[POINT 8,STRBUF]
DMOVE B,RCPBPT(O) ;Recipient
ADJBP C,B ;C=end pointer
CALL QOTSTR ;Output the user name string
FATAL (Impossible QUOSTR failure in CHARCP)
MOVE A,B ;Foo, QOTSTR preserves A...
IFXN. F,FM%RLY
MOVEI C,"@" ;Use @ to decrease chance of servers choking on
IDPB C,A ;quotes. Ok since no other @ follows.
MOVE C,A ;Save pointer
HRRZ B,HSTHST(N) ;Add host name
CALL MOVST0
EXCH A,C ;Flush the domain if any
CALL GETDOM
MOVE B,C
SETO A,
ADJBP A,B
ENDIF.
MOVEI D,-STRBUF+1(A) ;Find length
IMULI D,4
LSH A,-41
SUB D,A
CITYPE < >
MOVX A,.PRIOU
MOVE B,[POINT 8,STRBUF]
MOVN C,D
SKIPE PRINTP
SOUT%
TYPE <: >
MOVE A,NETJFN
MOVE B,[POINT 8,STRBUF]
MOVN C,D
SOUT%
ERJMP .+1
RET
;;Find (pseudo)domain in host name if any. If successful, A has domain block
;;and B pointer to the domain name.
GETDOM: STKVAR <DOMPTR>
TXCE A,.LHALF
TXCN A,.LHALF
HRLI A,(POINT 7,)
SETZM DOMPTR
DO.
ILDB B,A
CAIN B,"."
MOVEM A,DOMPTR
JUMPN B,TOP.
ENDDO.
MOVE A,DOMTBL
SKIPN B,DOMPTR
RET
PUSH P,C
TBLUK%
POP P,C
JXE B,TL%EXM,R ;Oops, not really a domain
MOVE B,DOMPTR
RETSKP
ENDSV.
;; Get chaos reply into TMPBUF, with timeout
;; A/ output JFN
;; On successful return, D has reply code
CHAREP: DO.
TMOSET(^D60,ENDLP.) ;Don't hang
SETZM TMPBUF ;Init empty buffer
MOVE B,[POINT 8,TMPBUF]
MOVX C,4000
MOVX D,<200!.CHCRT>
SIN% ;Read response line
ERJMP ENDLP.
TMOCLR
SETZ D,
DPB D,B ;Replace newline with null
MOVE A,[POINT 8,TMPBUF] ;Pointer to message (including status since
ETYPE <%1W> ; Unix doesn't send any text with status)
LDB D,[POINT 8,TMPBUF,7] ;Return status byte
RETSKP
ENDDO.
TMOCLR ;No more timeout
SETZM TMPBUF ;Flush any partial reply
RET
;; Here to copy error string to STRBUF with ending crlf
;; b = ptr to string source
CHAECP: DMOVE A,[POINT 7,STRBUF ;a := output buffer
POINT 8,TMPBUF] ;Error reply from network?
SKIPN TMPBUF
MOVE B,[POINT 7,[ASCIZ/Chaosnet error/]] ;No
CALLRET MOVST2
;;;Output string to Chaosnet, non-skip if failure
;;; A/ destination JFN
;;; C/ pointer
;;; D/ byte count
;;;This routine will never win an award for efficiency.
CHOSTR: DO.
SOJL D,RSKP
ILDB B,C ;Get next char
CAIN B,.CHLFD ;Lfs don't go
LOOP.
CAIL B,.CHBSP
CAILE B,.CHCRT
CAIA
TXO B,200
BOUT%
ERJMP R ;Failed: give error return
LOOP.
ENDDO.
SUBTTL Pup routines
PUPTIM==^D12000 ;Ethernet user time-out (msec)
PUPDTM==^D20000 ;Ethernet Daemon time-out (msec)
PUPSTM==^D60000 ;Ethernet Send reply time-out (msec)
; Packet level input/output
OPDEF PUPI% [JSYS 441]
OPDEF PUPO% [JSYS 442]
; Flags for PUPI%/PUPO%
PU%CHK==:1B1 ;Compute/check checksum
PU%TIM==:1B4 ;No input timeout in MS in AC3
; Packet structure definitions (from PUPSYM)
MNPLEN==:^D22 ;Minimum Pup Length in bytes
MXPLEN==:^D554 ;Maximum Pup Length in bytes
MXPBLN==:<MXPLEN+3>/4 ;Maximum size of PB, in words
DEFSTR PUPLEN,TMPBUF,15,16 ;Pup Length
DEFSTR PUPTYP,TMPBUF,31,8 ;Pup Type
PBCONT==5 ;Word data starts at
; Marks for mail transport
YESMRK==3 ;Yes
NOMRK==4 ;No
EOCMRK==6 ;End of command
HEREFL==5 ;Here is the file
STMAIL==20 ;Store mail
MBXEXC==23 ;Mailbox exception
; OF%MOD file open modes
.PUORW==16 ;Open port in raw packet mode
; MTOPR% functions
.MORMK==23 ;Read the most recently received mark
.MOSAB==25 ;Generate abort and close connection
.MORAB==26 ;Read abort code and string (abort state only)
; BSP port states
P%RFCO==1 ;RFC out
P%OPEN==3 ;Open
P%ABRT==7 ;Abort
; B/ Name to connect to
; C/ Address to use
PUPSND: STKVAR <PUPNAM,PUPADR,DSTHPT>
MOVEM A,DSTHPT ;Save ultimate host pointer
MOVEM P,SAVEP ;Save the starting P
MOVEM B,PUPNAM ;Save pointer
MOVEM C,PUPADR ;Save address
HRROI A,LCLNCN ;Local name for this network
SETO B, ;Output local host
CALL $PUPNS
FATAL (Can't get Pup local host name)
MOVE A,PUPNAM ;Get immediate destination
MOVE B,DSTHPT ;Get ultimate destination
CALL GENHDR ;Generate headers
SKIPN MSGDOP(M) ;Want to send message?
IFSKP.
MOVE A,[POINT 7,STRBUF] ;a := ptr to net file name str
MOVEI B,[ASCIZ/PUP:!J./] ;Output device and local host part
CALL MOVSTR
MOVE B,PUPNAM ;Host name
CALL OUTAHQ ;Add it, in absolute form
MOVEI B,[ASCIZ/+Misc-Services/] ;Misc-Services well-known socket
CALL MOVST0 ;Finish up the string as ASCIZ
MOVX A,GJ%OLD!GJ%SHT ;Old, short form, name from string
HRROI B,STRBUF
GTJFN% ;Get a JFN for the port
ERJMP ADEADH ;Fail
MOVEM A,NETJFN ;Save JFN
MOVX B,FLD(8,OF%BSZ)!FLD(.PUORW,OF%MOD)!OF%RD!OF%WR
OPENF% ;Open in raw packet mode
IFJER.
MOVE A,NETJFN ;Release output JFN
RLJFN%
JWARN
SETZM NETJFN
CALLRET ADEADH ;Fail
ENDIF.
;; Set up recipient blocks for loop
MOVE N,SAVEN ;n := starting recipient host
MOVEI O,HSTRCP(N) ;o := start of recipient list
CALL NXTRCP ;Next recipient
IFNSK.
CALL $CLOSF
RETSKP ;No recipients???
ENDIF.
DO.
CALL RSTRCP ;Reset error flags from other tries
SETZM TMPBUF ;Clear start of buffer
MOVE A,[TMPBUF,,TMPBUF+1]
BLT A,TMPBUF+MXPBLN-1 ;Clear it out for the length of a packet
MOVX A,300 ;Get packet type for ether send
STOR A,PUPTYP ;Save it
MOVE A,[POINT 8,PBCONT+TMPBUF] ;Get dest ptr
CALL PUPSDR ;Say who this send is from
MOVEI B,":" ;Colon
IDPB B,A ;Drop it in
CALL OUTRCP ;Copy string for net recipient
SKIPN GTDBLK+.GTDRD ;Doing MX?
IFSKP.
MOVX B,"%" ;Yes, shove in relay poop
BOUT% ;Probably this should have been done better
HRRO B,FRNHST
CALL OUTAHS
ENDIF.
MOVEI B,":" ;Colon
IDPB B,A ;Drop it in
CALL OUTMSG ;Add message text
FATAL <Unexpected +1 return from OUTMSG>
MOVEI B,(A) ;Compute address of last word
SUBI B,TMPBUF-1 ;Compute # 36-bit words used
LSH B,2 ;Convert to bytes
LSH A,-^D33 ;Get bytes not used in last word
SUBI B,(A) ;Compute Pup length
ADDI B,2 ;Include checksum
STOR B,PUPLEN ;Save length
HRRZ A,NETJFN ;Get JFN back
TXO A,PU%CHK ;Compute checksum
MOVE B,[MXPBLN,,TMPBUF] ;Max length, from buffer
PUPO% ;Send it out
IFJER.
CALL $CLOSF ;Close output JFN
CALLRET ADEADH ;Random lossage
ENDIF.
HRRZ A,NETJFN ;Get JFN again
TXO A,PU%CHK!PU%TIM ;Checksum, with timeout
MOVX C,PUPSTM ;Waiting for up to a minute
PUPI% ;Read it back in
IFJER.
CALL $CLOSF ;Close JFN
CALLRET ADEADH ;Random lossage
ENDIF.
LOAD A,PUPTYP ;Get type
CAIN A,301 ;Success?
IFSKP.
LOAD B,PUPLEN ;Get length of Pup
SUBI B,MNPLEN ;Minus minimum number is length of error string
IFE. B ;If we have nothing
HRROI B,[ASCIZ/Unknown network error/] ;Make up a string
ELSE.
MOVE B,[POINT 8,PBCONT+TMPBUF] ;Get pointer to error
ADJBP A,B ;Point to end of error message
SETZ C, ;Get a null
IDPB C,A ;Drop it in at end of string
ENDIF.
HRROI A,STRBUF ;Into string buffer
SETZ C, ;Ending on null
SOUT% ;Copy reason for failure
MOVX B,FR%FAI!FR%ERM ;Permanent failure with text message
CALL STEMSG ;Remember lossage for recipient
ENDIF.
CALL NXTRCP ;Find another recipient
EXIT. ;No more
LOOP. ;Do next
ENDDO.
CALL $CLOSF ;Flush the JFN
MOVE A,MSGDOP(M) ;Get back delivery options
CAIE A,D%SAML ;Send and mail?
RETSKP ;No, done sending
MOVE N,SAVEN ;n := starting recipient host
MOVEI O,HSTRCP(N) ;o := start of recipient list
ENDIF.
MOVE A,[POINT 7,STRBUF] ;a := ptr to net file name str
MOVEI B,[ASCIZ/PUP:!J./] ;Output device and local host part
CALL MOVSTR
HLRZ B,PUPADR ;b := dest subnet #
MOVX C,^D8 ;Octal output
NOUT%
ERJMP R
MOVEI B,[ASCIZ/#/] ;Add a #
CALL MOVSTR
HRRZ B,PUPADR ;b := dest host #
NOUT%
ERJMP R
MOVEI B,[ASCIZ/#/] ;Add another #
CALL MOVSTR
MOVEI B,[ASCIZ/0+Mail/] ;And finish with the "mail" socket
CALL MOVST0 ;(ASCIZ)
MOVX A,GJ%OLD!GJ%SHT ;Old, short form, name from string
HRROI B,STRBUF
GTJFN% ;Get a JFN for the port
ERJMP ADEADH ;Fail
MOVEM A,NETJFN ;Ok, save JFN
MOVX B,<<FLD ^D8,OF%BSZ>!<FLD 1,OF%MOD>!OF%RD!OF%WR>
OPENF% ;Initiate rendezvous
IFJER.
MOVE A,NETJFN ;a := output JFN
RLJFN% ;Release it
JWARN
SETZM NETJFN
CALLRET ADEADH
ENDIF.
MOVEI A,PUPTIM ;Set time-out count (user/daemon)
SKIPE DAEMNP
MOVEI A,PUPDTM
MOVEM A,ICPTIM
DO.
MOVE A,NETJFN ;a := net JFN
SETZ C, ;No addresses returned
GDSTS%
IFNJE.
ANDI B,17 ;Isolate port state in b
CAIN B,P%OPEN ;State = OPN ?
EXIT. ;Yes, have connection
CAIN B,P%RFCO ;State = RFC out ?
SKIPE CTGCNT ;Yes, ^G abort?
ANSKP.
MOVX A,^D100 ;No, RFC pending, a := 100 msec
MOVNI B,(A) ;Time-out expired?
ADDB B,ICPTIM
ANDG. B
DISMS% ;No, wait 100 msec
LOOP.
ENDIF.
CALL $CLOSF ;Close it
CALLRET ADEADH ;Add to dead host list
ENDDO.
SETZM CTGCNT ;Clear ^G abort flag
MOVE A,NETJFN ;a := transmit JFN
MOVX B,.MOEOF ;b := "mark" MTOPR% fct
MOVX C,STMAIL ;Start property list transfer
MTOPR%
ERJMP PUPJER ;Just in case
TXO F,FP%BKA ;Show sender property not sent
DO.
CALL NXTRCP ;Get the next recipient
EXIT. ;No more
CALL RSTRCP ;Reset error flags from other tries
MOVE A,[POINT 7,STRBUF] ;a := place for temp string
MOVEI B,[ASCIZ/((/] ;Start property punctuation
CALL MOVSTR
TXZN F,FP%BKA ;Sender property already sent?
IFSKP.
MOVEI B,[ASCIZ/End-of-Line-Convention CRLF)(Sender /]
CALL MOVSTR
CALL PUPSDR ;Output string for sender
MOVEI B,[ASCIZ/)(/] ;Finish this property entry and start another
CALL MOVSTR
ENDIF.
MOVEI B,[ASCIZ/Mailbox /] ;Start mailbox property entry
CALL MOVSTR
CALL OUTRCP ;Output this recipient's name
SKIPN GTDBLK+.GTDRD ;Doing MX?
IFSKP.
MOVX B,"%" ;Yes, shove in relay poop
BOUT% ;Probably this should have been done better
HRRO B,FRNHST
CALL OUTAHS
ENDIF.
MOVEI B,[ASCIZ/))/] ;End this property entry
CALL MOVST0
HRRZ A,NETJFN ;a := output JFN
HRROI B,STRBUF ;b := string just built
SETZ C,
SOUT% ;Send it off
ERJMP PUPJER
LOOP. ;Do all the recipients
ENDDO.
MOVE A,NETJFN ;a := transmit JFN
MOVX B,.MOEOF ;b := "mark" MTOPR% fct
MOVX C,EOCMRK ;End our transmission
MTOPR%
ERJMP PUPJER ;Just in case
CALL RPLYP ;Get the remote reply
IFSKP.
MOVE A,NETJFN ;a := transmit JFN
MOVX B,.MOEOF ;b := "mark" MTOPR% fct
MOVX C,HEREFL ;Good, so here comes the mail file...
MTOPR%
ERJMP PUPJER ;Just in case
CALL OUTMSG ;Output the mail text
JRST PUPJER ;+1, error, close up shop
MOVE A,NETJFN ;a := transmit JFN
MOVX B,.MOEOF ;b := "mark" MTOPR% fct
MOVX C,YESMRK ;End our transmission
MTOPR%
ERJMP PUPJER ;Just in case
SETZB B,C ;Yes code
BOUT%
ERJMP PUPJER
HRROI B,[ASCIZ/End of mail text./]
SOUT%
ERJMP PUPJER
MOVX B,.MOEOF ;b := "mark" MTOPR% fct
MOVX C,EOCMRK ;End our transmission
MTOPR%
ERJMP PUPJER ;Just in case
CALL RPLYP ;Get the remote response
ANSKP.
CALL $CLOSF ;Close it - take care of data error
HRROI A,STRBUF ;Print reply text
CIETYP < %1W>
HRRZS B ;b := starting mark
CAIN B,YESMRK ;Mail OK?
IFSKP.
MOVX B,FR%TMP!FR%ERM ;Treat as temp, save error text
CALL STUMSG ;Update user errors
ENDIF.
ELSE.
CALL PUPBRT ;Server barfed, abort connection
ENDIF.
RETSKP ;Return success
ENDSV.
;;;Say who this is from
PUPSDR: SKIPN D,MSGSDR(M) ;d := adr of sender host entry block
FATAL <No sender block set up>
HRRZ C,HSTRCP(D) ;c := adr of sender "recipient" entry block
MOVE B,RCPBPT(C) ;b,c := sender name ptr/-byte count
MOVN C,RCPCNT(C)
SOUT%
PUSH P,A ;Save destination
HRRO A,HSTHST(D) ;Pointer to sender host
CALL $PUPSN ;Recognized to Pup world?
IFSKP.
POP P,A ;Restore destination BP
MOVEI B,"@" ;Success, punctuate
IDPB B,A
HRRO B,HSTHST(D) ;Output name in absolute form
CALLRET OUTAHS ;That's all for this sender
ENDIF.
POP P,A ;Restore destination BP
MOVE B,HSTHST(D) ;Get host pointer
CAIN B,LCLNAM ;If local name, don't need extra path
IFSKP.
MOVEI B,"%" ;Use kludgy routing to make sure destination
IDPB B,A ; doesn't choke on unknown sender host
HRRO B,HSTHST(D) ;b := local host
SOUT% ;Output it in relative form
ENDIF. ;Fall out to addition of local name
;; Sender not given, on local host, or routed with "%".
;; Add at-sign and Pup name for local host.
MOVEI B,"@" ;Punctuate
IDPB B,A
HRROI B,LCLNCN ;Output absolute local host name
CALLRET OUTAHS ;Return after adding host name
;;;JSYS error while sending mail
PUPJER: CALL NETJER ;Get JSYS error string
JRST PUPBRT ;Abort connection
;;;JSYS error in a subroutine
PUPJEX: TMOCLR ;This may be needed
CALL NETJER ;Get last JSYS error
MOVE P,SAVEP ;Reset the stack
JRST PUPBRT
;;;Error in a subroutine, text of error in B
PUPERX: TMOCLR ;This may be needed
MOVE A,[POINT 7,STRBUF]
CALL MOVST0 ;Create error string
PUPERY: MOVE A,[POINT 7,STRBUF] ;Here when STRBUF set up
CIETYP < %1W
> ;CRLF and text
MOVX B,FR%TMP!FR%ERM ;Save error info for dequeue
CALL STUMSG ;Update user errors
MOVE P,SAVEP ;Reset the stack
; JRST PUPBRT
;;;Here to abort connection
PUPBRT: HRRZ A,NETJFN ;a := output JFN
MOVEI B,.MOSAB ;Abort function
SETZ C, ;No code assigned
HRROI D,[ASCIZ/Mail transfer aborted/] ;Abort text
MTOPR% ;Abort the connection
ERJMP .+1 ;Just in case
CALLRET $CLOSF ;Close the connection
; Routine to handle remote replies
; Entry: Remote response expected
; Call: CALL RPLYP
; Return: +1 if hard failure blocking us from continuing
; +2 if all ok to proceed
RPLYP: STKVAR <RPLMRK,RPLREP>
DO.
CALL RSPPUP ;Wait for his reply
IFNSK.
MOVEM B,RPLMRK ;Error reply, save end mark,,start mark
MOVEM C,RPLREP ;And the reply code
HRRZ A,RPLMRK ;Get start mark
CAIE A,NOMRK ;"No" mark?
IFSKP.
HRROI A,STRBUF ;Output error string
CIETYP < %1W>
MOVX B,FR%TMP!FR%ERM ;Assume temporary problem
CAIE C,41 ;Bad "mailbox" property syntax?
CAIN C,42 ;Or "sender" property syntax?
MOVX B,FR%FAI!FR%ERM ;Yes, permanent error
CAIE C,40 ;All mailboxes bad?
CAIN C,110 ;Permanent file system problem?
MOVX B,FR%FAI!FR%ERM ;Yes, permanent error
CALLRET STUMSG ;Update user msgs
ENDIF.
CAIE A,-1 ;"Timeout mark"?
IFSKP.
HRROI A,STRBUF ;Yes, output error string
CIETYP < %1W>
MOVX B,FR%TMP!FR%ERM ;Assume temporary problem
CALLRET STUMSG ;Update user msgs
ENDIF.
CAIN A,MBXEXC ;"Mailbox exception" mark?
IFSKP.
HRROI A,STRBUF ;No, some strange lossage
CIETYP < %1W>
MOVX B,FR%FAI!FR%ERM ;Permanent error
CALLRET STUMSG ;Update user msgs
ENDIF.
MOVE A,[POINT 7,STRBUF] ;a := ptr into reply string
SETZ B, ;b := start of "index" code
DO.
ILDB D,A ;d := char
CAIL D,"0" ;Digit?
CAILE D,"9"
EXIT. ;No, analyze what we have
IMULI B,^D10 ;Form decimal value
ADDI B,-"0"(D)
LOOP.
ENDDO.
CIETYP < %1W> ;Type msg for user
MOVE N,SAVEN ;n := starting recipient host
MOVEI O,HSTRCP(N) ;o := start of recipient list
IFLE. B
HRROI A,[ASCIZ/Server bug: Impossible mailbox exception index/]
CIETYP < %1W>
MOVX B,FR%FAI!FR%ERM ;Assume temporary problem
CALLRET STUMSG
ENDIF.
DO.
CALL NXTRCP ;No, get the next one
IFNSK.
HRROI A,[ASCIZ/Server bug: Mailbox exception index out of range/]
CIETYP < %1W>
MOVX B,FR%FAI!FR%ERM ;Assume temporary problem
CALLRET STUMSG
ENDIF.
SOJG B,TOP. ;Index down to our man
ENDDO.
MOVX B,FR%TMP!FR%ERM ;Assume temporary failure
SKIPN C,RPLREP ;c := reply code
IFSKP.
CAIE C,3 ;No, transient error?
MOVX B,FR%FAI!FR%ERM ;No, assume permanent error
ENDIF.
CALL STEMSG ;Install the error flags and message
ENDIF.
HLRZ A,B ;a := ending mark type
CAIE A,EOCMRK ;EOC?
LOOP. ;No, get the rest
ENDDO.
RETSKP
ENDSV.
; Routine to wait for a response from the Ethernet
; Entry: connection opened
; Call: CALL RSPPUP
; Return: +1, negative reply or timeout
; +2, positive reply
; b = end mark,,start mark, c = reply code, strbuf = text
; If the expected mark/code/text sequence is violated, a mark type of
; 0 is returned. The terminating mark is left set.
RSPPUP: STKVAR <MRKTYP,MRKCOD>
SETZM STRBUF ;Clear reply text
TMOSET(^D120,PUPTMO) ;Max 2 mins for a reply
CALL RCVCH ;Better have a mark now...
CALL CLMARK ;OK, clear the mark
JSP B,RSPPER ;No mark, sequence error
MOVEM B,MRKTYP ;Save the starting mark
CALL RCVCH ;Now read the code value
JSP B,RSPPER ;Mark - sequence error
MOVEM B,MRKCOD ;Save the code
HRROI B,STRBUF ;b := ptr to receive the text
MOVX C,<5*STRBSZ>-1 ;c := max byte count
SETZ D, ;Or terminate on null
SIN%
ERJMP .+1
IFE. C
MOVEI B,[ASCIZ/Pup too long/]
JRST PUPERX
ENDIF.
CALL RCVCH0 ;Check the termination
TRNA ;Mark ends the text
JSP B,RSPPER ;No mark, fail
HRLM B,MRKTYP ;Save it
TMOCLR ;No more time out
CAIE B,EOCMRK ;Last one EOC?
IFSKP.
CALL CLMARK ;Yes, clear the last mark
JSP B,RSPPER ;None, bomb out
CAIE B,EOCMRK ;Got one, better be EOC
JSP B,RSPPER ;No, bomb out
ENDIF.
MOVE C,MRKCOD ;c := reply code
MOVE B,MRKTYP ;b := end mark,,start mark
HRRZ A,B ;a := start mark
CALL PUPDBG ;Print text if debugging
CAIE A,YESMRK ;Yes mark?
RET ;No, fail return
RETSKP ;Success return
ENDSV.
; Here when time-out on reply wait. Returns error msg in STRBUF and
; dummy ending marks.
PUPTMO: DMOVE A,[POINT 7,STRBUF
[ASCIZ/Connection timed-out/]]
CALL MOVST0 ;Set up an error string
TMOCLR ;No more time out
SETOB B,C ;Set timeout code in return AC's
CALLRET PUPDBG ;Print text if debugging and return
; Here on random Pup protocol error
; JSP B,RSPPER
RSPPER: STKVAR <RSPEPC>
MOVEM B,RSPEPC ;Save error PC
DMOVE A,[POINT 7,STRBUF
[ASCIZ/Pup protocol error, PC=/]]
CALL MOVSTR ;Set up an error string
HRRZ B,RSPEPC ;Retrieve PC
MOVX C,^D8 ;Octal output
NOUT% ;Put PC in error reply
JFATAL
TMOCLR ;No more time out
SETZB B,C ;Response error, clear return ac's
; CALLRET PUPDBG ;Print text if debugging and return
; Routine to print Ethernet reply text in debug mode
; Entry: strbuf = adr of reply text
; b = end mark,,start mark
; c = reply code
; Call: CALL PUPDBG
; Return: +1 always, prints only if DEBUGP non-zero
PUPDBG: SKIPN DEBUGP ;Debugging network protocol?
RET ;No
SAVEAC <A,B,D>
HRROI A,STRBUF ;a := reply text
HLRZ D,B ;d := end mark
HRRZS B ;b := start mark
CETYPE < PUP: [%2O] %3O %1W [%4O]> ;CRLF and text
RET
; Fetch a character from the remote host.
; Entry: NETJFN = receive JFN
; Call: CALL RCVCH
; Return: +1, mark encountered. b = mark type
; +2, b = char received
RCVCH: HRRZ A,NETJFN ;a := receive JFN
BIN% ;b := next input char
IFNJE.
CAIE B,.CHNUL ;Null byte?
RETSKP ;No, got a char - return +2
ENDIF.
RCVCH0: CALL CHKMRK ;Check for mark state
IFSKP.
MOVEI B,.MORMK ;Read mark type
MTOPR%
ERJMP PUPJEX ;Can't do much with this
MOVE B,C ;b := mark type
RET ;Return +1
ENDIF.
ANDI B,17 ;Isolate port state
CAIE B,P%ABRT ;Abort?
IFSKP.
MOVEI B,.MORAB ;Yes, get the abort reason
HRROI D,STRBUF
MTOPR%
ERJMP PUPJEX ;Just in case
JRST PUPERY ;And close things out
ENDIF.
MOVX B,.CHNUL ;Just null char -- return it
RETSKP
; Routine to clear a mark state
; Entry: NETJFN = receive JFN
; Call: CALL CLMARK
; Return: +1, no mark set
; +2, mark cleared, b = type
CLMARK: CALL CHKMRK ;Check for mark state
RET ;None
TXZ B,1B4 ;Mark present, clear it
SDSTS% ;A Mark, clear it
MOVEI B,.MORMK ;Read mark type
MTOPR%
ERJMP PUPJEX ;Just in case
MOVE B,C ;b := mark type
RETSKP ;Return +2
; Routine to check for mark input state
; Entry: NETJFN = receive JFN
; Call: CALL CHKMRK
; Return: +1, no mark
; +2, mark present, b = status
CHKMRK: MOVE A,NETJFN ;a := receive JFN
SETZ C,
GDSTS% ;Check state of connection
IFXN. B,1B5 ;EOF?
MOVEI B,[ASCIZ/Pup connection EOF/]
CALLRET PUPERX ;Abort and close the connection
ENDIF.
TXNN B,1B4 ;Mark?
RET
RETSKP ;Yes, skip return
SUBTTL Special routines
;;; Send message in M to Special host in E
; B/ Host name to connect to
; C/ Host number to use
SPCSND: STKVAR <SPCPTR,SPCADR,<SPCLCL,^D13>,SPCHPT,DSTHPT>
MOVEM A,DSTHPT ;Save ultimate host pointer
MOVEM B,SPCPTR ;Save host pointer
MOVEM C,SPCADR ;And address
HRROI A,LCLNCN ;Local name for this network
SETO B, ;Output local host
CALL $SPCNS
FATAL (Can't get Special local host name)
HRROI A,SPCLCL ;Make absolute copy of local name string
HRROI B,LCLNCN
CALL OUTAHS
MOVE A,SPCPTR ;Get immediate destination
MOVE B,DSTHPT ;Get ultimate destination host pointer
CALL GENHDR ;Generate headers
HRROI A,STRBUF ;Output directory name
MOVE B,SPCADR ;From Special host (a.k.a. directory) number
DIRST%
ERJMP ADEADH ;Failed
MOVEI B,[ASCIZ/-MAIL./] ;Filename of outgoing mail
CALL MOVSTR
PUSH P,A ;Save string poiter
GTAD% ;Get system date/time
MOVE B,A ;Output it in octal
POP P,A
MOVX C,^D8
NOUT%
JFATAL
AOS B,NXTSEQ ;Get next unique number
MOVNS B ;With hyphen...output it too
NOUT%
JFATAL
HRROI B,[ASCIZ/.-1;P777700/] ;Next generation, protection 777700
CALL MOVST0
MOVX A,GJ%SHT ;Get a JFN on it...
HRROI B,STRBUF
GTJFN%
ERJMP ADEADH ;Failed completely
MOVEM A,NETJFN
MOVX B,<<FLD ^D7,OF%BSZ>!OF%WR>
OPENF%
IFJER.
MOVE A,NETJFN
RLJFN%
JWARN
CALLRET ADEADH
ENDIF.
SKIPN MSGRPT(M) ;Have a return path?
IFSKP.
MOVEI B,"@" ;Yes, must prepend local host as part
BOUT% ; of source route. Output an at
HRROI B,SPCLCL ;Local host name
SETZ C,
SOUT%
MOVE B,MSGRPT(M) ;Make pointer to return path
HRLI B,(<POINT 7,>)
ILDB B,B ;Get first character of return path
CAIE B,"@" ;Additional source routing specification seen?
SKIPA B,[":"] ;No, use colon to terminate source routing
MOVEI B,"," ;Else must use comma for continuation
BOUT% ;Output the character
MOVE B,MSGRPT(M) ;Now output return path
HRLI B,(<POINT 7,>)
SOUT%
ELSE.
HRROI A,STRBUF ;Output to recipient buffer
MOVE D,MSGSDR(M) ;D := addr of sender host entry block
HRRZ C,HSTRCP(D) ;C := adr of recipient entry block
MOVE B,RCPBPT(C) ;B,C := sender name ptr/-byte count
MOVN C,RCPCNT(C)
SOUT%
HRRZ B,HSTHST(D) ;B := sender host pointer
CAIN B,LCLNAM ;Is it our host?
MOVEI B,SPCLCL ;Yes, use canonical form
MOVEM B,SPCHPT ;Save host pointer
CAIN B,SPCLCL ;Is it me?
IFSKP.
MOVEI B,"%" ;Punctuate
IDPB B,A
MOVEI B,SPCLCL ;Set up local name
EXCH B,SPCHPT ;Restore host
HRROS B
SOUT%
ENDIF.
MOVE C,A ;Save termination
MOVE A,NETJFN ;Restore JFN
MOVE B,[POINT 7,STRBUF]
CALL QOTSTR ;Output it quoted
FATAL (Special net QOTSTR failed)
MOVEI B,"@" ;Punctuate
BOUT%
HRRO B,SPCHPT ;Restore host
SOUT% ;Output host name
ENDIF.
HRROI B,CRLF0 ;Now start recipient list
SOUT% ;Delimiting with first CRLF
DO.
CALL NXTRCP ;Get next recipient
EXIT. ;No, done with recipients
CALL RSTRCP ;Reset error flags from other tries
SETZM TMPBUF ;Clear reply string buffer
MOVE A,NETJFN ;Get back JFN
CALL OUTRCP ;Output recipient
SKIPN GTDBLK+.GTDRD ;Doing MX?
IFSKP.
MOVX B,"%" ;Yes, shove in relay poop
BOUT% ;Probably this should have been done better
HRRO B,FRNHST
CALL OUTAHS
ENDIF.
HRROI B,CRLF0 ;Newline
SETZ C,
SOUT%
LOOP.
ENDDO.
MOVX B,.CHFFD ;End of recipients
BOUT%
HRRO B,MSGNHD(M) ;Pointer to headers
HLRZ C,MSGNHD(M) ;Size of headers
MOVNS C
SOUT% ;Output headers
MOVE B,MSGTXT(M) ;Pointer/size of message body
MOVN C,MSGTCN(M)
SOUT% ;Output message body
CLOSF% ;Close queue file
JWARN <Error closing Special queue file>
RETSKP
ENDSV.
SUBTTL JSYS jacket routines
; Routine to close a net connection. If the connection has a data
; error, a second CLOSF% is done to abort the JFN.
; Entry: NETJFN/ net JFN
; Call: CALL $CLOSF
; Return: +1 always
$CLOSF: SAVEAC <A,B> ;Preserve these guys
STKVAR <CLZJFN> ;JFN to close
SKIPN A,NETJFN ;Have JFN?
RET ;No, just return
MOVEM A,CLZJFN ;Save the JFN to close
SETZM NETJFN ;And clear the cell
GTSTS% ;Get its status
ERJMP .+1 ;Ignore error
JXE B,GS%NAM,R ;This shouldn't happen, but check anyway
IFXE. B,GS%OPN ;JFN open?
RLJFN% ;This is easy - just flush the JFN
JWARN <Error releasing network JFN> ;Lost??
RET
ENDIF.
DO.
TMOSET(^D60,ENDLP.) ;Prevent hanging
CLOSF%
IFNJE.
TMOCLR ;Succeeded, clear timer and return
RET
ENDIF.
ENDDO.
TMOCLR
MOVE A,CLZJFN ;Try again
TXO A,CZ%ABT ;Abort it without waiting for anything
CLOSF%
JWARN <Error closing net connection>
RET
ENDSV.
; Versions of BOUT%, SOUT%, and SOUTR% which output to primary output if
;DEBUGP is set, to allow protocol debugging.
$BOUT: SKIPE DEBUGP ;If debugging, output to primary output too
CALL DBGBOU
JSP CX,$TIMER ;Put a timer on this if necessary
BOUT%
ERJMP R
RETSKP
$SOUT: SKIPE DEBUGP ;If debugging, output to primary output too
CALL DBGSOU
JSP CX,$TIMER ;Put a timer on this if necessary
SOUT%
ERJMP R
RETSKP
$SOUTR: SKIPE DEBUGP ;If debugging, output to primary output too
CALL DBGSOU
JSP CX,$TIMER ;Put a timer on this if necessary
SOUTR%
ERJMP R
RETSKP
$TIMER: SKIPGE INTOK ;Is there a timer set up already?
JRST (CX) ;Yes, use it then
TMOSET(MAXTMB,TIMOUT) ;Wait 5 minutes before giving up
CALL (CX) ;Do the code
TRNA ;+1 Return
AOS (P) ;+2 Return
TMOCLR ;Clear the timer
RET ;Return +1/+2
TIMOUT: TMOCLR ;Clear timeout
SAVEAC <A,B>
MOVX A,.FHSLF ;Set last error
MOVX B,TTMSX1 ;"Unable to send within timeout interval"
SETER%
ERJMP .+1
RET
DBGBOU: SAVEAC <A>
MOVX A,.PRIOU
BOUT%
RET
DBGSOU: SAVEAC <A,B,C,D>
MOVX A,.PRIOU
SOUT%
RET
SUBTTL General-purpose subroutines
;;;Move a string from B to A
MOVSTR: HRLI B,(<POINT 7,0>)
MOVST1::DO.
ILDB D,B
IFN. D
IDPB D,A
LOOP.
ENDIF.
ENDDO.
RET
;;;Move string and terminating null
MOVST0: HRLI B,(<POINT 7,0>)
MOVST2: SAVEAC <D>
DO.
ILDB D,B
IDPB D,A
JUMPN D,TOP.
ENDDO.
RET
;;; Make a copy of string in A, return address in B, count in C
CPYSTR::PUSH P,A ;Save address
HRLI A,(<POINT 7,0>)
SETZ C,
DO.
ILDB D,A
JUMPE D,ENDLP.
AOJA C,TOP.
ENDDO.
MOVEI A,5(C) ;Account for null and round wd cnt up
IDIVI A,5
CALL ALCBLK
FATAL <Memory exhausted>
HRL B,(P)
HRRZM B,(P)
ADDI A,(B)
BLT B,-1(A)
POP P,B
RET
SUBTTL Interrupt stuff
;;;Here to initialize the timer, called via JSP CX,SETTIM. Note that A,B,C
;;;are clobbered!
SETTIM: MOVE A,[.FHSLF,,.TIMEL] ;Tick the timer
MOVX B,<TMRTCK*^D1000> ;Every TMRTCK seconds
SETZ C, ;On channel 0
TIMER%
ERJMP .+1
JRST (CX)
;;;Here on timer interrupt
TIMINT: MOVEM 17,INTACS+17 ;Save ACs
MOVEI 17,INTACS
BLT 17,INTACS+16
AOSE TIMKIL ;If we weren't asked to kill the clock
JSP CX,SETTIM ;Reinitialize the timer
AOSE INTOK ;Should time out now?
IFSKP.
SKIPN A,TIMLOC ;Get time-out routine
FATAL <No time-out PC set>
MOVEM A,INTPC ;Set it
MOVE P,TIMRTP ;Reset stack ptr
ENDIF.
MOVSI 17,INTACS ;Restore ACs
BLT 17,17
DEBRK%
;;; Here on ^G interrupt
CTGINT: AOS CTGCNT
DEBRK%
SUBTTL IPCF handling
;Here to initialize for IPCF - we want to be known as [SYSTEM]MMAILR
IPCINI: SKIPE IPCFON ;Has IPCF been set up yet?
RET ;Yes, don't do it again
SETZM IPCNT ;Zero count of MSEND%s we've done
SETZM PIDGET+.IPCFS ;Indicate we want a fresh PID
DO.
MOVE A,IPCNT ;Get the count
CAIG A,5 ;Too many?
IFSKP.
WARN <Unable to send to <SYSTEM>INFO>
RET
ENDIF.
SETZ A, ;Assume we have a PID
SKIPN PIDGET+.IPCFS ;Do we?
MOVX A,IP%CPD ;No
MOVEM A,PIDGET+.IPCFL
SETZM PIDGET+.IPCFR ;Send to INFO
MOVEI A,.IPCFP+1 ;Length of packet
MOVEI B,PIDGET ;Packet address
MSEND%
IFJER.
AOS B,IPCNT ;Failed!
TXNN B,1 ;Warn only every other try
JWARN <Trying to send to INFO...>
SETZM PIDGET+.IPCFS ;Clear possible bad PID
MOVEI A,^D1000 ;Wait a while for things to settle
DISMS%
LOOP.
ENDIF.
AOS IPCNT ;Increment count
DO.
SETZB C+.IPCFL,C+.IPCFS ;No flags, any sender
MOVE C+.IPCFR,PIDGET+.IPCFS ;Get our PID
MOVE C+.IPCFP,[IPCFBL,,IPCFBF] ;Where to read into
MOVEI A,.IPCFP-.IPCFL+1 ;Get response from <SYSTEM>INFO
MOVEI B,C
MRECV%
IFJER.
JWARN <MRECV% from <SYSTEM>INFO failed>
RET
ENDIF.
LOAD D,IP%CFC,C+.IPCFL
CAIE D,.IPCCC ;From SYSTEM?
CAIN D,.IPCCF ;Or INFO?
CAIA
LOOP. ;No, toss it
ENDDO.
TXNE C+.IPCFL,IP%CFM ;Delivered?
LOOP. ;No, try again
ENDDO.
IFXN. C+.IPCFL,IP%CFE ;See if any errors
WARN <Error in message from <SYSTEM>INFO>
RET
ENDIF.
SETZM IPCFOK ;Disable IPCF interrupts
SETZM NOSLEP ;And sleeps
MOVEI A,.FHSLF ;Enable the channel
MOVX B,1B<IPCHAN>
AIC%
MOVEI C,.MUPIC ;Enable for IPCF interrupts
MOVE D,PIDGET+.IPCFS ;For our new PID
MOVEI E,IPCHAN ;On this channel
MOVEI A,E-C+1 ;Length of arg block
MOVEI B,C ;Location
MUTIL%
JFATAL <Could not enable IPCF interrupts>
SETOM IPCFON ;Note IPCF set up
RET
; Here when an IPCF packet is received
; Note that since we only get interrupted when the queue goes from empty
; to non-empty, we must ensure that the queue is empty before dismissing
; the interrupt! No JWARNs may be done here as we may be in a UUO when this
; happens
IPCINT: MOVEM 17,INTACS+17 ;Save ACs
MOVEI 17,INTACS
BLT 17,INTACS+16
DO.
JSP C,IPCHEK ;Check the queue
EXIT. ;Done, depart
MOVE A,IPCFBF+.IPCFL+1 ;Check flags
IFXN. A,IP%CFV ;Page request?
MOVX A,IP%CFB!IP%CFV ;Don't block and read a page
MOVEM A,IPCFBF+.IPCFL
SETZM IPCFBF+.IPCFS ;Any sender
MOVE A,PIDGET+.IPCFS ;Set up our PID
MOVEM A,IPCFBF+.IPCFR
MOVE A,[1000,,IPCPAG/1000] ;Read a page worth
MOVEM A,IPCFBF+.IPCFP
MOVX A,.IPCFP+1 ;Read the data
MOVEI B,IPCFBF
MRECV%
ERJMP .+1 ;MRECV% to read data page failed
LOOP.
ENDIF.
MOVX A,IP%CFB!IP%TTL ;Don't block and truncate
MOVEM A,IPCFMS+.IPCFL
SETZM IPCFMS+.IPCFS ;Any sender
MOVE A,PIDGET+.IPCFS ;Set up our PID
MOVEM A,IPCFMS+.IPCFR
MOVX A,.IPCFP+1 ;Now read the emssaage
MOVEI B,IPCFMS
MRECV%
ERJMP TOP. ;MRECV% to read IPCF message failed?
MOVE A,IPCFBF+.IPCI0 ;Get word 0 of user's request
CAME A,[SIXBIT/PICKUP/] ;Wakeup and reply?
IFSKP.
MOVX A,IP%CFO ;Yes, allow us to exceed send quota
MOVEM A,IPCFMS+.IPCFL
MOVE A,PIDGET+.IPCFS ;Get our PID
EXCH A,IPCFMS+.IPCFS ;From us
MOVEM A,IPCFMS+.IPCFR ;To him
SKIPL IPCFOK ;Were we sleeping?
SKIPA A,[SIXBIT/BUSY/] ;No, so say so
MOVE A,[SIXBIT/GOING/] ;Yes, tell him we're continuing
MOVEM A,IPCFBF+.IPCI0 ;Set the reply
MOVX A,.IPCFP+1 ;Send reply
MOVEI B,IPCFMS
MSEND%
ERJMP .+1 ;MSEND% to send reply failed
MOVE A,[SIXBIT/WAKEUP/] ;Fake a WAKEUP request
ENDIF.
CAME A,[SIXBIT/WAKEUP/] ;Just wakeup?
IFSKP.
SETOM NOSLEP ;Do not sleep next time around
AOSN IPCFOK ;Ok to interrupt?
AOS INTPC ;Yes, bump PC from DISMS%
ENDIF.
LOOP. ;And see if any more in queue
ENDDO.
MOVSI 17,INTACS ;Restore ACs
BLT 17,17
DEBRK% ;Dismiss interrupt
; Here to check for a packet, called by JSP C,IPCHEK
IPCHEK: MOVX A,.MUQRY ;Query function for MUTIL%
MOVEM A,IPCFBF
MOVE A,PIDGET+.IPCFS ;Query packets for our PID
MOVEM A,IPCFBF+1
MOVX A,.IPCFP+2 ;Get length
MOVEI B,IPCFBF ;Address
MUTIL%
ERJMP (C) ;MUTIL% failed -- no JWARN, may be interrupt
JRST 1(C) ;Got it, so win
; Here for wakeup interrupt to net fork
WAKTOP: MOVEI A,.FHSLF ;On self
MOVE B,[LEVTAB,,CHNTAB] ;With interrupt table
SIR% ;Set up interrupt system
EIR%
WAKINI: MOVEI A,.FHSLF ;If multiforking,
MOVX B,1B<WAKCHN> ;Need channel to wake up other forks
AIC% ;So activate it
RET
; Here for fork 1 to set up so fork 2 will be interrupted
WAKNET: SAVEAC <A,B> ;Don't mung registers
MOVX A,.FHSUP ;On the mother fork
MOVX B,1B<WAKCHN> ;With wakeup interrupt
IIC% ;Initiate interrupt condition
RET
WAKINT: MOVEM 17,INTACS+17 ;Save ACs
MOVEI 17,INTACS
BLT 17,INTACS+16
SKIPE FORKX ;Are we the top fork?
IFSKP.
MOVE A,FHTAB+NETFRK-1 ;Yes, get network daughter fork
MOVX B,1B<WAKCHN> ;And wakeup interrupt channel
IIC% ;Wake up the fork
ELSE.
SETOM NOSLEP ;Do not sleep next time around
AOSN IPCFOK ;Ok to interrupt?
AOS INTPC ;Yes, bump PC from DISMS%
ENDIF.
MOVSI 17,INTACS ;Restore ACs
BLT 17,17
DEBRK% ;Return from interrupt
SUBTTL UUO handler
; UUO enters here via JSR UUOH
UUOH0: AOSE INUUO ;Recursive call?
CALL CRASH ;No, crash
MOVEM 17,UUOACS+17 ;Save AC 17
MOVEI 17,UUOACS ;Save AC's 0-16
BLT 17,UUOACS+16
MOVE P,[IOWD NUPDL,UUOPDL] ;Set up local stack
PUSH P,UUOH ;Save the UUO PC for debugging
LDB A,[POINT 9,UUOLOC,8] ;a := opcode field
CAIL A,MXUUO ;UUO valid?
CALL CRASH ;No, die
CALL @UUOS(A) ;Dispatch to handler routine
SOS INUUO ;Reset the entry flag
POP P,UUOH ;Restore the UUO PC
MOVSI 17,UUOACS ;Restore ACs
BLT 17,17
JRSTF @UUOH ;Dismiss UUO
; UUO handler dispatch table
UUOS: CRASH ;UUO 0 is impossible
%TYPE
%ETYPE
%ERROR
MXUUO==.-UUOS ;Maximum UUO
%TYPE: SKIPN PRINTP
RET
CALL TYCRIF ;Check if we should do a CRLF
HRRO A,UUOLOC ;Get string
PSOUT%
RET
TYCRIF: SKIPE DAEMNP ;Daemon?
JRST DTYCRF ;Yes, different routine
MOVE A,UUOLOC ;Get instruction
TXNE A,<10,0> ;Wants CRLF all the time?
JRST CRLF ;Yes
TXNE A,<1,0> ;Wants fresh line?
JRST CRIF ;Yes
RET
DTYCRF: MOVE A,UUOLOC ;Get instruction
TXNN A,<11,0> ;Want a CRLF at any time?
RET ;No, continuation of previous message probably
TIMSMP: SAVEAC <A,B,C>
CALL CRLF1 ;Always CRLF to log file, RFPOS% unreliable
MOVEI A,.PRIOU ;Now timestamp output
SETO B,
SETZ C,
ODTIM%
ERJMP .+1
MOVX A,.CHSPC ;Space before text
PBOUT%
MOVX A,.FHSLF ;Get my primary JFN's
GPJFN%
AOJN B,R ;Don't write "MMailr (n)" if output to file
TMSG <MMailr (>
MOVE A,FORKX ;Output fork number
ADDI A,"0"
PBOUT%
TMSG <): >
RET
CRIF: SAVEAC <A,B>
MOVEI A,.PRIOU
RFPOS%
TXNE B,.RHALF ;If not at start of line,
CALL CRLF1 ;Type CRLF
RET
CRLF: SAVEAC <A>
CRLF1: HRROI A,CRLF0
PSOUT%
RET
CRLF0: ASCIZ/
/
%ERROR: SKIPN DAEMNP ;Different code if daemon
IFSKP.
MOVE B,UUOLOC ;Get instruction
IFXN. B,<<10,0>> ;Fatal error?
MOVX A,.FHSLF ;Be sure this gets printed
SETO B,
SPJFN%
SKIPN A,LOGJFN ;And close off log file if we can
IFSKP.
TXO A,CO%NRJ
CLOSF%
NOP
ENDIF.
SKIPN A,STAJFN ;Also nuke statistics file
ANSKP.
TXO A,CO%NRJ
CLOSF%
NOP
ENDIF.
CALL TIMSMP ;Timestamp output
ELSE.
CALL CRIF ;Get a fresh line
MOVE B,UUOLOC ;Get instruction
TXNE B,<10,0> ;Wants %?
SKIPA A,["?"] ;No
MOVEI A,"%"
PBOUT%
ENDIF.
MOVE B,UUOLOC
IFXN. B,.RHALF ;Any message to print?
CALL %ETYE0 ;Yes, print it out
MOVE B,UUOLOC ;And recover instruction
ENDIF.
IFXN. B,<<4,0>> ;Wants JSYS error message?
IFXN. B,.RHALF ;If a previous message, type delimiter
TMSG < - >
ENDIF.
MOVX A,.PRIOU
HRLOI B,.FHSLF ;This fork
SETZ C,
ERSTR%
ERJMP .+1
ERJMP .+1
MOVE B,UUOLOC ;See if primary message was given
IFXE. B,.RHALF
TMSG <, at > ;None, should give PC...
HRRZ T,UUOH ;Get PC of UUO caller
SUBI T,1
CALL SYMOUT
ENDIF.
ENDIF.
CALL CRLF ;Output CRLF
MOVE B,UUOLOC ;Get instruction
TXNE B,<10,0> ;Fatal error?
CALL CRASH
RET ;No, return to user
;;; Fatal errors
CRASH: MOVEM 17,FATACS+17 ;Save ACs at time of crash
MOVEI 17,FATACS
BLT 17,FATACS+16
MOVE 17,FATACS+17
SKIPE DAEMNP ;If not running as a daemon
IFSKP.
DO.
TMSG <?Fatal error - can't continue
>
HALTF% ;Just die
LOOP.
ENDDO.
ENDIF.
MOVX A,.FHSLF ;Be sure this gets printed
SETO B,
SPJFN%
SKIPN A,LOGJFN ;And close off log file if we can
IFSKP.
TXO A,CO%NRJ ;Don't flush yet to allow debug
CLOSF% ;Don't SETZM yet so dump has JFN
NOP
ENDIF.
SKIPN A,STAJFN ;Close statistics file
IFSKP.
TXO A,CO%NRJ ;Don't flush yet to allow debug
CLOSF% ;Don't SETZM yet so dump has JFN
NOP
ENDIF.
MOVX A,GJ%FOU!GJ%NEW!GJ%SHT
HRROI B,[ASCIZ/MAIL:MMAILR-CRASH-DUMP.EXE;P770000/]
GTJFN%
IFJER.
DO.
HALTF% ;Just die
TMSG <?Can't get crash dump file
>
LOOP.
ENDDO.
ENDIF.
MOVE B,A
CALL TIMSMP
TMSG <Fatal error - taking crash dump onto >
MOVX A,.PRIOU
SETZ C,
JFNS% ;Output name of the file
MOVE A,B
HRLI A,.FHSLF ;This fork
MOVE B,[777760,,20] ;Save all assigned nonzero memory
SAVE% ;Take the crash dump
IFJER.
TMSG < (failed)> ;Don't blow up if out of disk space
ENDIF.
RESET% ;Flush everything we were doing
TMSG < ...reloading in 5 minutes
>
SETOM TIMKIL ;Kill the clock
MOVE A,[5*^D60*^D1000] ;5 minutes
DISMS%
MOVX A,GJ%SHT!GJ%OLD
HRROI B,[ASCIZ/SYS:MMAILR.EXE/]
GTJFN%
IFJER.
MOVX A,GJ%SHT!GJ%OLD
HRROI B,[ASCIZ/SYSTEM:MMAILR.EXE/]
GTJFN%
IFJER.
DO.
TMSG <?Can't get MMAILR.EXE
>
HALTF% ;Just die
LOOP.
ENDDO.
ENDIF.
ENDIF.
HRRM A,RLDSLF ;Save JFN in reload routine
MOVSI P,RLDSLF ;Blt the reload rtn into acs
BLT P,P
SKIPN FORKX ;Top fork?
IFSKP.
HRRI %RLDFX,<FRKTAB-ENTVEC>-1 ;No, entry vector offset for daughter
ADD %RLDFX,FORKX ;Get fork index
ENDIF.
JRST %RLDSL
; Following is the ac routine used to reload ourselves
RLDSLF:
PHASE 0 ;Loc cntr := 0
.FHSLF,,0 ;0 GET arg
-1 ;1 PMAP% arg to clear memory
.FHSLF,,0 ;2 PMAP% arg to clear memory
0 ;3 PMAP% dummy access arg
1000 ;4 PMAP% cntr for all memory
%RLDSL:!PMAP% ;5 Entry to clear memory
ADDI B,1 ;6 Bump page ptr
SOJG D,%RLDSL ;7 PMAP% loop
MOVE A,F ;10 a := GET arg
GET% ;11
MOVEI A,.FHSLF ;12 a := our frk handle
CLZFF% ;13 Cleanup outstanding files
%RLDFX:!MOVEI B,0 ;14 Start at entry vec
SFRKV% ;15
HALTF% ;16 ???
0 ;17
DEPHASE
%FATL1: HALTF%
TMSG <?Can't continue
>
CALL CRASH
; Clever symbol table lookup routine. For details, read "Introduction to
;DECSYSTEM-20 Assembly Language Programming", by Ralph Gorin, published by
;Digital Press, 1981. Called with desired symbol in T.
SYMOUT: SETZB C,E ;No current program name or best symbol
MOVE D,116 ;Symbol table pointer
HLRO A,D
SUB D,A ;-Count,,ending address +1
SYMLUP: LDB A,[POINT 4,-2(D),3] ;Symbol type
JUMPE A,NXTSYM ;Program names are uninteresting
CAILE A,2 ;0=prog name, 1=global, 2=local
IFSKP.
MOVE A,-1(D) ;Value of the symbol
CAME A,T ;Exact match?
IFSKP.
MOVE E,D ;Yes, select it
JRST FNDSYM
ENDIF.
CAML A,T ;Smaller than value sought?
IFSKP.
SKIPE B,E ;Get best one so far if there is one
CAML A,-1(B) ;Compare to previous best
MOVE E,D ;Current symbol is best match so far
ENDIF.
ENDIF.
NXTSYM: ADD D,[2000000-2] ;Add 2 in the left, sub 2 in the right
JUMPL D,SYMLUP ;Loop unless control count is exhausted
SKIPN D,E ;Did we find anything helpful?
JRST OCTSYM
;Found an entry that looks close. See if it really is and if so use it
FNDSYM: MOVE A,T ;Desired value
SUB A,-1(D) ;Less symbol's value = offset
CAIL A,200 ;Is offset small enough?
IFSKP.
MOVE D,E ;Yes, get the symbol's address
MOVE A,-2(D) ;Symbol name
TXZ A,<MASKB 0,3> ;Clear flags
CALL SQZTYO ;Print symbol name
MOVE B,T ;Get desired value
SUB B,-1(D) ;Less this symbol's value
JUMPE B,R ;If no offset, don't print "+0"
MOVEI A,"+" ;Add + to the output line
PBOUT%
ELSE.
OCTSYM: MOVE B,T ;Here if PC must be in octal
ENDIF.
MOVX A,.PRIOU ;And copy numeric offset to output
MOVEI C,^D8
NOUT%
ERJMP R
RET
; Convert a 32-bit quantity in A from squoze to ASCII
SQZTYO: IDIVI A,50 ;divide by 50
PUSH P,B ;save remainder, a character
SKIPE A ;if A is now zero, unwind the stack
CALL SQZTYO ;call self again, reduce A
POP P,A ;get character
ADJBP A,[POINT 7,[ASCII/ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.$%/],6]
LDB A,A ;convert squoze code to ASCII
PBOUT%
RET
%ETYPE: SKIPN PRINTP
RET
CALL TYCRIF ;Type a CRLF maybe
%ETYE0: HRRZ N,UUOLOC
%ETYS0: HRLI N,(<POINT 7,0>) ;Get byte pointer to string
DO.
ILDB A,N ;Get char
IFN. A
CAIN A,"%" ;Escape code?
IFSKP.
PBOUT% ;No, just print it out
LOOP.
ENDIF.
SETZ O, ;Reset AC
DO.
ILDB A,N
CAIL A,"0" ;Is it part of addr spec?
CAILE A,"7"
IFSKP.
IMULI O,^D8 ;Yes, increment address
ADDI O,-"0"(A)
LOOP.
ENDIF.
ENDDO.
CAIG A,"Z" ;If within range of special codes
CAIGE A,"A"
IFSKP.
CALL @%ETYTB-"A"(A) ;Do code-dependent thing
ELSE.
CALL %ETYP0 ;Else output character as is
JUMPE A,ENDLP. ;If string terminated with "%" exit now
ENDIF.
LOOP.
ENDIF.
ENDDO.
RET
%ETYP0: PUSH P,A ;Here if function not defined, save character
MOVEI A,"%" ;Output leading %
PBOUT%
POP P,A ;Now output the losing character
PBOUT%
RET
%ETYTB: %ETYPA ;A - print time
%ETYPB ;B - print date
%ETYP0 ;C
%ETYPD ;D - print decimal
%ETYER ;E - error code
%ETYPF ;F - floating
%ETYP0 ;G
%ETYPH ;H - RH as octal
%ETYP0 ;I
%ETYPJ ;J - filename
REPEAT 4,<%ETYP0> ;K, L, M, N
%ETYPO ;O - octal
%ETYPP ;P - pluralizer
REPEAT 2,<%ETYP0> ;Q, R
%ETYPS ;S - string
%ETYPT ;T - date and time
%ETYPU ;U - user name
%ETYP0 ;V
%ETYPW ;W - string without "%" processing
REPEAT 3,<%ETYP0> ;X, Y, Z
%ETYPA: MOVX C,OT%NDA ;No day, just time
JRST %ETYB0
%ETYPT: TDZA C,C ;Both date and time
%ETYPB: MOVX C,OT%NTM ;No time, just day
%ETYB0: JUMPE O,.+2 ;If AC field spec'd
SKIPA B,UUOACS(O) ;Use it
SETO B, ;Else use now
MOVEI A,.PRIOU
ODTIM%
RET
%ETYPD: SKIPA C,[^D10] ;Decimal
%ETYPO: MOVEI C,^D8 ;Octal
MOVE B,UUOACS(O) ;Get data
%ETYO0: MOVEI A,.PRIOU
NOUT%
ERJMP .+1
RET
%ETYER: MOVEI A,.PRIOU
MOVSI B,.FHSLF ;This fork
HRR B,UUOACS(O) ;Get error code
SETZ C,
ERSTR%
ERJMP .+1
ERJMP .+1
RET
%ETYPF: MOVEI A,.PRIOU
MOVE B,UUOACS(O)
SETZ C,
FLOUT%
ERJMP .+1
RET
%ETYPH: MOVEI C,^D8
HRRZ B,UUOACS(O)
JRST %ETYO0
%ETYPJ: MOVEI A,.PRIOU
HRRZ B,UUOACS(O)
MOVE C,[001110,,1]
JFNS%
RET
%ETYPP: MOVEI A,"s"
MOVE B,UUOACS(O)
CAIE B,1
PBOUT% ;Make plural unless just one
RET
%ETYPS: PUSH P,N
SKIPE N,UUOACS(O)
CALL %ETYS0 ;Recursive call
CPOPNJ: POP P,N
RET
%ETYPU: MOVEI A,.PRIOU
MOVE B,UUOACS(O)
DIRST%
ERJMP .+1
RET
%ETYPW: MOVE A,UUOACS(O)
TXNN A,.LHALF
HRLI A,(<POINT 7,0>)
PSOUT%
RET
SUBTTL Utility Routines
;;;Helper routine for JSR SAVACS. MPP is necessary because some of the
;;;routines which use SAVACS are less than careful about making sure the
;;;stack context is the same as it was right after the JSR SAVACS call (e.g.
;;;some error returns fail to pop saved stuff on the stack). These should
;;;eventually be identified and fixed, then MPP can be flushed.
ACBASE==17 ;Base where AC0 resides on stack
;Reference saved AC's with AC-ACBASE(P)
SAVAC0: PUSH P,MPP ;Save former stack context save
ADJSP P,ACBASE ;Create room on the stack for our ACs
MOVEM ACBASE-1,(P) ;Save AC16 on stack
MOVEI ACBASE-1,-<ACBASE-1>(P) ;AC0 to lowest save area location
BLT ACBASE-1,-1(P) ;Save AC0-AC15
MOVE ACBASE-1,(P) ;Retrieve AC16
CALL [ MOVEM P,MPP ;Save current stack context
JRST @SAVACS] ;Call invoking routine
JRST SAVAR0 ;+0
JRST SAVAR1 ;+1
JRST SAVAR2 ;+2
JRST SAVAR3 ;+3
JRST SAVAR4 ;+4
JRST SAVAR5 ;+5
SAVAR6: AOS -<ACBASE+1>(P) ;+6, hopefully as hairy as we'll ever get!
SAVAR5: AOS -<ACBASE+1>(P) ;+5
SAVAR4: AOS -<ACBASE+1>(P) ;+4
SAVAR3: AOS -<ACBASE+1>(P) ;+3
SAVAR2: AOS -<ACBASE+1>(P) ;+2
SAVAR1: AOS -<ACBASE+1>(P) ;+1
SAVAR0: MOVSI ACBASE-1,-<ACBASE-1>(P) ;AC0 from lowest save area location
BLT ACBASE-1,ACBASE-1 ;Restore AC0-AC15
ADJSP P,-ACBASE ;Garbage collect stack location
POP P,MPP ;Restore former stack context save
RET ;Return to caller
; "Super" SFUST emulation.
; Entry: a = JFN
; b = ptr to author string
; Call: CALL .SFUST
; Return: +1, always
.SFUST: STKVAR <AUTJFN>
MOVEM A,AUTJFN ;Save JFN
MOVX A,.CHCNV ;Quote character
TXC B,.LHALF ;See if LH = -1
TXCN B,.LHALF
HRLI B,(<POINT 7,0>) ;Yes, set up as byte pointer
MOVE D,[POINT 7,FRMMSG] ;A convenient place to write it into
DO.
ILDB C,B
CAIE C,.CHCNV ;Quote?
IFSKP.
IDPB C,D ;Yes, next character is quoted already
ILDB C,B
IDPB C,D
LOOP.
ENDIF.
CAIL C,"a" ;Character lowercase?
CAILE C,"z"
CAIA
IDPB A,D ;Yes, quote it
IDPB C,D
JUMPN C,TOP.
ENDDO.
HRROI A,FRMMSG ;Remove relative domain
CALL $RMREL
MOVE A,AUTJFN ;Restore JFN
HRLI A,.SFLWR ;Set its writer
HRROI B,FRMMSG
SFUST%
ERJMP .+1
RET
ENDSV.
; Routine to fetch the write date/time of a file
; Entry: a = file JFN
; Call: CALL .GFWDT
; Return: +1, b = file write date/time
.GFWDT: SAVEAC <C>
MOVEI B,B ;Answer into b
MOVX C,<.RSWRT+1> ;Only the write date/time
RFTAD%
RET
; Routine to compare two strings ignoring case differences
; Entry: a,b = ptrs to strings
; Call: CALL STRCMP
; Return: +1, match failed
; +2, strings match
STRCMP: SAVEAC <C,D>
DO.
ILDB C,A ;c := next char from a
CAIL C,"a" ;Raise it if necessary
CAILE C,"z"
CAIA
SUBI C,"a"-"A"
ILDB D,B ;d := next char from b
CAIL D,"a" ;Raise it if necessary
CAILE D,"z"
CAIA
SUBI D,"a"-"A"
CAME C,D ;Same?
IFSKP.
JUMPN C,TOP. ;If not end of strings, continue
RETSKP ;Match, return +2
ENDIF.
ENDDO.
RET
; Routine to compare two strings ignoring case differences
; Entry: a = ptr to ASCIZ string
; b/c = ptr/len of string
; Call: CALL STRCAL
; Return: +1, match failed
; +2, strings match
STRCAL: ILDB T,A ;t,tt := next chars raised
JUMPE T,R ;If ended here, no match
CAIL T,"a"
CAILE T,"z"
CAIA
SUBI T,"a"-"A"
ILDB TT,B
CAIL TT,"a"
CAILE TT,"z"
CAIA
SUBI TT,"a"-"A"
CAME T,TT ;Match?
RET ;No
SOJG C,STRCAL ;Check if more input
ILDB T,A ;No more in string 2, 1st ended?
JUMPE T,RSKP ;If so, have a match
RET ;Otherwise, no match
; Routine to compare two strings ignoring case differences
; Entry: a/b = ptr/len of string 1
; c/d = ptr/len of string 2
; Call: CALL STRCLL
; Return: +1, match failed
; +2, strings match
STRCLL: CAME B,D ;Strings same length?
RET ;No, can't match
JUMPE B,RSKP ;Same length, if null, match already
DO.
ILDB T,A ;t,tt := next chars raised
CAIL T,"a"
CAILE T,"z"
CAIA
SUBI T,"a"-"A"
ILDB TT,C
CAIL TT,"a"
CAILE TT,"z"
CAIA
SUBI TT,"a"-"A"
CAME T,TT ;Match?
RET ;No
SOJG B,TOP. ;Check if more input
ENDDO.
RETSKP ;Good match
...LIT: XLIST
LIT
LIST
END <ENTVCL,,ENTVEC> ;Set up entry vector