FTP Server

The Powernet FTP server is a multi-threaded server that can accept multiple FTP connections. The model used is described in SERVERS.FTH. The code for the command channel is based on the Telnet code.

The objectives of the design are to use "not a lot" of RAM, and to keep the code size down by not providing many bells and whistles. A side effect of the low RAM usage is that directory listings are rather slow. Initially, the intention was only to support the requirements of RFC959, the primary FTP specification. The only client that actually works fully in this mode is WS_FTP. In order to support clients that are not so forgiving, a few extra commands are supported. The FTP server has been tested with:

The use of the FATfiler file system code is assumed. Because this is not a fully thread-safe file system and only has a single "working directory" for all threads, this FTP server is not suitable for use with FTP clients that assume a Unix-style operating system. Such clients include the FTP client built into Finder on Mac OS X. FileZilla works well on the Mac, as well as on Linux and Windows.

FTP data

struct /FTPdata \ -- n
The standard service data structure is extended for FTP.

: cleanFTP      \ *sv -- *sv
Clean up an FTP service block before it is released.

: my_ftpState           \ -- addr
Holds the state of the FTP system.

: my_ftpPassive?        \ -- addr
Holds the passive flag.

: my_ftpBinary? \ -- addr
Holds the binary flag.

: my_ftpQuit?   \ -- addr
Holds the QUIT flag.

: my_ftpDataIP          \ -- addr
Holds the IP adddress of the data channel.

: my_ftpDataPort        \ -- addr
Holds the port number of the data channel.

: my_ftpDataSock        \ -- addr
Holds the socket handle of the data channel.

: my_ftpDataState       \ -- addr
Holds the state number of the data channel.

: my_ftpDataFile        \ -- addr
Holds the handle of the file being read/written.

: my_ftpLine?           \ -- addr
Holds true if a complete line can be processed.

: my_ftpLineLen         \ -- addr
Holds the current length of the line excluding terminators.

: my_ftpSrc$            \ -- addr
Buffer that holds a source file path as a counted string.

: my_ftpDest$           \ -- addr
Buffer that holds a destination file path as a counted string.

: my_ftpBuff            \ -- addr
Buffer that holds FTP data for transfer.

FTP vectored I/O

The FTP server establishes its own generic I/O for the data and command channels. The data channel operations are written for minimum RAM usage and use the sockets for buffering.

Data socket

: checkSocket   \ hs -- ior
The ior is returned non-zero if the socket is invalid or not in TCPS_ESTABLISHED state.

: sockKey?      \ hs -- #chars
Return the number of available characters from the socket. If an error has occurred, 1 is returned.

: sockKey       \ hs -- char
Return a character from a socket. On a socket error, an LF is returned.

: sockType      \ caddr len hs --
Send a string/buffer to the socket.

: sockEmit      \ char hs --
Send a character to the socket.

: sockCr        \ hs --
Send a CR/LF pair to the socket.

The following five words are used to provide generic I/O on the FTP data channel.

: FTPdataKey    ( -- char )     my_ftpDataSock @ sockKey  ;
: FTPdataKey?   ( -- flag )     my_ftpDataSock @ sockKey?  ;
: FTPdataEmit   ( char -- )     my_ftpDataSock @ sockEmit  ;
: FTPdataType   ( caddr len -- ) my_ftpDataSock @ sockType  ;
: FTPdataCr     ( -- )          my_ftpDataSock @ sockCr  ;

create ConFTPdata       \ -- addr ; OUT managed by upper driver
Function despatch table for FTP data channel I/O. OUT is managed by the upper level driver.

: [FTPdataIo    \ -- ; R: --  ipvec opvec
Redirects console I/O to the FTP data channel. Use in the form:

  [FTPdataIo ... io]

Command socket

create ConsoleFTP       \ -- addr ; OUT managed by upper driver
Function despatch table for FTP command channel I/O. OUT is managed by the upper level driver.

: Init-ConsoleFTP       \ --
Initialise the command channel. Note that the FTP socket must have been set up and and the private service area initialised.

: FTPio         \ --
Select the FTP command channel as the console.

: checkFTP      \ -- ior
Return non-zero if there is an error in the FTP command channel.

Sampling the command channel input

: -ftpLine      \ --
Reset the FTP line input.

: +ftpLine      \ --
Mark that a complete line is available.

: ftpBS         \ --
The backspace operation for input.

: +ftpCmdChar   \ char --
Add the character to the command line being assembled.

: ?FTPacceptable        \ --
Process the next service input character on the command channel. Input is terminated by LF, and CR is ignored. This satisfies the requirements of DOS, Windows, Unices and the TCP/IP NVT (Network Virtual Terminal).

Diagnostic control

1 value FTPdiags?       \ -- n
Set this non-zero to get diagnostic information.

: [ftp  ( -- )  FTPdiags? if  [io consoleio decimal  ;
A COMPILER macro used to surround debug code, and terminated by FTP].


  [FTP  ." debug message"  FTP]

: ftp]  ( -- )  io]  endif  ;
A COMPILER macro that terminates an [FTP ... FTP] structure.

: .fdLine       \ caddr len --
Display FTP text with leading CR on Forth console. If FTPdiags? is set to zero, no action is taken.

Directory listing for FTP

Each line of the display is "sort of" in Unix ls format.


----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z
d---------   1 owner    group               0 May  9 19:45 Softlib

create months   \ -- addr
String containing 3 character text for the months.

: .ftpDate      \ --
Display the current directory entry's date in the form:

  Mmm dd  yyyy

e.g.

  Apr 30  2012

: .ftpDirLine   \ --
Display a directory entry in FTP format. FTP clients get the size information from this format.

: .ftpFile      \ --
List the file data for the last file found.

: .ftpdir       \ --
Display a list of files in FTP format.

: .ftpDirNlst   \ --
Display a list of file names in FTP NLST format.

Status returns

: .ftpResp      \ caddr len --
Send the string plus a CR/LF pair to the command socket and optionally to the console.

: .ftp150       \ --
Return status 150 - about to transfer data

: .ftp200       \ --
Return status 200 - good command.

: .ftp202       \ --
Return status 202 - not needed

: .ftp211       \ --
Return status 211 - not available

: .ftp230       \ --
Return status 230 - user logged in.

: .ftp250       \ --
Return status 250 - good file command.

: .ftp226       \ --
Return status 226 - transfer successful.

: .ftp331       \ --
Return status 331 - password needed

: .ftp350       \ --
Return status 350 - need more info.

: .ftp425       \ --
Return status 425 - can't make data connection.

: .ftp426       \ --
Return status 426 - aborted.

: .ftp450       \ --
Return FTP error code 450 - action not taken.

: .ftp451       \ --
Return FTP error code 451.

: .ftp502       \ --
Return FTP error code 502 command not implemented.

: .ftp504       \ --
Return FTP error code 504.

: .ftp550       \ --
Return FTP error code 550.

Data socket operations

: [sm           \ -- 0
Starts the definition of a state machine's states.

: smState       \ n -- n+1
Defines the next state as an EQU and increments the state number.

: sm]           \ n --
Finishes the state machine and defines an equate of the number of states.

The FTP data transfer state machine.

[sm
  smState ftpDtIdle             \ No data transfer in progress
  smState ftpListening          \ PASV mode, wait connection
  smState ftpConnected          \ wait transfer command
  smState ftpReadSendData       \ RETRieve
  smState ftpRecvWriteData      \ STORe
  smState ftpErrorState         \ it's bad if we get here!
sm] #FTPsm      \ -- n

Select the the FTP data socket states.

: goFTPidle     \ --
  ftpDtIdle my_ftpDataState !  ;
: goFTPlistening        \ --
  ftpListening my_ftpDataState !  ;
: goFTPconnected        \ --
  ftpConnected my_ftpDataState !  ;
: goFTPReadSend \ --
  ftpReadSendData my_ftpDataState !  ;
: goFTPRecvWrite        \ --
  ftpRecvWriteData my_ftpDataState !  ;

: umin          \ u1 u2 -- u1|u2
Minimum of two unsigned values.

: closeDataFile \ --
Perform an emergency close of the data file if it is open.

: hasConn?      \ hs -- ior true | 0
Return true and an ior if the socket has an error or a completed connection. For a good connection, ior is non-zero.

: newDataSocket \ -- hs|0
Create a new FTP data socket and set it to listen. The socket handle is also stored in the FTP service structure.

: discDataSocket        \ --
If open, flush the socket transmit data and disconnect the FTP data socket. This is a graceful close unless the socket has failed.

: closeDataSocket       \ --
If open, close the FTP data socket. This is not a graceful close.

: termDataStream        \ --
Gracefully shut down a STREAM mode data transfer and return to FTPDTIDLE state.

: closeDataStream       \ --
Hurriedly shut down a STREAM mode data transfer and return to FTPDTIDLE state.

: CheckFTPdata  \ -- ior
Return non-zero if the FTP data socket is in error.

: FTPdataFailed?        \ -- ior
Return non-zero if the FTP data socket is not in established state. If not established, the data socket is closed and we return to FTPDTIDLE state.

: doDTidle      \ --
The action in FTPDTIDLE state. Check the data socket. If it fails, close it.

: doDTlistening \ --
The action in FTPLISTENING state.

: doDTconnected \ --
The action in FTPCONNECTED state.

: doDTReadSend  \ --
The action in FTPREADSENDDATA state.

: doDTRecvWrite \ --
The action in FTPRECVWRITEDATA state.

: doDTerror     \ --
The action in FTPERRORSTATE state.

create FTPdataActions   \ -- addr
A table of xts corresponding to the FTP data channel state.

: doFTPdata     \ --
We have a data socket. Execute the action for the state.

: waitDataConnected     \ -- ior
Wait for a passive mode data connection to be made. Return non-zero on failure.

: waitDataEstablished   \ -- ior
Wait for a passive mode data connection to get to TCP state TCPS_ESTABLISHED. Return non-zero on failure.

Command processing

An FTP command exists on a single line. The first token identifies the command. Each command identifier is a Forth word which parses any more data needed by the command.

: parse-name    \ "text" -- caddr len
Return the next space-delimited string from the input stream.

: parse-path    \ "<pathname>" -- caddr len
Return the next space-delimited path name from the input stream. Clip it to MAX_PATH 1- bytes.

: getSrcParam   \ --
Read the parameter and store it as the source name.

: getDestParam  \ --
Read the parameter and store it as the destination name.

: getSrcDir     \ --
Read the parameter and store it as the source name. If the name ends in a '/' character, remove it. Some FTP clients terminate directory names with a '/', which can confuse the FAT file system.

: SrcParam      \ -- addr len
Return the string saved as the source parameter.

: FTPannounce   \ --
Issues FTP signon message.

: +decByte      \ u caddr len -- u' caddr' len'
Accumulate the next byte of a comma separated set of numbers, e.g. 1,25,33,4. The accumulator u is shifted left by 8 bits and the next number in the text is added. The updated accumulator and remaining text are returned.

: .decByte      \ u -- u'
Display the top byte of u as a decimal number and shift it left by 8 bits.

: .comma        \ --
Display a comma.

: FTPabort426   \ -- 426
Close the data socket and data file and command with a 426 response.

: setDataSock   \ -- ior
Depending on the mode, check or establish a data connection. Return non-zero on error.

: .quo          ( -- ) [char] " emit  ;
Display a double quotes.

: .pwdResp      \ --
Display the PWD response with no CR/LF.

: .257SrcResp   \ --
A 257 response with the source buffer

Login and security

Access to FTP is provided through the FTP verbs (words) USER PASS and optionally ACCT.

defer doFTPuser \ --
Process the FTP USER command. The default accepts any user. Once the user and password have been confirmed, the ftpState variable can be set to 1.

defer doFTPpass \ --
Process the FTP PASS command. The default accepts any password. Once the user and password have been confirmed, the ftpState variable can be set to 1.

defer doFTPacct \ --
Process the FTP ACCT command. The default accepts any password. Once the user and password have been confirmed, the ftpState variable can be set to 1.

: (FTPuser)     \ --
The default action performed for the USER command. All users are accepted.

: (FTPpass)     \ --
The default action performed for the PASS command. All passwords are accepted.

: (FTPacct)     \ --
The default action performed for the ACCT command. All accounts are accepted.

Implemented FTP commands

FTP commands all start with a command name ("verb" in FTP parlance). Each is implemented as a Forth word in the FTPvoc vocabulary.

vocabulary FTPvoc       \ --
Vocabulary holding FTP commands for execution.

' FTPvoc >body @ constant FTPwid        \ -- x
Wordlist holding FTP commands for execution.

: FTPinterpret  \ --
Interpret the current line containing an FTP command.

also FTPvoc definitions
Start of FTP command definitions.

: USER          \ -- ; USER <name>
Handle the USER command.

: PASS          \ -- ; PASS <password>
Handle the PASS command.

: ACCT          \ -- ; ACCT <password>
Handle the ACCT command.

: SYST          \ -- ; SYST
Handle the SYST command.

: STAT          \ -- ; STAT
Process the STAT command. Commands with parameters are rejected.

: HELP          \ -- ; HELP [<param]
The HELP command just describes the system.

: NOOP          \ -- ; NOOP
The NOOP command just returns good.

: STRU          \ -- ; STRU F
Obsolete command - we just accept F.

: MODE          \ -- ; MODE S
Obsolete command - we just accept S.

: TYPE          \ -- ; TYPE <params>
Process the TYPE command. Valid parameters are

 A, A N, I, L 8

: QUIT          \ -- ; QUIT
Process the QUIT command.

: ABOR          \ -- ; ABOR
Process the ABORt command.

: PORT          \ -- ; PORT a1,a2,a3,a4,ph,pl
Process the PORT command. The IP address and port are saved in my_ftpDataIP and my_ftpDataPort.

: PASV          \ -- ; PASV
Process the PASV command. RFC 0959 does not document PASV well. See http://cr.yp.to/ftp/retr.html for more details. A successful response is of the form:

  227 Entering Passive Mode (a1,a2,a3,a4,ph,pl)

: RETR          \ -- ; RETR <filepathname>
Process the RETRieve command. Starts transmission of a server file to the FTP client.

: CWD           \ -- ; CWD dirpath
Process the CWD command.

: XCWD  CWD  ;  \ -- ; XCWD dirpath
Process the XCWD command.

: PWD           \ -- ; CWD dirpath
Process the PWD command.

: XPWD  PWD  ;  \ -- ; XCWD dirpath
Process the XPWD command.

: LIST          \ -- ; LIST <filespec/dirspec>
Process the LIST command. The parameter is optional. If there is no parameter, the current directory is listed. If the parameter is a file, the details of that file are listed. If the parameter is a directory, the files and directories are listed in "sort of" the Unix ls format.

: NLST          \ -- ; LIST <filespec/dirspec>
Process the LIST command. The parameter is optional. If there is no parameter, the current directory is listed. If the parameter is a directory, only the files are listed by returning one name per line.

: RNFR          \ -- ; RNFR <filespec>
Process the RNFR command, checking that the file exists.

: RNTO          \ -- ; RNTO <filespec>
Process RNTO, the second part of the file rename operation.

: DELE          \ -- ; DELE <filespec>
Process the file delete command.

: MKD           \ -- ; MKD <pathspec>
Process the make directory command.

: RMD           \ -- ; RMD <pathspec>
Process the delete directory command.

previous definitions
End of FTP command definitions.

FTP service tasks

: FTPcommands   \ --
Empty the return stack, store 0 in SOURCE-ID, and enter interpretation state. FTPcommands repeatedly inputs an command line and FTPinterprets it. Note that any task that uses FTPcommands must initialise 'TIB, BASE, IPVEC, and OPVEC.

: FTPservice    \ --
The FTP service task launched for each established Telnet connection.

: #FTPconns     \ -- u
Return the number of FTP connections. This is the number FTP service tasks running. Each one needs two sockets.

FTP listening task

: FTPserverPass \ --
One iteration through the FTP server.

: FTPserver     \ -- ; stay here forever
The FTP listening task.

0 value FTPtask \ -- 0|task
Returns 0 or the FTP server task if running.

: startFTPserver        \ --
Start the FTP server task.

: stopFTPserver \ --
Stop the FTP server.

Diagnostics

This code is only compiled if the EQUate DIAGS? is set non-zero.

: +ftp  initfatfs . powernet  ;
Start the file system and PowerNet.

: .FTPchain     \ --
Display data about the FTP sockets.