Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_SRC_3_19910112 - sources/sndmsg.mac
There are 11 other files named sndmsg.mac in the archive. Click here to see a list.
	TITLE SNDMSG - Routines to send local and network TTY messages
	SUBTTL David Eppstein / Stanford University / 22 April 1983

	;; Taken from SEND.MAC, written March 1981 by Kirk Lougheed
	;;
	;; Judy Anderson of HP and Ken Olum of FLAIR wrote the Chaos support.
	;; Frank Fujimoto and David Eppstein, both of Stanford, wrote
	;; the TCP/SMTP support.  David Eppstein wrote the Pup Ethernet code.

	SEARCH MONSYM,MACSYM,SNDDEF
	EXTERN $TCOPN,$TCPSI,$TCCLS,$TCSOU,$TCSND,$TCBOU,$TCBIN,$TCSIN
	EXTERN $GTPRO,$GTLCL
	ASUPPRESS
	SALL
; 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==600		;Length of command buffers (for long msgs)
	PDLEN==100		;Length of pushdown stack
	MAISKT==^D25		;Get socket for SMTP


; Macros for returning error strings and codes

DEFINE RETLOC (STRLOC,ERCODE) <
	JRST [	HRROI A,STRLOC
		IFNB <ERCODE>,<MOVX C,ERCODE>
		RET ]
>

DEFINE RETSTR (ERTEXT,ERCODE) <RETLOC [ASCIZ\ERTEXT\],ERCODE>
; 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 DATA		;Uninitialized unpaged data

	NBFLEN==100
NETBUF:	BLOCK NBFLEN		;Used to build "filename" for ICP etc

NETUSR:	BLOCK 20		;Foreign username
NETADR: BLOCK 20		;Foreign site name
LCLHST:	BLOCK 10		;Local host name string (copy with GETLCL)
NTOJFN:	BLOCK 1			;JFN for net

	.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/updated pointer, B/0, C/unchanged
;
; 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
;	Word 2 (.SDTCC): Interrupt channel for TCP interrupt
;			 See $TCPSI in TCPIO.MAC for details

$SEND::	SAVEAC <D,ADR,ABLOCK,CX> ;Don't mung anything for caller
	TRVAR <MSPTR,MSEPTR>	;Place to save message pointer and end of it
	SETZM LCLHST		;No local host name found yet
	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.
	    MOVE B,ADR		;Failed, get address block back
	    RET			;And return failure
	  ENDIF.
	ENDDO.
	SETO A,			;Get -1
	ADJBP A,MSEPTR		;So we can point to null at end of message
	MOVE B,ADR		;Get final value in ADR (should be 0)
	MOVE C,ABLOCK		;Get address block address
	RETSKP			;Return success with them
; 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,NETBUF		;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,NETBUF		;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.
	  MOVEI 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:	HRROI A,NETUSR		;Into net user string
	HRROI B,1(ADR)		;With pointer from address block
	CALL MOVSTR		;Copy string
	HRROI A,NETADR		;Now into host name
	HRROI B,1(B)		;With next string in block
	CALL MOVSTR		;Copy string
	HRROI A,NETADR		;Point to network site name
	MOVEI C,NETLST		;Get list of protocols supported
	CALL $GTPRO		;Look it up
	 RETSTR <Unrecognized host name>,TTXNET
	HRRZ C,(C)		;Get dispatch routine for protocol
	JRST (C)		;Go jump to it


; List of networks we know about.  For each network we have a pointer
; to a string for the name of the net and the address of a handler routine.
; The called routine gets a host number in B, should return +2 for success.

NETLST:	[ASCIZ/Chaos/],,DOCHA	;MIT Chaosnet
	[ASCIZ/Pup/],,DOETH	;Pup Ethernet
	[ASCIZ/TCP/],,DOINT	;TCP/IP Internet
	0
; DOINT - Send a message over the Internet

DOINT:	MOVE A,B		;Get host number where it belongs
	MOVEI B,MAISKT		;Get maiser socket number
	MOVEI C,1		;Job random factor (we should be the only one)
	CALL $TCOPN		;Open the connection
	 RETSTR <Host not responding>,TTXNET
	MOVEM A,NTOJFN		;Save the JFN
	MOVE B,.SDTCC(ABLOCK)	;Get interrupt channel to use
	CALL $TCPSI		;Set up interrupt
	 RETSTR <Couldn't get interrupt channel for response>,TTXNET
	CALL GETRSP		;Pick up 220 site ident
	IFSKP.
	  CALL INTHEL		;Say hello to the foreign site
	ANSKP.
	  CALL GETRSP		;Pick up reply (most likely 250)
	ANSKP.
	  CALL INTSOM		;Say we're going to send a message
	ANSKP.
	  CALL GETRSP		;Get its response
	ANSKP.
	  CALL INTRCV		;Say who we want to receive it
	ANSKP.
	  CALL GETRSP		;Pick up reply (250 or 251)
	ANSKP.
	  HRROI B,[ASCIZ\DATA
\]				;Point to string to send
	  CALL INTSTR		;Go send it
	ANSKP.
	  CALL GETRSP		;Pick up reply (most likely 354)
	ANSKP.
	  CALL INTMSG		;Send the message
	ANSKP.
	  CALL GETRSP		;Pick up reply (most likely 250)
	ANSKP.
	  HRROI B,[ASCIZ \QUIT
\]				;Tell the remote host goodbye
	  CALL INTSTR		;Go send that off
	   NOP			;Don't care about reply, msg already accepted
	  CALL GETRSP		;See if they accept that
	   NOP			;Don't care about reply, msg already accepted
	  MOVE A,NTOJFN		;Get our jfn
	  SETZM NTOJFN		;We have no more net JFN
	  CALL $TCCLS		;Close it
	  RETSKP
	ENDIF.
	PUSH P,A		;Some error.  Save error string
	MOVE A,NTOJFN		;Get the JFN for our connection
	HRROI B,[ASCIZ \QUIT
\]				;Say we want to go bye-bye
	SETZ C,			;Stop on null
	CALL $TCSOU		;Output the string
	 NOP
	CALL $TCSND		;Send it off
	 NOP
	HRROI B,NETBUF		;Into this buffer
	MOVX C,NBFLEN*5-1	;With this many possible characters
	CALL SMTNIN		;Read in reply (most likely 221 but who cares?)
	 NOP
	CALL $TCCLS		;Close the connection and return
	POP P,A			;Get error string back
	MOVEI C,TTXNET		;Random network error
	RET
; GETRSP - listen for responses from the foreign MAISER
; returns +1, with MAISER code in A, text in BUFFER
; if MAISER sends a code .GE. 400, displays error and dies

GETRSP:	MOVE A,NTOJFN		;A/foreign socket
	HRROI B,BUFFER		;Into scratch buffer
	MOVX C,1000*5-1		;With this many characters
	CALL SMTNIN		;Read MAISER's response
	 RET
	CAIL B,400		;Is it a non-error?
	 RETLOC BUFFER		;No, return error string
	RETSKP
; INTHEL - build a message into BUFFER to be sent on the Internet by MAISER

INTHEL:	HRROI A,BUFFER		;Get a pointer to our output buffer
	HRROI B,[ASCIZ/HELO /]
	CALL CPYSTR		;Start our message
	CALL GETLCL		;Append our local site
	 RET			;Propagate fail return
	JRST INTGO		;Finish up the message


; INTSOM - tell the foreign host we're going to send them a message

INTSOM:	HRROI A,BUFFER		;Put everything in our buffer
	HRROI B,[ASCIZ/SOML FROM:</]
	CALL CPYSTR		;Initiate message
	CALL .DIRST		;Add our user name
	MOVEI B,"@"		;Append at-sign for return address
	IDPB B,A
	CALL GETLCL		;Add our site name
	 RET			;Propagate fail return
	MOVEI B,">"		;Close with a close broket
	IDPB B,A
	JRST INTGO		;go finish up


; INTRCV - tell foreign host to whom we want to send

INTRCV:	HRROI A,BUFFER		;Where to build message to send
	HRROI B,[ASCIZ/RCPT TO:</]
	CALL CPYSTR		;Start command line for recipient
	HRROI B,NETUSR		;Get net user name
	CALL CPYSTR		;Add it in
	MOVEI B,"@"		;Get an at-sign
	IDPB B,A		;Add that too
	HRROI B,NETADR		;Get site name of recipient site
	CALL CPYSTR		;Add it in
	MOVEI B,">"		;Want to close off the name
	IDPB B,A		;So do so
	JRST INTGO
; INTMSG - build the actual message and send it
; we have to add RFC822 standard header lines here...

INTMSG:	SETZ D,			;Clear line position
	HRROI A,BUFFER		;Point to buffer
	HRROI B,[ASCIZ/Date: /]	;First header
	CALL CPYSTR		;Copy out
	SETO B,			;Output current date/time
	MOVX C,OT%DAY!OT%SPA!OT%TMZ!OT%SCL ;RFC 822 standard date/time
	ODTIM%			;Add time to buffer
	HRROI B,[ASCIZ/
From: /]			;Second header
	CALL CPYSTR		;Copy it off
	CALL .DIRST		;Add our user name
	MOVEI B,"@"		;Atsign
	IDPB B,A		;Add it in
	CALL GETLCL		;Add local host name
	 RET			;Propagate fail return
	HRROI B,[ASCIZ/
To: /]				;Final header
	CALL CPYSTR		;Copy it in
	HRROI B,1(ADR)		;Point to recipient username
	CALL MOVSTR		;Copy across
	MOVEI C,"@"		;Get atsign
	DPB C,A			;Replace null with it
	HRROI B,1(B)		;Now point to site name
	CALL CPYSTR		;Copy that
	HRROI B,[ASCIZ/
/]				;A CRLF
	CALL CPYSTR		;To tie off header
	CALL INTGO		;Send header string off (will add other CRLF)
	 RET			;Propagate fail return
	MOVE C,MSPTR		;Point to message
	ILDB B,C		;Get the first character
	DO.
	  CALL INTCHR		;Send char out, maybe processing prefix period
	   RETSTR <Couldn't send char to net>
	  ILDB B,C		;Get another
	  JUMPN B,TOP.		;Go back for the next char
	ENDDO.
	MOVEM C,MSEPTR		;Save pointer to end of message
	SKIPE D			;If not at start of line, need extra crlf
	 SKIPA B,[POINT 7,[ASCIZ \
.
\]]
	  MOVE B,[POINT 7,[ASCIZ \.
\]]				;Will eventually end with a CRLF . CRLF
	JRST INTSTR		;Append the trailer
; Copy in one data character, being careful with CRLFs and dots

INTCHR:	MOVE A,NTOJFN		;Get jfn for output
	CAIE B,"."		;If we have a period
	IFSKP.
	  IFE. D		;If at the start of a line
	    CALL $TCBOU		;Send an extra dot
	     RET
	  ENDIF.
	  SETO D,		;In the middle of a line
	  JRST $TCBOU		;Now send our first dot
	ENDIF.
	CAIE B,.CHCRT		;Carriage return?
	IFSKP.
	  MOVEM B,D		;Yes, set line position positive to remember
	  JRST $TCBOU		;And go send it
	ENDIF.
	CAIN B,.CHLFD		;Line feed?
	 SKIPG D		;And previous carriage return?
	  CAIA			;No, go on
	   TDZA D,D		;Yes, we are now at the start of a line
	    SETO D,		;Else we are in the middle of a line
	JRST $TCBOU		;In any case go send the char
; INTGO - add CRLF to string in buffer and send it off
; INTSTR - send string pointed to in B

INTGO:	MOVEI B,.CHCRT		;A carriage return
	IDPB B,A		;Append it to our buffer
	MOVEI B,.CHLFD		;A linefeed
	IDPB B,A		;Add it, too
	MOVEI B,.CHNUL		;A null
	IDPB B,A		;Tie off string with it
	HRROI B,BUFFER		;What to send
INTSTR:	MOVE A,NTOJFN		;Where to send
	SETZ C,			;End on null
	CALL $TCSOU		;Launch
	 RETSTR <Couldn't send string to network>
	CALL $TCSND		;Force sending of this
	 RETSTR <Couldn't force data through network>
	RETSKP
; SMTNIN - input a number (like NIN%), break on non-digit
; call with A/SMTP connection JFN, B/pointer to line buffer, C/char count
; returns +1/failure, +2/success, number in B, C and D smashed

SMTNIN:	TRVAR <ATMPTR,RETNUM,BUFCTR,<LINPTR,2>>
	DMOVEM B,LINPTR		;Save where to start
	SETZM ATMPTR		;No continuation lines yet
	SETZM RETNUM		;Nothing to return yet
	DO.
	  SETZ C,		;Nothing this time through
	  DO.
	    CALL $TCBIN		;Get the next character from our stream
	     RETSTR <Couldn't read SMTP server response>
	    CAIL B,"0"		;Is it in range
	     CAILE B,"7"	;to be a digit?
	      IFSKP.
		IMULI C,10	;Yes, shift result by octal radix
		ADD C,B		;Add in digit
		SUBI C,"0"	;Minus offset for ASCII characters
		LOOP.		;Go back for the next one
	      ENDIF.
	  ENDDO.
	  SKIPG C		;If somehow out of range
	   RETSTR <SMTP input number was not positive>
	  EXCH C,RETNUM		;Exchange with value to return
	  IFG. C
	    CAME C,RETNUM	;If had a number already but is not the same
	     RETSTR <SMTP multi-line input had different codes>
	  ENDIF.
	  CAIE B,"-"		;If we got a dash
	  IFSKP.
	    CALL GETLFD		;Then read in some more
	     RET		;Propagate fail return
	    LOOP.
	  ENDIF.
	ENDDO.
	CAIE B," "		;If not a space
	 RETSTR <SMTP code terminator is neither a dash nor a space>
;	JRST GETLFD

; GETLFD - Read in rest of server response line

GETLFD:	SKIPE B,ATMPTR		;Get pointer
	IFSKP.
	  DMOVE B,LINPTR	;If none, point to start of buffer
	  MKPTR (B)		;Make sure it is a byte pointer
	ELSE.
	  MOVEI C,.CHCRT	;Else get a CR
	  DPB C,B		;Drop it in over the null we left
	  MOVEI C,.CHLFD	;And a linefeed
	  IDPB C,B
	  MOVEI C," "		;And finally a space
	  IDPB C,B		;Drop it in to make pretty
	  SOS C,BUFCTR		;Count off, get old buffer counter
	  SOS C,BUFCTR		;And again
	ENDIF.
	MOVEI D,.CHCRT		;Read to a carriage return
	CALL $TCSIN		;Do the read
	 RET			;Propagate fail return
	MOVEM B,ATMPTR		;Save new atom buffer pointer
	MOVEM C,BUFCTR		;And new buffer space counter
	SETZ C,			;Get a null
	DPB C,B			;Drop it in over carriage return
	CALL $TCBIN		;Now read in the linefeed
	 RET
	MOVE B,RETNUM		;Get the number to return
	RETSKP
; DOCHA - Send a message via the CHAOSnet

; Chaosnet symbols abstracted from CHASYM
	.CSRFS==3		;RFC sent
	.CSOPN==4		;Connection is open
	.MOPKR==27		;Read packet
	CHPKDT==4		;???
	$CPKNB==041400,,0	;???

DOCHA:	PUSH P,B		;Save foreign host
	HRROI A,NETBUF		;Set up pointer for building filename string
	HRROI B,[ASCIZ/CHA:/]
	CALL CPYSTR		;Start with correct device name
	POP P,B			;Get host number back
	MOVEI C,^D8		;Octal
	NOUT%
	 RETSTR <Couldn't copy host number>,TTXNET
	HRROI B,[ASCIZ/.SEND_/]
	CALL CPYSTR		;Put contact name into buffer too
	HRROI B,NETUSR		;And net username
	CALL CPYSTR
	MOVX A,GJ%SHT!GJ%OLD	;Short form, old file
	HRROI B,NETBUF		;Filename
	GTJFN%
	 RETSTR <Can't get Chaosnet connection>,TTXNET
	MOVEM A,NTOJFN		;Save JFN for later
	MOVX B,FLD(8,OF%BSZ)!FLD(6,OF%MOD)!OF%WR ;Don't wait, write, 8 bits
	OPENF%
	 RETSTR <Can't open Chaosnet connection>,TTXNET
	DO.
	  GDSTS%
	   RETSTR <Can't get Chaosnet connection status>,TTXNET
	  CAIN B,.CSOPN		;Open?
	   RETSKP		;Yes, success, take a skip return
	  CAIE B,.CSRFS		;RFC sent?
	   RETSTR <Unexpected Chaosnet connection status>,TTXNET
	  MOVEI A,^D100
	  DISMS%		;Wait a tenth of a second
	  MOVE A,NTOJFN		;Get back JFN
	  LOOP.
	ENDDO.
	CALL CHAMSG		;Build the message to be sent
	 RET
	MOVE A,NTOJFN		;A/network jfn
	MOVE B,[POINT 8,BUFFER]	;B/buffer where CHAMSG left the message
	SETZB C,D		;Normal SOUT, flush randomness in C & D
	SOUT%
	TXO A,CO%WCL		;Wait for data to be acknowledged
	CLOSF%			;Close the Chaosnet connection
	IFNJE. <RETSKP>
	MOVE A,NTOJFN		;A/network jfn
	MOVEI B,.MOPKR		;Get Los/CLS packet
	HRROI C,NETBUF		;Drop it into NETBUF
	MTOPR%
	 RETSTR <Connection failed>,TTXNET
	MOVE A,[POINT 8,NETBUF+CHPKDT]
	MOVE B,A		;Now, make sure asciz
	LDB C,[$CPKNB+NETBUF]	;Get length
	ADJBP B,C		;To point to end of string
	SETZ B,			;Get a null
	DPB B,C			;And tie off message with it
	MOVX C,TTXNET		;Have pointer to string, now set err code
	RET
; CHAMSG - build a message to be sent over the CHAOSnet into BUFFER

CHAMSG:	MOVE A,[POINT 8,BUFFER]	;Get a pointer to our output buffer
	CALL .DIRST		;Add our user name
	MOVEI B,"@"		;Get an atsign
	IDPB B,A		;Drop it in
	CALL GETLCL		;Copy local host name to our buffer
	 RET			;Propagate fail return
	MOVEI B,.CHSPC		;A space
	IDPB B,A		;Add it to the buffer
	SETO B,			;B/want present time
	MOVX C,OT%NSC!OT%12H!OT%TMZ!OT%SCL ;C/format
	ODTIM%			;Write the time
	MOVEI B,215		;CHAOSnet EOL
	IDPB B,A		;Drop that in
	MOVE B,MSPTR		;Get pointer to message text
	DO.
	  ILDB C,B		;Get a byte from the user
	  CAIN C,15		;Carriage return?
	   MOVEI C,215		;Replace with chaos EOL character
	  CAIE C,12		;If linefeed, ignore it.
	   IDPB C,A		;Put character in the buffer
	  JUMPN C,TOP.		;And continue until we have transferred a null
	ENDDO.
	MOVEM B,MSEPTR		;Save pointer to end of message
	MOVEI C,215		;Put another carriage return for pretty
	DPB C,A			;Clobber the null we left there
	SETZ C,
	IDPB C,A		;And terminate string with a null
	RETSKP
; DOETH - Send a message via the Pup Ethernet

; Definitions for Pup-based Ethernet (stolen from PUPPAR)

IFNDEF PUPI%,<
	OPDEF PUPI% [JSYS 441]	;JSYS to send a packet over the Ether
	OPDEF PUPO% [JSYS 442]	;JSYS to pick up a packet

	PU%CHK==1B1		;Compute/check checksum
	PU%MEI==1B3		;Header is in 16-bit hardware mode
	PU%TIM==1B4		;(PUPI%) input timeout in ms in ac3

	.PUORW==16		;Raw I/O mode for OPENF%
>;IFNDEF PUPI%

	PBCONT==5		;Start of data in buffer

	MNPLEN==^D22		;Minimum Pup length (bytes) incl header
	MXPLEN==^D554		;Maximum Pup length
	MXPBLN==<MXPLEN+3>/4	;Maximum packet buffer size (words)

	DEFSTR PUPLEN,BUFFER,17,16	;Pup length
	DEFSTR PUPTYP,BUFFER,35,8	;Pup type

DOETH:	PUSH P,B		;Save net number
	HRROI A,NETBUF		;Get pointer to name string area
	HRROI B,[ASCIZ/PUP:!J./]
	CALL CPYSTR		;Copy string
	HLRZ B,(P)		;Get net num back
	MOVEI C,^D8		;Octal
	NOUT%			;Add the number
	 RETSTR <Couldn't copy net number>,TTXNET
	CALL NUMSGN		;Add a number sign
	POP P,B			;Get host number back
	HRRZS B			;Just the host number
	NOUT%			;Add the number (8 still in C)
	 RETSTR <Couldn't copy host number>,TTXNET
	CALL NUMSGN		;Another number sign
	MOVEI B,"4"		;Well-known socket for misc-services
	IDPB B,A		;Drop it in
	SETZ B,			;Get a null
	IDPB B,A		;Drop that in too
	MOVX A,GJ%SHT!GJ%OLD	;Short-style gtjfn, old file
	HRROI B,NETBUF		;From name we just built
	GTJFN%			;Get the jfn
	 RETSTR <Couldn't get network connection>,TTXNET
	MOVX B,FLD(^D8,OF%BSZ)!FLD(.PUORW,OF%MOD)!OF%RD!OF%WR
	OPENF%			;Open in raw packet mode
	 RETSTR <Couldn't open network connection>,TTXNET
	MOVEM A,NTOJFN		;Save JFN
	CALL BLDETH		;Build packet into buffer
	HRRZ A,NTOJFN		;Get JFN back
	TXO A,PU%CHK!PU%MEI	;Compute checksum, MEIS headers
	MOVE B,[MXPBLN,,BUFFER] ;Max length, from buffer
	PUPO%			;Send it out
	 ERJMP JSRET		;Random lossage
	SETZM BUFFER		;Clear first word of buffer
	MOVE A,[BUFFER,,BUFFER+1] ;Make BLT word
	BLT A,BUFFER+MXPBLN	;Clear out in case string ends on word boundary
	HRRZ A,NTOJFN		;Get JFN again
	TXO A,PU%CHK!PU%MEI!PU%TIM ;Checksum, MEIS headers, timeout
	MOVE B,[MXPBLN,,BUFFER] ;Max length, into buffer
	MOVX C,^D<30*1000>	;Wait for up to 30 seconds
	PUPI%			;Read it back in
	IFNJE.			;Got one?
	  LOAD A,PUPTYP		;Get type
	  CAIN A,301		;Success?
	   RETSKP
	  LOAD B,PUPLEN		;Get length of pup
	  MOVEI C,MNPLEN	;Minimum number of bytes
	  SUBI C,(B)		;Minus that count is num chars text
	  MOVE A,[POINT 8,PBCONT+BUFFER] ;Get pointer to error
	  MOVX C,TTXNET		;And error code
	  RET			;Return with it
	ENDIF.
	RETSTR <Failed to receive remote reply>,TTXNET

NUMSGN:	MOVEI B,""		;Quote with control-V
	IDPB B,A		;Drop it in
	MOVEI B,"#"		;Number sign
	IDPB B,A		;Drop it in
	RET
; Build packet into buffer for PUPO%
; returns +1/always, mungs registers A-D

BLDETH:	SETZM BUFFER		;Clear start of buffer
	MOVE A,[BUFFER,,BUFFER+1]
	BLT A,BUFFER+MXPBLN-1	;Clear it out for the length of a packet
	MOVEI A,300		;Get packet type for ether send
	STOR A,PUPTYP		;Save it
	MOVE A,[POINT 8,PBCONT+BUFFER] ;Get dest ptr
	CALL .DIRST		;Copy user name or string describing us
	MOVEI B,":"		;Colon
	IDPB B,A		;Drop it in
	HRROI B,NETUSR		;Get net user name
	CALL CPYSTR		;Add it in
	MOVEI B,":"		;Another colon
	IDPB B,A		;Drop it in
	MOVE B,MSPTR		;Point to message
	CALL CPYSTR		;Copy it in
	MOVEM B,MSEPTR		;Save pointer
	MOVEI D,@A		;Compute address of last word
	SUBI D,BUFFER-1		;Compute # 36-bit words used
	LSH D,2			;Convert to bytes
	LSH A,-^D33		;Get bytes not used in last word
	SUBI D,(A)		;Compute pup length
	ADDI D,2		;Include checksum
	STOR D,PUPLEN		;Save length
	RET
; $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

; GETLCL - copy name of local site into pointer in A
GETLCL:	SAVEAC <B,C,D>		;Don't mung other registers
	SKIPE LCLHST		;Got a host name yet?
	IFSKP.
	  MOVE D,A		;No, make destination pointer safe
	  HRROI A,LCLHST	;Point to permanent site name storage
	  CALL $GTLCL		;Get our name with HSTNAM routine
	   RETSTR <Couldn't find local site name>
	  MOVE A,D		;Now restore saved pointer
	ENDIF.
	HRROI B,LCLHST		;With our site name
	CALL CPYSTR		;Copy string into pointer
	RETSKP			;And give success return

	END