Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_SRC_3_19910112 - mm-dom/mm.mac
There are no other files named mm.mac in the archive.
;[SRI-NIC]SRC:<MM-DOM>MM.MAC.16, 29-Jul-88 14:54:35, Edit by MKL
; fix bug in HSTNAM/VSEND code
;[SRI-NIC]SRC:<MM-DOM>MM.MAC.15, 29-Jun-88 15:58:04, Edit by MKL
; allow users to redefine MAILQ: (not GJ%PHY in GTJFN)
;[SRI-NIC]SRC:<MM-DOM>MM.MAC.13, 25-May-88 22:28:10, Edit by MKL
; add "V" commands (VSEND, VREPLY) that verify host names,
; leaving normal SEND and REPLY that don't.  Setting flag F%VRFY
; to decide whether to use previous edit or not.
;[SRI-NIC]SRC:<MM-DOM>MM.MAC.10, 17-May-88 16:08:11, Edit by MKL
; make HSTNAM not call $GTCAN
;[SRI-NIC]SRC:<MM-DOM>MM.MAC.2, 18-Apr-88 14:09:26, Edit by MKL
;Merge our MM with new domainized MM

;SRC:<MM-NEW>MM.MAC.21230, 21-May-87 12:57:42, Edit by KNIGHT
; Reduce length of file block in QUEUE% arguments by two words
;SRC:<MM-NEW>MM.MAC.21228, 20-May-87 15:25:07, Edit by KNIGHT
; Don't set a switch if parse failed!
;SRC:<MM-NEW>MM.MAC.21227, 20-May-87 12:32:05, Edit by KNIGHT
; Fix count on separate pages separator SOUT%
;SRC:<MM-NEW>MM.MAC.21226, 20-May-87 11:29:22, Edit by KNIGHT
; Add /UNIT to READ LIST subcommand
;SRC:<MM-NEW>MM.MAC.21225, 20-May-87 09:45:14, Edit by KNIGHT
; Make LISTs out of READ work. 
;SRC:<MM-NEW>MM.MAC.21224, 19-May-87 20:42:50, Edit by KNIGHT
; Blerf - no ret in INILUP - always go back to mama
;SRC:<MM-NEW>MM.MAC.21221, 14-May-87 12:25:24, Edit by KNIGHT
; SET PRINTER-DEFAULT now asks for keywords, not just a string
;SRC:<MM-NEW>MM.MAC.21220, 14-May-87 10:21:15, Edit by KNIGHT
; Add /UNIT switch to LIST command
;SRC:<MM-NEW>MM.MAC.21219, 14-May-87 09:52:41, Edit by KNIGHT
; Reference STRBUF for message temp file, not literal
;SRC:<MM-NEW>MM.MAC.21218, 24-Feb-87 10:06:44, Edit by KNIGHT
; QUEUE% JSYS doc lied.  Unit attribute in LH, unit number in RH, not v.v.
;SRC:<MM-NEW>MM.MAC.21215,  5-Feb-87 10:54:24, Edit by KNIGHT
; Default with no printer name is unit zero 
;SRC:<MM-NEW>MM.MAC.21214, 27-Jan-87 15:43:27, Edit by KNIGHT
; Call SHOPRN if necessary when creating INIT file
;SRC:<MM-NEW>MM.MAC.21210, 27-Jan-87 09:52:18, Edit by KNIGHT
; Create MMNIC, require it, and move some code there so this'll assemble
;SRC:<MM-NEW>MM.MAC.21208, 26-Jan-87 15:39:19, Edit by KNIGHT
; Well, dike some junk out of this loser to flush core size errors in MACRO
; Someone really oughta learn the value of modularity and conciseness.
;SRC:<MM-NEW>MM.MAC.21207, 26-Jan-87 13:58:27, Edit by KNIGHT
; Implement SET PRINTER-DEFAULT for routing of LIST/LASER commands.  As
; a result, the LIST and LASER command are now the same command
;[SRI-NIC]SRC:<MM-NEW>MM.MAC.21196,  3-Dec-86 14:49:36, Edit by MKL
; [NIC2468] at PUTMSG convert escapes to dollar-signs
; because lasering a message was sending escapes to printer.
;[SRI-NIC]SRC:<MM-NEW>MM.MAC.21191, 18-Nov-86 19:10:46, Edit by MKL
; log nasty stuff
;SRC:<MM-NEW>MM.MAC.21188, 24-Apr-86 14:33:01, Edit by KNIGHT
; BBD: instead of str:<BBOARD>
;[SRI-NIC]SRC:<MM-NEW>MM.MAC.21187,  5-Feb-86 23:53:01, Edit by MKL
;fix a bug in alias code (from ERC)
;[SRI-NIC]SRC:<MM-NEW>MM.MAC.21184,  4-Feb-86 18:03:59, Edit by MKL
;[NIC2041] fix bugs in laser command
;[SRI-NIC]SRC:<MM-NEW>MM.MAC.21171,  7-Nov-85 14:57:25, Edit by MKL
;remove my alias hacks and put in columbia's under ALIASW again
;[SRI-NIC]SRC:<MM>MM.MAC.2040,  1-Feb-85 11:14:16, Edit by HSS
;[NIC2040] Add laser command
;[SRI-NIC]SRC:<MM>MM.MAC.1040,  5-Jan-85 20:09:47, Edit by IAN
;[NIC2037] Accept "P" and "PR" as abbreviations for "PREVIOUS"
;[SRI-NIC]SRC:<MM>MM.MAC.1038,  3-Dec-84 12:04:56, Edit by IAN
;Don't force the protection of new MAILQ: files
;[SRI-NIC] SRC:<MM>MM.MAC.1036,  7-Nov-84 16:10:58, Edit by HSS
;Set LPTCFM so listing never causes stupid questions to be asked.

	TITLE MM Mail Munger -- TOPS-20 mailsystem
	SUBTTL Written by Michael McMahon /MMcM/TAH/SMC/MRC/TCR/KLH

;Version # stuff

VWHO==0				;Who last edited (0=MM developers)
VMAJ==6				;Major version (same as TOPS-20)
VMIN==1				;Minor version
VEDIT==^D1152			;Edit number, MM.EXE should be same

;  The original version of MM was written by Michael McMahon at SRI
; International, presently at Symbolics.  At the time, it used a unique
; command parser designed by McMahon (ULTCMD), and had a similar user
; interface to the then-popular Tenex MSG program.  Stuart McLure Cracraft
; was also involved in early MM development and was primarily responsible
; for early popularizing of MM.
;
;  In the summer of 1978, a version of MM came to DEC.  Ted Hess at DEC
; converted it to MACRO and to use the COMND% JSYS instead of ULTCMD.
; At this point, MM and the program which was later to become DECmail/MS
; diverged.  Today, the difference between the two is that MM is free
; and has had continuous development.  DECmail/MS costs $15K and hasn't
; been touched much in the past few years.
;
;  Since the summer of 1979 most of the MM maintenance and development
; has been done by Mark Crispin, with occasional contributions from others
; too numerous to name.  MM has matured to become the standard mailsystem
; on most of the existing TOPS-20 systems.  Extensive input from its
; numerous users has made MM a powerful and reliable mailsystem.
;
;  Communications about MM should be addressed to:
;
;	Mark Crispin
;	PANDA PROGRAMMING
;	1802 Hackett Ave., Rainbow Suite
;	Mountain View, CA  94043-4431
;	USA
;	 +1 (415) 968-1052
;	[email protected] or [email protected]
	SUBTTL Definitions

	SEARCH MACSYM,MONSYM	;System definitions
	SALL			;Suppress macro expansions
	ASUPPRESS		;Save some symbol table space
	.DIRECTIVE FLBLST	;Sane listings for ASCIZ, etc.
	.TEXT "/NOINITIAL"	;Suppress loading of JOBDAT
	.TEXT "MM/SAVE"		;Save as MM.EXE
	.TEXT "/SYMSEG:PSECT:CODE" ;Put symbol table and patch w/ code
	.REQUIRE MMHELP		;Help strings
	.REQUIRE MMUUO		;UUO handler
	.REQUIRE FSCOPY		;Fast string copy
	.REQUIRE HSTNAM		;Host name routines
	.REQUIRE WAKEUP		;MMailr wakeup routines
	.REQUIRE BLANKT		;Blank screen routines
	.REQUIRE RELAY		;Relay hosts
	.REQUIRE SYS:MACREL	;MACSYM support routines
IFN NICSW,<
	.REQUIRE MMNIC		;NIC support routines
ALIASW==-1			;Columbia ALIAS code
>;IFN NICSW
IFNDEF OT%822,OT%822==:1

; Routines invoked externally

	EXTERN FSCOPY
	EXTERN UUOH,CRLF0,CRIF,CRLF
	EXTERN H1CMDT,H1RCMD,H1SCMD,.HSETM,INIVTB,NINVRS
	EXTERN $GTCAN,$GTLCL,$INRLY,$GTRLY,$RMREL
	EXTERN $WAKE
	EXTERN $BLANK

; Assembly values

IFNDEF NHSPGS,<NHSPGS==^D5>	;Number of pages for host strings in cache
IFNDEF NHPPGS,<NHPPGS==2>	;Number of pages for host cache pointers
 NHOSTS==<NHPPGS*1000>-1	;Maximum number of hosts in cache
IFNDEF NEDPGS,<NEDPGS==^D20>	;Number of pages between MM and editor
IFNDEF NKYPGS,<NKYPGS==1>	;Number of pages for keyword hacking
IFNDEF NMSGS,<NMSGS==2000>	;Number of messages we can handle
IFNDEF NMSWRN,<NMSWRN==^D100>	;Number free msgs before warning user
IFNDEF NPGWRN,<NPGWRN==^D40>	;Number free pages before warning
IFNDEF NTOPGS,<NTOPGS==4>	;Number of pages for TO/CC/etc addr blocks
IFNDEF NTXPGS,<NTXPGS==^D40>	;Number of pages for text input
IFN NICSW,<
IFNDEF MAXBBD,<MAXBBD==^D52>	;Maximum number of BBoards supported
>
IFNDEF MAXBBD,<MAXBBD==^D50>	;Maximum number of BBoards supported

IFN ALIASW,<
IFNDEF DATORG,<DATORG==2000>	; Data on page 2
IFNDEF CODORG,<CODORG==14000>	; Code on page 14
IFNDEF PAGORG,<PAGORG==110000>	;CU21 Paged data on page 106
IFNDEF MTXORG,<MTXORG==310000>	;CU21 MAIL.TXT on page 363 (nic 310)
>
IFNDEF DATORG,<DATORG==1000>	;Data on page 1
IFNDEF CODORG,<CODORG==12000>	;Code on page 12
IFNDEF PAGORG,<PAGORG==104000>	;Paged data on page 104
IFNDEF MTXORG,<MTXORG==301000>	;MAIL.TXT on page 301
;;;Special version of FLDDB. which has default pointer instead of string
DEFINE FLDDF. (TYP,FLGS,DATA,HLPM,DEFM,LST) <
 ..XX==<FLD(TYP,CM%FNC)>+FLGS+<0,,LST>
 IFNB <HLPM>,<..XX==CM%HPP!..XX>
 IFNB <DEFM>,<..XX==CM%DPP!..XX>
 ..XX
 DATA+0
 IFNB <HLPM>,<-1,,[ASCIZ HLPM]>
 IFB <HLPM>,<0>
 IFNB <DEFM>,<-1,,DEFM>
>;DEFINE FLDDF.

DEFINE PUSHAE (AC,LIST) <
 IRP LIST,<PUSH AC,LIST>
>;DEFINE PUSHAE

DEFINE POPAE (AC,LIST) <
 IRP LIST,<POP AC,LIST>
>;DEFINE POPAE

DEFINE DEFERR (X,Y) <
 DEFINE X (Z) <
  IFB <Z>,<UERR Y,0>
  IFNB <Z>,<UERR Y,[ASCIZ/Z/]>
 >;DEFINE X
>;DEFINE DEFERR

DEFINE CMD (X,Y,Z) <
 IFB <Z>,<
  IFB <Y>,<[ASCIZ\X\],,.'X>
  IFNB <Y>,<[ASCIZ\X\],,Y>>
 IFNB <Z>,<
  IFB <Y>,<[Z
   ASCIZ\X\],,.'X>
  IFNB <Y>,<[Z
   ASCIZ\X\],,Y>>
>;DEFINE CMD

DEFINE CMD1 (X,Y,Z) <CMD (X,Y,<Z+CM%FW>)>

DEFINE VAR (X,Y,Z) <
 [ASCIZ/X/],,[Z,,Y]
>;DEFINE VAR

DEFINE HDY (X,Y,Z) <
 RADIX ^D10
 [ASCIZ/X/],,[<Y-1>*512+<Z-1>,,DATHDY]
 RADIX ^D8
>;DEFINE HDY

DEFINE CITYPE (X) <UTYPE 1,[ASCIZ/X/]>
DEFINE ETYPE (X) <UETYPE [ASCIZ/X/]>
DEFINE CETYPE (X) <UETYPE 10,[ASCIZ/X/]>
DEFINE CIETYP (X) <UETYPE 1,[ASCIZ/X/]>
DEFINE NOISE (X) <UNOI [ASCIZ/X/]>
DEFINE DEFALT (X) <UDEF [ASCIZ/X/]>
DEFINE PROMPT (X) <UPRMT [ASCIZ/X/]>
DEFINE CONFRM <CALL CONF>
DEFINE NOINT <CALL .NOINT>	;Trap CTRL/C's
DEFINE OKINT <CALL .OKINT>	;Untrap CTRL/C's

DEFERR WARN,3
DEFERR JWARN,7
DEFERR ERROR,11
DEFERR JERROR,15
DEFERR FATAL,12
DEFERR JFATAL,16
DEFERR SNARL,13			;Snarl = "error, but return to caller"
DEFERR JSNARL,17

	PURGE DEFERR
;;;AC's

F==:0				;Flags
A=:1				;Temp and JSYS
B=:2				;Ditto
C=:3				;Ditto
D=:4				;Ditto
E=:5				;Temp & local to routine
T=:6				;Ditto
U=:7				;Ditto
V=:10				;Ditto 
W=:11				;Ditto
L=:12
M=:13				;Current message if any
N=:14
O=:15
;CX=:16				;MACSYM temporary AC
;P=:17

;;;OPDEF's

OPDEF PRINT [1B8]
OPDEF UTYPE [2B8]
OPDEF UETYPE [3B8]
OPDEF UERR [4B8]
OPDEF UNOI [5B8]
OPDEF UDEF [6B8]
OPDEF UPRMT [7B8]
OPDEF UHELP [10B8]

;;;Various useful characters
.CHLAB=="<"			;Left broket
.CHRAB==">"			;Right broket
;;;Flags

F%F1==  1B0			;Temp
F%F2==  1B1
F%F3==  1B2
F%F4==  1B3
F%AT==  1B4			;@ see in address
F%ADR== 1B5			;Seen non-blank part of an address
F%QOT== 1B6			;Inside a quoted string
F%SWRN==1B7			;User has been warned about oversized mail file
F%BB==  1B8			;Reading BBoard
F%RTE== 1B9			;Return to EXEC eventually
F%CC==  1B10			;In CC command
F%COMA==1B11			;Type comma except before 1st field
F%TYPS==1B12			;Type out numbers of messages handled
F%QUOT==1B13			;Generate quoted host names
F%FST== 1B14			;Fast parse in PRADDF
F%RELD==1B15			;Include relative domains with host names
;;;;;;==1B16
;;;;;;==1B17
F%READ==1B18			;Inside the READ command
F%SEND==1B19			;Inside the SEND commands
F%RSCN==1B20			;Called by command line
F%MOD== 1B21			;Reading system mail
F%AMOD==1B22			;Auto MOD handling
F%TECO==1B23			;Using TECO based editor
F%RONL==1B24			;Read only file
F%ALIA==1B25			;Aliasing another user
F%ESND==1B26			;Editor said send it off
F%TECP==1B27			;Editor supports hairy TECO interface
F%TAK==:1B28			;Take file in progress
F%HOER==:1B29			;Halt on error
F%RSCC==1B30			;Original parse of RSCAN% line
F%QUEU==1B31			;Queued mail seen
F%DIRE==1B32			;In message Dired mode
F%DIRR==1B33			;Want to re-enter Dired having done reply
;;;;;;==1B34
IFN NICSW,<
F%LIST==1B34			;Doing a LIST (as opposed to FLIST) command
F%VRFY==1B35
>;IFN NICSW
;;;;;;==1B35
	SUBTTL Page allocation

	.PSECT DATPAG,PAGORG	;Enter paged data

IFE NICSW,<
DEFINE DEFPAG (ADDR,LENGTH) <
ADDR:	IFB <LENGTH>,<BLOCK 1000>
	IFNB <LENGTH>,<BLOCK 1000*LENGTH>
>;DEFINE DEFPAG
>;IFE NICSW
IFN NICSW,<
DEFINE DEFPAG (ADDR,LENGTH) <
ADDR::	IFB <LENGTH>,<BLOCK 1000>
	IFNB <LENGTH>,<BLOCK 1000*LENGTH>
>;DEFINE DEFPAG
>;IFN NICSW

DEFPAG HDRPAG			;Headers
 SUBBUF=HDRPAG+700		;Address of subject buffer
 SUBEND=.-1			;End of subject buffer
 SUBBSZ==<<SUBEND-SUBBUF>*5>+4	;Length of subject buffer
DEFPAG TXTPAG,NTXPGS		;Message text page
DEFPAG TOPAG,NTOPGS		;Storage for TO/CC lists
IFN NICSW,<
DEFPAG PKTPAG,2			;PRINTER-KEYWORD-TABLES.BIN goes here
>;IFN NICSW

;;;Addresses are kept in chained blocks of the following format:
ADRFLG==0			;Flags
 DEFSTR (ADINV,ADRFLG,0,1)	;Invisible address (don't show in sent message)
 DEFSTR (ADTYP,ADRFLG,8,2)	;Type of address
  AD.LCL==0			;Local mailbox (must be 0)
  AD.FIL==1			;Local file (must = FILIST-LCLIST)
  AD.NET==2			;Remote user (must = NETLST-LCLIST)
  AD.GRP==3			;Group name
 DEFSTR (ADSIZ,ADRFLG,17,9)	;Size of block
 DEFSTR (ADPTR,ADRFLG,35,18)	;Pointer to next address in To/cc/bcc list
ADRLNK==1			;Ptr (back,,fwd) LCLIST/FILIST/NETLST
ADRUSR==2			;Local user number if AD.LCL
ADRHST==ADRUSR			;Host pointer if AD.NET
ADRSTR==3			;First word of string
DEFPAG FWDPAG			;Page for mapping to MAILBOX/FINGER
DEFPAG HSTSTR,NHSPGS		;Host name string cache
DEFPAG HSTTAB,NHPPGS		;Pointers to above in TBLUK% format
DEFPAG FLGPAG			;For MAILER.FLAGS
DEFPAG EDBPAG,2			;Editor buffer block page
DEFPAG EDPAGE,NEDPGS		;Editor pages for data
DEFPAG SRTPAG,0			;Sorting free space (shared)
DEFPAG SPLPAG,0			;SPELL pages for transfer (shared)
DEFPAG WRTPGS,NEDPGS		;Writeable pages
DEFPAG KEYPAG			;Page full of keyword names
DEFPAG KEYPGS,NKYPGS		;Pages for keyword lists/strings
DEFPAG UHDPAG			;Page for user generated headers
 USRHDR=:UHDPAG			;Ptr to end header options/start user headers
				;Free count after header options (negative)
 USRHFP=USRHDR+2		;Ptr to end of user headers
				;Free count after user headers (negative)
 USRHDT=USRHFP+2		;Text of header options/user headers

MSGLEN==:12			;Length of block
 NMSGPG==<NMSGS/1000*MSGLEN>	;Number of message pages
DEFPAG MSGPGS,NMSGPG		;Storage for message data

; The "starting byte" for a message is the byte # relative to
; beginning of message-file pages (MSGPGS).  All "offsets" are byte #s
; relative to this starting byte.  The "whole msg" includes the
; initial date/length/flags line peculiar to TOPS-20 message files, whereas
; the "message body" does not include it (it does include the header).
; The "header" is everything in the message body up to and including
; the double CRLF separating it from the remainder of the body, which
; is the "text" of the message.
MSGALL==MSGPGS+0		;Starting byte of message
MSGSAL==MSGPGS+1		;Size of whole message
MSGBOD==MSGPGS+2		;Size of message body,,offset to body
MSGFRM==MSGPGS+3		;Size of from field,,offset to field
MSGSUB==MSGPGS+4		;Size of subject,,offset to field
MSGDAT==MSGPGS+5		;Date of message (GTAD fmt)
MSGFLG==MSGPGS+6		;Flags,,offset to msg text
 MSGHLN==MSGFLG			; Used for refs to RH above
MSGBTS==MSGPGS+7		;Message bits
MSGFBS==MSGPGS+10		;Message bits actually in file
MSGMID==MSGPGS+11		;Message ID

;Hard-wired flags kept in the RH of MSGBTS and MSGFBS.
M%SEEN==1			;Message has been seen
M%DELE==2			;Message is deleted
M%ATTN==4			;Message wants attention (always-show)
M%RPLY==10			;Message has been replied to
M%RSRV==20			;Message flag reserved for expansion
M%RSR1==40			;Message flag reserved for expansion
M%FLAG==M%SEEN!M%DELE!M%ATTN!M%RPLY!M%RSRV!M%RSR1 ;All message flags
M%KEYW==777777777700		;Remaining flags are for keywords

;MM flags kept in the LH of MSGFLG.
M%RECE==1B0			;Message is recent (sign bit)
M%FRME==1B1			;Message is from me
M%FRNM==1B2			;Messages is from someone else

	.ENDPS
	.PSECT MTXDAT,MTXORG

MTXPGN==MTXORG/1000		;Start of file mapping area
NMTXPG==1000-MTXPGN		;Number of MAIL.TXT pages
DEFPAG MTXPAG,NMTXPG		;File mapping area

	PURGE DEFPAG
	.ENDPS
	SUBTTL Impure storage

	LOC 20

FRKACS:	BLOCK 20		;Setup for editor fork's ACs
.JBUUO:	BLOCK 1			;UUO executed
.JB41:	CALL UUOH		;UUO handler
LCLHST:	BLOCK 1			;Local host pointer
MBXFIL:	BLOCK 42		;Home mailbox for COPY/MOVE default
HCSHFF:	BLOCK 1			;First free word in host cache
PRGNAM:	BLOCK 2			;Save area for subsystem/program names
MYUSR:	BLOCK 1			;Login user
MYCDIR:	BLOCK 1			;Connected directory
MYDIR:	BLOCK 1			;Login directory
MYPDIR:	BLOCK 1			;Post office box directory
MYAUSR:	BLOCK 1			;Alias "login user"
MYJOBN:	BLOCK 1			;Job number
	BLOCK <116-.>		;.JBSYM must be at 116
.JBSYM:	BLOCK 1			;Symbol table pointer
MUSRST:	BLOCK 10		;ASCII of login user
MAUSRS::BLOCK 10		;ASCII of alias login user
	BLOCK <140-.>		;Low segment must start at 140

	RELOC			;Enter low segment

LCLHNM:	BLOCK ^D13		;Local host name string without relative domain
	NPDL==277		;Size of PDL
PDL:	BLOCK NPDL		;Pushdown list

	.PSECT DATA,DATORG	;Enter data area

	NCPDL==477		;Size of command PDL
CMDRET::BLOCK 1			;Usual return dispatch for error
CMDSTK:	BLOCK 1			;Current command stack ptr
CMPDL:	BLOCK NCPDL		;Command stack
 HSTBFL==30
HSTBUF:	BLOCK HSTBFL		;Host name buffer for HSTSTR routines

SAVMOD:	BLOCK 5			;Normal TTY modes
EDMOD:	BLOCK 5			;Editor modes
WCMDPT:	BLOCK 1			;Working copy of command ptr
PREVPT:	BLOCK 1			;Pointer to previous message list
PRVSEQ:	BLOCK 1+<NMSGS/3>	;Previous message sequence list
PRVSQZ==.
MSGSEQ:	BLOCK 1+<NMSGS/3>	;Table of numbers of messages
MSGSQZ==.
WRKSEQ:	BLOCK 1+<NMSGS/3>	;Table of numbers of messages
ZERMEM==.			;Start clearing here at startup
SNDCAL:	BLOCK 1			;Caller of send subcommands
SEQCAL:	BLOCK 1			;Caller of header subcommands
OKTINT:	BLOCK 1			;Is it ok for timer to interrupt now?
CHKTIM:	BLOCK 1			;Next time to check for new messages
IFE NICSW,<
MSGJFN:	BLOCK 1			;JFN for current message file
>;IF NICSW
IFN NICSW,<
MSGJFN::BLOCK 1			;JFN for current message file
>;IFN NICSW
MSGJF2:	BLOCK 1			;JFN to open for write
OUTJFN:	BLOCK 1			;Output file JFN
INIJFN:	BLOCK 1			;MM.INIT JFN
TMPJFN:	BLOCK 1			;Temporary files
HSTJFN:	BLOCK 1			;Host tables, etc.
MSCANF:	BLOCK 1			;Msg scan direction flag
GTSQDF:	BLOCK 1			;GETSEQ default sequence (if >0)
HDONLY:	BLOCK 1			;List msg headers only
SEPPGS:	BLOCK 1			;List msgs on separate pages
WRKMSG:	BLOCK 1			;Current working msg "number,,index"
LASTM:	BLOCK 1			;Number of messages in current file
LASTRD:	BLOCK 1			;Date file last fetched

;; The following must be in this exact order.  They are filled by GTFDB%.
FILPGS:	BLOCK 1			;Size of the file in pages
FILSIZ:	BLOCK 1			;Size of the file (bytes)
FILCRV:	BLOCK 1			;Creation date
FILWRT:	BLOCK 1			;Write date
FILRD:	BLOCK 1			;Last read date of file
;; End GTFDB% block

NRECNT:	BLOCK 1			;Number of recent messages
NUNSEE:	BLOCK 1			;Number of unseen messages
NDELET:	BLOCK 1			;Number of deleted messages
PRIORM:	BLOCK 1			;Saved current message number
M.RPLY:	BLOCK 1			;Index to msg being replied to, -1=none
LSTMSG:	BLOCK 1			;Saved last message for typing out seq
DOMSG:	BLOCK 1			;Dispatch to process next message
NXTMSD:	BLOCK 1			;Dispatch to fetch next message
MSGSPT:	BLOCK 1			;Pointer into numerical msg sequence (MSGSEQ)
MSRNG:	BLOCK 1			;Range in progress flag: -1 if no range
				; else ending msg number
PSIPC1:	BLOCK 1			;Saved pc from psi routine
PSIPC2:	BLOCK 1			;Ditto
INPSIF:	BLOCK 1			;Flag non-zero when in PSI code
CTCCNT:	BLOCK 1			;Count of CTRL/C's while trapped
EXECFK:	BLOCK 1			;Saved fork handle for EXEC
EDFORK:	BLOCK 1			;Editor fork
EFRKPC:	BLOCK 1			;Editor fork's PC
EDPAG0:	BLOCK 1			;First page of editor fork mapped in
SPLFRK:	BLOCK 1			;SPELL's fork handle
SPLIFL:	BLOCK 1			;Input file JFN
SPLOFL:	BLOCK 1			;Output file JFN
AFTDAT:	BLOCK 1			;After parameter in GTAD% format
DLVOPT:	BLOCK 1			;Delivery option index
TOLIST:	BLOCK 1			;TO list pointers tail,,head
CCLIST:	BLOCK 1			;CC list pointers tail,,head
BCCLST:	BLOCK 1			;BCC list
FREETO:	BLOCK 1			;Pointer to free space for to/cc lists
;;;Following three cells must be in this order and correspond to the ADTYP defs
LCLIST:	BLOCK 1			;List of local recipients
FILIST:	BLOCK 1			;List of file "recipients"
NETLST:	BLOCK 1			;List of network recipients
;;;End of critical order
MSGSIZ:	BLOCK 1			;Size of last message we sent
MOVDSP:	BLOCK 1			;Dispatch for typing or setting to, etc
REPDAT:	BLOCK 1			;Reply date
SAVU:	BLOCK 1			;Used by address parser
SAVL:	BLOCK 1			;Saved sequence pointer
SAVP:	BLOCK 1			;Used by sequence parser
NXTIME:	BLOCK 1			;Time for before/after/on filters
CLEVEL::BLOCK 1			;Command/subcommand level
TPADD1:	BLOCK 1			;Top level command dispatch
TPADDR::BLOCK 1			;Reparse address for COMND reparsing
REPARP:	BLOCK 1			;Save of stack for reparse
READPP:	BLOCK 1			;Save of P in READ for REDRET
SENDPP:	BLOCK 1			;Save of P in SEND for SNDRET
LSTCHR:	BLOCK 1			;Confirming character
BUFNAM:	BLOCK 2			;Name of the editor buffer
EDINAM:	BLOCK 2			;Type of edit being performed
UNTHDR:	BLOCK 1			;Save of unto header word
KEYPTR:	BLOCK 1			;Pointer to keyflag string area
KEYBTS:	BLOCK 1			;Keyflag bits in a message sequence
KEYBTM:	BLOCK 1			;Keyflag bits to modify
KEYLPF:	BLOCK 1			;Pointer to "find" keyword list
KEYLPM:	BLOCK 1			;Pointer to "modify" keyword list
KEYFRE:	BLOCK 1			;Pointer to free space in keywd pages
KYCPYF:	BLOCK 1			;Temp for KYCPY, add/del flag
KYCPYC:	BLOCK 1			;Temp for KYCPY, edit count
RMLPTR:	BLOCK 1			;String pointer and flag for REMAIL
RSTMOD:	BLOCK 1			;Type of restore draft
MNSMSG:	BLOCK 1			;Whether or not to include msg in REPLY
SRTFRE:	BLOCK 1			;Free space ptr for sorting msgs
SRTTAB:	BLOCK 1			;Start of msg sorting tree
SRTLFT:	BLOCK 1			;Ptr to node with earliest date
SRTRGT:	BLOCK 1			;Ptr to node with latest date
NSORTD:	BLOCK 1			;Number of nontrivial sorts
SRTIDX:	BLOCK 1			;Index to current temp block
SRTBLK:	BLOCK 2			;Temp block ptr
SRBLK0:	BLOCK MSGLEN		;Temp storage for sorting
SRBLK1:	BLOCK MSGLEN

; The following AC blocks are for routines which save ACs but don't need to
;save P
TMRACS:	BLOCK 17		;During timer interrupt routines
ABOACS:	BLOCK 17		;AC save during abort routines
ABOCAN:	BLOCK 1			;-1 to enable aborts
ABOIP:	BLOCK 1			;Abort In Progress if -1
ABOSTS:	BLOCK 1			;Current state of CTRL/N (-1 if armed)
ABORTF:	BLOCK 1			;Abort seen, set by unvectored CTRL/N
ABOPDP:	BLOCK 1			;APDL abort stack pointer

IFNDEF APDLLN,<APDLLN==20>	;Allow this many abort nestings
	BLOCK 3			;Zero-entry fence for abort stack
APDL:	BLOCK APDLLN*3		;Abort stack (3 wds/entry)
 FRMSTL==^D99
FRMSAM:	BLOCK <FRMSTL/5>+1	;"From: " string for all msgs
FRMSCM:	BLOCK <FRMSTL/5>+1	;"From: " string for current msg
REPSAM:	BLOCK <FRMSTL/5>+1	;"Reply-to: " string for all msgs
REPSCM:	BLOCK <FRMSTL/5>+1	;"Reply-to: " string for current msg
COMNDB:	BLOCK 1			;AC2 of last COMND
DOMTBL:	BLOCK 1			;Address of domain TBLUK table.  Actually
				;used only as a flag that $INRLY has run

;;Storage for BBoard code
BBLWD:	BLOCK 1			;Last write date of current BBoard file
	BBXPAG=WRTPGS		;Where to map index page to
	UXPAG==20		;Page in IDX file of user data
IDXJFN:	BLOCK 1			;Index file JFN
IDXNAM:	BLOCK 20		;Name of index file
BBXDAT:	BLOCK 1			;Last idx date known
BBCURR:	BLOCK 1			;Current BB for stepping
BBMAX:	BLOCK 1			;Max number of BBs for quick comparison
BBTAB::	BLOCK 1			;TBLUK%-like table (not alphabetical)
	BLOCK MAXBBD		;Entry: address of string,,0
ZEREND==.-1			;End of where to clear

BBDTAB:	BLOCK MAXBBD+1		;BBoard table
	BLOCK MAXBBD*4		;String space for BBoards
BBDEND: BLOCK 10		;Allow for overflow
BBDSTR: BLOCK 1			;Pointer to first free BBoard string
;;;User variables

VARBEG==.
RSCFLG::BLOCK 1			;Return to MAIL.TXT on BB rescan if .NE. 0
TRSTPR::BLOCK 1			;Terse text prompt
LPTCFM::BLOCK 1			;Lineprinter conformation
VBSBBD::BLOCK 1			;Quiet flag for INDEX stuff
FLMAUT::BLOCK 1			;Flagged messages autotype suppress
USEEDT::BLOCK 1			;Use the editor automatically
RINCME::BLOCK 1			;Include me in any replies by default
RCCOTH::BLOCK 1			;Reply cc's everyone other than from
GTCNDR::BLOCK 1			;>0 conn dir always, <0 postbox, 0 ask
INITER::BLOCK 1			;-1 if an error occurred in MM.INIT
ESCSND::BLOCK 1			;Escape sends automatically
JISMOD::BLOCK 1			;JIS mode
SIMODE::BLOCK 1			;SI/SO mode
REPDIS::BLOCK 1			;Reply command automatically displays
RFMDEF::BLOCK 1			;Reply<cr> means just from, not all
BLSCST::BLOCK 1			;Blank screen on startup
BSPDSP::BLOCK 1			;Output backspace instead of CTRL/H
CRSEND::BLOCK 1			;Just return sends message
LSTHDR::BLOCK 1			;Output a list of headers at the start
				; of the listing
SNDVBS::BLOCK 1			;Degree of sending verbosity
ABOFLG::BLOCK 1			;CTRL/N aborts >0 always, 0 ask, <0 never
EDTFLG::BLOCK 1			;CTRL/E edits >0 always, 0 ask, <0 never
LSTPAG::BLOCK 1			;List messages on separate pages
SAVFIL::BLOCK 42		;SAVED.MESSAGES file to use
MCPFIL::BLOCK 42		;MAIL.CPY file to use
PERNAM::BLOCK 20		;Personal name
DEFCCL::BLOCK 20		;Default cc list
DEFBCL::BLOCK 20		;Default bcc list
DEFPRO::BLOCK 1			;Default protection for .TXT files
DEFPST::BLOCK 2			;String version of above
KEYTBL::BLOCK <^D31>		;Table of keywords for messages
USRHTB::BLOCK <^D31>		;Table of user message headers
SPRHDR::BLOCK <^D31>		;Table of headers to not type out
ONLHDR::BLOCK <^D31>		;Table of headers to only type out
TOPRMT::BLOCK 10		;Top-level prompt string
REPRMT::BLOCK 10		;Read-level prompt string
SEPRMT::BLOCK 10		;Send-level prompt string
MSPRMT::BLOCK 10		;Message sequence prompt string
ASKBCC::BLOCK 1			;Prompt for bcc recipients in send
LSTDEV::BLOCK 10		;Listing device file name
INSMSG::BLOCK 1			;Always insert msg in reply text
DFSHML::BLOCK 1			;Default "short" msg length
DEFBBD::BLOCK 10		;Default BBoard
VAREND==.-1
;;; COMND buffers

QPRMPT:	BLOCK 24		;Space for a prompt string
NXTPAT:	BLOCK 1			;Adr of cmd pattern string
PATFRE:	BLOCK 1			;Adr of next pattern string
	CSBFSZ==2000
CSBUF:	BLOCK CSBFSZ		;Command line buffer
	PATSTR==CSBUF+CSBFSZ/2	;Also used for from filters
CMDGTB:	BLOCK .GJATR+1		;GTJFN% block
CMDFLB::BLOCK 4			;Individual field block
	STRBSZ==2000
STRBUF::BLOCK STRBSZ		;Temporary string space
TMPBUF=STRBUF+400
FILNAM=STRBUF+STRBSZ-100
	LEVPLN==20
LEVPDL=STRBUF+STRBSZ-LEVPLN
;;; Non-zeroed storage

SPLNAM:	ASCIZ/SYS:SPELL.EXE/	;Name of SPELL program
SPLOFF==2			;Entry vector offset to run at

TTXTIB:	.RDBRK			;Number of words in this block
	BLOCK .RDBRK		;Remainder of block
TXTPTR==TTXTIB+.RDDBP		;Put updated pointer here
TXTCNT==TTXTIB+.RDDBC		;Put count here

CMDBLK::BLOCK .CMGJB+1		;COMND state block

;Initial CSB contents
CMIBLK:	REPARS			;.CMFLG Flag bits,,Reparse dispatch adr
	.PRIIN,,.PRIOU		;.CMIOJ Input JFN,,Output JFN
	0			;.CMRTY Byte pointer to CTRL/R text
	POINT 7,CSBUF		;.CMBFP Byte pointer to start of text
	POINT 7,CSBUF		;.CMPTR Byte pointer to next input
	CSBFSZ*5		;.CMCNT Count of space left in buffer
	0			;.CMINC Count of chars left in buffer
	POINT 7,STRBUF		;.CMABP Byte pointer to atom buffer
	STRBSZ*5		;.CMABC Size of atom buffer
	CMDGTB			;.CMGJB Address of GTJFN% argument block

REQID=='MM'			;Request ID for our ENQing

ENQBLK:	1,,ENQBLL		;Number of locks, block size
	REQID			;Interrupt channel, request ID
	0			;Flags, level number,,JFN
	-1,,ENQNAM		;Pointer to name string
	0			; (this name used because MS uses it)
	0
ENQBLL==.-ENQBLK		;Length of ENQ% BLOCK
ENQNAM:	ASCIZ/Mail expunge interlock/

	.ENDPS
	SUBTTL Pure storage

	.PSECT CODE,CODORG	;Enter code

;;;Mailbox file name strings

MLBXDV:	ASCIZ/POBOX/
MLBXFN:	ASCIZ/MAIL.TXT.1/
MLBXNM:	ASCIZ/MAIL/
MLBXEX:	ASCIZ/TXT/

IFE NICSW,<
BBDIR:	ASCIZ/BBOARD/
>;IFE NICSW
IFN NICSW,<
BBDEV:	ASCIZ/BBD/
>;IFN NICSW

;;;Break mask for slurping up a hostname

	BRINI.
	BRKCH. (.CHNUL,042)	;Everything below #, $
	BRKCH. (045,054)	;...until -, .
	BRKCH. (057)		;...until numerics
	BRKCH. (072,100)	;...until uppercase alphabetics, [
	BRKCH. (134)		;...until ]
	BRKCH. (136,140)	;...until lowercase alphabetics
	BRKCH. (173,177)	;...everything above "z"

HNMMSK:	EXP W0.,W1.,W2.,W3.	;Mask for host name

;;;Break mask for slurping up a user name

	BRINI.
	BRKCH. (.CHNUL,042)	;Everything below #, $, %
	BRKCH. (046,051)	;...until *
	BRKCH. (053,054)	;...until -, .
	BRKCH. (057)		;...until numerics
	BRKCH. (072,100)	;...until uppercase alphabetics
	BRKCH. (133,136)	;...until underscore
	BRKCH. (140)		;...until lowercase alphabetics
	BRKCH. (173,177)	;...everything above "z"

UNMMSK:	EXP W0.,W1.,W2.,W3.	;Mask for user name

;;;Text input masks

	BRINI.
	BRKCH. (.CHCNB)
	BRKCH. (.CHCND)
	BRKCH. (.CHCNE)
	BRKCH. (.CHVTB)
	BRKCH. (.CHFFD)
	BRKCH. (.CHCNZ)
	BRKCH. (.CHESC)
TXTMSK:	EXP W0.,W1.,W2.,W3.	;Mask for ordinary text input

;;;Japanese Industrial Standard masks

	BRINI.
	BRKCH. (.CHCNB)
	BRKCH. (.CHCND)
	BRKCH. (.CHCNE)
	BRKCH. (.CHVTB)
	BRKCH. (.CHFFD)
	BRKCH. (.CHCNZ)
TXTJIS:	EXP W0.,W1.,W2.,W3.	;Mask for JIS text entry

	BRINI.
	BRKCH. (.CHLFD)
	BRKCH. (.CHCRT)
LINJIS:	EXP W0.,W1.,W2.,W3.	;Mask for JIS line entry

;;;Table of special characters which are quoted

	BRINI.
	BRKCH. (.CHNUL,.CHSPC)	;all controls are special characters
	BRKCH. (042)		;""""
	BRKCH. (050,051)	;"(", ")"
	BRKCH. (054)		;","
	BRKCH. (072,074)	;":", ";", "<"
	BRKCH. (076)		;">"
	BRKCH. (100)		;"@"
	BRKCH. (133)		;"["
	BRKCH. (134)		;"\"
	BRKCH. (135)		;"]"

SPCMSK:	EXP W0.,W1.,W2.,W3.	;Form table of special characters

;;;Interrupt storage

LEVTAB:	PSIPC1
	PSIPC2
	0

CHNTAB:	PHASE 0
CTCCHN:!1,,CTCINT		;CTRL/C trap on chan 0
	BLOCK 3
ABOCHN:!1,,ABOINT		;CTRL/N on chan 4
TMRCHN:!2,,TMRINT		;Timer on chan 5
	BLOCK <^D36-.>		;Interrupt vector table
	DEPHASE

;;;Entry vector

IFNDEF VI%DEC,<			;In case MACSYM is prior to release 6
 VI%DEC==1B18
>;IFNDEF VI%DEC

EVEC:	JRST GO			;Entry vector
	JRST GOAMOD
VERNUM:	VI%DEC!<FLD VWHO,VI%WHO>!<FLD VMAJ,VI%MAJ>!<FLD VMIN,VI%MIN>!<FLD VEDIT,VI%EDN>
EVECL==.-EVEC
	SUBTTL Command tables

;;;Top level commands

CMDTAB:	NCMDS,,NCMDS 
	CMD1 A,ENTANS,CM%ABR!CM%INV
	CMD ALIAS
ENTANS:	CMD ANSWER
	CMD APPEND
	CMD1 BB,ENTBB,CM%ABR!CM%INV
	CMD BBDATE
ENTBB:	CMD BBOARD
	CMD BLANK
	CMD BUG
	CMD CHECK
	CMD CONTINUE
	CMD COPY
	CMD COUNT
	CMD CREATE-INIT,.CRINI
	CMD1 D,ENTDEL,CM%ABR!CM%INV
	CMD DAYTIME
IFN ALIASW,<
	CMD DEFINE
>
ENTDEL:	CMD DELETE
	CMD DIRED
	CMD DISABLE
	CMD ECHO
	CMD EDIT
	CMD ENABLE
	CMD1 EX,ENTXIT,CM%ABR!CM%INV
	CMD EXAMINE
ENTXIT:	CMD EXIT
	CMD EXPUNGE
	CMD FILE-LIST,.FLIST
	CMD FIND
	CMD FLAG
	CMD FORWARD
	CMD FROM
	CMD GET
	CMD1 H,ENTHDR,CM%ABR!CM%INV
ENTHDR:	CMD HEADERS
	CMD HELP
	CMD IGNORE
	CMD JUMP
	CMD1 K,ENTKIL,CM%ABR!CM%INV
	CMD KEYWORDS
ENTKIL:	CMD KILL
IFN NICSW,<
	CMD LASER
>
	CMD LIST
	CMD LITERAL-TYPE,.LTYPE
	CMD LOGOUT
	CMD1 MA,ENTMRK,CM%ABR!CM%INV
	CMD1 MAIL,.SEND,CM%INV
ENTMRK:	CMD MARK
	CMD MOVE
	CMD1 N,ENTNXT,CM%ABR!CM%INV
	CMD NET-MAIL,.MAILE
ENTNXT:	CMD NEXT
IFE NICSW,<
	CMD PREVIOUS
>
IFN NICSW,<
	CMD P,ENTPRV,CM%ABR!CM%INV	;[NIC2037]
	CMD PR,ENTPRV,CM%ABR!CM%INV	;[NIC2037]
ENTPRV:	CMD PREVIOUS			;[NIC2037]
>
	CMD PROFILE
	CMD PUSH
	CMD QUIT
	CMD1 R,ENTRED,CM%ABR!CM%INV
	CMD1 RE,ENTRED,CM%ABR!CM%INV
ENTRED:	CMD READ
	CMD REMAIL
	CMD1 REP,ENTREP,CM%ABR!CM%INV
	CMD1 REPL,ENTREP,CM%ABR!CM%INV
ENTREP:	CMD REPLY,.ANSWER
	CMD REPLY-TO,.REPTO
	CMD RESTORE-DRAFT,.RESTO
	CMD1 S,ENTSND,CM%ABR!CM%INV
	CMD1 SE,ENTSND,CM%ABR!CM%INV
ENTSND:	CMD SEND
	CMD SET
	CMD SHOW
	CMD SORT
	CMD STATUS
	CMD STEP
	CMD SYSTEM-MSGS,.SYSTE
	CMD1 T,ENTTYP,CM%ABR!CM%INV
	CMD TAKE
ENTTYP:	CMD TYPE
	CMD1 U,ENTUND,CM%ABR!CM%INV
	CMD UNANSWER
ENTUND:	CMD UNDELETE
	CMD UNFLAG
	CMD UNKEYWORDS
	CMD UNMARK
	CMD VERSION
IFN NICSW,<
	CMD VREPLY
	CMD VSEND
>
NCMDS==.-CMDTAB-1

;;;READ commands

RCMDTB:	NRCMDS,,NRCMDS
	CMD1 ANSWER,.REPLY,CM%INV
	CMD BLANK
	CMD CONTINUE
	CMD COPY
	CMD1 D,ENTRDE,CM%ABR!CM%INV
	CMD DAYTIME
ENTRDE:	CMD DELETE,.RDELM
	CMD ECHO
	CMD EDIT,.REDIT
	CMD FILE-LIST,.FLIST
	CMD FLAG,.RFLAG
	CMD FORWARD,.RFORW
	CMD1 H,ENTRHE,CM%ABR!CM%INV
ENTRHE:	CMD HEADER,.RHEAD
	CMD HELP
	CMD1 K,ENTRKI,CM%ABR!CM%INV
	CMD KEYWORDS,.RKEYW
ENTRKI:	CMD KILL,.RKILL
IFN NICSW,<
	CMD LASER
>
	CMD LIST
	CMD LITERAL-TYPE,.LRTYP
	CMD1 M,ENTRMV,CM%ABR!CM%INV
	CMD1 MAIL,.SEND,CM%INV
	CMD MARK,.RMARK
ENTRMV:	CMD MOVE
	CMD1 N,ENTRNE,CM%ABR!CM%INV
	CMD NET-MAIL,.MAILE
ENTRNE:	CMD NEXT,.RNEXT
	CMD1 P,ENTRPR,CM%ABR!CM%INV
ENTRPR:	CMD PREVIOUS,.RPREV
	CMD PUSH
	CMD QUIT,.RQUIT
	CMD1 R,ENTRRP,CM%ABR!CM%INV
	CMD1 RE,ENTRRP,CM%ABR!CM%INV
	CMD REMAIL,.RREMA
ENTRRP:	CMD REPLY
	CMD1 S,ENTSEN,CM%ABR!CM%INV
ENTSEN:	CMD SEND
	CMD SPELL,.RSPEL
	CMD1 T,ENTRTY,CM%ABR!CM%INV
	CMD TAKE
ENTRTY:	CMD TYPE,.TYPMS
	CMD1 U,ENTRUN,CM%ABR!CM%INV
	CMD UNANSWER,.RUNAN
ENTRUN:	CMD UNDELETE,.RUDLM
	CMD UNFLAG,.RUFLG
	CMD UNKEYWORDS,.RUKYW
	CMD UNMARK,.RUMRK
IFN NICSW,<
	CMD VREPLY
	CMD VSEND
>
NRCMDS==.-RCMDTB-1

;;;SEND (and REPLY) commands

SCMDTB:	NSCMDS,,NSCMDS
	CMD AFTER
	CMD BCC
	CMD BLANK
	CMD CC
	CMD1 D,ENTSDI,CM%ABR!CM%INV
	CMD DAYTIME
	CMD DELIVERY-OPTIONS,.DELIV
ENTSDI:	CMD DISPLAY
	CMD ECHO
	CMD EDIT,.SEDIT
	CMD ERASE
	CMD FROM
	CMD HELP
	CMD INSERT,.INSFL
	CMD LITERAL-TYPE,.LRTYP
	CMD1 MAIL,.SSEND,CM%INV
	CMD PUSH
	CMD QUIT,.SQUIT
	CMD REMOVE,.UNTO
	CMD REPLY-TO,.REPTO
	CMD RESTORE-DRAFT,.SREST
	CMD1 S,ENTSDR,CM%ABR!CM%INV
	CMD SAVE-DRAFT,.SSAVE
ENTSDR:	CMD SEND,.SSEND
	CMD SPELL,.SSPEL
	CMD SUBJECT
	CMD1 T,ENTSTY,CM%ABR!CM%INV
	CMD TAKE
	CMD TEXT
	CMD TO
ENTSTY:	CMD TYPE,.TYPMS
	CMD USER-HEADER,.USHDR
NSCMDS==.-SCMDTB-1

;;;ERASE commands

ECMDTB:	NECMDS,,NECMDS
	CMD ALL,.ERSAL
	CMD BCC,.ERSBC
	CMD CC,.ERSCC
	CMD REPLY-DATE,.ERSDT
	CMD SUBJECT,.ERSSB
	CMD TEXT,.ERSTX
	CMD TO,.ERSTO
NECMDS==.-ECMDTB-1

;;;DISPLAY commands

DCMDTB:	NDCMDS,,NDCMDS
	CMD ALL,.DSALL
	CMD BCC,.DSBCC
	CMD CC,.DSCC
	CMD FROM,.DSFRM
	CMD HEADER,.DSHDR
	CMD REPLY-TO,.DSREP
	CMD SUBJECT,.DSSUB
	CMD TEXT,.DSTXT
	CMD TO,.DSTO
NDCMDS==.-DCMDTB-1

;;;EDIT commands

EDCMTB:	NEDCMS,,NEDCMS
	CMD HEADERS,.EDHEA
	CMD TEXT,.EDTXT
NEDCMS==.-EDCMTB-1

;;;REPLY commands

RPCMTB:	NRPCMS,,NRPCMS
	CMD ALL,.REPAL
	CMD SENDER,.REPFM
NRPCMS==.-RPCMTB-1

;;;Sequence commands

SQCMTB:	NSQCMS,,NSQCMS
	CMD1 A,ENTALL,CM%INV!CM%ABR
	CMD1 AFTER,STQAFT,CM%INV
ENTALL:	CMD ALL,STQALL
	CMD ANSWERED,STQANS
	CMD BEFORE,STQBEF
	CMD1 C,ENTCUR,CM%INV!CM%ABR
	CMD CC-ME,STQCCM
ENTCUR:	CMD CURRENT,STQCUR
	CMD DELETED,STQDEL
	CMD1 F,ENTFRM,CM%INV!CM%ABR
	CMD FLAGGED,STQFLG
	CMD1 FR,ENTFRM,CM%INV!CM%ABR
	CMD1 FRO,ENTFRM,CM%INV!CM%ABR
ENTFRM:	CMD FROM,STQFRM
	CMD FROM-ME,STQFMM
	CMD INVERSE,STQREV
	CMD KEYWORDS,STQKYW
	CMD1 L,ENTLST,CM%INV!CM%ABR
ENTLST:	CMD LAST,STQLST
	CMD LONGER,STQLNG
	CMD NEW,STQNEW
	CMD ON,STQON
	CMD PREVIOUS-SEQUENCE,STQPRV
	CMD RECENT,STQREC
	CMD SEEN,STQSEE
	CMD SHORTER,STQSHT
	CMD SINCE,STQAFT
	CMD SUBJECT,STQSBJ
	CMD1 T,ENTTO,CM%INV!CM%ABR
	CMD TEXT,STQTXT
ENTTO:	CMD TO,STQTO
	CMD TO-ME,STQTOM
	CMD1 U,ENTUNS,CM%ABR!CM%INV
	CMD UNANSWERED,STQUNA
	CMD UNDELETED,STQUND
	CMD UNFLAGGED,STQUNF
	CMD UNKEYWORDS,STQUKW
ENTUNS:	CMD UNSEEN,STQUNS
NSQCMS==.-SQCMTB-1

;;;RSCAN commands

RSCMTB:	NRSCMS,,NRSCMS
	CMD ALIAS
	CMD BBOARD
	CMD BUG
	CMD EXAMINE
	CMD FIND
	CMD GET
	CMD HEADERS,.RSHEA
	CMD1 R,ENTRSR,CM%INV!CM%ABR
ENTRSR:	CMD READ,.RSREA
	CMD RESTORE-DRAFT,.RESTO
	CMD1 S,ENTSNR,CM%INV!CM%ABR
ENTSNR:	CMD SEND
	CMD SYSTEM-MSGS,.SYSTE
	CMD1 T,ENTTYR,CM%ABR!CM%INV
	CMD TAKE
ENTTYR:	CMD TYPE,.RSTYP
IFN NICSW,<
	CMD VSEND
>
NRSCMS==.-RSCMTB-1

;;;Date keywords

DATTAB:	NDATBS,,NDATBS
	VAR FRIDAY,DATDOW,4
	VAR MONDAY,DATDOW,0
	VAR SATURDAY,DATDOW,5
	VAR SUNDAY,DATDOW,6
	VAR THURSDAY,DATDOW,3
	VAR TODAY,DATDAY,0
	VAR TUESDAY,DATDOW,1
	VAR WEDNESDAY,DATDOW,2
	VAR YESTERDAY,DATDAY,1
NDATBS==.-DATTAB-1

FLTAB:	NFLTAB,,NFLTAB
	VAR FIRST,DATFST
	VAR LAST,DATLST
	VAR LOGIN,LOGLST
NFLTAB==.-FLTAB-1

	PURGE VAR		;Last occurance

;;;Holiday keywords

HOLDAY:	NHLDYS,,NHLDYS
	HDY APRIL-FOOLS,4,1
	HDY BASTILLE-DAY,7,14
	HDY BEETHOVENS-BIRTHDAY,12,16
	HDY BILBOS-BIRTHDAY,9,22
	HDY CHRISTMAS,12,25
	HDY COLUMBUS-DAY,10,12
	HDY FLAG-DAY,6,14
	HDY FRODOS-BIRTHDAY,9,22
	HDY GONDORIAN-NEW-YEAR,3,25
	HDY GROUND-HOGS-DAY,2,2
	HDY GUY-FAWKES-DAY,11,5
	HDY HALLOWEEN,10,31
	HDY INDEPENDENCE-DAY,7,4
	HDY LEAP-DAY,2,29
	HDY LINCOLNS-BIRTHDAY,2,12
	HDY MAY-DAY,5,1
	HDY MEMORIAL-DAY,5,30
	HDY NEW-YEARS,1,1
	HDY SAINT-PATRICKS-DAY,3,17
	HDY SHERLOCK-HOLMES-BIRTHDAY,1,6
	HDY VALENTINES-DAY,2,14
	HDY WASHINGTONS-BIRTHDAY,2,22
NHLDYS==.-HOLDAY-1

	PURGE HDY
	SUBTTL Interrupt routines

;;;Timer interrupt

TMRINT:	MOVEM 16,TMRACS+16
	MOVEI 16,TMRACS
	BLT 16,TMRACS+15
	CALL SETTIM		;Set next timer up
	SKIPE OKTINT		;OK for timer at this time?
	 CALL CHECKT		;Yes, check for new messages
TMRIN0:	MOVSI 16,TMRACS
	BLT 16,16
	DEBRK%			;No, return

SETTIM:	MOVE A,[.FHSLF,,.TIMEL]	;Elapsed time
	MOVE B,[^D<5*60*1000>]	;5 minutes
	MOVEI C,TMRCHN		;Timer channel
	TIMER%
	 NOP
	RET
; Finds difference of two 7-bit byte pointers
; Returns A/ B - C

PTRDIF:	SAVEAC <B,C>
	STKVAR <PTRSAV>
	MOVEM C,PTRSAV
	MULI B,5		;Convert to canonical form
	ADD C,UADBP7(B)		; with help of magic table
	MOVE A,C		;Save it
	MOVE B,PTRSAV		;Now convert second BP in same way
	MULI B,5
	ADD C,UADBP7(B)
	SUB A,C			;Get difference B-C
	RET

	ENDSV.

	133500,,0		;To handle -5 produced by 440700
	BLOCK 4
UADBP7:	-54300,,5
	-104300,,4
	-134300,,3
	-164300,,2
	-214300,,1
;;;Interrupt control routines
; Trap CTRL/C interrupts
.NOINT:	SAVEAC <A>
	SETZM CTCCNT		;Clear accumulated count
	MOVE A,[.TICCC,,0]	;Assign CTRL/C to channel 0
	ATI%
	 ERJMP .+1		;Oh well, we tried
	RET
	
; Untrap CTRL/C interrupts
.OKINT:	SAVEAC <A>
	MOVX A,.TICCC		;Deassign CTRL/C
	DTI%
	 ERJMP .+1		;Oh well, we tried
	SKIPN A,CTCCNT		;Any seen?
	 RET
	CAIG A,1		;Hot call?
	IFSKP.
	  MOVX A,.PRIIN		;Yes, clear buffers
	  CFIBF%
	  MOVX A,.PRIOU
	  CFOBF%
	ENDIF.
	SETZM CTCCNT		;Show these accounted for
	HRROI A,[ASCIZ/^C/]
	PSOUT%			;(Might be in UUO stuff)
	HALTF%			;Return to upper fork now
	RET			;Carry on

; CTRL/C interrupt comes here
CTCINT:	AOS CTCCNT		;Count it
	DEBRK%			;And return for now
;;;CTRL/N interrupt (abort) routines
; How to use the abort routines:
;  Abort handling is set up in a structured fashion, so that low level
; routines can handle aborts without the higher level routines knowing
; about them.  Likewise it is possible for routines to "undo" some things
; when aborted, before passing the abort higher up.  In the simplest
; case an abort will just set a flag which the routine can check when it
; gets around to it.  All this is done by means of an abort stack, APDL.
; Note that control-N can be be either "armed" or disabled without
; affecting the abort stack.  An "abort" is usually but not necessarily
; generated by a control-N; in particular, the ABORET routine will trigger
; an abort.  All aborts, at all levels, can be disabled by clearing ABOCAN.
;
;	To initialize, CALL ABOINI.  CTRL/N is left turned off.
;	To specify an abort vector:
;		SETABT <loc> ;The previous abort vector is pushed.
;			;An abort will reset P to its value at
;			;the time SETABT was done, and jump to <loc>.
;	To unspecify a vector:
;		RET	;Restores the previous vector and returns.
; Flags:
;	ABORTF - set when aborted but vector is null.
;		Cleared by ABOINI and by dispatch to a non-null vector.
;	ABOSTS - state of CTRL/N.  0 = enabled, -1 = disabled.
;		Saved by SETABT, restored by RET if ABOCAN permits it
;		Also restored by abort, but actual CTRL/N state will be off.
;	ABOCAN - 0 = keep CTRL/N and aborts off, -1 = can abort.
;
; An abort will:
;	(1) ask the user for confirmation, if appropriate
;	(2) pop the abort stack, restoring:
;		PDL ptr saved from SETABT
;		CTRL/N state saved from SETABT
;	(3) turn off CTRL/N without altering "CTRL/N state", which now indicates
;		whether it is OK to turn CTRL/N back on or not.
;	(4) dispatch to the popped abort vector.
;
;	The routine vectored to is responsible for re-enabling
;	and/or propagating aborts by calling ABORET, since
;	CTRL/N has been turned off to ensure the routine isn't
;	itself clobbered until it's ready.  If all levels call ABORET,
;	an abort will percolate back up to the topmost layer in controlled
;	fashion.
;
; Turning aborts off:
;	The good way to turn aborts off within a section of code is:
;		SETABT
;		CALL ABNOFF
;		... code ...
;		RET
;	This is better than simply calling ABNOFF because the previous
;	abort state is saved and restored.  E.G. just doing ABNOFF and
;	then ABNON would lose if aborts had been off prior to ABNOFF!
; ABOINI - Initialize abort routines.  Clears stack, leaves ctl-N
;	turned off.  Does not touch ABOCAN.

ABOINI:	CALL ABNOFF		;Turn off control-N first
	SETZM ABOPDP		;Clear abort stack
	SETZM ABOIP		;Clear abort-in-progress flag
	SETZM ABORTF		;And abort-seen flag
	RET

; SETABT <loc>	- set abort vector, save PDL
;	If no argument, default is that aborts just set the ABORTF flag.
;	A routine can then just periodically check this with a SKIPGE.

DEFINE SETABT (LOC) <
 CALL $ABSET
  NOP LOC+0
>;DEFINE SETABT

$ABSET:	PUSH P,A		;Preserve these AC's
	PUSH P,B
	SKIPN A,ABOPDP		;Get abort PDL ptr
	 MOVE A,[-APDLLN*3,,APDL-1]
	PUSH A,ABOSTS		;Save CTRL/N state
	HRRZ B,@-2(P)		;Save abort vector
	PUSH A,B
	MOVE B,P
	ADJSP B,-3		;Get P as of SETABT invocation
	PUSH A,B		;Save that too.
	MOVEM A,ABOPDP		;Update abort PDL ptr.
	POP P,B			;Restore AC's
	MOVE A,[PC%USR+$ABRET]	;Routine to undo $ABSET
	EXCH A,-1(P)		;Stack it, get our return
	EXCH A,(P)		;Restore A, stack return from $ABSET for RET
	SKIPGE ABORTF		;If abort already attempted,
	 JRST ABORET		; trigger this level!
	SKIPE ABOCAN		;If allowed to,
	 JRST ABNON		; return with ctl-N enabled
	CALLRET ABNOFF		;Else make sure it's off.
;;;$ABRET - Pop abort vector and PDL, entered by CALLRET $ABRET.
;;;Triggers abort for next level if ABORTF flag is set.  If the current
;;;stack level doesn't match the stack level for this abort, we run down
;;;the abort stack until we find the abort matching this stack level or
;;;we run out of space.  This is so main stack backing up due to an error
;;;will work.

$ABRET:	PUSH P,A		;Can't use SAVEAC because of test below
	PUSH P,B
	SKIPN A,ABOPDP		;Get abort PDL ptr
	 FATAL ($ABRET called without any abort context)
	DO.
	  POP A,B		;Get PDL ptr saved by last SETABT
	  IFE. B
	    FATAL ($ABRET called at invalid stack level)
	  ENDIF.
	  ADJSP B,2		;Compensate for stuff pushed on stack
	  ADJSP A,-1		;Flush abort vector
	  CAMN B,P		;PDL must be same as when SETABT given.
	  IFSKP.
	    ADJSP A,-1		;Flush CTRL/N status
	    LOOP.		;Now try a level lower
	  ENDIF.
	ENDDO.
	POP A,ABOSTS		;Restore CTRL/N state
	SKIPE ABOSTS		;Should it be off?
	 SKIPN ABOCAN		; or did someone turn us off?
	  CALL ABNOFF		; Ensure off.
	MOVEM A,ABOPDP		;Put back updated APDL ptr
	POP P,B
	POP P,A
	SKIPE ABOSTS		;If new status wants it,
	 CALL ABNON		; ensure CTRL/N on.
	SKIPN ABORTF		;If a "quiet" abort happened,
	 RET
	CALLRET ABORET		; try to propagate it.
; ABNDIS - Disable CTRL/N (abort vector stack not reset)
ABNDIS:	SETZM ABOSTS		;Say CTRL/N is off
ABNDS0:	SAVEAC <A>
	MOVX A,.TICCN		;Deassign CTRL/N
	DTI%
	 ERJMP .+1
	MOVX A,.TICCX		;Deassign CTRL/X
	DTI%
	 ERJMP .+1
	RET

; ABNOFF - Disallow CTRL/N abort (abort vector stack not reset)
; ABNON - Allow CTRL/N abort (abort vector stack not reset)
ABNOFF:	SETZM ABOSTS		;Say CTRL/N is off
	CAIA
ABNON:	 SETOM ABOSTS		;Say CTRL/N is on
	SKIPGE ABOFLG		;Never arm if user doesn't want aborts
	 JRST ABNDS0
	SAVEAC <A>
	SKIPE SIMODE		;Don't do this if Katakana
	IFSKP.
	  MOVE A,[.TICCN,,4]	;Assign CTRL/N on chan 4
	  ATI%
	   ERJMP .+1
	ENDIF.
	MOVE A,[.TICCX,,4]	;Assign CTRL/X on chan 4
	ATI%
	 ERJMP .+1
	RET

; ABORET - Re-invokes abort for current (just-popped) vector if allowed to.
; Returns from user vector routine.

ABORET:	SETZM ABORTF		;Clear flag to avoid confusion
	SKIPE ABOCAN		;Aborts disabled?
	 SKIPN ABOSTS		; or CTRL/N off at this level?
	  JRST ABNOFF		;  Sigh, don't trigger higher abort.
	CALL ABNON		;Hurray, ensure CTRL/N really on.
	SAVEAC <A,B>
	MOVX A,.FHSLF
	MOVX B,1B4
	IIC%			;Trigger an abort as if CTRL/N typed. 
	RET
; Abort interrupt routine

ABOINT:	SKIPN ABOIP		;CTRL/N abort already in progress?
	 SKIPN ABOSTS		;Or, is CTRL/N action turned off?
	  DEBRK%		;Yes, go away peacefully, having eaten CTRL/N
	MOVEM 16,ABOACS+16	;Here on actual interrupt
	MOVEI 16,ABOACS
	BLT 16,ABOACS+15
	MOVX A,.PRIIN
	RFMOD%
	TXZE B,TT%OSP		;Cancel CTRL/O if enabled
	 SFMOD%
	DO.
	  SKIPGE A,ABOFLG	;Never abort?
	   EXIT.		;Yes, just dismiss
	  IFE. A		;Need confirmation?
	    CALL ABOCFM		;Yes, confirm abort
	     EXIT.		;User said no
	  ENDIF.
	  SKIPE A,ABOPDP	;Get abort PDL ptr
	   SKIPN -1(A)		;Make sure abort vector non-zero
	  IFSKP.
	    POP A,P		;Restore PDL ptr saved by SETABT.
	    POP A,PSIPC1	;Put abort vector into dispatch loc
	    POP A,ABOSTS	;Restore CTRL/N status
	    MOVEM A,ABOPDP	;Put back updated abort-PDL ptr.
	    SETOM ABOIP		;Set abort in progress flag
	    SETZM ABORTF	;Clear flag, since action being taken.
	  ELSE.
	    SETOM ABORTF	;Here to set flag and return
	  ENDIF.
	ENDDO.
	MOVSI 16,ABOACS		;Restore Abort ACs
	BLT 16,16
	DEBRK%
;;;Confirm an abort.  Saves state of command parse in case no abort
SBFLEN==20			;Length of text/atom buffers stolen from stack

ABOCFM:	STKVAR <ABSREP,<ABSCMD,<.CMGJB+1>>,<ABOTXB,SBFLEN>,<ABOATB,SBFLEN>>
	MOVX A,.PRIIN		;Clear typeahead
	CFIBF%
	MOVE A,REPARP		;Save old reparse address
	MOVEM A,ABSREP
	HRLI A,CMDBLK		;Location of command block to save
	HRRI A,ABSCMD		;Location where to save to
	BLT A,.CMGJB+ABSCMD	;Save command block
	JRST ABOPMT		;Can't do a PROMPT UUO here
ABOCF1:	CALL YESNO		;Get answer, default to YES
	 TRNA			;Non-skip return
	  AOS (P)		;Skip return
	HRLI A,ABSCMD		;Restore from our save area
	HRRI A,CMDBLK		;Destination address
	BLT A,CMDBLK+.CMGJB	;Restore old CMDBLK
	MOVE A,ABSREP		;Restore reparse address
	MOVEM A,REPARP
	RET

; This strange spaghetti set of JRSTs is there for a reason.  It simulates
;a PROMPT UUO, but without messing up UUO context or pushing anything on the
;stack.  If we ever free up AC15 we could use TRVARs and this would be cleaner.

ABOPMT:	MOVE A,[CMIBLK,,CMDBLK] ;Initialize CMDBLK to virgin state
	BLT A,CMDBLK+.CMGJB
	HRROI A,[ASCIZ/Abort? /] ;Set up prompt
	MOVEM A,CMDBLK+.CMRTY
	HRROI A,ABOTXB		;First bfr stolen from stack
	MOVEM A,CMDBLK+.CMBFP	;Start of text pointer
	MOVX B,5*SBFLEN		;Size of buffers in characters
	DMOVEM A,CMDBLK+.CMPTR	;Next input pointer, space left
	HRROI A,ABOATB		;Next buffer stole from stack
	DMOVEM A,CMDBLK+.CMABP	;Atom buffer pointer/size
	MOVEI B,[FLDDB. .CMINI]
	CALL $COMND
	JRST ABOCF1

	ENDSV.
	SUBTTL Main program

GO:	TDZA F,F		;Reset flags
GOAMOD:	 MOVX F,F%AMOD		;Automatic mod handling
	RESET%
	MOVE P,[IOWD NPDL,PDL]
	GJINF%			;Now get login user
	DMOVEM A,MYUSR		;Save user/directory numbers
	MOVEM C,MYJOBN		;Save job number
	MOVEM A,MYAUSR		;Also ALIAS user
	HRROI A,MUSRST		;Real login name for ALIAS default
	MOVE B,MYUSR		;RCUSR% and DIRST% want number in B
	DIRST%
	 NOP
	SETZ A,			;Now get directory number
	RCDIR%
	MOVEM C,MYDIR		;Save that too
	MOVEM C,MYPDIR		;And as post office box directory
	CALL SETUSR		;Set internal login user
	MOVE A,[SIXBIT/MM/]	;Set subsystem name
	SETNM%
	SETO A,			;Get our names
	MOVE B,[-2,,PRGNAM]
	MOVEI C,.JISNM
	GETJI%
	 JFATAL
	MOVE A,[JRST CMDRES]	;Setup initial return dispatch
	MOVEM A,CMDRET
	MOVE A,[CMIBLK,,CMDBLK]	;Initialize CMDBLK to virgin state
	BLT A,CMDBLK+.CMGJB
	MOVEI D,SAVMOD
	CALL GETTYM		;Get current TTY modes
	MOVE T,[SAVMOD,,EDMOD]	;Give a reasonable set of editor modes
	BLT T,EDMOD+4
	MOVX A,.FHSLF		;Setup interrupt stuff
	RPCAP%
	TXZ B,.RHALF		;Only enable lh caps at first
	IOR C,B
	EPCAP%
	MOVE B,[LEVTAB,,CHNTAB]
	SIR%
	EIR%
	MOVX B,<<1B<CTCCHN>>!<1B<ABOCHN>>!<1B<TMRCHN>>> ;CTRL/C, CTRL/N, timer
	AIC%
	CALL ABOINI		;Set up abort routines
	CALL SETTIM		;Set up timer interrupt
	HRROI A,MLBXDV		;Get post office box structure
	STDEV%
	IFJER.
	  HRROI A,STRBUF	;Failed, get logged-in directory string
	  MOVE B,MYDIR		;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,.CLNJB		;Create systemwide logical name
	  HRROI B,MLBXDV	; for post office box
	  HRROI C,STRBUF	;From login structure
	  CIETYP <[%2R: not found, defining as %3R]
>
	  CRLNM%
	   JFATAL
	ELSE.
	  MOVE A,[POINT 7,STRBUF] ;Otherwise we need postbox directory
	  MOVEI B,[ASCIZ//]	;Null name
	  CALL MKPSTR		;Make postbox directory name
	  SETZ A,		;Now get directory number
	  HRROI B,STRBUF	; of postbox
	  RCDIR%
	  IFNJE.
	    TXNN A,RC%NOM!RC%AMB ;Found the direcotyr?
	     MOVEM C,MYPDIR	;Yes, use it as postbox
	  ENDIF.
	ENDIF.
	MOVEI A,MAXBBD		;Reset the BBoard table to empty
	MOVEM A,BBDTAB
	MOVEI A,BBDTAB+MAXBBD+1	;Clear string space
	MOVEM A,BBDSTR
	MOVE A,[POINT 7,STRBUF]	;Make BBoard filename string
IFE NICSW,<
	MOVEI B,MLBXDV		;Post office box structure
	CALL MOVSTR
	MOVEI B,[ASCIZ/:</]
	CALL MOVSTR
	MOVEI B,BBDIR		;BBoard directory
	CALL MOVSTR
	MOVEI B,[ASCIZ/*>*./]	;All files
>;IFE NICSW
IFN NICSW,<
	MOVEI B,BBDEV		;Post office box structure
	CALL MOVSTR
	MOVEI B,[ASCIZ/:*./]
>;IFN NICSW
	CALL MOVSTR
	MOVEI B,MLBXEX		;Only this extension
	CALL MOVSTR
	MOVEI B,[ASCIZ/.1/]	;Generation 1 only
	CALL MOVST0
	MOVX A,GJ%SHT!GJ%OLD!GJ%DEL!GJ%IFG
	HRROI B,STRBUF
	GTJFN%
	IFNJE.
	  MOVE D,A		;Save JFN over this clobberage
	  DO.
	    HRRZ A,BBDSTR	;Current BBoard pointer
	    CAILE A,BBDEND	;Any space left?
	    IFSKP.
	      HRROS A		;Yes, make string pointer
	      HRRZ B,D		;JFN to output
	      MOVX C,1B8	;Name only
	      JFNS%		;Insert BBoard name in string space
	      SETZ C,		;Tie off name
	      IDPB C,A
	      ADDI A,1		;Next string begins on this word
	      HRLZ B,BBDSTR	;Pointer to this string for TBADD
	      MOVEM A,BBDSTR	;Update string pointer
	      MOVEI A,BBDTAB	;Add to the table
	      TBADD%
	      IFJER.
		WARN <Too many BBoards, table truncated>
		EXIT.
	      ENDIF.
	      MOVE A,D		;Retrieve JFN
	      GNJFN%		;Get next BBoard
	       ERJMP ENDLP.	;No more BBoards to do
	      LOOP.
	    ENDIF.
	    WARN <Insufficient string space for all bulletin boards>
	  ENDDO.
	ENDIF.
	HRRZ A,D		;Got all BBoards, release the JFN now
	RLJFN%
	 ERJMP .+1
	MOVEI A,NHOSTS		;Initialize host string cache
	MOVEM A,HSTTAB
	HRROI A,HSTSTR		;Initialize host strings
	HRRZM A,LCLHST		;First string is local host name
	CALL $GTLCL		;Get local host name
	 FATAL (Unable to get local host name)
	IBP A			;Skip over following byte
	MOVEI A,1(A)		;Start next string on next word
	MOVEM A,HCSHFF		;Set up host cache first free
	MOVEI A,HSTTAB		;Put local host name in cache
	MOVS B,LCLHST
	TBADD%
	MOVE A,[POINT 7,LCLHNM]	;Now make copy of local name string
	MOVE B,LCLHST
	CALL MOVST0
	HRROI A,LCLHNM		;Now remove its relative domain
	CALL $RMREL
;	JRST GOINIT
;;;Now ready to read in the user's MM.INIT
GOINIT:	SETZM ZERMEM
	MOVE A,[ZERMEM,,ZERMEM+1]
	BLT A,ZEREND		;Clear out garbage stuff
IFN ALIASW,<
	Move A,[xwd 0,777]
	Movem A,PalTbl		;reset alias table
>
	SETOM WRKSEQ		;Show no previous sequence
	AOS MSCANF		;Assume forward sequence scanning
	CALL ININIT		;Initialize init variables
	MOVE A,[POINT 7,STRBUF]	;Build init filename
	MOVEI B,[ASCIZ/MM.INIT/]
	CALL MAKSTR
	MOVX A,GJ%OLD!GJ%SHT	;See if MM.INIT present
	HRROI B,STRBUF
	GTJFN%
	IFNJE.
	  CALL DOINIT		;Init file present, parse it
	ENDIF.
;;;Here go and lookup personal name if MM.INIT doesn't set it up
	SKIPE PERNAM		;Did MM.INIT set it up?
	 JRST NOFING		;Don't need FINGER for this
	MOVX A,GJ%OLD!GJ%SHT	;Look up FINGER
	HRROI B,[ASCIZ/SYS:FINGER.EXE/]
	GTJFN%
	 ERJMP NOFING		;FINGER not present
	PUSH P,A		;Save JFN
	MOVX A,CR%CAP		;Create a new fork
	CFORK%
	IFJER.
	  POP P,A		;Can't get fork, punt
	  RLJFN%		;Flush the JFN
	   NOP
	  JRST NOFING
	ENDIF.
	EXCH A,(P)		;Save fork handle, get JFN
	PUSH P,A		;In case of error in GET
	HRL A,-1(P)		;Get prog into fork
	GET%
	IFJER.
	  POP P,A		;Can't get program, punt
	  RLJFN%		;Flush the JFN
	   NOP
	  JRST NOFING
	ENDIF.
	ADJSP P,-1		;Flush JFN
	MOVE A,[.FHSLF,,FWDPAG/1000] ;Map page FWDPAG of this fork
	HRLZ B,(P)		;From page 777 of FINGER
	HRRI B,777
	MOVX C,PM%RD!PM%WR!PM%PLD ;Read/write/preload
	PMAP%
	 ERJMP FNGERR
	HRROI A,FWDPAG		;Give our user name to FINGER
	MOVE B,MYAUSR
	DIRST%
	 ERJMP FNGERR		;???
	MOVE A,(P)		;Get back fork handle
	MOVEI B,3		;Start inferior at offset 3
	SFRKV%
	 ERJMP FNGERR
	RFORK%			;Resume, in case it didn't get going
	 ERJMP FNGERR
	WFORK%			;Sleep until fork is finished
	 ERJMP FNGERR
	DMOVE A,PRGNAM		;Restore program name
	SETSN%
	 JFATAL
	MOVE A,(P)		;See if it finished okay
	RFSTS%
	HLRZ A,A
	CAIE A,.RFHLT		;Fork halted?
	IFSKP.
	  HRROI A,PERNAM	;Now copy personal name into PERNAM
	  HRROI B,FWDPAG
	  MOVEI C,117		;Up to 20 words
	  MOVEI D,0		;Terminated by a null
	  SOUT%
	ENDIF.
FNGERR:	SETO A,			;Unmap shared page
	MOVE B,[.FHSLF,,FWDPAG/1000] ;Mapped to this fork's FWDPAG
	SETZ C,
	PMAP%
	POP P,A			;Now kill the fork
	KFORK%
	MOVEI D,SAVMOD		;Restore TTY modes
	CALL SETTYM
NOFING:	SKIPL INITER		;Did an error happen?
	IFSKP.
	  TMSG <
[The above error(s) indicate(s) some problem in MM.INIT, the file
 which contains your personal MM profile parameters.  If you have
 not edited or otherwise altered your MM.INIT, it's likely that
 your MM.INIT was created by an older version of MM, and is
 referencing some obsolete feature that is no longer supported by
 MM.  If this is the case, answer YES to the following question.]

>
	  PROMPT <May I rewrite your MM.INIT file to correct these errors? >
	  CALL YESNO		;Yes, offer to fix it
	ANSKP.
	  CALL CRINI0		;Fix it
	ENDIF.

;;;Here after INIT file has been processed

	IFXN. F,F%AMOD		;Auto mod handling?
	  CALL SYSTE1		;Yes, setup for system mail
	  SETZB CMDSTK		;No subcommands
	  MOVE L,[POINT 12,MSGSEQ,11] ;Pointer to where to store messages
	  CALL STQALL		;Assume all msgs will be considered
	  CALL PSHCMD		;NXTSEQ should always be the first function!!!
	  CALL STQNEW		;Setup sequencer
	  CALL PSHCMD
	  HLRE A,CMDSTK		;Compute number of entries
	  ADDI A,NCPDL
	  MOVNS A
	  HRLI A,CMPDL
	  MOVSM A,CMDSTK	;Save it
	  MOVE C,[POINT 12,MSGSEQ,23] ;Begin looking at this sequence first
	  MOVEM C,MSGSPT	;Save initial sequence pointer
	  SETOM WRKMSG		;Say sequence hasn't begun yet!
	  SETOM MSRNG		;Say no range in progress
	  MOVE L,[POINT 12,WRKSEQ,11] ;Init ptr to working sequence
	  MOVNI M,MSGLEN
	  MOVEI A,TYPE1		;Msg processing routine
	  CALL DOMSGS		;Go do messages
	  PUSH P,[GO]		;In case of continue
	  CALLRET QUIT0		;And exit
	ENDIF.
	CALL DORSCN		;Do RSCAN% hacking
	AOSN INITER		;Error in init processing?
	IFSKP.
	  SKIPE BLSCST		;Clear off the screen, maybe
	   CALL $BLANK		;Blank screen
	ENDIF.
	CALL .VERS1		;Tell version
	CALL GETFIL		;Get and parse file
	MOVE A,[POINT 7,STRBUF]	;Now TAKE user's MM.CMD file
	MOVEI B,[ASCIZ/MM.CMD/]
	CALL MAKSTR		;Build file name with login directory
	MOVX A,GJ%OLD!GJ%SHT
	HRROI B,STRBUF
	GTJFN%			;Try to find file
	IFSKP. <CALL TAKE1>	;Do TAKE
	SKIPLE MSGJFN		;Is there a mailbox?
	 CALL CMDSUM		;Yes, show summary
CMDRES::MOVE P,[IOWD NPDL,PDL]	;Errors that return to command level
				; come here.
	TXZ F,F%RSCC		;No more RSCAN% reparsing
CMDLUP:	IFXE. F,F%TAK		;In TAKE file?
	  TXZE F,F%RSCN		;No, command line routine terminated?
	   CALL QUIT0		;Yes, go get rid of file and stop
	ENDIF.
	SETZM KEYFRE		;Reset keyword buffer
	CALL CHECK		;Check for new messages
	SKIPGE M		;Make sure have a valid message
	 SKIPA M,PRIORM		;Don't, use last one then
	  MOVEM M,PRIORM	;Yes, save in case for next time
	MOVE A,[TOPRMT,,CMDTAB]	;Pointer to current command
	CALL CMDINI		;Init command state, etc.
	CALL ABOINI		;Now re-init abort routines
	SETOM OKTINT		;OK for timer interrupt here
	SETOM ABOCAN		;OK to arm CTRL/N aborts.
	CALL GETCMD
	CALL (A)
	JRST CMDLUP		;And keep going

CMDSUM:	SETABT CMDABO		;May now allow abort of type-out
	CALL RECENT		;Show data on recent messages
	CALLRET SUMMRY		;And a summary of the files contents

; Standard abort vector for main command loop.
CMDABO:	MOVX A,.PRIIN		;Make sure TTY input buffer empty
	CFIBF%
	 ERJMP .+1
	MOVEI D,SAVMOD		;Restore program's modes
	CALL SETTYM
	MOVEI A,CMDRES		;Restore return address
	TXNE F,F%READ
	 MOVEI A,REDRET
	TXNE F,F%SEND
	 MOVEI A,SNDRET
	HRRM A,CMDRET
	SETZM CMDFLB+.CMDEF	;Clear any default setup during this
	SETZM ABOIP		;Clear abort in progress flag!
	JRST (A)
	SUBTTL Command routines

;;;Headers of messages

.RSHEA:	CALL RSCFIL		;RSCAN% call, get the file
.HEADE:	CALL DFSQTH		;Get sequence, default to current
	MOVEI A,TYPHDR		;Setup to type out header
	CALLRET DOMSGS		;And go handle them all

;;;Give status

.STATU:	CONFRM
	CALL .STATF		;Print file status
	CALL RECEN1		;Get poop on new messages
	CALL SUMMRY
	SKIPL M			;Range check
	 CAMLE M,LASTM
	  SETZ M,		;Go to the beginning
	CIETYP < Currently at message %M.
>
	RET

;;;Print current alias and file name.
.STATF:	HRROI A,MAUSRS		;If an alias is in effect
	TXNE F,F%ALIA		;Then let user know to whom
	 CIETYP < Alias: %1S>
	SKIPG A,MSGJFN
	 ERROR <No current file>
	CIETYP < File: %1J>	;Say what file we are using
	RET
;;;Type messages

.RSTYP:	CALL RSCFIL		;Get file for RSCAN% command handling
.TYPE:	CALL DFSQTH
	MOVEI A,TYPE1
	CALLRET DOMSGS
	
TYPE1:	CALL CHKDEL		;Not the deleted ones
	 RET
	CALLRET TYPMSG

;;; Literal typing (no filters)
.LTYPE:	CALL DFSQTH
	MOVEI A,LTYPE
	CALLRET DOMSGS

LTYPE:	CALL CHKDEL
	 RET
	CALLRET TYPMSL

.KILL:	CALL .DELET		;Delete messages
	CALLRET .NEXT0		;Do an implicit NEXT

.MARK:	SKIPA A,[MRKMSG]	;Mark messages
.DELET:	 MOVEI A,DELMSG		;Delete messages
DELET0:	MOVEM A,DOMSG		;Set up handler
	CALL DFSQTH		;Get sequence, default to current
DELET1:	TXOA F,F%TYPS		;Say to print numbers of things done
DOMSGS:	 MOVEM A,DOMSG		;Here with routine to handle them in A
	SETABT			;Allow peaceful aborts, arm CTRL/N
	DO.
	  CALL NXTMSG		;Next message spec'd
	   RET			;None left, return
	  SKIPGE ABORTF		;If abort was requested,
	   ERROR <Aborted>	; stop processing sequence.
	  CALL @DOMSG		;Process the message
	  LOOP.
	ENDDO.

;;;Put keywords on messages

.UNKEY:	SKIPA A,[UNKMSG]
.KEYWO:	 MOVEI A,KEYMSG
	PUSH P,A
	CALL GETKY0		;Get list of keywords
	MOVEM U,KEYBTM		;Save keyflag mask bits
	MOVEM V,KEYLPM		;And keyword list
	POP P,A
	CALLRET DELET0		;And go handle sequence
.NEXT:	NOISE (MESSAGE)
	CONFRM
.NEXT0:	SKIPG MSGJFN
	 ERROR <No current file>
	CAMGE M,LASTM		;At last message?
	IFSKP.
	  CIETYP < Currently at end, message %M.
>
	  RET
	ENDIF.
	ADDI M,MSGLEN		;Nope, increment him
.NEXT1:	CALL CHKDEL		;Deleted?
	 RET
	CALLRET TYPMSG		;No, type the next one then

.PREVI:	NOISE (MESSAGE)
	CONFRM
	SKIPG MSGJFN
	 ERROR <No current file>
	IFE. M
	  CIETYP < Currently at beginning, message %M.
>
	  RET
	ENDIF.
	SUBI M,MSGLEN
	CALLRET .NEXT1

.JUMP:	STKVAR <JMPMSG>
	SKIPG MSGJFN
	 ERROR <No current file>
	NOISE (TO MESSAGE NUMBER)
	MOVEI B,[FLDDB. .CMNUM,,^D10] ;Read a number
	CALL CMDFLD
	MOVEM B,JMPMSG
	CONFRM
	EXCH M,JMPMSG		;Get back number typed
	SUBI M,1
	IMULI M,MSGLEN		;Convert to msg pointer
	CAMG M,LASTM
	 RET			;Number ok, return
	MOVE M,JMPMSG		;Number bad, restore old pointer
BADNUM:	ERROR <Number out of range>

	ENDSV.
.FLAG:	SKIPA A,[FLGMSG]	;Flag messages
.UNFLA:	 MOVEI A,UFLMSG		;Unflag messages
	CALLRET DELET0

.UNMAR:	SKIPA A,[UMKMSG]	;Unmark messages
.UNANS:	 MOVEI A,UANMSG		;Unanswer messages
	CALLRET DELET0

.UNDEL:	MOVEI A,UNDMSG		;Set up handler
	MOVEM A,DOMSG
	MOVEI A,[ASCIZ/PREVIOUS-SEQUENCE/] ;Default to previous sequence
	CALL DFSQA1
	CALLRET DELET1
.BLANK:	NOISE (SCREEN)
	CONFRM
	CALLRET $BLANK

.EXIT:	NOISE (AND UPDATE MESSAGE FILE)
	CONFRM
	TXO F,F%F1		;Re-Get mail file
	SKIPLE MSGJFN		;If have a file,
	 CALL EXPUNG		;Expunge first
	CALLRET QUIT0		;And then quit

.LOGOU:	NOISE (AND UPDATE MESSAGE FILE)
	CONFRM
	TXZ F,F%F1		;Don't bother getting mail file again
	SKIPLE MSGJFN		;If have a file,
	 CALL EXPUNG		;Expunge first
	SETO A,			;Flush us
	LGOUT%			;Do the kill
	JERROR <Logout failed>	;Woops, bombed?

.EXPUN:	NOISE (DELETED MESSAGES)
	CONFRM
	SKIPG MSGJFN
	 ERROR <No current file>
	TXO F,F%F1		;Re-Get mail file
;	CALLRET EXPUNG
EXPUNG:	TXNN F,F%RONL		;Not on system mail you don't
	 CALL GETJF2		;Get write JFN so no one interferes
	  RET			;Failed, or system mail
	SETOM WRKSEQ		;Show no previous sequence
	SETZB L,E		;Clear offset, and count of bytes saved
	MOVNI M,MSGLEN		;Begin with first message
	DO.
	  ADDI M,MSGLEN		;Step to next message
	  MOVX A,M%DELE		;Deleted bit
	  TDNE A,MSGBTS(M)	;Is it deleted?
	  IFSKP.
	    MOVE C,MSGSAL(M)	;No, must save, get length of this message
	    ADD E,C		;Keep track of total
	    IFN. L		;If no bytes deleted yet, no moving
	      MOVE V,MSGALL(M)	;Get starting byte of message
	      CALL CHR2BP	;Get byte pointer in a to old msg
	      CALL FSCOPY	;Do a fast string copy
	      ADDM L,MSGALL(M)	;Update position in file of start
	    ENDIF.
	  ELSE.
	    IFE. L		;The first deleted msg we have seen?
	      MOVX A,EN%BLN	;Exclusive use, no level numbers
	      HRR A,MSGJFN	;File's JFN
	      MOVEM A,ENQBLK+.ENQLV
	      DMOVE A,[.ENQMA	;Change our lock to be exclusive
		       ENQBLK]
	      ENQ%
	      IFJER.
		WARN <Can't do expunge - another process has the file open>
		CALLRET CLSJF2	;Get rid of the JFN we made
	      ENDIF.
	      MOVE V,E
	      CALL CHR2BP	;Yes, byte pointer to last saved byte
	      MOVE O,A		;Init pointer to output area
	      MOVEI A,MTXPAG	;And make messages private
	      HRRZ B,FILPGS
	      DO.
		MOVES (A)
		SOJLE B,ENDLP.
		ADDI A,1000
		LOOP.
	      ENDDO.
	    ENDIF.
	    SUB L,MSGSAL(M)	;Increment count of byte offset
	  ENDIF.
	  CAMGE M,LASTM		;At the last msg?
	   LOOP.		;No, do next then
	ENDDO.
	IFE. L			;Any messages deleted?
	  CITYPE < No messages deleted, so no update needed
>
	  CALLRET CLSJF2
	ENDIF.
	IFE. E
	  CITYPE < All messages deleted, deleting file
>
	  DMOVE A,[.DEQID	;Get rid of any locks we got
		   REQID]
	  DEQ%
	   ERJMP .+1		;Ignore failure
	  SKIPLE A,MSGJFN	;Make damn sure this JFN is out of
	   CLOSF%		; the way, so the DELF% doesn't get a
	    NOP			; DELFX2 loser
	  SETOM MSGJFN
	  CALL CLSJF2
	  SETZM FILSIZ
	  HRRZ A,MSGJF2
	  TXO A,DF%EXP
	  DELF%
	   JWARN
	  HRRZ A,MSGJF2
	  RLJFN%
	   NOP
	  SETOM MSGJF2
	  RET
	ENDIF.
	CITYPE < Expunging deleted messages
>
	NOINT			;CTRL/C from here on is deadly...
	MOVE B,E		;See how many pages touched
	IDIVI B,5000
	JUMPE C,.+2
	 ADDI B,1
	HRRZ C,FILPGS		;Number we had mapped to start
	SUBI C,(B)		;Less number touched
	IFN. C			;All pages touched?
	  PUSH P,B		;No, save new count for later
	  SETO A,
	  ADD B,[.FHSLF,,MTXPGN]
	  TXO C,PM%CNT
	  PMAP%			;Unmap those not touched
	  POP P,B		;Number of pages touched
	  HRL B,MSGJF2		;Write msg file JFN
	  PMAP%			;Make pages in the file go away
	  IFJER.
	    JWARN <Can't unmap file pages, probably another user has file open>
	  ENDIF.
	ENDIF.
	HRRZ A,MSGJF2		;Write msg file JFN
	HRROI B,MTXPAG		;Write out new pages
	MOVN C,E
	SOUT%
	HRLI A,.FBSIZ
	SETO B,
	MOVE C,E		;Update byte count
	CHFDB%
	LDB B,[POINT 6,FILPGS,11] ;Get byte size
	CAIN B,7		;If not 7,
	IFSKP.
	  HRLI A,.FBBYV		;Make it be
	  MOVX B,FB%BSZ
	  MOVX C,7B11
	  CHFDB%
	ENDIF.
	CALL CLSJF2		;Get rid of write JFN
	MOVX A,EN%BLN!EN%SHR	;No level number, shared access
	HRR A,MSGJFN
	MOVEM A,ENQBLK+.ENQLV	;Change the access back to shared
	DMOVE A,[.ENQMA
		 ENQBLK]
	ENQ%
	 ERJMP .+1		;Don't care
	OKINT			;OK, let him CTRL/C now
	JXE F,F%F1,R		;Should we get mail file back?
	CALL SIZFIL		;Yes, go thru normal channels
PARSEA:	SETZ M,			;Read entire file, remarking
	CALL PARSEF		; recent msgs
	CALLRET RECEN2
.ANSWE:	CALL DFSQTH		;Get in sequences, def to current
	SETABT CMDABO
	MOVEI A,ANSRET		;Return here on error
	HRRM A,CMDRET
	DO.
	  CALL NXTMSG		;Get next message
	   EXIT.		;Unless all done
	  CALL CHKDEL		;Deleted?
	   LOOP.		;Yes, forget it
	  MOVE A,[POINT 7,TMPBUF]
	  MOVEI B,[ASCIZ/ Send reply for message # /]
	  CALL MOVSTR
	  MOVEI B,MSGLEN(M)
	  IDIVI B,MSGLEN
	  MOVX C,^D10
	  NOUT%
	   JERROR
	  MOVEI B,[ASCIZ/ to: /]
	  CALL MOVST0
	  UPRMT TMPBUF		;Prompt for all/sender
	  MOVEM L,SAVL
	  SETOM CLEVEL		;Don't let CTRL/U go to top level
	  MOVEI A,ANSWE1	;Set reparse address
	  HRRM A,CMDBLK+.CMFLG
	  MOVEM P,REPARP
ANSWE1:	  MOVE P,REPARP
	  CALL REPLY0		;Reply to it
ANSRET:	  MOVE L,SAVL
	  LOOP.			;How about another?
	ENDDO.
	MOVEI A,CMDRES		;Reset the error handler
	HRRM A,CMDRET
	JRST CMDRES		;And back to snarf a command
;;;Count messages

.COUNT:	CALL DFSQAL		;Get sequence, default is all
	SETZM NRECNT		;Place to store count
	MOVEI A,CNTMSG
	MOVEM A,DOMSG
	CALL DELET1		;Map over them, printing and counting
	SKIPE A,NRECNT		;Get the total count
	IFSKP.
	  CITYPE <No messages>
	ELSE.
	  ETYPE < = %1D message%1P>
	ENDIF.
	RET

CNTMSG:	AOS NRECNT
	RET
;;;Append messages together

.APPEN:	STKVAR <APPMSG,APPPTR,APPLEN>
	SKIPG MSGJFN		;Must have a file
	 ERROR <No current file>
	CALL GETSEQ		;Get a bunch of messages no default
	TXNE F,F%RONL		;File read-only?
	 ERROR (File is read-only)
	TXO F,F%TYPS		;Type out numbers of messages
	CALL APPNXM		;Get an undelete message sequence
	 RET			;Nothing to append
	MOVEM M,APPMSG		;Save index of first msg
	MOVE C,[POINT 7,TXTPAG]	;Lots of string space
	MOVEM C,APPPTR
	SETZM APPLEN		;Initially zero length
	DO.
	  HRRZ V,MSGBOD(M)
	  CALL MCH2BP		;Get byte pointer to message
	  HLRZ C,MSGBOD(M)	;And length
	  ADDM C,APPLEN		;Update total length
	  MOVE O,APPPTR
	  CALL FSCOPY		;Copy in the message
	  MOVEM O,APPPTR
	  CALL APPNXM		;Get next message
	   EXIT.		;All done
	  CALL DELMSG		;Delete it
	  LOOP.			;For the whole sequence
	ENDDO.
	MOVE A,[POINT 7,TXTPAG]
	MOVE C,APPLEN		;Get total length
	MOVE M,APPMSG		;The appended msgs go here
	CALL RPLMSG		;Go replace that message
	 ERROR <Append failed, message(s) deleted>
	UETYPE [ASCIZ/ => %M/]
	RET

	ENDSV.

APPNXM:	DO.
	  CALL NXTMSG		;Get first sequence
	   RET			;Nothing to append
	  CALL CHKDEL		;Is it deleted?
	   LOOP.		;Yes, ignore it, try for another
	ENDDO.
	RETSKP			;Here we have a message
.RSREA:	CALL RSCFIL		;Get file for RSCAN% command handling
.READ:	CALL DFSQNW		;Get sequence, default to unseen
	CALL CHECKT		;Do a CHECK in case new mail came in
	MOVEM P,READPP		;Save stack
	TXO F,F%READ		;Say in read command
	MOVE A,[POINT 12,PRVSEQ,11] ;Initialize previous sequence pointer
	MOVEM A,PREVPT
	MOVE A,[PRVSEQ,,PRVSEQ+1] ;Clear previous sequence list
	SETOM PRVSEQ
	BLT A,PRVSQZ-1
	MOVEI A,REDRET		;Return here
	HRRM A,CMDRET		;On error
READ0:	MOVE A,PREVPT		;Paranoia check
	CAMN A,[POINT 12,PRVSQZ-1,23] ;Reached end of list?
	 ERROR <Too many messages in read list>
	ILDB A,PREVPT		;See if a next message from backup
	CAIN A,7777		;Is there a next message?
	IFSKP.
	  IMULI A,MSGLEN	;Yes, convert to message index
	  MOVEI M,(A)		;Set current message to this
	ELSE.
	  CALL NXTMSG		;Get next message
	   JRST RQUIT0		;None, all done
	  MOVE A,M		;Convert index to msg # w/o zapping M
	  IDIVI A,MSGLEN
	  DPB A,PREVPT		;Save message on previous stack
	ENDIF.
READ1:	CALL CHKDEL		;Don't if deleted msg
	 JRST REDRET
	SKIPE BLSCST		;Unless user doesn't want it
	 CALL $BLANK		;Clear the screen perhaps
	CALL TYPMSG		;And type the message out
	SKIPGE RINCME		;Special include me mode?
	 SETZM SAVFIL		;Yes, reset default moved to
REDRET:	MOVE P,READPP		;Restore stack
REDCLP:	MOVE A,[REPRMT,,RCMDTB]	;Read command
	CALL CMDINI
	DEFALT (NEXT)		;CR moves on to next message
	SETZM KEYFRE		;Reset keyword buffer
	CALL GETCMD
	CALL (A)
	JRST REDCLP		;Keep going

.RNEXT:	CONFRM
.RNEX1:	CALL UPDBIT		;Update message
	CALL CHECK		;Check for new guys
 	CALLRET READ0

.RQUIT:	CONFRM
	MOVEI B,7777
	IDPB B,L		;Mark end of sequence
RQUIT0:	CALL UPDBIT		;Update this message
	MOVEI A,CMDRES
	HRRM A,CMDRET
	TXZ F,F%READ
	MOVE P,READPP		;Restore stack to calling level
	CALLRET CHECKT		;Check and return to top level

;;; Read mode previous command, determines the message from the history
.RPREV:	CONFRM
	CALL UPDBIT		;Update file
	SETO A,			;Back up previous sequence pointer
	ADJBP A,PREVPT		;Note this ISN'T a 7-bit byte pointer
	LDB B,A			;Get previous message number
	CAIN B,7777		;Backed up too far?
	 ERROR <Already at start of sequence>
	MOVEM A,PREVPT		;No, update previous point
	IMULI B,MSGLEN		;Convert to message index
	MOVE M,B		;And set as current
	CALLRET READ1		;Return to READ code
;;;Sending subcommands

.CONTI:	NOISE (SENDING MESSAGE)
	CONFRM
	SKIPL SNDCAL
	 ERROR <There is no sending to continue>
	SETZM LSTCHR		;Don't accidentally send it off
	SETABT CMDABO		;Allow aborts to top-level
	MOVEM P,SENDPP		;Save stack for SNDRET
	SKIPL M.RPLY		;Continuing a reply?
	 MOVE M,M.RPLY		;Yes, insure we have the correct index!
	CALLRET SEND1A		;Enter send mode, SNDCAL already set up

IFN NICSW,<
.VSEND:	TXOA F,F%VRFY
.SEND:	 TXZ F,F%VRFY
>
	NOISE (MESSAGE TO)
	SETABT CMDABO		;Allow aborts to top-level
	CALL SNDIN0
	CALL GETTO0		;Get to: without prompting
	HRRZ A,CMDRET		;Save where we came from
	HRROM A,SNDCAL
	MOVEM P,SENDPP		;Save stack for SNDRET
	MOVEI A,SEND1A		;Enter SEND level here so error on CTRL/E
	HRRM A,CMDRET		; leaves us at SEND level
	SKIPN TOLIST
	IFSKP.
	  CALL PRSCCL		;Add default lists
	  CALL GETMS1		;Get message without cc or to
	  HRRZ A,SNDCAL		;Restore caller context
	  HRRM A,CMDRET
	ELSE.
	  TXZ F,F%HOER		;User wants hand-holding, no more halt
	  CALL SNDIN0		;Reset fields
	  CALL GETMSG		;Prompt for message
	  CALL PRSCCL		;Add default lists
	  HRRZ A,SNDCAL		;Restore caller context
	  HRRM A,CMDRET
	ENDIF.
;	CALLRET SEND0

;;;Here from several places to enter SEND level, possibly sending right away.
SEND0:	MOVE A,LSTCHR		;Get last character
	SKIPG ESCSND		;Escape sends automatically?
	IFSKP.
	  CAIE A,.CHCND		;Yes, wants that?
	   CAIN A,.CHESC
	    JRST SSEND0		;Yes, just send if off then
	ELSE.
	  CAIE A,.CHCNZ		;No, got CTRL/Z?
	ANSKP.
	  SKIPL ESCSND		;Yes, CTRL/Z sends automatically?
	   TXNE F,F%RSCN	;Or called in command line?
	    JRST SSEND0		;Yes to either, send message
	ENDIF.
SEND1:	HRRZ A,CMDRET		;Save where we came from
	HRROM A,SNDCAL		; flagging it is continuable
	MOVEM P,SENDPP		;Save stack for SNDRET
;;;SEND1A is an alternative entry point if SNDCAL and SENDPP have been set up
SEND1A:	MOVEI A,SNDRET		;Enter SEND level
	HRRM A,CMDRET
	TXO F,F%SEND
	CALL ABNOFF		;Suppress CTRL/N but retain abort vector
SNDRET:	MOVE P,SENDPP		;Reset stack
SNDLUP:	TXZE F,F%ESND		;Editor said to send it?
	 JRST SSEND1		;Yes, do that right away
	MOVE A,[SEPRMT,,SCMDTB]
	CALL CMDINI
	SKIPE CRSEND		;Does bare CR send message?
	 DEFALT (SEND)
	CALL GETCMD
	CALL (A)
	JRST SNDLUP

;;;Send off the message.  Haven't yet entered SEND mode, do so now.
SSEND0:	MOVEM P,SENDPP		;Save stack for SNDRET
	HRRZ A,CMDRET		;Save where we came from
	HRROM A,SNDCAL		; flagging it is continuable
	MOVEI A,SNDRET		;Enter SEND level in case error
	HRRM A,CMDRET
	TXO F,F%SEND
	CALL ABNOFF		;Suppress CTRL/N but retain abort vector
	JRST SSEND1

.SSEND:	CONFRM
SSEND1:	CALL SNDMSG		;Send it off
	HRRZS SNDCAL		;Don't let user continue this one
	SKIPGE M.RPLY		;Was this a reply we just sent?
	IFSKP.
	  MOVE M,M.RPLY
	  MOVX A,M%RPLY		;Mark replying to this message
	  IORM A,MSGBTS(M)
	  CALL UPDBIT
	ENDIF.
	JXN F,F%RSCN,SQUI1	;If called from command line then done
	TXZ F,F%SEND		;Else, leave SEND (or REPLY) command
	HRRZ A,SNDCAL		; (do same thing as SQUI1)
	HRRM A,CMDRET
	MOVE P,SENDPP
	CALLRET CHECKT		;Now check for new messages

.SQUIT:	CONFRM
SQUI1:	TXZ F,F%SEND		;Not in send command or a reply anymore
	HRRZ A,SNDCAL		;Get where we entered from
	HRRM A,CMDRET		;Set up to go back there
	MOVE P,SENDPP		;Reset stack
	RET			;And return to caller

.SEDIT:	DEFALT (TEXT)
	MOVEI A,EDCMTB
	CALLRET .ERAS2		;Get field to edit
.DELIV:	NOISE (FOR THIS MESSAGE ARE)
	MOVEI B,[FLDDB. .CMKEY,,DOPTTB]
	CALL CMDFLD		;Get a keyword
	HRRZ B,(B)		;Get keyword value
	PUSH P,B		;Save value
	CONFRM
	POP P,DLVOPT		;Save delivery option
	RET

DOPTTB:	NQDOPS,,NQDOPS
DOPTAB:	PHASE 0
	[ASCIZ/MAIL/],,.	;Mail (MUST BE FIRST IN TABLE!!!!!!!!)
D%SAML:![ASCIZ/SAML/],,.	;Send and mail
	[ASCIZ/SEND/],,.	;Send
D%SOML:![ASCIZ/SOML/],,.	;Send or mail
	DEPHASE
NQDOPS=.-DOPTAB

.AFTER:	NOISE (DATE)
	MOVEI B,[FLDDB. .CMTAD,,CM%IDA!CM%ITM,,,<[
		 FLDDB. .CMTAD,,CM%IDA,,,<[
		 FLDDB. .CMTAD,,CM%ITM]>]>]
	CALL CMDFLD
	PUSH P,B		;Remember date/time
	CONFRM
	POP P,AFTDAT		;Set date/time
	RET
.ERASE:	NOISE (MESSAGE FIELD)
	MOVEI A,ECMDTB
.ERAS2:	CALL SUBCMD
	PUSH P,A
	CONFRM
	POP P,A
	CALLRET (A)

.DISPL:	NOISE (MESSAGE FIELD)
	DEFALT (ALL)
	SETABT CMDABO		;Allow CTRL/N abort
	MOVEI A,DCMDTB
	CALLRET .ERAS2
IFN NICSW,<
.VREPL:	TXOA F,F%VRFY
.REPLY:	 TXZ F,F%VRFY
>
	NOISE (TO)
REPLY0:	MOVEI A,[ASCIZ/ALL/]
	SKIPE RFMDEF
	 MOVEI A,[ASCIZ/SENDER/]
	UDEF (A)		;Setup right default
	MOVEI A,RPCMTB
	CALL SUBCMD
	PUSH P,A
	MOVEI A,[ASCIZ/INCLUDING/]
	SKIPN INSMSG
	 MOVEI A,[ASCIZ/NOT-INCLUDING/]
	UDEF (A)
	MOVEI A,RICMTB		;See if to include message text
	CALL SUBCMD
	HRREM A,MNSMSG		;Set insert message flag
	NOISE (MESSAGE TEXT IN THE REPLY)
	CONFRM
	POP P,A
	CALLRET (A)

RICMTB:	NRICMS,,NRICMS
	CMD INCLUDING,-1
	CMD NOT-INCLUDING,0
NRICMS==.-RICMTB-1
.REPAL:	TXOA F,F%F3		;Say reply to everyone
.REPFM:	 TXZ F,F%F3		;Say just reply to sender
.REPL6:	CALL SNDIN0		;Erase drafts
	MOVEM M,M.RPLY		;In reply mode
	MOVEI T,[ASCIZ/
Date:/]
	CALL FNDHDR
	IFSKP.
	  SETZB B,C
	  IDTIM%		;Try to parse it
	  IFJER.
	    MOVE B,MSGDAT(M)	;Bad format, use recv date
	  ENDIF.
	ENDIF.
	MOVEM B,REPDAT		;Set up as reply date
	CALL REPSUB		;Construct the subject
	TXZ F,F%F1!F%F4!F%CC	;No Reply-To, barf on errors, put in To list
	PUSH P,[0]		;Save default host name for PRTOCC
	DO.
	  MOVEI T,[ASCIZ/
ReSent-Reply-To:/]		;This overrides all
	  CALL FNDHDR
	  IFNSK.
	    MOVEI T,[ASCIZ/
Reply-To:/]			;Look for overiding header
	    CALL FNDHDR
	     EXIT.
	  ENDIF.
	  TXO F,F%F1		;Flag that we processed a Reply-To
	  SETZ E,		;No host name defaulting
	  CALL PRADDT		;Get the guy and add him in
	  JXE F,F%AT,ENDLP.	;Network address?
	  MOVE E,TOLIST		;Get default host
	  MOVE E,ADRHST(E)
	  MOVEM E,(P)		;Set it as default now just in case
	ENDDO.
	HRRZ V,MSGFRM(M)	;Handle From so we use host default
	IFE. V			;Don't know who it's from?
	  CITYPE <%Can't tell who message is From>
	  CALL GETTO		;Ask him who it's to then...
	ELSE.
	  CALL MCH2BP
	  SETZ E,		;No host name defaulting
	  TXNE F,F%F1		;Doing Reply-To?
	   TXO F,F%F4		;Yes, don't barf on errors here
	  CALL PRADDR		;Process the address
	  HRRZ U,FREETO		;Get block pointer returned by PRADDR
	  CAIN U,(W)		;Same as free pointer?
	  IFSKP.
	    SETZM (P)		;Set default to local host
	  ANDXN. F,F%AT		;Network address?
	    MOVE E,ADRHST(U)	;Yes, have new default
	    MOVEM E,(P)		;Set it as default now
	  ENDIF.
	  TXZN F,F%F1		;Did we see a Reply-To just now?
	   CALL ADDTO		;No, add the address then
	ENDIF.
	MOVEI T,[ASCIZ/
To:/]				;Find start of addresses
	TXZE F,F%F3		;Wants reply to all addresses?
	 CALL FNDHDR
	IFSKP.
	  MOVE E,(P)		;Get back default host address
	  CALL PRTOCC		;Get to and cc lists
	  MOVEI U,TOPAG+ADRSTR	;First recipient's name
	  MOVEI N,1		;Allow only one occurance
	  CALL DOUNTO
	  MOVEI U,MAUSRS	;Remove me from the list
	  SETZ N,		;Allow 0 occurances
	  CALL DOUNTO
	ENDIF.
	POP P,E			;Recover stack
	SKIPN RINCME		;Include me in replies?
	IFSKP.
	  HRROI B,MAUSRS	;Yes, me
	  MOVE U,FREETO		;Get some free space
	  SETZM ADRFLG(U)
	  SETZM ADRLNK(U)
	  PUSH P,B
	  MOVEI A,ADRSTR(U)
	  HRLI A,(<POINT 7,>)
	  CALL MOVST0
	  MOVEI A,1(A)		;Point to next free word
	  MOVEI W,(A)		;Get new end of area
	  SUBI A,(U)		;Get length
	  STOR A,ADSIZ,(U)	;Store size field
	  POP P,B
	  MOVX A,RC%EMO		;Require an exact match
	  RCUSR%
	  MOVEM C,ADRUSR(U)
	  MOVEI T,CCLIST	;Add a cc from this string
	  CALL ADDTO0
	  SKIPL RINCME		;Want special cc to self?
	ANSKP.
	  HLRZ B,CCLIST		;Yes, flag special user number for this file
	  SETOM ADRUSR(B)
	ENDIF.
	CALL PRSCCL		;Parse default bcc list here
	HRRZ A,CMDRET		;Save where we came from
	HRROM A,SNDCAL		; flagging it is continuable
	MOVEM P,SENDPP		;Save stack for SNDRET
	MOVEI A,SEND1A		;Enter SEND level if error
	HRRM A,CMDRET
	IFXN. F,F%DIRE		;From MMail Dired mode?
	  CALL .EDTXT		;Yes, go into edit right away
	  HRRZ A,SNDCAL		;Restore caller context
	  HRRM A,CMDRET
	  CALL SEND0
	  DMOVE A,[ASCIZ/Dired/]
	  DMOVEM A,BUFNAM
	  DMOVEM A,EDINAM
	  RET
	ENDIF.
	SKIPE MNSMSG		;Insert current msg text?
	 CALL FORMS2		;Yes
	SKIPE REPDIS		;Display reply at startup?
	 CALL .DSHDR		;Yes, do so
.REPL7:	CALL GETTXT		;Get text of reply
	HRRZ A,SNDCAL		;Restore caller context
	HRRM A,CMDRET
	CALLRET SEND0		;And go get more or send it off
;;;Add user headers

.USHDR:	SKIPN USRHTB		;Any user headers defined?
	 ERROR <No defined user headers>
	MOVEI B,[FLDDB. .CMKEY,,USRHTB]
	CALL CMDFLD		;Get a keyword
	HLRZ U,(B)		;Save address of string
	CALL GETLIN
	CONFRM
	CALL USHDRL		;New header line
	MOVEI B,(U)		;Address of string
	CALL USHDR1
	MOVEI B,[ASCIZ/: /]
	CALL USHDR1
	MOVEI B,STRBUF		;And finally user's line
	CALL USHDR1
	DMOVEM D,USRHFP
	IDPB C,D		;End with a null
	RET

USHDRL:	DMOVE D,USRHFP		;Get pointers so far
	IFE. D
	  DMOVE D,[POINT 7,USRHDT
		   1-776*5]
	  RET			;First time out, init pointer
	ENDIF.
	MOVEI B,CRLF0		;Else put in newline first
USHDR1:	HRLI B,(<POINT 7,>)	;Copy a string and update count
	DO.
	  ILDB C,B
	  JUMPE C,R
	  IDPB C,D
	  AOJL E,TOP.
	ENDDO.
USHDRE:	ERROR <String space exhausted>

;;;Save current message draft in a file

.SSAVE:	CALL GETOFI		;Get output file with no default
	CONFRM
	MOVE O,[POINT 7,HDRPAG]
	MOVE A,[IDPB A,O]
	MOVEM A,MOVDSP		;Set up to move into memory
	SKIPN A,USRHDR		;Has any user headers?
	IFSKP.
	  ILDB A,A		;Just header options?
	ANDN. A			;Yes, go on to other header items
	  MOVE B,USRHDR		;Pointer to start of user headers
	  CALL MOVSB3		;Go add that in
	ENDIF.
	TXO F,F%RELD		;Relative domains must be in
	CALL MOVSB1		;Insert subject
	CALL MOVTO		;And To
	CALL MOVCC		;And cc
	CALL MOVREP		;And Reply-To
	CALL MOVRDT		;And In-Reply-To
	MOVEI B,[ASCIZ/

/]
	CALL MOVSB2		;And a couple blank lines
	SETZ A,
	IDPB A,O		;Mark end of this with a null too
	MOVE A,OUTJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%WR>
	OPENF%
	IFJER.
	  MOVE A,OUTJFN
	  JERROR <Can't open draft file "%1J">
	ENDIF.
	MOVE B,[POINT 7,HDRPAG,13]
	SETZ C,
	SOUT%
	HRROI B,TXTPAG		;And put in text
	SOUT%
CLOSFR:	CLOSF%
	 NOP
	SETZM OUTJFN
	RET

;;;Restore saved message draft

.RESTO:	CALL .SREST		;Load it up
	SKIPGE RSTMOD		;/SEND?
	 JRST SNDMSG		;Yes, just send it
	SKIPE RSTMOD		;/COMMAND?
	 JRST SEND1		;Yes, go to command mode right away
	CALL .DSHDR		;Display what we brought back
	SKIPE TXTPAG		;Is there text to the message?
	IFSKP.
	  CALL GETTXT		;No, get text of reply
	ELSE.
	  SETABT CMDABO		;Allow CTRL/N to abort back to toplevel
	  UTYPE [BYTE (7) 15,12,15,12,0]
	  HRRZ A,CMDRET		;Save where we came from
	  HRROM A,SNDCAL	; flagging it is continuable
	  MOVEM P,SENDPP	;Save stack for SNDRET
	  MOVEI A,SEND1A	;Enter SEND level if error
	  HRRM A,CMDRET
	  CALL .TEXT2		;Typeout and get some more text
	  HRRZ A,SNDCAL		;Restore caller context
	  HRRM A,CMDRET
	ENDIF.
	CALLRET SEND0		;And enter send mode

RSTLST:	FLDDB. .CMCFM,,,,,<[FLDDB. .CMSWI,,RSTTAB]>
RSTTAB:	RSTTBL,,RSTTBL
	CMD COMMAND,1
	CMD SEND,-1
	CMD TEXT,0
RSTTBL==<.-RSTTAB>-1

.SREST:	NOISE (FROM FILE)
	MOVEI B,[FLDDB. .CMIFI]
	CALL CMDFLD		;Get the file
	MOVEM B,TMPJFN
	SETZM RSTMOD
	MOVEI B,RSTLST
	CALL CMDFLD
	LOAD D,CM%FNC,(C)
	CAIN D,.CMCFM		;Confirm?
	 JRST RESTO0		;Yes
	HRRE B,(B)
	MOVEM B,RSTMOD
	CONFRM
RESTO0:	MOVE A,TMPJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	OPENF%
	IFJER.
	  MOVE A,TMPJFN
	  JERROR <Can't open draft file "%1J">
	ENDIF.
	CALL SNDIN0		;Erase everything so far
	MOVE A,TMPJFN
	MOVEI C,5000
	MOVEI D,.CHLFD		;Read a line at a time
	MOVE B,[POINT 7,HDRPAG]	;Read the headers in
RESTO1:	MOVE E,B		;Save the start of this line
	SIN%
	 ERJMP .+1
	ILDB T,E		;Get character at start of line
	CAIE T,.CHCRT		;Blank line?
	 CAIN T,.CHLFD
	  TDZA T,T
	JUMPN T,RESTO1
	DPB T,E			;Make it end with a null anyway
	SKIPA E,[POINT 7,HDRPAG]
RSTLUP:	 SKIPA E,B
RESTO2:	MOVE B,E		;Get copy of pointer
	MOVE C,[POINT 7,STRBUF]
	SETZM STRBUF
	SETZM STRBUF+1
RESTO3:	ILDB T,B
	JUMPE T,RSTTXT		;Done with headers
	CAIE T,.CHCRT		;End of line before : is an error
	 CAIN T,.CHLFD
	IFNSK.
	  MOVEI A,STRBUF
	  ERROR <%1S does not look like a header line>
	ENDIF.
	CAIL T,"a"
	 CAILE T,"z"
	  CAIA
	   SUBI T,"a"-"A"	;Make uppercase
	IDPB T,C
	CAIE T,":"		;End of the name of it?
	 JRST RESTO3
	DMOVE C,STRBUF
	CAME C,[ASCIZ/TO:/]
	 CAMN C,[ASCIZ/CC:/]
	  JRST RSTTO		;Parse a to or cc list
	CAMN C,[ASCII /SUBJE/]
	 CAME D,[ASCIZ/CT:/]
	  JRST RSTRND		;Random line, insert as user option
	DO.
	  ILDB T,B		;Flush whitespace
	  CAIE T,.CHSPC
	   CAIN T,.CHTAB
	    LOOP.
	ENDDO.
	MOVE C,[POINT 7,SUBBUF] ;Where the subject goes
	IFN. T
	  DO.
	    CAIE T,.CHCRT
	     CAIN T,.CHLFD
	      EXIT.
	    IDPB T,C
	    ILDB T,B
	    JUMPN T,TOP.
	  ENDDO.
	  CAIN T,.CHCRT
	   IBP B		;Move over LF after CR
	ENDIF.
	MOVEI D,0
	IDPB D,C
	JUMPN T,RSTLUP

RSTTXT:	CALL PRSCCL		;Add default lists
	MOVE A,TMPJFN
RSTTX0:	BIN%
	JUMPE B,CLOSFR		;Eof, no text then
	CAIE B,.CHCRT
	 CAIN B,.CHLFD
	  JRST RSTTX0		;Flush CRLFs
	BKJFN%
	 NOP
	CALLRET INSFL3		;And now insert the file as text

RSTRND:	PUSH P,E		;Save current line
	CALL USHDRL		;New header line
	POP P,B			;Get line again
	DO.
	  ILDB T,B
	  CAIE T,.CHCRT
	   CAIN T,.CHLFD
	    EXIT.
	  JUMPE T,ENDLP.
	  AOJGE E,USHDRE
	  IDPB T,D
	  LOOP.
	ENDDO.
	DMOVEM D,USRHFP		;Update pointers
	CAIN T,.CHCRT
	 IBP B			;Move over LF after CR
	MOVEI C,0
	IDPB C,D
	JUMPN T,RSTLUP
	JRST RSTTXT

RSTTO:	MOVE A,E		;Get start of line again
	PUSH P,RCCOTH		;Don't change type of message
	SETZB E,RCCOTH		;Assume default
	CALL PRTOCC		;Parse to and cc lines
	POP P,RCCOTH
	MOVE E,A
	DO.
	  LDB B,E		;Now back up to start of line that didn't match
	  CAIE B,.CHCRT
	   CAIN B,.CHLFD
	    JRST RESTO2
	  JUMPE B,RSTTXT
	  ADD E,[7B5]
	  SKIPGE E
	   SUB E,[43B5+1]
	  LOOP.
	ENDDO.
;;;Move messages into files

.COPY:	SKIPA A,[PUTMSG]
.MOVE:	 MOVEI A,MOVMSG
	MOVEM A,DOMSG
	TXNE F,F%READ		;In read command?
	 JRST .RCOP1		;Yes
	CALL GETOUT		;Get output file
	CALL DFSQTH		;Get message sequence
	MOVE A,OUTJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%APP> ;Open for append
	OPENF%
	IFJER.
	  MOVE A,OUTJFN
	  JSNARL <Can't open "%1J"> ;Give error message
	  RLJFN%
	   NOP
	  SETZM OUTJFN
	  RET
	ENDIF.
.COPY1:	CALL DELET1		;Go handle the sequence
.COPY2:	SKIPL RINCME		;Special include me mode?
	IFSKP.
	  HRROI A,SAVFIL	;Yes, update name of last moved file
	  MOVE B,OUTJFN
	  MOVE C,[111110,,JS%PAF]
	  JFNS%
	ENDIF.
IFN NICSW,<
	TXNE F,F%LIST
	 RET
>;IFN NICSW
	MOVE A,OUTJFN
	CLOSF%
	 JERROR <Can't close output file>
	SETZM OUTJFN
	RET

.RCOP1:	CALL GETOUT		;Get output file
	CONFRM
.RCOPA:	MOVE A,OUTJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%APP> ;Open for append
	OPENF%
	IFJER.
	  MOVE A,OUTJFN
	  JSNARL <Can't open "%1J"> ;Give error message
	  RLJFN%
	   NOP
	  SETZM OUTJFN
	  RET
	ENDIF.
.RCOP2:	CALL @DOMSG		;Process it
	CALLRET .COPY2		;And go close it up
LSWTAB:	NLSWTB,,NLSWTB
	CMD HEADERS-ONLY,HDONLY
	CMD SEPARATE-PAGES,SEPPGS
IFN NICSW,<
	CMD UNIT
	EXTERN .UNIT		;In MMNIC.MAC
>;IFN NICSW
NLSWTB==<.-LSWTAB>-1

IFN NICSW,<
; FILE-LIST switch table
FSWTAB:	NFSWTB,,NFSWTB
	CMD HEADERS-ONLY,HDONLY
	CMD SEPARATE-PAGES,SEPPGS
NFSWTB==<.-FSWTAB>-1

; READ LIST subcommand switch table
RSWTAB:	NRSWTB,,NRSWTB
	CMD UNIT
NRSWTB==<.-RSWTAB>-1
>;IFN NICSW

.FLIST:	CALL GETOFI
	JSP D,.LIST0		;Do the work
	 NOP			;Command specific

IFE NICSW,<
.LIST:	SETZM OUTJFN		;Forget any old output file
	JSP D,.LIST0		;Do the work
	 NOISE (ON LISTING DEVICE) ;Command specific

.LIST0:	MOVEI A,LPTMSG
	MOVEM A,DOMSG
	SETZM HDONLY		;Default headers + msgs
	SETZM SEPPGS		;Default no separate pgs
	IFXN. F,F%READ
	  XCT 0(D)		;Do command specific inst. (NOISE, etc)
	  CONFRM
	  CALL GETLPT		;Open device
	   RET			;Failed
	  CALLRET .RCOP2	;Now send that single message
	ENDIF.
	NOISE (OPTIONS)
	MOVEI B,[FLDDB. .CMSWI,,LSWTAB,<message sequence
  or optional LIST switch,>]
	CALL $COMND
	IFXE. A,CM%NOP		;Was a switch given?
	  HRRZ B,(B)		;Get flag to set
	  SETOM (B)		;And set it
	ENDIF.
	CALL DFSQTH		;Get sequence
	CALL GETLPT		;Open device
	 RET			;He didn't really mean it
	SKIPN LSTHDR		;Include headers in the list?
	 SKIPE HDONLY		;No, did user override with /HEADERS-ONLY?
	IFSKP. <JRST .COPY1>	;No, just handle the sequence
	MOVE A,[POINT 7,WRTPGS]	;Output file name identifier
	MOVEI B,[ASCIZ/-- Messages from file: /]
	CALL MOVSTR
	MOVE B,MSGJFN
	MOVE C,[111110,,JS%PAF]
	JFNS%
	MOVEI B,[ASCIZ/ --
   /]
	CALL MOVSTR
	SETO B,			;Note date/time
	MOVX C,OT%DAY!OT%FDY!OT%FMN!OT%4YR!OT%DAM!OT%SPA!OT%TMZ!OT%SCL
	ODTIM%
	MOVEI B,[ASCIZ/

/]
	CALL MOVST0
	MOVE A,OUTJFN		;Write it to the file
	HRROI B,WRTPGS
	SETZ C,
	SOUT%
	MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	MOVEM A,PREVPT
	TXO F,F%TYPS		;Say to print numbers of things done
.LIST1:	CALL NXTMSG		;Cycle through messages once
	 JRST .LIST2
	MOVE A,PREVPT		;Paranoia check
	CAMN A,[POINT 12,PRVSQZ-1,23] ;Reached end of list?
	 ERROR <Too many messages in list>
	MOVEI A,(M)		;Get message index
	IDIVI A,MSGLEN		;Convert to number
	IDPB A,PREVPT
	MOVE O,[POINT 7,WRTPGS]
	CALL TYPHD0
	MOVE A,OUTJFN
	HRROI B,WRTPGS
	SETZ C,
	SOUT%
	JRST .LIST1

.LIST2:	MOVEI A,7777		;Tie off list
	IDPB A,PREVPT
	MOVE A,OUTJFN		;All done, put this on one page
	HRROI B,CRLF0
	SETZ C,
	SOUT%
	MOVX B,.CHFFD		;Form feed
	BOUT%
	SKIPE HDONLY		;Headers only?
	IFSKP.
	  MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	  MOVEM A,PREVPT
	  DO.
	    MOVE A,M		;Save last message pointer
	    ILDB M,PREVPT	;Get message to output
	    CAIN M,7777		;End of list?
	    IFSKP.
	      IMULI M,MSGLEN	;Convert to message index
	      CALL LPTMSG	;Output message on printer
	      LOOP.		;Get next message to output
	    ENDIF.
	  ENDDO.
	  MOVE M,A		;Done, get back M so current isn't 7777
	ENDIF.
	MOVE A,OUTJFN
	CLOSF%
	 JERROR <Can't close output file>
	SETZM OUTJFN
	RET
>;IFE NICSW
IFN NICSW,<
.LIST:
.LASER:	TXO F,F%LIST		;Indicate doing a list command
	JSP D,.LIST0		;[NIC2040]
	 NOISE (MESSAGE)	;[NIC2040]

.LIST0:	MOVEI A,LPTMSG
	MOVEM A,DOMSG
	SETZM HDONLY		;Default headers + msgs
	SETZM SEPPGS		;Default no separate pgs
	MOVE A,PRNUNT		;Set unit for this request to default printer
	MOVEM A,LPRNUN		;(User can change with /UNIT: switch)
	IFXN. F,F%READ
	  XCT 0(D)		;Do command specific inst. (NOISE, etc)
	  MOVEI B,[FLDDB. .CMCFM,,,,,<[FLDDB. .CMSWI,,RSWTAB]>]
	  CALL CMDFLD		;Get a confirmation or switch
	  LOAD D,CM%FNC,(C)	;Get function code that won.
	  CAIE D,.CMSWI		;Switch?
	  IFSKP.		;Yes, then we need to do the unit junk
	    CALL .UNIT		;Only choice there was, so do it
	    MOVEM B,LPRNUN	;Stash new printer name
	    CONFRM		;Confirm the command
	  ENDIF.
	ELSE.
	  NOISE (OPTIONS)
	  MOVEI B,[FLDDB. .CMSWI,,LSWTAB,<message sequence
  or optional LIST switch,>]
	  TXNN F,F%LIST		;Not list command, alternate switch table
	   MOVEI B,[FLDDB. .CMSWI,,FSWTAB,<message sequence
  or optional FILE-LIST switch,>]
	  CALL $COMND
	  IFXE. A,CM%NOP
	    HRRZ B,(B)		;Get pointer to item
	    CAIE B,.UNIT	;User wants to enter unit name?
	    IFSKP.		;Yes, then go parse it
	      CALL .UNIT
	      MOVEM B,LPRNUN	;Success, then store new printer name
	    ELSE.
	      SETOM (B)		;Flag, set it
	    ENDIF.
	  ENDIF.
	ENDIF.
	TXNN F,F%READ		;Not for read...
	 CALL DFSQTH		;Get sequence
	IFXN. F,F%LIST
	  MOVE A,[POINT 7,STRBUF]	; Make a filename string
	  MOVE B,MYDIR		; Directory number
	  DIRST%		; Stick it in there
	   TRN
	  HRROI B,[ASCIZ/MM-LIST.TMP.-1;T/]	;[NIC2040]
	  SETZ C,
	  SOUT%
	  IDPB C,B		; Tie it off
	  MOVX A,GJ%SHT!GJ%FOU!GJ%NEW	;[NIC2040]
	  HRROI B,STRBUF
	  GTJFN%		;[NIC2040]
	   JERROR <Can't get listing temporary file>
	  MOVEM A,OUTJFN	;[NIC2040]
	  TXO A,CF%NUD
	  MOVX B,FB%RET		;Set retention count to zero
	  SETZ C,
	  CHFDB%
	   ERJMP .+1
	ENDIF.
	CALL GETLPT		;Open device
	 RET			;He didn't really mean it
	IFXN. F,F%READ
	  CALL LPTMSG
	ELSE.
	  SKIPE LSTHDR		;Include headers in the list?
	  IFSKP.
	    CALL .COPY1		;No, do it	
	  ELSE.	 
	    MOVE A,[POINT 7,WRTPGS]	;Output file name identifier
	    MOVEI B,[ASCIZ/-- Messages from file: /]
	    CALL MOVSTR
	    MOVE B,MSGJFN
	    MOVE C,[111110,,JS%PAF]
	    JFNS%
	    MOVEI B,[ASCIZ/ --
   /]
	    CALL MOVSTR
	    SETO B,		;Note date/time
	    MOVX C,OT%DAY!OT%FDY!OT%FMN!OT%4YR!OT%DAM!OT%SPA!OT%TMZ!OT%SCL
	    ODTIM%
	    MOVEI B,[ASCIZ/

/]
	    CALL MOVST0
	    MOVE A,OUTJFN	;Write it to the file
	    HRROI B,WRTPGS
	    SETZ C,
	    SOUT%
	    MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	    MOVEM A,PREVPT
	    TXO F,F%TYPS	;Say to print numbers of things done
	    DO.
	      CALL NXTMSG	;Cycle through messages once
	      IFSKP.
	        MOVE A,PREVPT	;Paranoia check
	        CAMN A,[POINT 12,PRVSQZ-1,23]	;Reached end of list?
	         ERROR <Too many messages in list>
	        MOVEI A,(M)	;Get message index
	        IDIVI A,MSGLEN	;Convert to number
	        IDPB A,PREVPT
	        MOVE O,[POINT 7,WRTPGS]
	        CALL TYPHD0
	        MOVE A,OUTJFN
	        HRROI B,WRTPGS
	        SETZ C,
	        SOUT%
	        LOOP.
	      ENDIF.
	    ENDDO.
	    MOVEI A,7777	;Tie off list
	    IDPB A,PREVPT
	    MOVE A,OUTJFN	;All done, put this on one page
	    HRROI B,CRLF0
	    SETZ C,
	    SOUT%
	    MOVX B,.CHFFD	;Form feed
	    BOUT%
	    SKIPE HDONLY	;Headers only?
	    IFSKP.
	      MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	      MOVEM A,PREVPT
	      DO.
	        MOVE A,M	;Save last message pointer
	        ILDB M,PREVPT	;Get message to output
	        CAIN M,7777	;End of list?
	        IFSKP.
	          IMULI M,MSGLEN	;Convert to message index
	          CALL LPTMSG	;Output message on printer
	          LOOP.		;Get next message to output
	        ENDIF.
	      ENDDO.
	      MOVE M,A		;Done, get back M so current isn't 7777
	    ENDIF.
	  ENDIF.
	ENDIF.
	MOVE A,OUTJFN
	TXNE F,F%LIST
	 TXO A,CO%NRJ		;Don't release JFN if LIST command
	CLOSF%
	 JERROR <Can't close output file>
	IFXN. F,F%LIST		;If list command, then queue the request
	  HRRZ B,A		;Get canonical JFN
	  HRROI A,QUEFIL	;Name of the filename to list
	  MOVX C,FLD(.JSAOF,JS%DEV)!FLD(.JSAOF,JS%DIR)!FLD(.JSAOF,JS%NAM)!FLD(.JSAOF,JS%TYP)!FLD(.JSAOF,JS%GEN)!JS%PAF
	  JFNS%			;Get the name
	  SKIPN A,LPRNUN	;Any special unit?
	  IFSKP.		;Yes...
	    MOVEM A,QUEUNT	;Stash it
	    MOVX A,QA%IMM!FLD(1,QA%LEN)!FLD(.QBPNM,QA%TYP)
	  ELSE.
	    HRLZI A,.QBUPH	;Physical unit number in LH is zero
	    MOVEM A,QUEUNT
	    MOVX A,QA%IMM!FLD(1,QA%LEN)!FLD(.QBUNT,QA%TYP)
	  ENDIF.
	  MOVEM A,QUNARG	;Stash the type of argument
	  MOVEI A,LQUARG	;Length of QUEUE% arguments
	  MOVEI B,QUEARG
	  QUEUE%		;Queue the request
	   ERJMP .+1
	  MOVE A,OUTJFN		;Get the JFN back
	  RLJFN%		;Flush it
	   TRN			;Don't care about errors
	ENDIF.
	SETZM OUTJFN
	RET

PRNUNT::0			; Default unit for print requests
LPRNUN::0			; Local variable, unit for this print request
QUEFIL: BLOCK ^D60
QUEARG:	FLD(^D60,QF%RSP)!FLD(.QUPRT,QF%FNC)
	QUEFIL
	FLD(^D58,QA%LEN)!FLD(.QBFIL,QA%TYP)
	-1,,QUEFIL
	QA%IMM!FLD(1,QA%LEN)!FLD(.QBCOP,QA%TYP)
	1			; 1 copy
	QA%IMM!FLD(1,QA%LEN)!FLD(.QBFMT,QA%TYP)
	.QBFAS			; ASCII file
	QA%IMM!FLD(1,QA%LEN)!FLD(.QBODP,QA%TYP)
	1			; Delete the file upon request completion
	FLD(2,QA%LEN)!FLD(.QBACT,QA%TYP)
	-1,,[ASCIZ/LPTACC/]	; The account to use
	QA%IMM!FLD(1,QA%LEN)!FLD(.QBFRM,QA%TYP)
	SIXBIT/NORMAL/		
QUNARG:	QA%IMM!FLD(1,QA%LEN)!FLD(.QBPNM,QA%TYP)
QUEUNT: 0	
LQUARG==:.-QUEARG
>;IFN NICSW
.RFORW:	NOISE (MESSAGE TO)
	CALL SNDIN0
	CALL GETTO0		;Get To: without prompting
	JRST .FORW1		;Join common code

.FORWA:	CALL DFSQTH		;Get message sequence, default to this
	TXO F,F%TYPS		;Say to print numbers of things done
	DO.
	  CALL NXTMSG		;Get next guy in list
	   ERROR <No messages to forward>
	  CALL CHKDEL		;Don't forward deleted msgs
	   LOOP.
	ENDDO.
	CALL SNDIN0		;Reset message drafts
	CALL GETTO		;Get recipients
.FORW1:	CALL PRSCCL		;Add default lists
	CALL GETTXT		;Get initial comments
	SETZB A,SUBBUF		;Init subject, get canonical pointer to text
	ADJBP A,TXTPTR
	CAMN A,[POINT 7,TXTPAG-1,34] ;Empty?
	IFSKP.
	  LDB C,A		;Get last char
	  MOVEI B,CRLF0
	  CAIE C,.CHLFD		;Unless have crlf
	   CALL MOVSTR		;Put one in
	  MOVEI B,[ASCIZ/                ---------------

/]
	  CALL MOVSTR
	  MOVEM A,TXTPTR	;Update pointer
	ENDIF.
	IFXN. F,F%READ		;If in read
	  CALL FORMSG		;Forward current message
	ELSE.
; Here in full command mode.  First output a header list if more than 1.
	  SETZM NRECNT		;Zero msg counter
	  PUSH P,TXTPTR		;Save starting text ptr
	  MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	  MOVEM A,PREVPT
	  CALL CRIF		;CRLF first if needed
	  DO.
	    CALL CHKDEL		;Deleted?
	    IFSKP.
	      MOVE A,PREVPT	;Paranoia check
	      CAMN A,[POINT 12,PRVSQZ-1,23] ;Reached end of list?
	       ERROR <Too many messages in list>
	      MOVEI A,(M)	;Get message index
	      IDIVI A,MSGLEN	;Convert to number
	      IDPB A,PREVPT
	      MOVE A,TXTPTR	;Output msg #
	      AOS B,NRECNT
	      MOVX C,NO%LFL!NO%OOV!4B17!^D10
	      NOUT%
	       NOP
	      MOVEI B,")"
	      IDPB B,A
	      MOVEM A,TXTPTR	;Save the pointer
	      CALL FWDHDR	;Set up header string
	      MOVE A,TXTPTR	;Now add it to the text
	      MOVEI B,WRTPGS
	      CALL MOVSTR
	      MOVEM A,TXTPTR	;Save new ending ptr
	    ENDIF.
	    CALL NXTMSG		;Get next guy in list
	     EXIT.		;Done
	    LOOP.		;Do next message
	  ENDDO.
; Here we check on overwriting the headers if only 1 msg going
	  MOVEI A,7777		;Tie off list
	  IDPB A,PREVPT
	  POP P,A		;Recover starting text ptr
	  MOVE B,NRECNT		;More than 1 msg?
	  CAILE B,1
	  IFSKP.
	    MOVEM A,TXTPTR	;No, overwrite headers
	    CALL FORMSG		;Just do the one
	  ELSE.
	    SETZM NRECNT	;And the msg counter
	    MOVE A,[POINT 12,PRVSEQ] ;Initialize previous sequence pointer
	    MOVEM A,PREVPT
	    DO.
	      MOVE A,M		;Save current sequence
	      ILDB M,PREVPT	;Get message to output
	      CAIN M,7777	;End of list?
	      IFSKP.
		IMULI M,MSGLEN	;No, convert to message index
		MOVE A,TXTPTR	;Output msg #
		MOVEI B,[ASCIZ/
Message /]
		CALL MOVSTR
		AOS B,NRECNT
		MOVEI C,^D10
		NOUT%
		 NOP
		MOVEI B,[ASCIZ/ -- ************************
/]
		CALL MOVSTR
		MOVEM A,TXTPTR	;Save the pointer
		CALL FORMSG	;Forward this one too
		LOOP.
	      ENDIF.
	    ENDDO.
; Here the last forwarded msg has been copied
	    MOVE M,A		;Restore current message so not 7777
	  ENDIF.
	ENDIF.
	MOVE A,TXTPTR
	SETZ B,			;Finish with null
	IDPB B,A
	CALLRET SEND0		;Maybe send it off or get more
;;;Remail a message to someone

.RREMA:	NOISE (MESSAGE TO)
	CALL SNDIN0
	CALL GETTO0		;Get To: without prompting
	JRST RMLMSG		;Join common code

.REMAI:	CALL DFSQTH		;Get a sequence and default it
	CALL SNDIN0		;Erase the message draft
	CALL GETTO		;Get the to: list
	MOVEI A,RMLMSG
	CALLRET DOMSGS		;Handle list of messages
.SYSTE:	CONFRM
SYSTE1:	MOVX A,GJ%OLD!GJ%SHT!GJ%ACC
	MOVEI B,[ASCIZ/SYSTEM/]
	CALL GETMFL
	 ERROR <No system message file>
	PUSH P,A		;Save JFN
	TXO F,F%MOD!F%RONL	;Flag for doing system mail
	TXZ F,F%F1		;Not the examine command
	CALLRET GETF1
;;; BBoard command and facility

.BBOAR:	MOVSI A,[GJ%OLD!GJ%XTN+1
		 .-.
IFE NICSW,<
		 -1,,MLBXDV
		 -1,,BBDIR
>;IFE NICSW
IFN NICSW,<
		 -1,,BBDEV
		 0
>;IFN NICSW
		 -1,,MLBXNM
		 -1,,MLBXEX
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0]		;.GJATR
	HRRI A,CMDGTB		;Initialize GTJFN% block
	BLT A,CMDGTB+.GJATR
	MOVEI B,[FLDDF. .CMKEY,,BBDTAB,<bulletin board mailbox,>,DEFBBD,<[
		 FLDDB.	.CMFIL]>]
	CALL $COMND		; "MAIL.TXT.1" default
	IFXN. A,CM%NOP		;Was a file name recognized?
	  HLLZS CMDGTB+.GJGEN	;No, toss away generation 1 default
	  SETZM CMDGTB+.GJDEV	;Toss all defaults
	  SETZM CMDGTB+.GJDIR
	  SETZM CMDGTB+.GJNAM	;Toss away "MAIL" default
	  SETZM CMDGTB+.GJEXT	;Toss away "TXT" default
	  MOVEI B,[FLDDF. .CMKEY,,BBDTAB,<bulletin board mailbox,>,DEFBBD,<[
		   FLDDB. .CMFIL]>]
	  CALL CMDFLD		;No defaults
	ENDIF.
	PUSH P,B		;Save data
	LOAD T,CM%FNC,(C)	;Get field type parsed
	MOVEI B,CNFCMD		;Have user confirm this command
	CALL $COMND
	IFXN. A,CM%NOP		;Okay?
	  POP P,A		;No, release JFN 
	  CAIN T,.CMFIL		;If it was a JFN...
	   RLJFN%
	    NOP
	  JERROR		;And go away
	ENDIF.
	TXO F,F%BB!F%F1		;BBoard time, F%F1 signals RONLY later
	CAIN T,.CMFIL		;File spec?
	 JRST GETFA		;Join get/exam code with JFN pushed
	MOVE A,[POINT 7,STRBUF]	;Construct bulletin board name
IFE NICSW,<
	MOVEI B,MLBXDV		;Start with device
>;IFE NICSW
IFN NICSW,<
	MOVEI B,BBDEV
>;IFN NICSW
	CALL MOVSTR
	MOVX B,":"
	IDPB B,A
IFE NICSW,<
	MOVX B,.CHLAB
	IDPB B,A
	MOVEI B,BBDIR
	CALL MOVSTR
	MOVX B,"*"
	IDPB B,A
	MOVX B,.CHRAB
	IDPB B,A
>;IFE NICSW
	POP P,D			;Pop index to BBoard table
	HLRO B,0(D)
	SOUT%
	MOVX B,"."
	BOUT%
	HRROI B,MLBXEX
	SOUT%			;Tie off with null
	IDPB C,A
	MOVX A,GJ%OLD!GJ%SHT!GJ%ACC!GJ%IFG
	HRROI B,STRBUF
	GTJFN%
	IFJER.
	  MOVX A,GJ%OLD!GJ%SHT!GJ%ACC!GJ%DEL!GJ%IFG ;Maybe deleted?
	  HRROI B,STRBUF	;Same file name
	  GTJFN%		;Is it there now?
	   JERROR <No BBoard message file>
	  RLJFN%		;Yeah, don't want it
	   NOP			;Shouldn't fail
	  ERROR <Empty BBoard message file>
	ENDIF.
	HRRZS A			;Flush flags
	PUSH P,A		;Save JFN, and
	JRST GETFA		;Join get/exam code
.DAYTI:	CONFRM
	MOVX A,.PRIOU
	SETOB B,C
	ODTIM%			;Give us ye old daytime
	RET
.ALIAS:	STKVAR <<ACCBLK,<.ACJOB+1>>,ACCDIR>
	MOVE A,[FLDDB. .CMUSR]	;Parse user name
	MOVEM A,CMDFLB
	UDEF MUSRST		;Default to login user name
	MOVEI B,CMDFLB
	CALL CMDFLD
	MOVEM B,ACCDIR		;Remember directory number
	CONFRM
	TXZN F,F%ALIA		;Already accessing a directory?
	IFSKP.
	  MOVX A,AC%REM!.ACJOB+1 ;Remove access of what's in blk
	  MOVEI B,ACCBLK
	  ACCES%
	   ERJMP .+1
	ENDIF.
	SETZ A,			;No flags
	MOVE B,ACCDIR		;Pick up required user to access
	RCDIR%			;Convert to directory number
	MOVEM C,.ACDIR+ACCBLK
	SETZM .ACPSW+ACCBLK	;First try without password
	SETOM .ACJOB+ACCBLK
	DO.
	  MOVX A,AC%OWN!.ACJOB+1 ;ACCESS and not CONNECT
	  MOVEI B,ACCBLK	;Try the access
	  ACCES%
	  IFJER.
	    MOVX A,.FHSLF	;Failed, see if need a psw
	    GETER%
	    HRRZS B
	    CAIE B,ACESX3
	     ERROR <Unable to access user directory because: %2E>
	    CALL GETPSW		;Get a password
	    HRROI B,STRBUF	;Try again with the password
	    MOVEM B,.ACPSW+ACCBLK
	    LOOP.
	  ENDIF.
	ENDDO.
	HRROI A,[ASCIZ//]	;Only do this once
	RSCAN%
	 NOP
	MOVE B,ACCDIR
	CALL UNTAKE		;Cancel any pending TAKE file
	CAMN B,MYUSR		;Aliased to self?
	 TDZA F,F		;Yes, clear all flags
	  MOVX F,F%ALIA		;Else clear all flags except ALIAS flag
	CALL SETUSR
	SKIPLE MSGJFN		;Do we presently have a file?
	 CALL UNMAPF		;Yes, unmap file
	SETZM LASTM		;No more messages
	CALL CLOSEF		;Release old cruft if present
	CALL CLOSEI		;Old index if present as well
	CALL KILED0		;Kill editor too
	MOVE P,[IOWD NPDL,PDL]	;Reset stack
	CALLRET GOINIT		;Reenter MM doing init file, etc.

	ENDSV.

;;;Set user number in B as login user name

SETUSR:	HRROI A,MAUSRS		;Temp name for speed
	MOVEM B,MYAUSR		;Set up alias user number
	DIRST%
	 NOP
	MOVE A,[POINT 7,MBXFIL]
	MOVEI B,MLBXFN		;Make mailbox string
	CALLRET MKPSTR
; Routine to fetch a password string
; Call:    CALL GETPSW
; Return:  +1, string in STRBUF
GETPSW:	PROMPT <Password: >
	MOVX A,.PRIIN		;Get current TTY mode
	RFMOD%
	PUSH P,B		;Save for later
	TXZ B,TT%ECO!TT%ECM	;Kill echo
	TXO B,TT%LIC		;Raise input
	SFMOD%
	STPAR%
	CALL GETLNC		;Get password string
	CALL CRLF		;Echo a CRLF
	MOVX A,.PRIIN		;Restore echo
	POP P,B
	SFMOD%
	STPAR%
	RET
	
;;;Give user help
.HELP:	NOISE (ON TOPIC)
	DEFALT (GENERAL)
	MOVEI A,H1CMDT		;Otherwise, help for top-level
	TXNE F,F%READ		;In read command?
	 MOVEI A,H1RCMD
	TXNE F,F%SEND		;In send command?
	 MOVEI A,H1SCMD
	CALL SUBCMD
	HLRZ B,(A)		;Code (LH.NE.0) or a string adr?
	JUMPN B,(A)		;Datum is code, go do it
	PUSH P,A
	CONFRM
	POP P,A
	SETABT CMDABO		;Allow CTRL/N aborting
	MOVE B,(A)		;Pick up string
	HRROI A,(B)
	PSOUT%
	RET

; HELP for SET command

.HSET::	MOVEI B,[FLDDB. .CMKEY,,INIVTB,,,<[FLDDB. .CMCFM]>]
	CALL CMDFLD
	LOAD C,CM%FNC,(C)	;Get the type parsed
	CAIE C,.CMCFM		;HELP SET <RETURN>?
	IFSKP.
	  HRROI A,.HSETM	;Yes, output default msg
	  PSOUT%
	  RET
	ENDIF.
	PUSH P,B		;Stash the help address for now
	CONFRM			;Confirm command
	POP P,U			;Restore help pointer
	SETABT CMDABO		;Allow CTRL/N aborting
	MOVX A,.PRIOU		;Set up output for CRISHW
	MOVEM A,TMPJFN
	HRRZ A,(U)		;Ptr to TBLUK% data
	HLRZ A,(A)		;Ptr to user data [INIDTA,,HLPMSG]
	HRRO A,(A)		;Pick up as string pointer
	PSOUT%			;Output help
	HRROI A,[ASCIZ/
This variable is currently set to:
/]
	PSOUT%
	CALLRET CRISHW		;And go print current value for user

; General help

.GENER::CONFRM
	HRROI B,[ASCIZ/HLP:MM.HLP/]
	MOVX A,GJ%OLD!GJ%SHT
	GTJFN%
	 JERROR <No help available>
	MOVEM A,TMPJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	OPENF%
	IFJER.
	  JWARN <Can't open help file>
	ELSE.
	  DO.
	    MOVE A,TMPJFN
	    BIN%
	    IFNJE.
	      MOVX A,.PRIOU
	      BOUT%
	      LOOP.
	    ENDIF.
	  ENDDO.
	ENDIF.
CLSTMP:	SKIPLE A,TMPJFN
	 CALL $CLOSF
	SETOM TMPJFN
	RET
.ECHO:	NOISE (TO THE TERMINAL)
	CALL GETLIN		;Get line from user
	CONFRM
	HRROI A,STRBUF		;Echo the input line
	PSOUT%
	RET

.ENABL:	NOISE (CAPABILITIES)
	CONFRM
	TXZE F,F%RONL		;This may let us mung a file
	 CALL LCKFIL		;Lock the file
	MOVX A,.FHSLF
	SETO C,
	EPCAP%
	RET

LCKFIL:	MOVX A,EN%SHR!EN%BLN	;Shared access, no level #'s
	HRR A,MSGJFN		;This file
	MOVEM A,ENQBLK+.ENQLV
	DO.
	  DMOVE A,[.ENQAA	;Try and get lock, but don't wait
		   ENQBLK]
	  ENQ%
	  IFJER.
	    WARN <File is locked, waiting...>
	    MOVEI A,^D5000	;Wait a bit
	    DISMS%
	    LOOP.		;Now try again
	  ENDIF.
	ENDDO.
	RET

.DISAB:	NOISE (CAPABILITIES)
	CONFRM
	TXOE F,F%RONL		;Don't allow any more file munging
	IFSKP.
	  DMOVE A,[.DEQID	;Get rid of any locks we got
		   REQID]
	  DEQ%
	   ERJMP .+1		;Ignore failure
	ENDIF.
	MOVX A,.FHSLF
	RPCAP%
	TXZ C,.RHALF
	EPCAP%
	RET
.QUIT:	CONFRM
QUIT0:	CALL UNMAPF		;Unmap old file
	SKIPG MSGJFN		;Have a JFN?
	IFSKP.
	  DMOVE A,[.DEQID	;Yes, get rid of any locks we got
		   REQID]
	  DEQ%
	   ERJMP .+1		;Ignore failure
	  MOVE A,MSGJFN
	  CALL $CLOSK
	ENDIF.
	CALL CLOSF1
	HALTF%			;Quit back to the EXEC
	SKIPG A,MSGJFN		;If we have JFN
	 RET
	PUSH P,M		;Save current message number
	PUSH P,LASTM		;And total number of messages
	PUSH P,LASTRD		;And original read date
	TXO F,F%AMOD		;Hack to not print stuff
	TXNN F,F%MOD		;Reading system mail?
	 TXNN F,F%RONL		;No, is file read-only?
	  TXZA F,F%F1		;System mail or not read only
	   TXO F,F%F1		;Read only, don't update dates
	CALL GETF3		;Get file back
	TXZ F,F%AMOD		;Undo mischief
	POP P,LASTRD		;Restore first read date
	CALL RECEN2		;Remark recent msgs
	POP P,A			;Get former last message
	POP P,M			;And current message
	CALLRET CHECKN		;Print any new messages
;;;List of recipients of bug reports for this version of MM
BUGLST:	ASCIZ/Bug-MM/

.BUG:	CONFRM
	CALL SNDINI		;Setup for sending a message
	MOVE A,[POINT 7,BUGLST]	;Process list of bug report recipients
	SETZ E,			;Set the folks up
	TXZ F,F%CC		;As to recipients
	TXO F,F%F4		;Ignore error in setup
	CALL PRADDT		;Process the list
	 SKIPE TOLIST		;Could we parse any of them?
	IFSKP.
	  MOVE A,[POINT 7,[ASCIZ/Operator/]] ;Use OPERATOR as last resort
	  SETZ E,		;Set up
	  TXZ F,F%CC!F%F4	;As to recipients
	  CALL PRADDT		;Process the list
	ENDIF.
	MOVE A,[POINT 7,SUBBUF]
	MOVEI B,[ASCIZ/Bug in/]
	CALL MOVSTR		;Setup default subject for this
	PUSH P,A
	CALL GETVER		;Tell what version is buggy
	POP P,A
	MOVEI B,STRBUF
	CALL MOVST0
	CITYPE < Please enter your MM comments or suggestions.
>
	HRRZ A,CMDRET		;Save where we came from
	HRROM A,SNDCAL		; flagging it is continuable
	MOVEM P,SENDPP		;Save stack for SNDRET
	MOVEI A,SEND1A		;Enter SEND level if error
	HRRM A,CMDRET
	CALL GETTXT		;Get text of reply
	HRRZ A,SNDCAL		;Restore caller context
	HRRM A,CMDRET
	CALLRET SEND0		;And go get more or send it off
.VERSI:	CONFRM
.VERS1:
IFE NICSW,<
	HRRO A,LCLHST		;Output local host name
>
IFN NICSW,<
	HRROI A,LCLHNM		;absolute local host name
>
	PSOUT%
	CALL GETVER
	UTYPE STRBUF
	RET
.SET:	NOISE (VARIABLE)
	MOVEI B,[FLDDB. .CMKEY,,INIVTB]
	CALL CMDFLD		;Get the name of the variable
	HRRZ T,(B)
	HLRZ N,(T)		;N points to [INIDTA,,HLPMSG]
	HRR T,(T)		;Get pointer to variable
	HLL T,(N)		;Get data
	NOISE (TO)
	HLRE N,T		;Get length of string
	JUMPE N,.VROCT		;Not a string, get an octal number
IFN NICSW,<
	CAIE N,.UNIT		;Want a printer keyword?
	IFSKP.			;Yes...
	  CALL .UNIT		;Get it, in B
	  PUSH P,B		;Save it
	  CONFRM
	  POP P,(T)		;Stash it
	  RET			;Done here
	ENDIF.
>;IFN NICSW
	CAIN N,INIDEC		;Want decimal number?
	 JRST .VRDEC		;Yes
	CALL GETLIN		;Read a line
	CONFRM
	IFG. N
	  MOVEI U,(T)		;Do routine if specified
	  MOVE T,[POINT 7,STRBUF]
	  JRST (N)
	ENDIF.
	MOVE B,[POINT 7,STRBUF]	;Trim the trailing white space
	CALL TRMTW
	HRROI A,(T)		;Where it goes
	HRLI A,440700
	MOVE B,[POINT 7,STRBUF]
	MOVM D,N
;	CALLRET STRCPY

;;;Copy a string, source in B, destination in A, length in D
STRCPY:	STKVAR <DSTPTR>
	MOVEM A,DSTPTR		;Save destination ptr in case overflow
	DO.
	  ILDB C,B		;Copy the string
	  IDPB C,A
	  SKIPE C
	   SOJGE D,TOP.
	ENDDO.
	CALL TRMSTR		;Clear last word of string
	JUMPGE D,R		;Okay if no overflow
	SETZ C,			;Tie off string (for 1 out of 5 case)
	DPB C,A
	MOVE C,DSTPTR
	WARN <String truncated to "%3S">
	RET

	ENDSV.
;;;Fetch a decimal or octal number
.VRDEC:	SKIPA B,[[FLDDB. .CMNUM,,^D10]]
.VROCT:	 MOVEI B,[FLDDB. .CMNUM,,^D8]
	CALL CMDFLD
	PUSH P,B
	CONFRM
	POP P,(T)
	RET

;;;Trim trailing white space from string

TRMTW:	ILDB C,B		;Find next occurrence
	CAIE C,.CHSPC		; of white space
	 CAIN C,.CHTAB
	  JRST TRMTW1
	JUMPN C,TRMTW		;Keep looking til end-of-string
	RET

TRMTW1:	MOVE A,B		;Remember where white begins
	ILDB C,B		;Follow white space
	CAIE C,.CHSPC		; as far as it goes
	 CAIN C,.CHTAB
	  JRST .-3
	JUMPN C,TRMTW		;End-of-string?
	DPB C,A			;Yes, terminate where white began

TRMSTR:	HLRZ B,A		;Get pointer info
	LSH B,-^D12		;Reduce to position
	SETO C,			;Initial mask
	LSHC B,(B)		;Shift mask to bits to keep
	ANDM C,0(A)		;Apply to last word of string
	RET
.FROM:	NOISE (NAME)
	CALL GETLIN		;Get line from user
	CONFRM
	MOVSI A,774000		;If there was no text entered,
	TDNE A,STRBUF		; then consider 'from self'
	IFSKP.
	  SETZM FRMSCM		;Special indication of from self
	  SETZM REPSCM		;Don't need Reply-To: set up
	  TXNE F,F%READ!F%SEND	;If top-level command
	   RET
	  SETZM FRMSAM		;Make it apply for all subsequent msgs
	  SETZM REPSAM
	  RET
	ENDIF.
	MOVE B,[POINT 7,STRBUF]	;Trim trailing white space
	CALL TRMTW
	DMOVE A,[POINT 7,FRMSCM	;Keep from field string here
		 POINT 7,STRBUF]
	MOVEI D,FRMSTL
	CALL STRCPY		;Copy the string
	IFXE. F,F%READ!F%SEND	;If top-level command
	  MOVE A,[POINT 7,FRMSAM] ;Make it apply for all subsequent msgs
	  HRROI B,FRMSCM
	  CALL MOVST0
	ENDIF.
.REPT1:	MOVE A,[POINT 7,REPSCM]	;Set up default Reply-To string here
	MOVEI B,MAUSRS		;My name
	CALL MOVSTR		;Put it in
	MOVE O,A		;Set up string pointer for MOVDSP
	MOVE A,[IDPB A,O]	;Set up output to memory
	MOVEM A,MOVDSP
	TXZ F,F%QUOT!F%RELD	;Don't quote it
	CALL MOVMHN		;Put in @SITE
	SETZ A,			;Tie off string
	IDPB A,O
	TXNE F,F%READ!F%SEND	;If top-level command
	 RET
	MOVE A,[POINT 7,REPSAM]	;Similarly for the Reply-to field
	HRROI B,REPSCM
	CALLRET MOVST0
.REPTO:	NOISE (ADDRESS)
	CALL GETLIN		;Get line from user
	CONFRM
	MOVSI A,774000		;If there was no text entered,
	TDNE A,STRBUF		; then consider 'from self'
	IFSKP.
	  SKIPE FRMSCM		;Is there a user-specified From?
	   JRST .REPT1
	  SETZM REPSCM		;Don't need Reply-To: set up
	  TXNN F,F%READ!F%SEND	;If top-level command
	   SETZM REPSAM		;Make it apply for all subsequent msgs
	  RET
	ENDIF.
	MOVE B,[POINT 7,STRBUF]	;Trim trailing white space
	CALL TRMTW
	DMOVE A,[POINT 7,REPSCM	;Keep from field string here
		 POINT 7,STRBUF]
	MOVEI D,FRMSTL
	CALL STRCPY		;Copy the string
	TXNE F,F%READ!F%SEND	;If top-level command
	 RET
	MOVE A,[POINT 7,REPSAM]	;Make it apply for all subsequent msgs
	HRROI B,REPSCM
	CALLRET MOVST0
.SORT:	NOISE (CHRONOLOGICALLY)
	CALL DFSQAL		;Get sequence, default to all
	TXO F,F%TYPS		;Print numbers of msgs done
	CALL INISRT		;Initialize sorting stuff
	MOVEI A,SRTMSG		;Go sort selected msgs
	CALL DOMSGS
	CALL PSTSRT		;Organize sorted msgs
	SKIPE NSORTD		;Anything sorted?
	 CALLRET CPYSRT		;Yes, copy sorted file
	RET
	SUBTTL Command subroutines

.RFLAG:	CONFRM
FLGMSG:	MOVX A,M%ATTN		;Flag message
	IORM A,MSGBTS(M)
	CALLRET UPDBIT

.RKILL:	CONFRM			;Confirm first
	CALL DELMSG		;Delete message
	CALLRET .RNEX1		;Go to next message

.RMARK:	CONFRM			;Confirm first
	CALLRET MRKMSG		;Now mark as seen

.RDELM:	CONFRM			;Confirm first
DELMSG:	SKIPA A,[M%DELE]	;Mark as deleted
MRKMSG:	 MOVX A,M%SEEN		;Mark as seen
	PUSH P,A		;Save bits
	MOVE A,MSGDAT(M)	;Get date of message
	IFXN. F,F%BB		;Playing with BBoards?
	  CAMLE A,BBXDAT	;Later than last one written?
	   CALL SXDAT		;Set it into index file
	ENDIF.
	POP P,A			;Restore bits
	IORM A,MSGBTS(M)
	CALLRET UPDBIT		;Go update the message bits, maybe

.RUFLG:	CONFRM
UFLMSG:	MOVX A,M%ATTN		;Unflag message
	CALLRET CLRBIT

.RUNAN:	CONFRM
UANMSG:	MOVX A,M%RPLY		;Unanswer message
	CALLRET CLRBIT

.RUMRK:	CONFRM
	CALLRET UMKMSG		;Go mark as unseen

.RUDLM:	CONFRM
UNDMSG:	SKIPA A,[M%DELE]	;Mark as undeleted
UMKMSG:	 MOVX A,M%SEEN		;Mark as unseen
CLRBIT:	ANDCAM A,MSGBTS(M)
	CALLRET UPDBIT		;Go update the message bits, maybe

.RUKYW:	CALL GETKEY		;Remove keywords
	MOVEM U,KEYBTM		;Save keyflag mask bits
	MOVEM V,KEYLPM		;and keyword list
	CONFRM
UNKMSG:	MOVE A,KEYBTM
	CALL CLRBIT		;Clear keyflags
	SKIPE A,KEYLPM
	 CALL KWDEL		;Delete keywords
	RET

.RKEYW:	CALL GETKEY		;Add keywords
	MOVEM U,KEYBTM		;Save keyflag mask bits
	MOVEM V,KEYLPM		; and keyword list
	CONFRM
KEYMSG:	MOVE A,KEYBTM		;Set keyflags
	IORM A,MSGBTS(M)
	CALL UPDBIT		;Go update the message bits
	SKIPE A,KEYLPM
	 CALL KWADD		;Add keywords
	RET
;;; Get an output file, defaulting to the SAVED-MESSAGES-FILE file if known,
;;; giving it the NEW-FILE-PROTECTION protection.  GETOFI doesn't have the
;;; SAVED-MESSAGES-FILE default, although it still defaults the protection.
GETOUT:	SETZM CMDGTB+.GJGEN	;Default to highest generation
	MOVEI B,[FLDDF. .CMFIL,CM%SDH,,output filespec,SAVFIL]
	TXNE F,F%BB
	 MOVEI B,[FLDDF. .CMFIL,CM%SDH,,output filespec,MBXFIL]
	JRST GETOU2		;Join common code

GETOFI:	MOVX A,.GJNHG		;Use next higher generation
	MOVEM A,CMDGTB+.GJGEN
GETOU0:	MOVEI B,[FLDDB. .CMFIL,CM%SDH,,output filespec]
GETOU2:	PUSH P,B		;Save block we selected
	SKIPLE A,OUTJFN		;Flush old output JFN
	 CLOSF%
	  ERJMP .+1
	NOISE (INTO FILE)	;Get an output file
	SETZM CMDGTB+.GJSRC	;Get space for GTJFN%
	MOVE A,[CMDGTB+.GJSRC,,CMDGTB+.GJSRC+1]
	BLT A,CMDGTB+.GJATR
	SKIPN B,DEFPRO		;Have default protection?
	IFSKP.
	  HRROI A,DEFPST	;Where to put string
	  MOVEM A,CMDGTB+.GJPRO	;Set up pointer to default
	  MOVE C,[6,,^D8]	;Columns,,radix
	  NOUT%
	   JERROR <New file protection error>
	ENDIF.
	POP P,B			;Get back block user specified
	CALL CMDFLD		;Get the file
	MOVEM B,OUTJFN		;Save it
	RET

GETLPT:	SKIPLE A,OUTJFN
	IFSKP.
	  SKIPE LPTCFM
	  IFSKP.
	    PROMPT <Do you really want to output to the lineprinter? >
	    CALL YESNO1
	    IFNSK.
	      TMSG <
Use the TYPE command to type a message on your terminal.  Use
the FILE-LIST command to list a message to a file, or the COPY
command if you want to write the file in mail file format.
>
	      RET
	    ENDIF.
	  ENDIF.
	  MOVX A,GJ%FOU!GJ%SHT
	  HRROI B,LSTDEV
	  GTJFN%
	   JERROR <Can't get listing device>
	ENDIF.
	MOVEM A,OUTJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%WR>
	OPENF%
	IFJER.
	  MOVE A,OUTJFN
	  SETZM OUTJFN
	  JERROR <Can't open listing device "%1J">
	ENDIF.
	RETSKP
LPTMSG:	MOVE A,OUTJFN		;Print msg number separator
	HRROI B,[ASCIZ/
Message /]
	SETZ C,
	SOUT%
	MOVEI B,0(M)
	IDIVI B,MSGLEN
	ADDI B,1
	MOVEI C,^D10
	NOUT%
	 NOP			;???
	HRROI B,[ASCIZ/ -- ************************
/]
	SETZ C,
	SOUT%
	CALL PUTMS1		;Output the message
	SKIPN LSTPAG		;Always separate pages?
	 SKIPE SEPPGS		;No, want it this time?
	  CAIA			;Yes
	   RET			;No, done
	HRROI B,[BYTE (7) .CHCRT,.CHLFD,.CHFFD,.CHCRT,.CHLFD]
IFE NICSW,<
	MOVNI C,5
>;IFE NICSW
IFN NICSW,<
	MOVNI C,3		;ONLY SOME OF IT
>;IFN NICSW
	SOUT%
	RET

PUTMSG:	CALL CHKDEL		;Not deleted msgs
	 RET
PUTMS1:	MOVE V,MSGALL(M)	;Get start of the message
	CALL CHR2BP
	MOVE B,A
	MOVN C,MSGSAL(M)	;Length
IFN NICSW,<
	push p,b
	push p,c
	call noesc		;change escapes to dollar-signs
	pop p,c
	pop p,b
>;IFN NICSW
	MOVE A,OUTJFN		;Where it goes
	SOUT%			;That's it
	RET

IFN NICSW,<
noesc:	ildb a,b
	caie a,.chesc
	ifskp.
	  movei a,"$"
	  dpb a,b
	endif.
	aojl c,noesc
	ret
>;IFN NICSW

;;; Make up the correct subject for a reply to the current message
REPSUB:	SKIPN A,MSGSUB(M)
	 RET			;No subject
	MOVE B,[POINT 7,STRBUF]
	CALL FORMSS		;Move it to temp space
	SETZ D,
	IDPB D,B		;And a null
	MOVE A,STRBUF		;Get start of it
	ANDCM A,[<BYTE (7) 40,40,0,0,177>+1];Uppercase and clear last byte
	CAMN A,[ASCIZ/RE: /]	;Already a response?
	IFSKP.
	  MOVE A,[ASCIZ/Re: /]
	  MOVEM A,SUBBUF	;Start subject off right
	  MOVE A,[POINT 7,SUBBUF,27] ;Start going into last byte
	ELSE.
	  MOVE A,[POINT 7,SUBBUF] ;Start at start of subject
	ENDIF.
	MOVEI B,STRBUF		;From here
	CALLRET MOVST0		;Move it and the null
MOVMSG:	CALL PUTMSG		;Move the message
	CALLRET DELMSG		;And delete it afterwards

;;; Forward the current message
FORMSG:	SKIPE A,MSGFRM(M)	;Has an author?
	 SKIPE SUBBUF		;Yes, need subject?
	IFSKP.
	  MOVE B,[POINT 7,SUBBUF]
	  MOVEI C,"["
	  IDPB C,B
	  CALL FORMSS
	  MOVEI C,":"
	  IDPB C,B
	  SKIPN A,MSGSUB(M)
	  IFSKP.
	    MOVX C,.CHSPC
	    IDPB C,B
	    CALL FORMSS
	  ENDIF.
	  MOVEI C,"]"
	  IDPB C,B
	  SETZ C,
	  IDPB C,B
	ENDIF.
FORMS2:	MOVE A,MSGBOD(M)	;Body of the message
	MOVE B,TXTPTR
	CALL FORMSN
	MOVEM B,TXTPTR
	RET

;;;Output the portion of the message pointed to by A into byte pointer in B,
;;;suppressing leading white space.
FORMSS:	HLRZ C,A
	JUMPE C,R		;None to do
	MOVEI V,(A)		;Get byte offset of field
	CALL MCH2BP		;Get byte pointer to it
FRMSS1:	ILDB D,A		;Get char
	JUMPE D,FRMSS2		;Never put in a null
	CAIE D,.CHTAB		;Ignore whitespace
	 CAIN D,.CHSPC
FRMSS2:	  SOJG C,FRMSS1	  
	JUMPE C,R		;Nothing to do
	JRST FRMSN2		;Join code in FORMSN

;;;Similar, but without whitespace suppression
FORMSN:	HRRZ C,B		;Get address of text
	CAIL C,TXTPAG+<1000*NTXPGS>-100 ;See if cutting it too close
	 ERROR <Too much text to forward> ;Loser
	HLRZ C,A
	JUMPE C,R		;None to do
	MOVEI V,(A)		;Get byte offset of field
	CALL MCH2BP		;Get byte pointer to it
FRMSN1:	ILDB D,A
FRMSN2:	SKIPE D			;Never put in a null
	 IDPB D,B
	SOJG C,FRMSN1
	RET
;;;Remail a single message
RMLMSG:	CALL .ERSTX		;Erase vestiges of previous REMAIL
	HRRZ V,MSGBOD(M)	;Get pointer to message body
	CALL MCH2BP
	HLRZ C,MSGBOD(M)	;Length of it
	MOVE B,[POINT 7,HDRPAG]	;Start of some headers
	MOVEI E,.CHLFD		;Start at new line
RMLMS1:	SOJL C,[ERROR <Badly formatted message>]
	ILDB D,A		;Get character
	IDPB D,B		;Stick it in
	EXCH D,E
	CAIN E,.CHCRT		;This char a CR?
	 CAIE D,.CHLFD		;And previous LF
	  JRST RMLMS1		;No, continue
	ADD B,[7B5]
	MOVEM B,RMLPTR		;This is the pointer to end of headers
	SOJL C,RMLMS2		;If there is more text
	IBP A			;Move over the LF
	MOVE B,TXTPTR		;Move the rest of it into text
	CALL FRMSN1
	MOVEM B,TXTPTR		;Update text pointer
	IDPB C,B		;Make sure it ends with a null
RMLMS2:	CALLRET SNDMSG		;Go send the message off
;;;Replace current message

RPLMSG:	SAVEAC <A,C,M>
	STKVAR <RPLPTR,RPLCNT,RPLPGO,RPLCPG,RPLDPG,RPLPGC,CURMSG>
	MOVEM A,RPLPTR		;Save byte pointer
	MOVEM C,RPLCNT		;And byte count
	MOVEM M,CURMSG		;And current message
	CALL GETJF2		;Get a write JFN
	 RET			;Failed
	CALL ABNOFF		;No aborts
	NOINT			;No outside diddling
	MOVEM A,OUTJFN		;Save it here as well
	MOVE B,MSGALL(M)	;Get start of whole message
	IDIVI B,5000		;Round down to start of page
	MOVEM C,RPLPGO		;Save remainder
	IMULI B,5000		;Set to start of page
	SFPTR%
	IFJER.
	  CALL CLSJF2		;Clean up file (or what's left)
	  OKINT			;CTRL/C OK now
	  JSNARL		;Output error
	  RET
	ENDIF.
	MOVE V,MSGALL(M)
	CALL CHR2BP		;Get byte pointer to message
	MOVE E,A		;Save it
	ANDI A,777000		;Get page number of start
	MOVEM A,RPLCPG		;Save start of core page
	MOVEI B,-MTXPAG(A)	;Get page offset
	LSH B,-9
	MOVEM B,RPLDPG		;Save starting disk page
	HRRZ C,FILPGS		;Get number of pages in the file
	SUBI C,(B)		;Less where we started
	MOVEM C,RPLPGC		;Save count
	DO.
	  MOVES (A)		;Make all pages after that private
	  ADDI A,1000
	  SOJG C,TOP.
	ENDDO.
;;;Remove the old pages from the file
	SETO A,			;Remove these pages from file
	MOVE B,RPLDPG		;Starting page
	HRL B,MSGJF2		;JFN
	MOVE C,RPLPGC		;Count
	TXO C,PM%CNT
	PMAP%			;Kill the old copies from file
	 ERJMP .+1
;;;Copy from start of first page up to message we are concerned with
	HRRZ A,MSGJF2		;Get write JFN again
	HRRO B,RPLCPG		;Start of first page
	MOVN C,RPLPGO		;Negate: use exact count
	SKIPE C			;Forget if count=0
	 SOUT%			;Copy to file
;;;Now put out revised message
	DO.
	  ILDB B,E		;Get character
	  BOUT%
	  CAIE B,","		;Until start of byte count
	   LOOP.
	ENDDO.
	MOVE B,RPLCNT		;New byte count
	MOVEI C,^D10
	NOUT%
	IFJER.
	  CALL CLSJF2		;Clean up file (or what's left)
	  OKINT			;CTRL/C OK now
	  JSNARL
	  RET
	ENDIF.
	DO.
	  ILDB B,E
	  CAIE B,";"		;Now look for start of message bits
	   LOOP.
	ENDDO.
	DO.
	  BOUT%
	  ILDB B,E
	  CAIE B,.CHLFD		;Until end of line
	   LOOP.
	ENDDO.
	BOUT%			;And that as well
	MOVE B,RPLPTR		;Get byte pointer
	MOVN C,RPLCNT		;And byte count
	SKIPE C
	 SOUT%			;Put that in now
	ADDI M,MSGLEN
	CAMLE M,LASTM		;Reached end of file?
	IFSKP.
	  MOVE V,MSGALL(M)	;No, beginning byte of remainder of file
	  MOVE E,LASTM
	  MOVN C,MSGALL(E)	;Compute last byte of file
	  SUB C,MSGSAL(E)
	  ADD C,V		;The "difference" is what to copy
	  CALL CHR2BP		;Compute the byte pointer
	  MOVE B,A
	  MOVE A,MSGJF2
	  SOUT%			;Send rest of file out
	ENDIF.
	MOVE A,OUTJFN
	RFPTR%
	IFJER.
	  CALL CLSJF2		;Clean up file (or what's left)
	  OKINT			;CTRL/C OK now
	  JSNARL
	  RET
	ENDIF.
	HRLI A,.FBSIZ
	MOVE C,B		;Current position
	SETO B,
	CHFDB%			;Make this the new end of the file
	CALL CLSJF2		;Close off file
	OKINT			;CTRL/C OK now
	CALL SIZFIL		;Get its new size info
	MOVE M,CURMSG		;Get back current message
	CALL PARSEF		;Reparse the file
	CALL RECEN2
	RETSKP			;Return +2

	ENDSV.
;;;Get TTY modes

GETTYM:	MOVX A,RT%DIM!.FHJOB	;Get job's interrupt word
	RTIW%
	DMOVEM B,3(D)
	MOVX A,.PRIOU
	RFMOD%
	MOVEM B,0(D)
	RFCOC%
	DMOVEM B,1(D)
	RET

;;;Set TTY modes

SETTYM:	MOVX A,ST%DIM!.FHJOB
	DMOVE B,3(D)
	STIW%
	 ERJMP .+1
	MOVX A,.PRIOU
	RFMOD%			;Get current mode
	ANDX B,TT%OSP		; so we preserve TT%OSP state
	IOR B,0(D)
	SFMOD%
	DMOVE B,1(D)
	SFCOC%
	RET
.CHECK:	NOISE (FOR NEW MESSAGES)
	CONFRM
	SKIPLE MSGJFN		;Have mail file?
	IFSKP.
	  CALL CHKNEW		;No, see if one now
	   RET			;Nope, return
	ELSE.
	  CALL SIZFIL		;Get current file poop
	ENDIF.
	CALLRET CHECKS		;Force check now

;;;Check for new messages periodically

CHECK:	GTAD%			;Get time now
	CAMG A,CHKTIM		;Time we had a look?
	 RET			;No, just return
CHECKT:	CALL CHECK1		;Check for change in file size
	IFNSK.
	  SKIPG MSGJFN		;No change, found a message file?
	   RET			;No, return
	  SKIPE A,FILWRT	;See when/if last written
	   CAMG A,FILRD		;Written since last read?
	    RET			;No, nothing changed
	ENDIF.

;;;Print message when there are new guys

CHECKS:	STKVAR <CURMSG,CURLST>
	MOVE A,MSGJFN
	CALL SETREF		;Set read date
	MOVEM M,CURMSG		;Save current message
	MOVE M,LASTM		;Start at the end
	MOVEM M,CURLST		;Save number of messages
	ADDI M,MSGLEN		;From that one on,
	CALL PARSEF		;Parse these new ones
	SKIPL CURLST		;Started from scratch?
	IFSKP.
	  SETZ A,		;Yes, find first really new msg
	  DO.
	    CAMLE A,LASTM	;More msgs?
	    IFSKP.
	      MOVE B,MSGDAT(A)	;Yes, date before file read date?
	      CAML B,LASTRD
	    ANSKP.
	      ADDI A,MSGLEN	;Yes, step to next msg
	      LOOP.
	    ENDIF.
	  ENDDO.

;;; Here A points to first msg to be considered new

	  SUBI A,MSGLEN		;OK, step back to last "old" msg
	  MOVEM A,CURLST	;Update previous LASTM
	  SKIPLE A		;Really starting at 0?
	   MOVEM A,CURMSG	;No, update prev "current" msg
	  CIETYP <
>				;Separator line
	  CALL .STATF		;Be sure user knows about file name
	ENDIF.
	MOVE A,CURLST		;Get old last message in A
	MOVE M,CURMSG		;Get current message in M
	CALLRET CHECKN

	ENDSV.
; Here with A/ old last message, M/ current message

CHECKN:	STKVAR <CURMSG,OLDLST,OLDLRD>
	MOVEM A,OLDLST		;Save old last message
	MOVEM M,CURMSG		;Save current message
	SUB A,LASTM		;Get number of new guys
	JUMPE A,R		;Done if no new ones
	MOVE B,LASTRD		;Save date file fetched
	MOVEM B,OLDLRD
	SKIPGE B,OLDLST		;Get old message if any
	IFSKP.
	  TXNE F,F%BB		;Reading a BBoard file?
	   SKIPA B,BBXDAT	;Yes, fake last read date
	    MOVE B,MSGDAT(B)	;Otherwise use date of last
	  MOVEM B,LASTRD	; previous real msg
	ENDIF.
	IDIVI A,MSGLEN
	MOVMS A
	MOVEI B,[ASCIZ/are/]
	CAIN A,1
	 MOVEI B,[ASCIZ/is/]
	CIETYP < There %2S %1D additional message%1P
>
	CALL RECENT		;Give the headers of the recent ones
	MOVE B,OLDLRD		;Restore date file fetched
	MOVEM B,LASTRD
	SKIPL M,CURMSG		;Restore current message
	 CAMLE M,LASTM		;Range check
	  SETZ M,		;Else go to the beginning
	CIETYP < Currently at message %M.
>
	RET

	ENDSV.
;;; Check for change in file size.  Used when read/write dates already updated
CHECK1:	GTAD%			;Get current date/time
	ADDI A,<5B17/^D<24*60>>	;Five minutes from now
	MOVEM A,CHKTIM		;Is next time to look
	SKIPG MSGJFN		;Have a file?
	 JRST CHKNEW
	PUSH P,FILSIZ		;Save current size
	CALL SIZFIL		;Get the current poop on it
	POP P,T			;Get back old size
	CAME T,FILSIZ		;Size changed?
	 RETSKP			;Yes, skip return
	RET			;No

;;;Check if MAIL.TXT has been undeleted
CHKNEW:	CALL FNDFL0		;Has it?
	 RET			;Nope, return
	SKIPE FILSIZ		;If file is empty, ignore it
	IFSKP.
	  RLJFN%		;Get rid of the file
	   NOP			;Ignore failure
	  RET
	ENDIF.
	TXNN F,F%RONL		;Only do if want to write
	 CALL LCKFIL
	MOVNI A,MSGLEN		;Flag for full parse
	MOVEM A,LASTM
	MOVE A,FILRD		;Save date when file read
	MOVEM A,LASTRD
	SETZ M,
	RETSKP
;;;Build string of version number in STRBUF

GETVER:	STKVAR <BASE>
	TMNN VI%DEC,VERNUM	;Decimal versions?
	 SKIPA A,[^D8]		;No, octal for typeout
	  MOVX A,^D10		;Yes, output in decimal
	MOVEM A,BASE
	MOVE A,[POINT 7,STRBUF]
	MOVEI B,[ASCIZ/ MM-20 /]
	CALL MOVSTR
	LOAD B,VI%MAJ,VERNUM
	IFN. B
	  MOVE C,BASE
	  NOUT%
	   NOP
	ENDIF.
	LOAD B,VI%MIN,VERNUM
	IFN. B
	  MOVEI C,"."		;New DEC minor version convention
	  IDPB C,A		; is . followed by number
	  MOVE C,BASE
	  NOUT%
	   NOP
	ENDIF.
	LOAD B,<VI%EDN&^-VI%DEC>,VERNUM
	IFN. B
	  MOVEI C,"("
	  IDPB C,A
	  MOVE C,BASE
	  NOUT%
	   NOP
	  MOVEI C,")"
	  IDPB C,A
	ENDIF.
	LOAD B,VI%WHO,VERNUM
	IFN. B
	  MOVEI C,"-"
	  IDPB C,A
	  MOVE C,BASE
	  NOUT%
	   NOP
	ENDIF.
	SETZ C,			;Put null in at end
	IDPB C,A
	RET

	ENDSV.
; Routine to initialize structure for sorting msgs by date:
;   SRTPAG = adr of "shuffle" table.  Each entry has the form,
;		source msg,,destination msg
;	      where at entry I,
;		"source" = index of msg block moving to I
;		"destination" = index of msg block I moves to
;   SRTTAB = sort tree for msgs.  Each node has the structure,
;		lh ptr,,rh ptr
;		index of msg at this node
;	      where,
;		lh ptr points to nodes with earlier dates
;		rh ptr points to nodes with later dates
;   SRTFRE = adr of next free cell
; Call:    CALL INISRT
; Return:  +1
INISRT:	MOVE B,LASTM		;Leave room for all msgs in
	IDIVI B,MSGLEN		; shuffle table
	ADDI B,1
	MOVEI A,SRTPAG(B)	;a := start of sorting tree
	HRROM A,SRTTAB		;Flag it as 1st node of tree
	MOVEM A,SRTFRE		;Also as free space ptr
	MOVNS B			;b := aobjn ptr to shuffle table
	MOVSI B,(B)
	SETZ A,			;Init shuffle tbl
	DO.
	  MOVEM A,SRTPAG(B)
	  ADD A,[MSGLEN,,MSGLEN] ;Bump to,,from ptrs
	  AOBJN B,TOP.		;Do all msgs
	ENDDO.
	RET			;Done
; Routine to add a msg into the sorting tree.  Since msgs are expected
; to be fairly well ordered, we keep separate ptrs to the leftmost and
; rightmost branches of the tree for easy appending.
; Entry:   m = adr of msg block
; Call:    CALL SRTMSG
; Return:  +1, new node linked in to sort tree
SRTMSG:	SAVEAC <E,N>
	STKVAR <NEWNOD,PRVNOD>
	SETZM NEWNOD		;No nodes initially
	SETZM PRVNOD
	SKIPLE E,SRTTAB		;Empty tree?
	IFSKP.
	  HRRZS SRTTAB		;Yes, clear lh flag and bypass search
	ELSE.
	  MOVE N,MSGDAT(M)	;n := date of current msg
	  MOVE B,SRTLFT		;New date lowest in group?
	  MOVE A,1(B)
	  CAMLE N,MSGDAT(A)
	  IFSKP.
	    MOVE E,B		;Yes, start search here
	    HLLOS NEWNOD	;Can't be new rightmost node
	  ELSE.
	    MOVE B,SRTRGT	;New date highest in group?
	    MOVE A,1(B)
	    CAMGE N,MSGDAT(A)
	    IFSKP.
	      MOVE E,B		;Yes, start search here
	      HRROS NEWNOD	;Can't be new leftmost node
	    ENDIF.
	  ENDIF.

;; Here to scan down the tree to find the proper place to append the
;; new msg

	  DO.
	    IFN. E		;Quit if last link
	      MOVEM E,PRVNOD	;More, save this one as prior node
	      MOVE A,1(E)	;a := adr of msg block for this node
	      CAML N,MSGDAT(A)
	      IFSKP.
		HLRZ E,0(E)	;New date < node, put it to left
		HRROS PRVNOD	;Flag lefthand ptr from prior node
		HLLOS NEWNOD	;Can't be new rightmost node
		LOOP.		;See if more on tree
	      ENDIF.
	      CAMG N,MSGDAT(A)
	      IFSKP.
		HRRZ E,0(E)	;New date > node, put it to right
		HRROS NEWNOD	;Can't be new leftmost node
		LOOP.		;See if more on tree
	      ENDIF.
	      SKIPL MSCANF	;Inverse scan?
	      IFSKP.
		HLRZ E,0(E)	;Yes, put it to left
		HRROS PRVNOD	;Flag lefthand ptr from prior node
		HLLOS NEWNOD	;Can't be new rightmost node
		LOOP.		;See if more on tree
	      ELSE.
		HRRZ E,0(E)	;No, put it to right
		HRROS NEWNOD	;Can't be new leftmost node
		LOOP.		;See if more on tree
	      ENDIF.
	    ENDIF.
	  ENDDO.
	ENDIF.

;; Here we are at the end of the current tree.  Enter the new node.
	MOVE A,SRTFRE		;a := adr of next free entry
	SETZM 0(A)		;Init the new entry
	MOVEM M,1(A)		;Save index to current msg in node
	MOVEI B,2(A)		;Update the free ptr
	MOVEM B,SRTFRE
	MOVE E,NEWNOD		;x := new left/rightmost node flag
	TXNN E,.LHALF		;New leftmost node?
	 MOVEM A,SRTLFT		;Yes
	TXNN E,.RHALF		;New rightmost node?
	 MOVEM A,SRTRGT		;Yes
	MOVE E,PRVNOD		;x := adr of previous node
	IFN. E			;If 1st one, quit
	  TXNE E,.LHALF		;LH link?
	   HRLZS A		;Yes, put link adr in lh
	  IORM A,0(E)		;Install it in the proper half
	ENDIF.
	MOVEI A,(M)		;Flag shuffle table that msg sorted
	IDIVI A,MSGLEN
	SETOM SRTPAG(A)
	RET

	ENDSV.
; Routine to linearize a sorted tree of msgs and to shuffle the msg
; blocks appropriately.
; Call:    CALL PSTSRT
; Return:  +1
PSTSRT:	SETZM NSORTD		;Clear count of non-trivial sorts
	SKIPG SRTTAB		;Anything in tree?
	 RET			;No, just return
	SAVEAC <T,E>
	MOVEI T,SRTPAG-1	;Assume forward scan
	SKIPG MSCANF		;Unless reversed
	 HRRZ T,SRTTAB		;Then start at top of table
	CALL SRTREE		;Sort the tree
	SKIPG NSORTD		;Any real movement?
	IFSKP.
	  MOVEI T,SRTPAG	;Yes, really shuffle the msg blocks now
	  DO.
	    CAML T,SRTTAB	;Done whole table?
	    IFSKP.
	      SKIPGE (T)	;No, marked as already done?
	       AOJA T,TOP.	;Yes, look at next one
	      HLRZ A,(T)	;a := msg # coming here
	      IDIVI A,MSGLEN
	      CAIE A,-SRTPAG(T)	;Move to self?
	       CALL SMVMSG	;No, migrate this chain
	      AOJA T,TOP.	;Try the next one
	    ENDIF.
	  ENDDO.
	ENDIF.
	SETO A,			;Unmap pages used for sort
	MOVE B,[.FHSLF,,<SRTPAG/1000>]
	MOVE C,SRTFRE	
	SUBI C,1		;Last word actually used
	LSH C,-^D9		;Last page touched
	SUBI C,-1(B)		;Number of pages to unmap
	TXO C,PM%CNT
	PMAP%
	RET
; Routine to traverse a sorted tree and linearly order the nodes in
; sequential open entries in the shuffle table
; Entry:   t = ptr to shuffle table
; Call:    CALL SRTREE
; Return:  +1
SRTREE:	HRRZ E,SRTTAB		;Set up X to head of tree
	JUMPE E,R		;If end of tree, quit
	DO.
	  PUSH P,E		;No, save adr of this node
	  HLRZ E,0(E)		;Point to lh branch
	  SKIPE E
	   CALL TOP.		;Check it out
	  POP P,E		;None on left, use this one
	  CALL NXTSHF		;Find next shuffle table entry
	  HRRZ A,1(E)		;a := index of sorted msg
	  HRLM A,(T)		;Put it in the table
	  IDIVI A,MSGLEN	;a := number of sorted msg
	  MOVEI B,-SRTPAG(T)	;b := index of where it goes
	  IMULI B,MSGLEN
	  HRRM B,SRTPAG(A)
	  CAIE A,-SRTPAG(T)	;Move in place?
	   AOS NSORTD		;No, bump count
	  HRRZ E,0(E)		;x := link to right (later dates)
	  JUMPN E,TOP.		;Check out that branch
	ENDDO.
	RET
;; Routine to find next shuffle table entry
; Entry:   t = previous table ptr
; Call:    CALL NXTSHF
; Return:  +1
NXTSHF:	SKIPLE MSCANF		;Forward scan
	IFSKP.
	  DO.
	    SOJL T,NXTSHX	;Step to earlier entry (bomb on error)
	    SKIPL (T)		;Sorted entry
	     LOOP.		;No, look further
	  ENDDO.
	ELSE.
	  DO.
	    ADDI T,1		;Yes, step to next entry
	    CAML T,SRTTAB	;Beyond table?
NXTSHX:	     FATAL <Error finding shuffle table entry>
	    SKIPL (T)		;Sorted entry?
	     LOOP.		;No, look further
	  ENDDO.
	ENDIF.
	RET			;OK, return this one
;; Routine to move msg blocks around according to the shuffle table
;; entries.  Shuffle table entry I contains SRC,,DST where SRC is the
;; msg block to be moved to I and DST is the msg block to which I is to
;; move.
; Entry:   t = current entry requiring movement
; Call:    CALL SMVMSG
; Return:  +1, msg blocks chained to t moved.
SMVMSG:	SAVEAC <T>		;Save current shuffle table index
	DMOVE A,[SRBLK0
		 SRBLK1]	;Init temp storage ptrs
	DMOVEM A,SRTBLK
	SETZM SRTIDX
	HLRZ A,(T)		;Save source block coming here
	MOVSI A,MSGPGS(A)
	HRRI A,SRBLK0
	BLT A,SRBLK0+MSGLEN-1
	DO.
	  MOVEI A,-SRTPAG(T)	;a := adr of current msg block
	  IMULI A,MSGLEN
	  ADDI A,MSGPGS
	  PUSH P,SRTIDX		;Save current temp buffer index
	  HRR B,(T)		;b := dst for current msg block
	  IDIVI B,MSGLEN
	  SKIPGE SRTPAG(B)	;Already transferred?
	  IFSKP.
	    AOS B,SRTIDX	;No, b := index to free temp buffer
	    TRNN B,1
	     SETZB B,SRTIDX
	    MOVE B,SRTBLK(B)	;Save current contents of msg block
	    HRLI B,(A)
	    MOVEI C,MSGLEN-1(B)
	    BLT B,(C)
	  ENDIF.
	  POP P,B		;Recover index to temp bfr to move here
	  HRL A,SRTBLK(B)	;Install new sorted msg block
	  MOVEI B,MSGLEN-1(A)
	  BLT A,(B)
	  HRROS (T)		;Mark this entry as updated
	  HRRZ A,(T)		;a := dst msg #
	  IDIVI A,MSGLEN
	  MOVEI T,SRTPAG(A)	;t := ptr to dst shuffle table entry
	  SKIPL (T)		;Dst already updated?
	   LOOP.		;No, more on this chain
	ENDDO.
	RET
;;; Copy sorted msgs to the file
CPYSRT:	SAVEAC <M>
	JXN F,F%RONL,R		;Can't change read only file
	CALL GETJF2		;Get a write JFN
	 RET			;Failed
	CALL ABNOFF		;No aborts
	NOINT			;No outside diddling
	MOVEI A,MTXPAG		;Core adr of first file page
	HRRZ B,FILPGS		;b := # of pages in the file
	DO.
	  MOVES (A)		;Make all pages private
	  ADDI A,1000
	  SOJG B,TOP.
	ENDDO.
	SETO A,			;Remove all pages from file
	HRLZ B,MSGJF2		;JFN,,first page
	HRRZ C,FILPGS		;Count
	TXO C,PM%CNT
	PMAP%			;Kill the old copies from file
	 ERJMP .+1
	SETZ M,			;Do all msgs
	DO.
	  MOVE V,MSGALL(M)	;Get byte ptr to start of msg
	  CALL CHR2BP
	  MOVE B,A		;Copy this msg out
	  HRRZ A,MSGJF2
	  MOVN C,MSGSAL(M)	;Negative number of bytes in msg
	  SOUT%
	  ADDI M,MSGLEN		;Step to next msg
	  CAMG M,LASTM		;All done?
	   LOOP.		;No
	ENDDO.
	CALL CLSJF2		;Close off file
	OKINT			;CTRL/C OK now
	CALL SIZFIL		;Get its new size info
	CALLRET PARSEA		;Reparse the entire file
	SUBTTL Lower level subroutines

;;;Copy a file name string from B to A, prefixing login directory.

MAKSTR:	PUSH P,B
	PUSH P,A
	SETZ A,			;Convert alias user to alias directory
	MOVE B,MYAUSR
	RCDIR%
	POP P,A
	MOVE B,C
	DIRST%
	 JFATAL
	POP P,B
	CALLRET MOVST0

;;;Copy a file name string from B to A, prefixing postbox directory.

MKPSTR:	PUSH P,B
	MOVEI B,MLBXDV
	CALL MOVSTR
	MOVX C,":"
	IDPB C,A
	MOVX C,.CHLAB
	IDPB C,A
	MOVEI B,MAUSRS
	CALL MOVSTR
	MOVX C,.CHRAB
	IDPB C,A
	POP P,B
;	CALLRET MOVST0

;;;Move string and terminating null

MOVST0:	HRLI B,(<POINT 7,>)
MOVST2:	DO.
	  ILDB C,B
	  IDPB C,A
	  JUMPN C,TOP.
	ENDDO.
	RET

;;;Move a string from B to A

MOVSTR:	HRLI B,(<POINT 7,>)
MOVST1::DO.
	  ILDB C,B
	  IFN. C
	    IDPB C,A
	    LOOP.
	  ENDIF.
	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
;;;Unmap pages from file

UNMAPF:	SETO A,
	MOVE B,[.FHSLF,,MTXPGN]
	HRRZ C,FILPGS		;Number of pages
	HRLI C,(PM%CNT)
	PMAP%
	RET

;;;Close the INDEX file

CLOSEI:	SKIPLE A,IDXJFN		;Is there one?
	 CALL $CLOSF		;Yes, throw it away
	SETZM IDXJFN		;Isn't one any more
	RET

;;;Close the file

CLOSEF:	SKIPG MSGJFN
	IFSKP.
	  DMOVE A,[.DEQID	;Get rid of any locks we got
		   REQID]
	  DEQ%
	   ERJMP .+1		;Ignore failure
	  SKIPLE A,MSGJFN
	   CALL $CLOSF
	  SETOM MSGJFN
	ENDIF.
CLOSF1:	SKIPLE A,MSGJF2
	 CALL $CLOSF
	SETOM MSGJF2
	TXZ F,F%SWRN		;Disable size warning now
	RET

$CLOSF:	GTSTS%			;Get file status
	TXNN B,GS%NAM		;Valid JFN?
	 RET
	IFXN. B,GS%OPN		;Yes, do CLOSF% if file open
	  CLOSF%
	   NOP
	ELSE.
	  RLJFN%
	   NOP
	ENDIF.
	RET

$CLOSK:	GTSTS%			;Get file status
	TXNE B,GS%NAM		;Valid JFN?
	 TXNN B,GS%OPN		;Yes, file open?
	  RET
	TXO A,CO%NRJ		;Yes, close file while keeping JFN
	CLOSF%
	 NOP
	RET
	SUBTTL File parsing subroutines

GETFLB:	CALL CLOSEI		;Flush any old index
	SKIPE FILSIZ		;Is the file empty?
	IFSKP.
	  MOVE A,MSGJFN		;Yes, get JFN for error message
	  SKIPN VBSBBD		;Want noisy behavior?
	   TXNN F,F%RSCN	;No, bother anyway unless rescanning
	    CIETYP <There are no messages in %1J>
	  CALL CLOSEF		;Clear out JFNs
	  JRST CMDRES
	ENDIF.
	HRRZ A,MSGJFN		;Get file JFN
	MOVE B,[1,,.FBWRT]	;Date of last user-write
	MOVEI C,BBLWD		;Save in BB-Last-Write-Date
	GTFDB%
	MOVE B,A		;Get JFN into B
	HRROI A,IDXNAM		;Create a file-name.idx
	MOVE C,[1B2!1B5!1B8!JS%PAF] ;Dump it
	JFNS%
	HRROI B,[ASCIZ/.IDX.1;P777070/] ;Find the index file
	SETZ C,
	SOUT%			;Copy the .idx
	IDPB C,A		;Tie off name with null
	DO.
	  SKIPE A,IDXJFN	;Have JFN yet?
	  IFSKP.
	    MOVX A,GJ%OLD!GJ%SHT ;See if the file is there
	    HRROI B,IDXNAM
	    GTJFN%
	    IFJER.
	      CAIE A,GJFX18	;No such file name,
	       CAIN A,GJFX19	;Or no such file type?
	        MOVEI A,GJFX24	;Yes, normalize to File-not-found
	      CAIN A,GJFX24	;File not found?
	      IFSKP.
		SKIPLE MSGJFN
		 CALL UNMAPF
		CALL CLOSEF	;No, real problem
		JERROR
	      ENDIF.
	      TXO F,F%F4	;Flag we require a new file here
	      CALL MAKIDX	;Call the indexer
	       JRST GETFBX	;Lost, just do examine
	      EXIT.		;And continue on through
	    ENDIF.
	    MOVEM A,IDXJFN	;Save copy of JFN
	  ENDIF.
	  HRRZ A,IDXJFN
	  MOVE B,[1,,.FBCRV]	;Get date of file creation
	  MOVEI C,D		; gets set to the
	  GTFDB%		; time/date BBoard was written
	  CAML D,BBLWD		;Is index current?
	   EXIT.
	  CALL MAKIDX
	   JRST GETFBX		;Lost, just do examine
	ENDDO.
	HRRZ A,IDXJFN		;JFN
	MOVE B,[1,,.FBSIZ]	;Get number of bytes
	MOVEI C,E		;Into E
	GTFDB%
	MOVX B,OF%RD!OF%THW!OF%WR ;Thawed access (writeable for date update)
	DO.
	  OPENF%
	  IFJER.
	    CAIE A,OPNX9	;Somebody else using file?
	    IFSKP.
	      TMSG <
Waiting for access...>
	      MOVEI A,^D2000
	      DISMS%
	      MOVE A,IDXJFN	;Restore JFN
	      LOOP.		;And retry the OPENF%
	    ENDIF.
	    PUSH P,A		;Save error code
	    CALL CLOSEI		;Throw away half-opened thing
	    SKIPLE MSGJFN
	     CALL UNMAPF
	    CALL CLOSEF		;And message file
	    POP P,A
	    ERROR <Can't open index for BBoard file - %1E>
	  ENDIF.
	ENDDO.
	HRRZ A,A		;Specify page 0 to start in left half
	FFFFP%			;Find first free file page (love those f's)
	HRRZ C,A		;First free is number of pages to map
	MOVE D,C		;Copy of count in D for lower loop
	CAIG C,NMSGPG		;Is index file too big?
	IFSKP.
	  CALL CLOSEI		;Yup, get rid of it
	  SKIPLE MSGJFN
	   CALL UNMAPF
	  CALL CLOSEF		;And message file
	  ERROR <Index file too big> ;Uh, yeah, it is
	ENDIF.
	HLLZ A,A		;JFN in left for PMAP%
	MOVE B,[.FHSLF,,<MSGPGS/1000>] ;Where to map to
	HRLI C,(PM%CNT!PM%RD!PM%PLD!PM%CPY) ;Read, load, copy
	PMAP%
	IFJER.
	  MOVX A,.FHSLF		;Get error code
	  GETER%
	  HRRZS B
	  PUSH P,B
	  CALL CLOSEI
	  SKIPLE MSGJFN
	   CALL UNMAPF
	  CALL CLOSEF
	  POP P,A
	  ERROR <Can't map in index pages - %1E>
	ENDIF.
	MOVEI B,MSGPGS		;Point to first page
	DO.
	  MOVES (B)		;Touch each page
	  ADDI B,1000		;Step pages
	  SOJG D,TOP.		;And iterate
	ENDDO.
	PUSH P,E
	CALL GXDAT		;Find last read date from IDX file
	POP P,E
	MOVEM A,LASTRD		;Store last read date
	SUBI E,MSGLEN-1		;Point to beginning of last message
	MOVEM E,LASTM		;In known place
	SETZ M,			;Parse all messages
	CALLRET PARSEI		;Parse the file using already loaded index

GETFBX:	TXZ F,F%BB		;No longer reading a BBoard
	TXO F,F%MOD!F%RONL	;Treat like system mail
	CALL CLOSEI		;Flush any index JFN
	SETO A,			;This job
	HRROI B,FILRD		;Where to stick info
	MOVEI C,.JILLN		;Get time of last login to use as the date/time
	GETJI%			; the file was last read
	 SETZM FILRD		;None, assume prehistoric times
	CITYPE <[Proceeding by doing an implicit "EXAMINE" using the previous login date
 as the "last read" date]>
	CALLRET GETFL1
;Here to get the last read date out of the index file and set
; the new read date to (A)

SXDAT:	SKIPA E,[MOVEM D,BBXPAG(C)] ;Instruction to SET date
GXDAT:	 MOVE E,[MOVE D,BBXPAG(C)] ;Instruction to GET date
	SKIPN IDXJFN		;Is there an index file?
	 ERROR <Can't set new date with no index file>
	PUSH P,A		;Save new read date
	LDB A,[POINT 8,MYAUSR,26] ;Load user-number/ 1000
	ADDI A,UXPAG		;Offset into file for page number
	HRL A,IDXJFN		;Get the JFN for the index file
	MOVE B,[.FHSLF,,BBXPAG/1000] ;Where to map to
	HRLI C,(PM%WR!PM%RD!PM%PLD) ;Get the page from the file
	PMAP%
	IFJER.
	  JSNARL <Can't map index data page> ;Failed, foo
	  ADJSP P,-1		;Flush read date
	  RET
	ENDIF.
	LDB C,[POINT 9,MYAUSR,35] ;Get index into page
	POP P,D			;Get new read date to set
	XCT E			;Either GET or SET date here
	SETO A,			;Unmap the IDX page now
	SETZ C,			;B should be ok, clear C
	PMAP%			;Throw away idx page now
	IFJER.
	  JSNARL <Can't unmap index data page> ;Shouldn't fail
	  RET
	ENDIF.
	SKIPN A,D		;Return date in A
	 SETO A,		;Never read should be -1
	MOVEM A,BBXDAT		;Save last date known to be in file
	MOVEM A,LASTRD		;Here also for status command
	RET
;Here to make an index of the BB file

MAKIDX:	MOVE A,MSGJFN		;JFN for message
	SETZ M,			;And start at message 0
	SKIPN VBSBBD		;Requested quiet?
	IFSKP.
	  IFXE. F,F%F4		;No, require new file?
	    ETYPE <Creating new index for %1J
>
	  ELSE.
	    ETYPE <Index file out of date, updating index for %1J
>
	  ENDIF.
	ENDIF.
	CALL PARSEF		;Read in the whole file
	SKIPE A,IDXJFN
	IFSKP.
	  HRROI B,IDXNAM	;No JFN yet, get one
	  TXNE F,F%F4		;Need new file?
	   SKIPA A,[GJ%NEW!GJ%SHT] ;Yes, be sure to get a new one
	    MOVX A,GJ%OLD!GJ%SHT ;Old file
	  GTJFN%
	  IFJER.
	    WARN <Can't get BBoard index - %1E
>
	    RET
	  ENDIF.
	  MOVEM A,IDXJFN	;Save JFN
	ENDIF.
	MOVX B,OF%WR!OF%RD!OF%THW ;Write the index file, but leave thawed
	OPENF%
	IFJER.
	  WARN <Can't open BBoard index - %1E
>
	  CALLRET CLOSEI	;Flush index JFN, return non-skip
	ENDIF.
	MOVE C,LASTM		;Get pointer to last message
	ADDI C,MSGLEN+1000-1	;Add length of block, normalize to 1
	LSH C,-^D9		;Shift right for page count
	HRLZ B,A		;Put JFN in b
	MOVE A,[.FHSLF,,<MSGPGS/1000>] ;Page to map out
	HRLI C,(PM%CNT!PM%WR)	;Set bits in count word
	PMAP%
	IFJER.
	  MOVX A,.FHSLF		;Get error code
	  GETER%
	  HRRZS B
	  PUSH P,B
	  CALL CLOSEI
	  SKIPLE MSGJFN
	   CALL UNMAPF
	  CALL CLOSEF
	  POP P,A
	  ERROR <Can't map out index pages - %1E
>
	ENDIF.
	EXCH A,B		;Get file in A, fork in B
	HLLZ A,A		;Start on file page 0
	HRLI C,(PM%CNT!PM%CPY!PM%PLD) ;Magic PMAP% bits 
	PMAP%
	IFJER.
	  MOVX A,.FHSLF		;Get error code
	  GETER%
	  HRRZS B
	  PUSH P,B
	  CALL CLOSEI
	  SKIPLE MSGJFN
	   CALL UNMAPF
	  CALL CLOSEF
	  POP P,A
	  ERROR <Can't map in index pages - %1E
>
	ENDIF.
	HRRZ C,C		;Get page count isolated in C
	MOVEI B,MSGPGS		;Point to first page
	DO.
	  MOVES (B)		;Touch each page
	  ADDI B,1000		;Step pages
	  SOJG C,TOP.		;And iterate
	ENDDO.
	HLRZ A,A		;Get JFN back in right half
	TXO A,CO%NRJ		;Don't release JFN
	CLOSF%
	 JERROR
	HRLI A,.FBCRV(CF%NUD)	;Set user word in fdb (do not update)
	SETO B,			; to date that BBoard file
	MOVE C,BBLWD		; was last written
	CHFDB%
	 ERJMP .+1
	HRLI A,.FBSIZ		;Set number of bytes (words)
	MOVE C,LASTM		;Offset to last message
	ADDI C,MSGLEN-1		;Plus size of last block
	CHFDB%
	 ERJMP .+1
	RETSKP
GETFIL:	TXZ F,F%BB		;No longer reading a BBoard
	CALL FNDFIL		;Try to find it first
	 RET			;Not there, forget it
GETFL1:	SKIPE FILSIZ		;Is the file empty?
	IFSKP.
	  MOVE A,MSGJFN		;Yes, get JFN for error message
	  CIETYP <There are no messages in %1J>
	  CALL CLOSEF		;Clear out JFNs
	  JRST CMDRES
	ENDIF.
	TXNN F,F%RONL		;Only do if want to write
	 CALL LCKFIL
	MOVE A,FILRD		;Save date when file read
	MOVEM A,LASTRD
	SETZ M,			;Parse all messages
	CALLRET PARSEF		;Now return
;;;Try to find a MAIL.TXT

FNDFIL:	TXNN F,F%RSCN		;Can't ask if RSCAN%
	 TXZA F,F%F1		;Ok to type messages if none there
FNDFL0:	  TXO F,F%F1		;Don't type anything
	CALL CLOSEF		;Get rid of old file perhaps
	GJINF%			;Get current connected directory
	MOVEM B,MYCDIR		;Keep this updated
	DO.
	  TXNN F,F%ALIA		;Aliasing another user?
	   SKIPGE GTCNDR	;Or always get postbox directory?
	  IFSKP.
	    CAME B,MYPDIR	;No, are we connected to postbox?
	     CAMN B,MYDIR	; or to login?
	  ANSKP.
	    MOVX A,GJ%OLD!GJ%SHT!GJ%ACC ;No, must investigate further
	    HRROI B,MLBXFN
	    GTJFN%		;Find file on connected directory
	    IFJER.
	      MOVE A,MYPDIR	;Failed, get post office box directory
	      MOVE B,MYCDIR	;and connected
	      MOVEI C,MLBXFN
	      TXNN F,F%F1	;Suppress messages?
	       CIETYP < No %3S in %2U, trying %1U...>
	    ELSE.
	      MOVEM A,MSGJFN	;Save the JFN away
	      JXN F,F%F1,ENDLP.	;If silence requested then we are done
	      MOVE B,MYCDIR	;Ready to compare postbox vs connected
	      SKIPG GTCNDR	;Always get connected directory?
	       CAMN B,MYPDIR	;Are they the same?
		EXIT.		;Yes, done
	      CIETYP <You are connected to directory %2U>
	      MOVE A,[POINT 7,QPRMPT] ;Compose prompt string..
	      HRROI B,[ASCIZ/Read /]
	      CALL MOVSTR
	      HRROI B,MLBXFN
	      CALL MOVSTR
	      HRROI B,[ASCIZ/ here? /]
	      CALL MOVST0
	      UPRMT QPRMPT
	      CALL YESNO
	      IFSKP. <EXIT.>	;User said yes
	      CALL FNDFLX	;Answer was no.  Flush connected directory
	      SETOM GTCNDR	;Make sure get from postbox at check time
	      MOVE C,MYPDIR	;Select postbox directory
	      CIETYP <Trying %3U...>
	    ENDIF.
	  ENDIF.
	  MOVE A,[POINT 7,FILNAM] ;Get postbox directory
	  MOVEI B,MLBXFN
	  CALL MKPSTR
	  MOVX A,GJ%OLD!GJ%SHT!GJ%ACC
	  HRROI B,FILNAM
	  GTJFN%
	  IFJER.
	    MOVEI C,MLBXFN
	    TXNN F,F%F1		;Suppress messages here?
	     CIETYP < You have no %3S>
	    RET
	  ENDIF.
	  MOVEM A,MSGJFN
	ENDDO.
	CALL SIZFIL		;Get the size of the file, etc.
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD> ;Try to open it
	OPENF%
	IFNJE. <RETSKP>
	MOVEI C,MLBXFN
	TXNN F,F%F1
	 CIETYP < Can't open %3S>
FNDFLX:	SKIPLE A,MSGJFN		;Get rid of stray JFN
	 RLJFN%
	  NOP
	SETOM MSGJFN		;Remember there is no mailbox!
	RET
;;; Here to get a YES or NO reply, skip if YES
;Call:	PROMPT <...prompt string...?>
;	CALL YESNO	or	CALL YESNO1
;	 <user answered NO return>
;	<user answered YES return>
; Note!!  YESNO always re-executes the instruction prior to the CALL
;that invoked it, under the assumption that it's a PROMPT UUO.  Any
;place that calls here without that must arrange for the previous
;instruction to serve the function of a prompt (see ABOCFM for the
;only case that needs to worry about this as of this writing).

YSNOCM:	FLDDB. .CMKEY,CM%SDH,YSNOTB,YES or NO
YSNOCD:	FLDDB. .CMKEY,CM%SDH,YSNOTB,YES or NO,YES
YSNOTB:	2,,2
	CMD NO,0
	CMD YES,-1

YESNO1:	MOVEM P,REPARP		;Entry for no default
YESN01:	MOVE P,REPARP
	MOVEI A,YESN01
	MOVEI B,YSNOCM
	JRST YESNO2
YESNO:	MOVEM P,REPARP		;Entry to default to YES
YESNO0:	MOVE P,REPARP
	MOVEI A,YESNO0		;Set reparse address back to here
	MOVEI B,YSNOCD
YESNO2:	HRRM A,CMDBLK+.CMFLG
	CALL $COMND
	TXNE A,CM%NOP		;Make sure valid answer
	 JRST YESNOE
	HRRE D,(B)		;Get answer
	MOVEI B,CNFCMD		;Make sure confirmed
	CALL $COMND
	TXNE A,CM%NOP
	 JRST YESNOE
	JUMPN D,RSKP
	RET

YESNOE:	SNARL <Please answer YES or NO>
	SOS (P)
	SOS (P)
	RET
FSCMDT:	NFSCMD,,NFSCMD		;Short table, 1 choice
	CMD FIRST,FINDF
NFSCMD==.-FSCMDT-1
FLCMDT:	NFLCMD,,NFLCMD		;2 choices
	CMD FIRST,FINDF
	CMD NEXT,BSTP1
NFLCMD==.-FLCMDT-1

.FIND:	SKIPN BBCURR		;BBoards started to be scanned yet?
	IFSKP.
	  DEFALT (NEXT)		; Yes, default is next one
	  MOVEI A,FLCMDT	; Use table with FIRST and NEXT options
	ELSE.
	  DEFALT (FIRST)	;Default is FIRST if just starting
	  MOVEI A,FSCMDT	;And use table with only FIRST option
	ENDIF.
	CALL SUBCMD		;Get user's command
	PUSH P,A		;Save response a moment
	NOISE (BBOARD WITH NEW MAIL)
	CONFRM
	POP P,A			;Restore table entry
	JRST (A)		;And jump to routine

FINDF:	HLRZ T,BBTAB		;Get the number of BBoards to T
	IFE. T
	  ERROR <No BBoards loaded into table>
	ENDIF.
	MOVEM T,BBMAX		;Save number of BBoards total
	SETZM BBCURR		;Set current one to 0
	CALLRET BSTP1		;And fall into stepping code

.IGNOR:	NOISE (THIS BBOARD AND FIND NEXT ONE)
	CONFRM
	TXNN F,F%BB		;Hacking BBoards?
	 ERROR <Can only IGNORE BBoard files>
	GTAD%
	CALL SXDAT		;Set "now" to last read date
	JRST BSTP1		;And step to next BBoard
.STEP:	NOISE (TO NEXT BBOARD FILE WITH NEW MAIL)
	CONFRM
BSTP1:	TXO F,F%BB		;Let world know we are BB hacking
	TXZ F,F%MOD		;No more system mail hacking if that
	AOS T,BBCURR		;Get current BBoard and step
	CAMG T,BBMAX		;Anything to look at?
	IFSKP.
	  TXNE F,F%RSCN		;Message if not rescan, or
	   SKIPE VBSBBD		; if user wants noise
	    CITYPE <No BBoards with new mail>
	  SETZM BBCURR		;Reset to start over if requested
	  SKIPG MSGJFN		;Any current file?
	  IFSKP.
	    CALL CLOSEI
	    SETZM LASTM		;No more messages
	    CALL UNMAPF		;Unmap old file
	    CALL CLOSEF		;Release old cruft
	  ENDIF.
	  TXZ F,F%BB!F%RONL	;Not hacking BB any more
	  TXZN F,F%RSCN		;If still RSCANing, or
	   TXNE F,F%RTE		;If returning to EXEC,
	    SKIPE RSCFLG	; and user wants EXEC return,
	  IFSKP.
	    TXO F,F%RSCN	;Set flag indicating to QUIT
	    RET			;And return back to top level
	  ENDIF.
	  CALL GETFIL
	  CALL RECENT
	  CALLRET SUMMRY
	ENDIF.
	MOVSI A,[GJ%OLD!GJ%XTN+1 ;Setup defaults
		 .-.
IFE NICSW,<
		 -1,,MLBXDV
		 -1,,BBDIR
>;IFE NICSW
IFN NICSW,<
		 -1,,BBDEV
		 0
>;IFN NICSW
		 -1,,MLBXNM
		 -1,,MLBXEX
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0]		;.GJATR
	HRRI A,CMDGTB		;Initialize GTJFN% block
	BLT A,CMDGTB+.GJATR
	HLRO B,BBTAB(T)		;Make pointer to BB string
	MOVE A,[.NULIO,,.NULIO]	;Need this to make GTJFN% work
	MOVEM A,CMDGTB+.GJSRC
	MOVEI A,CMDGTB		;Point to block again
	GTJFN%			;Find the file
	IFJER.
	  MOVX A,GJ%DEL		;Maybe it's deleted?
	  IORM A,CMDGTB		;Allow deleted files
	  MOVEI A,CMDGTB	;Point to block
	  GTJFN%		;Get it now?
	  IFNJE.
	    RLJFN%		;Got it, was deleted, so ignore it
	     NOP		;Errors don't count
	    JRST BSTP1		;Loop for more files
	  ENDIF.
	  CAIE A,GJFX18		;No such file name,
	   CAIN A,GJFX19	;Or no such file type?
	    MOVX A,GJFX24	;Yes, normalize to File-not-found
	  CAIE A,GJFX24		;File not found?
	   JERROR		; No, real problem
	  CIETYP <BBoard file not found: >
	  HLRO A,BBTAB(T)	;Get string again
	  PSOUT%		;Type file name
	  TMSG <, ignored>	;And disposition
	  JRST BSTP1		;Not there, just try next one
	ENDIF.
	PUSH P,A		;Save JFN
	TXO F,F%RONL		;Read only for BB command
	SKIPG MSGJFN		;Any current file?
	IFSKP.
	  SETZM LASTM		;No more messages
	  CALL UNMAPF		;Yes, unmap old file
	  CALL CLOSEF		;Release old cruft
	ENDIF.
	POP P,MSGJFN		;Restore new MSGJFN
	CALL SIZFIL		;And the size
	SKIPN FILSIZ		;Is it an empty file?
	 JRST BSTP1		;Yeah, move on to next one
	MOVE B,[<FLD 7,OF%BSZ>!OF%RD!OF%PDT] ;Read access, no updates
	OPENF%
	IFJER.
	  JSNARL <Can't open file>
	  JRST BSTP1
	ENDIF.
	CALL GETFLB		;Special read for BBoard files
	CALL RECENT		;Find recent messages, type headers
	SKIPN NUNSEE		;Any mail here?
	 JRST BSTP1		;No, step along
	TXZE F,F%RSCN		;No RSCAN%, stay at comnd level.
	 TXO F,F%RTE		;But return to EXEC (maybe...)
	CALLRET SUMMRY		;Print summary, return with file in

;;;Set date of BBoard message file
.BBDAT:	NOISE (OF LAST MESSAGE SEEN IS)
	CALL GETDAT
	PUSH P,B
	CONFRM
	POP P,A
	CALLRET SXDAT

;;;Get another message file

.EXAMI:	TXOA F,F%F1		;Examine command
.GET:	 TXZ F,F%F1		;Get command
	TXZ F,F%BB!F%RTE	;Not BB, don't return to EXEC
	NOISE (MSGS FROM FILE)
	MOVSI A,[GJ%OLD!GJ%XTN+1 ;Setup file defaults
		 .-.
		 0
		 0
		 -1,,MLBXNM
		 -1,,MLBXEX
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0
		 0]		;.GJATR
	HRRI A,CMDGTB		;Initialize GTJFN% block
	BLT A,CMDGTB+.GJATR
	MOVEI B,[FLDDB. .CMFIL]	;Want existing file name with
	CALL $COMND		; "MAIL.TXT.1" default
	IFXN. A,CM%NOP		;Was a file name recognized?
	  HLLZS CMDGTB+.GJGEN	;No, toss away generation 1 default
	  SETZM CMDGTB+.GJDEV	;Toss all defaults
	  SETZM CMDGTB+.GJDIR
	  SETZM CMDGTB+.GJNAM	;Toss away "MAIL" default
	  SETZM CMDGTB+.GJEXT	;Toss away "TXT" default
	  MOVEI B,[FLDDB. .CMFIL] ;Now try again with
	  CALL CMDFLD		; no defaults
	ENDIF.
	PUSH P,B		;Save JFN
	MOVEI B,CNFCMD		;Have user confirm this command
	CALL $COMND
	IFXN. A,CM%NOP		;Okay?
	  POP P,A		;No, release JFN, and
	  RLJFN%
	   NOP
	  JERROR		; and go away
	ENDIF.
GETFA:	TXZ F,F%AMOD!F%MOD!F%RONL ;Not hacking system mail any more
	TXNE F,F%F1
	 TXO F,F%RONL		;Read only for examine command
GETF1:	SKIPG MSGJFN		;Any current file?
	IFSKP.
	  SETZM LASTM		;No more messages
	  CALL UNMAPF		;Unmap old file
	  CALL CLOSEF		;Release old cruft
	ENDIF.
	POP P,MSGJFN		;Restore new MSGJFN
IFN NICSW,<
	TXNN F,F%BB
	 CALL LOGEM##
>;IFN NICSW
GETF3:	CALL SIZFIL		;And the size
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD> ;Read access
	TXNE F,F%F1		;Examine command?
	 TRO B,OF%PDT		;Yes, don't update anything
	OPENF%
	IFJER.
	  MOVE A,MSGJFN
	  JSNARL <Can't open message file "%1J">
	  JRST FNDFLX
	ENDIF.
	HRROI A,STRBUF		;Create a file-name.init
	MOVE B,MSGJFN		;Pick up the msg file
	MOVE C,[1B2!1B5!1B8!JS%PAF] ;Dump it
	JFNS%
	HRROI B,[ASCIZ/.MM-INIT/] ;So we can have mail-specific init
	SETZ C,
	SOUT%			;Copy the .init
	IDPB C,A		;Tie off name with null
	MOVX A,GJ%OLD!GJ%SHT	;See if the file is there
	HRROI B,STRBUF
	GTJFN%
	IFSKP.
	  PUSH P,A		;Save JFN
	  CALL ININIT		;Erase previous init file parameters
	  POP P,A
	  CALL DOINIT		;Init from the file
	ENDIF.
	IFXN. F,F%BB		;Reading BBoard file?
	  CALL GETFLB		;Special read for BBoard files
	  TXO F,F%F1		;Always type headers
	ELSE.
	  CALL GETFL1		;Get file and parse it, barf if empty
	  TXNN F,F%AMOD		;Unless auto mod
	   TXNN F,F%MOD		;Mod prints headers
	    TXNE F,F%RSCN	;Allow return to top-level
	     TXOA F,F%F1	;Type headers if from command line
	      TXZ F,F%F1
	ENDIF.
	CALL RECEN0		;Remark new messages
	SKIPE RSCFLG		;If user wants to stay in MM
	 TXNN F,F%BB		;And we are reading a BB,
	  SKIPA
	   TXZ F,F%RSCN		;Then don't allow return to EXEC
	SKIPG NUNSEE		;If mail to read
	 TXNN F,F%BB		; or not reading a BBoard, then
	  TXZ F,F%RSCN		; no RSCAN%, stay at comnd level.
	TXNN F,F%RSCN		;So, if still rscanning, no summary
	 TXNE F,F%AMOD		;No summary if auto mod
	  SKIPA
	   CALL SUMMRY
	RET
;;;Get JFN on local mailbox
; A/ GTJFN% flags
; B/ location of local user name string
;	CALL GETMFL
; Ret +1; GTJFN% error
; Ret +2; GTJFN% okay, A/ JFN

GETMFL:	STKVAR <FLAGS,USER>
	MOVEM A,FLAGS		;Save GTJFN% flags
	MOVEM B,USER		;Save user string
	MOVE A,[POINT 7,FILNAM]	;Deliver local mail right away
	MOVEI B,MLBXDV		;Set up post office box name
	CALL MOVSTR
	MOVX B,":"		;Device delimiter
	IDPB B,A
	MOVX B,.CHLAB		;Directory delimiter
	IDPB B,A
	MOVE B,USER		;Get back user string
	CALL MOVSTR		;Now, the local user name
	MOVX C,.CHRAB
	IDPB C,A
	MOVEI B,MLBXFN		;And the mailbox name
	CALL MOVST0
	MOVE A,FLAGS
	HRROI B,FILNAM
	GTJFN%
	 ERJMP R		;Let caller decide action on error
	RETSKP			;Skip return okay

	ENDSV.
;;;Get size of current file, return MSGJFN in A

SIZFIL:	SKIPG A,MSGJFN
	 RET
	MOVE B,[5,,.FBBYV]
	MOVEI C,FILPGS
	GTFDB%			;Get the size stuff
	IFJER.
	  JSNARL
	  RET
	ENDIF.
	IFXN. F,F%MOD		;Getting system mail?
	  SETO A,		;This job
	  HRROI B,FILRD		;Where to stick info
	  MOVEI C,.JILLN	;Get time of last login
	  GETJI%
	   SETZM FILRD
	  MOVE A,MSGJFN		;Get back JFN
	ENDIF.
	LDB U,[POINT 6,FILPGS,11] ;Get byte size
	MOVE V,FILSIZ		;Else get the size now
	CAIN U,7		;If 7 bit,
	IFSKP.
	  CAIN U,^D36		;36 bit is easier
	  IFSKP.
	    MOVEI T,^D36
	    IDIVI T,(U)		;Get number of bytes in a word
	    IDIVI V,(T)		;Get number of words
	  ENDIF.
	  IMULI V,5		;Into bytes
	  MOVEM V,FILSIZ	;Save the size
	ENDIF.
	IDIVI V,5000		;Since we have the file open, the
	JUMPE V+1,.+2		;Page count may be too little
	 ADDI V,1		;So, we must check against the
	HRRZ T,FILPGS		;Size according to the byte count
	CAIN V,(T)		;If GTFDB% equals computed,
	 RET			;Then done
	MOVE A,MSGJFN		;Find first free page,
	GTSTS%			;If file is open
	IFXE. B,GS%OPN		;Is it open?
	  HRRM V,FILPGS		;No, use what we have
	  RET
	ENDIF.
	FFFFP%			;Get first free page
	HRRM A,FILPGS		;And use it
	MOVE A,MSGJFN		;Callers expect JFN in A
	RET
;;;Parse the file from message (M) on
PARSEI:	TXOA F,F%F3		;Flag index already loaded
PARSEF:	 TXZ F,F%F3		;No index, do the work
	HRRZ A,MSGJFN		;Check status of JFN
	GTSTS%
	TXNN B,GS%NAM		;Legal JFN?
	 ERROR <Message file disappeared>
	IFXE. B,GS%OPN		;Open?
	  MOVX B,<<FLD 7,OF%BSZ>!OF%RD>	;Try to open it
	  OPENF%
	  IFJER.
	    MOVE A,MSGJFN
	    JERROR <Can't open message file "%1J">
	  ENDIF.
	ENDIF.
	HRRZ C,FILPGS
	CAIG C,NMTXPG		;Enough room?
	IFSKP.
	  TXZ F,F%BB!F%RONL	;No longer BBoard hacking
	  SETZM FILPGS		;Keep UNMAPF from getting confused
	  CALL CLOSEF		;Get rid of JFN, etc
	  MOVEI V,NMTXPG
	  ERROR <File size (%3D pages) is larger than MM's limit of %10D>
	ENDIF.
	SKIPN V,M		;Start with first message
	IFSKP.
	  MOVE V,MSGALL-MSGLEN(M) ;No, get start of message
	  ADD V,MSGSAL-MSGLEN(M)
	ENDIF.
	MOVEI A,^D5000		;Compute first page of transfer
	IDIVM V,A		; from starting byte
	SUBI C,(A)		;Compute number of pages to read
	MOVEI B,MTXPGN(A)	;First page here to map into
	HRL A,MSGJFN		;File they come from
	HRLI B,.FHSLF
	TXO C,PM%CNT!PM%RD!PM%PLD!PM%CPY ;Map read copy-write preloaded
	PMAP%
	DO.
	  TXZE F,F%F3		;Already have index?
	   EXIT.		;Yes, check sizes and return
	  SETZM MSGPGS(M)	;Clear out this entry by zapping
	  MOVSI A,MSGPGS(M)	;1st word, then set up
	  HRRI A,MSGPGS+1(M)	;BLT word to flush rest of entry.
	  BLT A,MSGPGS+MSGLEN-1(M) ;(standard flush code)
	  CALL CHR2BP		;Get byte pointer to this
	  CAMGE V,FILSIZ	;Are we at the end of file?
	  IFSKP.
	    CAME V,FILSIZ	;Really at EOF?
	     WARN <File has bad format: last message extends beyond EOF>
	    EXIT.		;Don't look for a new message
	  ENDIF.
	  MOVEM V,MSGALL(M)	;Start of whole message
	  DO.
	    MOVE B,A		;Copy current pointer
	    ILDB T,A		;Get character
	    CALL BP2CHR		;Get character pointer
	    CAML V,FILSIZ	;Running off end of file?
	     EXIT.		;Yes, not interested in trailing nulls
	    JUMPE T,TOP.	;Ignore nulls
	    MOVE A,B		;Restore current pointer
	    SETZB B,C		;Use default parsing
	    IDTIM%		;Parse the date/time
	    IFJER.
	      TXON F,F%F3	;Note warning given
	       WARN <File has bad format: message %M has no receive date>
	      SETO B,
	    ENDIF.
	    CALL BP2CHR		;Get character pointer
	    CAMGE V,FILSIZ	;Ran off EOF?
	    IFSKP.
	      WARN <File has bad format: spurious message header at EOF>
	      EXIT.		;Ignore bad msg, make final size checks
	    ENDIF.
	    MOVEM B,MSGDAT(M)	;Receive date
	    LDB T,A		;Get character
	    CAIN T,","
	    IFSKP.
	      CALL PARMSB	;Message in bad format, advance to next
	       EXIT.		;No more messages
	      LOOP.		;Possible message here
	    ENDIF.
	    MOVEI C,^D10	;Decimal
	    CALL $NIN
	    HRLM B,MSGBOD(M)	;Save length of real message
	    MOVEI C,10		;Octal
	    CALL $NIN
	    MOVEM B,MSGBTS(M)	;Save message bits
	    MOVEM B,MSGFBS(M)
	    SETZM MSGFLG(M)
	    DO.			;Search for end of line
	      ILDB T,A		;Get character
	      CALL BP2CHR	;Get character pointer
	      CAML V,FILSIZ	;Running off end of file?
	       EXIT.		;Yes, leave this
	      JUMPE T,TOP.	;Ignore nulls
	      CAIE T,.CHCRT	;Ignore CR's
	       CAIN T,.CHSPC	;Ignore spaces; HERMES inserts 'em
		LOOP.
	    ENDDO.
	    CAMGE V,FILSIZ	;Ran off EOF?
	    IFSKP.
	      WARN <File has bad format: spurious message header at EOF>
	      EXIT.		;Ignore bad msg, make final size checks
	    ENDIF.
	    CAIN T,.CHLFD	;Saw end of line?
	    IFSKP.
	      CALL PARMSB	;Message in bad format, advance to next
	       EXIT.		;No more messages
	      LOOP.		;Possible message here
	    ENDIF.
	    TXZ F,F%F3		;Clear error flag
	    CALL BP2MCH		;Get character position
	    HRRM V,MSGBOD(M)	;Save start of real message
	    HLRZ B,MSGBOD(M)	;Get size again
	    ADDI B,(V)		;Get end of whole thing
	    MOVEM B,MSGSAL(M)	;Save size of whole message
	    ADD B,MSGALL(M)	;Compute absolute byte of end of msg
	    PUSH P,B		;Save it for later
	    MOVEI T,[ASCIZ/
From:/]
	    CALL FNDHDC		;Find it and count it
	    IFNSK.
	      MOVEI T,[ASCIZ/
Sender:/]
	      CALL FNDHDC
	       NOP		;Guess there is none
	    ENDIF.
	    HRRZM V,MSGFRM(M)
	    HRLM W,MSGFRM(M)
	    MOVEI T,[ASCIZ/
Subject:/]
	    CALL FNDHDC		;Find header and count bytes
	     NOP		;Don't care if fails
	    HRRZM V,MSGSUB(M)
	    HRLM W,MSGSUB(M)	;Save position and size
	    MOVEI T,[ASCIZ/
Message-ID:/]
	    CALL FNDHDC		;Find header and count bytes
	     NOP		;Don't care if fails
	    HRRZM V,MSGMID(M)
	    HRLM W,MSGMID(M)	;Save position and size
	    POP P,V		;Recover ending address
	    MOVEM M,LASTM	;Update total number of messages
	  ENDDO.
	  TXZ F,F%F3		;Clear error flag
	  CAML V,FILSIZ		;Is this the last one?
	   EXIT.		;Yes, now make final size checks
	  CAIGE M,MSGLEN*<NMSGS-1> ;Got all we can handle?
	  IFSKP.
	    SETZM FILPGS	;Keep UNMAPF from getting confused
	    CALL CLOSEF		;Get rid of JFN, etc.
	    MOVEI C,NMSGS
	    ERROR <Message file has more messages than MM's limit of %3D>
	  ENDIF.
	  ADDI M,MSGLEN		;No, go to next message
	  LOOP.
	ENDDO.
	IFXE. F,F%SWRN		;Has warning already been given?
	  MOVE B,LASTM		;See if we are getting close
	  IDIVI B,MSGLEN	; to the maximum msg count
	  ADDI B,1		;yduJ pacification
	  MOVEI C,NMSGS
	  CAIL B,-NMSWRN(C)
	   WARN <Number of messages (%2D) is approaching MM's limit of %3D>
	  HRRZ V,FILPGS		;See if we are getting close
	  MOVEI C,NMTXPG	; to the maximum file size
	  CAIL V,-NPGWRN(C)
	   WARN <Message file size (%10D pages) is approaching MM's limit of %3D>
	  MOVE M,LASTM
	  CAIGE M,MSGLEN*<NMSGS-NMSWRN>
	   CAIL V,<NMTXPG-NPGWRN>
	ANNSK.
	  TXO F,F%SWRN		;Flag warning given
	  CIETYP <
 If either the number of messages or the size of the message file exceeds
 MM's limit, then MM will be unable to process the message file.  To
 prevent this, you should either "DELETE" some messages or split up your
 mail file by "MOVE"ing some messages to another file.  Then use the EXPUNGE
 command to remove those messages from your mail file.
>
	ENDIF.
	RET
;;;Here when encountered a bad message header.  Advance to the next line.
;;;Non-skip if at EOF or too many messages
;;;Skips if should try parsing another message.

PARMSB:	SKIPE MSGDAT(M)		;Was previous msg bad too?
	IFSKP.
	  WARN <File has bad format: invalid header for message %M>
	  SETOM MSGDAT(M)	;Mark this one bad.
	  ADDI M,MSGLEN		;Bump to start reading next msg.
	  CAIL M,MSGLEN*<NMSGS-1>
	   RET
	ENDIF.
	DO.
	  ILDB T,A		;Search for LF
	  CALL BP2CHR
	  CAMGE V,FILSIZ	;Ran off EOF?
	  IFSKP.
	    WARN <Garbage extends to end of file>
	    RET			;Ignore bad msg, make final size checks
	  ENDIF.
	  CAIE T,.CHLFD		;Found that LF?
	   LOOP.		;No
	ENDDO.
	SETOM MSGDAT(M)		;Set flag saying prev msg was bad
	CALL BP2CHR		;Get new V for this BP
	MOVE B,V
	SUB B,MSGALL-MSGLEN(M)	;Find length of garbage thus far
	MOVEM B,MSGSAL-MSGLEN(M) ;Update total length of bad msg
	HRLZM B,MSGBOD-MSGLEN(M) ;Set "body" to all of bad msg.
	MOVEM V,MSGALL(M)
	RETSKP			;Return, letting caller see if another there
;;; Our own version of NIN, does not hack negative or anything like that
$NIN:	SETZ B,
	DO.
	  ILDB D,A
	   ERJMP R		;In case of non-ex page
	  CAIL D,"0"
	   CAILE D,"0"-1(C)
	    RET			;Done
	  CAIN C,^D8		;This makes overflow not happen
	   LSH B,3
	  CAIE C,^D8
	   IMULI B,(C)
	  ADDI B,-"0"(D)
	  LOOP.
	ENDDO.
; Find header and count the bytes in it

FNDHDC:	CALL FNDHDR
	IFNSK.
	  SETZB V,W		;Say we didn't find it
	  RET
	ENDIF.
	SETZ W,			;Count size of field in w
	CALL CNTHDL		;Count this header line we found
	RETSKP			;Success return

; Count bytes in this header line into current count in w

CNTHDL:	DO.
	  ILDB T,A		;Get char
	  CAIE T,.CHCRT		;Until the CR
	   AOJA W,TOP.
	ENDDO.
	RET
;;;Try to find a header in the message body

FNDHDR:	HRRZ W,MSGHLN(M)	;Length of header
	JUMPN W,FNDHD1
	HLRZ W,MSGBOD(M)	;Number of bytes in whole
	PUSH P,T
	MOVEI T,[BYTE (7) 15,12,15,12]
	CALL FNDHD1		;Find blank line indicating end
	 SETZ V,
	POP P,T
	HRRM V,MSGHLN(M)	;Save length of header
	SKIPN W,V
	 HLRZ W,MSGBOD(M)
FNDHD1:	HRRZ V,MSGBOD(M)	;Starting byte
	CALL SEARCH		;Try to find it
	 RET			;No good
	AOS (P)
	CALLRET BP2MCH		;And get char pointer
;;;Try to match a pattern string within a given portion of a msg

SEARCH:	HRLI T,(<POINT 7,>)
	TDZA A,A
SEARC1:	 ADDI A,1		;One more char in search table
	ILDB B,T		;Get a character
	MOVEM B,STRBUF(A)	;Compile search table
	JUMPN B,SEARC1
	IFE. A
	  SKIPE W		;If there is no pattern
	   RET			; fail if there is text
	  RETSKP		; else say there is a match!
	ENDIF.
	SUBI W,(A)		;Difference between text and pattern
	JUMPL W,R		; lengths is the maximum # of times
				; to check for the presence of pattern
	CALL MCH2BP		;Get byte pointer
	SKIPL A			;Aligned to word boundary already
	 JSP U,SEARQ		;Pattern may begin within this word
	MOVE N,STRBUF		;First character
	IMUL N,[BYTE (1)0 (7)1,1,1,1,1]
	MOVE O,N
	XOR O,[BYTE (1)0 (7)40,40,40,40,40]
	JSP U,.+1		;Come back to top if pattern not found
	DO.
	  MOVE B,N		;Pattern to match
	  MOVE C,O		;Case indept one
	  MOVE D,(A)		;Word to try
	  LSH D,-1		;Right justify text word
	  MOVE E,D
	  EQVB D,B		;If the first pattern char is present
	  EQVB E,C		; this results in '177' at that char
	  ADD D,[BYTE (1)1 (7)1,1,1,1,1] ;Add 1 to each char complementing LSB,
	  ADD E,[BYTE (1)1 (7)1,1,1,1,1] ; but note that any carry from '177'
	  EQV D,B		; un-complements LSB of left char!
	  EQV E,C		;Check sameness of each char LSB
	  TDNN D,[BYTE (1)1 (7)1,1,1,1,1] ;If any char LSB remains the same
	   TDNE E,[BYTE (1)1 (7)1,1,1,1,1] ; then there is at least one match!
	    JRST SEARQ		; Yes, go see!
	  SUBI W,5		;We just tested five chars
	  JUMPL W,R		;Not found
	  AOJA A,TOP.		;Try some more
	ENDDO.

SEARQ:	MOVE E,A		;Remember where we begin
	DO.
	  SETZ B,
	  DO.
	    SKIPN C,STRBUF(B)	;Get next char
	     RETSKP		;Null, we found a match
	    ILDB D,A		;Get next char
	    TRC D,(C)		;XOR text and pattern chars
	    CAIE D,0		;Exact match?
	     CAIN D,40		;No, 'other case' match?
	      AOJA B,TOP.	;Yes, keep trying
	  ENDDO.
	  SOJL W,R		;No, Quit if we've run out of text
	  IBP E			;Incrememt pointer to next char in word
	  MOVE A,E		;Get back pointer
	  TLNE E,760000		;Stop at end of word
	   LOOP.
	ENDDO.
	MOVEI A,1(E)		;Point to start of next word
	HRLI A,440700
	JRST 0(U)		;Not found this word, try some more
;;;Convert byte count in V to byte pointer in A

MCH2BP:	ADD V,MSGALL(M)		;Enter here with relative byte count
CHR2BP:	SAVEAC <B>
	MOVE A,V
	IDIVI A,5
	ADDI A,MTXPAG		;Offset it right
	HLL A,BPS(B)
	RET

;;;Vice versa

BP2MCH:	CALL BP2CHR
	SUB V,MSGALL(M)		;Return relative byte count
	RET

BP2CHR:	LDB C,[POINT 6,A,5]	;Get position field
	MOVEI V,1-MTXPAG(A)	;Clear out bp field
	IMULI V,5
	IDIVI C,7
	SUBI V,(C)
	RET

BPS:	POINT 7,0
	POINT 7,0,6
	POINT 7,0,13
	POINT 7,0,20
	POINT 7,0,27
	POINT 7,0,34
;;;Parse the rest of this line as addresses from byte pointer in A,
;;;Inserting default host name pointed to by E,
;;;Using free space from FREETO.  F%F4 set means no error messages from here.

LEVPDP:	IOWD LEVPLN,LEVPDL	;Level stack pointer
PRADDF:	TXOA F,F%FST!F%F4	;Entry for fast parse
PRADDR:	 TXZ F,F%FST		;Slow parse entry
	MOVE W,FREETO		;Start pointer out right
PRADD0:	SETZ C,			;Not looking for anything
	MOVE V,LEVPDP		;Get some room for pdl
	MOVEI U,STRBUF		;Get some random string space
PRAD00:	MOVEI T,(U)		;Save start of address
	HRLI U,(<POINT 7,>)	;Make byte pointer for storing name
	TXZ F,F%F2!F%AT!F%ADR!F%QOT ;Clear state flags
PRAD01:	ILDB B,A		;Get char
	CAIN B,","
	 JRST PRADD0		;Null address, forget it
	JUMPE B,PRADD5		;End of address prematurely
	CAIN B,.CHCRT		;Ignore CR
	 JRST PRAD01
	CAIE B,.CHLFD		;Saw LF?
	IFSKP.
	  MOVE B,A		;Sniff ahead at next character
	  ILDB B,B
	  CAIE B,.CHTAB		;Was it whitespace?
	   CAIN B,.CHSPC
	    JRST PRAD01		;Yes, saw a continuation line
	  JRST PRADD5
	ENDIF.
	CAIE B,.CHTAB
	 CAIN B,.CHSPC
	  JRST PRAD01		;Flush leading white space
	JRST PRAD10		;Start with this character

;;;Here is the main parsing loop

PRADD1:	ILDB B,A		;Get next character
PRAD10:	CAIN B,.CHCRT		;Ignore random CR
	 ILDB B,A
	CAIE B,.CHLFD		;End of line?
	IFSKP.
	  MOVE B,A
	  ILDB B,B		;See if continuation
	  CAIE B,.CHSPC
	   CAIN B,.CHTAB
	    JRST PRADD1		;Continuation, continue parse
	  JRST PRADD5		;End of line, do address
	ENDIF.
	JUMPE B,PRADD5
	JUMPE C,PRAD11		;Looking for a special character?
	CAIN B,(C)		;Yes, found it?
	 JRST PRAD14		;Yes
	IFGE. C
	  CAIN B,"("		;If addresses not allowed inside,
	   ADDI D,1		;Bump count if going up another level
	  JXN F,F%F2,PRADD1	;Toss it out if ignoring characters
	  JRST PRAD12		;No, go ahead and process it
	ENDIF.
;;;This is a hack.  Its purpose is to get a reasonable parse for:
;;;	<foo:bar@rag>
;;;e.g. where the terminating ";" is missing.  It accomplishes this by
;;;considering right broket to always close off a level even if inner
;;;levels weren't closed.
	CAIE B,.CHRAB		;Close broket?
	 JRST PRAD11		;No, some text character
	PUSH P,V		;Save current level state
	PUSH P,C		;Also current level search character
PRAD09:	CAMN V,LEVPDP		;Gone down too many levels?
	IFSKP.
	  POP V,C		;Back up one level
	  CAIE B,(C)		;Does it match this level?
	   JRST PRAD09		;No, back up further
	  ADJSP P,-2		;Success, toss out old levels
	  JRST PRAD14		;Do level completion stuff
	ENDIF.
	POP P,C			;Yes, retrieve level search character
	POP P,V			;And level state
PRAD11:	JXN F,F%F2,PRADD1	;Go away if ignoring characters
	CAIE B,""""		;Start or end of quoted string?
	IFSKP.
	  TXC F,F%QOT		;Complement " state
	  JRST PRADD1		;And go get some more
	ENDIF.
	JXN F,F%QOT,PRAD13	;If quoted string, insert all other characters
	CAIN B,","		;End of address?
	 JRST PRADD5		;Yes, finish up
	CAIN B,.CHLAB		;Start of address after junk?
	 JRST PRAD22		;Yes, set to look for matching broket
	CAIN B,":"		;Or group name: junk;?
	 JRST PRAD23		;Yes, look for ;
	CAIN B,"("		;Start of comment?
	 JRST PRAD24		;Yes, look for )
PRAD12:	CAIN B,.CHSPC		;End of a token?
	 JRST PRADD3		;Yes, check for things like "@"
	CAIN B,"@"		;Start of some hostname?
	 JRST PRADD4
	CAIE B,.CHLAB		;Don't let these filter in
	 CAIN B,";"
	  JRST PRADD1
PRAD13:	CALL PRADPB		;Ordinary character, just stick it in
	TXO F,F%ADR		;This address is non-null
	JRST PRADD1		;And on for more

PRAD14:	CAIN B,")"		;Close paren?
	 SOJG D,PRAD11		;If count unexpired, treat as ordinary
	MOVE D,C		;Found matching frob
	POP V,C
	TXZ F,F%F2		;Don't ignore any more chars
	TLNN D,200000		;Don't insert char?
	 JRST PRAD13		;No, insert it then
	DO.
	  ILDB B,A		;Flush trailing whitespace
	  CAIE B,.CHTAB
	   CAIN B,.CHSPC
	    LOOP.
	ENDDO.
	JRST PRAD10

PRAD22:	SKIPA B,[.CHRAB]
PRAD23:	 MOVEI B,";"
	PUSH V,C		;Save previous state
	HRROI C,(B)		;Allow nesting with these
	MOVEI U,(T)		;Flush whatever there was before
	JRST PRAD00		;And go re-init all fields

PRAD24:	PUSH V,C
	MOVEI C,")"		;Will look for matching close
	TXO F,F%F2		;This is a comment, ignore it
	TLO C,200000		;Comments don't insert when done
	MOVEI D,1		;Init nesting count
	JRST PRADD1

;;;End of a token, check for @

PRADD3:	PUSH P,A		;Save where we are now
	PUSH P,B		;And the current character
PRAD30:	ILDB B,A		;Get next one
PRAD36:	CAIE B,.CHTAB
	 CAIN B,.CHSPC
	  JRST PRAD30		;Flush whitespace
	CAIN B,.CHCRT		;Ignore random CR
	 ILDB B,A
	CAIE B,.CHLFD		;Line feed?
	IFSKP.
	  MOVE B,A		;Yes, peek at next character
	  ILDB B,A		;Continuation?
	  CAIE B,.CHTAB
	   CAIN B,.CHSPC
	    JRST PRAD36		;Yes, handle it
	ELSE.
	  CAIN B,"("
	   JRST PRAD32
	  CAIE B,"@"
	ANSKP.
	  ILDB B,A		;Allow continuation
	  CAIN B,.CHCRT		;Ignore random CR
	   ILDB B,A
	  CAIN B,.CHLFD		;Line feed?
	   ILDB B,A		;Yes, maybe a continuation line
	  CAIE B,.CHTAB
	   CAIN B,.CHSPC
	    JRST PRAD33		;Matched, go treat like "@"
	ENDIF.
	POP P,B			;Get back character that fooled us
	POP P,A			;And byte pointer after it
	JRST PRAD13		;And go treat like normal one
PRAD32:	ADJSP P,-2
	JRST PRAD10
PRAD33:	ADJSP P,-2		;Flush what we saved and enter @ code
	PUSH P,A		;Save current pointer
PRAD35:	ILDB B,A
	CAIE B,.CHTAB		;Ignore excess whitespace
	 CAIN B,.CHSPC
	  JRST PRAD35
	CAIN B,.CHCRT		;Ignore CR too
	 ILDB B,A
	CAIN B,.CHLFD		;Linefeed?
	IFSKP.
	  POP P,A		;No, assume start of host name
	  JRST PRADD4
	ENDIF.
	ILDB B,A		;Yes, continuation line?
	CAIE B,.CHTAB
	 CAIN B,.CHSPC
	IFNSK.
	  ADJSP P,-1		;Yes, update pointer to here
	  JRST PRADD4
	ENDIF.
	POP P,A			;This is a wierd case
PRADD4:	TXO F,F%AT		;Flag @ seen
	MOVEM U,SAVU
	SETZ B,
	IDPB B,U		;Stick a null onto end of address
	MOVEI U,1(U)		;Point to next word
	HRLI T,(U)		;This will be the start of the hostname
	HRLI U,(<POINT 7,>)
	JRST PRAD01

;;;Here when we have finished parsing the address, stick in any host default
;;;and build up the final block

PRADD5:	PUSH P,A		;Save byte pointer
	CAIE B,","
	 TXZA F,F%COMA
	  TXO F,F%COMA
PRAD50:	LDB B,U			;Flush trailing whitespace
	CAIE B,.CHSPC
	 CAIN B,.CHTAB
	IFNSK.
	  ADD U,[7B5]
	  SKIPGE U
	   SUB U,[43B5+1]
	  JRST PRAD50
	ENDIF.
	SETZ B,
	IDPB B,U		;End with null
	MOVSI B,(<POINT 7,(T),6>) ;See if got a non-null address
	LDB B,B
	JUMPE B,PRAD53		;Flush address if empty
	MOVEI U,(W)
	MOVEM U,SAVU		;In case of final parse error
	SETZM ADRFLG(U)
	SETZM ADRLNK(U)
	MOVEI A,ADRSTR(W)
	HRLI A,(<POINT 7,>)
	MOVEI B,(T)
	CALL MOVST0		;Move in user name
	MOVEI A,1(A)		;Point to next free word
	SUBM A,W		;Get length
	EXCH A,W
	STOR A,ADSIZ,(U)	;Store size field
	JXN F,F%AT,PRAD54	;Handle net recipient if host name seen
	SKIPE C,E		;Was there a default host?
	 JRST PRAD52		;Yes, use it then
PRAD51:	TXZ F,F%AT		;Make sure this is clear for REPLY
	HRROI B,(T)		;User name
	HRROI A,[ASCIZ/System/]	;Is address SYSTEM?
	STCMP%
	IFE. A
	  MOVX C,SYSCOD		;Yes, pick up system code
	  JRST PRA520		;Set type as local user
	ENDIF.
	HRROI B,(T)		;User name
	MOVX A,RC%EMO
	RCUSR%
	IFNJE.
	  JXE A,<RC%NOM!RC%AMB>,PRA520 ;Bad local user?
	ENDIF.
	HRROI A,(T)		;Yes, maybe forwarded or something
	TXNN F,F%FST		;Fast parse requested?
	 CALL CHKFWD		;Forwarded?
	  JRST PRAD55		;No, assume error
	MOVE C,LCLHST		;Get host string pointer
	JRST PRAD52

PRA520:	TDZA A,A		;Local recipient
PRAD52:	 MOVEI A,AD.NET		;Network recipient
	STOR A,ADTYP,(U)	;Store type field
	MOVEM C,ADRUSR(U)	;And host/user number
PRAD53:	POP P,A			;Get back byte pointer
	JXN F,F%COMA,PRADD0	;Unless end of line get next one as well
	RET			;All done, return

PRAD54:	JXN F,F%FST,PRAD59	;Fast parse requested?
	HLRO A,T		;Host name to look up
	CALL HSTNAM		;See if name known
	 JRST PRAD57		;Name not found
	CAMN A,LCLHST		;Really our local host?
	 JRST PRAD51		;Yes, make local address
	MOVE C,A		;Else network address, get host pointer
	JRST PRAD52

PRA550:	PUSH P,A
	SKIPA B,[[ASCIZ/local file/]]
PRAD55:	 MOVEI B,[ASCIZ/local user/]
	MOVEI C,(T)
PRAD56:	TXNN F,F%F4		;Unless silence requested
	 CIETYP < No such %2S as "%3R", address ignored
>
PRAD59:	TXZ F,F%AT		;No network address, etc.
	MOVE W,SAVU
	JRST PRAD53

PRAD57:	MOVEI B,[ASCIZ/host/]
	HLRZ C,T
	JRST PRAD56

;;;Deposit header byte into buffer after checking for overflow (some insanely
;;;long header, etc.)
PRADPB:	PUSH P,B		;Save character
	HRRZ B,U
	CAIL B,LEVPDL		;Beyond a reasonable maximum?
	 JRST CPPOPJ		;Yes, ignore request
	POP P,B
	IDPB B,U
	RET
;;;Get To and cc lists from message, default host in E

PRTOCC:	SKIPE RCCOTH		;Make everybody cc?
	 TXOA F,F%CC		;Yes, do this from the start
	  TXZ F,F%CC		;Not in CC yet
PRTO11:	CALL PRADDT		;Parse this line
	LDB B,A			;Get terminating character
	JUMPE B,R		;Null means all done now
	CAIN B,.CHCRT		;Was it a CR?
	 IBP A			;Yes, move over the LF too
PRTO12:	ILDB B,A		;Get next char
	JUMPE B,R
	CAIE B,.CHTAB		;Whitespace indicates continuation
	 CAIN B,.CHSPC
	  JRST PRTO11
	JRST PRTO15		;Look for To/cc

PRTO14:	ILDB B,A		;Here if don't allow continuation
	JUMPE B,R		;Punt if done
PRTO15:	CAIE B,"T"		;More to maybe
	 CAIN B,"t"
	  JRST PRTO20
	CAIE B,"C"		;Or maybe start of cc
	 CAIN B,"c"
	  JRST PRTO30
	CAIN B,.CHCRT		;Look like CR?
	 ILDB B,A		;Yes, get the LF?
	CAIN B,.CHLFD		;Blank line?
	 RET			;Yes, done with headers
PRTO13:	ILDB B,A		;Otherwise soak up line
	CAIN B,.CHLFD		;Saw linefeed yet?
	 JRST PRTO14		;Yes, try this line (no continuation)
	JUMPN B,PRTO13		;Keep on going unless EOM
	RET

PRTO20:	ILDB B,A
	CAIE B,"O"
	 CAIN B,"o"
	  CAIA
	   RET
	ILDB B,A
	CAIE B,":"
	 RET			;No good I guess
	JRST PRTO11		;Get rest of this line then

PRTO30:	ILDB B,A
	CAIE B,"C"
	 CAIN B,"c"
	  CAIA
	   RET
	ILDB B,A
	CAIE B,":"
	 RET
	TXO F,F%CC		;Now doing cc
	JRST PRTO11		;And now go get more
;;;Add new recipients to the appropriate lists

ADDTO:	TXNE F,F%CC
	 SKIPA T,[CCLIST]
	  MOVEI T,TOLIST
ADDTO0:	HRRZ U,FREETO
	HRRZM W,FREETO		;Update free pointer now
	DO.
	  CAIN U,(W)		;Got to where we left off?
	   RET			;Yes, done
	  LOAD B,ADTYP,(U)	;Get type field
	  MOVEI B,LCLIST(B)
	  CALL ADDLST		;Add into transmission medium list
	  IFSKP.
	    SKIPN (T)		;Not duplicate, this the first entry?
	     HRRM U,(T)		;Yes, store it as head then
	    HLRZ B,(T)		;Get old tail
	    IFN. B
	      STOR U,ADPTR,(B)	;Link to old tail
	    ENDIF.
	    HRLM U,(T)		;This is new tail
	  ENDIF.
	  LOAD B,ADSIZ,(U)	;Get size
	  ADDI U,(B)
	  LOOP.
	ENDDO.

;;;Thread block in U into list in B

ADDLST:	MOVE C,ADRUSR(U)
	SKIPE V,(B)
	IFSKP.
	  HRRM U,(B)		;No previous, store this at the end
	  RETSKP
	ENDIF.
	DO.
	  CAMG C,ADRUSR(V)
	  IFSKP.
	    HRRZ D,ADRLNK(V)	;Get next element of list
	    JUMPE D,ENDLP.	;None there, put on end of list
	    MOVEI V,(D)
	    LOOP.
	  ELSE.
	    CAIN B,LCLIST
	     CAME C,ADRUSR(V)	;Local user matches exactly?
	      SKIPA D,V
	       RET		;Yes, flush it
	    HRRM V,ADRLNK(U)	;Link to next
	    HLRZ V,ADRLNK(V)	;Get previous
	    HRLM U,ADRLNK(D)	;Link to previous
	    IFE. V
	      HRRM U,(B)	;No previous, store this at the end
	      RETSKP
	    ENDIF.
	  ENDIF.
	ENDDO.
	HRRM U,ADRLNK(V)	;Add this to end of list
	HRLM V,ADRLNK(U)	;Link to previous
	RETSKP

;;;Remove element in W from transmission medium list

REMLST:	HLRZ A,ADRLNK(W)	;Link to previous this medium
	HRRZ B,ADRLNK(W)	;Link to next this transmission medium
	SKIPE B			;Unless tail of list...
	 HRLM A,ADRLNK(B)	;New link to previous for next element
	SKIPE A			;Unless head of list...
	 HLRM B,ADRLNK(A)	;New link to next for previous element
	IFE. A			;If this was the head of the list
	  LOAD A,ADTYP,(W)	;Get transmission medium type
	  HRRM B,LCLIST(A)	;Set as starting pointer
	ELSE.
	  HRRM B,ADRLNK(A)	;Link as next
	ENDIF.
	RET
	SUBTTL Message handling subroutines

;;;Type out header of a message

.RHEAD:	CONFRM			;Type header of current message
TYPHDR:	SETABT CMDABO		;Allow CTRL/N abort
	CALL TYPINI		;Init CCOC state
	CALL CRIF		;Get a fresh line
	MOVE O,[POINT 7,WRTPGS]	;Place to put the string
	CALL TYPHD1
	HRROI A,WRTPGS		;Now type it out
	PSOUT%
	MOVEI D,SAVMOD
	CALLRET SETTYM

;;;Stick the header for a message into the string in O

TYPHD0:	TXZA F,F%F3		;Not to TTY
TYPHD1:	 TXO F,F%F3		;To TTY
	MOVE T,MSGBTS(M)	;Get messages bits
	MOVX A,.CHSPC		;This if message not recent
	SKIPL MSGFLG(M)		;Message recent?
	IFSKP.
	  MOVEI A,"R"		;Yes, note as recent
	  TXON T,M%SEEN		;Unseen as well?
	   MOVEI A,"N"		;Yes, is new then
	ENDIF.
	IDPB A,O
	TXNE T,M%SEEN
	 SKIPA A,[.CHSPC]
	  MOVEI A,"U"		;Unseen
	IDPB A,O
	TXNN T,M%ATTN		;Flagged
	 SKIPA A,[.CHSPC]
	  MOVEI A,"F"
	IDPB A,O
	TXNN T,M%RPLY		;Answered
	 SKIPA A,[.CHSPC]
	  MOVEI A,"A"
	IDPB A,O
	TXNN T,M%DELE
	 SKIPA A,[.CHSPC]
	  MOVEI A,"D"		;Deleted
	IDPB A,O
	MOVEI B,MSGLEN(M)	;Message number
	IDIVI B,MSGLEN
	CAIGE B,^D1000		;yduJ pacification
	IFSKP.
	  MOVEI A,"*"		;Indicate 1000 or over
	  IDPB A,O
	  SUBI B,^D1000
	  MOVX C,NO%LFL!NO%ZRO!2B17!^D10
	ELSE.
	  MOVX C,NO%LFL!3B17!^D10
	ENDIF.
	MOVE A,O
	NOUT%
	 NOP
	MOVEI B,[ASCIZ/) /]
	CALL MOVSTR
	PUSH P,A
	SKIPLE B,MSGDAT(M)	;Date
	IFSKP.
	  DMOVE T,[ASCIZ/      /] ;Fill with spaces if not there
	ELSE.
	  HRROI A,T		;Where to stick string
	  MOVX C,OT%NTM
	  ODTIM%
	  TLZ U,(<BYTE (7) 0,177>) ;Clear out year and anything else
	ENDIF.
	MOVE A,(P)
	MOVEI B,T
	CALL MOVSTR
	MOVEM A,(P)
	CALL FRMMEP		;Check if message is from me or not
	 MOVE A,MSGFRM(M)	;Isn't, show From field
	MOVEI B,^D15		;Limited to 15 chars
	POP P,O			;Get back string pointer
	CALL TYPHDX
	IFN. B			;None more needed
	  MOVX A,.CHSPC
	  DO.
	    IDPB A,O
	    SOJG B,TOP.		;Fill with spaces
	  ENDDO.
	ENDIF.
	MOVE A,MSGBTS(M)	;Relevant keyword flags
	CALL KEYSTR		;Insert string for that
	MOVE A,MSGSUB(M)	;Subject field
	IFXE. F,F%F3		;Outputting to TTY?
	  MOVEI B,^D200		;No, use a very large limit then!
	  CALL TYPHDS		;Output subject
	  MOVE A,O
	ELSE.
	  MOVEI B,^D33		;Limit to 33 chars
	  SUBI B,(T)		;Less what we used for keywords
	  CAIGE B,4		;Yes, do sanity check on count
	ANSKP.
	  SKIPE JISMOD		;JIS terminal?
	   SUBI B,2
	  SKIPE SIMODE		;Katakana?
	   SUBI B,1
	  CALL TYPHDS		;Output subject
	  MOVE A,O
	  MOVEI B,[ASCIZ/(B/]	;Reset to romanji from kanji
	  SKIPE JISMOD
	   CALL MOVSTR
	  MOVEI B,.CHCNO	;Reset to romanji from katakana
	  SKIPE SIMODE
	   IDPB B,A
	ENDIF.
	MOVEI B,[ASCIZ/ (/]
	CALL MOVSTR
	HLRZ B,MSGBOD(M)	;Length of message
	MOVEI C,^D10
	NOUT%
	 NOP
	MOVEI B,[ASCIZ/ chars)
/]
	CALL MOVST0
	ADD A,[7B5]		;Return pointer before null
	SKIPG O,A
	 SUB A,[43B5+1]
	RET

; Routine to set up msg header for forwarding
FWDHDR:	MOVE A,[POINT 7,WRTPGS]	;Place to put the string
	MOVEI B,.CHSPC
	IDPB B,A
	PUSH P,A		;Save current ptr
	SKIPLE B,MSGDAT(M)	;Date
	IFSKP.
	  DMOVE T,[ASCIZ/      /] ;Fill with spaces if not there
	ELSE.
	  HRROI A,T		;Where to stick string
	  MOVX C,OT%NTM
	  ODTIM%
	  TLZ U,(<BYTE (7) 0,177>) ;Clear out year etc.
	ENDIF.
	MOVE A,(P)
	MOVEI B,T
	CALL MOVSTR
	MOVEM A,(P)
	CALL FRMMEP		;Check if message is from me or not
	 MOVE A,MSGFRM(M)	;Isn't, show From field
	MOVEI B,^D20		;Limited to 20 chars
	POP P,O			;Get back string pointer
	CALL TYPHDX
	IFN. B			;None more needed?
	  MOVX A,.CHSPC
	  DO.
	    IDPB A,O
	    SOJG B,TOP.		;Fill with spaces
	  ENDDO.
	ENDIF.
	MOVE A,MSGSUB(M)	;Subject field
	MOVEI B,^D45		;Limited to 45 chars
	CALL TYPHDS
	MOVE A,O
	MOVEI B,CRLF0
	CALLRET MOVST0
TYPHDS:	TDZA E,E		;Don't ignore addresses
TYPHDX:	 SETO E,		;Ignore addresses within brokets
	MOVEI D,.CHSPC
	IDPB D,O
	JUMPE A,R		;Nothing there to type
	HRRZ V,A		;Start of field
	HLRZ C,A		;Length
	JUMPE C,R		;If empty, give up
	CALL MCH2BP		;Get byte pointer
	DO.
	  ILDB D,A		;Get first character
	  CAIE D,.CHSPC		;Saw whitespace?
	   CAIN D,.CHTAB
	    SOJG C,TOP.		;Yes, ignore it
	  JUMPE C,R		;If nothing left, lost
	  IFN. E		;If flushing things in brokets
	    CAIN D,.CHLAB	;Start of broketed address?
	     RET		;Yes, lost.  Don't use remaining characters
	  ENDIF.
	ENDDO.
	CAILE C,(B)		;Number of eligible chars too large?
	 MOVEI C,(B)		;Yes, truncate
	SUBI B,(C)		;Get number of chars needed to fill
	IDPB D,O		;Stash character in string
	SOJLE C,R		;Count it
	DO.
	  ILDB D,A
	  CAIE D,.CHLAB		;Start of address?
	  IFSKP.
	  ANDN. E
	    ADDI B,(C)		;Yes, don't use remaining characters
	    RET
	  ENDIF.
	  IDPB D,O
	  SOJG C,TOP.
	ENDDO.
	RET

;;;Check if message is from me, and setup to type out To: field if so

FRMMEP:	MOVE A,MSGFLG(M)
	IFXE. A,M%FRME!M%FRNM	;See if we have done this before
	  HRRZ V,MSGFRM(M)	;No, have to check
	  MOVX A,M%FRNM		;Not from me if don't know who it's from
	  IFN. V		;Know who it's from?
	    CALL MCH2BP
	    SETZ E,		;No host name defaulting
	    PUSH P,F		;Save all flags
	    MOVEI W,TOPAG
	    SKIPN FREETO	;Make sure have some free space to work with
	     MOVEM W,FREETO
	    CALL PRADDF		;Get the guy, but don't add to anything
	    POP P,F
	    MOVE W,FREETO	;Get the address just added
	    HRROI A,MAUSRS
	    HRROI B,ADRSTR(W)
	    STCMP%
	    SKIPN A		;Match?
	     SKIPA A,[M%FRME]	;Yes, from me
	      MOVX A,M%FRNM	;Not from me
	  ENDIF.
	  IORB A,MSGFLG(M)
	ENDIF.
	JXE A,M%FRME,R		;Single return to use From if not me
	MOVEI T,[ASCIZ/
To:/]
	CALL FNDHDC		;Find To: field
	 RET			;Not found, use From
	HRREI A,-3(V)		;Include length of "To:"
	JUMPL A,R		;Didn't find to, still need From
	HRLI A,3(W)		;Length of string plus "To: " header
	RETSKP
;;; Translate bits into string, byte pointer in O, bits in A
;;; Returns bytes output in T
KEYSTR:	TXZ F,F%COMA
	SETZ T,			;Init count
	TXZ A,M%FLAG
	JUMPE A,R
KEYST1:	JFFO A,KEYST2		;{
	MOVEI C,"}"
	TXZE F,F%COMA		;Anything output?
	 IDPB C,O		;Yes, finish it up
	RET

KEYST2:	MOVSI C,400000
	MOVN D,B
	LSH C,(D)
	XOR A,C			;Clear out the bit in question
	HLRZ C,KEYTBL		;Number of entries in table
	MOVEI D,KEYTBL+1	;Start of table
KEYST3:	SOJL C,KEYST1		;Failed to find anything, forget it
	HRRZ E,(D)		;Get number for this frob
	CAIE E,(B)		;Matches?
	 AOJA D,KEYST3		;Keep looking
	TXOE F,F%COMA		;Started list yet?
	IFSKP.
	  MOVEI C,.CHSPC	;No, start it up with space and bracket
	  IDPB C,O
	  ADDI T,1
	  MOVEI C,"{"		;}
	  AOJA T,KEYST4
	ENDIF.
	MOVEI C,","
KEYST4:	IDPB C,O
        ADDI T,1                        ;{  "," or "}"
	HLRZ D,(D)
	HRLI D,(<POINT 7,>)
KEYST5:	ILDB C,D
	JUMPE C,KEYST1
	IDPB C,O
	AOJA T,KEYST5
;;;Type out a message

.LRTYP:	SKIPG MSGJFN
	 ERROR <No current file>
	CONFRM			;Confirm first
	CALLRET TYPMSL

.TYPMS:	SKIPG MSGJFN
	 ERROR <No current file>
	CONFRM			;Confirm first
TYPMSG:	TXZA F,F%F2		;Normal filtering
TYPMSL:	 TXO F,F%F2		;Literally from message
	CALL TYPINI		;Init CCOC state
	SETABT CMDABO		;Allow aborts during typeout
	HLRZ C,MSGBOD(M)	;Length of message
	CIETYP < Message %M (%3D characters):
>
	JUMPE C,TYPMS4		;If empty message output nothing more
	MOVN C,C
	HRRZ V,MSGBOD(M)
	CALL MCH2BP
	MOVE B,A
	TXNE F,F%F2		;Unless literal headers requested
	 JRST TYPMS3
	SKIPN SPRHDR		;Any suppressed headers?
	 SKIPE ONLHDR		;Or only certain ones?
	  JRST TYPSHD		;Yes, process the slow way then
TYPMS3:	MOVX A,.PRIOU
	SOUT%			;Print the message out
TYPMS4:	MOVEI D,SAVMOD		;Restore program's modes
	CALL SETTYM
	MOVX A,M%SEEN		;Mark message as seen
	IORM A,MSGBTS(M)
	MOVE A,MSGDAT(M)	;Get date of message
	CAMLE A,BBXDAT		;Later than last one written?
	 TXNN F,F%BB		;Playing with BBoards now?
	  SKIPA			;No or no, don't write anything
	   CALL SXDAT		;Set it into index file
	CALLRET UPDBIT		;And maybe update

;;;Message typeout init routine

TXTINI:	SAVEAC <A,B,C>
	SKIPN SIMODE		;If Katakana terminal...
	IFSKP.
	  MOVX A,.TICCN		;Deassign CTRL/N to undo any previous assign
	  DTI%
	   ERJMP .+1
	  MOVX A,ST%DIM!.FHJOB	;Cancel special handling of CTRL/O
	  DMOVE B,SAVMOD+3
	  TXZ B,1B<.CHCNO>
	  STIW%
	   ERJMP .+1		;We tried...
	ENDIF.
	JRST TYPIN0

TYPINI:	SAVEAC <A,B,C>
TYPIN0:	MOVX A,.PRIOU		;Yes, current CCOC
	RFCOC%
	SKIPN BSPDSP		;User want image backspaces?
	IFSKP.
	  TXZ B,1B17		;Want 2B17 for image backspace
	  TXO B,2B17
	ENDIF.
	SKIPN SIMODE		;User want image SI/SO?
	IFSKP.
	  TXZ B,1B29!1B31
	  TXO B,2B29!2B31
	ENDIF.
	SKIPN JISMOD		;User want image escapes?
	IFSKP.
	  TXZ C,1B19
	  TXO C,2B19
	ENDIF.
	SFCOC%			;Set updates
	RET

;;;Type out the headers not in the suppressed list only, count in C, bp in B
TYPSHD:	TXZ F,F%F2		;Clear state flag
TYPSH0:	ILDB D,B		;Get first character of line
	CAIE D,.CHCRT		;Start of blank line?
	IFSKP.
	  ADD B,[7B5]		;Yes, back over it
	  JRST TYPMS3		;And type rest of message
	ENDIF.
	SETZ E,			;Reset pointer to :
	CAIE D,.CHSPC		;Space
	 CAIN D,.CHTAB		;Or tab is continuation line
	  ADDI E,1		;Remember this specially
	SKIPA A,[POINT 7,STRBUF] ;Save header here
TYPSH1:	 ILDB D,B		;Get next character
	AOJGE C,TYPMS4		;Nothing but headers
	IDPB D,A		;Stick it in
	JUMPN E,TYPSH2		;Unless already saw a :
	CAIN D,":"		;If this is one
	 SKIPA E,A		;Remember it's position
TYPSH2:	  CAIE D,.CHLFD		;End of a line?
	   JRST TYPSH1		;No, continue accumulating
	SETZ D,			;See if this is a losing header
	IDPB D,A		;Make line end with null
	JUMPE E,TYPSH3		;Didn't see a :, type the line out
	CAIN E,1		;Continuation line?
	 JRST TYPSH4		;Yes, check against last case
	DPB D,E
	PUSH P,B		;Save current pointer
	HRROI B,STRBUF
	PUSH P,C
	SKIPN ONLHDR		;Have headers to type out explicitly?
	IFSKP.
	  MOVEI A,ONLHDR
	  TBLUK%
	  TXNE B,TL%NOM!TL%AMB!TL%ABR ;Complement the flags,
	   TDZA A,A		;if no match, say it matched
	    MOVX A,TL%NOM
	ELSE.
	  MOVEI A,SPRHDR
	  TBLUK%		;Look for it
	  HLLZ A,B		;Get result flags
	ENDIF.
	POP P,C
	POP P,B
	TXNN A,TL%NOM!TL%AMB!TL%ABR ;One we know to flush?
	 TXOA F,F%F2		;Yes, remember we flushed it
	  TXZA F,F%F2		;No, will print it
	   JRST TYPSH0		;Handle next line
	MOVEI D,":"		;Put back in the :
	DPB D,E
TYPSH3:	HRROI A,STRBUF
	PSOUT%			;Type out a winning line
	JRST TYPSH0		;And continue to next one
TYPSH4:	TXNE F,F%F2		;Continuation line, last one flushed?
	 JRST TYPSH0		;Yes, flush this too
	JRST TYPSH3		;No, type this part too

CHKDEL:	MOVX A,M%DELE
	TDNN A,MSGBTS(M)	;Deleted?
	 RETSKP			;No, skip return
	CIETYP < Message %M deleted, ignored.
>
	RET			;Single return
;;;Type out headers of recent messages

RECEN1:	SAVEAC <M>
RECEN2:	TXZA F,F%F1		;Don't type headers
RECENT:	 TXO F,F%F1		;Say type headers
RECEN0:	SKIPG MSGJFN		;Any message file?
	 RET			;No, don't do anything
	SETZB M,NRECNT
	SETZM NUNSEE
	SETZM NDELET
	TXO F,F%F2		;No BB banner typed yet
	DO.
	  DO.
	    SKIPLE B,MSGDAT(M)	;Get recv date of message
	     CAMG B,LASTRD	;Check against last read date
	    IFNSK.
	      TXNN F,F%BB	;If BBoard mail, or
	       TXNE F,F%MOD	;If doing system mail
	      IFNSK.
		MOVX A,M%SEEN
		IORM A,MSGBTS(M) ;Make all old messages seen
		EXIT.
	      ENDIF.
	      MOVE A,MSGBTS(M)	;a := msg bits
	      SKIPE FLMAUT	;Suppress showing flagged messages?
	       EXIT.		;Yes, don't print header
	      JXN A,M%DELE,ENDLP. ;Don't print header if msg deleted
	      JXE A,M%ATTN,ENDLP. ;Don't print header if not flagged
	    ELSE.
	      MOVX A,M%RECE	;Bit to set if recent
	      IORM A,MSGFLG(M)	;Say it's recent
	      AOS NRECNT	;Count one more
	    ANDXN. F,F%BB!F%MOD	;If BBoard or system mail,
	      MOVX A,M%SEEN
	      ANDCAM A,MSGBTS(M) ;Make all recent unseen
	    ENDIF.
	    IFXN. F,F%F1	;Want headers?
	      MOVE A,MSGJFN	;Yes, get JFN for possible banner
	      TXNE F,F%BB	;If not BBoard file,
	       TXZN F,F%F2	;Or we already typed banner,
		TRNA		;Then don't do it again.  Else,
		 CIETYP <Reading BBoard file %1J>
	      CALL TYPHDR	;Type the header
	    ENDIF.
	  ENDDO.
	  MOVE A,MSGBTS(M)
	  TXNN A,M%SEEN		;Count unseen and deleted messages
	   AOS NUNSEE
	  TXNE A,M%DELE
	   AOS NDELET
	  CAML M,LASTM		;Thru with all msgs?
	  IFSKP.
	    ADDI M,MSGLEN
	    LOOP.		;No
	  ENDIF.
	ENDDO.
	MOVE A,NRECNT
	IMULI A,MSGLEN
	SKIPE M			;Unless all messages are new,
	 SUBI M,(A)		;Set current msg to last non-recent
	MOVNI A,MSGLEN		;Set prior M to -1 in case all new
	MOVEM A,PRIORM
	RET
;;;Type out summary of the current file

SUMMRY:	SKIPG MSGJFN		;Is there a file?
	 RET			;No, nothing to say here
	MOVE A,LASTM		;Get number of messages
	IDIVI A,MSGLEN
	AOS D,A
	MOVEI B,[ASCIZ/Last read: %3T/]
	TXNE F,F%MOD		;Special message for system mail
	 MOVEI B,[ASCIZ/Last login: %3T/]
	SKIPG C,LASTRD		;Last read date
	 MOVEI B,[ASCIZ/Never read/]
	SUB D,NRECNT		;Number of old messages
	SKIPN NRECNT
	 TDZA E,E
	  MOVEI E,[ASCIZ/ (%4D old)/]
	HRRZ T,FILPGS		;Number of pages
	CETYPE < %2S, %1D message%1P%5S, %6D page%6P>
	MOVE T,NUNSEE
	SUB T,NRECNT
	SKIPG T
	 TDZA E,E
	  MOVEI E,[ASCIZ/ %6D message%6P unseen/]
	SETZ C,
	SKIPG D,NDELET
	IFSKP.
	  MOVEI C,[ASCIZ/; %4D deleted/]
	  SKIPG T
	   MOVEI C,[ASCIZ/ %4D message%4P deleted/]
	ENDIF.
	CETYPE <%5S%3S>
	RET
;;;Update the file copy of the message bits, unless in read command

UPDBIT:	MOVE B,MSGBTS(M)	;Get new copy of bits
	TXNN F,F%RONL		;Don't try to munge system mail
	 CAMN B,MSGFBS(M)	;Old matches new?
	  RET			;Yes, no need to do any more
	CALL GETJF2		;Get a second JFN if don't already
	 RET			;Failed
	CALL ABNOFF		;No aborts
	NOINT			;No outside diddling
	MOVE V,MSGALL(M)	;Start of the message header
	CALL CHR2BP		;Get byte pointer
	DO.
	  ILDB B,A		;Get char
	  CAIE B,.CHCRT		;At end of line??
	  IFSKP.
	    CALL CLSJF2		;Ugh.  Put away the JFN
	    SETO B,		;And see if message known to be bad.
	    CAME B,MSGDAT(M)	;Skip if known bad.
	     WARN <File has bad format: unable to find message flag field>
	  ELSE.
	    CAIE B,";"		;At start of bits?
	     LOOP.
	    PUSH P,A		;Save the core pointer
	    SUBI A,MTXPAG	;Get absolute pointer
	    TLNN A,760000	;Make sure point to correct first word
	     ADD A,[43B5+1]
	    PUSH P,A		;Save that pointer
	    ANDI A,-1
	    IDIVI A,1000	;Get page number we need
	    HRL A,MSGJF2
	    CAIL B,775		;If near end of page
	     SKIPA C,[PM%CNT+PM%WR+PM%RD+2] ;Map two pages
	      MOVX C,PM%WR!PM%RD
	    MOVE B,[.FHSLF,,WRTPGS/1000]
	    PMAP%
	    POP P,D		;Get back byte pointer
	    TXZ D,777000	;Just relative to page
	    ADDI D,WRTPGS	;Offset right
	    POP P,A		;Get back core pointer
	    MOVE B,MSGBTS(M)	;Bits to set out
	    MOVEM B,MSGFBS(M)	;Set file bits since we're changing it
	    MOVEI E,^D12	;There are twelve chars..
	    DO.
	      SETZ C,		;Compose next "digit"
	      ROTC B,3
	      ADDI C,"0"
	      IDPB C,D		;Update disk file
	      SOJG E,TOP.
	    ENDDO.
	    SETO A,
	    MOVE B,[.FHSLF,,WRTPGS/1000]
	    MOVE C,[PM%CNT+2]
	    PMAP%		;Unmap the pages
	    CALL CLSJF2		;Close up the file
	  ENDIF.
	ENDDO.
	OKINT			;Reenable interrupts
	RET
;;; Here to close out writable version of msg file
CLSJF2:	HRLZ A,MSGJF2		;JFN,,first file page
	MOVEI B,777		;Update all pages
	UFPGS%
	 JWARN <File update failed>
	HRRZ A,MSGJF2
	TXO A,CO%NRJ		;Keep this JFN around
	CLOSF%
	 ERJMP .+1
	HRRZ A,MSGJF2
;	CALLRET SETREF		;Set read date to now

SETREF:	JXN F,F%RONL,R		;Never set reference date if read-only
	MOVE C,A		;Save JFN
	GTAD%			;Set read date to now 
	EXCH C,A		;Get back JFN
	HRLI A,.FBREF
	SETO B,			;Cause we are going to reparse
	CHFDB%
	 ERJMP .+1		;Maybe no access, don't worry
	HRRZS A			;Flush the LH to purify JFN value
	RET			;Done
GETJF2:	JXN F,F%RONL,R		;Don't open second handle if read-only
	SKIPLE A,MSGJF2		;Have one already?
	IFSKP.
	  HRROI A,FILNAM	;No, make a new one
	  MOVE B,MSGJFN		;One we do have
	  MOVE C,[111110,,JS%PAF]
	  JFNS%
	  MOVX A,GJ%OLD!GJ%SHT!GJ%ACC
	  HRROI B,FILNAM
	  GTJFN%
	   JERROR <Can't get second handle on file>
	  MOVEM A,MSGJF2	;Save JFN
	ENDIF.
	MOVE B,[<FLD 7,OF%BSZ>!OF%RD!OF%WR!OF%DUD] ;Open for write
		; No DDMP dribble so that the disk copy isn't left in
		; an inconsistant state
	OPENF%			;(Now write-locked against new msgs).
	IFJER.
	  CAIE A,OPNX9		;File busy?
	  IFSKP.
	    MOVX A,^D2000	;Wait 2 seconds and try again
	    DISMS%
	    JRST GETJF2
	  ENDIF.
	  MOVE A,MSGJF2
	  JWARN <Can't open "%1J" for write>
	  RET
	ENDIF.
	CALL CHECK1		;File size change? (dates changed)
	IFSKP.
	  CALL CLSJF2		;Close our write JFN
	  CALL CHECKS		;Update new msgs
	  JRST GETJF2		;And try again
	ENDIF.
	MOVE A,MSGJF2		;Return value
	RETSKP
	SUBTTL Message sequence subroutines

;All messages = 1:n
STQALL:	MOVE A,[BYTE (12) 7777,0,6000]
	MOVEM A,MSGSEQ
	SETOM MSGSEQ+1
	CALLRET GTSQNS

;Previous-sequence
STQPRV:	LDB A,[POINT 12,WRKSEQ,23] ;Was there a previous sequence?
	CAIN A,7777
	 ERROR <No previous sequence>
	MOVE A,[WRKSEQ,,MSGSEQ]	;Copy previous working sequence
	BLT A,MSGSEQ+<NMSGS/3-1>
	CALLRET GTSQNS		;Handle like numeric sequence

STQUND:	SKIPA A,[NXTUND]	;Undeleted
STQDEL:	 MOVEI A,NXTDEL		;Deleted
STQDL0:	MOVEM A,NXTMSD
	RET

; Headers only or separate pages when listing msgs
STQSEE:	SKIPA A,[NXTSEE]	;Seen
STQUNS:	 MOVEI A,NXTUNS		;Unseen
	CALLRET STQDL0

STQREC:	SKIPA A,[NXTREC]	;Recent
STQNEW:	 MOVEI A,NXTNEW		;New
	CALLRET STQDL0

STQFLG:	SKIPA A,[NXTFLG]	;Flagged
STQUNF:	 MOVEI A,NXTUNF		;Unflagged
	CALLRET STQDL0

STQANS:	SKIPA A,[NXTANS]	;Answered
STQUNA:	 MOVEI A,NXTUNA		;Unanswered
	CALLRET STQDL0

;Sequences which are really flag setting commands
STQREV:	MOVNS MSCANF		;Invert scan direction
	SETZM NXTMSD		;No need to qualify each msg
	RET
STQLST:	NOISE (NUMBER OF MESSAGES) ;Last n
	MOVEI B,[FLDDB. .CMNUM,,^D10,,1]
	CALL CMDFLD		;Get a number
	JUMPLE B,BADNUM		;Must be positive number
	CAIN B,1		;Just one?
	 JRST STQLS1		;Last one message
	MOVE C,LASTM
	IDIVI C,MSGLEN
	SUBM C,B		;Starting message of sequence
	AOJL B,BADNUM		;Number out of range
	CALL GTSQLC		;Put that in as the start
	MOVEI B,2000(C)		;Last message as end of sequence
	CALLRET STQLS2		;And go handle that sequence

STQCUR:	SKIPGE B,M		;Current message
	 MOVE B,PRIORM		;No valid current, try prior current
	JUMPL B,[ERROR <No current message>]
	SKIPA
STQLS1:	 MOVE B,LASTM		;Just last message
	IDIVI B,MSGLEN
STQLS2:	CALL GTSQLC		;Save on list
	CALLRET GTSQNR		;Done with list

STQTO:	SKIPA A,[NXTTO]		;Match to string
STQFRM:	 MOVEI A,NXTFRM		;Match from string
	CALLRET STQSB0		;Common routine to get pattern

STQFMM:	MOVEI A,NXTFRM		;Match "from me" string
	JRST STQCC0

STQTOM:	SKIPA A,[NXTTOM]	;Match "to me" string
STQCCM:	 MOVEI A,NXTCCM		;Match "cc me" string
STQCC0:	MOVEM A,NXTMSD
	HRROI B,MAUSRS		;Use my alias string
	CALLRET STQSB2		;Install pattern

STQTXT:	SKIPA A,[NXTTXT]	;Match text substring
STQSBJ:	 MOVEI A,NXTSBJ		;Match subject string
STQSB0:	MOVEM A,NXTMSD
	NOISE (STRING)
	MOVEI B,[FLDDB. .CMQST,,,,,<[FLDDB. .CMTXT]>]
	CALL CMDFLD		;Read quoted string, or text line
	HRROI B,STRBUF		;Copy string to pattern buffer
STQSB2:	HRRO A,PATFRE
	HRRZM A,NXTPAT		;Save ptr to start
	SETZ C,
	SOUT%
	TLNN A,760000		;Final null in next word?
	 ADDI A,1		;Yes, skip over it
	MOVEI A,1(A)
	MOVEM A,PATFRE		;Update free ptr
	CAIL A,<CSBUF+CSBFSZ>	;Overflow?
	 ERROR <Pattern string space overflow>
	RET

; Discriminate by msg size
STQSHT:	SKIPA A,[NXTSHT]	;"Shorter than" spec
STQLNG:	 MOVEI A,NXTLNG		;"Longer than" spec
	MOVEM A,NXTMSD		;Save the processing routine
	HRROI A,STRBUF		;Set up default number string
	MOVE B,DFSHML
	MOVEI C,^D10
	NOUT%
	 ERJMP BADNUM
	NOISE (THAN NUMBER OF CHARACTERS)
	MOVEI B,[FLDDF. .CMNUM,,^D10,,STRBUF]
	CALL CMDFLD		;Get a number
	JUMPL B,BADNUM		;Must be positive number
	MOVEM B,NXTIME		;Borrow time cell for length
	RET

	PURGE FLDDF.		;Last occurance in MM
STQBEF:	SKIPA A,[NXTBEF]	;Before date
STQAFT:	 MOVEI A,NXTAFT		;After date
	CALLRET STQON1

STQON:	MOVEI A,NXTON		;On date
STQON1:	MOVEM A,NXTMSD
	NOISE (DATE)
	CALL GETDAT
	MOVEM B,NXTIME
	RET

STQKYW:	SKIPA A,[NXTKEY]	;Keyword
STQUKW:	 MOVEI A,NXTUNK		;Unkeyword
	MOVEM A,NXTMSD
	CALL GETKEY
	MOVEM U,KEYBTS		;Save keyflag bits to hunt for
	MOVEM V,KEYLPF		;and keyword list
	RET

;;;Get sequence, default to current message

DFSQTH:	SKIPA A,[[ASCIZ/CURRENT/]] ;Setup default number to this message
DFSQNW:	 MOVEI A,[ASCIZ/UNSEEN/] ;Default to unseen
	CALLRET DFSQA1

DFSQRC:	SKIPA A,[[ASCIZ/RECENT/]] ;Default to recent
DFSQAL:	 MOVEI A,[ASCIZ/ALL/]	;Default to all messages
DFSQA1:	SKIPG MSGJFN		;Must have a file
	 ERROR <No current file>
	UDEF (A)		;This is the default
;	CALLRET GETSEQ
;;;Message sequence handler
; Flags:
;   F%F1 on	Subcommands being entered on separate lines
;   F%F2 on	Current line had a command
;   F%F3 on	Negation in progress [hook only for now]
;   F%F4 on	Negation just seen   [hook only for now]

GETSEQ:	NOISE (MESSAGES)
	SETABT CMDABO		;Allow abort out of sequence type-in
	CALL ABNOFF		;Don't CTRL/N out until subcommand level
	TXZ F,F%F1!F%F2!F%F3!F%F4!F%TYPS ;Default don't type sequence #'s
	SETOB E,LSTMSG
	MOVE A,CMDRET		;Get caller's CMDRET
	MOVEM A,SEQCAL		;Save it in case subcommands change it
	SETZB A,CMDSTK		;No subcommands yet
	MOVE L,[POINT 12,MSGSEQ,11] ;Pointer to where to store messages
	CALL STQALL		;Assume all msgs will be considered
	CALL PSHCMD		;NXTSEQ should always be the first function!!!
	MOVMS MSCANF		;Assume forward scan
	MOVEI A,PATSTR		;Init pattern string space
	MOVEM A,PATFRE
	PUSH P,M		;Place for msg ptr at line start
	PUSH P,L		;Place for seq ptr at line start
	PUSH P,MSCANF		;Place for MSCANF at line start
	PUSH P,CMDSTK		;Place for CMDSTK at line start
	MOVEM P,SAVP		;Save the main stack ptr
	MOVE A,[FLDDB. .CMCMA,CM%SDH,,,,GTNBK3]
	MOVEM A,CMDFLB
	UHELP [ASCIZ/"," to enter message-sequence subcommand mode/]
	MOVX B,CM%DPP
	SKIPE A,CMDFLB+.CMDEF	;Default provided?
	 IORM B,CMDFLB+.CMFNP	;Yes, say there is one
	HRRZM A,GTSQDF		;Remember default (if any)
	MOVEI B,CMDFLB		;Keep default if any
	CALL $COMND		;Parse it with comma possible
	IFXN. A,CM%NOP		;Did it win?
	  MOVEI B,GTNBK5	;No, parse it again to get good error
	  CALL CMDFLD
	  FATAL <Impossible non-error after error in GETSEQ>
	ENDIF.
	LOAD A,CM%FNC,(C)	;Get field type
	CAIE A,.CMCMA		;Comma?
	IFSKP.
	  TXO F,F%F1		;Yes, flag start of subcommands
	  CONFRM		;Better be end of line
	  MOVEI A,GETSQR	;Go here on command error
	  HRRM A,CMDRET		;Set as error return
	  CALL ABNON		;Allow abort out of sequence type-in
	  JRST GETSQ3
	ENDIF.
	JRST GETSQ2
GETSQ0:	TXZ F,F%F3!F%F4		;Reset negation flags
GETSQ1:	MOVE A,[FLDDB. .CMCFM,,,,,GTNBK3]
	MOVEM A,CMDFLB
	SKIPLE A,GTSQDF		;Is there (still) a default?
	 UDEF (A)		;Yes, set it up
	MOVEI B,CMDFLB
	CALL CMDFLD		;Parse the field
	LOAD A,CM%FNC,(C)	;Get field type
GETSQ2:	CAIE A,.CMTOK		;Token?
	IFSKP.
	  CALL GETSQT		;Yes, parse token
	  CALL PSHCMD		;Put command on stack
	  JXN F,F%F1,GETSQ3	;Subcommands on separate lines?
	  JRST GETSQ5		;No, all done then
	ENDIF.
	CAIE A,.CMNUM		;Number?
	IFSKP.
	  CALL GETSQN		;Yes, collect sequence
	  CALL PSHCMD		;Put command on stack
	  JXN F,F%F1,GETSQ3	;Subcommands on separate lines?
	  JRST GETSQ5		;No, all done then
	ENDIF.
	CAIE A,.CMCFM		;Is it the end?
	IFSKP.
	  SKIPE CMDSTK		;Any prior commands?
	  IFSKP.
	    CALL STQCUR		;Default use current msg
	    CALL PSHCMD		;Install it
	    JRST GETSQ5		;And finish up
	  ENDIF.
	  TXNE F,F%F1		;Subcommands on separate lines?
	   TXZN F,F%F2		;Yes, any on this line?
	    JRST GETSQ5		;No, finish up
	  JRST GETSQ3		;Get some more
	ENDIF.
	HRRZ A,(B)		;No, get routine addrs
	CALL (A)		;Go there and return
	TXZE F,F%F4		;Negation just set?
	 JRST GETSQ1		;Yes, do rest of command
	CALL PSHCMD		;Stack this command
	JRST GETSQ0		;Get next subcommand
; Here to begin a new line
GETSQ3:	MOVEM M,-3(P)		;Save msg ptr for next line
	MOVEM L,-2(P)		;Save seq ptr for next line
	MOVE A,MSCANF		;Save scan dir for next line
	MOVEM A,-1(P)
	MOVE A,CMDSTK		;Save cmd ptr for next line
	MOVEM A,0(P)

GETSQI:	MOVSI A,MSPRMT		;Reinit COMND parser
	CALL CMDIN1		;Reinit block
; We come here to reparse the input if necessary!
GETSQ4:	MOVE P,SAVP		;Reset the main stack
	MOVE A,0(P)		;Reset the command stack
	MOVEM A,CMDSTK
	MOVE A,-1(P)		;Reset the scan direction
	MOVEM A,MSCANF
	MOVE L,-2(P)		;Reset the seq ptr
	MOVE M,-3(P)		;Reset the msg ptr
	TXZ F,F%F2		;No commands yet on this line
	JRST GETSQ0

; Here to finish preparation of a sequence subcommand stack
GETSQ5:	MOVE A,SEQCAL		;Restore caller's CMDRET
	MOVEM A,CMDRET
	HLRE A,CMDSTK		;Compute number of entries
	ADDI A,NCPDL
	MOVNS A
	HRLI A,CMPDL
	MOVSM A,CMDSTK		;Save it
	MOVE C,[POINT 12,MSGSEQ,23] ;Begin looking at this sequence first
	MOVEM C,MSGSPT		;Save initial sequence pointer
	SKIPL C,MSCANF		;Done if forward scan
	IFSKP.
	  DO.
	    ILDB A,MSGSPT	;Else, find end of sequence list
	    CAIE A,7777
	     LOOP.
	  ENDDO.
	  ADJBP C,MSGSPT	;Back up to last msg index
	  MOVEM C,MSGSPT
	ENDIF.
	SETOM WRKMSG		;Say sequence hasn't begun yet!
	SETOM MSRNG		;Say no range in progress
	MOVE L,[POINT 12,WRKSEQ,11] ;Init ptr to working sequence
	ADJSP P,-4		;Reset stack ptr
	RET

; Here command entry error
GETSQR:	MOVE P,SAVP		;Reset main stack
	JRST GETSQI		;Continue at subcommand level
; Routine to put a new command frame on the sequence subcommand stack
; Entry:   CMDSTK = current subcommand stack ptr
;	   NXTMSD = latest subcommand dispatch
;	   NXTPAT = string arg adr for subcommand
;	   NXTIME = Time argument for SINCE, BEFORE, AFTER, etc.
;	   KEYBTS = keyword bits argument for keyword subcommand
;	   KEYLPM = keyword modify list
; Call:    CALL PSHCMD
; Return:  +1
PSHCMD:	TXO F,F%F2		;Note at least 1 cmd this line
	SKIPG NXTMSD		;Is there any search routine?
	 RET			;No, probably a flag setting command
	SKIPL B,CMDSTK		;Stack started?
	 MOVE B,[IOWD NCPDL,CMPDL] ;No, init command stack
	MOVX A,F%F3		;Negation command?
	TXZE F,F%F3
	 IORM A,NXTMSD		;Yes, note it
	HRRZ A,NXTMSD		;Check the func
	CAIN A,NXTSEQ		;Numerical sequence given?
	 JRST PSHCM1		;Yes, handle specially
	SETOM GTSQDF		;Cancel any default
PSHCM0:	PUSH B,NXTMSD		;Save search routine
	PUSH B,NXTPAT		;and string pattern adr
	PUSH B,NXTIME		;Save time argument
	PUSH B,KEYBTS		;Save keyword bits
	PUSH B,KEYLPM		;And modify keywords list
	MOVEM B,CMDSTK		;Save the new ptr
	RET

PSHCM1:	SKIPL CMDSTK		;Is the numerical sequence on the stack?
	 JRST PSHCM0		;No, simply put it there (it will be first!)
	SETOM GTSQDF		;Cancel any default
	MOVE A,NXTMSD		;Reset search routine entry on stack
	MOVEM A,CMPDL		; (it may change the negation flag)
	RET			;The other entries don't matter for this
;;;Token - check for % or . and supply number
GETSQT:	MOVEI B,4000		;Special number meaning "last msg"
	LDB A,[POINT 7,STRBUF,6] ;Get token character
	CAIE A,"."		;. = current message
	 JRST GETST1
	SKIPGE B,M		;Current message
	 MOVE B,PRIORM		;No valid current, try prior current
	JUMPL B,[ERROR <No current message>]
	IDIVI B,MSGLEN
	JRST GETST1

;;;Number parsed - handle n:m n,m or n alone

GETSQN:	JUMPE B,BADNUM		;Range error
	SOJL B,BADNUM
	MOVE C,LASTM
	IDIVI C,MSGLEN
	CAILE B,(C)		;His number > last message?
	 JRST BADNUM
GETST1:	JUMPGE E,GTSQ2N		;2nd in series n:m
	CALL GTSQLC		;Save number on list
	MOVEI B,GTNBK1		;Now try for <cr> ! , ! : ! #
GTSQNF:	CALL CMDFLD
	LOAD A,CM%FNC,(C)	;Get fcn parsed
	CAIN A,.CMCFM		;EOL?
	 JRST GTSQNR		;Yes - done
	CAIE A,.CMCMA		;Comma?
	 LDB E,[POINT 7,STRBUF,6] ;No, get token for later guidance
	MOVEI B,GTNBK4		;Yes - try for <number> ! . ! %
	CALL CMDFLD
	LOAD A,CM%FNC,(C)	;Get fcn parsed
	CAIN A,.CMCFM		;EOL?
	 JRST GTSQNR		;Yes - done
	CAIE A,.CMNUM		;Number?
	IFSKP.
	  LDB A,L		;Get first number
	  CAIN E,"#"		;Are we handling a msg set?
	   ADDI B,(A)		;Yes, second number is n+m-1
	  JRST GETSQN		;Yes - handle
	ENDIF.
	CALLRET GETSQT		;Handle token

GTSQLC:	CAMN L,[POINT 12,MSGSQZ-1,23] ;Reached end of list?
	 ERROR <Too many messages in list>
	IDPB B,L		;Save number in list
	SETOM NXTMSD		;Flag previous sequence clobbered
	RET

;;;2nd in range seen - fill list
GTSQ2N:	TRO B,2000		;Mark as end of range
	CALL GTSQLC		;Save in table
;	CALLRET GTSQNC		;Go try for more

GTSQNC:	SETO E,			;Say looking for 1st number of pair
	MOVEI B,GTNBK2		;Try for <cr> ! ,
	CALLRET GTSQNF

;;;EOL seen, wrapup numbers

GTSQNR:	MOVEI B,7777		;Mark end of list
	IDPB B,L
GTSQNS:	MOVE L,[POINT 12,MSGSEQ,11] ;Reset list
	MOVEI A,NXTSEQ		;Numeric sequence is basis
	MOVEM A,NXTMSD		;Setup as dispatch
	SETZM NXTPAT		;Init storage for seq ptr
	RET			;Return
GTNBK1:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/:/]>,<":" to specify a message range>,,GTNB11
GTNB11:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/#/]>,<"#" to specify a message set>,,GTNBK2
GTNBK2:	FLDDB. .CMCMA,CM%SDH,,<"," to specify another message number>,,CNFCMD

GTNBK3:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/%/]>,<"%" to specify the last message>,,GTNB31
GTNB31:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/./]>,<"." to specify the current message>,,GTNB32
GTNB32:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/*/]>,<"*" to specify the last message>,,GTNB33
GTNB33:	FLDDB. .CMNUM,CM%SDH,^D10,<a single message number
  or range of message numbers n:m
  or set of message numbers n#m (m messages beginning with n)>,,GTNBK5
GTNBK5:	FLDDB. .CMKEY,,SQCMTB,<message sequence,>

;;;Same as GTNBK3, but without the SQCMTB table keywords.  It has to be done
;;;this way because keywords have to be parsed after tokens if a keyword is
;;;a default, otherwise the default keyword will be taken if a token is input.
GTNBK4:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/%/]>,<"%" to specify the last message>,,GTNB41
GTNB41:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/./]>,<"." to specify the current message>,,GTNB42
GTNB42:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/*/]>,<"*" to specify the last message>,,GTNB43
GTNB43:	FLDDB. .CMNUM,CM%SDH,^D10,<a single message number
  or range of message numbers n:m
  or set of message numbers n#m (m messages beginning with n)>
;;;Get the next message in this sequence and maybe type out its number

NXTMSG:	SKIPL A,CMDSTK		;Anything on command stack?
	 JRST NXTMS6		;No, just quit
NXTMSL:	MOVEM A,WCMDPT		;Save working ptr
	MOVE B,0(A)		;Get command fct
	MOVEM B,NXTMSD
	MOVE B,1(A)		;And any pattern string
	MOVEM B,NXTPAT
	MOVE B,2(A)		;Time argument
	MOVEM B,NXTIME
	MOVE B,3(A)		;Keyflag bits
	MOVEM B,KEYBTS
	MOVE B,4(A)		;Keyword string arguments
	MOVEM B,KEYLPM
	CALL @NXTMSD		;Check out the next msg
	 JRST NXTMSG		;No go, step to next msg
	MOVE A,WCMDPT		;Try for next command frame
	ADJSP A,4
	AOBJN A,NXTMSL
NXTMS0:	AOS (P)			;Found one, set to skip return
	HRRZ M,WRKMSG		;Get msg index
	HLRZ A,WRKMSG		; and msg number
	JUMPL L,NXTMS1		;Always start new when beginning
	LDB B,L			;Get last one out
	TRZN B,2000		;Already a range?
	 HRROS B		;No, must use next slot
	CAIN A,1(B)		;Next in numeric order?
	 TROA A,2000		;Yes, construct a range
	  CAIA
	   JUMPGE B,[DPB A,L	;Put it in place
		     JRST NXTMS2]
NXTMS1:	IDPB A,L		;Use next slot
NXTMS2:	TXNN F,F%TAK		;Don't type out sequences in a TAKE
	 TXNN F,F%TYPS		;Want to type out numbers?
	  RET			;No, all done
	SKIPGE A,LSTMSG		;Any last message?
	 JRST NXTMS5		;No, install this one then
	CAIN M,MSGLEN(A)	;Yes, is this one the next one?
	 JRST NXTMS4		;Yes, keep accumulating
	CALL PRTSEQ		;Print what is there now otherwise
NXTMS3:	HRLM M,LSTMSG		;And set ourselves up as start
NXTMS4:	HRRM M,LSTMSG		;Set ourselves up as next link in chain
	RET

NXTMS5:	TXZ F,F%COMA		;Reset comma flag
	JRST NXTMS3

NXTMS6:	MOVEI B,7777
	IDPB B,L		;Mark end of sequence
	TXNE F,F%TAK		;Don't type sequence if TAKE file
	 RET
	TXNE F,F%TYPS		;Finishing up, type last number?
	 SKIPGE LSTMSG		;And have non-empty sequence
	  RET			;No, done
PRTSEQ:	TXOE F,F%COMA		;Maybe a comma first
	 PRINT ","
	PRINT .CHSPC
	MOVX A,.PRIOU
	HLRZ T,LSTMSG		;Get start of sequence
	MOVEI B,MSGLEN(T)
	IDIVI B,MSGLEN
	MOVEI C,^D10
	NOUT%
	 NOP
	HRRZ B,LSTMSG		;Get end
	CAIN B,(T)		;Same?
	 RET			;Yes, that's it
	PRINT ":"
	ADDI B,MSGLEN
	IDIVI B,MSGLEN
	MOVEI C,^D10
	NOUT%
	 NOP
	RET
;;;Get next message selecting routines

NXTSQ0:	SETOM MSRNG		;Say not stepping range
NXTSEQ:	MOVE B,LASTM		;Determine number of last msg
	IDIVI B,MSGLEN		; ..
	SKIPL MSRNG		;Are we in a range?
	 JRST NXTSQR		;Yes, special handling
	LDB A,MSGSPT		;Get the next msg to do
	CAIE A,7777		;Reached the end of the sequence?
	IFSKP.
	  HLRZS CMDSTK		;Yes, save command stack size while
	  RET			; causing NXTMSG to terminate!
	ENDIF.
NXTSQ2:	MOVE C,MSCANF		;Advance the sequence pointer
	ADJBP C,MSGSPT
	MOVEM C,MSGSPT
	TRZE A,2000		;Is this the end of a range?
	 JRST NXTSQ4		;Yes, handle
	CAMLE A,B		;Special check for "last msg" number
	 MOVE A,B		; ..
NXTSQ1:	HRLZM A,WRKMSG		;Return msg number
	IMULI A,MSGLEN
	HRRM A,WRKMSG		;Return msg index
	RETSKP			;Say there is another msg

NXTSQ4:	MOVEM A,MSRNG		;Remember end of (forward) scan
	SKIPL MSCANF		;Backward scan?
	 JRST NXTSQR		;No, step forward
	LDB C,C			;Yes, get beginning of range
	MOVEM C,MSRNG		;Remember when range ends!
	JRST NXTSQ2

NXTSQR:	HLRZ A,WRKMSG		;Get number of previous msg
	CAMN A,MSRNG		;Are we at boundary of range?
	 JRST NXTSQ0		;Yes, done with range
	CAMG A,MSRNG		;Compare current with range
	 AOSA A			;Current less than range, increment
	  SOJL A,NXTMS0		;Current greater than range, decrement
	CAMLE A,B		;Paranoia: is sequence still valid?
	 JRST NXTSQ0		;No, too high
	JRST NXTSQ1		;Yes, use this msg number

NXTANS:	SKIPA B,[M%RPLY]	;Answered
NXTSEE:	 MOVEI B,M%SEEN		;Seen
	CALLRET NXTDL0

NXTFLG:	SKIPA B,[M%ATTN]	;Flagged
NXTDEL:	 MOVEI B,M%DELE		;Deleted
NXTDL0:	SKIPA C,[TDNE B,MSGBTS(A)] ;Bit must be set
NXTREC:	 MOVE C,[SKIPGE MSGFLG(A)] ;Recent
	CALLRET NXTAL0

NXTUNA:	SKIPA B,[M%RPLY]	;Unanswered
NXTUNF:	 MOVEI B,M%ATTN		;Unflagged
	CALLRET NXTUD0

NXTUNS:	SKIPA B,[M%SEEN]	;Unseen
NXTUND:	 MOVEI B,M%DELE		;Undeleted
NXTUD0:	MOVE C,[TDNN B,MSGBTS(A)] ;Bit must be clear
NXTAL0:	HRRZ A,WRKMSG		;Start here
	XCT C			;Test it out
	 RETSKP			;Matches
	RET			;No go

NXTNEW:	HRRZ A,WRKMSG		;New
	MOVEI B,M%SEEN
	SKIPGE MSGFLG(A)	;New are recent
	 TDNE B,MSGBTS(A)	; & unseen messages
	  CAIA
	   RETSKP
	RET			;No go

NXTKEY:	MOVE C,[CALL NXTKY0]
	CALLRET NXTAL0
   ;Enter here to trigger if keyflag or keyword found
NXTKY0:	MOVE B,KEYBTS
	TDNE B,MSGBTS(A)	;Keyflag found?
	 RET			;Yes, take no-skip win return.
	CALL NXTKW		;Hmm, try looking for keyword.
	 RETSKP			;Failed, take skip return.
	RET			;Won.

NXTUNK:	MOVE C,[CALL NXUNK1]
	CALLRET NXTAL0

   ;Enter here to trigger if keyflag or keyword NOT found
NXUNK1:	MOVE B,KEYBTS
	TDNE B,MSGBTS(A)	;Keyflag there?
	 RETSKP			;Yes, so take skip loss return.
				;No, fall thru to see if keyword there.

NXTKW:	PUSH P,M		;Save current-msg ptr
	MOVEI M,(A)
	SKIPE A,KEYLPF		;Search with given keyword list
	 CALL KWFND
	CAIE A,			;Failed to find keyword?
	 AOS -1(P)		;Found it!  Take skip return
	MOVEI A,(M)		;Restore A
	POP P,M			;and current msg.
	RET

; Discriminate msgs by length
NXTSHT:	SKIPA C,[CAMG B,NXTIME]	;Shorter than limit
NXTLNG:	 MOVE C,[CAML B,NXTIME]	;Longer than limit
	HRRZ A,WRKMSG		;Msg to check
	HLRZ B,MSGBOD(A)	;b := its body length
	XCT C
	 RETSKP			;OK, use it
	RET			;No go
;;;Find substring in From field

NXTSBJ:	SKIPA C,[CALL SBJSTR]	;Routine to match Subject string
NXTFRM:	MOVE C,[CALL FRMSTR]	;Routine to match From string
	CALLRET NXTAL0		;Use common loop

NXTTO:	SKIPA C,[CALL TCCSTR]	;Routine to match To/Cc string
NXTTXT:	 MOVE C,[CALL TXTSTR]	;Routine to match text substring
	CALLRET NXTAL0

NXTTOM:	SKIPA C,[CALL TOSTR]	;Routine to match To string
NXTCCM:	 MOVE C,[CALL CCSTR]	;Routine to match Cc string
	CALLRET NXTAL0

FRMSTR:	HRRZ V,MSGFRM(A)	;From field for this message
	HLRZ W,MSGFRM(A)
FRMST2:	SAVEAC <A,C,M>
	HRRZM A,M		;Setup this temporarily so search works
	HRRZ T,NXTPAT		;String to match
	CALL SEARCH		;Look for string
	 RETSKP			;Didn't find it, skip return
	RET

SBJSTR:	HRRZ V,MSGSUB(A)	;Subject field for this message
	HLRZ W,MSGSUB(A)
	CALLRET FRMST2

TXTSTR:	HRRZ V,MSGBOD(A)
	HLRZ W,MSGBOD(A)
	CALLRET FRMST2
;;;Match a To: or cc:
TCCSTR:	CALL TOSTR		;Check To-list
	 RET			;Won
	CALLRET CCSTR		;To-list failed, try cc list

TOSTR:	SAVEAC <A,C,M>		;Messages with string in to field
	STKVAR <TOTMPA,TOTMPM>
	MOVEM A,TOTMPA
	MOVEM M,TOTMPM
	MOVEI M,(A)		;Temporarily point to right message
	MOVEI T,[ASCIZ/
To:/]
	CALL FNDHDR
	 RETSKP			;Didn't find it, skip return
	MOVE M,TOTMPM
	TDZA W,W
TOSTR1:	 ADDI W,2		;Count the crlf too
	CALL CNTHDL		;Count characters in this line
	IBP A			;Skip LF too
	ILDB T,A
	CAIE T,.CHTAB
	 CAIN T,.CHSPC		;Continuation line?
	  AOJA W,TOSTR1		;Yes, get some more
	CAIE T,"T"
	 CAIN T,"t"
	IFNSK.
	  ILDB T,A		;Looking for TO:
	  CAIE T,"O"
	   CAIN T,"o"
	ANNSK.
	  ILDB T,A
	  CAIE T,":"
	ANSKP.
	  ADDI W,3		;Count TO: itself
	  JRST TOSTR1
	ENDIF.
	MOVE A,TOTMPA
	HRRZM A,M		;Setup this temporarily so search works
	HRRZ T,NXTPAT		;String to match
	CALL SEARCH		;Look for string
	 RETSKP			;Didn't find it, skip return
	RET

	ENDSV.

CCSTR:	SAVEAC <A,C,M>		;Messages with string in CC field
	STKVAR <CCTMPA,CCTMPM>
	MOVEM A,CCTMPA
	MOVEM M,CCTMPM
	MOVEI M,(A)		;Temporarily point to right message
	MOVEI T,[ASCIZ/
cc:/]
	CALL FNDHDR
	 RETSKP			;Didn't find it, skip return
	MOVE M,CCTMPM
	TDZA W,W
CCSTR1:	 ADDI W,2		;Count the crlf too
	CALL CNTHDL		;Count characters in this line
	IBP A			;Skip LF too
	ILDB T,A
	CAIE T,.CHTAB
	 CAIN T,.CHSPC		;Continuation line?
	  AOJA W,CCSTR1		;Yes, get some more
	CAIE T,"C"
	 CAIN T,"c"
	IFNSK.
	  ILDB T,A		;Looking for cc:
	  CAIE T,"C"
	   CAIN T,"c"
	ANNSK.
	  ILDB T,A
	  CAIE T,":"
	ANSKP.
	  ADDI W,3		;Count TO: itself
	  JRST CCSTR1
	ENDIF.
	MOVE A,CCTMPA
	HRRZM A,M		;Setup this temporarily so search works
	HRRZ T,NXTPAT		;String to match
	CALL SEARCH		;Look for string
	 RETSKP			;Didn't find it, skip return
	RET
NXTBEF:	SKIPA C,[CAMLE B,MSGDAT(A)] ;Before date
NXTAFT:	 MOVE C,[CAMG B,MSGDAT(A)] ;After date
	MOVE B,NXTIME
	CALLRET NXTAL0

NXTON:	MOVE C,[CALL NXTON1]	;On date
	CALLRET NXTAL0

NXTON1:	MOVE B,MSGDAT(A)
	SUB B,NXTIME
	TLNE B,-1		;More than a day's difference?
	 AOS (P)		;Yes, fail
	RET
	SUBTTL Sending subroutines

SNDINI:	CALL SNDIN0
PRSCCL:	SKIPN DEFCCL		;Any default cc list?
	 JRST PRSCC0		;No
	MOVE A,[POINT 7,DEFCCL]
	SETZ E,
	TXO F,F%CC		;As cc recipients
	TXZ F,F%F4
	CALL PRADDR		;Process default CC list
	MOVEI T,CCLIST		;Set up CC list
	CALL ADDTO0		;Go add whole bunch to list then
PRSCC0:	SKIPN DEFBCL		;Any default bcc list?
	 RET			;No
	MOVE A,[POINT 7,DEFBCL]
	SETZ E,
	TXO F,F%CC		;As cc recipients
	TXZ F,F%F4
	CALL PRADDR		;Process default BCC list
	MOVEI T,BCCLST		;Set up BCC list
	CALLRET ADDTO0		;Go add whole bunch to list then

;;; Version of SNDINI that does not parse DEFCCL
SNDIN0:	SETOM M.RPLY		;Assume not a reply to anyone
	CALL .ERSAL		;Go erase everything
	SETZM RMLPTR		;Not remail yet
	SETZM FRMSCM		;Assume from user
	MOVE A,[POINT 7,FRMSCM]
	HRROI B,FRMSAM
	SKIPE FRMSAM		;Unless
	 CALL MOVST0		; the user requested something else!
	SETZM REPSCM		;Assume reply to user
	MOVE A,[POINT 7,REPSCM]
	HRROI B,REPSAM
	SKIPE REPSAM		;Unless
	 CALL MOVST0		; the user requested something else!
	RET

PRADDT:	TXZ F,F%F4		;Barf on errors
PRADT1:	CALL PRADDR		;Process list
	CALLRET ADDTO		;Go add whole bunch to list then
;;;Look up a host name with byte pointer A and return the address of its
;;; canonical name string in A.  Skips if name found

HSTNAM:	SAVEAC <B,C,D>
	STKVAR <HSTPTR>
	MOVEM A,HSTPTR
	MOVEI A,HSTTAB		;See if in cache already
	MOVE B,HSTPTR
	TBLUK%
	IFXN. B,TL%EXM		;Already in the table?
	  HLRZ A,(A)		;Great, get the string address
	  RETSKP		;Return success
	ENDIF.
	MOVE A,HSTPTR
	HRRO B,HCSHFF		;Store name in free area in host cache
	SETZ C,			;Use any protocol, don't care about address
IFN NICSW,<
	IFXE. F,F%VRFY
	 EXCH A,B
	 HRLI A,440700
	 CALL MOVST0		;canonical name is same as given us
	 SETZ B,		;unknown host address
	 JRST HSTNMX		;and success!
	ELSE.
	 CALL $GTCAN		;Canonicalize the name
	ENDIF.
>;IFN NICSW
	IFNSK.
	  SKIPE DOMTBL		;Failed, see if pseudo-domains are initialized
	  IFSKP.
	    MOVEI A,ALCBLK	;No, do so.  Routine to assign memory
	    SETZ B,		;Say don't bother making relay lists
	    CALL $INRLY
	    MOVEM A,DOMTBL	;Save fact that we are initialized
	  ENDIF.
	  MOVE A,HSTPTR		;Get back A
	  CALL $GTRLY		;Try relays
	   RET
	  MOVE B,A		;Canonical name
	  HRR A,HCSHFF		;To free area
	  HRLI A,(<POINT 7,>)
	  CALL MOVST2		;Copy it
	ENDIF.
HSTNMX:	IBP A			;Make sure we include at least one null
	MOVEI D,1(A)		;Pointer to next word after name returned
	CAIL D,HSTSTR+<NHSPGS*1000>
	 FATAL <Host name cache overflowed>
	MOVEI A,HSTTAB		;See if in cache already
	HRRO B,HCSHFF
	TBLUK%
	IFXE. B,TL%EXM		;Already in the table?
	  MOVEI A,HSTTAB	;Point to the table
	  HRLZ B,HCSHFF
	  TBADD%		;Add it to table
	  MOVEM D,HCSHFF	;Update current host cache free pointer
	ENDIF.
	HLRZ A,(A)		;Get the string address
	RETSKP			;Return success	  	

	ENDSV.

;;;Routine to assign memory from free storage; presently we use the host cache.
;;;Accepts:
;;; A/	size of block to assign
;;;	CALL ALCBLK
;;;+1:	Failure
;;;+2:	Success, with:
;;; B/	address of block assigned

ALCBLK:	SAVEAC <A>
	MOVE B,HCSHFF		;Get free block from here
	ADD A,B			;First address after block
	CAIL A,HSTSTR+<NHSPGS*1000> ;Make sure it fits
	 RET			;No
	MOVEM A,HCSHFF		;Put back as next free
	RETSKP
;;;Send the current message off

SNDMS5:	CALL GETTO		;Insist upon having a to-list
SNDMSG:	STKVAR <JFNTAD,SIZE>
	SKIPE TOLIST		;Is there a to-list?
	IFSKP.
	  SKIPN A,CCLIST	;Try moving cc-list to to-list
	   JRST SNDMS5		;No recipients, demand some
	  SETZM CCLIST
	  MOVEM A,TOLIST	;Move appropriate list to to-list
	ENDIF.
	TXZ F,F%F2		;Haven't got funny SAVFIL yet
	MOVE A,TXTPTR		;Get end of message
	MOVEI B,CRLF0
	LDB C,A
	CAIN C,.CHLFD		;Unless ended with CRLF
	IFSKP.
	  CALL MOVST0		;Put one in now
	  ADD A,[7B5]		;And back over the null
	ENDIF.
	MOVEM A,TXTPTR
	LDB A,[POINT 7,MCPFIL,6] ;Is there a mail copy file?
	IFN. A			;Only do it if so
	  DMOVE A,[POINT 7,FILNAM ;Copy mail copy filename string
		   POINT 7,MCPFIL]
	  CALL MOVSTR
	  MOVEI B,[ASCIZ/;P770000;T/] ;Set protection and temporary
	  CALL MOVST0		;Complete filename string
	  MOVX A,GJ%FOU!GJ%NEW!GJ%SHT!.GJNHG
	  HRROI B,FILNAM	;Get it back
	  GTJFN%
	  IFJER.
	    HRROI B,FILNAM
	    JWARN <Can't get mail copy file "%2S">
	  ELSE.
	    MOVEM A,JFNTAD	;Save JFN in case OPENF% fails
	    DO.
	      MOVX B,<<FLD ^D7,OF%BSZ>!OF%WR>
	      OPENF%
	      IFJER.
		MOVE A,JFNTAD	;Let user try CONTINUE
		JWARN <Can't open mail copy file "%1J">
		PROMPT <Should I try again? >
		CALL YESNO	;Offer to create file for user
		 EXIT.
		CITYPE <[Type CONTINUE when ready to retry]
>
		MOVE A,JFNTAD
		HALTF%
		LOOP.
	      ENDIF.
	      HRROI B,TXTPAG
	      SETZ C,
	      SOUT%
	      TXO A,CO%NRJ
	      CLOSF%
	       NOP
	    ENDDO.
	    MOVE A,JFNTAD	;Flush saved JFN
	    RLJFN%
	     NOP		;Ignore failure
	  ENDIF.
	ENDIF.
	HRRZ B,TXTPTR		;Compute number of characters in text
	SUBI B,TXTPAG-1		;1+End addr-Start addr
	IMULI B,5		;Times 5 chars/word
	LDB C,[POINT 6,TXTPTR,5] ;Get terminating pointer offset
	IDIVI C,7		;C=# of free bytes in word
	SUBI B,(C)
	SKIPN RMLPTR		;Unless remail
	 ADDI B,9		;Count for dashes later
	MOVEM B,MSGSIZ		;Save size of text portion of message
	MOVEM B,SIZE
	GTAD%			;Get date/time now
	MOVEM A,JFNTAD		;Save for later
	SKIPN LCLIST		;Any local recipients?
	 SKIPE FILIST		;Or file recipients?
	IFSKP.
	  SKIPE SAVFIL		;Or uses SAVED.MESSAGES feature
	ANSKP.			;No, just go send network out then
	ELSE.
	  TXZ F,F%RELD		;Setup headers for local recipients
	  CALL SETHDR
	  CALL SNDLCL		;Try to send local messages
	  CALL SNDFIL		;Try to send file messages
	  SKIPL RINCME		;Special include me mode?
	  IFSKP.
	    TXZN F,F%F2		;Yes, did we see that address?
	     SETZM SAVFIL	;No, don't send any file guy then
	  ENDIF.
	  CALL FILMSG		;Store SAVED.MESSAGES
	  MOVE A,SIZE		;Restore MSGSIZ in case ever needed again
	  MOVEM A,MSGSIZ
	ENDIF.
	SKIPN NETLST		;Any network recipients?
	 RET
	MOVE A,JFNTAD		;Restore TAD
	TXO F,F%QUOT!F%RELD	;Set headers for network recipients
	CALL SETHDR
	CALL SNDNET		;Queue mail
	 CALL MAIFLG		;Queued to user directory, update flags
	CALLRET $WAKE		;Send wakeup call to MMailr

	ENDSV.
;;;Setup header of message for this kind of recipient, A/ TAD for this header

SETHDR:	STKVAR <HDRTAD>
	MOVEM A,HDRTAD		;Save date/time user wants to show
	SKIPE O,RMLPTR		;Doing remail command?
	IFSKP.
	  MOVE O,[POINT 7,HDRPAG] ;Set up header block
	  MOVE A,TXTPTR
	  MOVEI B,[ASCIZ/-------
/]
	  CALL MOVST0		;Put in dashes at end
	  MOVEI B,[ASCIZ/Date: /]
	ELSE.
	  MOVEI B,[ASCIZ/ReSent-Date: /]
	ENDIF.
	MOVE A,[IDPB A,O]	;Set up to move into memory
	MOVEM A,MOVDSP
	CALL MOVSB2
	MOVE A,O		;Current pointer
	MOVE B,HDRTAD		;User's argument
	MOVX C,OT%DAY!OT%SPA!OT%TMZ!OT%SCL!OT%822 ; RFC 822 standard date/time
	ODTIM%
	MOVE O,A		;Update header pointer
	SKIPN RMLPTR		;Doing Remail?
	IFSKP.
	  MOVEI B,[ASCIZ/
ReSent-From: /]
	  CALL MOVFRR
	  MOVEI B,[ASCIZ/
ReSent-Sender: /]
	  TXNN F,F%ALIA		;Aliased?
	   SKIPE FRMSCM		;Or is there a "From:" other than user?
	    CALL MOVSRR		;Yes, output sender
	  MOVEI T,[ASCIZ/
ReSent-To: /]
	  CALL MOVTRM
	  MOVEI B,[ASCIZ/
ReSent-/]
	  CALL MOVSB2
	ELSE.
	  CALL MOVFRM		;Output From
	  CALL MOVSUB		;Insert subject
	  TXNN F,F%ALIA		;Aliased?
	   SKIPE FRMSCM		;Or is there a "From:" other than user?
	    CALL MOVSDR		;Yes, output sender
	  CALL MOVTO		;And To
	  CALL MOVCC		;And cc
	  CALL MOVREP		;And Reply-To
	  CALL MOVRDT		;And In-Reply-To
	  CALL MOVUSH		;Insert user-generated headers
	  MOVEI B,[ASCIZ/
/]
	  CALL MOVSB2		;Prepare for Message-ID
	ENDIF.
	MOVEI B,[ASCIZ/Message-ID: </]
	CALL MOVSB2
	MOVE A,O		;Current pointer
	MOVE B,HDRTAD		;User's argument
	MOVX C,^D10
	NOUT%
	 JFATAL
	MOVX B,"."
	IDPB B,A
	MOVE B,MYJOBN
	NOUT%
	 JFATAL
	MOVX B,"."
	IDPB B,A
	MOVE B,MYUSR		;Login directory
	DIRST%
	 JFATAL
	MOVE O,A		;Update pointer
	MOVX A,"@"		;Output "@host" in absolute form
	XCT MOVDSP
	MOVEI B,LCLHNM
	CALL MOVSB2
	MOVX A,.CHRAB		;Close it off
	XCT MOVDSP
	MOVEI B,[ASCIZ/

/]
	CALL MOVSB2		;And a couple blank lines
	SETZ A,
	IDPB A,O		;Mark end of this with a null too
	MOVEI A,1-HDRPAG(O)
	IMULI A,5		;Compute characters used in headers
	LDB B,[POINT 6,O,5]
	IDIVI B,7
	SUBI A,1(B)
	ADDM A,MSGSIZ		;Update size of whole thing
	RET

	ENDSV.
;;;File away the message in SAVED.MESSAGES if in logged directory

FILMSG:	SKIPN RMLPTR		;Doing remail?
	 SKIPN SAVFIL		;Wants one at all?
	  RET			;Doing remail or no saved messages file
	MOVEI T,M%SEEN		;Mark message as seen
	MOVX A,GJ%OLD!GJ%SHT	;Enter here to send to a file
	DO.
	  HRROI B,SAVFIL
	  GTJFN%		;Try to get guy's SAVED.MESSAGES
	  IFJER.
	    HRROI B,SAVFIL
	    JWARN <Can't get output file "%2S">
	    CAIE A,GJFX24	;File not found error?
	     RET		;No, probably bad filename
	    PROMPT <May I create it? >
	    CALL YESNO		;Offer to create file for user
	     RET		;User said no
	    MOVX A,GJ%NEW!GJ%SHT ;Make a new file
	    LOOP.		;Try again
	  ENDIF.
	ENDDO.
FILMS1:	PUSH P,A		;Save JFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%APP> ;Open for append
	OPENF%
	IFJER.
	  POP P,A		;Get JFN back
	  JWARN <Can't open output file "%1J">
	  RLJFN%		;Flush the JFN
	   NOP
	  RET
	ENDIF.
	POP P,A
	SKIPLE SNDVBS		;Verbose sending mode?
	 TXOA F,F%F1		;Yes, type out file name
FILMS2:	  TXZ F,F%F1		;Don't type out file name
	SETO B,			;Now
	MOVX C,OT%TMZ		;Timezone as well
	ODTIM%
	MOVEI B,","
	BOUT%
	MOVE B,MSGSIZ		;Get back size
	MOVX C,^D10		;Decimal
	NOUT%
	 JERROR
	MOVEI B,";"
	BOUT%
	MOVE B,T		;Bits to put in
	MOVE C,[NO%LFL!NO%ZRO!NO%MAG!14B17!^D8] ;"000000000000"
	NOUT%
	 JERROR
	HRROI B,CRLF0
	SETZ C,
	SOUT%			;Write header bits and crlf
	HRROI B,HDRPAG
	SOUT%			;Copy the headers
	HRROI B,TXTPAG
	SOUT%			;And the text
	TXZE F,F%F1		;Want file name?
	 CIETYP < *%1J -- ok>
	CLOSF%
	 JWARN <Couldn't close output file>
	SETZM OUTJFN
	RET

;;;Send other disk file recipients

SNDFIL:	HRRZ W,FILIST
	DO.
	  JUMPE W,R		;Done with file recipients
	  MOVEI T,0		;Mark as unseen
	  MOVX A,GJ%SHT
	  HRROI B,ADRSTR(W)	;Get name of file
	  GTJFN%		;Try to get file
	  IFJER.
	    HRROI B,ADRSTR(W)
	    JWARN <Can't get output file "%2S">
	  ELSE.
	    CALL FILMS1		;Send it off
	  ENDIF.
	  HRRZ W,ADRLNK(W)	;Get next one
	  LOOP.
	ENDDO.
;;;Queue network mail

SNDNET:	TXZ F,F%QUOT
	TXO F,F%QUEU		;Flag we have queued mail
	MOVE A,[POINT 7,STRBUF]	;Build name in STRBUF
	MOVEI B,[ASCIZ/MAILQ:[--QUEUED-MAIL--]/]
	CALL MOVSTR		;Copy initial string
	CALL MOVQNM		;Set unique extension
	CALL GNTQFL		;Make a network queue file
	IFSKP.
	  AOS (P)		;Flag no need to update mailer flags
	ELSE.
	  MOVE A,[POINT 7,STRBUF] ;No MAILQ:, use login directory
	  MOVEI B,[ASCIZ/[--QUEUED-MAIL--]/]
	  CALL MAKSTR		;Put in start of file name
	  ADD A,[7B5]		;Back up over null
	  CALL MOVQNM		;Set unique extension
	  CALL GNTQFL		;Make a network queue file
	   JERROR <Can't get queue file>
	ENDIF.
	MOVEM A,OUTJFN
	MOVX B,.CHFFD		;Write delivery options line
	BOUT%
	HRROI B,[ASCIZ/=DELIVERY-OPTIONS:/]
	SETZ C,
	SOUT%
	MOVE B,DLVOPT		;Get delivery option
	HLRO B,DOPTAB(B)
	SOUT%			;Output it
	HRROI B,CRLF0
	SOUT%
	SKIPN AFTDAT		;AFTER parameter?
	IFSKP.
	  MOVX B,.CHFFD		;Write after line
	  BOUT%
	  HRROI B,[ASCIZ/=AFTER:/]
	  SOUT%
	  MOVE B,AFTDAT		;Output After date/time
	  MOVX C,OT%NSC!OT%SCL
	  ODTIM%
	  HRROI B,CRLF0
	  SETZ C,
	  SOUT%
	ENDIF.
	SETO E,			;Clear last host sent
	HRRZ W,NETLST		;Get start of network list
	SKIPN W			;Output it
	 FATAL (No recipients in SNDNET)
	DO.
	  MOVE A,OUTJFN		;Get back JFN for output
	  SKIPN B,ADRHST(W)	;Get host address
	   MOVE B,LCLHST	;Use local host-name if zero
	  CAMN B,E		;Same as last time?
	  IFSKP.
	    MOVE E,B		;Set new "last host"
	    MOVEI B,.CHFFD	;Formfeed separates hosts
	    BOUT%
	    HRRO B,E
	    SOUT%		;Output the host
	    SETZ C,
	    HRROI B,CRLF0
	    SOUT%
	  ENDIF.
 	  HRROI B,ADRSTR(W)	;Name of recipient
	  SETZ C,
	  SOUT%
	  HRROI B,CRLF0
	  SOUT%
	  SKIPG SNDVBS		;Super-verbose sending?
	  IFSKP.
	    MOVEI A,ADRSTR(W)	;Yes, get guy's name
	    SKIPN B,ADRHST(W)	;Get host pointer
	     MOVE B,LCLHST	;Local host if 0
	    CIETYP < %1R@%2R -- queued
>
	  ENDIF.
	  HRRZ W,ADRLNK(W)	;Get next one to do
	  JUMPN W,TOP.		;Do it if it exists
	ENDDO.
	MOVE A,OUTJFN		;Get back JFN for output
	HRROI B,[BYTE (7) .CHFFD,.CHCRT,.CHLFD,0] ;Otherwise finish up
	SETZ C,			;With <form><crlf>
	SOUT%
	HRROI B,HDRPAG		;Start of headers
	SETZ C,
	SOUT%
	HRROI B,TXTPAG		;Start of text
	SOUT%
	CLOSF%			;All there is to it
	 JSNARL <Can't close message file>
	SETZM OUTJFN
	RET			;All done, return
;;;Build a unique queued mail file extension string, source pointer in A

MOVQNM:	MOVEI B,[ASCIZ/.NEW-/]
	CALL MOVSTR		;Copy initial string
	PUSH P,A		;Create frame to save string pointer
	GTAD%			;Now output date/time
	MOVE B,A
	POP P,A
	MOVX C,^D8		;Output in octal
	NOUT%
	 JFATAL			;Can't happen
	MOVEI B,[ASCIZ/-MM-J/]
	CALL MOVSTR
	MOVE B,MYJOBN		;Get job number in B
	MOVX C,^D10		;Output in octal
	NOUT%
	 JFATAL			;Can't happen
IFE NICSW,<
	MOVEI B,[ASCIZ/.-1;P770000/] ;Next generation, set protection.
>
IFN NICSW,<
	MOVEI B,[ASCIZ/.-1/] ;Next generation, set protection.
>
	CALLRET MOVST0		;Finish string, tie off with null
;;;Set mailer flags

MAIFLG:	TXZN F,F%QUEU		;Any queued mail to do?
	 RET
	MOVX A,GJ%OLD!GJ%SHT!GJ%PHY ;Set the mailer flags
	HRROI B,[ASCIZ/MAIL:MAILER.FLAGS.1/]
	GTJFN%			;Get JFN on flags file
	IFJER.
	  JWARN <Unable to get mailer flags>
	  RET
	ENDIF.
	PUSH P,A
	MOVX B,OF%THW!OF%WR!OF%RD
	OPENF%
	IFJER.
	  POP P,A
	  RLJFN%
	   NOP
	  JWARN <Unable to open mailer flags>
	RET
	ENDIF.
	HRLZ A,(P)		;Page 0
	MOVE B,[.FHSLF,,FLGPAG/1000]
	MOVX C,PM%RD!PM%WR
	PMAP%
	HRRZ C,MYAUSR		;Alias login directory
	IDIVI C,^D36
	MOVSI A,(1B0)
	MOVN D,D
	ROT A,(D)
	IORM A,FLGPAG(C)	;Set my bit
	SETO A,
	MOVE B,[.FHSLF,,FLGPAG/1000]
	SETZ C,
	PMAP%
	POP P,A
	CLOSF%
	 NOP
	RET
; Routine to create a queue file for network mail.
; Entry:   strbuf = file name string
; Call:    CALL GNTQFL
; Return:  +1, error
;	   +2, success, a = JFN
GNTQFL:	STKVAR <QFLJFN>
	DO.
IFE NICSW,<
	  MOVX A,GJ%NEW!GJ%FOU!GJ%PHY!GJ%SHT ;Standard flags plus physical
>
IFN NICSW,<
	  MOVX A,GJ%NEW!GJ%FOU!GJ%SHT ;Standard flags
>
	  HRROI B,STRBUF	; for MAILQ:
	  GTJFN%
	   ERJMP R		;No go
	  MOVEM A,QFLJFN	;Save the JFN
	  MOVX B,<<FLD 7,OF%BSZ>!OF%WR>
	  OPENF%
	  IFJER.
	    PUSH P,A		;Save error code
	    MOVE A,QFLJFN	;Get losing JFN
	    RLJFN%		;Release it
	     NOP
	    POP P,A		;Recover error code
	    CAIE A,OPNX9	;If file busy, try again
	     CAIN A,OPNX2	;File disappeared?
	      LOOP.		;Yes, try again
	    CAIE A,OPNX23	;Over allocation?
	     RET		;No, return failure
	    JSNARL <Can't open queue file>
	    HALTF%
	    LOOP.
	  ENDIF.
	ENDDO.
	HRLI A,.FBBYV		;Set to keep all versions
	MOVX B,FB%RET
	SETZ C,
	CHFDB%			;Keep all versions
	 ERJMP .+1
	HRRZ A,QFLJFN		;A := clean output JFN
	RETSKP			;And return +2

	ENDSV.
;;;Run MMailr to send off what we queued

.MAILE:	NOISE (QUEUED MESSAGES)
	CONFRM
	CALL $WAKE		;Send wakeup to daemon
	MOVX A,GJ%OLD!GJ%SHT
	HRROI B,[ASCIZ/SYS:MMAILR.EXE/]
	GTJFN%
	 JERROR <Couldn't find file to run>
	CALL RUNFL0
	KFORK%			;Don't need it any more
	CALLRET .PUSH1		;Do an automatic CHECK

RUNFIL:	TXZA F,F%F3		;Run enabled
RUNFL0:	 TXO F,F%F3		;Don't run enabled
	PUSH P,A		;Save the JFN
	TXNE F,F%F3		;Wants to run enabled?
	 TDZA A,A		;No
	  MOVX A,CR%CAP		;Yes, give it our caps
	CFORK%
	 JERROR <Couldn't create fork>
	PUSH P,C		;Make sure users can use MMAILR
	SETO B,			;All priv's possible
	SETZ C,			;But none enabled
	TXZE F,F%F3		;If not to be enabled
	 EPCAP%			;At least give him possibles
	POP P,C
	EXCH A,(P)		;Get back JFN
	HRL A,(P)
	GET%
	IFJER.
	  JERROR <Couldn't get file to run>
	ENDIF.
	POP P,A			;Get back fork handle
RUNFL2:	PUSH P,CMDRET		;Save original command return
	PUSH P,F		; and flags.
	MOVEI B,RUNRES		;Go here if error
	HRRM B,CMDRET
	TXZ F,F%READ!F%SEND	;Don't let these misdirect!
	SETZ B,
	SFRKV%			;At regular startup point
	WFORK%

RUNRES:	POP P,F			;Restore original flags
	POP P,CMDRET		; and command return
	SAVEAC <A,C,D>
	DMOVE A,PRGNAM		;Restore names
	SETSN%
	 JFATAL
	MOVEI D,SAVMOD		;Restore TTY modes
	CALLRET SETTYM

.PUSH:	CONFRM
	SETABT			;Save previous abort state
	CALL ABNDIS		;Don't CTRL/N out of EXEC
	SKIPLE A,EXECFK		;Have a fork yet?
	IFSKP.
	  MOVX A,GJ%OLD!GJ%SHT
	  HRROI B,[ASCIZ/DEFAULT-EXEC:/]
	  GTJFN%
	  IFJER.
	    MOVX A,GJ%OLD!GJ%SHT
	    HRROI B,[ASCIZ/SYSTEM:EXEC.EXE/]
	    GTJFN%
	     JERROR <Couldn't find file to run>
	  ENDIF.
	  CALL RUNFIL		;No, make a fork and run it
	  MOVEM A,EXECFK	;And keep the fork handle
	ELSE.
	  CALL RUNFL2		;Already have one, just run it
	ENDIF.
.PUSH1:	SAVEAC <M>
	SKIPG MSGJFN		;Do we have a mail file?
	 RET			;No, don't do any check then
	CALL CHECKT		;Check for new messages and report
	CALLRET PARSEA		;Reparse entire file in case bits changed
;;;Erase fields

.ERSAL:	SETZM SUBBUF		;Reset subject
	SETZM TOLIST		;Reset to and cc pointers
	SETZM CCLIST
	SETZM BCCLST
	SETZM LCLIST
	SETZM FILIST
	SETZM NETLST
	SETZM REPDAT		;No reply date
	SETZM AFTDAT		;No after date
	SETZM DLVOPT		;Delivery option is MAIL
	MOVEI A,TOPAG
	MOVEM A,FREETO		;Reset free space pointer
.ERSTX:	MOVE A,[POINT 7,TXTPAG]
	MOVEM A,TXTPTR		;Reset pointer to text space
	SETZM TXTPAG		;And make sure it starts with null
	MOVX A,<5*NTXPGS*1000>-^D10 ;Text buffer size-10.
	MOVEM A,TXTCNT
	DMOVE A,USRHDR		;User's headers from init file
	DMOVEM A,USRHFP		;Set up as current user headers
	MOVEI B,0
	IDPB B,A		;Make sure they end with a null
	RET

.ERSDT:	SETZM REPDAT		;No reply date
	RET

.ERSSB:	SETZM SUBBUF
	RET

.ERSBC:	MOVEI T,BCCLST		;Erase bcc list
	CALLRET ERSTO0

.ERSCC:	SKIPA T,[CCLIST]	;Erase cc list
.ERSTO:	 MOVEI T,TOLIST		;Erase to list
ERSTO0:	HRRZ W,(T)
	IFN. W
	  DO.
	    CALL REMLST		;Remove from transmission medium list
	    LOAD W,ADPTR,(W)	;Get next in list
	    JUMPN W,TOP.
	  ENDDO.
	ENDIF.
	SETZM (T)
	SKIPN CCLIST		;All list empty now?
	 SKIPE TOLIST
	  RET
	SKIPE BCCLST
	 RET
	MOVEI A,TOPAG		;Yes, reset free pointer
	MOVEM A,FREETO
	RET

;;;Display fields

.DSHDR:	CALL DISHDR
	MOVEI D,SAVMOD
	CALLRET SETTYM

.DSALL:	CALL DISHDR
	CALL MOVTX1
	MOVEI D,SAVMOD
	CALLRET SETTYM

DISHDR:	CALL TYPINI
	MOVE A,[PBOUT%]		;Set up to type it out to tty
	TXO F,F%RELD		;Show relative domains
	CALL MOVFR0
	CALL MOVSB1
	CALL MOVTO1
	CALL MOVCC1
	CALL MOVBC1
	CALL MOVRP1
	CALL MOVRDT
	CALLRET MOVUS1

.DSFRM:	SKIPA B,[MOVFR0]
.DSREP:	 MOVEI B,MOVRP0
	CALLRET .DSCC1

.DSSUB:	SKIPA B,[MOVSB0]
.DSTXT:	 MOVEI B,MOVTX0
	CALLRET .DSCC1

.DSTO:	SKIPA B,[MOVTO0]
.DSCC:	 MOVEI B,MOVCC0
.DSCC1:	CALL TYPINI
	MOVE A,[PBOUT%]
	TXO F,F%RELD		;Show relative domains
	CALL (B)
	MOVEI D,SAVMOD
	CALLRET SETTYM

.DSBCC:	MOVEI B,MOVBC0
	CALLRET .DSCC1
MOVFRM:	MOVE A,[IDPB A,O]
MOVFR0:	MOVEM A,MOVDSP		;Set up instruction
MOVFR1:	MOVEI B,[ASCIZ/
From: /]
MOVFRR:	CALL MOVSB2
	SKIPN FRMSCM		;If the user has given a "From:"
	IFSKP.
	  MOVEI B,FRMSCM	; then use it
	  CALLRET MOVSB2
	ENDIF.
	SKIPN PERNAM		;Has a personal name?
	IFSKP.
	  MOVEI B,PERNAM
	  CALL MOVSB2
	  MOVX A,.CHSPC
	  XCT MOVDSP
	  MOVX A,.CHLAB
	  XCT MOVDSP
	ENDIF.
	MOVEI B,MAUSRS		;My name
	CALL MOVSB2		;Put it in
	CALL MOVMHN		;Put in@SITE
	SKIPN PERNAM		;Has a personal name?
	 RET			;No, all done
	MOVX A,.CHRAB
	XCT MOVDSP
	RET

MOVSDR:	MOVE A,[IDPB A,O]	;Output Sender
MOVSD0:	MOVEM A,MOVDSP		;Set up to move into memory
MOVSD1:	MOVEI B,[ASCIZ/
Sender: /]
MOVSRR:	CALL MOVSB2
	MOVE A,O
	MOVE B,MYUSR		;Login directory
	DIRST%
	 JFATAL
	MOVE O,A		;Update pointer
	CALLRET MOVMHN		;Output local host

MOVREP:	MOVE A,[IDPB A,O]	;Output Reply-To
MOVRP0:	MOVEM A,MOVDSP		;Set up to move into memory
MOVRP1:	SKIPN REPSCM		;Did user specify a Reply-To?
	 RET			;No, return now
	MOVEI B,[ASCIZ/
Reply-To: /]			;Yes, use it
	CALL MOVSB2
	HRROI B,REPSCM		;Move in the text and return
	CALLRET MOVSB2

MOVUSH:	MOVE A,[IDPB A,O]
MOVUS0:	MOVEM A,MOVDSP		;Set up to move into memory
MOVUS1:	SKIPN USRHFP		;Has any user headers?
	 RET			;No, none
	MOVEI B,CRLF0		;Put in crlf first
	CALL MOVSB2
	MOVEI B,USRHDT
	CALLRET MOVSB2		;Go add that in

MOVSUB:	MOVE A,[IDPB A,O]	;Output subject
MOVSB0:	MOVEM A,MOVDSP		;Set up to move into memory
MOVSB1:	LDB A,[POINT 7,SUBBUF,6]
	JUMPE A,R		;No subject
	MOVEI B,[ASCIZ/
Subject: /]
	CALL MOVSB2		;Print header part
	MOVEI B,SUBBUF		;Start of actual string
MOVSB2:	HRLI B,(<POINT 7,>)
MOVSB3:	ILDB A,B		;Get char
	JUMPE A,R		;Done
	XCT MOVDSP		;Handle it
	JRST MOVSB3

MOVTXT:	MOVE A,[IDPB A,O]	;Output text
MOVTX0:	MOVEM A,MOVDSP		;Set up to move into memory
MOVTX1:	MOVEI B,[ASCIZ/

/]
	CALL MOVSB2
	MOVEI B,TXTPAG
	CALL MOVSB2
	LDB A,TXTPTR
	MOVEI B,CRLF0
	CAIE A,.CHLFD		;Unless ended with CRLF
	 CALL MOVSB2		;Put one in
	MOVEI B,[ASCIZ/-------
/]
	CALLRET MOVSB2		;And end it up

MOVBC0:	MOVEM A,MOVDSP		;Output BCC
MOVBC1:	MOVEI T,[ASCIZ/
Bcc: /]
	HRRZ W,BCCLST
	CALLRET MOVTO2

MOVCC:	MOVE A,[IDPB A,O]	;Output CC
MOVCC0:	MOVEM A,MOVDSP
MOVCC1:	MOVEI T,[ASCIZ/
cc: /]
	HRRZ W,CCLIST
	CALLRET MOVTO2

MOVTO:	MOVE A,[IDPB A,O]	;Output to
MOVTO0:	MOVEM A,MOVDSP
MOVTO1:	MOVEI T,[ASCIZ/
To: /]
MOVTRM:	HRRZ W,TOLIST
MOVTO2:	DO.
	  JUMPE W,R		;None here, forget it
	  IFQN. ADINV,(W)	;Don't print if invisible requested
	    LOAD W,ADPTR,(W)	;Get next in list
	    LOOP.
	  ENDIF.
	ENDDO.
	SKIPA B,T		;Use keyword for first time
MOVTO3:	 MOVEI B,[ASCIZ/
    /]				;Yes, just indent
	CALL MOVSB2		;Print header
	MOVEI D,3		;Init horizontal position
MOVTO4:	MOVEI B,ADRSTR(W)	;Get name
	TXZ F,F%QOT		;Currently not a quoted string
	HRLI B,(<POINT 7,>)	;Make string pointer to address
	LOAD C,ADTYP,(W)	;Get type field
	CAIE C,AD.FIL		;File recipient?
	IFSKP.
	  TXO F,F%QOT		;Yes, flag must quote
	  MOVEI A,""""		;Yes, start the quote
	  XCT MOVDSP
	  MOVEI A,"*"		;Now splat
	  XCT MOVDSP
	ELSE.
	  PUSH P,B		;Save string pointer
	  PUSH P,C		;And type
	  PUSH P,D		;And byte count
	  DO.			;Search string for specials
	    ILDB C,B
	    IFN. C
	      IDIVI C,^D32	;C/ word to check, D/ bit to check
	      MOVNS D
	      MOVX A,1B0	;Make bit to check
	      LSH A,(D)
	      TDNN A,SPCMSK(C)	;Is it a special character?
	       LOOP.		;No, continue search
	      TXO F,F%QOT	;Must quote this address
	    ENDIF.
	  ENDDO.
	  POP P,D		;Restore byte count
	  POP P,C		;And type
	  POP P,B		;Restore string pointer
	ANDXN. F,F%QOT		;Need to quote?
	  MOVEI A,""""		;Yes, do so
	  XCT MOVDSP
	ENDIF.
	DO.			;Copy string to designated output
	  ILDB A,B
	  IFN. A
	    XCT MOVDSP
	    AOJA D,TOP.
	  ENDIF.
	ENDDO.
	IFXN. F,F%QOT		;Need to quote?
	  MOVEI A,""""
	  XCT MOVDSP
	ENDIF.
	CAIE C,AD.GRP		;Distribution list?
	IFSKP.
	  MOVEI B,[ASCIZ/: ;/]	;Yes, set up empty list
	ELSE.
	  CAIE C,AD.NET		;Network recipient?
	  IFSKP.
	    HRRO B,ADRHST(W)	;Yes, get host pointer
	    IFXE. F,F%RELD	;Include relative domains?
	      MOVE A,[POINT 7,TMPBUF] ;No, copy it to temporary space
	      CALL MOVST0
	      HRROI A,TMPBUF	;Remove relative domains from it
	      CALL $RMREL
	      HRROI B,TMPBUF	;Continue with pointer to it
	    ENDIF.
	  ELSE.
	    TXNN F,F%RELD	;Include relative domain?
	     SKIPA B,[POINT 7,LCLHNM] ;No, use absolute local hostname
	      HRRO B,LCLHST	;Else use relative local hostname
	  ENDIF.
	  MOVE A,[POINT 7,STRBUF] ;Write host name here temporarily
	  IFXN. F,F%QUOT	;Need to write rubouts around it?
	    MOVX C,.CHDEL
	    IDPB C,A
	  ENDIF.
	  SETZ C,
	  SOUT%			;Output name string
	  IFXN. F,F%QUOT
	    MOVEI B,.CHDEL
	    IDPB B,A
	  ENDIF.
	  IDPB C,A		;Tie off string with null
	  MOVEI A,"@"		;Output at delimiter
	  XCT MOVDSP
	  ADDI D,1		;Count 1 char for this
	  MOVEI B,STRBUF
	ENDIF.
	HRLI B,(<POINT 7,>)	;Make string pointer to address
	DO.			;Copy string to designated output
	  ILDB A,B
	  IFN. A
	    XCT MOVDSP
	    AOJA D,TOP.
	  ENDIF.
	ENDDO.
	DO.
	  LOAD W,ADPTR,(W)	;Get next in list
	  JUMPE W,R
	  JN ADINV,(W),TOP.	;Don't print if invisible requested
	ENDDO.
	MOVEI A,","
	XCT MOVDSP
	TXNE F,F%QUOT		;Always generate continuation line
	 AOJA E,MOVTO3
	CAIL D,^D65		;Near end?
	 AOJA E,MOVTO3		;Yes, get new line for more then
	MOVX A,.CHSPC
	XCT MOVDSP
	ADDI D,2
	JRST MOVTO4
MOVRDT:	SKIPG REPDAT		;Has a reply date?
	 RET			;No
	HLRZ C,MSGMID(M)	;Get size of Message-ID field
	IFN. C			;If have an ID
	  MOVEI B,[ASCIZ/
In-Reply-To: /]
	  CALL MOVSB2
	  HRRZ V,MSGMID(M)	;Get byte offset of field
	  CALL MCH2BP		;Get byte pointer to it
	  MOVE B,A
	  HLRZ C,MSGMID(M)	;And counter
	  DO.			;Ignore leading whitespace
	    ILDB A,B
	    CAIE A,.CHSPC
	     CAIN A,.CHTAB
	      SOJG C,TOP.
	    JUMPE C,R
	    SKIPE A
	     XCT MOVDSP
	    SOJE C,R
	  ENDDO.
	  DO.
	    ILDB A,B
	    SKIPE A		;Never put in a null
	     XCT MOVDSP
	    SOJG C,TOP.
	  ENDDO.
	  RET
	ENDIF.
	MOVEI B,[ASCIZ/
In-Reply-To: Message/]
	CALL MOVSB2
	HLRZ C,MSGFRM(M)	;Get size of From: field
	IFN. C			;Has an author?
	  HRRZ V,MSGFRM(M)	;Get byte offset of field
	  CALL MCH2BP		;Get byte pointer to it
	  MOVE B,A		;Put pointer in A
	  DO.			;Flush leading whitespace
	    ILDB A,B		;Get char
	    IFE. A		;Ignore nulls
	      SOJG C,TOP.
	    ELSE.
	      CAIE A,.CHTAB	;Ignore whitespace
	       CAIN A,.CHSPC
	        SOJG C,TOP.
	    ENDIF.
	  ENDDO.
	  IFN. C
	    SETO A,		;Back up pointer by 1
	    ADJBP A,B
	    PUSH P,A		;And save it for below
	    MOVEI B,[ASCIZ/ from "/]
	    CALL MOVSB2
	    POP P,B		;Retrieve pointer
	    DO.
	      ILDB A,B
	      SKIPE A		;Never put in a null
	       XCT MOVDSP
	      SOJG C,TOP.
	    ENDDO.
	    MOVEI A,""""
	    XCT MOVDSP
	  ENDIF.
	ENDIF.
	MOVEI B,[ASCIZ/ of /]
	CALL MOVSB2
	SETZ A,
	MOVE B,MOVDSP		;Get instruction
	CAMN B,[IDPB A,O]	;Output to string?
	 MOVE A,O		;Yes, get current BP
	CAMN B,[PBOUT%]		;Output to TTY?
	 MOVX A,.PRIOU		;Yes, select terminal output
	MOVE B,REPDAT
	MOVX C,OT%DAY!OT%SPA!OT%TMZ!OT%SCL!OT%822 ; RFC 822 standard date/time
	ODTIM%
	CAIE A,.PRIOU		;Unless going to current output
	 MOVE O,A		;Set byte pointer to value from ODTIM%
	RET

MOVMHN:	MOVX A,"@"		;Now put in an "@"
	XCT MOVDSP
	TXNE F,F%RELD		;Include relative domain?
	 SKIPA B,LCLHST		;Yes, output host name string
	  MOVEI B,LCLHNM	;No, output absolute name
	MOVE A,MOVDSP		;Get current output
	CAME A,[PBOUT%]		;If output to TTY
	 TXNN F,F%QUOT		;Or no quoted host names
	  JRST MOVSB2		;Just output host name string
	MOVX A,.CHDEL
	XCT MOVDSP
	CALL MOVSB2
	MOVX A,.CHDEL
	XCT MOVDSP
	RET
;;;Get some more text

GETTXT:	SKIPE USEEDT		;Go straight to editor?
	 JRST TXTED		;Yes, do so
	SKIPE TRSTPR		;Terse or verbose?
	IFSKP.
	  SKIPE JISMOD		;JIS terminal?
	   SKIPA A,[[ASCIZ\CTRL/D\]]
	    MOVEI A,[ASCIZ\ESCAPE or CTRL/D\]
	  MOVEI B,[ASCIZ\or CTRL/Z\]
	  TXNN F,F%RSCN
	   SKIPGE ESCSND
	    MOVEI B,[ASCIZ\to get to send command level, CTRL/Z to send\]
	  SKIPLE ESCSND
	   MOVEI B,[ASCIZ\to send, CTRL/Z to get to send command level\]
	  SKIPE SIMODE		;Katakana terminal?
	   SKIPA C,[[ASCIZ\, CTRL/X to abort.\]]
	    MOVEI C,[ASCIZ\, CTRL/N or CTRL/X to abort.\]
	  SKIPGE ABOFLG		;Wants abort?
	   MOVEI C,[ASCIZ\.\]
	  UETYPE 1,[ASCIZ" Message:
 (End with %1S %2S.
  Use CTRL/B to insert a file, CTRL/E to enter editor, CTRL/K to redisplay
  message, CTRL/L to clear screen and redisplay%3S)
"]				;UETYPE 1, = CIETYPE
	ELSE.
	  CITYPE < Msg:
>
	ENDIF.
	CALLRET .TEXT0

.TEXT:	CONFRM
.TEXT0:	SKIPE ABOSTS		;Unless vector already exists,
	IFSKP.
	  SETABT CMDABO		;Allow abort back to toplevel
	ENDIF.
.TEXT1:	SKIPE USEEDT		;Go straight to editor?
	 JRST TXTED		;Yes, do so
	CALL TXTINI
	MOVX A,RD%JFN		;Set up flags
	MOVEM A,TTXTIB+.RDFLG
	MOVE A,CMDBLK+.CMIOJ	;Get where I/O is going
	MOVEM A,TTXTIB+.RDIOJ	;Let TEXTI% know about it
	MOVE A,[POINT 7,TXTPAG]	;Where it starts
	MOVEM A,TTXTIB+.RDBFP
	SKIPE JISMOD		;Kanji user?
	 SKIPA A,[TXTJIS]	;Yes, use JIS break mask
	  MOVEI A,TXTMSK	;Else use regular break mask
	MOVEM A,TTXTIB+.RDBRK
	MOVEI A,TTXTIB
	TEXTI%
	 JERROR
	MOVEI D,SAVMOD		;Restore program's modes
	CALL SETTYM
	LDB B,TXTPTR
	MOVEM B,LSTCHR		;Save terminator
	SETZ A,
	DPB A,TXTPTR		;Replace terminator with null
	SETO A,
	ADJBP A,TXTPTR
	MOVEM A,TXTPTR
	AOS TXTCNT
	CAIN B,.CHCNB		;CTRL/B inserts file
	 JRST TXTFIL
	CAIN B,.CHCNE		;CTRL/E - enter editor on text
	 JRST TXTEDC
	CAIN B,.CHVTB		;Wants retype of whole thing?
	 JRST .TEXT2
	CAIE B,.CHFFD		;Clear and retype?
	 RET			;No, must have terminated right
	CALL $BLANK		;Yes
	CITYPE < Msg:>
.TEXT2:	CALL CRIF
	CALL TYPINI		;Init CCOC words
	HRROI A,TXTPAG		;Start of stuff
	PSOUT%
	MOVEI D,SAVMOD		;Restore program's modes
	CALL SETTYM
	JRST .TEXT1		;And go get some more

TXTFIL:	PROMPT <(Insert file: >
	MOVEI B,TXTFL1
	HRRM B,CMDBLK+.CMFLG
	MOVEM P,REPARP
TXTFL1:	MOVE P,REPARP
	MOVEI B,[FLDDB. .CMIFI,,,,,[
		 FLDDB. .CMCFM,CM%SDH,,<return to cancel file insertion>]]
	CALL $COMND
	JXN A,CM%NOP,TXTFLE
	LOAD A,CM%FNC,(C)	;Get field type
	CAIE A,.CMCFM		;Confirm?
	IFSKP.
	  TMSG <...No file inserted)
>				;Yes, abort CTRL/B input
	  JRST .TEXT1
	ENDIF.
	MOVEM B,TMPJFN
	MOVEI B,CNFCMD
	CALL $COMND		;Confirm
	JXN A,CM%NOP,TXTFLE
	MOVE A,TMPJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	OPENF%
	 ERJMP TXTFLE
	CALL INSFL3
	TMSG <...EOF)
>
	JRST .TEXT1

TXTFLE:	JWARN			;Error getting file, return for text
	CALL CRLF
	JRST .TEXT1

TXTEDC:	SKIPLE A,EDTFLG		;Editing always done?
	IFSKP.
	  IFE. A		;No, do we ask?
	    PROMPT <Edit message text? > ;Yes
	    CALL YESNO		;Confirm edit
	     JRST .TEXT1	;User said no
	  ELSE.
	    IDPB B,TXTPTR	;No, put the character in the buffer
	    SOS TXTCNT
	    JRST .TEXT1
	  ENDIF.
	ENDIF.
TXTED:	CALL CRIF		;Edit text, get fresh line
	CALLRET .EDTXT		;And go start doing it

.INSFL:	MOVEI B,[FLDDB. .CMIFI]
	CALL CMDFLD		;Get the file
	MOVEM B,TMPJFN
	CONFRM
	MOVE A,TMPJFN
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	OPENF%
	IFJER.
	  MOVE A,TMPJFN
	  JWARN <Can't open input file "%1J">
	  CALLRET CRLF
	ENDIF.
INSFL3:	MOVE B,TXTPTR
	MOVN C,TXTCNT
	SIN%
	 ERJMP .+1
	CLOSF%
	 NOP
	SETOM TMPJFN
	IFGE. C			;A fencepost but what the hell
	  SETZ C,		;Full buffer
	  SNARL <Buffer overflow - file too large>
	ENDIF.
	EXCH B,TXTPTR		;B - source of copy to remove nulls
	MOVE A,B		;A - dest of copy
	MOVNM C,TXTCNT		;C - current character
	DO.
	  CAMN B,TXTPTR		;TXTPTR - end of source text
	  IFSKP.
	    ILDB C,B		;Copy 
	    SKIPE C		;Removing nulls
	     IDPB C,A
	    SKIPN C		;Each one skipped increases free space
	     AOS TXTCNT
	    LOOP.
	  ENDIF.
	ENDDO.
	MOVEM A,TXTPTR		;Updated end of text
	RET
;;;Get a new subject

.SUBJE:	SKIPN SIMODE		;If not a funny terminal...
	 SKIPE JISMOD
	IFSKP.
	  CALL GETLIN		;The easy way...
	  CONFRM
	  JUMPE B,.ERSSB	;None given, erase the subject then
	  MOVE A,[STRBUF,,SUBBUF]
	  BLT A,SUBEND		;Move over the subject
	  RET
	ENDIF.
	CONFRM			;Else do it the hard way...
GETSUB:	TXZ F,F%HOER		;No more error halting
	SKIPE ABOSTS		;Unless vector already exists,
	IFSKP.
	  SETABT CMDABO		;Allow abort back to toplevel
	ENDIF.
	TMSG < Subject: >
	STKVAR <<TSUBIB,.RDBRK+1>>
	MOVX A,.RDBRK		;Size of block
	MOVEM A,.RDCWB+TSUBIB
	MOVX A,RD%JFN		;Set up flags
	MOVEM A,.RDFLG+TSUBIB
	MOVE A,CMDBLK+.CMIOJ	;Get where I/O is going
	MOVEM A,.RDIOJ+TSUBIB	;Let TEXTI% know about it
	MOVE A,[POINT 7,SUBBUF]	;Where it starts
	MOVEM A,.RDDBP+TSUBIB
	MOVEM A,.RDBFP+TSUBIB
	MOVX A,SUBBSZ		;Subject buffer length
	MOVEM A,.RDDBC+TSUBIB
	HRROI A,[ASCIZ/ Subject: /]
	MOVEM A,.RDRTY+TSUBIB
	MOVEI A,LINJIS		;Use JIS line break mask
	MOVEM A,.RDBRK+TSUBIB
	SKIPN SIMODE		;Katakana mode?
	 SKIPE JISMOD		;JIS mode?
	  CALL TXTINI		;Yes, init
	MOVEI A,TSUBIB
	TEXTI%
	 JERROR
	MOVEI D,SAVMOD		;Restore program's modes
	CALL SETTYM
	LDB A,.RDDBP+TSUBIB	;See if need to eat an LF
	CAIN A,.CHCRT		;Well?
	 PBIN%			;Yes, do so
	SETZ A,			;Tie off line at the break
	DPB A,.RDDBP+TSUBIB
	RET

GETBCC:	PROMPT < bcc: >
	SKIPE ABOSTS		;Unless vector already exists,
	IFSKP.
	  SETABT CMDABO		;Allow abort back to toplevel
	ENDIF.
.BCC:	PUSH P,[BCCLST]		;Add someone to bcc list
	CALLRET CC1

GETCC:	PROMPT < cc: >
	SKIPE ABOSTS		;Unless vector already exists,
	IFSKP.
	  SETABT CMDABO		;Allow abort back to toplevel
	ENDIF.
.CC:	PUSH P,[CCLIST]
CC1:	TXO F,F%CC		;Say in cc command
	CALLRET .TO2		;And enter TO command

GETTO:	PROMPT < To: >
	TXZ F,F%RSCC		;Now out of RSCAN% code
GETTO0:	SKIPE ABOSTS		;Unless vector already exists,
	IFSKP.
	  SETABT CMDABO		;Allow abort back to toplevel.
	ENDIF.
.TO:	PUSH P,[TOLIST]		;What list to add to
	TXZ F,F%CC
.TO2:	TXZ F,F%F3!F%COMA!F%F4	;Don't allow funny local names
	MOVE W,FREETO		;Start with some free space
	PUSH P,CMDRET		;Save error dispatch
	DO.
	  CALL GETUSR		;Get the user name
	  IFSKP.
	    TXNE F,F%COMA	;Got one, comma seen?
	     LOOP.		;Yes, get another then
	  ENDIF.
	ENDDO.
	POP P,CMDRET
	POP P,T			;Get list to add to
	CALLRET ADDTO0		;Now add the whole line in and return

;;;Get prompted message

GETMSG:	CALL GETTO
GETMS0:	CALL GETCC
	SKIPE ASKBCC		;Prompt for bcc?
	 CALL GETBCC
GETMS1:	CALL GETSUB
	CALLRET GETTXT
;;;Remove user

.UNTO:	TXZ F,F%COMA!F%F4	;No comma seen yet
	TXO F,F%F3		;Allow funny addresses
	MOVE W,FREETO		;Some random space to use
	DO.
	  CALL GETUSR		;Get a user name
	   ERROR <Null address invalid>
	  JXN F,F%COMA,TOP.	;Wants more?
	ENDDO.
	HRRZS W			;Just in case
	PUSH P,W		;Save tail of list
	HRRZ U,FREETO		;Get head of list
	DO.
	  PUSH P,U		;Save current pointer
	  MOVEI U,ADRSTR(U)	;Point to text of name
	  SETZ N,		;Allow 0 occurances of that name
	  CALL DOUNTO		;Remove the name
	  IFE. N
	    ERROR <Address "%7S" not found>
	  ENDIF.
	  POP P,U
	  LOAD B,ADSIZ,(U)	;Get size
	  ADDI U,(B)
	  CAME U,(P)		;End of list yet?
	   LOOP.
	ENDDO.
CPPOPJ:	ADJSP P,-1		;No more, fix up stack and return
	RET
;;;Remove name from string in U, allowing only (n) occurances

DOUNTO:	MOVEI V,TOLIST		;Get to pointers
	CALL DOUNTL
	MOVEI V,CCLIST
	CALL DOUNTL
	MOVEI V,BCCLST
DOUNTL:	MOVEM V,UNTHDR		;Save header address for fixing last
	DO.
	  LOAD W,ADPTR,(V)
	  JUMPE W,R		;None of this class
	  MOVEI B,(U)		;Target string
	  HRLI B,(<POINT 7,>)
	  MOVEI A,ADRSTR(W)	;This particular one
	  HRLI A,(<POINT 7,>)
	  DO.
	    ILDB C,B		;Get char from target
	    ILDB D,A
	    IFN. C		;Null means it matches
	      CAIN D,(C)
	       LOOP.		;Chars match?
	      TRC D,(C)
	      CAIN D,.CHSPC	;Case only?
	       LOOP.		;Yes, keep looking
	    ELSE.
	      IFE. D
		SOSL N		;Count one more occurance
	      ANSKP.
		LOAD A,ADSIZ,(W) ;Get length of this block
		ADDI A,(W)	;Point to start of next block
		CAMN A,FREETO	;Was this the last entry?
		 MOVEM W,FREETO	;Yes, just update end pointer
		CALL REMLST	;Remove from transmission medium list
		LOAD W,ADPTR,(W) ;Get next link in to/cc list
		STOR W,ADPTR,(V) ;Relink previous
	        IFE. W		;If this is the end of the list now
		  HRLM V,@UNTHDR ;Update last (this fixes a bug)
		  CAIE V,TOLIST	;Was this the head of the list?
		   CAIN V,CCLIST
		    SETZM (V)	;Yes, clear whole thing
		  CAIN V,BCCLST
		   SETZM (V)
		ENDIF.
		EXIT.		;A-okay here
	      ENDIF.
	    ENDIF.
	    MOVEI V,(W)		;Setup to get next in list
	  ENDDO.
	  LOOP.
	ENDDO.
	SUBTTL SPELL interfacing subroutines

;;;SEND/REPLY command entry
.SSPEL:	CONFRM
	CALL SPLSET		;Set up for SPELL
	MOVE A,[POINT 7,TXTPAG]	;Starting byte pointer
	MOVE B,TXTPTR		;Ending one
	CALL SPLLEN		;Compute size of field
	CALL SPLICP		;Copy it into SPELL's input file
	CALL SPLGET		;Set up the SPELL fork
	CALL SPLRUN		;Run SPELL
	 RET
	MOVE A,[POINT 7,TXTPAG]	;Put updated text here
	MOVEI C,NTXPGS*1000*5	;Maximum size of receiving area
	CALL SPLOCP		;Get the updated text
	MOVEM B,TXTPTR		;Update end of text pointer
	SETZ C,
	IDPB C,B		;End it with a null too
	CALLRET SPLCLN		;Cleanup and return

;;;READ command entry
.RSPEL:	CONFRM			;Spell check this message
	CALL CHKDEL		;Make sure there is a message
	 RET
	CALL SPLSET		;Set up for SPELL
	HRRZ V,MSGBOD(M)
	CALL MCH2BP		;Get byte pointer to the message
	HLRZ C,MSGBOD(M)	;And its length
	CALL SPLICP		;Copy the msg and return
	CALL SPLGET		;Set up the SPELL fork
	CALL SPLRUN		;Run SPELL
	 RET
	MOVE A,[POINT 7,SPLPAG]	;Get the updated msg into here
	MOVEI C,NEDPGS*1000*5	;Size of that area
	CALL SPLOCP		;Get the updated msg
	CALL RPLMSG		;Replace the current msg with it
	 SNARL <Unable to update text from Spell>
	CALLRET SPLCLN		;Cleanup and return

;;;Routine to set up temp file for SPELL to use as input
;;;On exit:
;;; SPLIFL/ JFN to the text to correct
;;; SPLOFL/ JFN of temp file for SPELL to return text in
;;;Clobbers ACs: A, B

SPLSET:	MOVX A,GJ%FOU!GJ%SHT!GJ%TMP ;Get a temp file for SPELL input
	HRROI B,[ASCIZ/MM-SPELL-IN.TMP;P770000/]
	GTJFN%
	 JERROR <Can't get SPELL input temporary file>
	MOVX B,<<FLD 7,OF%BSZ>!OF%WR> ;We want to write msg into it
	OPENF%
	 JERROR <Can't open SPELL input temporary file>
	MOVEM A,SPLIFL		;Save it
	MOVX A,GJ%FOU!GJ%SHT!GJ%TMP ;Get SPELL's output file
	HRROI B,[ASCIZ/MM-SPELL-OUT.TMP;P770000/]
	GTJFN%
	 JERROR <Can't get SPELL output temporary file>
	MOVEM A,SPLOFL
	RET				

;;;Routine to compute the size of the current TEXT field
;;;On entry:
;;; A/ start byte pointer of field
;;; b/ end byte pointer of field
;;;On exit:
;;; C/ size of field in characters
;;;Clobbers ACs: B

SPLLEN:	LDB C,[POINT 6,B,5]
	IDIVI C,7		;Get chars within word
	SUBI B,(A)
	HRREI B,(B)		;Get number of words
	IMULI B,5		;Into chars
	SUBI B,(C)		;Get total number of chars
	LDB C,[POINT 6,A,5]
	IDIVI C,7
	ADDI B,(C)
	MOVE C,B		;Leave size in AC c
	RET

;;;Routine to copy text into SPELL's input file
;;;On entry:
;;; A/ start of text pointer
;;; C/ length of field in characters
;;; SPLIFL/ SPELL's input file opened for write
;;;On exit:
;;; SPLIFL/ good JFN, but closed
;;;Clobbers ACs: A, B, C

SPLICP:	MOVE B,A
	MOVE A,SPLIFL
	MOVNS C
	SOUT%
	TXO A,CO%NRJ		;Don't release the JFN
	CLOSF%
	 JFATAL
	RET

;;;Routine to get a SPELL fork
;;;On exit:
;;; A/ fork handle just gotten
;;; SPLFRK/ fork handle which is ready to run SPELL in
;;;Clobbers ACs: a,b

SPLGET:	SKIPE SPLFRK		;Do we have a fork yet?
	IFSKP.
	  MOVX A,CR%CAP		;Create a fork for SPELL
	  CFORK%
	  JERROR <Can't create SPELL fork>
	  MOVEM A,SPLFRK	;Save the fork handle
	ENDIF.
	MOVX A,GJ%OLD!GJ%SHT
	HRROI B,SPLNAM		;Name of SPELL program
	GTJFN%
	 JERROR <Can't find find SPELL program>
	HRL A,SPLFRK		;Get the fork handle again
	GET%
	MOVE A,SPLFRK		;Return with the fork handle in A
	RET

;;;Routine to run SPELL
;;;On entry:
;;; SPLFRK/ fork handle of SPELL or 0 if none yet
;;; SPLIFL/ JFN of the text to correct (should be in the file already)
;;; SPLOFL/ JFN of where to write the corrected code
;;;Clobbers ACs: A, B, C

SPLRUN:	SAVEAC <D>
	STKVAR <<SPLACS,20>>
	MOVEI B,SPLACS		;Get the old ACs
	RFACS%
	IFJER.
	  JSNARL <Can't access SPELL>
	  CALLRET SPLPNT	;Punt SPELL
	ENDIF.
	MOVE C,SPLIFL		;Set the input
	MOVEM C,A(B)
	MOVE C,SPLOFL		;And output JFNs in SPELL's ACs
	MOVEM C,B(B)
	SFACS%
	MOVEI B,SPLOFF		;Start SPELL, HERMES entry point
	SFRKV%
	IFJER.
	  JSNARL <Can't start SPELL, probably wrong version>
	  CALLRET SPLPNT
	ENDIF.
	WFORK%			;And wait for SPELL to finish
	DMOVE A,PRGNAM		;Restore our program name
	SETSN%
	 JFATAL
	MOVEI D,SAVMOD		;Restore TTY modes
	CALL SETTYM
	RETSKP

	ENDSV.

;;;Routine to get (into the edit buffer) the changed text
;;;On entry:
;;; A/ pointer to where to put the text
;;; C/ size of where to put the text
;;; SPLOFL/ JFN for SPELL's output file (not opened)
;;;On exit:
;;; SPLOFL/ same JFN, but closed
;;; A/ pointer to buffer
;;; B/ pointer to end of buffer
;;; C/ count (in characters) of size of buffer
;;;Clobbers AC: B

SPLOCP:	PUSH P,A		;Save where to put the text
	PUSH P,C		;And size of area
	MOVE A,SPLOFL		;Get the JFN again
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD> ;Open for read this time
	OPENF%
	 JERROR
	MOVE B,-1(P)		;Get the start of the area
	MOVN C,(P)		;Maximum count
	SIN%			;Read it in there
	 ERJMP .+1
	SKIPL C
	 SNARL <Buffer overflow - file too large>
	TXO A,CO%NRJ		;Don't release the JFN (yet)
	CLOSF%
	 NOP
	ADD C,(P)		;Compute byte count
	ADJSP P,-1
	POP P,A			;And point to start of text
	RET

;;;Routine to punt SPELL after an error (execute-only, wrong version, etc.)

SPLPNT:	CALL SPLCLN		;Clean up
	SKIPE A,SPLFRK		;Now kill the fork
	 KFORK%
	SETZM SPLFRK
	SAVEAC <B,C,D>
	MOVEI D,SAVMOD		;Restore TTY modes
	CALLRET SETTYM

;;;Routine to clean up after finishing with SPELL this time
;;;On exit:
;;; SPLIFL/ garbage
;;; SPLOFL/ garbage
;;;Clobbers ACs: a

SPLCLN:	HRRZ A,SPLIFL
	TXO A,DF%EXP		;Delete and expunge the input file
	DELF%
	 ERJMP .+1		;Ignore errors
	HRR A,SPLOFL		;Same here
	DELF%
	 ERJMP .+1
	RET
	SUBTTL Editor interfacing subroutines

;;;Edit commands

.EDIT:	CALL DFSQTH		;Edit specifies messages
	MOVX A,.REDI1
	CALLRET DOMSGS

.REDIT:	CONFRM			;Edit this message
.REDI1:	CALL CHKDEL
	 RET
	TXNE F,F%RONL		;File read-only?
	 ERROR (File is read-only)
	CALL SEDMSG		;Set editor to munge current message
	IFXN. F,F%TECO		;TECO based?
	  MOVE A,EDBPAG+0(T)	;BJ
	  MOVEM A,EDBPAG+2(T)
	ENDIF.
	DMOVE A,[ASCIZ/Message/]
	DMOVEM A,EDINAM
	DMOVEM A,BUFNAM
	CALL RESTED		;Now edit it
	CALL GEDBUF		;Get the editted text
	CALL .EDFIN		;Go restore TTY modes
	CALL RPLMSG		;Replace current message with that
	 SNARL <Unable to update text from editor>
	MOVX A,M%SEEN		;Mark message as seen
	IORM A,MSGBTS(M)
	RET
	
;;;Prepare for editting the current message

SEDMSG:	DMOVE A,[ASCIZ/Message/]
	DMOVEM A,BUFNAM
	HRRZ V,MSGBOD(M)
	CALL MCH2BP		;Get byte pointer to message
	HLRZ B,MSGBOD(M)	;And length
	CALLRET EDREPL		;Load message into the editor

;;;Edit message text

.EDTXT:	SETABT			;Save previous abort state
	CALL ABNDIS		;Don't CTRL/N out of editor
	DMOVE A,[ASCIZ/Reply/]	;Name of the buffer
	DMOVEM A,BUFNAM
	SKIPGE M.RPLY
	 MOVE A,[ASCIZ/Send/]	;Only 5 chars needed here
	DMOVEM A,EDINAM		;Name of edit type
	MOVE A,[POINT 7,TXTPAG]	;Starting byte pointer
	MOVE B,TXTPTR		;Ending one
	CALL SEDBUF		;Setup editor text
	IFXN. F,F%TECP		;Hairy interface?
	  CALL SEDMSG		;Yes, put message in the message buffer
	  CALL SEDHDR		;And headers in the headers buffer
	ENDIF.
	DMOVE A,[ASCIZ/Reply/]
	DMOVEM A,BUFNAM
	CALL RESTED		;Run the editor
	CALL GEDTXT		;Get the new text
	TXNE F,F%TECP
	 CALL GEDHDR		;Get updated headers if need be
	CALLRET .EDFIN

;;;Get it back and update if necessary

GEDTXT:	DMOVE A,[ASCIZ/Reply/]
	DMOVEM A,BUFNAM
	CALL GEDBUF		;Get the editted text
	MOVE B,[POINT 7,TXTPAG]	;Replace it here
	CALL FRMSN1		;Move string
	MOVEM B,TXTPTR		;Update pointer
	SETZ D,
	IDPB D,B		;And end with a null too
	RET

;;;All done, restore TTY modes for program

.EDFIN:	SAVEAC <A,B,C,D>
	MOVEI D,EDMOD		;Save editor modes
	CALL GETTYM
	MOVEI D,SAVMOD		;And restore ours
	CALLRET SETTYM
;;;Edit headers

.EDHEA:	DMOVE A,[ASCIZ/Default/]
	DMOVEM A,EDINAM
	CALL SEDHDR		;Put in headers
	CALL RESTED		;Edit them
	CALL GEDHDR		;Get new ones
	CALLRET .EDFIN		;And all done

;;;Put in headers

SEDHDR:	MOVE O,[POINT 7,WRTPGS]	;Some temp space
	TXZ F,F%QUOT		;Don't quote it
	TXO F,F%RELD		;Include relative domains
	CALL MOVTO
	CALL MOVCC1
	CALL MOVSUB
	CALL MOVREP		;And Reply-To
	DMOVE A,[ASCIZ/Headers/]
	DMOVEM A,BUFNAM
	MOVE A,[POINT 7,WRTPGS]	;Starting pointer
	DO.
	  ILDB B,A
	  CAIE B,.CHCRT		;Move over blank lines
	   CAIN B,.CHLFD
	    LOOP.
	ENDDO.
	ADD A,[7B5]
	MOVE B,O		;Ending one
	CALLRET SEDBUF		;Setup editor for that

;;;Get the new headers

GEDHDR:	DMOVE A,[ASCIZ/Headers/]
	DMOVEM A,BUFNAM
	CALL GEDBUF		;Get what it gave back
	PUSH P,A		;Save pointers to editor text
	PUSH P,C
	CALL .ERSTO		;Erase to field
	CALL .ERSCC		;And cc field
	CALL .ERSSB		;And subject field
	POP P,C			;Get back pointers
	POP P,A
	JUMPLE C,R		;No text there
	ADJBP C,A		;Get ending byte pointer
	SETZ D,
	IDPB D,C		;Put a null at the end
	TXZ F,F%CC		;Start with to field
	TXO F,F%RELD		;Include relative domains
	SETZ E,			;No default host name
GEDHD1:	ILDB B,A		;Get next char
	CAIE B,.CHTAB		;Whitespace indicates continuation
	 CAIN B,.CHSPC
	  JRST GEDHDS
	CAIE B,"T"		;More to maybe
	 CAIN B,"t"
	  JRST GEDHTO
	CAIE B,"C"		;Or maybe start of cc
	 CAIN B,"c"
	  JRST GEDHCC
	CAIE B,"s"
	 CAIN B,"S"
	  JRST EDSUBJ		;Get the subject now
GEDHD2:	CAIN B,.CHLFD		;Saw linefeed yet?
	 JRST GEDHD1		;Yes, try this line
	JUMPE B,R		;Keep on going unless EOM
	ILDB B,A		;Otherwise soak up line
	JRST GEDHD2

GEDHTO:	ILDB B,A
	CAIE B,"O"
	 CAIN B,"o"
	  CAIA
	   JRST GEDHD2		;Soak up line if no match
	ILDB B,A
	CAIE B,":"
	 JRST GEDHD2		;No good I guess
GEDHDS:	CALL PRADDT		;Parse this line
	LDB B,A			;Get terminating character
	JUMPE B,R		;Null means all done now
	CAIN B,.CHCRT		;Was terminator CR?
	 IBP A			;Yes, move over the LF too
	JRST GEDHD1		;Try for another line

GEDHCC:	ILDB B,A
	CAIE B,"C"
	 CAIN B,"c"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,":"
	 JRST GEDHD2
	TXO F,F%CC		;Now doing cc
	JRST GEDHDS		;And now go get addresses

EDSUBJ:	ILDB B,A
	CAIE B,"U"
	 CAIN B,"u"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,"B"
	 CAIN B,"b"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,"J"
	 CAIN B,"j"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,"E"
	 CAIN B,"e"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,"C"
	 CAIN B,"c"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,"T"
	 CAIN B,"t"
	  CAIA
	   JRST GEDHD2
	ILDB B,A
	CAIE B,":"
	 JRST GEDHD2
EDSUB1:	ILDB B,A
	JUMPE B,R
	CAIE B,.CHLFD
	 CAIN B,.CHCRT
	  JRST GEDHD2
	CAIE B,.CHSPC
	 CAIN B,.CHTAB
	  JRST EDSUB1
	MOVE C,[POINT 7,SUBBUF]
EDSUB2:	IDPB B,C
	ILDB B,A
	CAIE B,.CHCRT
	 CAIN B,.CHLFD
	  SETZ B,
	JUMPN B,EDSUB2
	IDPB B,C
	JRST GEDHD2
;;;Setup the editor's text

SEDBUF:	LDB C,[POINT 6,B,5]
	IDIVI C,7		;Get chars within word
	SUBI B,(A)
	HRREI B,(B)		;Get number of words
	IMULI B,5		;Into chars
	SUBI B,(C)		;Get total number of chars
	LDB C,[POINT 6,A,5]
	IDIVI C,7
	ADDI B,(C)
;	CALLRET EDREPL		;Run editor over this field

;;;Replace the editor's buffer with a given string, BP in A, byte count in B

EDREPL:	STKVAR <EDTBYT,EDTCNT,EDTJFN,EDTCMD>
	MOVEM A,EDTBYT		;Save byte pointer
	MOVEM B,EDTCNT		;Save character count
	CAIG B,5*^D512*NEDPGS	;Larger than buffer?
	IFSKP.
	  CALL KILED0		;Kill the editor fork
	  ERROR <Buffer overflow - Message is too large to be EDITed>
	ENDIF.
	SUBI B,5*^D512*NEDPGS	;Get difference (do it this way so we
	MOVMS B			; have difference for warning message)
	CAILE B,^D5000		;Within 5000 characters of limit?
	IFSKP.
	  WARN <Message is very large; only %2D free characters in the edit buffer>
	  TXNE F,F%SEND		;Inside SEND level?
	   SKIPA A,[[ASCIZ/SAVE-DRAFT/]]
	    MOVEI A,[ASCIZ/MOVE/]
	  CIETYP <
 If your editing will add more than %2D characters you should "%1S"
 the message to a temporary file and edit it that way.  If your editor
 is an MIT-TECO based editor you have even less space due to TECO
 overhead storage.
>
	  MOVEI A,^D5000	;Be sure the warning message stays on
	  DISMS%		; the screen long enough to be seen
	ENDIF.
	SKIPG EDFORK		;If don't have a fork yet,
	IFSKP.
	  MOVEI D,EDMOD		;Yes, Restore editor TTY modes
	  CALL SETTYM		; in case clobbered by error
	ELSE.
	  CALL GETED		;Get one now
	  IFXE. F,F%TECO	;If it isn't TECO based, must use temp file
	    MOVEM A,EDTCMD	;Save start of rescan
	    MOVX A,GJ%SHT!GJ%FOU ;Note: can't use GJ%TMP because of
	    MOVX B,<<FLD 7,OF%BSZ>!OF%WR> ; of cretinous EDIT-20
	    CALL GEDTMP
	    CIETYP <[You must write out file %1J when done editing]
>
	    MOVEM A,EDTJFN	;Save JFN of temp file to edit
	    MOVEI A,^D5000	; if they run TV, etc...
	    DISMS%
	    MOVE A,EDTCMD	;Get back pointer for rescan
	    MOVE B,EDTJFN	;File name to output
	    MOVE C,[001110,,JS%PAF]
	    JFNS%
	    MOVEI B,CRLF0	;Finish up command line
	    CALL MOVST0
	    MOVE A,EDTJFN	;Recover JFN
	    MOVE B,EDTBYT	;Recover pointer
	    MOVN C,EDTCNT	;Recover count
	    SOUT%		;Write it out
	    CLOSF%
	     NOP
	    CALLRET RUNED	;And go start it
	  ENDIF.
	  MOVEI B,[ASCIZ/0FSExit/] ;Telling it to return right away,
	  CALL MOVST0
	  CALL RUNED		;Start up the editor
	  SKIPN FRKACS+3	;Exitted other than with 0?
	ANSKP.
	  TXO F,F%TECP		;Say have hairy editor interface
	  CALL RESTE0		;And continue it
	ENDIF.
	LDB T,[POINT 9,FRKACS+2,35] ;Get position in page
	IFXE. F,F%TECP		;Fancy interface, FS Superior will do it all
	  MOVE B,EDBPAG+5(T)	;Save addr of end of buffer
	  MOVSI A,EDBPAG+0(T)	;Start with beginning addr
	  HRRI A,EDBPAG+1(T)	;Into virtual beg
	  BLT A,EDBPAG+5(T)	;Up to end pointer
	  SUB B,EDBPAG+5(T)	;See how many chars we "deleted"
	  ADDM B,EDBPAG+6(T)	;Increase the gap that many
	  SETZM EDBPAG+11(T)	;Not modified yet
	ENDIF.
	MOVE B,EDTCNT		;Get char count
	CALL EDINSC		;Request it to insert
	MOVE A,EDBPAG+2(T)	;Address of current position
	SUB A,EDTCNT		;Back over the chars to be inserted
	CALL EDCHRP		;Get byte pointer
	MOVE B,EDTBYT		;Get back byte pointer
	MOVE C,EDTCNT		; and character count
	DO.
	  ILDB D,B
	  IDPB D,A
	  SOJG C,TOP.		;For all requested
	ENDDO.
	RET

	ENDSV.
;;; Generate a temp file unique to this job

GEDTMP:	STKVAR <GTJARG,OPNARG,<TMPFIL,10>>
	MOVEM B,OPNARG		;Save OPENF% args
	MOVEM A,GTJARG		;Save GTJFN% args
	HRROI A,TMPFIL		;Some string space
	MOVE B,MYJOBN		;Job number
	MOVX C,<NO%LFL!NO%ZRO+3B17+^D10>
	NOUT%
	 MOVE A,[POINT 7,TMPFIL]
	MOVEI B,[ASCIZ/MM.TMP.0;P770000/] ;Can't be ;T -- EDIT detests it!
	CALL MOVST0
	MOVE A,GTJARG
	HRROI B,TMPFIL
	GTJFN%
	IFJER.
	  CALL KILED0
	  JERROR <Can't get TMP file>
	ENDIF.
	MOVE B,OPNARG
	OPENF%
	IFJER.
	  CALL KILED0
	  JERROR <Can't open TMP file>
	ENDIF.
	RET
;;;Here to make a new editor, returns with start of JCL in A and F%TECO
;;;setup correctly

GETED:	SETZM CMDGTB		;Get space for GTJFN%
	MOVE A,[CMDGTB,,CMDGTB+1] ;Allows:
	BLT A,CMDGTB+.GJATR	; DEFINE EDITOR:SYS:EMACS
	MOVX A,GJ%OLD		;to work instead of only:
	MOVEM A,CMDGTB+.GJGEN	; DEFINE EDITOR:SYS:EMACS.EXE
	MOVE A,[.NULIO,,.NULIO]	;--subtle, but consistent with
	MOVEM A,CMDGTB+.GJSRC	;  how EXEC handles same...
	HRROI A,[ASCIZ/EXE/]
	MOVEM A,CMDGTB+.GJEXT
	MOVEI A,CMDGTB
	HRROI B,[ASCIZ/EDITOR:/]
	GTJFN%
	 JERROR <Can't get editor>
;	JRST GETED0		;Fall through
GETED0:	PUSH P,A		;Save JFN around fork creation
	MOVX A,CR%CAP!CR%ACS
	MOVEI B,FRKACS		;Set these initial ac's
	CFORK%
	IFJER.
	  POP P,A		;Release editor JFN
	  RLJFN%
	   NOP
	  JERROR <Can't create editor fork>
	ENDIF.
	MOVEM A,EDFORK		;Save it
	POP P,A			;Restore JFN
	MOVE B,[1,,.FBUSW]
	MOVEI C,C
	GTFDB%			;Get user word
	TXZ F,F%TECO!F%TECP	;Assume not TECO based
	HLRZ C,C
	CAIN C,(<SIXBIT /TEC/>)	;Check for TECO based
	 TXOA F,F%TECO		;It is, remember that
	  CIETYP <[%1J is not MIT-TECO based]>
	HRL A,EDFORK
	GET%			;Get in the editor
	IFJER.
	  CALL KILED0
	  JERROR <Can't load editor>
	ENDIF.
	DMOVE A,[POINT 7,STRBUF+1 ;Load pointer to JCL string
		 ASCII/EDIT /]	;"EDIT" works better than pgm name
	MOVEM B,STRBUF		; because some editors check job name
	RET			; and others only know CREATE/EDIT
;;;Here to run the editor

RUNED:	HRROI A,STRBUF		;Set that up for user
	RSCAN%
	 NOP
	MOVE A,EDFORK
	SETZ B,			;Start at normal entry
	MOVE C,[SFRKV%]
	JRST WAITED
;;;Here to restart fork to edit something

RESTED:	TXNN F,F%TECO		;Already all done if not TECO based
	 RET
	TXNN F,F%TECP		;Hairy interface?
	 JRST RESTE0		;Just resume editor
	MOVE A,[POINT 7,STRBUF]	;Else setup to tell all that's going on
	MOVEI B,[ASCIZ/FOO /]
	CALL MOVSTR
	MOVEI B,BUFNAM
	CALL MOVSTR
	MOVEI B,.CHESC
	IDPB B,A
	MOVEI B,EDINAM
	CALL MOVSTR
	MOVEI B,CRLF0
	CALL MOVST0
	CALLRET RUNED		;Start over so ..L run again

RESTE0:	MOVE B,EFRKPC		;Forks old PC
	MOVE C,[SFORK%]
;	JRST WAITED
;;;Here to wait for the editor fork

WAITED:	CALL WAITE1		;Run editor, allow CTRL/Ns
	DMOVE A,PRGNAM		;Restore our name
	SETSN%
	 JFATAL
	MOVE A,EDFORK
	TXNE F,F%TECO		;TECO based?
	 JRST EDFTRM		;Yes, check on it's status
	KFORK%			;No, can't reuse it
	SETOM EDFORK		;Forget we had it at all
	RET

WAITE1:	SETABT			;Save previous abort state
	CALL ABNDIS		;Don't abort out of editor
	MOVE A,EDFORK
	XCT C			;Do SFRKV% or SFORK%
	RFORK%			;Thaw it
	WFORK%			;And wait for it to terminate
	SETZM ABORTF		;Clear abort flag
	RET
;;;Here when fork terminates

EDFTRM:	FFORK%			;Freeze it
	RFSTS%			;Get its status
	TXZ A,RF%FRZ		;We know it's frozen already
	HLRZ A,A
	CAIE A,.RFHLT		;Voluntary termination?
	 JRST KILLED		;No, kill it off, it's bombed
	MOVEM B,EFRKPC		;Save the PC for restarting it
	MOVE A,EDFORK		;Need fork again
	RWM%			;See why it stopped
	TXNE B,1B1		;Level 1 in progress?
	 JRST CTLCED		;Yes, means the guy CTRL/C'd out
	MOVE A,EDFORK
	MOVEI B,FRKACS		;Get its AC's
	RFACS%
	MOVE A,FRKACS+2		;Pointer to buffer block
	IDIVI A,1000		;Get page number of block
	MOVEI T,(B)		;Save position in page
	HRL A,EDFORK
	MOVE B,[.FHSLF,,EDBPAG/1000] ;Into our area
	MOVX C,PM%CNT!PM%RD!PM%WR!2 ;Read write
	PMAP%
	MOVE A,EDBPAG(T)	;Char address of beginning of buffer
	IDIVI A,5000		;Get page number
	HRL A,EDFORK
	MOVE B,[.FHSLF,,EDPAGE/1000]
	MOVX C,PM%CNT!PM%RD!PM%WR!NEDPGS
	PMAP%			;Map those pages too, read/write
	LSH A,9			;Get word address
	HRREI A,-EDPAGE(A)
	MOVEM A,EDPAG0		;Save address of first page mapped
;;;
;;; The argument to FS Exit has not been well-defined in the past, so here
;;;is its definition today:
;;;	LH	RH	Action
;;;---------------------------
;;; .GE. 0		No special action (1 at setup indicates MMAIL loaded)
;;; .LT. 0  .GE. 0	LH is command, RH is new current message
;;;     -1  .LT. 0	Entire value is command
;;;.LT. -1  .LT. 0	LH is command, RH ignored
;;;
;;; The commands are:
;;; -1	Send the message off
;;; -2	Return without updating message
;;; -3	Return, updating the message
;;; -4	Reply to the current message

	HLRE A,FRKACS+3		;Negative argument to FS Exit?
	JUMPGE A,R		;No, done
	HRRE B,FRKACS+3		;Select a different message?
	IFL. B
	  CAMN A,[-1]		;No; was LH -1?
	   MOVE A,FRKACS+3	;Yes, then RH may be significant
	ELSE.
	  IMULI B,MSGLEN
	  CAMG B,LASTM		;And in range
	   MOVE M,B		;Select it
	ENDIF.
	AOJE A,FSEXT1
	AOJE A,FSEXT2
	AOJE A,FSEXT3
	AOJE A,FSEXT4
	RET

FSEXT1:	TXO F,F%ESND		;-1FS Exit -- send the message off
	RET

FSEXT2:	CALL .EDFIN		;-2FS Exit -- don't update fields
	ERROR <Edit aborted by editor>

FSEXT3:	CALL SEDMSG		;-3FS Exit -- update current msg
	CALLRET RESTE0

FSEXT4:	CALL .REPL6		;-4FS Exit -- reply to message
	TXNE F,F%DIRE		;Dired mode?
	 TXO F,F%DIRR		;Yes, indicate want reentry
	RET
;;;Editor terminated badly

KILLED:	CALL KILED0		;Kill editor
	ERROR <Editor fork terminated involuntarily, edit lost>

KILED0:	SKIPLE A,EDFORK
	 KFORK%			;Kill it off
	SETOM EDFORK		;And forget about it
	MOVEI D,SAVMOD		;Restore program's modes
	CALLRET SETTYM

;;;CTRL/C typed from editor, make it percolate up

CTLCED:	HALTF%
	CALLRET RESTE0		;And resume it afterwards
;;;Get the editted field

GEDBUF:	TXNN F,F%TECO		;Was this TECO based editor
	 JRST GEDBF2		;No, get updated version of file
	MOVE B,EDBPAG+4(T)
	MOVEM B,EDBPAG+2(T)	;ZJ
	TXNN F,F%TECP		;Ordinary TECO,
	 TDZA B,B		;Insert 0 chars
	  SETO B,		;Else negative so don't kill
	CALL EDINSC		;Move gap to end
	MOVE C,EDBPAG+4(T)
	SUB C,EDBPAG+1(T)	;Number of chars in it
	MOVE A,EDBPAG+1(T)	;Start of virtual buffer
	CAML A,EDBPAG+3(T)
	 ADD A,EDBPAG+6(T)
;	CALLRET EDCHRP		;Get byte pointer and return

;;;Convert char address to byte pointer, taking gap into account

EDCHRP:	STKVAR <EDTADR,<EDTACS,4>>
	IDIVI A,5
	SUB A,EDPAG0		;Make absolute
	MOVEM B,EDTADR		;Save address
	MOVEI B,EDPAGE+<^D512*NEDPGS> ;Last possible address of edited text
	SUBI B,(A)		;Free words
	IMULI B,5		;Number of characters free
	SUB B,EDTADR		;...after offsetting for partial word
	CAMG C,B		;Count from editor greater than buffer?
	IFSKP.
	  DMOVEM A,EDTACS
	  DMOVEM C,2+EDTACS
	  MOVEI D,SAVMOD	;Restore program's modes
	  CALL SETTYM
	  DMOVE B,1+EDTACS	;Get buffer arguments
	  WARN <Edited text (%3D characters) too large, text truncated to %2D characters
>
	  PROMPT <Do you want to abort the edit and cancel all changes? >
	  CALL YESNO1
	  IFSKP.
	    CALL KILED0
	    ERROR <Edit aborted>
	  ENDIF.
	  MOVEI D,EDMOD		;Restore editor tty modes
	  CALL SETTYM
	  MOVE A,EDTACS		;Restore AC's
	  MOVE C,1+EDTACS	;Set message size to what we can get
	  MOVE D,3+EDTACS
	ENDIF.
	MOVE B,EDTADR		;Get editing address
	HLL A,BPS(B)		;Make byte pointer
	RET

	ENDSV.
; Here for text retrieval from non-EMACS editor
GEDBF2:	STKVAR <FILJFN,<FILINF,2>>
	MOVX A,GJ%OLD!GJ%SHT
	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	CALL GEDTMP		;Find the temp file again
	MOVEM A,FILJFN		;Save JFN
	HRROI B,EDPAGE		;Where to put it
	MOVX C,-<NEDPGS*1000*5>
	SIN%
	 ERJMP .+1
	IFGE. C
	  MOVE B,[2,,.FBBYV]	;Get file I/O info and byte size
	  MOVEI C,FILINF	;Get file info into there
	  GTFDB%
	  MOVE C,1+FILINF	;Get byte count
	  LOAD B,FB%BSZ,FILINF	;Get byte size
	  CAIN B,7		;If not 7-bit, must figure things out
	ANSKP.
	  CAIN B,^D36		;If not 36 bit, we have to do it the hard way
	  IFSKP.
	    MOVEI A,^D36
	    IDIVI A,(B)		;Get number of bytes/word
	    IDIVI C,(A)		;Now number of words
	  ENDIF.
	  IMULI C,5		;Convert words into bytes
	ENDIF.
	MOVE A,FILJFN
	SETZ B,			;Editor may have made new non-temp vers
	DELNF%
	 NOP
	CLOSF%
	 NOP
;;;At this point C either has a positive file byte count if the file was too
;;;large or a negative free space byte count.
	IFL. C			;Free space exists?
	  ADDI C,NEDPGS*1000*5	;Yes, compute byte count used by text
	ELSE.	
	  MOVE B,C		;Else get size of file in B
	  MOVX C,NEDPGS*1000*5	;Get size of our buffer in C
	  CAMN B,C		;File exactly fits into buffer?
	ANSKP.
	  WARN <Edited text (%2D characters) too large, text truncated to %3D characters
>
	  PROMPT <Do you want to abort the edit and cancel all changes? >
	  CALL YESNO1
	ANSKP.
	  CALL KILED0
	  ERROR <Edit aborted>
	ENDIF.
	MOVE A,[POINT 7,EDPAGE]
	RET

	ENDSV.
;;;Request editor to insert (b) chars at PT

EDINSC:	MOVEM B,EDBPAG+8(T)	;Set up as SUPARG
	MOVE A,[POINT 7,STRBUF]
	MOVEI B,[ASCIZ/FOO /]	;Be stupid or TECO will outsmart itself
	CALL MOVSTR
	MOVEI B,BUFNAM
	CALL MOVSTR
	MOVEI B,CRLF0
	CALL MOVST0
	HRROI A,STRBUF		;Tell it which buffer to use
	RSCAN%
	 NOP
	MOVE A,EDFORK
	HRRZ B,EDBPAG+7(T)	;Where to start it
	SFORK%			;Start it
	RFORK%			;Thaw it
	WFORK%			;Wait for it
	MOVEI D,EDMOD		;Save editor modes
	CALL GETTYM
	DMOVE A,PRGNAM		;Restore our name
	SETSN%
	 JFATAL
	MOVE A,EDFORK
	CALLRET EDFTRM		;Remap the right page, etc
;;;Message dired mode

.DIRED:	CALL DFSQAL		;Get sequence, default to all messages
	TXO F,F%DIRE		;Entering dired mode
	PUSH P,[POINT 7,WRTPGS]	;Get some string space
	DO.
	  CALL NXTMSG
	   EXIT.
	  MOVE O,(P)
	  CALL TYPHD0		;Insert the headers
	  MOVEM O,(P)
	  LOOP.
	ENDDO.
	DMOVE A,[ASCIZ/Dired/]
	DMOVEM A,BUFNAM
	DMOVEM A,EDINAM
	MOVE A,[POINT 7,WRTPGS]	;Starting pointer
	POP P,B			;Ending
	CALL SEDBUF		;Stick it in the editor
	IFXE. F,F%TECP
	  WARN <You do not have the EMACS MMail interface loaded
Type "HELP DIRED" at MM top-level for more information
>
	  MOVEI A,^D5000	;Make sure message stays around a bit
	  DISMS%
	ENDIF.
	TXZ F,F%DIRR		;Don't need to loop yet
	DO.
	  CALL RESTED		;Run the editor over it
	  TXZE F,F%DIRR		;Reenter?
	   LOOP.		;Yes, do so
	ENDDO.
	TXZ F,F%DIRE		;Done with dired mode
	DMOVE A,[ASCIZ/Dired/]
	DMOVEM A,BUFNAM
	CALL GEDBUF		;Get what it gave back
	JUMPLE C,R		;No text there
	ADJBP C,A		;Get ending byte pointer
	SETZ D,
	IDPB D,C		;Put a null at the end
DIRED3:	ILDB B,A		;Get start of line
	JUMPE B,.EDFIN		;All done
	SETZ E,			;Accumulate bits here
	ILDB B,A		;Seen
	CAIN B,.CHSPC
	 TRO E,M%SEEN
	ILDB B,A		;Flagged
	CAIE B,.CHSPC
	 TRO E,M%ATTN
	ILDB B,A
	CAIE B,.CHSPC
	 TRO E,M%RPLY
	ILDB B,A
	CAIE B,.CHSPC
	 TRO E,M%DELE
	MOVEI C,^D10
	NIN%
	IFNJE.
	  IMULI B,MSGLEN
	  MOVEI M,-MSGLEN(B)
	  MOVEI B,M%SEEN!M%ATTN!M%DELE ;Change these bits
	  ANDCAM B,MSGBTS(M)
	  IORM E,MSGBTS(M)
	  PUSH P,A
	  CALL UPDBIT
	  POP P,A
	ENDIF.
	DO.
	  ILDB B,A		;Flush the rest of the line
	  JUMPE B,.EDFIN
	  CAIE B,.CHLFD
	   LOOP.
	ENDDO.
	JRST DIRED3
	SUBTTL Init file handler

;;;For the time being the syntax is just
;;;<variable> <val><crlf>, where val is just an octal number or string

;;;Reset all init file variables
ININIT:	SETZM VARBEG		;Most variables are zero
	MOVE A,[VARBEG,,VARBEG+1]
	BLT A,VAREND
	SETOM RFMDEF		;Reply<cr> means just from, not all
	SETOM BLSCST		;Blank screen on startup
	SETOM CRSEND		;Just return sends message
	SETOM LSTHDR		;Output a list of headers in listings
	SETOM RCCOTH		;Reply CC's others (less confusion)
IFN NICSW,<
	SETOM LPTCFM		;[NIC] LPT will not ask stupid questions!
>
	AOS SNDVBS		;Degree of sending verbosity
	AOS EDTFLG		;Always edit on ^E
	MOVEI A,^D1500		;Default "short" msg length
	MOVEM A,DFSHML
	DMOVE A,[ASCII/MM>/	;Top-level prompt
		 ASCII/M>/]	;Message-sequence prompt
	MOVEM A,TOPRMT
	MOVEM B,MSPRMT
	DMOVE A,[ASCII/R>/	;Read prompt
		 ASCII/S>/]	;Send prompt
	MOVEM A,REPRMT
	MOVEM B,SEPRMT
	MOVE A,[POINT 7,LSTDEV]	;Set default listing device
	HRROI B,[ASCIZ/LPT:MM.LST/]
	CALL MOVST0
	MOVE A,[POINT 7,MCPFIL]	;Set mail copy filename
	HRROI B,[ASCIZ/MAIL.CPY/]
	CALL MKPSTR		;Make file name string
	MOVEI A,KEYPAG
	MOVEM A,KEYPTR		;Initialize pointer free space
	SETZM USRHDR		;Reset user headers
	MOVE A,[POINT 7,DEFBBD]	;Setup a default for everybody
IFE NICSW,<
	MOVEI B,MLBXDV		;Post office box name
>;IFE NICSW
IFN NICSW,<
	MOVEI B,BBDEV		;Post office box name
>;IFN NICSW
	CALL MOVSTR
	MOVX B,":"		;Device delimiter
	IDPB B,A
IFE NICSW,<
	MOVX B,.CHLAB		;Directory delimiter
	IDPB B,A
	MOVEI B,BBDIR		;BBoard directory
	CALL MOVSTR
	MOVX B,.CHRAB		;Directory delimiter
	IDPB B,A
>;IFE NICSW
	MOVEI B,MLBXFN		;Filename
	CALLRET MOVST0		;Set it up and return to caller
;;;Here to process an init file with JFN in A
DOINIT:	MOVX B,<<FLD 7,OF%BSZ>!OF%RD>
	OPENF%
	IFJER.
	  RLJFN%		;Discard JFN
	   NOP			;Don't care
	  MOVEI A,STRBUF
	  JWARN <Can't open init file "%1S">
	  RET
	ENDIF.
	MOVEM A,INIJFN		;Save this for later
INILUP:	SKIPG A,INIJFN
	 RET			;Bug trap
	HRROI B,STRBUF
	MOVEI C,STRBSZ*5
	MOVEI D,.CHLFD		;Read a line
	SIN%
	 ERJMP CLSINI		;All done with it
	MOVE T,[POINT 7,STRBUF]	;Handle this line
INILP1:	ILDB C,T
	CAIE C,.CHCRT		;Reached end of line
	 CAIN C,.CHLFD
	  JRST INILPX		;Can't understand it then
	CAIE C,.CHTAB		;Tab or space ok
	 CAIN C,.CHSPC
	  JRST INILP2
	JRST INILP1

INILP2:	SETZ C,
	DPB C,T			;Stick in a null
	MOVEI A,INIVTB		;Init file variables
	HRROI B,STRBUF
	TBLUK%
	TXNE B,TL%NOM!TL%AMB	;No good?
	 JRST INILPX		;Yes, complain
	HRRZ A,(A)
	HRRZ U,(A)		;Get address of corresponding variable
	HLRZ E,(A)		;E points to [INIDTA,,HLPMSG]
	HLRE E,(E)		;Get string length allowed
	JUMPE E,INIOCT		;Zero means variable is fixnum
IFN NICSW,<
	CAIE E,.UNIT		;Printer unit keyword?
	IFSKP.			;Yes...
	  SKIPN PRNUNT		;Got one?
	  CALL PRNINI##		;No, then get it
	  JRST INILUP
	ENDIF.
>;IFN NICSW
	CAIN E,INIDEC		;Want decimal number?
	 JRST INIDEC
	IFG. E
	  CALL (E)		;Call routine if there is one
	  JRST INILUP
	ENDIF.
	HRLI U,(<POINT 7,>)	;Make byte pointer to it
	DO.			;Now process string
	  ILDB C,T
	  IFN. C
	    CAIE C,.CHCRT	;Exit if end of line
	     CAIN C,.CHLFD
	      EXIT.
	    AOJG E,INILPX	;Ran out of room in variable
	    IDPB C,U
	    LOOP.
	  ENDIF.
	ENDDO.
	MOVEI C,0
	IDPB C,U
	JRST INILUP
;; Get user name
INIUNM::CAIE U,MAUSRS		;Bug check
	 FATAL <INIUNM call error>
	MOVE A,[POINT 7,MAUSRS]	;Set up pointer
	MOVNI E,^D39		;Maximum characters in user name
	DO.			;Now process string
	  ILDB C,T
	  IFN. C
	    CAIE C,.CHCRT	;Exit if end of line
	     CAIN C,.CHLFD
	      EXIT.
	    IDPB C,A
	    AOJLE E,TOP.
	    SNARL <User name too long>
	    JRST INIUNX
	  ENDIF.
	ENDDO.
	MOVEI C,0
	IDPB C,A
	MOVX A,RC%EMO		;Require exact match
	HRROI B,MAUSRS		;Get pointer
	RCUSR%			;Get user number
	IFNJE.
	  IFXE. A,RC%NOM!RC%AMB	;Valid user name?
	    CAMN C,MYAUSR	;Same as alias user?
	     RET		;Yes, return success
	    MOVE A,MYAUSR	;Get alias user number
	    SNARL <User "%7S" differs from alias "%1U">
	  ELSE.
	    SNARL <No such user as "%7S">
	  ENDIF.
	ELSE.
	  SNARL <Invalid user name>
	ENDIF.
INIUNX:	HRROI A,MAUSRS		;Invalid user name, set name
	MOVE B,MYAUSR		;  to Alias user name
	DIRST%
	 JFATAL
	SKIPN INIJFN		;Init file in progress?
	 RET			;No, just return
	ADJSP P,-1		;Yes, flush caller
	JRST INIERR		;Treat as init file error
;; Number conversion
INIDEC::SKIPA D,["9"]		;Decimal conversion
INIOCT:	 MOVEI D,"7"		;Octal conversion
	SETZB A,B		;Here to input a fixnum variable
	DO.
	  ILDB C,T		;Get next char
	  IFN. C
	    CAIE C,.CHTAB
	     CAIN C,.CHSPC
	      LOOP.		;Ignore blanks
	    CAIE C,.CHCRT
	     CAIN C,.CHLFD	;End of line?
	      EXIT.
	    CAIN C,"-"
	     AOJA A,TOP.	;Negativize
	    CAIL C,"0"
	     CAILE C,(D)
	      JRST INILPX	;Not a proper digit, barf
	    IMULI B,1-"0"(D)	;Scale what we have by one digit
	    ADDI B,-"0"(C)
	    LOOP.
	  ENDIF.
	ENDDO.
	TRNE A,1		;Did it get negative?
	 MOVN B,B		;Yes
	MOVEM B,(U)		;Save variable value
	JRST INILUP

INILPX:	CALL INIERR		;Log error
	JRST INILUP

CLSINI:	MOVE A,INIJFN
	CLOSF%
	 JERROR
	SETZM INIJFN
	RET
;;; Initialize a table of keywords from a comma separated list
INIBB::	TXOA F,F%F4		;Flag BB list
INIKEY:: TXZ F,F%F4		;Flag as Key list
	SETZM (U)		;Originally no keywords
INIKY0:	ILDB C,T		;Get first character
	CAIE C,.CHTAB
	 CAIN C,.CHSPC
	  JRST INIKY0
	CAIE C,.CHCRT		;No entries
	 CAIN C,.CHLFD
	  RET
	JUMPE C,R		;This will happen from SET command
	TXNN F,F%F4		;BBoard?
	 SKIPA A,[^D30]		;Initialize header of table
	  MOVEI A,MAXBBD	;Yes, use this value
	MOVEM A,(U)
	HRLZ B,KEYPTR		;Initial string pntr,,0 (for TBADD%)
	MOVSI D,(<POINT 7,>)
	HLR D,B			;Byte pointer to string
INIKY2:	CAIN C,","		;End of keyword?
	 JRST INIKY3
	IDPB C,D		;Store as a keyword character
INIKY1:	ILDB C,T		;Get next character
	CAIE C,.CHTAB
	 CAIN C,.CHSPC
	  JRST INIKY1
	CAIE C,.CHCRT
	 CAIN C,.CHLFD
	  JRST INIKY3
	JUMPE C,INIKY3		;This will happen from SET command
	JRST INIKY2

INIKY3:	HLRZ A,D		;Check pointer
	CAIE A,(<POINT 7,>)	;Was the keyword null?
	IFSKP.
	  SNARL <Null keyword invalid>
	  JRST INIERR
	ENDIF.
	SETZ A,
	IDPB A,D
	MOVEI A,(U)		;Table pointer
	IFXN. F,F%F4		;BBoard hacking?
	  HLRZ E,(A)		;Yes, simulate TBADD% stuff
	  HRRZ A,(A)
	  AOS E			;Point to next free entry
	  CAMG E,A		;Room left in table?
	  IFSKP.
	    JSNARL <BBoard table full>
	    JRST INIERR
	  ENDIF.
	  HRLM E,(U)		;Update table header with new count
	  ADD E,U		;Make pointer into table for new entry
	  MOVEM B,(E)		;Save string in table
	ELSE.
	  TBADD%
	  IFJER.
	    JSNARL <Keyword setup error>
	    JRST INIERR
	  ENDIF.
	ENDIF.
	ADDI D,1		;Update pointer
	CAIN C,","		;More to come?
	IFSKP.
	  HRRZM D,KEYPTR	;No, update free string pointer
	  RET
	ENDIF.
	HRLI D,(<POINT 7,>)	;Yes, make byte pointer
	HRLI B,(D)		;Update TBADD% copy as well
	AOJA B,INIKY1
;;; Init a string that gets extended by lines
INILNS::ILDB C,T
	CAIE C,.CHTAB
	 CAIN C,.CHSPC
	  JRST INILNS		;Flush whitespace
	ADD T,[7B5]		;Back over first character
	SKIPE D,(U)		;Is there something already?
	IFSKP.
	  MOVNI E,776*5-1	;No, init to start at after 4 words
	  MOVEI D,4(U)
	  HRLI D,(<POINT 7,>)
	ELSE.
	  AOS E,1(U)		;Extend it with a crlf
	  AOJGE E,INIERR
	  MOVEI C,.CHCRT
	  IDPB C,D
	  MOVEI C,.CHLFD
	  IDPB C,D
	ENDIF.
	DO.
	  AOJGE E,INIERR	;Ran out of room in variable
	  ILDB C,T
	  JUMPE C,ENDLP.
	  CAIE C,.CHCRT
	   CAIN C,.CHLFD
	    EXIT.
	  JUMPE C,ENDLP.	;This will happen with SET command
	  IDPB C,D
	  LOOP.
	ENDDO.
	DMOVEM D,(U)		;Store ending pointer and count
	MOVEI C,0		;And end string with null
	IDPB C,D
	RET

INIERR:	SKIPN INIJFN		;Init file in progress?
	 RET			;No, don't do this barfage
	MOVEI A,STRBUF		;Tell user the losing line
	SNARL <Error in MM.INIT: "%1S">
	SETOM INITER		;Note an error happened
	RET
;;; Create a new MM.INIT prompting in ENGLISH!
.PROFI:	CONFRM
	SETABT CMDABO		;Allow aborts to top-level

;;;SEND-VERBOSE-FLAG
	MOVEI A,1		;Set up for super-verbose
	MOVEM A,SNDVBS
	CITYPE <Normally, when you send a message you are told the disposition of
each address; whether it was delivered or queued for later delivery.
>
	PROMPT <Do you want to suppress this typeout? >
	CALL YESNO1
	 CAIA			;No
	  SETOM SNDVBS		;Yes, super-terse

;;;REPLY-INCLUDE-ME
	SETZM RINCME		;Set up for no replies to me
	PROMPT <Do you want to receive copies of your replies to messages? >
	CALL YESNO1
	 CAIA			;No
	  AOS RINCME		;Yes, include me in replies

;;;REPLY-SENDER-ONLY-DEFAULT
	SETZM RFMDEF		;Set up for reply to everybody
	CITYPE <Normally, when you REPLY to or ANSWER a message, the reply will
default to only sending to the person you got the message from.
You can have MM default instead to replying to everybody listed in
the message header.>
	PROMPT <Do you want REPLY to default to everybody? >
	CALL YESNO1
	 SETOM RFMDEF		;No, reply to sender only

;;;BLANK-SCREEN-STARTUP
	SETOM BLSCST		;Set up for screen blanking
	PROMPT <Do you want to erase the screen at startup and between messages? >
	CALL YESNO1
	 SETZM BLSCST		;No, no screen blanking

;;;CONTROL-N-ABORT
	SETZM ABOFLG		;Set up to ask before aborting
	CITYPE <Normally the abort command control-N asks for confirmation before
aborting.>
	PROMPT <Do you want control-N to abort without asking? >
	CALL YESNO1
	 CAIA			;No
	  AOS ABOFLG		;Yes, abort without asking

	CITYPE <Other profile options may be set by using the SET command to set the
option, and CREATE-INIT to update your MM.INIT profile file.  You may
also edit MM.INIT with an editor.  Use the "HELP SET variable-name"
command for a desriptions of individual MM.INIT options, and the SHOW
command to list the complete environment.
>
	CALLRET CRINI0
;;;Show init file parameters

.SHOW:	NOISE (INIT FILE PARAMETERS)
	CONFRM
	MOVX A,.PRIOU
	JRST SHOW1

;;;Create the guy an init file

.CRINI:	CONFRM
CRINI0:	MOVE A,[POINT 7,STRBUF]
	MOVEI B,[ASCIZ/MM.INIT/]
	CALL MAKSTR
	MOVX A,GJ%NEW!GJ%FOU!GJ%SHT
	HRROI B,STRBUF
	GTJFN%
	 JERROR <Can't get init file>
	MOVX B,<<FLD 7,OF%BSZ>!OF%WR>
	OPENF%
	 JWARN <Can't open init file>
SHOW1:	MOVEM A,TMPJFN		;Save this for later
	MOVE U,[-NINVRS,,INIVTB+1]
;	CALLRET CRILUP

CRILUP:	CALL CRISHW		;Call common entry with HELP SET code
	AOBJN U,CRILUP
	CLOSF%
	 JWARN <Couldn't close init file>
	SETOM TMPJFN
	RET

CRISHW: HRRZ T,(U)		;U points to INIVTB entry
	HLRZ A,(T)		;A points to [INIDTA,,HLPMSG]
	HRR T,(T)		;Variable
	HLL T,(A)		;Initial data
	MOVE A,TMPJFN
	HLRO B,(U)		;Get name of variable
	SETZ C,
	TXNE T,.LHALF		;Check for routine type entry
	 JUMPG T,CRILP4
CRILP0:	SOUT%
	MOVEI B,.CHSPC
	BOUT%
	TXNE T,.LHALF		;A string
	 JRST CRILP3		;Yes
	MOVEI C,^D8
CRILP2:	MOVE B,(T)
	NOUT%
	 JWARN
CRILP1:	HRROI B,CRLF0
	SETZ C,
	SOUT%
	RET			;Return

; String-type entry

CRILP3:	HRROI B,(T)		;Type out string
	SOUT%
	JRST CRILP1

; Routine-type entry.  We must handle each of these as a special case

CRILP4:	HLRZ D,T		;Get dispatch item
	CAIE D,INIUNM		;User name?
	IFSKP.
	  HRROS T		;Yes, set up as string
	  JRST CRILP0
	ENDIF.
	CAIE D,INIDEC		;Decimal number?
	IFSKP.
	  SOUT%			;Yes, print string
	  MOVEI B,.CHSPC	;And space
	  BOUT%
	  MOVEI C,^D10		;Set radix
	  JRST CRILP2
	ENDIF.
	CAIE D,INILNS		;HEADER-OPTIONS?
	IFSKP.
	  SKIPN USRHDR		;Are there any user headers?
	   RET			;Return
	  SOUT%			;Yes, write out option name
	  MOVEI B,.CHSPC
	  BOUT%
	  MOVE T,[POINT 7,USRHDT] ;Get pointer to string
	  DO.
	    ILDB B,T		;Get byte from string
	    JUMPE B,CRILP1	;Null means all done
	    BOUT%		;Write byte in file
	    CAIE B,.CHLFD	;Line feed?
	    IFSKP.
	      HRROI B,[ASCIZ/HEADER-OPTIONS /] ;Yes, write new header
	      SOUT%
	    ENDIF.
	    LOOP.
	  ENDDO.
	ENDIF.
	CAIE D,INIBB		;BB table?
	IFSKP.
	  HLRZ D,(T)		;Is there anything in this table?
	  JUMPE D,R		;No, don't hack it -- go get next item
	  SOUT%			;Yes, write out option name
	  MOVEI B,.CHSPC
	  BOUT%
	  HLLO D,(T)		;Get size of table
	  EQVI D,(T)		;Form AOBJN pointer to table
	  ADJSP D,1		;Skip past header word
	  DO.
	    HLRO B,(D)		;Get a keyword string entry
	    SOUT%
	    AOBJP D,CRILP1
	    MOVEI B,","		;Insert comma delimiter
	    BOUT%
	    LOOP.
	  ENDDO.
	ENDIF.
	CAIE D,INIKEY		;Keyword table?
	IFSKP.
	  HLRZ D,(T)		;Is there anything in this table?
	  JUMPE D,R		;No, don't hack it -- go get next item
	  SOUT%			;Yes, write out option name
	  MOVEI B,.CHSPC
	  BOUT%
	  HLLO D,(T)		;Get size of table
	  EQVI D,(T)		;Form AOBJN pointer to table
	  ADJSP D,1		;Skip past header word
	  PUSH P,D		;Save the table pointer
	  HLLZ T,D		;Set up outside loop counter
	  DO.
	    HRRZ B,(D)		;Get the keyword index for this entry
	    CAIE B,(T)		;Is this the index we want?
	     AOBJN D,TOP.	;No, try again
	    JUMPGE D,[FATAL <Keyword table messed up>] ;Bug trap
	    HLRO B,(D)		;Found the index, now output its string
	    SOUT%
	    AOBJP T,ENDLP.
	    MOVEI B,","		;Insert comma delimiter if more to come
	    BOUT%
	    MOVE D,(P)		;Restore search pointer
	    LOOP.
	  ENDDO.
	  ADJSP P,-1		;Clean up stack
	  JRST CRILP1		;Now try next index
	ENDIF.
IFN ALIASW,<
	CAIE D,PALINI		; Personal aliases?
	IFSKP.
	  CALL SHOPAL		; Yes, display them
	  JRST CRILP1		; and handle next option
	ENDIF.
>
IFN NICSW,<
	CAIE D,.UNIT##		; Printer unit name?
	IFSKP.
	  SKIPN PRNUNT		; Yes, is there one that isn't defaulted?
	   RET			; No, handle next option
	  CALL SHOPRN##		; Yes, display it in the init file
	  JRST CRILP1		; CRLF and handle next option
	ENDIF.
>;IFN NICSW
	HLRZ B,(U)		;Something new; get the losing string
	WARN <Unprocessable SET option "%2S"
>
	RET
	SUBTTL Keyword manipulating routines

; OVERVIEW
;	There are two different kinds of keywords that MM understands.
; To distinguish them, they are called "keyflags" and "keywords".  The
; keyflags are what MM used to call keywords -- they are bit flags set
; in the preamble to the message, and are only meaningful on a per-user
; basis.  The number of these flags is limited to 30.
;      By contrast, keywords are text strings appearing in the "Keywords"
; field of the message header.  These are per-message and stay with it.
; There is no limit to how many keywords a message can have.  MM knows
; how to add and delete keywords (in effect modifying the message header);
; in some cases MM can insert extra spaces so as to leave room for
; easily adding new keywords later without having to change the overall
; length of the message.

; When keywords are specified by the user, they are stored in a keyword
; list of cells; each keyword cell has the format
;		cell:	<# chars>,,<addr of next cell>
;			<byte ptr to string>
; Keyword strings are not necessarily terminated with a null, since
; sometimes the string may reside in a read-only page of the message file.
; The "find" list is used when putting together a message sequence; the
; "modify" list is used when adding or deleting keywords.  Both may
; be active simultaneously.

; KWADD - Add keywords
;	A/ keyword list ptr
;	M/ message to add keywords to

KWADD:	JUMPE A,R		;Ensure list exists
	TXNE F,F%RONL		;Don't try to hack read-only files
	 RET
	SETO B,
	CAMN B,MSGDAT(M)	;If message looks like a baddie
	 RET			; then don't even try.
	PUSH P,A
	MOVEI T,[ASCIZ/
Keywords:/]
	CALL FNDFLD
	IFSKP.
	  MOVE D,A		;Found one, skip cons-up code.
	  MOVE C,W		;Set up char cnt & BP in C & D.
	  JRST KWADD2
	ENDIF.

; Set up keyword buffer with field name, and adjust vars
; so that "field loc" is at end of msg header, with zero length.
	HRRZ V,MSGHLN(M)	;Get offset to start of msg text
	HRRZ A,MSGBOD(M)	;For compare get start of body offset
	CAIG A,-4(V)		;Make sure at least 4 chars in header!
	IFSKP.
	  WARN <Message %M has bad format, keyword not added>
	  JRST KWADD9		;Ugh, we're probably losing
	ENDIF.
	SUBI V,4		;Get offset to point before CRLFCRLF
	PUSH P,V
	CALL MCH2BP		;Convert to BP in A
	POP P,V
	MOVE D,A		;Store as BP to old field string.
	SETO C,			;Say count-1 to invoke fieldname insert

; Copy field into keyword buffer, adding any words which don't
; already exist.  If none were added, can just return.
KWADD2:	MOVE A,(P)		;Furnish keyword list
	SETZ B,			;Say to add them
	CALL KYCPY		;Copy field, with keyword fixes.
	 JRST KWADD9		;No changes?  Win...

; At this point we must have
; A/ # chars in new field string, B/ BP to same
; C/ # chars in old field string, D/ BP to same (in file pages)
; V/ offset from start of msg to place D points to.

; Check - new field size less or eq to current?
KWADD3:	CAMG A,C		;Compare char counts
	 JRST KWDEL3		;Less, super win!  Hand off to KWDEL.

; Must insert cruft.  Pad out the rest of the last line with blanks,
; so as to leave some scratch space for future edits.
KWADD4:	CAIL W,^D70		;Has some room?
	 JRST KWADD5		;Naw, don't bother.
	SUBI W,^D70		;Has some, get neg # of blanks to add.
	PUSH P,A
	ADJBP A,B		;Get BP in A pointing to end of string
	MOVEI E,.CHSPC
	IDPB E,A		;Append blanks
	AOJL W,.-1
	POP P,A

; Copy header into buffer, inserting new field.  Append body.
KWADD5:	MOVE E,A		;Save # chars in new field string
	MOVE T,B		;Save BP to new field string.
	MOVE D,C		;Save # chars in old field string
	MOVE C,V		;Count is # chars to start of field
	HRRZ V,MSGBOD(M)	;Find offset to actual msg body
	SUBI C,(V)		;Get proper count
	MOVE W,C		;Copy into overall length count.
	CALL MCH2BP		;Convert V into BP in A, pt to msg body
	MOVE V,C		;Get # bytes of pre-field body, plus
	ADD V,D			;# bytes of old-fld, let sit in V
	MOVE B,[POINT 7,TXTPAG]	;Destination is text-input area
	CALL MOVASC		;Copy the stuff
	ADJBP D,A		;Skip over old fld, put skipped BP in D
	MOVE A,T		;Restore BP to new fld
	MOVE C,E		; and count
	ADD W,C			;Update overall length
	CALL MOVASC		;Copy new field into header
	MOVE A,D		;Now point to rest of message
	HLRZ C,MSGBOD(M)	;Find # chars left - get msg size
	SUB C,V			;And subtract stuff to end of old fld.
	ADD W,C			;Update overall length
	CALL MOVASC		;Now move all of rest of msg!
	MOVE A,[POINT 7,TXTPAG]	;BP to message
	MOVE C,W		;# chars
	CALL RPLMSG
	 SNARL <Unable to update keywords>
KWADD9:	POP P,A
	RET

; KWDEL - Delete keywords
;	A/ keyword list ptr
;	M/ message to delete keywords from

KWDEL:	JUMPE A,R		;Ensure list exists
	TXNE F,F%RONL		;Don't try to hack read-only files
	 RET
	PUSH P,A
	CALL KWFNDX		;Find keywords field, see if any match.
	 JRST KWDEL9
	
; Match exists.  Copy field into keyword buffer,
; ignoring words given in keyword list.
	MOVE A,(P)		;Furnish keyword list
	SETO B,			;Say to flush matches
	CALL KYCPY		;Copy field, modulo keyword fixes.
	 JRST KWDEL9		;No changes?  Win...

; Check - new field should have size less or eq to current.
; If NOT, then should insert.  For now, error.
	CAMLE A,C		;Compare char counts
	 JRST KWADD4		;Must insert, fooey.
	IFE. A			;Make old-field string include fld name
	  ADDI C,^D11		;Add to total length
	  SUBI V,^D11		;Move start offset back
	  MOVNI E,^D11		;and adjust start BP back
	  ADJBP E,D		;also.
	  MOVE D,E
	  JRST KWADD5
	ENDIF.
KWDEL3:	MOVE E,C
	SUB E,A			;Find # of blanks to pad with
	JUMPLE E,KWDEL4		;Might be equal, esp. if no change.
	ADJBP A,B		;Get BP to end of new string
	MOVEI T,.CHSPC
	IDPB T,A
	SOJG E,.-1
	MOVE A,C		;Count becomes same as original.

; Open write JFN, point to right place, and stick new stuff
; in, overwriting old field.
KWDEL4:	PUSHAE P,<B,C,D>	;Save count, BP, and BP into file
	NOINT			;No outside diddling
	CALL GETJF2		;Get write JFN
	IFNSK.
	  POPAE P,<D,C,B>	;Failed, just return.
	  OKINT
	  JRST KWDEL9
	ENDIF.
	POP P,A			;Note file BP restored to A
	MULI A,5		;Do magic to get
	ADD B,UADBP7(A)		; canonical # bytes from loc 0 into B
	SUB B,[5*MTXPAG]	;Get absolute # bytes from beg of file
	MOVE A,MSGJF2
	SFPTR%			;Set output ptr to this loc
	IFJER.
	  CALL CLSJF2
	  OKINT
	  JERROR <Can't point to message keyword field>
	ENDIF.
	POP P,C			;Restore # chars
	MOVNS C			;Neg for SOUT%
	POP P,B			;Restore BP to new field string
	SOUT%			;Smash old string!
	CALL CLSJF2		;Done, close up shop.
	OKINT
KWDEL9:	POP P,A
	RET
; KWFND - Find keywords in message
;	A/ keyword list ptr
;	M/ message to look in
; Returns A/ ptr to winning keyword cell
;		or 0 if found none.

KWFND:	JUMPE A,R		;Avoid this fuss if possible.
	SAVEAC <B,C,D,T,V,W>
	CALL KWFNDX
	 SETZ A,		;Loss return.
	RET

; KWFNDX - Auxiliary for KWFND and KWDEL.  Hunts up keyword field
;	and sees if any of the specified keywords are present.
;	A/ keyword list ptr
; Returns .+1 if failed, .+2 if success
;	A/ ptr to winning keyword cell
;	C/ # chars in keyword field string
;	D/ BP to keyword field string
; Clobbers B,T,W, etc.

KWFNDX:	STKVAR <KWFPTR>
	MOVEM A,KWFPTR
	MOVEI T,[ASCIZ/
Keywords:/]
	CALL FNDFLD
	IFSKP.
	  MOVE D,A		;Set up BP to field string
	  MOVE C,W		; and # chars in string
	  MOVE T,KWFPTR		;Set initial ptr to keyword cell
	  DO.
	    HLRZ A,(T)		;Get # chars in keyword
	    MOVE B,1(T)		; and BP to keyword string.
	    CALL LKFNDW		;See if keyword exists in field string.
	    IFSKP.
	      MOVEI A,(T)
	      RETSKP		;Found one!
	    ENDIF.
	    HRRZ T,(T)		;Loop: get next ptr
	    JUMPN T,TOP.	;If run out, nothing to delete!
	  ENDDO.
	ENDIF.
	MOVE A,KWFPTR		;Nothing found, restore pointer and return
	RET

	ENDSV.
; FNDFLD - Finds field in message header.
;	T/ addr of ASCIZ name of field
;	M/ message to look in
; Returns .+1 if failed, .+2 if won.
;	A/ BP to start of field
;	W/ # chars in field (includes continuation lines)
;	V/ offset from start of msg.

FNDFLD:	CALL FNDHDR
	 RET
	AOS (P)			;We won, so ensure skip return.
	PUSH P,A
	TDZA W,W		;Start counting # chars in field
FNDFD2:	 ADDI W,2		;Here for continuation line, count CRLF
	CALL CNTHDL		;Count up to but not including CR
	IBP A			;Skip LF too
	ILDB T,A		;See if continuation line
	CAIE T,.CHTAB
	 CAIN T,.CHSPC
	  AOJA W,FNDFD2		;Yes, count whitespace and loop.
	POP P,A
	RET
; KYCPY - Copy keyword field string, modulo specified keyword edits.
;	A/ keyword list ptr
;	B/ 0 to add, -1 to delete keywords
;	C/ # chars in field string (if -1, furnishes fieldname)
;	D/ BP to field string
; Returns .+1 if no changes, .+2 if string hacked.
;	A/ # chars in new string
;	B/ BP to new string

KYCPY:	SKIPN U,A		;Move keyword list ptr to U
	 RET			;No list, no skip.
	MOVEM B,KYCPYF		;Save flag
	SKIPN B,KEYFRE		;Set up BP to 1st free
	 MOVEI B,KEYPGS
	HRLI B,(<POINT 7,>)	;loc in keyword pages.
	MOVEI A,(B)
	SUBI A,KEYPGS		;Find # words used
	IMUL A,[-5]		; then - # chars used
	ADD A,[NKYPGS*1000*5]	; and finally get # chars available.
	SETZ W,			;Cheat - smash current column.
	PUSHAE P,<B,C,D,E,T,V,A>
	SETZM KYCPYC		;Clear local count of edits
	MOVE E,A
	JUMPGE C,KYCPY1		;If field string count is -1,
				; means we want fieldname inserted...
	MOVE A,[POINT 7,[ASCIZ/
Keywords:/]]
	MOVEI C,^D11
	CALL MOVASC		;Move string in as prefix.
	SUBI E,(C)		;Update # chars left
	MOVEI W,-2(C)		; and column count (note CRLF clears)
	SETZ C,
KYCPY1:	MOVE T,B
	MOVE V,B		;Save orig BP to dest in V
	HRLZS U			;Keep orig keyword list ptr in LH
KYCPY2:	CALL LKGETW		;Get word (A,B) from string (C,D)
	 JRST KYCPY6		;EOF, all copied.
	PUSHAE P,<C,D>
	HLR U,U			;Init ptr to keyword cells
	CAIA
KYCPY3:	 HRR U,(U)		;Get next
	TXNN U,.RHALF		;Any more?
	 JRST KYCPY4		;No, stop comparing.
	HLRZ C,(U)		;Get char cnt
	TXZ C,1B18		;(flush sign bit which says if already saw)
	MOVE D,1(U)		; and BP
	CALL LKWCMP		;Compare words...
	 JRST KYCPY3		;No match, try another keyword
	SKIPN KYCPYF		;Found a keyword!  Deleting or adding?
	 SKIPGE (U)		;Adding if already seen, pretend Delete
	  JRST KYCPY5		;Deleting, just skip the copy.
	MOVSI D,(1B0)		;Adding but word already there, so
	IORM D,(U)		; mark it seen, then drop thru to copy
KYCPY4:	CALL KYCPYS		;Invoke little subroutine to do it.
	CAIA
KYCPY5:	 AOS KYCPYC		;Skipped copy, bump count of edits.
	POPAE P,<D,C>
	JRST KYCPY2		;Now go get another word.

KYCPYS:	CAIG E,3(A)		;Ensure enough room for word&separators
	 ERROR <Keyword field too big>
	SUBI E,(A)		;We'll use this much for sure
	ADDI W,(A)		;Update line length, ditto.
	CAME T,V		;First word copied? (Comp BP with orig)
	IFSKP.
	  MOVEI C,.CHSPC	;Yes, just space out
	  IDPB C,T
	  SUBI E,1
	  AOJA W,KYCPS3
	ENDIF.
	MOVEI C,","
	IDPB C,T
	SUBI E,2
	ADDI W,2		;Update line length (anticipate space)
	CAIL W,^D71		;See if it would be too big
	IFSKP.
	  MOVEI C,.CHSPC	;Nope, is OK.  Just tack on space
	  IDPB C,T
	ELSE.
	  MOVEI C,.CHCRT	;Sigh, must create continuation line.
	  IDPB C,T
	  MOVEI C,.CHLFD
	  IDPB C,T
	  MOVEI C,.CHTAB
	  IDPB C,T
	  SUBI E,2		;Update # chars left (extra LF, TAB)
	  MOVEI W,^D8		;Reset line length (tabbed out)
	ENDIF.
KYCPS3:	MOVE C,A
	MOVE A,B
	MOVE B,T
	CALL MOVASC		;Copy word
	MOVE T,B		;Get back updated BP
	RET

; EOF hit on field string.
KYCPY6:	SKIPE KYCPYF		;Were we adding?
	 JRST KYCPY9		;Nope, all's done.
	HLR U,U
	CAIA
KYCPY7:	 HRR U,(U)
	JXE U,.RHALF,KYCPY9	;Really all done now
	SKIPL A,(U)		;See if sign bit set for this keyword
	IFSKP.
	  TXZ A,1B0		;Yes, already in.  Clear it
	  MOVEM A,(U)		; so as to leave list in original state
	  JRST KYCPY7
	ENDIF.
	HLRZS A			;Must add word.  Get char cnt
	MOVE B,1(U)		; and BP
	CALL KYCPYS		;Invoke subroutine to do copy
	AOS KYCPYC		;Bump count of edits
	JRST KYCPY7		;And get another keyword.

KYCPY9:	HLRZS U			;Return orig keyword list ptr to RH.
	POP P,A			;Get back original cnt of # chars left
	SUB A,E			;Find # chars written to string
	POPAE P,<V,T,E,D,C,B>
	CAIGE C,0		;Make sure a -1 value
	 SETZ C,		; is fixed to 0 on exit.
	SKIPE KYCPYC		;Were any edits done?
	 AOS (P)		;Yes, take skip return.
	RET
; LKFNDW - skips if finds word in string.
;	A/ # chars in word
;	B/ BP to word
;	C/ # chars in string to search
;	D/ BP to string
; Mustn't clobber C,D

LKFNDW:	JUMPLE A,R
	PUSHAE P,<E,C,D,A,B>
LKFDW2:	CALL LKGETW		;Get word from string.
	 JRST LKFDW9
	CAME A,-1(P)		;Strings same length?
	 JRST LKFDW2
	PUSH P,C
	PUSH P,D
	MOVE E,-2(P)		;Retrieve BP to search word
LKFDW4:	ILDB C,B		; get char from string
	ILDB D,E		;And from search word
	CAIL C,"a"
	 CAILE C,"z"
	  CAIA
	   SUBI C,"a"-"A"
	CAIL D,"a"
	 CAILE D,"z"
	  CAIA
	   SUBI D,"a"-"A"
	CAIN C,(D)
	IFSKP.
	  POP P,D
	  POP P,C
	  JRST LKFDW2
	ENDIF.
	SOJG A,LKFDW4
	POP P,D
	POP P,C
	AOS -5(P)
LKFDW9:	POPAE P,<B,A,D,C,E>
	RET

; LKWCMP - Word compare.
;	A/ <#> for A
;	B/ BP  for A
;	C/ <#> for B
;	D/ BP  for B
; Skips on success.

LKWCMP:	CAIE A,(C)		;Counts must be equal.
	 RET			;Quickie...
	JUMPE A,RSKP
	PUSHAE P,<A,B,C,D,E>
LKWCM2:	ILDB E,B
	ILDB A,D
	CAIN A,(E)
	 JRST LKWCM7
	XORI A,(E)		;Fold into each other
	CAIE A,40		;If result is 40, possibly match.
	 JRST LKWCM9		;Else definitely don't.
	CAIL E,"A"		;If one original not between 140
	 CAILE E,"z"		; and 172 inclusive,
	  JRST LKWCM9		;Can fail immediately.
	CAILE E,"Z"
	 CAIL E,"a"
	  CAIA
	   JRST LKWCM9
LKWCM7:	SOJG C,LKWCM2
	AOS -5(P)
LKWCM9:	POPAE P,<E,D,C,B,A>
	RET

; LKGETW - Get word from string of format "FOO, BAR ZAP, ETC"
;	Words are ended by anything that SCNTRM skips on.
;	C/ # chars
;	D/ BP to string
; Fails if EOF
; Return .+2
;	A/ # chars
;	B/ BP to word
;	C/ updated # chars left
;	D/ updated BP to rest of string

LKGETW:	JUMPLE C,R
	PUSH P,C
	PUSH P,D
LKGTW2:	MOVEM D,(P)		;Store BP at beg of word
	CALL SCNTRM		;Scan for terminators
	 CAIA
	  JRST LKGTW2		;Loop till hit first real char.
	IFL. A
	  ADJSP P,-2		;Jump if EOF, nothing to return.
	  RET
	ENDIF.
	MOVEM C,-1(P)		;Store char cnt at start of word
	CALL SCNTRM		;Scan again for terminators
	 JUMPGE A,.-1		;Scan over text.
	POP P,B
	POP P,A
	SUB A,C			;Find # chars in word.
	RETSKP

SCNTRM:	SOJL C,[SETO A,
		RET]
	ILDB A,D
	CAIE A,.CHCRT
	 CAIN A,.CHLFD
	  JRST SCNTR8
	CAIN A,.CHTAB
	 JRST SCNTR8
	CAIE A,.CHSPC
	 CAIN A,","
SCNTR8:	  AOS (P)
	RET

; Source BP in A, Dest BP in B, count in C
; Updates A,B but not C.
MOVASC:	JUMPLE C,R
	PUSH P,C
	PUSH P,D
	ILDB D,A
	IDPB D,B
	SOJG C,.-2
	POP P,D
	POP P,C
	RET
	SUBTTL Command parsing routines

COMNDX:	TXNN F,F%TAK		;TAKE file in progress?
	 JFATAL			;No, we have lost badly
	MOVX A,.FHSLF		;Yes, check last error
	GETER%
	HRRZS B			;Only want error code
	CAIE B,IOX4		;End of TAKE file?
	 JFATAL
	CALL UNTAKE		;Yes, leave TAKE file
	IFXN. F,F%RSCN		;Calling from command line?
	  SETZ A,		;Yes, pretend we did a COMND, here is A
	  MOVEI B,[QUIT0]	;Here is B, pretend QUIT command
	  RET			;Return
	ENDIF.
	MOVE A,CMDBLK+.CMRTY	;Retype prompt
	PSOUT%
	MOVE A,CMDBLK+.CMBFP	;And any input
	PSOUT%			; (A bit kludgy, but oh well...)
	SKIPA B,COMNDB		;Reload function and retry
$COMND:	 MOVEM B,COMNDB		;Save first function block addr
	MOVEI A,CMDBLK
	COMND%			;Only one in MM
	 ERJMP COMNDX		;Handle unusual conditions
	RET			;Let caller decide what is good
;;;TAKE commands from file
.TAKE:	TXZN F,F%TAK		;TAKE in progress?
	IFSKP.
	  CONFRM		;Yes, confirm here
	  HLRZ A,CMDBLK+.CMIOJ	;Get TAKE file JFN back
	  JRST UNTAK0		;Untake with no message and return
	ENDIF.
	NOISE (COMMANDS FROM FILE)
	SETZM CMDGTB		;Clear GTJFN% block
	MOVE A,[CMDGTB,,CMDGTB+1]
	BLT A,CMDGTB+.GJATR
	MOVX A,GJ%OLD		;Require old file
	MOVEM A,CMDGTB+.GJGEN
	HRROI A,[ASCIZ/CMD/]	;Default extension is .CMD
	MOVEM A,CMDGTB+.GJEXT
	MOVEI B,[FLDDB. .CMFIL]	;File name with defaults
	CALL CMDFLD		;Parse it
	PUSH P,B		;Save JFN over confirm
	CONFRM
	POP P,A
TAKE1:	MOVX B,<<FLD 7,OF%BSZ>!OF%RD> ;Need read access only
	OPENF%
	 JERROR <Can't open TAKE file>
	HRLS A			;Input JFN in left half
	HRRI A,.NULIO		;No output
	MOVEM A,CMDBLK+.CMIOJ	;Set as new I/O JFNs
	TXO F,F%TAK		;Flag TAKE in progress
	RET

;;;Restore terminal as command input source
UNTAKE:	TXZN F,F%TAK		;Flag no more TAKE file
	 RET			;No TAKE in progress, ignore
	HLRZ A,CMDBLK+.CMIOJ	;Get TAKE file JFN back
	CIETYP <[End of %1J]
>				;Indicate end of TAKE file
UNTAK0::CLOSF%			;Close it
	 NOP			;In case called from UUO handler
	MOVE A,[.PRIIN,,.PRIOU]	;Restore command input from primaries
	MOVEM A,CMDBLK+.CMIOJ
	RET
;;;Initialize command line
CMDINI::SKIPA B,[REPARS]	;Entry for normal reparsing
CMDIN1:	 MOVEI B,REPAR1		;Entry for no-JFN clobber reparsing
	TXZ F,F%HOER		;No more exiting on errors if command
				; level (user typed ESC or something)
	HLROM A,CMDBLK+.CMRTY	;Set up prompt string
	MOVEM A,TPADD1		;Save command pointers
	MOVEM B,CMDBLK+.CMFLG	;Store reparse address
	SKIPN A,TPADDR		;Set some kind of reparse handler
	 MOVEI A,CMDIN2		;Use after .CMINI (this prevents too
	MOVEM A,TPADDR		; much embarassment if a confirm bug)
	MOVEI B,[FLDDB. .CMINI]	;Init command block
	CALL $COMND
CMDIN2:	POP P,TPADDR		;Save address of caller
	MOVEM P,REPARP		;Save reparse P
	HRRZ A,CMDBLK+.CMFLG	;Get reparse address
	JRST (A)		;Dispatch to it

;;;Normally the JRST (A) above will merely drop into the normal reparse
;;;routine here.  But in some cases (e.g. multiple-line sequence) you do
;;;not want reparsing to clear OUTJFN or temporaries.

REPARS:	CALL CLSTMP		;Get rid of stray JFNs
	SKIPLE A,OUTJFN
	 CLOSF%
	  NOP
	SETZM OUTJFN
REPAR1:	SETZM CMDFLB		;Init command field block
	MOVE A,[CMDFLB,,CMDFLB+1]
	BLT A,CMDFLB+3
	MOVE P,REPARP		;Get back reparse P
	MOVE A,TPADD1		;Get back command pointers
	JRST @TPADDR		;And return

CONF:	MOVEI B,CNFCMD		;Get confirmation
	CALLRET CMDFLD

CNFCMD:	FLDDB. .CMCFM

;;;Normal command levels
SUBCMD:	AOSA CLEVEL		;One level deeper
GETCMD:	 SETZM CLEVEL		;At the top
	HRRZM A,CMDFLB+.CMDAT	;Address of keyword table
	SETZM CMDFLB+.CMFNP	;.CMKEY = 0
	CALL CMDNO2		;Parse the field
	SETZM CMDFLB+.CMHLP	;Reset default and help messages
	SETZM CMDFLB+.CMDEF
	HRRZ A,(B)		;Get address of routine
	SETZM OKTINT		;No more timer ints now
	AOS CLEVEL		;Know that we aren't top-level
	RET
CMDNO1::MOVEM A,CMDFLB+.CMDAT
CMDNO2:	MOVX A,CM%DPP
	SKIPE CMDFLB+.CMDEF	;Default provided?
	 IORM A,CMDFLB+.CMFNP	;Yes, say there is one
	MOVX A,CM%HPP
	SKIPE CMDFLB+.CMHLP	;Help provided?
	 IORM A,CMDFLB+.CMFNP	;Yes, say there is help
	MOVEI B,CMDFLB
;	CALLRET CMDFLD

;;;Parse an arbitrary field
IFE NICSW,<
CMDFLD:	CALL $COMND
>;IFE NICSW
IFN NICSW,<
CMDFLD::CALL $COMND
>;IFN NICSW
	TXNE A,CM%NOP
	 JERROR			;Give JSYS error message and return
	RET			;Did ok
;;;Read in a text line
GETLIN:	MOVEI B,[FLDDB. .CMTXT]	;Get a text line
	CALLRET CMDFLD

GETLNC:	MOVEI A,GETLN0		;Get a text line, with confirm
	HRRM A,CMDBLK+.CMFLG	;Reparse address is just us if at top
	MOVEM P,REPARP
GETLN0:	MOVE P,REPARP
	MOVEI B,[FLDDB. .CMCFM,CM%SDH,,,,[FLDDB. .CMTXT]]
	SETZM STRBUF		;Else make sure atom buffer clear
	CALL CMDFLD		;Go read a line
	LOAD A,CM%FNC,(C)
	CAIE A,.CMCFM		;Confirm?
	 CONFRM			;No, do it now then
	RET
;;;Parse a date
GETDAT:	MOVEI B,DATFLB
	CALL CMDFLD
	LOAD T,CM%FNC,(C)	;Get field type parsed
	CAIN T,.CMTAD		;Date and time?
	 RET			;Yes, just return that time
	CAIN T,.CMTOK		;Token? (must be "-, #, %, *")
	 JRST DOTOK
	HRRZ T,(B)		;Else get data for it
	MOVE T,(T)
	CALLRET (T)		;And call the right routine

DATFLB:	FLDDB. .CMTAD,,CM%IDA!CM%ITM,,,DATFL1
DATFL1:	FLDDB. .CMTAD,,CM%IDA,,,DATFL2
DATFL2:	FLDDB. .CMTAD,,CM%ITM,,,DATFL3
DATFL3:	FLDDB. .CMKEY,,DATTAB,,,DATFL4
DATFL4:	FLDDB. .CMKEY,,FLTAB,,,DATFL5
DATFL5:	FLDDB.	.CMTOK,CM%SDH,<-1,,[ASCIZ/-/]>,<"-" followed by the number of days in the past>,,DATFL6
DATFL6:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/*/]>,<"*" to use the receive date of the last message>,,DATFL7
DATFL7:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/%/]>,<"%" to use the receive date of the last message>,,DATFL8
DATFL8:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/#/]>,<"#" followed by a message number to use the receive date for that message>,,DATFL9
DATFL9:	FLDDB. .CMKEY,,HOLDAY,<holiday,>

DOTOK:	MOVE B,.CMDAT(C)	;Get pointer
	HRLI B,(<POINT 7,>)
	ILDB T,B		;And load first byte of token
	CAIN T,"-"		;Minus?
	 JRST OFFDAT		;Yes, it's a date offset
	CAIN T,"#"		;Message number?
	 JRST MSGNUM		;Else, "#" means message number
	JRST DATLST		;*, % mean date of last message

MSGNUM:	NOISE (MESSAGE NUMBER)
	MOVEI B,[FLDDB. .CMNUM,,^D10] ;Read a number
	CALL CMDFLD
	SOS B			;Convert to normal form
	IMULI B,MSGLEN		;Convert
	SKIPL B			;Can't be lt zero,
	 CAMLE B,LASTM		;Or greater than last one
	  ERROR <Message number out of range>
	MOVE B,MSGDAT(B)	;Use receive date for this message
	RET

DATFST:	MOVE B,MSGDAT		;Get date of first message
	RET

DATLST:	MOVEI B,MSGDAT
	ADD B,LASTM
	MOVE B,(B)		;Get date of last message
	RET

LOGLST:	SETO A,			;Date/time of last login
	MOVE B,[-1,,D]
	MOVEI C,.JISTM
	GETJI%
	 TDZA B,B		;If failed, use tad 0
	  MOVE B,D
	RET

OFFDAT:	NOISE (NUMBER OF DAYS)
	MOVEI B,[FLDDB. .CMNUM,,^D10] ;Read a number
	CALL CMDFLD
	SKIPG B
	 ERROR <Number of days in past must be positive>
	HRLZ B,B		;Get number of days to left half
	JRST DAT.1		;And join day-of-week code

DATDOW:	SETO B,
	SETZ D,
	ODCNV%
	MOVSI C,8(C)		;Get day of week into lh
	SUBM C,T		;Get difference from desired
DATDAY:	HLLZ B,T		;Get number of days to offset
	CAMLE B,[7,,0]		;If week wrapped around,
	 SUB B,[7,,0]		;Take next one
DAT.1:	GTAD%
	SUBM A,B
	SETZ D,
	ODCNV%
	SETZ D,			;Midnight of that day
	IDCNV%
	 SETO B,
	RET

DATHDY:	GTAD%			;Get now for later
	SETO B,
	SETZ D,
	ODCNV%
	HLRZ E,B		;Save year
DATHD1:	LDB B,[POINT 9,T,8]	;Get month
	HRLI B,(E)		;Get year
	HLLZ C,T
	TLZ C,777000		;Get day of month
	SETZ D,
	IDCNV%
	 SETO B,
	CAML B,A		;Must be before today
	 SOJA E,DATHD1		;Else try last year
	RET
;;;Get User@site string, W/ addr where to stick block, return in U

USRLST:	FLDDB. .CMCFM,,,,,USRLS1
USRLS1:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/*/]>,<"*" for sending to a file
  or "@" to send indirect from a file>,,USRLS2
IFE ALIASW,<
USRLS2:	FLDDB. .CMUSR,,,,,USRLS3
>
IFN ALIASW,<
USRLS2:	FLDDB. .CMKEY,,PALTBL,<personal alias,>,,[FLDDB. .CMUSR,,,,,USRLS3]
>
USRLS3:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/./]>,<"." for yourself>,,USRLS4
USRLS4:	FLDDB. .CMKEY,,<[1,,1
			[ASCIZ/SYSTEM/],,SYSCOD]>,<special mailbox,>,,USRLS5
USRLS5:	FLDDB. .CMQST,,,,,USRLS6
USRLS6:	FLDBK. .CMFLD,,,<network address>,,UNMMSK

ADRLST:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/@/]>,<confirm with carriage return
  or "," for another address
  or "@" for a network host name
  or ":" to make this a group name>,,ADRLS1
ADRLS1:	FLDDB. .CMTOK,CM%SDH,<-1,,[ASCIZ/:/]>
;;;Here when an error occurs in a user parse, to get it again
CMDUSE:	MOVE P,REPARP		;Restore saved P
	TXZE F,F%RSCC		;Error from RSCAN% call?
	 RET			;Yes, just return
	MOVEI B,[FLDDB. .CMINI]	;Re-init comnd state block
	CALL $COMND
CMDUS0:	MOVX A,CM%XIF		;Here for reparse
	ANDCAM A,CMDBLK+.CMFLG
	MOVE P,REPARP		;Restore saved P
	MOVE W,TPADD1		;Reset list as of start
	TXZ F,F%COMA!F%F4
;	JRST GETUSR

;;;Here's where we actually go and parse addresses
GETUSR:	MOVEI B,USRLST		;Set up user command list
	TXZE F,F%COMA		;Is this the first one?
	 SKIPA B,[USRLS1]	;Yes, don't allow CRLF
	  SKIPE CLEVEL		;At top level?
	IFSKP.
	  MOVEI A,CMDUS0	;Setup local reparse address
	  HRRM A,CMDBLK+.CMFLG
	  MOVEI A,CMDUSE	;Setup error dispatch
	  HRRM A,CMDRET
	  MOVEM P,REPARP
	  MOVEM W,TPADD1	;Save list so far
	ENDIF.
	CALL CMDFLD		;Parse a field
	LOAD E,CM%FNC,(C)
	CAIN E,.CMCFM
	 RET			;Null field, return
	MOVEI U,(W)
	SETZM ADRFLG(U)
	SETZM ADRLNK(U)
	IFXN. F,F%F4
	  SETONE ADINV,(U)
	ENDIF.
IFN ALIASW,<
;;; this is kind of gross, but it's necessary in the case where the
;;; user types ESC to complete an alias name, but then adds "@host".
;;; comnd doesn't complete the keyword name in the buffer, so we have
;;; to do it ourselves to make sure usrstr(u) matches what the user sees.

getus3:	txne a,cm%esc		; user terminated field with escape?
	caie e,.cmkey		; and it's a keyword?
	ifskp.
	  hrrz a,b		; and it's a personal alias?
	  cail a,paltbl
	  cail a,paltbl+1000
	anskp.			; yes
	  push p,b		; save b
	  hrroi a,strbuf	; point to atom buffer
	  hlro b,(b)		; and original keyword
	  stcmp%		; compare them
	  txnn a,sc%sub		; atom buffer a subset of keyword?
	  ifskp.		; yes...
	    move a,[point 7,strbuf]
	    do.			; skip to end of atom buffer
	      ildb c,a		; ...
	      jumpn c,top.
	    od.
	    ildb c,b		; get first char of completion string
	    dpb c,a		; drop it in
	    call movst2		; copy completion into atom buffer
	  endif.
	  pop p,b		; restore b
	endif.
>
	CAIE E,.CMKEY		;Keyword?
	 CAIN E,.CMUSR		;Username?
	  MOVEM B,ADRUSR(U)	;Save keyword pointer or user number
	CAIE E,.CMTOK		;Token?
	IFSKP.
	  MOVE A,.CMDAT(C)	;Yes
	  HRLI A,(<POINT 7,>)
	  ILDB A,A		;Get first char of token
	  CAIE A,"*"		;File type?
	  IFSKP.
	    SETZM CMDGTB	;Get space for GTJFN%
	    MOVE A,[CMDGTB,,CMDGTB+1] ;Note that .CMOFI is NOT used since
	    BLT A,CMDGTB+.GJATR	; it uses existing gen# + 1
	    MOVEI B,[FLDDB. .CMFIL,CM%SDH,,<file name to output message to>]
	    CALL CMDFLD
	    HRROI A,ADRSTR(W)	;Output string for this name
	    MOVX C,JS%SPC	;Output everything
	    JFNS%
	    PUSH P,A		;Save updated string pointer
	    MOVEI A,(B)
	    RLJFN%		;Don't need it till later
	     NOP
	    MOVX A,AD.FIL	;File recipient
	    STOR A,ADTYP,(U)
	    SETZM ADRUSR(U)
	    POP P,A		;Restore updated string pointer
	  ELSE.
	    MOVE B,MYAUSR	;Must be . meaning me
	    MOVEM B,ADRUSR(U)
	    MOVEI A,ADRSTR(W)
	    HRLI A,(<POINT 7,>)
	    MOVEI B,MAUSRS	;Use my name string too
	    CALL MOVST0		;Move in user name
	  ENDIF.
	ELSE.
	  MOVEI A,ADRSTR(W)
	  HRLI A,(<POINT 7,>)
	  MOVEI B,STRBUF	;Set up pointer to string
	  CALL MOVST0		;Move in user name
	ENDIF.
	MOVEI A,1(A)		;Point to next free word
	SUBM A,W		;Get length
	EXCH A,W
	STOR A,ADSIZ,(U)	;Store it away
	MOVEI B,ADRLST
	MOVX D,CM%XIF
	IORM D,CMDBLK+.CMFLG
	CALL $COMND
	ANDCAM D,CMDBLK+.CMFLG
	IFXE. A,CM%NOP		;Was it @ or :?
	  MOVE A,.CMDAT(C)	;Yes, get token
	  HRLI A,(<POINT 7,>)
	  ILDB A,A
	  CAIE A,":"		;Distribution list
	  IFSKP.
	    TXO F,F%F4!F%COMA	;Say we are within a distribution list
				;Also pretend there was a comma so the
				; reparse setup code isn't confused
	    MOVX A,AD.GRP	;Distribution list type recipient
	    STOR A,ADTYP,(U)
	    JRST GETUSR		;And go get some more guys
	  ENDIF.
	  MOVEI B,[FLDBK. .CMFLD,,,host name,,HNMMSK]
	  CALL CMDFLD		;Parse it
	  HRROI A,STRBUF
	  CALL HSTNAM		;See if name known
	  IFNSK.
	    MOVEI A,STRBUF
	    ERROR <Unrecognized host name "%1S">
	  ENDIF.
	  MOVEM A,ADRHST(U)	;Save host address
	  MOVX A,AD.NET		;Network recipient
	  STOR A,ADTYP,(U)
	  MOVEI B,[FLDDB. .CMCFM,,,,,<[FLDDB. .CMCMA]>]
	  CALL CMDFLD
	  LOAD D,CM%FNC,(C)
	  CAIN D,.CMCMA
	   TXO F,F%COMA
	ELSE.
	  CAIE E,.CMUSR		;Was it a user before?
	   TXNE F,F%F3		;Or funny addresses ok?
	  IFSKP.
	    CAIN E,.CMTOK	;File name/token?
	  ANSKP.
IFN ALIASW,<
	    caie e,.cmkey	; was it an alias?
	    ifskp.
getus1:	      hrrz b,adrusr(u)	; maybe, get back tbluk entry address pointer
	      cail b,paltbl	; keyword from the alias table?
	      cail b,paltbl+1000 ; ...
	      ifskp.
		movei a,paltbl	; get address of the table
		hrroi b,adrstr(u) ; point to string they typed
	        tbluk%		; look it up in the table (again, sigh)
		 erjmp getus2	; punt on error
		txnn b,tl%exm	; exact match?
		ifskp.
		  hrrz a,(a)	; pick up alias definition
		  hrli a,(point 7) ; make it a good pointer
		  setzm e	; not really necessary, can you guess why?
		  movei w,(u)	; reinitialize w
		  call pradd0	; parse the alias body
		  jrst getus0	; and we're done with this guy
		endif.		; end exact keyword match
;;; we're here because an alias name was input but it was abbreviated;
;;; we check for a username, to save the overhead of calling chkfwd...
		movx a,rc%emo	; want an exact match
		hrroi b,adrstr(u) ; on the string the user typed
		rcusr%		; do we have a username here?
		txne a,rc%nom!rc%amb
		ifskp.		; yes...
		  movem c,adrusr(u) ; save the user number
		  jrst getus0	; and we're done with this guy
		endif.
		jrst getus2	; oh well
	      endif.		; end keyword is an alias
getus2:	    movei e,.cmfld	; pretend we typed .cmfld
	    endif.		; end keyword parsed...
>
	    CAIE E,.CMKEY	;Was it System?
	    IFSKP.
	      MOVX B,SYSCOD	;Yes, get the special user number
	      MOVEM B,ADRUSR(U) ;Set it in the block
	      MOVEI B,[ASCIZ/System/] ;User name for string
	      MOVEI W,(U)	;Re-initialize W from base in U
	      MOVEI A,ADRSTR(W) ;Pointer to string area
	      HRLI A,(<POINT 7,>)
	      CALL MOVST0	;Move in file name string
	      MOVEI A,1(A)	;Point to next free word
	      SUBM A,W		;Get length
	      EXCH A,W
	      STOR A,ADSIZ,(U)	;Store it away
	    ELSE.
	      HRROI A,ADRSTR(U)	;Local addr, not user, try forwarding
	      CALL CHKFWD	;Did we find it?
	       ERROR <No such local user as "%1R">
	      MOVE A,LCLHST	;Get host string pointer
	      MOVEM A,ADRHST(U) ;Set up host properly
	      MOVX A,AD.NET	;Network recipient
	      STOR A,ADTYP,(U)
	    ENDIF.
	  ENDIF.
IFN ALIASW,<
GETUS0:
>
	  MOVEI B,[FLDDB. .CMCFM,,,,,<[FLDDB. .CMCMA]>]
	  CALL CMDFLD		;Must be comma or confirm here
	  LOAD D,CM%FNC,(C)	;Get field type
	  CAIN D,.CMCMA
	   TXO F,F%COMA
	ENDIF.
	RETSKP
;;; GETKEY - Parse list of keywords.
;	Returns	U/ keyflag bit mask
;		V/ keyword list ptr

KEYLST:	FLDDB. .CMTOK,,<POINT 7,[ASCIZ/*/]>,,,KEYLS1
KEYLS1:	FLDDB. .CMKEY,,KEYTBL,,,KEYLS2
KEYLS2:	FLDDB. .CMFLD,,,<keyword>

GETKEY:	SKIPA B,[[FLDDB. .CMCMA,CM%SDH,,<","
 or confirm with carriage return>]]
GETKY0:	 MOVEI B,[FLDDB. .CMCMA,CM%SDH,,<","
 or message sequence>]
	PUSH P,B
	SETZ U,			;Init bits
	MOVEI B,KEYLST
	SETZ V,			;Clear keyword list
	CALL CMDFLD
	LOAD D,CM%FNC,(C)
	CAIE D,.CMTOK		;Was "*" typed?
	 JRST GETKY2		;No, assume got a keyword.
	HRROI U,777700		;Yes, do crock = set all flag bits!
	JRST CPPOPJ

GETKY1:	MOVEI B,[FLDDB. .CMKEY,,KEYTBL,,,<[FLDDB. .CMFLD]>]
	CALL CMDFLD		;Get a keyword
GETKY2:	LOAD D,CM%FNC,(C)	;Find which function won
	CAIN D,.CMKEY		;If twas a keyflag,
	 JRST GETKY7		;go handle the bits.

;Store keyword onto keyword list.
	SKIPN D,KEYFRE		;Get keyword freespace ptr
	 MOVEI D,KEYPGS		;Initialize if necessary.
	HRLI D,(<POINT 7,>)	;Make it a BP
	MOVE A,D
	MOVE B,[POINT 7,STRBUF]
	CALL MOVST2		;Move string, with null for good luck
	MOVEM D,2(A)		;Store ptr to string in cell following
	MOVEI D,1(A)		;Save addr to keyword cell
	MOVE C,[POINT 7,STRBUF]	;Set up for B-C
	CALL PTRDIF		;Return B-C in A
	SUBI A,1		;Minus 1 cuz of the null
	CAIG A,			;Check.  For now, complain, but
	 ERROR <Null keyword>	;Later just get another keyword.
	HRLZM A,(D)		;Store count in keyword cell
	HRRM V,(D)		;Link new cell to rest of list
	MOVEI V,(D)		;Cell now linked in!
	ADDI D,2
	MOVEM D,KEYFRE		;Update freespace pointer.
	JRST GETKY8		;Now go get another keyword.

GETKY7:	HRRZ B,(B)		;Handle a keyflag.
	MOVNS B
	MOVSI A,400000
	LSH A,(B)
	IOR U,A			;Set the given bit

GETKY8:	MOVE B,(P)		;See if a comma follows
	CALL $COMND
	JXE A,CM%NOP,GETKY1	;Yup, get more stuff.
	JRST CPPOPJ		;Not a comma, return
;;;Check for forwarding.  Pointer to string in A, skip returns if exists

CHKFWD:	TXC A,.LHALF		;Fix software pointer to hardware pointer
	TXCN A,.LHALF
	 HRLI A,(<POINT 7,>)
	SAVEAC <A,D>
	STKVAR <PTR,JFN,FRK>
	MOVEM A,PTR		;Save pointer
	MOVX A,GJ%OLD!GJ%SHT	;Get JFN of forwarder
	HRROI B,[ASCIZ/SYS:MMAILBOX.EXE/]
	GTJFN%
	 ERJMP R		;Can't
	MOVEM A,JFN		;Save JFN
	MOVX A,CR%CAP		;Create an inferior fork
	CFORK%
	 JERROR <Can't create forwarding fork>
	MOVEM A,FRK		;Save fork handle
	MOVE A,JFN		;Get back JFN
	HRL A,FRK		;Get prog into fork
	GET%
	IFJER.
	  MOVE A,JFN		;Flush the JFN
	  RLJFN%
	   ERJMP .+1
	ELSE.
	  HRLZ A,FRK		;Page 0 of inferior
	  MOVE B,[.FHSLF,,FWDPAG/1000] ;Mapped to this fork's FWDPAG
	  MOVX C,PM%RD!PM%WR	;Read+write access
	  PMAP%
	..TAGF (ERJMP,)		;I sure wish ANNJE. existed!
	  MOVE A,PTR		;Get string pointer
	  MOVE B,[POINT 7,FWDPAG+200] ;Copy string
	  DO.
	    ILDB C,A
	    IDPB C,B
	    JUMPN C,TOP.
	  ENDDO.
	  MOVE A,FRK		;Set inferior's AC1 to 1 for local site
	  MOVEI B,4		;Start up inferior
	  SFRKV%
	  IFNJE.
	    WFORK%
	  ..TAGF (ERJMP,)	;I sure wish ANNJE. existed!
	    RFSTS%		;See if it finished ok
	  ..TAGF (ERJMP,)	;I sure wish ANNJE. existed!
	    LOAD A,RF%STS,A	;Get status
	    CAIE A,.RFHLT	;HALTF%?
	  ANSKP.
	    SKIPLE FWDPAG+177	;Success answer?
	     AOS (P)		;Indicate success
	  ENDIF.
	  MOVEI D,SAVMOD	;Restore TTY modes
	  CALL SETTYM
	  SETO A,		;Unmap shared page
	  MOVE B,[.FHSLF,,FWDPAG/1000] ;Mapped to this fork's FWDPAG
	  SETZ C,
	  PMAP%
	   ERJMP .+1
	ENDIF.
	MOVE A,FRK		;Flush the fork
	KFORK%
	 ERJMP .+1
	RET

	ENDSV.
;;;Parse command line

RSPRTB:	NRSPTB,,NRSPTB
	CMD BB,0
	CMD MAIL,.SEND
	CMD MM,0
	CMD NMM,0
	CMD SNDMSG,.SEND
NRSPTB==<.-RSPRTB>-1

DORSCN:	SETZ A,
	RSCAN%
	 SETZ A,
	JUMPE A,R		;No command line
	MOVSI A,[ASCIZ//]	;Dummy prompt
	TXO F,F%RSCC		;Note RSCAN% command
	CALL CMDINI		;Init COMND state block
	SETZM CLEVEL		;At top level now
	MOVEI A,CMDRES		;Reinit error dispatch
	HRRM A,CMDRET
	MOVEI B,[FLDDB. .CMKEY,,RSPRTB]
	CALL $COMND
	JXN A,CM%NOP,DORSCE	;If error, flush line
	HRRZ A,(B)
	IFN. A
	  NOISE (TO)		;In case EXEC has (TO) noise word
	  TXO F,F%HOER		;Return to EXEC on any error
	  AOS CLEVEL		;Now a level deeper
	  SETOM ABOCAN		;OK to arm CTRL/N aborts
	ELSE.
	  MOVEI B,[FLDDB. .CMKEY,,RSCMTB] ;Parse MM RSCAN% command
	  CALL $COMND
	  JXN A,CM%NOP,DORSCE
	  HRRZ A,(B)		;Get dispatch address
	ENDIF.
	TXO F,F%RSCN		;Say called from command line
	AOS CLEVEL		;Now a level deeper
	SETOM ABOCAN		;OK to arm CTRL/N aborts.
	CALL (A)
	JRST CMDRES		;And go to top-level

;  Here on COMND error.  Either the rescanned command was garbage, or
; it wasn't an MM-related command at all (e.g. some EXEC command).  Just
; ignore it instead of trying to figure out every possible case.
DORSCE:	MOVX A,.PRIOU		;Flush rest of line
	BKJFN%
	 NOP
	DO.
	  SIBE%			;Don't hang on this BIN%
	  IFNSK.
	    BIN%
	    CAIE B,.CHLFD
	     LOOP.
	  ENDIF.
	ENDDO.
	DMOVE A,[POINT 7,CSBUF	;Avoid embarassment if user types CTRL/H
		 CSBFSZ*5]	; first thing
	DMOVEM A,CMDBLK+.CMPTR
	RET			;Now return to upper level

;;;Read in file for RSCAN% command handling

RSCFIL:	CALL GETFIL		;For read from command line
	SKIPG MSGJFN		;Is there a mailbox?
	 XCT CMDRET		;No, error
	CALLRET RECEN2		;Remark new messages w/o headers
	SUBTTL Deliver local mail using MMailr

;;;Queue local mail to MMailr

SYSCOD==-2			;Special user number for SYSTEM

SNDLCL:	SKIPN W,LCLIST		;Get start of local recipients
	 RET
	DO.
	  MOVE A,ADRUSR(W)	;Is this special local recipient?
	  CAME A,[-1]
	  IFSKP.
	    TXON F,F%F2		;Yes, setup as saved.messages file
	     SKIPE SAVFIL	;Unless have one from moving
	    IFSKP.
	      HRROI A,SAVFIL
	      MOVE B,MSGJFN
	      MOVE C,[111110,,JS%PAF]
	      JFNS%
	    ENDIF.
	    HRRZ W,ADRLNK(W)	;Get next in line
	    JUMPN W,TOP.
	    RET
	  ENDIF.
	  CAME A,[SYSCOD]	;Mailing to SYSTEM?
	  IFSKP.
	    MOVX A,GJ%OLD!GJ%DEL!GJ%PHY!GJ%SHT ;Verify it exists
	    HRROI B,[ASCIZ/POBOX:<SYSTEM>MAIL.TXT.1/]
	    GTJFN%
	  ..TAGF (ERJMP,)	;I sure wish ANNJE. existed!
	    RLJFN%		;Now get rid of this JFN
	     NOP
	    MOVX A,GJ%FOU!GJ%DEL!GJ%PHY!GJ%SHT ;Get the JFN we really want
	    HRROI B,[ASCIZ/POBOX:<SYSTEM>MAIL.TXT.1/]
	    GTJFN%		;Try to get mail file
	  ..TAGF (ERJMP,)	;I sure wish ANNJE. existed!
	    MOVEM A,OUTJFN	;Save it
	    MOVX B,<<FLD 7,OF%BSZ>!OF%APP> ;Open for append
	    OPENF%
	    IFJER.
	      MOVE A,OUTJFN
	      RLJFN%
	       NOP
	    ELSE.
	      SETZ T,		;Mark as unseen
	      CALL FILMS2	;Go actually append it
	      MOVEI A,ADRSTR(W)	;Get the guy's name again
	      SKIPL SNDVBS	;Super-terse sending?
	       CIETYP < SYSTEM -- ok> ;No, tell of local sending
	      SETO A,		;Shout there's a new system message
	      HRROI B,[ASCIZ/
[From SYSTEM: New Message-of-the-Day available]
/]
	      TTMSG%		;Tell everybody
	       ERJMP .+1	;Ignore ITRAP
	      HRRZ W,ADRLNK(W)	;Get next in list
	      JUMPN W,TOP.
	      RET
	    ENDIF.
	  ENDIF.
	  CALL REMLST		;Prevent circular list
	  SETZM ADRUSR(W)	;Clear host/user number for this guy
	  MOVEI B,NETLST	;Thread entry into network recipients
	  MOVEI U,(W)
	  HRRZ W,ADRLNK(W)	;Get next link for next time
	  SETZM ADRLNK(U)	;Clear any previous links
	  CALL ADDLST		;Add onto this list
	   NOP			;Don't worry about duplicate
	  JUMPN W,TOP.
	ENDDO.
	RET

IFN ALIASW,<
	.psect datpag
paltbl::xwd 0,777		; personal-alias keyword table
	block 777
	.endps
	.psect data
palkey:	block 20		; alias keyword
paldef:	block 100		; alias definition
palias:	block 1			; TBLUK table entry
	.endps

.defin:	noise (ALIAS)
	movei b,[flddb. .cmfld,,,<alias name>]
	call cmdfld		; parse a keyword name
	ldb a,[point 7,strbuf,6]
	skipn a
	error <No alias name given>
	hrroi a,palkey
	hrroi b,strbuf
	setzm c
	sout%			; copy alias name away
	noise (AS)		; spit out some noise
	movei b,[flddb. .cmcfm,cm%sdh,,<recipient list, text string>,,[flddb. .cmtxt,cm%sdh,,<confirm to delete alias>]]
	call cmdfld		; parse the body
	load d,cm%fnc,.cmfnp(c)	; get the function code
	cain d,.cmcfm		; confirm?
	jrst paldel		; yes, delete the alias
	hrroi a,paldef
	hrroi b,strbuf
	setzm c
	sout%			; copy the definition away
	confrm			; confirm it
	call setpal
	 ret
	ret

;;; Install the alias in palkey with definition in paldef
;;; Returns +2 on success

setpal:	hrroi a,palkey		; install palkey in alias table
	call cpystr		; copy the alias away
	hrlm b,palias		; save pointer to it
	hrroi a,paldef
	call cpystr		; save the definition away
	hrrm b,palias		; save pointer to it
	movei a,paltbl		; get address of table
	move b,palias		; and entry to add
	tbadd%			; add it to the table
	ifjer.
	 jsnarl <Can't add alias>
	 ret
	endif.
	retskp

;;; Delete the alias in palkey
paldel:	movei a,paltbl
	hrroi b,palkey
	tbluk%
	ifjer.
	 jsnarl <No such alias>
	endif.
	txnn b,tl%nom!tl%amb	; no match, or ambiguous?
	ifskp.
	 snarl <Invalid or ambiguous alias name>
	 ret
	endif.
	move b,a
	movei a,paltbl
	tbdel%
	ifjer.
	 jsnarl <Can't delete alias>
	 ret
	endif.
	ret

;;; PALINI is called to process an ALIAS line in MM.INIT
palini::movei a,.chspc		; get a space
	dpb a,t			; put strbuf back together, for inierr
	call palskp		; skip whitespace
	 ret			; return quietly if none there
	move d,[point 1,palmsk,0] ; get break mask in d
	move a,[point 7,palkey]	; set up pointer to keyword buffer
	setzm palkey		; null out the keyword
palin1:	ildb c,t		; get a byte
	idpb c,a		; store it
	call brkchr		; break character?
	 jrst palin1		; no, loop
	setzm d
	dpb d,a			; null-terminate the keyword
	caie c,.chspc		; make sure field is terminated properly
	cain c,.chtab
	ifskp.
	  caie c,.chlfd
	  cain c,.chcrt		; end of line?
	  setzm c
	  jumpe c,palbad	; yes, punt
	  snarl <Invalid character in alias name>
	  callret inierr
	endif.
	call palskp		; skip whitespace
	 jrst palbad		; complain if no alias definition found
	move a,[point 7,paldef]
	move b,t
	do.
	  ildb c,t		; get a character
	  caie c,.chcrt		; end of line?
	  cain c,.chlfd
	  setzm c		; yes, drop in null
	  idpb c,a		; deposit it
	  jumpn c,top.		; loop until string exhausted
	od.
	call setpal		; define the alias
	 jrst palbad
	ret			; and return

palbad:	snarl <Invalid ALIAS definition>
	callret inierr

;;; See if character in C is a break character in the mask pointed to by D
brkchr:	saveac <b,c,d>
	move b,c		; calculate pointer to the right bit in the
	idivi b,^d32		;  mask
	addi d,(b)		; find the right word...
	adjbp c,d		; find the right bit
	ldb c,c			; load the bit
	jumpe c,r		; if zero, not a break character
	retskp			; else return +2

;;; break mask for personal aliases
palmsk:	exp keyb0.,keyb1.,keyb2.,keyb3.

;;; skip past whitespace pointed at by T, and complain if end-of-line found
palskp:	ildb c,t		; get a character
	caie c,.chspc		; space?
	cain c,.chtab		; or tab?
	jrst palskp		; yes, keep looking
	jumpe c,r		; SET command can do this to us
	caie c,.chlfd		; linefeed?
	cain c,.chcrt		; carriage return?
	ret			; yes, complain
	setom c			; back up pointer in t
	adjbp c,t
	move t,c
	retskp			; and return

;;; SHOPAL is called to display ALIAS option in MM.INIT

shopal:	hllz d,paltbl		; get number of ALIAS options
	movns d			; negate it (in left half)
	aos d			; bump it to to skip TBLUK header
	jrst shopa2		; skip crlf first time
shopa1:	hrroi b,crlf0
	setzm c
	sout%
shopa2:	fmsg <ALIAS >
	hlro b,paltbl(d)	; alias name
	setzm c
	sout%
	movei b,.chspc
	bout%
	hrro b,paltbl(d)	; alias expansion
	setzm c
	sout%
	aobjn d,shopa1		; loop for each alias
	ret
>
	SUBTTL End of program

XLIST				;For clean listings
	LIT
LIST				;Literals are XLISTed out

	END <EVECL,,EVEC>