TITLE SNDMSG - Routines to send local and network TTY messages
       SUBTTL David Eppstein/DE/KSL/MRC/WHP4

       SEARCH MONSYM,MACSYM,SNDDEF
       EXTERN $WAKE
       EXTERN $GTLCL,$RMREL
       ASUPPRESS
       SALL
IFNDEF OT%822,OT%822==:1

; Definitions and Storage

A=:1                            ;Temporary AC's for JSYS use etc
B=:2
C=:3
D=:4

ADR=10                          ;Pointer to current address block
ABLOCK=11                       ;Pointer to argument block

FP=:15                          ;TRVAR frame pointer
CX=:16                          ;Scratch for MACREL
P=:17                           ;Main stack pointer

       BUFLEN==MAXCHR/5        ;Length of command buffers (for long msgs)
       PDLEN==100              ;Length of pushdown stack

; Macros for returning error strings and codes

DEFINE RETSTR (ERTEXT,ERCODE,OP<JRST>) <
       OP [    HRROI A,[ASCIZ\ERTEXT\]
               MOVX C,ERCODE
               RET ]
>

; Storage allocation

       .PSECT DATPAG           ;Paged storage

BUFFER: BLOCK 1000              ;Random text storage etc (must be whole page)
       BUFPAG==BUFFER/1000     ;The associated page number

       .ENDPS

       .PSECT CODE             ;Rest of this file is pure code

; $SEND - Send a terminal message to multiple recipients
; Call with A/pointer to ASCIZ message text
;           B/list of recipients to send to
;           C/argument block
; Returns +1/Failed, A/error string
;                    B/points to failed recipient
;                    C/error code (TTXxxx defined in SNDDEF)
;         +2/Success, A, B, and C changed
;
; Format of the recipient list:
;       Each recipient block is in the form of a header word possibly
;       followed by some data words.  The format of the header word is
;       RC%TYP,,RC%NXT where RC%TYP is a code for the type of recipient
;       coded for by this block, and RC%NXT is the next recipient.
;       End the list with a 0 RC%NXT field.  Values for RC%TYP are:
;               RC.TTY - Recipient is a local terminal.
;                        Data is the terminal number (without .TTDES)
;               RC.USR - Recipient is a local user - data is user number.
;               RC.ALL - Send to all local users (no data).
;               RC.NET - Send to a net user.  Data are two word-aligned
;                        ASCIZ strings for the user and host names.
;
;       Example: to send to TTY4, FMF, and CSD.KDO@SCORE one might have:
;               RBLOCK: RC.TTY,,RBLK0
;                       4               ;TTY 4 (not 400004)
;               RBLK0:  RC.USR,,RBLK1
;                       500000,,137     ;FMF's user number (use RCUSR%)
;               RBLK1:  RC.NET,,0
;                       ASCIZ/CSD.KDO/  ;Net user name (CSD.KDO)
;                       ASCIZ/SCORE/    ;Net host name (SCORE)
;
;
; Format of the argument block:
;       Word 0 (.SDPID): PID for IPCF messages.  Set to zero to make $SEND
;                        create a new PID - it will be filled in if created.
;       Word 1 (.SDFLG): Flags for the IPCF send server.  Defined flags:
;               1B0 (T%USER) - "User program features" like typing the status
;                              of a send to user with multiple ttys
;               1B1 (T%RAFT) - Obey REFUSE SYS after this message
;               1B2 (T%HDR)  - Don't supply a message header (needs WHEEL)
;               1B3 (T%RSYS) - Obey REFUSE SYS always

$SEND:: SAVEAC <D,ADR,ABLOCK,CX> ;Don't mung anything for caller
       TRVAR <MSPTR,QUEJFN>    ;Make some pseudo-globals
       SETZM QUEJFN            ;So nobody thinks random crud is a JFN
       MKPTR (A)               ;Turn into a byte pointer
       MOVEM A,MSPTR           ;Get message text pointer
       MOVE ADR,B              ;Get address block
       MOVE ABLOCK,C           ;Get argument block
       DO.
         CALL SNDONE           ;Send one message
         IFSKP.
           MOVE B,.SDFLG(ABLOCK) ;Done, get SNDSRV flags
           TXZN B,T%RAFT       ;Obey REFUSE SYS after that message
           IFSKP.
             TXON B,T%RSYS     ;Yes, set flag for that
              MOVEM B,.SDFLG(ABLOCK) ;Save changed flag word
           ENDIF.
           LOAD ADR,RC%NXT,(ADR) ;Done, get next
           JUMPN ADR,TOP.      ;And loop back with it
         ELSE.
           CALL MQUEUE         ;Failed, queue net sends
           MOVE B,ADR          ;Get address block back
           RET                 ;And return failure
         ENDIF.
       ENDDO.
       CALL MQUEUE             ;Done, finish queueing net sends
       MOVE A,MSPTR            ;Get message text pointer again
       MOVE B,ADR              ;Get final value in ADR (should be 0)
       MOVE C,ABLOCK           ;Get address block address
       RETSKP                  ;Return success with them

; Finish queueing net sends

MQUEUE: SAVEAC <A,B,C>          ;Don't mung error code or whatever
       SKIPN QUEJFN            ;Are we queueing anything?
        RET                    ;No, done already

       ;; Finish creating the queued mail file
       HRROI A,BUFFER          ;Get buffer back
       FMSG <
Date: >                         ;Yes, start text
       SETO B,
       MOVX C,OT%DAY!OT%SPA!OT%TMZ!OT%SCL!OT%822 ; RFC 822 standard date/time
       ODTIM%
       FMSG <
From: >
       CALL .DIRST             ;Put in my username
       FMSG <@>
       PUSH P,A
       CALL $GTLCL             ;Local host name
       IFNSK.
         POP P,A
         RETSTR <Couldn't get local host name>,TTXNET
       ENDIF.
       POP P,A
       CALL $RMREL
       MOVE A,QUEJFN
       HRROI B,BUFFER          ;Get buffer back
       SETZ C,                 ;Until null
       SOUT%                   ;Start making file
       FMSG <

>
       MOVE B,MSPTR            ;Get pointer to message text
       SETZ C,                 ;Ending on null
       SOUT%                   ;Send it off
       CLOSF%                  ;Close the file
        NOP
       CALLRET $WAKE           ;Wake up MMailr and return

; SNDONE - Send one message
; returns +1/failure, A and C set up; +2/success

SNDONE: LOAD A,RC%TYP,(ADR)     ;Get send type
       CAIG A,RC.NET           ;In range?
       IFSKP.
         HRROI A,[ASCIZ/Unknown function code/]
         MOVEI C,TTXUNK        ;No, get error string and code
         RET                   ;Return failure
       ENDIF.
       CAIN A,RC.NET           ;If it's a net send
        JRST SNDNET            ;Then send that way
       JRST SNDLOC             ;Else it's a local send

; Here to make error string for JSYS error and return

JSRET:  HRROI A,BUFFER+200      ;Use a likely buffer
       HRLOI B,.FHSLF          ;With ourself
       MOVEI C,40*5-1          ;Number of characters available
       ERSTR%                  ;Copy error string
        NOP
        NOP
       HRROI A,BUFFER+200              ;Now point to the string we made
       MOVX C,TTXNET           ;Random network error (not called from SNDLOC)
       RET

; Sending a Local Message

SNDLOC: LOAD A,RC%TYP,(ADR)     ;Fetch type of field we parsed
       MOVE A,[ SIXBIT/SNDLIN/
                SIXBIT/SNDUSR/
                SIXBIT/SNDALL/ ](A)
       MOVEM A,BUFFER+SN$HDR   ;Set up appropriate function code
       MOVE A,1(ADR)           ;Retrieve word of data (garbage for SNDALL)
       MOVEM A,BUFFER+SN$DAT   ;Set up data
       MOVE A,.SDFLG(ABLOCK)   ;Get flags for IPCF
       MOVEM A,BUFFER+SN$FLG   ;Set them
       HRROI A,BUFFER+SN$MSG   ;Into the appropriate place in the IPCF page
       MOVE B,MSPTR            ;From message pointer given to us
       CALL MOVSTR             ;Copy the message
       MOVE D,.SDPID(ABLOCK)   ;With PID we were given
       CALL DOIPCF             ;Go send it off
       IFNSK.
         MOVEM D,.SDPID(ABLOCK) ;Failed, copy PID back anyway
         RET                   ;Give fail return
       ENDIF.
       MOVEM D,.SDPID(ABLOCK)  ;Succeeded, copy PID back
       SKIPN BUFFER+SN$HDR     ;Header word is -1 on errors
       IFSKP.
         HRROI A,BUFFER+SN$STR ;Point to error string
         MOVE C,BUFFER+SN$ERR  ;And get error code returned
         RET                   ;Return failure
       ENDIF.
       MOVE A,BUFFER+SN$TTY    ;Fetch number of terminals sent to
       CAIG A,1                ;One or none?
        RETSKP                 ;Yes, done (none?? should be error)
       MOVE A,.SDFLG(ABLOCK)   ;Get argument flags
       TXNN A,T%USER           ;We running a user program?
        RETSKP                 ;No, be quiet

       ;; Multiple jobs with T%USER set - give status of each
       LOAD A,RC%TYP,(ADR)     ;Get address type
       CAIE A,RC.ALL           ;Is it to *?
       IFSKP.                  ;Yes, "* has multiple jobs" is ugly, so:
         TMSG <Status of system-wide send:
>
       ELSE.
         MOVX A,.PRIOU         ;To the terminal
         MOVE B,ADR            ;With current address
         CALL $WTRCP           ;Write recipient name
         TMSG < has multiple jobs:
>                               ;Start message
       ENDIF.
       MOVN D,BUFFER+SN$TTY    ;Fetch and negate no. of terminals pawed over
       HRLZS D                 ;Swap and clear
       DO.
         TMSG <  >             ;Space over for pretty
         MOVEI A,.PRIOU        ;A to the terminal
         HRRZ B,BUFFER+SN$TTY+1(D)
         ADDI B,.TTDES         ;Make TTY number into a device designator
         DEVST%                ;Write device name, e.g. TTY6
          ERJMP .+1
         TMSG <: -- >  ;Add a colon and separating dashes
         HLRZ B,BUFFER+SN$TTY+1(D)      ;Fetch status flag for that line
         HRRO A,[ [ASCIZ/refused/]
                  [ASCIZ/ok/]
                  [ASCIZ/timed out/]
                  [ASCIZ/refused/] ]+1(B)
         PSOUT%                ;Print out the status
         TMSG <
>                               ;End with a CRLF
         AOBJN D,TOP.          ;Loop over the list
       ENDDO.
       RETSKP

; DOIPCF - send an IPCF page to the send server and await the response
; call with BUFFER/message, D/PID of this process (zero if none)
; returns +1/failure, +2/success, reply page in BUFFER

       PACLEN==7               ;Length of MSEND/MRECV packet

DOIPCF: STKVAR <SRVPID,<SNDARG,4>,<PACKET,PACLEN>>
       MOVX A,IP%CPD           ;Get create pid flag into place
       SKIPE D                 ;Do we already have a pid?
        SETZ A,                ;Yes, no special flags needed
       MOVEM A,.IPCFL+PACKET   ;Set up flag word
       MOVEM D,.IPCFS+PACKET   ;We are the sender
       SETZM .IPCFR+PACKET     ;INFO is the receiver
       MOVEI A,SNDARG
       HRLI A,4
       MOVEM A,.IPCFP+PACKET   ;Set up pointer to argument block
       MOVX A,.IPCIW
       MOVEM A,.IPCI0+SNDARG   ;Get pid for this name
       SETZM .IPCI1+SNDARG     ;No duplicate
       DMOVE A,[ASCIZ/SNDSRV/] ;Point to string for SNDSRV's PID name
       DMOVEM A,.IPCI2+SNDARG  ;Stash the id
       MOVEI A,PACLEN
       MOVEI B,PACKET
       MSEND%                  ;Ask info for server PID, maybe create our PID
        RETSTR <Error while sending to INFO>,TTXIPC
       MOVE D,.IPCFS+PACKET    ;Fetch our PID in case it was created
       MOVEM D,.IPCFR+PACKET   ;We are receiver this time
       SETZM .IPCFL+PACKET     ;Sender is INFO
       MOVEI A,PACLEN
       MOVEI B,PACKET
       MRECV%                  ;Receive reply from INFO
        RETSTR <Error receiving from INFO>,TTXIPC
       LDB A,[POINT 6,.IPCFL+PACKET,29] ;Get INFO error code field
       IFN. A
         CAIN A,76             ;Couldn't find it?
          RETSTR <Server not running>,TTXIPC
         RETSTR <INFO error other than server not running>,TTXIPC
       ENDIF.
       MOVE A,.IPCI1+SNDARG
       MOVEM A,SRVPID          ;Store server's PID
       MOVX A,IP%CFV
       MOVEM A,.IPCFL+PACKET   ;Sending a page of data
       MOVEM D,.IPCFS+PACKET   ;We are the sender
       MOVE A,SRVPID
       MOVEM A,.IPCFR+PACKET   ;The server is the receiver
       MOVEI A,BUFPAG          ;From the data page
       HRLI A,1000             ;A whole page full
       MOVEM A,.IPCFP+PACKET
       MOVEI A,PACLEN
       MOVEI B,PACKET
       MSEND%                  ;Send off the request
        RETSTR <Error sending to server>,TTXIPC
       MOVX A,IP%CFV
       MOVEM A,.IPCFL+PACKET   ;Receiving a page of data
       MOVE A,SRVPID
       MOVEM A,.IPCFS+PACKET   ;From the server
       MOVEM D,.IPCFR+PACKET   ;To our own PID
       MOVEI A,BUFPAG          ;Back to the same data page
       HRLI A,1000
       MOVEM A,.IPCFP+PACKET
       MOVEI A,PACLEN
       MOVEI B,PACKET
       MRECV%                  ;Receive a reply
        RETSTR <Error receiving from server>,TTXIPC
       RETSKP

; SNDNET - Send a Network Message
; returns +1/failure, A and C set for return; +2/success

SNDNET: SKIPE A,QUEJFN          ;Do we have a queued mail file yet?
        JRST NETRCP            ;Yes, just add recipient
       HRROI A,BUFFER          ;Into a free place
       HRROI B,[ASCIZ/MAILQ:[--QUEUED-MAIL--].NEW-/]
       SETZ C,
       SOUT%                   ;Start making name
       PUSH P,A                ;Create frame to save string pointer
       GTAD%                   ;Now output date/time
       MOVE B,A
       MOVE A,(P)
       MOVEI C,^D8             ;Output in octal
       NOUT%
        RETSTR <Couldn't make name for queued mail file>,TTXNET
       FMSG <-SNDMSG-J>
       MOVEM A,(P)             ;Update saved string pointer
       GJINF%                  ;Get my job number
       POP P,A                 ;Get string pointer back
       MOVE B,C                ;Get job number in B
       MOVEI C,^D10            ;Output in octal
       NOUT%
        RETSTR <Couldn't make name for queued mail file>,TTXNET
       FMSG <.-1;P770000>      ;Next generation, set protection
       MOVX A,GJ%SHT!GJ%FOU    ;File for output
       HRROI B,BUFFER          ;With nice file name
       GTJFN%                  ;Get handle on queue file
        RETSTR <Couldn't get handle on queued mail file>,TTXNET
       MOVX B,FLD(7,OF%BSZ)!OF%WR ;Writing
       OPENF%                  ;Open it up
        RETSTR <Couldn't open queued mail file>,TTXNET
       MOVEM A,QUEJFN          ;Save for later
       HRROI A,BUFFER          ;Into buffer space
       FMSG < =DELIVERY-OPTIONS:SEND
_>                             ;This is a send, from someone
       CALL $GTLCL             ;Get local host name
        RETSTR <Couldn't get local host name>,TTXNET
       FMSG <
>                               ;CRLF (still in buffer)
       CALL .DIRST             ;And our user name
       FMSG <
>                               ;Another CRLF
       MOVE A,QUEJFN           ;Get file again
       HRROI B,BUFFER          ;Get buffer back
       SETZ C,                 ;Until null
       SOUT%                   ;Start making file

; Here with A/QUEJFN, add one recipient to the list in the file
NETRCP: MOVEI B,"L"-100         ;Control L
       BOUT%                   ;To start another host/recip pair
       HRROI A,BUFFER          ;Get a place to buffer user name
       HRROI B,1(ADR)          ;With pointer from address block
       CALL MOVSTR             ;Copy string
       MOVE A,QUEJFN           ;Get JFN back again
       HRROI B,1(B)            ;With next string in block
       SOUT%                   ;Send host name first
       FMSG <
>                               ;Then a CRLF
       HRROI B,BUFFER          ;Then point to user name
       SETZ C,                 ;To null
       SOUT%                   ;Add user name
       FMSG <
>                               ;Another CRLF to tie it off
       RETSKP                  ;Done for now with the net send

; $SSTAT - Send a request for statistics to the send server
; call with D/PID, returns +1/always, D/updated PID

$SSTAT::SAVEAC <A,B,C,CX>       ;Don't mung caller's registers
       MOVE A,['SNDSTA']       ;SIXBIT function code
       MOVEM A,BUFFER+SN$HDR   ;Set it in IPCF page
       SETZM BUFFER+SN$FLG     ;No format flags
       CALL DOIPCF             ;Send it off
        NOP                    ;Ignore failure
       RET

; $WTRCP - Make string for recipient block
; Call with A/Destination pointer (string or JFN)
;           B/Recipient block (as for $SEND)
; Returns +1/Always

$WTRCP::SAVEAC <B,C,ADR>        ;Don't mung caller's registers
       MKPTR (A)               ;Make sure we have a real byte pointer
       MOVE ADR,B              ;Copy address block pointer
       LOAD B,RC%TYP,(ADR)     ;Find out what kind of send this is
       JRST @[ WRTTTY          ;RC.TTY - to line number
               WRTUSR          ;RC.USR - to user number
               WRTALL          ;RC.ALL - to all
               WRTNET ](B)     ;RC.NET - network send

WRTTTY: MOVE B,1(ADR)           ;Get send data
       MOVEI B,.TTDES(B)       ;Turn into device designator
       DEVST%                  ;Type it
        IFNJE. <RET>
       FMSG <Unknown terminal>
       RET

WRTUSR: MOVE B,1(ADR)           ;Get send data
       DIRST%                  ;Type user name
        IFNJE. <RET>
       FMSG <Unknown user>
       RET

WRTALL: FMSG <*>                ;To everyone, just type a star
       RET

WRTNET: HRROI B,1(ADR)          ;Point to user
       SETZ C,                 ;No limit
       SOUT%                   ;Add it
       HRROI C,1(B)            ;Save it
       MOVEI B,"@"             ;Atsign
       BOUT%                   ;Add that too
       MOVE B,C                ;Get pointer back
       SETZ C,                 ;Ending on null
       SOUT%                   ;Copy host name
       RET

; Random string copying utilities

;MOVSTR - move ASCIZ string from source in B to dest in A, including the null
MOVSTR: MKPTR (A)
       MKPTR (B)
       DO.
         ILDB C,B
         IDPB C,A
         JUMPN C,TOP.
       ENDDO.
       RET

; CPYSTR - copy an asciz string from source to destination without the null
CPYSTR: MKPTR (A)
       MKPTR (B)
       DO.
         ILDB C,B
         JUMPE C,R
         IDPB C,A
         LOOP.
       ENDDO.

; .DIRST - Copy our user name or string describing n-l-i into pointer in A
DIRST:  SAVEAC <B,C,D>          ;Don't mung other registers
       PUSH P,A                ;Save pointer
       GJINF%                  ;Get our user number
       HLRZ B,A                ;Get left half only
       CAIE B,500000           ;Look like a user number?
       IFSKP.
         MOVE B,A              ;Save user number
         POP P,A               ;Get pointer back
         DIRST%                ;Type it out
          IFSKP. <RET>         ;If succeeded, we're all done
         HRROI B,[ASCIZ/Unknown user, /] ;Else start making string
       ELSE.
         POP P,A               ;Get dest back
         HRROI B,[ASCIZ/Not logged in, /]
       ENDIF.
       CALL CPYSTR             ;Start string
       IFL. D                  ;If detached (how'd a n-l-i det run send???)
         HRROI B,[ASCIZ/detached/]
         JRST CPYSTR           ;Say so
       ENDIF.
       MOVEI B,.TTDES(D)       ;Else get as terminal designator
       DEVST%                  ;Write "TTYn"
        ERJMP .+1              ;Ignore errors
       RET                     ;All done

       END