Home

GEONius.com
30-Apr-2004
E-mail

nft_util - FTP Server Framework

The NFT_UTIL package provides the basis for implementing a File Transfer Protocol (FTP) server. The implementation of the NFT_UTIL package itself and its companion package of command processing functions, nft_proc.c, was based on the following Request for Comments (available at a number of Internet sites):

RFC 765 - File Transfer Protocol (obsolete)
Although superseded by RFC 959, this RFC described the (defunct?) mail-related FTP commands: MAIL, MLFL, MSAM, MSOM, MSRQ, and MRCP.

RFC 959 - File Transfer Protocol (FTP)
The official FTP RFC, well-written, not dry.

RFC 1123 - Requirements for Internet Hosts -- Application and Support
This all-encompassing RFC clarified some remaining issues in the FTP standard and took into account existing practice.

as well as some empirical testing with the SunOS 4.1.3 and HP/UX 9.05 FTP servers.

FTP Sessions

An FTP server listens for and accepts network connection requests from clients who wish to transfer files. A session for a particular client begins when that client first connects to the FTP server and ends when the client is disconnected from the server; an FTP server with multiple clients would have multiple sessions active simultaneously. Associated with each session are two network connections:

Control - is the connection over which commands are sent to the FTP server and replies returned to the client. The control connection stays open for the life of the session.
Data - is a connection over which files and other data are sent and received. A new data connection is established for each data transfer (e.g., the sending of a single file) and closed when the transfer completes.

FTP commands are CR/LF-terminated, ASCII strings consisting of an upper case, 3- or 4-character keyword followed by zero or more, space-separated arguments; for example, the following command requests the retrieval of a file:

    RETR thisFile

Some of the more common FTP commands are:

USER userName (Automatically issued by the FTP client)
PASS password
log a user onto the FTP server's host.

CWD newDirectory (FTP Client Command: cd newDirectory)
CDUP (FTP Client Command: cd ..)
change the current working directory. CDUP moves up to the parent of the current working directory.

PWD (FTP Client Command: pwd)
returns the current working directory.
NLST [directory] (FTP Client Command: ls)
returns a list of the files in the specified directory (which defaults to the current working directory).

TYPE A|I (FTP Client Command: ascii or binary)
specifies the type of data being transferred, "A" for ASCII and "I" (Image) for binary.
PORT hostAndPort (Automatically issued by the FTP client)
specifies a port on the client's host to which the FTP server should connect for data transfers. This command is usually issued anew prior to each file transfer or directory listing.

RETR fileName (FTP Client Command: get fileName)
retrieves the specified file from the FTP server's host.

STOR fileName (FTP Client Command: put fileName)
store the to-be-transferred data on the FTP server's host under the specified file name.

QUIT (FTP Client Command: bye or quit)
terminates the session.

FTP replies consist of a 3-digit, numeric status code followed by descriptive text. The status codes are enumerated in RFC 959 (but see RFC 1123 for some updates). For example, the RETR command typically results in two replies being returned to the client over the control connection:

    "150 Data connection opened: thisFile (98765 bytes)"

    ... the contents of "thisFile" are sent over the data connection ...

    "226 Transfer complete: thisFile (98765 bytes)

RFCs 959 and 1123 specify what status codes should be used in reply to which commands. Although there are a few exceptions, the implementor is free to choose the format and contents of the reply text.

The NFT Package

The NFT package provides a server implementation with a high-level means of conducting an FTP session. The server is responsible for listening for and answering a network connection request from a client:

    TcpEndpoint  client, server ;
    ...
    tcpListen ("ftp", 99, &server) ;
    tcpAnswer (server, -1.0, &client) ;

Once a client connection has been established, an FTP session can be created:

    NftSession  session ;
    ...
    nftCreate (client, NULL, NULL, NULL, &session) ;

The server is now ready to read and process FTP commands from the client:

    char  *command ;
    ...
    nftGetLine (session, &command) ;
    nftEvaluate (session, command) ;

When the client connection is broken or an FTP QUIT command is received, the server should terminate the FTP session:

    nftDestroy (session) ;

Putting all of the above together yields a simple - but working - FTP server:

    #include  <stdio.h>			-- Standard I/O definitions.
    #include  "tcp_util.h"		-- TCP/IP networking utilities.
    #include  "nft_util.h"		-- FTP utilities.

    int  main (int argc, char *argv[])
    {
        char  *command ;
        TcpEndpoint  client, server ;
        NftSession  session ;
					- Listen at port 21.
        tcpListen ((argc > 1) ? argv[1] : "21", 99, &server) ;

        for ( ; ; ) {			-- Answer next client.
            tcpAnswer (server, -1.0, &client) ;
            nftCreate (client, NULL, NULL, NULL, &session) ;
            nftPutLine (session, "220 Service is ready.\n") ;
            for ( ; ; ) {		-- Service connected client.
                if (nftGetLine (session, &command))  break ;
                nftEvaluate (session, command) ;
                if ((nftInfo (session))->logout)  break ;
            }
            nftDestroy (session) ;	-- Lost client.
        }

    }

The server's name is specified as the first argument on the command line (i.e., argv[1]) and defaults to port 21. If a client connection is broken, the server loops back to wait for the next client.

In event-driven applications (e.g., those based on the X Toolkit or the IOX dispatcher), the socket connection underlying an FTP session, returned by nftFd(), can be monitored for input by your event dispatcher. Because input is buffered, the input callback must repeatedly call nftGetLine() or nftRead() while nftIsReadable() is true.

Extending an NFT Server

The NFT_UTIL package provides a means for modifying or extending the functionality of an NFT-based FTP server; it does this by maintaining a table that maps FTP command keywords to the C functions that process those commands. When called to evaluate a command string, nftEvaluate() parses the command line into an argc/argv array of arguments. It then looks up the command keyword (argv[0]) in the table and calls the command processing function bound to that keyword, passing the argc/argv array of arguments to the function.

nftCreate() initializes a session's keyword-function map with default entries for the commands called for in the RFCs. These default entries are defined in the defaultCommands[] and defaultCallbacks[] arrays below. The default command processing functions - except for those for PASV, PORT, and QUIT - are found in the companion package, nft_proc.c. An application can modify the processing of an existing command by registering a new command processing function for the command. The following example replaces the default nftRETR() retrieval function with myRetrieve():

    extern  int  myRetrieve (NftSession session,
                             int argc, const char *argv[],
                             void *userData) ;
    ...
    nftRegister (session, "RETR", myRetrieve) ;

nftRegister() can also be used to add entirely new FTP commands to the keyword-function map. When replacing an existing command's callback, study the RFCs and the default implementation of the command so as to ensure that your implementation covers all the bases.

Command Processing Functions

Application-specific command processing functions registered with nftRegister() and invoked by nftEvaluate() should be declared as follows:

    int  myFunction (NftSession  session,
                     int  argc,
                     const  char  *argv[],
                     void  *userData)
    {
        ... body of function ...
    }

argc is the number of arguments (plus the command keyword) in the command string being evaluated; argv[] is an array of character strings, each one being one of the arguments. For example, an FTP ALLO command,

    "ALLO 123 456"

would be parsed into an argc/argv array as shown here:

    argc = 3    argv[0] = "ALLO"
                argv[1] = "123"		-- File size
                argv[2] = "456"		-- Maximum record/page size

The command processing function is responsible for verifying the number and validity of a command's arguments. The userData argument is an arbitrary (void *) pointer specified when the session was created.

A number of NFT functions are available for use in a command processing function. A pointer to the public information in a session structure can be obtained with a call to nftInfo():

    NftSessionInfo  *info = nftInfo (session) ;

nftPutLine() should be used to format and send a reply message to the client over the session's control connection; the following example returns a response for the PWD command:

    nftPutLine (session, "257 \"%s\"\n", info->currentDirectory) ;

Commands such as RETR, STOR, and LIST must establish a separate network connection for transferring data back and forth. A basic implementation of the RETR command illustrates the use of the nftOpen(), nftWrite(), and nftClose() functions to transfer data:

    #include  <stdio.h>			-- Standard I/O definitions.
    #include  <string.h>		-- C Library string functions.
    #include  "nft_util.h"		-- FTP utilities.

    int  myRetrieve (NftSession  session,
                     int  argc,
                     const  char  *argv[],
                     void  *userData)
    {
        char  buffer[1024], fileName[1024] ;
        FILE  *file ;
        int  length ;
        NftSessionInfo  *info = nftInfo (session) ;

		        -- Append the file name to the current working
			-- directory and open the file for reading.
        strcpy (fileName, info->currentDirectory) ;
        strcat (fileName, argv[1]) ;
        file = fopen (fileName, "rb") ;

			-- Establish a network connection with the client
			-- for the purpose of transferring data.
        nftOpen (session) ;
        nftPutLine (session, "150 Transferring: %s\n", fileName) ;

			-- Send the contents of the file to the client.
        while ((length = fread (buffer, 1, sizeof buffer, file)) > 0)
            nftWrite (session, length, buffer, NULL) ;

			-- End the data transfer.
        nftPutLine (session, "226 Transfer complete.\n") ;
        nftClose (session) ;
        fclose (file) ;

        return (0) ;

    }

The STOR command is implemented in a similar fashion, with the calls to fread(3) and nftWrite() replaced by calls to nftRead() and fwrite(3), respectively. The NFT_UTIL package handles the PORT and PASV commands that precede a data transfer command, so nftOpen() will establish the appropriate type of data connection with the application (or its programmer) being none the wiser.

The myRetrieve() function above does not strictly conform to the FTP standard in that it only supports binary transfers ("TYPE I") of arbitrary files. Text files should be transferred according to the Telnet protocol. In particular, each end-of-line marker (e.g., "\n") in a text file must be transmitted as a Telnet end-of-line sequence: a carriage return followed by a line feed ("\r\n"). The FTP client and the server are responsible for making the appropriate conversions to and from their hosts' end-of-line conventions when sending or receiving ASCII data.

nftRead() and nftWrite() do not perform these conversions for you. However, the CR/LF utilities (see crlf_util.c) simplify the handling of ASCII text. Using these conversion utilities, the myRetrieve() function needs to be modified in only two places in order to support ASCII text transfers:

    #include  "crlf_util.h"			-- CR/LF utilities.

    int  myRetrieve (...)
    {
        ...
			-- Open the file for reading.
        if (info->representation[0] == 'A')	-- ASCII transfer?
            file = fopen (fileName, "r") ;	-- Open ASCII file.
        else
            file = fopen (fileName, "rb") ;	-- Open binary file.
        ...
			-- Send the contents of the file to the client.
        while ((length = fread (buffer, 1, (sizeof buffer)/2, file)) > 0) {
            if (info->representation[0] == 'A') {
                nl2crlf (buffer, length, sizeof buffer) ;
                length = strlen (buffer) ;
            }
            if (nftWrite (session, length, buffer, NULL))  break ;
        }

        ...

    }

Within the send loop, nl2crlf() is called to convert newline characters in the file to the carriage return/line feed sequence. Note that only half of the buffer is used for reading, thus allowing for the worst case scenario of a string consisting entirely of newlines being expanded to twice its size.


Public Procedures

nftClose() - closes a session's data connection.
nftCreate() - creates an FTP session.
nftDestroy() - deletes an FTP session.
nftEvaluate() - evaluates an FTP command.
nftFd() - returns a session's control or data socket number.
nftGetLine() - reads the next line from a session's control connection.
nftIgnoreCmd() - ignores an FTP command.
nftInfo() - returns a pointer to the session's public information.
nftIsReadable() - checks if input is waiting to be read from a session's control or data connection.
nftIsUp() - checks if a session's control or data connection is up.
nftIsWriteable() - checks if data can be written to a session's control or data connection.
nftName() - returns the name of a session's control or data connection.
nftNextCommand() - reads and evaluates the next FTP command.
nftOpen() - opens a session's data connection.
nftPASV() - processes the FTP PASV command.
nftPeer() - returns the name of the session's peer.
nftPORT() - processes the FTP PORT command.
nftPutLine() - writes a line of output to a session's control connection.
nftQUIT() - processes the FTP QUIT command.
nftRead() - reads input from a session's data connection.
nftRegister() - maps an FTP command to a user-supplied function.
nftSyntax() - returns the syntax of an FTP command.
nftWrite() - writes output to a session's data connection.

Source Files

nft_util.c
nft_util.h

Command Processing Functions

nftAccessCmds() - processes FTP access control commands.
nftCWD() - processes the FTP CWD command.
nftFileCmds() - processes the FTP file management commands.
nftHELP() - processes the FTP HELP command.
nftListCmds() - processes the FTP LIST and NLST commands.
nftMODE() - processes the FTP MODE command.
nftRETR() - processes the FTP RETR command.
nftServiceCmds() - processes FTP service commands.
nftSTAT() - processes the FTP STAT command.
nftStoreCmds() - processes the FTP APPE and STOR commands.
nftSTRU() - processes the FTP STRU command.
nftTYPE() - processes the FTP TYPE command.
nftUSER() - processes the FTP USER command.

Source Files

nft_proc.c
nft_proc.h

Alex Measday  /  E-mail