;<PUP>PUPFTS.MAC;55 13-JAN-83 14:19:36 EDIT BY TAFT
;<PUP>PUPFTS.MAC;54 8-NOV-82 16:33:00 EDIT BY TAFT
;<PUP>PUPFTS.MAC;53 4-OCT-82 16:28:45 EDIT BY TAFT
; Bug fix in GFNPRP
;<PUP>PUPFTS.MAC;51 26-SEP-82 15:14:10 EDIT BY TAFT
; Implement New-enumerate
; Observe Desired-property requests
;<PUP>PUPFTS.MAC;49 10-AUG-81 18:00:30 EDIT BY TAFT
; Time zones in Retrieve date properties.
;<PUP>PUPFTS.MAC;47 18-MAY-81 11:41:48 EDIT BY TAFT
;<PUP>PUPFTS.MAC;46 24-APR-81 21:26:29 EDIT BY TAFT
; Include time zone in date properties
;<PUP>PUPFTS.MAC;45 16-JAN-81 10:41:07 EDIT BY TAFT
; Strip "↑.registry" if present in distribution list name.
; Suppress trailing "." in Name-body if filename extension is blank.
;<PUP>PUPFTS.MAC;44 2-DEC-80 09:17:45 EDIT BY TAFT
; Fix bug in handling of connect name in LOGCHK.
;<PUP>PUPFTS.MAC;43 29-AUG-80 13:43:23 EDIT BY TAFT
; Add FRNHAD
;<PUP>PUPFTS.MAC;42 26-AUG-80 11:11:23 EDIT BY TAFT
; Generate full property list in Delete
;<PUP>PUPFTS.MAC;41 15-AUG-80 18:38:29 EDIT BY TAFT
; Call RECPAS from LOGCHK
;<PUP>PUPFTS.MAC;40 22-JUL-80 19:16:04 EDIT BY TAFT
; Do an automatic Expunge if "directory full" error occurs during Store
; CKRPAR generate No rather than Comment for errors in single-file retrieves
;<PUP>PUPFTS.MAC;39 2-FEB-80 18:00:31 EDIT BY TAFT
; Send Size property in Retrieve property list
;<PUP>PUPFTS.MAC;38 20-JAN-80 17:42:05 EDIT BY TAFT
; FTPM does not attempt to send if receive connection closes in midstream.
;<PUP>PUPFTS.MAC;37 28-NOV-79 11:36:13 EDIT BY TAFT
; Add checks to distinguish between FTP and Mail server functions.
; Add code for anonymous distribution list retrieval in Mail server.
;<PUP>PUPFTS.MAC;36 4-NOV-79 12:24:55 EDIT BY TAFT
; Set creation date during [Store] if present in property list
;<PUP>PUPFTS.MAC;35 2-SEP-79 15:59:33 EDIT BY TAFT
;<PUP>PUPFTS.MAC;34 16-JAN-79 14:26:59 EDIT BY TAFT
;<PUP>PUPFTS.MAC;33 8-OCT-78 19:49:40 EDIT BY TAFT
; Rejuvenate watchdog timer in REFILL procedure so we don't get
; nailed while processing long mail distribution lists
;<PUP>PUPFTS.MAC;32 4-SEP-78 13:05:01 EDIT BY TAFT
; Add Rename error code 112
;<PUP>PUPFTS.MAC;31 4-JUL-78 16:41:36 EDIT BY TAFT
; Fix bug in Rename failure code
;<PUP>PUPFTS.MAC;30 24-MAY-78 18:26:44 EDIT BY TAFT
; Add [New-store] command
;<PUP>PUPFTS.MAC;29 3-FEB-78 19:46:55 EDIT BY TAFT
; Add distinct error code for file busy
;<PUP>PUPFTS.MAC;28 16-SEP-77 10:14:14 EDIT BY TAFT
;<PUP>PUPFTS.MAC;27 1-SEP-77 15:54:44 EDIT BY TAFT
; Make a few things internal for the mail server
;<PUP>PUPFTS.MAC;26 26-JUL-77 14:58:18 EDIT BY TAFT
; "Directory" defaults server filename to *.*;*
; Reset watchdog timer in appropriate places
;<PUP>PUPFTS.MAC;25 3-JUN-77 13:09:16 EDIT BY TAFT
; Add "Rename" command
;<PUP>PUPFTS.MAC;24 15-APR-77 09:55:20 EDIT BY TAFT
; Move some flag bits to avoid conflict with PUPDEF
; Default version to lowest in "Delete"
;<PUP>PUPFTS.MAC;23 7-APR-77 17:42:08 EDIT BY TAFT
; Zero memory cells holding JFNs when they are closed or released.
;<PUP>PUPFTS.MAC;22 31-MAR-77 21:10:00 EDIT BY TAFT
; Add hooks for mail server
;<PUP>PUPFTS.MAC;20 19-MAR-77 20:05:57 EDIT BY TAFT
; Add "Delete" command
;<PUP>PUPFTS.MAC;19 19-MAR-77 13:39:42 EDIT BY TAFT
; Fix bugs in Tenex-paged stuff
;<PUP>PUPFTS.MAC;18 18-MAR-77 19:13:59 EDIT BY TAFT
; Add REFILL procedure for property list parser
;<PUP>PUPFTS.MAC;16 18-MAR-77 17:41:10 EDIT BY TAFT
; Rip out property list parser and data transfer code --
; now share these modules with PUPFTP.
; Add checks for Tenex-paged type.
;<PUP>PUPFTS.MAC;14 17-OCT-76 00:41:41 EDIT BY TAFT
; Permit retrieving a 36-bit file with any byte size, but
; generate a suitable warning message
;<PUP>PUPFTS.MAC;13 30-JUN-76 17:21:38 EDIT BY TAFT
; Remove various utility routines to PUPUTL.MAC
;<PUP>PUPFTS.MAC;11 1-JUN-76 19:41:50 EDIT BY TAFT
; Change FIXNAM to apply generic filename properties as defaults
;<PUP>PUPFTS.MAC;8 1-JUN-76 16:43:58 EDIT BY TAFT
; "Directory" returns properties as separate commands for each file
;<PUP>PUPFTS.MAC;7 31-MAY-76 19:13:49 EDIT BY TAFT
; Include dates in retrieve property lists
;<PUP>PUPFTS.MAC;5 14-MAY-76 20:07:10 EDIT BY TAFT
; Add code to handle CLOSF failure at FTPEND
;<PUP>PUPFTS.MAC;4 27-MAR-76 18:01:12 EDIT BY TAFT
; Straighten out defaulting of account during store
;<PUP>PUPFTS.MAC;3 26-MAR-76 02:12:08 EDIT BY TAFT
; Convert lower-case password properties to upper-case
; Copyright 1979, 1980 by Xerox Corporation
TITLE PUPFTS -- FTP SERVER FORK OF PUP SERVER
SUBTTL E. A. Taft / October, 1975
SEARCH PUPDEF,PSVDEF,STENEX
USEVAR FTPVAR,FTPPVR
; Parameters
; Herald for version reply
VERTXT: ASCIZ /1.37 13-Jan-83/
FTPVER==1 ; Protocol version implemented
; Local flags
LOGCKF==1B27 ; Login parameters have been checked
ACTCKF==1B28 ; Account has been checked
CONCKF==1B29 ; Connect parameters have been checked
DSKDVF==1B30 ; Current file is on disk
; Assemble main command dispatch table
DEFINE XN(SYM,TYPE,NAME,FLAGS) <
REPEAT TYPE-<.-MRKDSP>,<0>
IF2,<IFNDEF C.'SYM,<EXTERN C.'SYM>>
0 ,, C.'SYM
>
DEFINE XS(SYM,TYPE,NAME,FLAGS) <
REPEAT TYPE-<.-MRKDSP>,<0>
IF2,<IFNDEF C.'SYM,<EXTERN C.'SYM>>
0 ,, C.'SYM
>
MRKDSP::MARKS
; Assemble Mark names and flags
DEFINE XN(SYM,TYPE,NAME,FLAGS) <
REPEAT TYPE-<.-MRKNAM>,<0>
FLAGS + [ASCIZ /NAME/]
>
DEFINE XS(SYM,TYPE,NAME,FLAGS) <
REPEAT TYPE-<.-MRKNAM>,<0>
1B0+FLAGS + [ASCIZ /NAME/]
>
MRKNAM::MARKS
NMARKS==.-MRKNAM
; -----------------------------------------------------------------
; Initialization, Main Loop, Termination
; -----------------------------------------------------------------
; Server fork started here
; Initialize
FTPFRK::JSYS FRKINI## ; Initialize ac's and stuff
HRRZ SV,SERVX ; Service table index in RH only
MOVEI A,400000 ; Initialize psi system
MOVE B,[LEVTAB##,,CHNTAB]
SIR
EIR
MOVE B,[ACTCHN] ; Activate channels
AIC
HRRZ A,FRKJFN(FX) ; Get address of foreign port
MOVE C,[1,,FRNHAD]
GDSTS
HRROI A,FRNHNM ; Get foreign host name
MOVE B,[1B1+1B2+C]
MOVE C,FRNHAD
SETZ D,
PUPNM
PUSHJ P,SCREWUP##
; Main command loop
COMLP: PUSHJ P,GETCMD ; Get next command
JRST FTPEND ; End received, close connection
MOVE C,MRKNAM(A) ; Check for command allowed in this server
CAIN SV,SV.MAI
TLZA C,(NOTFTS) ; We are a mail server
TLZ C,(NOTMLS) ; We are an FTP server
TLNE C,(NOTFTS+NOTMLS)
JRST [ HRRO A,MRKNAM(A)
HLRO B,SRVDSP##(SV)
FTPM(NO,3,<Command [%1S] not permitted in %2S server>)
JRST COMLP]
MOVE P1,MRKDSP(A) ; Get dispatch
PUSHJ P,0(P1) ; Process the command
JRST COMLP ; Repeat for next
; Here when End received
FTPEND::HLRZ A,FRKJFN(FX) ; Get Pup input JFN
CLOSF ; Close it
PUSHJ P,SCREWUP##
HRRZS A,FRKJFN(FX) ; Now same for output JFN
CLOSF
JRST [ ELOG <Connection closed uncleanly%/ - %1J>
HRRZ A,FRKJFN(FX) ; Should work a second time
CLOSF
HALTF
JRST .+1]
SETZM FRKJFN(FX) ; Note that no JFNs exist now
HALTF ; Terminate normally
; -----------------------------------------------------------------
; Individual command handlers
; -----------------------------------------------------------------
; "End-Of-Command"
C.EOC: FTPM(EOC,,,1) ; Just send answering EOC
; "Version"
C.VERS: HRROI A,VERTXT ; Give version string for text
HRROI B,LCLHNM## ; Local host name string
HLRO C,SRVDSP##(SV) ; Server type name
FTPM(VERS,FTPVER,<%2S Pup %3S Server %1S>,1)
; Commands that should never be received at top level
C.YES: ; "Yes"
C.NO: ; "No"
C.FILE: ; "Here-is-file"
C.PLST: ; "Here-is-property-list"
DTYPE < - Unexpected command%/>
; Commands that are ignored
C.ABOR: ; "Abort" (no-op at top level)
C.COMM: POPJ P, ; "Comment"
; "Retrieve"
C.RETR: SETZM FILPRP ; Clear out property list
MOVE A,[FILPRP,,FILPRP+1]
BLT A,FILPRP+PLSIZE-1
HRROI A,NETBUF ; Set pointer to argument string
MOVEI B,FILPRP ; File property list
PUSHJ P,SCNPRP## ; Scan property list
POPJ P, ; Failed
MOVEI A,FILPRP
MOVEI B,LOGCHK ; Normal login check
CAIN SV,SV.MAI ; Mail server?
MOVEI B,ANOLOG ; Yes, anonymous login
PUSHJ P,0(B) ; Check login/connect parameters
POPJ P, ; Failed
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,FILPRP ; Where property list is now
PUSHJ P,FIXNAM ; Fix args, build command list
MOVSI C,(1B2+1B11) ; Old file, permit "*"
HLLM C,TEMP
GTJFN ; Lookup the Server-Filename string
JRST RGJFER ; Failed, give reason and quit
MOVEM A,SRCJFN## ; Ok, save source JFN and flags
HRRZS A
PUSHJ P,CHKDSK ; Check device type
HRRZ A,FILPRP+P.TYPE ; Get transfer type
CAIN A,3 ; Paged?
TRNE F,DSKDVF ; Yes, device other than disk?
JRST C.RET1 ; No, ok
HRRZ A,SRCJFN## ; Yes, release JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
FTPM(NO,15,<Type Tenex-Paged illegal for non-disk files>,1)
; Now have JFN (possibly indexable)
; Back here for each file referenced by the JFN
C.RET1: MOVE A,[FILPRP,,TMPPRP] ; Copy supplied property list
BLT A,TMPPRP+PLSIZE-1 ; into temps for this retrieval
MOVE A,SRCJFN## ; Get file JFN, including left half bits
MOVEI B,TMPPRP ; Set pointer to property list
PUSHJ P,CKRPAR ; Check retrieval parameters
JRST C.RET8 ; Bad, bypass this file
PUSHJ P,GNRPRP ; Generate property list reply
; Await "Yes" or "No" from user
C.RET2: PUSHJ P,GETCMD ; Get next command
JRST [ HRRZ A,SRCJFN## ; Connection closed, release JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
JRST FTPEND] ; Handle EOF normally
CAIN A,MKNO ; Check command
JRST C.RET8 ; "No", skip this file
CAIN A,MKYES
JRST C.RET6 ; "Yes", send this file
MOVE C,MRKNAM(A) ; Not one of those, get dispatch
TLNN C,(OKRETR) ; Command ok in this context?
JRST [ HRRZ A,SRCJFN## ; No, flush file JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
HRROS C ; Make string ptr to command name
FTPM(NO,3,<Command [%3S] out of sequence during Retrieve>,1)]
MOVE C,MRKDSP(A) ; Ok, get command dispatch
PUSHJ P,0(C) ; Do the command
JRST C.RET2 ; Look for another
; "Retrieve" (cont'd)
; Here when "Yes" encountered. Now send the file.
C.RET6: PUSHJ P,OPNRET ; Open file for retrieve
JRST C.RET8 ; Failed
HRRZ A,SRCJFN## ; Ok, get file JFN
LOG <Retrieve %1F> ; Make log entry
MOVEI A,MKFILE ; Insert "Here-Is-File" mark
SETZ B,
PUSHJ P,BEGCMD
HRRZ A,FRKJFN(FX) ; Destination is net
MOVEM A,DSTJFN##
MOVEI A,TMPPRP ; Property list
PUSHJ P,SNDDAT## ; Do the retrieval
JRST [ HRRZ A,SRCJFN## ; Data error, get JFN
FTPM(NO,103,<Data error in %1F, Retrieve aborted>)
JRST C.RET7]
HRRZ A,SRCJFN##
FTPM(YES,0,<Retrieve of %1F completed>)
C.RET7: HRLI A,400000 ; Close file, don't release JFN
CLOSF
PUSHJ P,SCREWUP##
; Here when done retrieval of one file. Check for more to do
C.RET8: SETZM DSTJFN##
MOVE A,SRCJFN## ; Get JFN with flags
GNJFN ; Step to next file if any
JRST [ SETZM SRCJFN## ; No more, done (JFN released)
POPJ P,]
JRST C.RET1 ; Another, go retrieve it
; "Retrieve" subroutines ...
; Check and/or default parameters for "Retrieve" command
; A/ JFN for file being retrieved -- including left-half bits
; B/ Pointer to property list
; Returns +1: Error, reply message already generated (as either Comment
; or No, depending on whether or not the JFN is indexable --
; i.e., whether or not successor files are possible)
; +2: Ok, type and byte size filled in appropriately
; Clobbers B-D
CKRPAR: PUSHJ P,SAVE1## ; Preserve another ac
MOVE P1,B ; Put the plist pointer there
SETZ B, ; Assume don't know byte size
TRNN F,DSKDVF ; Retrieving from disk?
JRST CKRPA2 ; No
MOVE B,[1,,11] ; Yes, read byte size from FDB
MOVEI C,C ; Put it here
PUSH P,A
HRRZS A
GTFDB
POP P,A
LDB B,[POINT 6,C,11] ; Extract byte size
CKRPA2: HRRZ C,P.TYPE(P1) ; Get specified transfer type
HRRZ D,P.BYTE(P1) ; Get specified byte size
JRST @[ RTYUNS ; Dispatch on type: Unspecified
RTYTXT ; Text
RTYBIN ; Binary
RTYPAG](C) ; Paged
; CKRPAR (cont'd)
; Here for type unspecified
RTYUNS: SKIPN D,B ; File byte size known?
JRST [ TLNE A,(77B5) ; Indexable JFN?
FTPM(COMM,,<Type specification required to retrieve %1F>,1)
FTPM(NO,,<Type specification required to retrieve %1F>,1)]
MOVEI C,2 ; Assume binary
CAIN B,7 ; 7-bit file?
MOVEI C,1 ; Yes, assume text
JRST CKRPA4 ; Go set parameters
; Here for type Text
RTYTXT: SKIPE D,B ; File byte size known?
CAIN D,↑D36 ; Yes, 36 bit words?
MOVEI D,7 ; Not known or 36, assume 7 bit
CAIE D,7 ; Legal byte size?
CAIN D,8
JRST CKRPA4 ; Yes
TLNE A,(77B5) ; Indexable JFN?
FTPM(COMM,,<Type Text inconsistent with byte size %4D of file %1F>,1)
FTPM(NO,,<Type Text inconsistent with byte size %4D of file %1F>,1)
; Here for type Binary
RTYBIN: JUMPN D,.+3 ; Transfer byte size specified?
SKIPN D,B ; No, substitute file byte size
JRST [ TLNE A,(77B5) ; Indexable JFN?
FTPM(COMM,,<Byte size specification required to retrieve %1F>,1)
FTPM(NO,,<Byte size specification required to retrieve %1F>,1)]
JUMPE B,CKRPA4 ; Ok if file byte size unknown
CAMN B,D ; Both known, consistent?
JRST CKRPA4 ; Yes
CAIE B,↑D36 ; No, allow only if file byte size is 36
JRST [ TLNE A,(77B5) ; Indexable JFN?
FTPM(COMM,,<File %1F not retrieved:
requested byte size = %4D but actual file byte size = %2D>,1)
FTPM(NO,,<File %1F not retrieved:
requested byte size = %4D but actual file byte size = %2D>,1)]
FTPM(COMM,,<File %1F byte size = %2D being retrieved
with byte size = %4D. This may be incorrect -- beware!>)
JRST CKRPA4
; Here for type Tenex-Paged
RTYPAG: TRNN F,DSKDVF ; Is local file on disk?
PUSHJ P,SCREWUP## ; No (should not get here)
MOVEI D,↑D36 ; Set local byte size to 36
; All cases converge here
; C/ Transfer type, D/ Transfer byte size
CKRPA4: MOVEM C,P.TYPE(P1) ; Store type and byte size
MOVEM D,P.BYTE(P1)
JRST SKPRET## ; Return +2
; "Retrieve" subroutines ...
; Generate retrieval property list reply
; Specifically, generate Here-is-Property-List command
; Assumes TMPPRP contains Type/Byte-size properties for this file,
; FILPRP contains Desired-property property from user request,
; SRCJFN setup
; Returns +1
; Clobbers A-D
GNRPRP: PUSHJ P,SAVE1##
MOVE P1,FILPRP+P.DPRP
HRROI A,NETBUF ; Where to buffer property list
HRRZ B,SRCJFN## ; Get file JFN
MOVE C,FILPRP+P.DPRP
PUSHJ P,GFNPRP ; Generate filename properties
HRRZ B,TMPPRP+P.TYPE ; Get type
HRRZ C,TMPPRP+P.BYTE ; Get byte size
TXNE P1,1B<X.TYPE>+1B<X.BYTE> ; Type and/or Byte-size requested?
XCT [ ; Append properties as appropriate
WRITE <(Type Text)>
WRITE <(Type Binary)(Byte-Size %3D)>
WRITE <(Type Tenex-Paged)>
]-1(B)
TXNE P1,1B<X.SIZE>+1B<X.CDAT>+1B<X.WDAT>+1B<X.RDAT> ; Want these props?
TRNN F,DSKDVF ; Is file on disk?
JRST GNRPR2 ; No, omit remaining properties
PUSH P,D+1
PUSH P,A ; Yes, save dest designator
HRRZ A,SRCJFN## ; Get file JFN
MOVE B,[4,,12] ; Read FDBSIZ FDBCRE, FDBWRT, FDBRED
MOVEI C,B ; Put them in B, C, D, D+1
GTFDB
POP P,A ; Restore dest designator
TXNE P1,1B<X.SIZE> ; Output whatever was requested
WRITE <(Size %2D)>
TXNE P1,1B<X.CDAT>
WRITE <(Creation-date %3Z)>
TXNE P1,1B<X.WDAT>
WRITE <(Write-date %4Z)>
JUMPE D+1,.+3 ; Skip if never read
TXNE P1,1B<X.RDAT>
WRITE <(Read-date %5Z)>
POP P,D+1
GNRPR2: HRROI A,NETBUF ; Make ptr to property list
FTPM(PLST,,<(%1S)>,1) ; Send off command and return
; Open file for retrieval
; Assumes file JFN is in SRCJFN
; and that transfer parameters are in TMPPRP
; Returns +1: Failed, "No" reply already generated
; +2: Succeeded, file open
; Clobbers A-D
OPNRET: TRNN F,DSKDVF ; Is file on disk?
JRST OPNRT1 ; No, bypass extension check
HRROI A,TEMP ; Yes, buffer file extension here
SETZM TEMP
HRRZ B,SRCJFN##
MOVSI C,(1B11) ; Get extension only
JFNS
MOVEI B,1B19+1B25 ; Assume want to open thawed
MOVE A,TEMP ; Get the extension
CAME A,[ASCII /SAV/] ; Leave thawed if .SAV
OPNRT1: MOVEI B,1B19 ; Open in frozen mode
MOVE A,TMPPRP+P.BYTE ; Get transfer byte size
DPB A,[POINT 6,B,5] ; Put in position
HRRZ A,SRCJFN## ; Setup JFN
OPENF ; Attempt to open
JRST [ TRC B,1B25 ; Failed, try flipping thawed bit
HRRZ A,SRCJFN##
OPENF
JRST ROPNER ; Failed again, give error
JRST .+1] ; Succeeded, continue
JRST SKPRET## ; Return +2
; "New-store"
C.NSTO: HRROS 0(P) ; Signal that this is a new-store
JRST .+2
; "Store"
C.STOR: HRRZS 0(P) ; Signal that this is an old-store
SETZM FILPRP ; Clear out property list
MOVE A,[FILPRP,,FILPRP+1]
BLT A,FILPRP+PLSIZE-1
HRROI A,NETBUF ; Set pointer to argument string
MOVEI B,FILPRP ; File property list
PUSHJ P,SCNPRP## ; Scan property list
POPJ P, ; Failed
MOVEI A,FILPRP
PUSHJ P,LOGCHK ; Check login/connect parameters
POPJ P, ; Failed
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,FILPRP ; Where property list is
PUSHJ P,FIXNAM ; Fix args, build command list
MOVE D,B ; Save main string ptr
MOVSI C,(1B0) ; For output use
HLLM C,TEMP
MOVE C,C.UACT ; Current user account designator
MOVEM C,TEMP+7
GTJFN ; Get a JFN for the file
JRST [ CAIE A,GJFX23 ; Failed, directory full?
JRST SGJFER ; Other error, fail
MOVE A,C.UNAM ; Yes, expunge login and connected dirs --
DELDF ; too hard to figure out exactly what dir
MOVE A,C.CNAM ; is involved, and login and connected dirs
CAME A,C.UNAM ; are the only ones we can expunge anyway
DELDF
MOVEI A,TEMP ; Try again
MOVE B,D
GTJFN
JRST SGJFER ; Still failed
JRST .+1]
MOVEM A,DSTJFN## ; Ok, save destination JFN
MOVE B,[FILPRP,,TMPPRP] ; Copy supplied property list
BLT B,TMPPRP+PLSIZE-1 ; into temps for this Store
PUSHJ P,CHKDSK ; Check device type
MOVEI B,TMPPRP ; Set pointer to property list
PUSHJ P,CKSPAR ; Check store parameters
JRST C.STO8 ; Bad, quit
PUSHJ P,OPNSTO ; Open file for store
JRST C.STO8 ; Failed
; Set file creation date to the date supplied in the property list
SKIPE C,TMPPRP+P.CDAT ; Date present in property list?
TRNN F,DSKDVF ; File on disk?
JRST C.STO2 ; No
HRRZ A,DSTJFN## ; Yes, set creation date
HRLI A,13
SETO B,
CHFDB
; Store (cont'd)
; Now that we have the file open, generate the positive reply
; and then await the "Here-is-file" command and file data
C.STO2: SKIPGE 0(P) ; Which kind of store?
JRST [ HRROI A,NETBUF ; New, buffer property list here
HRRZ B,DSTJFN##
MOVE C,FILPRP+P.DPRP
PUSHJ P,GFNPRP ; Generate filename properties
HRROI A,NETBUF ; Send Here-is-property-list reply
FTPM(PLST,,<(%1S)>)
JRST C.STO3]
HRRZ A,DSTJFN## ; Get JFN for use in message
FTPM(YES,0,<File %1F open, ready for data>)
C.STO3: PUSHJ P,GETCMD ; Get next command
JRST STOEND ; End received
CAIN A,MKFILE ; "Here-is-file"?
JRST C.STO7 ; Yes, go receive file
CAIN A,MKNO ; "No"? (i.e. abort)
JRST [ HRRZ A,DSTJFN## ; Yes, report abortion
LOG <Store of %1F aborted>
JRST KILFIL] ; Flush output file and return
MOVE C,MRKNAM(A) ; No, get dispatch
TLNN C,(OKSTOR) ; Command ok during "Store"?
JRST [ HRRO C,MRKNAM(A) ; No, make ptr to command name
FTPM(NO,3,<Command [%3S] out of sequence during Store>)
JRST KILFIL] ; Flush output file and return
MOVE C,MRKDSP(A) ; Ok, get dispatch
PUSHJ P,0(C) ; Do the command
JRST C.STO3 ; Look for another
; Here when "Here-is-file" command encountered
C.STO7: HRRZ A,DSTJFN ; Get file JFN
LOG <Store %1F> ; Make log entry
HLRZ A,FRKJFN(FX) ; Source is net
MOVEM A,SRCJFN##
MOVEI A,TMPPRP ; Property list being used
PUSHJ P,RECDAT## ; Do the store
JRST [ PUSHJ P,GETCMD ; Failed, suck up next command
JRST STOEND ; End received
HRRZ A,DSTJFN## ; Report failure
LOG <Data error during Store of %1F>
FTPM(NO,103,<Data error during Store of %1F>)
SETZM SRCJFN##
JRST KILFIL] ; Flush output file and return
SETZM SRCJFN##
PUSHJ P,GETCMD ; Done, get next command
JRST STOEND ; End received
CAIN A,MKNO ; Terminated by "No"?
JRST [ HRRZ A,DSTJFN## ; Report abortion
LOG <Store of %1F aborted>
FTPM(NO,106,<Store of %1F not completed>)
JRST KILFIL] ; Flush output file and return
CAIE A,MKYES ; Terminated by "Yes"?
JRST [ HRRO C,MRKNAM(A) ; No, make ptr to command name
FTPM(NO,3,<Command [%3S] out of sequence during Store>)
JRST KILFIL] ; Flush output file and return
HRRZ A,DSTJFN## ; Transfer ok
FTPM(YES,0,<Store of %1F completed>)
CLOSF ; Close file
ELOG <Unlikely CLOSF error: %1J%/>
SETZM DSTJFN##
POPJ P, ; Done
; Store (cont'd)
; Here to give up before opening file
C.STO8: HRRZ A,DSTJFN## ; Just release JFN
RLJFN
PUSHJ P,SCREWUP
SETZM DSTJFN##
POPJ P, ; Done
; Here when End received in the middle of a Store
STOEND: HRRZ A,DSTJFN## ; Report abortion
LOG <Store of %1F aborted>
PUSHJ P,KILFIL ; Delete output file if possible
JRST FTPEND ; Handle EOF normally
; "Store" subroutines ...
; Check and/or default parameters for "Store" command
; A/ JFN for file being stored
; B/ Pointer to property list
; Returns +1: Error, reply message already generated
; +2: Ok, type and byte size filled in appropriately
; Clobbers B-D
CKSPAR: HRRZ D,P.BYTE(B) ; Get bytesize specification
HRRZ C,P.TYPE(B) ; Dispatch on type
JRST @[ STYUNS ; Unspecified
STYTXT ; Text
STYBIN ; Binary
STYPAG](C) ; Paged
STYUNS: FTPM(NO,102,<Type specification required to store %1F>,1)
STYPAG: TRNN F,DSKDVF ; Paged, devide disk?
FTPM(NO,15,<Type Tenex-Paged illegal for non-disk files>,1)
MOVEI D,↑D36 ; Byte size is 36
JRST .+2
STYTXT: MOVEI D,7 ; Text, use byte size 7
STYBIN: SKIPN D ; Ensure have byte size
FTPM(NO,102,<Byte size specification required to store %1F>,1)
HRRM D,P.BYTE(B) ; Store byte size in property list
JRST SKPRET## ; Return +2
; Open file for store
; Assumes file JFN is in DSTJFN
; and that transfer parameters are in TMPPRP
; Returns +1: Failed, reply already generated
; +2: Succeeded, file open
; Clobbers A-D
OPNSTO: MOVE B,TMPPRP+P.BYTE ; Get transfer byte size
ROT B,-6 ; Put in position
HRRI B,1B20 ; Say opening for writing
HRRZ A,DSTJFN## ; Setup JFN
OPENF ; Attempt to open
JRST SOPNER ; Failed, give error
JRST SKPRET## ; Succeeded, return +2
; "New-enumerate"
C.NENU: HRROS 0(P) ; Set flag to denote new protocol
JRST .+2
; "Enumerate"
C.ENUM: HRRZS 0(P)
SETZM FILPRP ; Clear out property list
MOVE A,[FILPRP,,FILPRP+1]
BLT A,FILPRP+PLSIZE-1
HRROI A,NETBUF ; Set pointer to argument string
MOVEI B,FILPRP ; File property list
PUSHJ P,SCNPRP## ; Scan property list
POPJ P, ; Failed
MOVEI A,FILPRP
PUSHJ P,LOGCHK ; Check login/connect parameters
POPJ P, ; Failed
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,FILPRP ; Where property list is now
PUSHJ P,FIXNAM ; Fix args, build command list
SKIPN C,0(A) ; Version specified?
MOVEI C,-3 ; No, default to *
HRLI C,(1B2+1B11) ; Old file, permit "*"
MOVEM C,0(A)
HRROI C,[ASCIZ /*/] ; Default name and extension to *
MOVEM C,4(A)
MOVEM C,5(A)
GTJFN ; Lookup the Server-Filename string
JRST RGJFER ; Failed, give reason and quit
MOVEM A,SRCJFN## ; Ok, save JFN and flags
SKIPL 0(P) ; New protocol?
JRST C.ENU2 ; No
MOVEI A,MKPLST ; Yes, begin the (single) reply
SETZ B,
PUSHJ P,BEGCMD
; Loop to generate file property list(s)
C.ENU2: PUSHJ P,SETWDT## ; Reset watchdog timer
HRRZ A,SRCJFN## ; Get JFN for file
PUSHJ P,CHKDSK ; Check device type
HRROI A,NETBUF ; Buffer the property list here
HRRZ B,SRCJFN## ; File JFN
MOVE C,FILPRP+P.DPRP
PUSHJ P,GENPRP ; Generate property list for file
HRROI B,NETBUF
SKIPGE 0(P) ; Which protocol?
JRST [ DTYPE <%2S>
HRRZ A,FRKJFN(FX) ; New, just send property list itself
SETZ C,
SOUT
JRST .+2]
FTPM(PLST,0,<%2S>) ; Old, send entire Here-is-property-list response
MOVE A,SRCJFN## ; Recover JFN and flags
GNJFN ; Step to next if any
JRST [ SETZM SRCJFN## ; No more, done
SKIPL 0(P)
POPJ P, ; Old protocol
PUSHJ P,ENDCMD ; New protocol, force byte stream
DTYPE <%/>
POPJ P,]
JRST C.ENU2 ; More, repeat
; Generate complete file property list for supplied JFN
; A/ Destination designator
; B/ JFN
; C/ Desired-property flags
; Returns +1 always: A/ Designator (updated if string ptr)
; Clobbers B-D
GENPRP: PUSHJ P,SAVE1##
MOVE P1,C ; Preserve Desired-property flags
HRLM B,0(P) ; Preserve file JFN
WRITE <(> ; Start property list
PUSHJ P,GFNPRP ; Generate filename properties
MOVE D,A ; Move designator to D
TXNE P1,1B<X.TYPE>+1B<X.BYTE>+1B<X.SIZE>+1B<X.CDAT>+1B<X.WDAT>+1B<X.RDAT>+1B<X.AUTH>
; Want any of these properties?
TRNN F,DSKDVF ; Is file on disk?
JRST GENPR9 ; No, no more attributes
; For disk, output all interesting things in the FDB
HLRZ A,0(P)
MOVE B,[25,,0] ; Read entire FDB
MOVEI C,TEMP ; Put it here
GTFDB
TXNN P1,1B<X.TYPE>+1B<X.BYTE> ; Want Type and/or Byte-size?
JRST GENPR2 ; No
LDB B,[POINT 6,TEMP+11,11] ; Get byte size
JUMPE B,GENPR2 ; Jump if unknown
CAIN B,7 ; Say Text if byte size 7
WRITE D,<(Type Text)>
CAIE B,7 ; Binary for anything else
WRITE D,<(Type Binary)(Byte-size %2D)>
GENPR2: MOVE B,TEMP+12 ; Get file length
TXNE P1,1B<X.SIZE> ; Want Size property?
WRITE D,<(Size %2D)>
MOVE B,TEMP+13 ; Get creation date
TXNE P1,1B<X.CDAT> ; Want Creation-date property?
WRITE D,<(Creation-date %2Z)>
MOVE B,TEMP+14 ; Get write date
TXNE P1,1B<X.WDAT> ; Want Write-date property?
WRITE D,<(Write-date %2Z)>
TXNN P1,1B<X.RDAT> ; Want Read-date property?
JRST .+3
SKIPE B,TEMP+15 ; Get read date if there is one
WRITE D,<(Read-date %2Z)>
TXNN P1,1B<X.AUTH> ; Want Author property?
JRST .+4
HLRZ B,TEMP+6 ; Get author
SKIPE B ; Output if present
WRITE D,<(Author %2U)>
GENPR9: MOVE A,D ; Destination designator back to A
WRITE <)> ; End property list
POPJ P, ; Done
; "Delete"
C.DELE: SETZM FILPRP ; Clear out property list
MOVE A,[FILPRP,,FILPRP+1]
BLT A,FILPRP+PLSIZE-1
HRROI A,NETBUF ; Set pointer to argument string
MOVEI B,FILPRP ; File property list
PUSHJ P,SCNPRP## ; Scan property list
POPJ P, ; Failed
MOVEI A,FILPRP
PUSHJ P,LOGCHK ; Check login/connect parameters
POPJ P, ; Failed
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,FILPRP ; Where property list is now
PUSHJ P,FIXNAM ; Fix args, build command list
SKIPN C,0(A) ; Was version specified?
MOVEI C,-2 ; No, default to lowest
HRLI C,(1B2+1B11) ; Old file, permit "*"
MOVEM C,0(A)
GTJFN ; Lookup the Server-Filename string
JRST RGJFER ; Failed, give reason and quit
MOVEM A,SRCJFN## ; Ok, save source JFN and flags
HRRZS A
PUSHJ P,CHKDSK ; Check device type
; Now have JFN (possibly indexable)
; Back here for each file referenced by the JFN
C.DEL1: PUSHJ P,SETWDT## ; Reset watchdog timer
HRROI A,NETBUF ; Where to buffer property list
HRRZ B,SRCJFN## ; Get file JFN
MOVE C,FILPRP+P.DPRP
PUSHJ P,GENPRP ; Generate full property list
HRROI A,NETBUF ; Make ptr to property list
FTPM(PLST,,<%1S>) ; Send it off
; Await "Yes" or "No" from user
C.DEL2: PUSHJ P,GETCMD ; Get next command
JRST [ HRRZ A,SRCJFN## ; Connection closed, release JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
JRST FTPEND] ; Handle EOF normally
CAIN A,MKNO ; Check command
JRST C.DEL8 ; "No", skip this file
CAIN A,MKYES
JRST C.DEL6 ; "Yes", delete this file
MOVE C,MRKNAM(A) ; Not one of those, get dispatch
TLNN C,(OKRETR) ; Command ok in this context?
JRST [ HRRZ A,SRCJFN## ; No, flush file JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
HRROS C ; Make string ptr to command name
FTPM(NO,3,<Command [%3S] out of sequence during Delete>,1)]
MOVE C,MRKDSP(A) ; Ok, get command dispatch
PUSHJ P,0(C) ; Do the command
JRST C.DEL2 ; Look for another
; Here when "Yes" encountered. Now delete the file.
C.DEL6: HRRZ A,SRCJFN## ; Get file JFN
LOG <Delete %1F> ; Make log entry
HRLI A,400000 ; Don't release JFN
DELF ; Delete file
JRST [ LOG <Delete failed - %1J>
PUSHJ P,DELERR ; Report reason
JRST C.DEL8] ; On to next
HRRZ A,SRCJFN## ; Succeeded, report
FTPM(YES,0,<File %1F deleted>)
; Here when done deleting one file. Check for more to do
C.DEL8: MOVE A,SRCJFN## ; Get JFN with flags
GNJFN ; Step to next file if any
JRST [ SETZM SRCJFN## ; No more, done (JFN released)
POPJ P,]
JRST C.DEL1 ; Another, go delete it
; "Rename"
C.RENA: SETZM FILPRP ; Clear out property list
MOVE A,[FILPRP,,FILPRP+1]
BLT A,FILPRP+PLSIZE-1
SETZM TMPPRP ; Clear out another property list
MOVE A,[TMPPRP,,TMPPRP+1]
BLT A,TMPPRP+PLSIZE-1
HRROI A,NETBUF ; Set pointer to argument string
MOVEI B,FILPRP ; Where to put properties
PUSHJ P,SCNPRP## ; Scan property list
POPJ P, ; Failed
MOVEI B,TMPPRP
PUSHJ P,SCNPRP## ; Scan second property list
POPJ P,
MOVEI A,FILPRP
PUSHJ P,LOGCHK ; Check login/connect parameters
POPJ P, ; Failed
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,FILPRP ; Where "old" property list is now
PUSHJ P,FIXNAM ; Fix args, build command list
MOVSI C,(1B2) ; Old file required
IORM C,0(A)
GTJFN ; Lookup the "old" server filename
JRST RGJFER ; Failed, give reason and quit
MOVEM A,SRCJFN## ; Ok, save JFN
MOVEI A,TEMP ; Where to build GTJFN command list
MOVEI B,TMPPRP ; Where "new" property list is now
PUSHJ P,FIXNAM ; Fix args, build command list
MOVSI C,(1B0+1B1) ; Output use, new file only
IORM C,0(A)
GTJFN ; Get JFN for new file
JRST [ PUSHJ P,RENGJF ; Failed, give reason
HRRZ A,SRCJFN## ; Release old JFN
RLJFN
PUSHJ P,SCREWUP##
SETZM SRCJFN##
POPJ P,]
MOVE B,A ; Ok, get new JFN
MOVE A,SRCJFN## ; Old JFN
RNAMF ; Attempt rename
JRST [ PUSHJ P,RENERR ; Failed, give reason
HRRZ A,SRCJFN## ; Release old JFN
RLJFN
PUSHJ P,SCREWUP##
JRST .+2]
FTPM(YES,0,<Rename to %2F successful>)
SETZM SRCJFN## ; Old JFN now released
MOVE A,B ; Release new JFN
RLJFN
PUSHJ P,SCREWUP##
POPJ P, ; Done
; Generate filename properties given JFN
; A/ Destination designator
; B/ JFN
; C/ Desired-property flags
; Assumes DSKDVF set properly
; Returns +1 always: A/ Designator (updated if string ptr)
; Generates Device, Directory, Name-Body, Version, and
; Server-Filename properties as appropriate
; Clobbers B-D
GFNPRP: PUSHJ P,SAVE1##
MOVE P1,C ; Preserve flags
HRLM B,0(P) ; Preserve file JFN
MOVE D,A ; Put dest designator in D
TXNE P1,1B<X.DEVI> ; Want Device property?
TRNE F,DSKDVF ; Device disk?
JRST GFNPR1 ; Yes, omit device property
MOVSI C,(1B2) ; Print just device
PUSHJ P,DOJFNS ; Convert device name to string
WRITE D,<(Device %2S)> ; Generate property
GFNPR1: TXNE P1,1B<X.DIRE> ; Want Directory property?
TRNN F,DSKDVF ; Device disk?
JRST GFNPR2 ; No, omit directory
HLRZ B,0(P) ; Yes, get file JFN
MOVSI C,(1B5) ; Want just directory
PUSHJ P,DOJFNS ; Convert directory to string
WRITE D,<(Directory %2S)>; Generate property
GFNPR2: TXNN P1,1B<X.NAMB> ; Want Name-body property?
JRST GFNPR5 ; No
HLRZ B,0(P) ; Get file JFN
MOVE C,[1B8+1B11+1B35] ; Want name and ext, punctuated
PUSHJ P,DOJFNS ; Convert to string
PUSH P,B ; See if it ends in "."
GFNPR4: ILDB C,B
CAIN C,"."
JRST [ MOVE C,B
ILDB C,C
JUMPN C,GFNPR4
DPB C,B ; Yes, strip off trailing "."
JRST .+2]
JUMPN C,GFNPR4
POP P,B
WRITE D,<(Name-Body %2S)>; Generate property
GFNPR5: TXNE P1,1B<X.VERS> ; Want Version property?
TRNN F,DSKDVF ; Device disk?
JRST GFNPR3 ; No, omit version
HLRZ B,0(P) ; Yes, get file JFN
MOVSI C,(1B14) ; Want just version
PUSHJ P,DOJFNS ; Convert version to string
WRITE D,<(Version %2S)> ; Generate property
GFNPR3: TXNN P1,1B<X.SFIL> ; Want Server-filename property?
JRST GFNPR6 ; No
HLRZ B,0(P) ; Get file JFN
TRNN F,DSKDVF ; Device disk?
TDZA C,C ; No, print in default format
MOVE C,[2B2+1B5+1B8+1B11+1B14+1B21+1B35] ; Yes, force dir
PUSHJ P,DOJFNS ; Convert filename to string
WRITE D,<(Server-Filename %2S)>; Generate property
GFNPR6: MOVE A,D ; Put designator back in A
POPJ P, ; Done
; Do JFNS with output suitable for inclusion in property list
; B/ File JFN
; C/ JFNS flags
; Returns +1:
; B/ String ptr to JFNS text
; Clobbers A-C
DOJFNS: MOVE A,[POINT 7,TEMP] ; Where to put temp text
JFNS ; Convert to string
MOVE A,[POINT 7,TEMP] ; Source string
MOVE B,[POINT 7,TEMP+100] ; Destination string
DOJFN1: ILDB C,A ; Get a char
CAIN C,"V"-100 ; Tenex filename quote?
JRST DOJFN1 ; Yes, flush it
CAIE C,"(" ; Character need to be quoted?
CAIN C,")"
JRST .+3 ; Yes
CAIE C,PQUOTE
JRST DOJFN2 ; No
MOVEI C,PQUOTE ; Yes, insert quote character
IDPB C,B
LDB C,A ; Recover character
DOJFN2: IDPB C,B ; Store the character
JUMPN C,DOJFN1 ; Repeat if not at end
MOVE B,[POINT 7,TEMP+100] ; Where result string is now
POPJ P, ; Done
; Here to handle GTJFN errors peculiar to "Retrieve"
RGJFER::CAIL A,GJFX18 ; File not found errors
CAILE A,GJFX21
CAIN A,GJFX24
FTPM(NO,100,<No such file exists>,1)
JRST GJFERR ; Handle rest same as Store
; GTJFN errors peculiar to "Rename"
RENGJF: CAIE A,GJFX27 ; New file only
CAIN A,GJFX20 ; No such version (?? -- really flakey)
FTPM(NO,112,<Rename "to" file already exists>,1)
; GTJFN errors peculiar to "Store"
SGJFER: CAIL A,GJFX18 ; File not found errors
CAILE A,GJFX21 ; Really mean can't create file
CAIN A,GJFX24
FTPM(NO,101,<No access to create that file>,1)
; Here for errors common to "Retrieve" and "Store"
GJFERR: CAIL A,GJFX4 ; Illegal format errors
CAILE A,GJFX15
CAIN A,GJFX31
FTPM(NO,11,<Illegal filename>,1)
CAIE A,GJFX33 ; More illegal format errors
CAIN A,GJFX34
FTPM(NO,11,<Illegal filename>,1)
CAIN A,GJFX16 ; More file not found errors
FTPM(NO,100,<No such device exists>,1)
CAIN A,GJFX17
FTPM(NO,100,<No such directory exists>,1)
CAIN A,GJFX32
FTPM(NO,100,<No such file exists>,1)
FTPM(NO,0,<Filename error: %1J>,1) ; All others
; Here to handle OPENF errors peculiar to "Retrieve"
ROPNER::HRRZ C,SRCJFN## ; Setup JFN for reply message
JRST OPNERR ; Join common code
; Here to handle OPENF errors peculiar to "Store"
SOPNER: HRRZ C,DSTJFN## ; Setup JFN for message
; OPENF failure code common to "Retrieve" and "Store"
OPNERR: CAIL A,OPNX3 ; Check for protection errors
CAILE A,OPNX6
CAIN A,OPNX13
FTPM(NO,101,<Access denied to file %3F>,1)
CAIN A,OPNX23
FTPM(NO,101,<Access denied to directory containing %3F>,1)
CAIN A,OPNX9 ; File busy error
FTPM(NO,111,<File %3F busy>,1)
CAIN A,OPNX10 ; Disk full error
FTPM(NO,104,<No room for file %3F>,1)
CAIN A,SFBSX2 ; Byte size error
FTPM(NO,102,<Illegal byte size for file %3F>,1)
FTPM(NO,0,<File open error: %1J for file %3F>,1) ; Other
; "Delete" errors
DELERR: HRRZ C,SRCJFN##
CAIN A,DELFX1
FTPM(NO,101,<Delete access denied to file %3F>,1)
FTPM(NO,0,<Delete error: %1J for file %3F>,1)
; "Rename" errors
RENERR: HRRZ C,SRCJFN## ; Get "from" JFN
CAIN A,RNAMX3
FTPM(NO,101,<Access denied to new file %2F>,1)
CAIN A,RNAMX5
FTPM(NO,111,<File %2F busy>,1)
CAIN A,RNAMX8
FTPM(NO,101,<Access denied to existing file %3F>,1)
CAIN A,RNMX10
FTPM(NO,101,<File %3F busy>,1)
FTPM(NO,0,<Rename %3F to %2F failed: %1J>,1)
; -----------------------------------------------------------------
; File system utilities
; -----------------------------------------------------------------
; Check for device disk
; A/ JFN for file being retrieved or stored
; Returns +1 always
; Sets DSKDVF flag appropriately
; Clobbers B, C
CHKDSK: PUSH P,A ; Save JFN
DVCHR ; Get device characteristics
POP P,A ; Restore JFN
TLNN B,377 ; Check device type
TROA F,DSKDVF ; Device is a disk
TRZ F,DSKDVF ; Device is not a disk
POPJ P,
; "Kill" destination file, i.e. delete all its pages and
; try very hard to make it go away (works only for disk).
; Assumes DSTJFN contains open output JFN and that DSKDVF
; has already been set appropriately.
; Returns +1 always
; Closes and releases JFN
; Clobbers A-D
KILFIL::HRRZ A,DSTJFN## ; Get destination JFN
DELF ; First attempt to delete file
JRST KILFI5 ; Non-directory or no access, stop
TRNN F,DSKDVF ; Output to disk?
JRST KILFI5 ; No, just close file
MOVE B,[1,,1] ; Yes, get FDBCTL word
MOVEI C,C
GTFDB
TLNN C,(1B4) ; FDBNXF set?
JRST KILFI5 ; No, file previously existed
PUSHJ P,DELPGS ; Yes, delete all pages in file
HRRZ A,DSTJFN## ; Close the file
HRLI A,400000 ; but don't release JFN
CLOSF
POPJ P, ; Failed? give up
HRLI A,1 ; Now set FDBNXF to make the
MOVSI B,(1B4) ; file look invisible
MOVSI C,(1B4)
CHFDB
HRRZ A,DSTJFN## ; Release JFN
RLJFN
CAI
SETZM DSTJFN##
POPJ P, ; Done
; Here to just close file
KILFI5: HRRZ A,DSTJFN##
CLOSF
CAI
SETZM DSTJFN##
POPJ P,
; Delete all pages in a disk file
; Assumes file open for writing
; A/ JFN
; Returns +1 always
; Clobbers A, B
DELPGS::HRLZ A,A ; Make designator for file page 0
SETO B, ; Set arg for deleting pages
DELPG1: FFUFP ; Find next existing page
POPJ P, ; No more, done
EXCH A,B ; Found one, delete it
PMAP
EXCH A,B
AOJA A,DELPG1 ; On to next
; Fix up filename strings for Retrieve, Store, etc.
; A/ Where to build command list for GTJFN
; B/ Property list pointer
; Specifically, if a Server-Filename was not supplied, construct
; one from the Name-Body property. Then setup the remaining
; properties (if any) as defaults.
; Returns +1:
; A/ unchanged
; B/ main string pointer (for GTJFN)
; Clobbers B, C
FIXNAM: HRRZ C,P.VERS(B) ; Default version
MOVEM C,0(A)
MOVE C,[377777,,377777] ; No file i/o for GTJFN
MOVEM C,1(A)
SKIPE C,P.DEVI(B) ; Device supplied?
HRROI C,P.DEVI(B) ; Yes, set default device
MOVEM C,2(A)
SKIPE C,P.DIRE(B) ; Directory supplied?
HRROI C,P.DIRE(B) ; Yes, set default directory
MOVEM C,3(A)
SETZM 4(A) ; No default name
SETZM 5(A) ; No default extension
SETZM 6(A) ; No default protection
SETZM 7(A) ; No default account
MOVEI B,P.SFIL(B) ; Make string ptr to server name
HRLI B,(POINT 7)
SKIPN 0(B) ; Is there one?
HRRI B,P.NAMB-P.SFIL(B) ; No, use name body
CAIE SV,SV.MAI ; Done if not a mail server
POPJ P,
; Can get here only during Retrieve command issued to Mail server.
; Force the directory to be <Secretary> and the extension to be .DL,
; and explicitly concatenate them to the main name string (rather than
; supplying them as defaults) so that they cannot be overridden by the user.
SETZM 2(A) ; No default device or directory
SETZM 3(A)
PUSH P,A
PUSH P,B
ILDB A,B ; Scan name string for "↑"
CAIE A,"↑"
JUMPN A,.-2
SETZ A, ; Truncate string at that point
DPB A,B
POP P,B
HRROI A,TEMP+20 ; Buffer new name here
WRITE <<SECRETARY>%2S.DL>
HRROI B,TEMP+20 ; Return that name instead
POP P,A
POPJ P,
; Anonymous login (permitted only for mail server .DL retrieval).
ANOLOG: SKIPN C.UNAM ; Currently logged in?
JRST SKPRET## ; No, proceed using PUPSRV capabilities
; If already logged in, check login/connect parameters as usual.
; Check and apply login/connect parameters
; A/ Property list pointer
; Returns +1: Incorrect, reply already generated
; +2: Parameters correct
; Updates flags LOGCKF, ACTCKF, and CONCKF appropriately
; Clobbers A-D
LOGCHK::PUSHJ P,SAVE1## ; Save P1
MOVE P1,A ; Setup property list pointer
; Check login directory # and password
SKIPN A,P.UNAM(P1) ; Setup supplied dir #
FTPM(NO,2,<User-Name and Password required>,1)
CAME A,C.UNAM ; Same as current?
JRST LOGCH1 ; No, have to re-check
HRROI A,P.UPSW(P1) ; Same password?
HRROI B,C.UPSW
PUSHJ P,STRCMP##
LOGCH1: TRZ F,LOGCKF+ACTCKF+CONCKF ; No, invalidate parameters
TRNE F,LOGCKF ; Need to check name/password?
JRST LOGCH2 ; No, bypass
; Name and/or password not the same as before (or not logged in
; previously). "Log in" this fork as specified.
HRRZ A,P.UNAM(P1) ; Get dir #
HRLI A,(1B1) ; Want proxy login
HRROI B,P.UPSW(P1) ; String ptr to password
CNDIR ; Do login of fork group
JRST [ CAIN A,CNDIX1 ; Failed, check error
FTPM(NO,21,<User-Password incorrect>,1)
FTPM(NO,20,<Login failure: %1J>,1)]
; *** Explicitly verify password, since it's not checked if
; logging in under the "current" login directory (e.g. SYSTEM)
HRLI A,(1B0) ; Say check password
CNDIR
FTPM(NO,21,<User-Password incorrect>,1)
; ***
; Now successfully "logged in". Record successful name/password
; combination for future checks.
HRRZ B,P.UNAM(P1) ; Ok, record current user name
MOVEM B,C.UNAM
MOVEM B,C.CNAM ; Now connected to that dir also
MOVSI A,P.UPSW(P1) ; Record successful password
HRRI A,C.UPSW
BLT A,C.UPSW+USRSTL/5
TRO F,LOGCKF+CONCKF ; Say logged in and connected ok
LOG <Login as user %2U> ; Make log entry
HRROI A,C.UACT+1 ; Where to put default acct string
GDACC ; Get default account
SETZ A, ; None, remember so
MOVEM A,C.UACT ; Store account designator
IFN RECPWF,<
MOVE A,C.UNAM
HRROI B,C.UPSW
PUSHJ P,RECPAS##
>
; LOGCHK (cont'd)
; See whether account is same as before.
LOGCH2: SKIPN B,C.UACT ; Get current account designator
JRST LOGCH3 ; None, force check of new one
SKIPE A,P.UACT(P1) ; Get specified account designator
CAMN A,B ; Numeric and same as current?
JRST LOGCH4 ; Yes (or none), don't force check
TLC A,(5B2) ; Zero B0-2 if numeric
TLC B,(5B2)
TLNE A,(7B2) ; Both string?
TLNN B,(7B2)
JRST LOGCH3 ; No, need to re-check
TLC A,(5B2) ; Yes, fix string pointers
TLC B,(5B2)
PUSHJ P,STRCMP## ; Compare strings
LOGCH3: TRZ F,ACTCKF ; Not equal, force check
LOGCH4: TRNE F,ACTCKF ; Need to check account?
JRST LOGCH6 ; No
; Account different from before, check new one.
HRRZ A,C.UNAM ; Yes, setup dir # of user
SKIPN B,P.UACT(P1) ; Get account designator if given
SKIPE B,C.UACT ; Default if not given
VACCT ; Verify account
FTPM(NO,22,<User-Account invalid>,1)
; New account is ok. Remember it for future checks.
SKIPN A,P.UACT(P1) ; Get supplied designator
JRST LOGCH5 ; Not supplied, remember default
MOVSI B,P.UACT(P1) ; Copy string if there is one
HRRI B,C.UACT
BLT B,C.UACT+USRSTL/5+1
HRROI B,C.UACT+1 ; Make string ptr
TLC A,(5B2) ; Is designator numeric?
TLNE A,(7B2)
MOVEM B,C.UACT ; No, store string ptr
LOGCH5: TRO F,ACTCKF ; Note that acct has been checked
; See whether connected dir # and password are same as before
LOGCH6: SKIPN A,P.CNAM(P1) ; Get specified connected dir #
MOVE A,C.UNAM ; None, assume same as login
CAME A,C.CNAM ; Same as current?
JRST LOGCH7 ; No, have to re-check
HRROI A,P.CPSW(P1) ; Same password?
HRROI B,C.CPSW
PUSHJ P,STRCMP##
LOGCH7: TRZ F,CONCKF ; No, invalidate parameters
TRNE F,CONCKF ; Need to check name/password?
JRST SKPRET## ; No, done, return +2
; Not the same, do new "connect" of fork to dir.
SKIPN A,P.CNAM(P1) ; Get connect dir #
MOVE A,C.UNAM ; None, use login dir #
HRROI B,P.CPSW(P1) ; Password
CNDIR ; Connect to directory
JRST [ CAIN A,CNDIX1 ; Failed, check error code
FTPM(NO,24,<Connect-Password incorrect>,1)
FTPM(NO,23,<Connect failure: %1J>,1)]
HRRZM A,C.CNAM ; Succeeded, save current dir #
MOVSI A,P.CPSW(P1) ; Record successful password
HRRI A,C.CPSW
BLT A,C.CPSW+USRSTL/5
TRO F,CONCKF ; Say connected ok
JRST SKPRET## ; Done, return +2
; -----------------------------------------------------------------
; Subroutines
; -----------------------------------------------------------------
; Get next command
; Returns +1: End received
; +2: Ok, A/ Mark type, B/ Subcommand byte (if any)
; Clobbers A-D
; Check status to distinguish between Mark and End
GETCMD::PUSHJ P,SETWDT## ; Reset watchdog timer
HLRZ A,FRKJFN(FX) ; Get input JFN
SETZ C, ; Don't want address stuff
GDSTS ; Get status
TLNE B,(1B5) ; End received?
POPJ P, ; Yes, fail return
TLZN B,(1B4) ; Mark received?
JRST GETCM4 ; No, go flush extraneous data
; Got Mark, prepare to process command
SDSTS ; Clear flag
MOVEI B,23 ; Get the Mark byte
MTOPR
CAIGE C,NMARKS ; Mark byte in range?
SKIPN D,MRKNAM(C) ; Yes, fetch name pointer
JRST GETCME ; No or undefined
PUSH P,C ; Save Mark byte
TLNE D,(NFETCH) ; Want to pre-fetch command text?
JRST GETCM2 ; No
HRROI B,NETBUF ; Yes, buffer as 7-bit ASCII
MOVNI C,5000 ; Max # bytes in buffer
SIN
SETZ A,
SKIPGE C ; Unless completely filled buffer,
IDPB A,B ; Put null on end
SKIPA D,[POINT 7,NETBUF] ; Init string ptr
GETCM2: SETZB D,NETBUF ; Here if no text
POP P,A ; Recover Mark byte
HRRO C,MRKNAM(A) ; Get name string
SKIPGE MRKNAM(A) ; Does command have subcommands?
JRST GETCM3 ; Yes
DTYPE <U: [%3S] %4S%/> ; No, print command if debugging
JRST SKPRET## ; Return +2
GETCM3: ILDB B,D ; Get subcommand
DTYPE <U: [%3S] <%2O> %4S%/>; Print command if debugging
JRST SKPRET## ; Return +2
; Here if command undefined
GETCME: FTPM(NO,1,<Undefined command [%3O]>)
; Flush byte stream data to next Mark
GETCM4: HLRZ A,FRKJFN(FX) ; Get input JFN
MOVE B,[POINT 8,NETBUF] ; Byte ptr to buffer
MOVNI C,4000 ; # bytes in buffer
SIN ; Suck bytes from net
JUMPGE C,GETCM4 ; Repeat if didn't get it all
JRST GETCMD ; Go look again for Mark
; Refill the command buffer if necessary
; A/ used string pointer into NETBUF
; Returns +1:
; A/ updated string pointer
; Clobbers B-D
REFILL::TRNN A,400 ; Have we read half the buffer?
POPJ P, ; No, nothing to do
MOVE B,[NETBUF+400,,NETBUF] ; Yes, move upper half down
BLT B,NETBUF+377
SUBI A,400 ; Fix pointer
PUSH P,A ; Save it
HLRZ A,FRKJFN(FX) ; Get net input JFN
HRROI B,NETBUF+400 ; Where to put more input
MOVNI C,400*5 ; Max # chars
SIN ; Get more input
SETZ A,
SKIPGE C ; Unless buffer filled,
IDPB A,B ; Put null on end
PUSHJ P,SETWDT## ; Reset watchdog timer
POP P,A ; Restore byte ptr
POPJ P,
; FTPM (mark type, sub-code, <string>, pop count)
; UUO to generate FTP reply message
%UFTPM::PUSH P,@40 ; Preserve control word
AOS 40 ; Advance to start of string
PUSHJ P,FORMAT## ; Call UUO output formatter
HRROI A,TEMP+600 ; Setup -- buffer reply here
PUSHJ P,UFTPM2 ; Completion -- send off reply
HRLS 0(P) ; Put pop count (+1) in both halves
SUB P,0(P) ; Pop stack appropriately
POPJ P, ; Return from UUO (or from caller)
; FTPM completion
UFTPM2: SETZ B, ; Terminate string with null
IDPB B,A
; If an End has been encountered on the receive connection, do not
; send any replies: the user is attempting to shut down the connection
; in the middle of a command, and sending any data may cause a deadlock.
HLRZ A,FRKJFN(FX)
SETZ C,
GDSTS
TLNE B,(1B5) ; End received?
POPJ P, ; Yes, do nothing
LDB A,[POINT 8,-6(P),7] ; Get Mark type
LDB C,[POINT 8,-6(P),15] ; Get subcommand code if any
HRROI B,TEMP+600 ; Point to buffered reply
; JRST SNDCMD ; Send off the command and return
; Send a command
; A/ Command number (Mark type)
; B/ String ptr to command text (0 => none)
; C/ Subcommand (iff command requires one)
; Returns +1
; Clobbers A-D
SNDCMD: PUSHJ P,BEGCMD ; Do the work
JRST ENDCMD ; Force transmission
; Begin command, i.e. do all the output but don't force
; transmission. Calling sequence same as SNDCMD
BEGCMD::HRRO D,MRKNAM(A) ; Get string ptr to command name
SKIPL MRKNAM(A) ; Does command have subcommands?
DTYPE <S: [%4S] %2S%/> ; No
SKIPGE MRKNAM(A)
DTYPE <S: [%4S] <%3O> %2S%/>; Yes
MOVE D,B ; Save string ptr
HRLM C,0(P) ; Save subcommand if any
MOVE C,A ; Copy command number
HRRZ A,FRKJFN(FX) ; Get output JFN
MOVEI B,3 ; Send Mark
MTOPR
HLRZ B,0(P) ; Get subcommand if any
SKIPGE MRKNAM(C) ; Does command have subcommands?
BOUT ; Yes, send subcommand code
SETZ C,
SKIPE B,D ; Is there a string?
SOUT ; Yes, send it
POPJ P, ; Done
; End command by forcing the byte stream
; Returns +1
; Clobbers A, B
ENDCMD: HRRZ A,FRKJFN(FX) ; Get output JFN
MOVEI B,21 ; Force transmission
MTOPR
POPJ P,
; PSI channel definitions
DEFINE PSI(CH,LEV,DISP) <
ACTCHN==ACTCHN!1B<CH>
RELOC CHNTAB+↑D<CH>
LEV ,, DISP
>
ACTCHN==0
CHNTAB: PSI(9,1,PDLOVF##) ; Pushdown overflow
PSI(11,1,FTSDTE) ; Data error
PSI(15,1,ILLINS##) ; Illegal instruction
PSI(16,1,ILLRED##) ; Illegal read
PSI(17,1,ILLWRT##) ; Illegal write
PSI(18,1,ILLXCT##) ; Illegal execute
PSI(20,1,ILLSIZ##) ; Machine size exceeded
RELOC CHNTAB+↑D36
; Handling for data error in FTP server fork
FTSDTE: PUSH P,A ; Save an ac
SKIPL A,SRCDSP ; Check for error dispatches
SKIPGE A,DSTDSP
TRNN A,-1 ; Both specified and armed?
JRST DATERR## ; No, treat as fatal error
HRRZM A,CH1PC ; Armed, clobber interrupt pc
SETZM SRCDSP ; Disarm errors
SETZM DSTDSP
POP P,A
MOVE P,ERRPDP ; Go to correct stack level
DEBRK ; Break to error dispatch
; Storage
LSP NETBUF,1 ; Network I/O buffer
LSP FILBUF,1 ; Local file buffer
LS ERRPDP ; Stack pointer to restore on data error
LS SRCDSP ; Source file data error dispatch
LS DSTDSP ; Destination file data error dispatch
LS C.UNAM ; Current login dir #
LS C.UPSW,USRSTL/5+1 ; Current login password
LS C.UACT,USRSTL/5+2 ; Current account designator
LS C.CNAM ; Current connected dir #
LS C.CPSW,USRSTL/5+1 ; Current connect password
LS FILPRP,PLSIZE ; File property list (Store/Retrieve)
LS TMPPRP,PLSIZE ; Temp property list (for one transfer)
LS FRNHAD ; Foreign host address (net,,host)
LS FRNHNM,10 ; Foreign host name as a string
END