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.