Chapter 6 - Internals

Creating a FtpServer instance is done using a FtpServerFactory. The is done with such code:

    FtpServerFactory serverFactory = new FtpServerFactory();
    FtpServer server = serverFactory.createServer();
    server.start();
    ...

Let’s see what happens when we create the factory and when the server is created

FtpServerFactory creation

The FtpServerFactory is associated with a FtpServerContext instance, which defines a set of elements:

  • a CommandFactory instance
  • a ConnectionConfig instance
  • a FileSystemFactory instance
  • a FtpletContainer instance
  • a FtpStatistics instance
  • a set of Listener instances
  • a MessageResource instance. It manages the messages (code and sub-ids) associated with the various supported languages.
  • a UserManager instance

The class hierarchy is the following:


 .-------------.
| FtpletContext |
 '-------------'
        ^
        |   .----------------.
        '--| FtpServerContext |
            '----------------'
                     ^
                     |   .-----------------------.
                     '--| DefaultFtpServerContext |
                         '-----------------------'

MessageResourceFactory and MessageResource

The MessageResourceFactory is used to create the MessageResource instance which will be an instance of the DefaultMessageResource class).

The MessageResource instance contains the messages for all the supported languages. Those messages are those sent to the client when a command is executed. For instance, the response for a STOR command may be:

Can't open data connection.

This is defined in the FtpStatus.properties file, among other response messages:

...
425.STOR=Can't open data connection.
...

Those messages are read from various places (the <lang> tag is the name of the language to use, like en, fr, etc):

  • Default messages
    • org/apache/ftpserver/message/FtpStatus.properties
    • org/apache/ftpserver/message/FtpStatus<lang>.properties_
  • Custom messages
    • org/apache/ftpserver/message/FtpStatus.gen
    • org/apache/ftpserver/message/FtpStatus<lang>.gen_

CommandFactory

This factory creates a Map which contains the Command instances associated with the command name. The current version support those commands:

Command RFC reference
ABOR rfc959, 4.1.3
ACCT rfc959, 4.1.1
APPE rfc959, 4.1.3
AUTH rfc2228, 3
CDUP rfc959, 4.1.1
CWD rfc959, 4.1.1
DELE rfc959, 4.1.3
EPRT rfc2428, 2
EPSV rfc2428, 3
FEAT rfc2389, 3
HELP rfc959, 4.1.3
LANG rfc2640, 4.1
LIST rfc959, 4.1.3
MD5 draft-twine-ftpmd5-00.txt , 3.1
MMD5 draft-twine-ftpmd5-00.txt , 3.2
MDTM rfc3659, 3
MFMT draft-somers-ftp-mfxx, 3
MKD rfc959, 4.1.3
MLSD rfc3659, 7
MLST rfc3659, 7
MODE rfc959, 4.1.2
NLST rfc959, 4.1.3
NOOP rfc959, 4.1.3
OPTS rfc2389, 4
PASS rfc959, 4.1.1
PASV rfc959, 4.1.2
PBSZ rfc2228, 3
PORT rfc959, 4.1.2
PROT rfc2228, 3
PWD rfc959, 4.1.3
QUIT rfc959, 4.1.1
REIN rfc959, 4.1.1
REST rfc959, 4.1.3, rfc3659, 5
RETR rfc959, 4.1.3
RMD rfc959, 4.1.3
RNFR rfc959, 4.1.3
RNTO rfc959, 4.1.3
SITE rfc959, 4.1.3
SITE_DESCUSER rfc959, 4.1.3
SITE_HELP rfc959, 4.1.3
SITE_STAT rfc959, 4.1.3
SITE_WHO rfc959, 4.1.3
SITE_ZONE rfc959, 4.1.3
SIZE rfc3659, 4
STAT rfc959, 4.1.3
STOR rfc959, 4.1.3
STOU rfc959, 4.1.3
STRU rfc959, 4.1.2
SYST rfc959, 4.1.3
TYPE rfc959, 4.1.2
USER rfc959, 4.1.1

The class hierarchy is the following:


 .--------------.
| CommandFactory |
 '--------------'
        ^
        |   .----------------------.
        '--| DefaultCommandFactory  |
            '----------------------'

And instance of a CommandFactory can be obtained by calling the CommandFactoryFactory.createCommandFactory:

        CommandFactoryFactory commandFactoryFactory = new CommandFactoryFactory();

		CommandFactory commandFactory = commandFactoryFactory.createCommandFactory();    

The CommandFactory instance will contain all the previously listed commands.

You can add some commands in the default list:

        CommandFactoryFactory commandFactoryFactory = new CommandFactoryFactory();

        commandFactoryFactory.addCommand("PASV", new PASVTest());

		CommandFactory commandFactory = commandFactoryFactory.createCommandFactory();    

The CommandFactory instance will contain all the previously listed commands plus the added PASV command.

You can also create non standard commands by setting the CommandFactoryFactory.useDefaultCommands to false. here is an example:

        CommandFactoryFactory commandFactoryFactory = new CommandFactoryFactory();

        // Add a non standard command
        commandFactoryFactory.addCommand("foo", new NOOP());

        // tell the factory it will contain non-standard commands
        commandFactoryFactory.setUseDefaultCommands(false);

        // Get the commandfactory having only the created non standard command
		CommandFactory commandFactory = commandFactoryFactory.createCommandFactory();


Here, the Commandfactory instance will onkly contain non-standard commands, and none of the default commands.

UserManagerFactory

This factory creates an UserManager instance, which may be file based or Database based.

There are two implementations of this factory:

  • DbUserManagerFactory which takes the information relative to the user from a SQL Database
  • PropertiesUserManagerFactory which takes the information relative to the user from properties file

The User class contain information about the users:

  • ftpserver.user.{username}.homedirectory: Path to the home directory for the user, based on the file system implementation used
  • ftpserver.user.{username}.userpassword: The password for the user. Can be in clear text, MD5 hash or salted SHA hash based on the configuration on the user manager
  • ftpserver.user.{username}.enableflag: true if the user is enabled, false otherwise
  • ftpserver.user.{username}.writepermission: true if the user is allowed to upload files and create directories, false otherwise
  • ftpserver.user.{username}.idletime: The number of seconds the user is allowed to be idle before disconnected. 0 disables the idle timeout
  • ftpserver.user.{username}.maxloginnumber: The maximum number of concurrent logins by the user. 0 disables the check.
  • ftpserver.user.{username}.maxloginperip: The maximum number of concurrent logins from the same IP address by the user. 0 disables the check.
  • ftpserver.user.{username}.uploadrate: The maximum number of bytes per second the user is allowed to upload files. 0 disables the check.
  • ftpserver.user.{username}.downloadrate: The maximum number of bytes per second the user is allowed to download files. 0 disables the check.

Here is an exemple of file containing some users:

ftpserver.user.admin.homedirectory=./test-tmp/ftproot
ftpserver.user.admin.userpassword=admin
ftpserver.user.admin.enableflag=true
ftpserver.user.admin.writepermission=true
ftpserver.user.admin.idletime=0
ftpserver.user.admin.maxloginnumber=0
ftpserver.user.admin.maxloginperip=0
ftpserver.user.admin.uploadrate=0
ftpserver.user.admin.downloadrate=0

In any case, we use a UserManager instance to manage the users once read by one of the two previous factories.

UserManager

The hierarchy is the following:


  .-----------.
 | UserManager |
  '-----------'
        ^
        |   .-------------------.
        '--| AbstractUserManager |
            '-------------------'
                       ^
                       |   .-------------.
                       +--| DbUserManager |
                       |   '-------------'
                       |
                       |   .---------------------.
                       '--| PropertiesUserManager |
                           '---------------------'

Those classes are used to get the User information. The User class stores the following data:

  • authorities: The user’s permissions. One of WritePermission, TransferRatePermission and ConcurrentLoginPermission
  • homeDir: The user’s home directory
  • isEnabled: A flag set to true if teh user is enabled
  • maxIdleTimeSec: The maximum time the user can remain iddle (default to infinite)
  • name: The user’s name
  • password: The hashed user’s password

The User interface is used to expose the available methods. The class hierarchy is:


  .----.
 | User |
  '----'
     ^
     |   .--------.
     '--| BaseUser |
         '--------'

As the users are stored either in a property file or in a SQL Database, we need a way to manage those users. There is a class that can bbe used to add users in those containes: the AddUser class. Otherwise, the UserManager instance can be used to delete or get users from their container. Creating a user programatically can be done either with the UserFactory or by instanciating a BaseUser class.

FileSystemFactory

This factory will be used to create the directory the user will access. The factory will be used later.

FtpletContainer

The FtpletCinainer is holding all the Ftplet elements in a HashMap.

A Ftplet is a piece of code that get executed before or after a command, and before and after a connection.

Here is the class hierarchy:


  .------.
 | Ftplet |
  '------'
      ^
      |   .---------------.
      '--| FtpletContainer |
          '---------------'
                       ^
                       |   .----------------------.
                       '--| DefaultFtpletContainer |
                           '----------------------'

FtpStatistics

This is used to store statistics about the FtpServer usage. The class hierarchy is teh following:


  .-------------.
 | FtpStatistics |
  '-------------'
         ^
         |   .-------------------.
         '--| ServerFtpStatistics |
             '-------------------'
                       ^
                       |   .--------------------.
                       '--| DefaultFtpStatistics |
                           '--------------------'

The FtpStatistics contains all the getters, the ServerFtpStatistics contains all the setters.

The following values are followed:

  • startTime
  • uploadCount
  • downloadCount
  • deleteCount
  • mkdirCount
  • rmdirCount
  • currLogins
  • totalLogins
  • totalFailedLogins
  • currAnonLogins
  • totalAnonLogins
  • currConnections
  • totalConnections
  • bytesUpload
  • bytesDownload

The methods are called by the Command instances.

ConnectionConfigFactory

This class create a ConnectionConfig instance.

It manages the following server parameters:

  • maxLogins
  • anonymousLoginEnabled
  • maxAnonymousLogins
  • maxLoginFailures
  • loginFailureDelay
  • maxThreads

By default, the server configuration will allow 3 fails login attempts, insert a 500ms delay between each attempt, allow 10 concurrent users, allow 10 anonymous users, and do not limit the number of threads dedicated for command processing.

Listeners

The Listeners are used to manage communications with the users. We keep a Map of created listeners. Those listeners are created by a ListenerFactory.

The ListenerFactory class contains a DataConnectionConfiguration instance which is created by a DataConnectionConfigurationFactory factory class.

The class hierarchy is the following:


  .--------.
 | Listener |
  '--------'
      ^
      |   .----------------.
      '--| AbstractListener |
          '----------------'
                  ^
                  |   .-----------.
                  '--| NioListener |
                      '-----------'

The NioListener class is responsible for all the TCP communication with the user. It depends on MINA SocketAcceptor_ class.

The AbstractListener class

The SocketAcceptor instance is declared with the following parameters:

  • ReuseAddress is set to true
  • The read buffer size is set to 2048
  • The receive buffer size is set to 512
  • The idle time is set to 300 seconds
  • The service chain contains, in this order from server to handler:
    • An optional SslFilter
    • A MdcInjectionFilter
    • An optional MinaSessionFilter
    • A ExecutorFilter
    • A ProtocolCodecFilter: The decoder is a simple instance of a TextLineDecoder. The encoder is an instance of a FtpResponseEncoder.
    • A MdcInjectionFilter
    • A FtpLoggingFilter

A Listener instance can be created through a call to the ListenerFactory.createListener() method, it should not be created directly. This method instanciate a NioListener after having chekced that the provided server name is valid.

Internally, it creates a FtpHandler instances, which is used to handle all the incoming FTP requests.

The instanciated listener will be added to the map of listeners, as default.

The server factory has been created, we now have to create the server and to start it.