RepositoryFtpServer.java
/*
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.filerepository.service.ftp;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.apache.ftpserver.ConnectionConfig;
import org.apache.ftpserver.DataConnectionConfigurationFactory;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.DefaultFtplet;
import org.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.Ftplet;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.message.MessageResource;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* The Class RepositoryFtpServer.
*/
@Component
@Slf4j
public class RepositoryFtpServer implements InitializingBean, DisposableBean {
/** The user manager. */
@Autowired
private UserManager userManager;
/** The message resource. */
@Autowired(required = false)
private MessageResource messageResource;
/** The ftp port. */
private int ftpPort;
/** The max threads. */
private int maxThreads = 20;
/** The max logins. */
// The maximum number of simultaneous users
private int maxLogins = 10;
/** The idle timeout. */
// Idle timeout
private int idleTimeout = 60;
/** The keystore path. */
private String keystorePath;
/** The keystore psw. */
private String keystorePsw;
/** The passive ports. */
private String passivePorts;
/** The external address. */
private String externalAddress;
/** The server. */
private FtpServer server = null;
/** The repository file system factory. */
@Autowired
private FileSystemFactory repositoryFileSystemFactory;
/**
* Set external address of the FTP server that will be returned to clients on
* the PASV command.
*
* @param externalAddress the new external address
* @see DataConnectionConfigurationFactory#setPassiveExternalAddress(String)
*/
public void setExternalAddress(final String externalAddress) {
this.externalAddress = externalAddress;
}
/**
* Set the passive ports to be used for data connections.
*
* @param passivePorts the new passive ports
* @see DataConnectionConfigurationFactory#setPassivePorts(String)
*/
public void setPassivePorts(final String passivePorts) {
this.passivePorts = passivePorts;
}
/**
* Set the path to Java keystore
*
* <pre>
* keytool -genkey -alias testdomain -keyalg RSA -keystore ftpserver.jks -keysize 4096
* </pre>
*
* @param keystorePath the new keystore path
*/
public void setKeystorePath(final String keystorePath) {
this.keystorePath = keystorePath;
}
/**
* Sets the keystore psw.
*
* @param keystorePsw the new keystore psw
*/
public void setKeystorePsw(final String keystorePsw) {
this.keystorePsw = keystorePsw;
}
/**
* Sets the user manager.
*
* @param userManager the new user manager
*/
public void setUserManager(final UserManager userManager) {
this.userManager = userManager;
}
/**
* Sets the message resource.
*
* @param messageResource the new message resource
*/
public void setMessageResource(final MessageResource messageResource) {
this.messageResource = messageResource;
}
/**
* Sets the ftp port.
*
* @param ftpPort the new ftp port
*/
public void setFtpPort(final int ftpPort) {
this.ftpPort = ftpPort;
}
/**
* Sets the max logins.
*
* @param maxLogins the new max logins
*/
public void setMaxLogins(final int maxLogins) {
this.maxLogins = maxLogins;
}
/**
* Sets the idle timeout.
*
* @param idleTimeout the new idle timeout
*/
public void setIdleTimeout(final int idleTimeout) {
this.idleTimeout = idleTimeout;
}
/**
* Sets the max threads.
*
* @param maxThreads the new max threads
*/
public void setMaxThreads(final int maxThreads) {
this.maxThreads = maxThreads;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws FtpException, UnknownHostException {
if (this.ftpPort < 1) {
log.warn("FTP server not started, port={}", ftpPort);
return;
}
final FtpServerFactory serverFactory = new FtpServerFactory();
serverFactory.setUserManager(userManager);
if (messageResource != null) {
serverFactory.setMessageResource(messageResource);
}
{
final ListenerFactory factory = new ListenerFactory();
// set the port of the listener
factory.setPort(ftpPort);
// set idle timeout
factory.setIdleTimeout(idleTimeout);
// define SSL configuration
final SslConfigurationFactory ssl = new SslConfigurationFactory();
// Create store: keytool -genkey -alias testdomain -keyalg RSA -keystore
// ftpserver.jks -keysize 4096
ssl.setKeystoreFile(new File(keystorePath));
ssl.setKeystorePassword(keystorePsw);
// set the SSL configuration for the listener
factory.setSslConfiguration(ssl.createSslConfiguration());
factory.setImplicitSsl(true);
// define Data Connection configuration
final DataConnectionConfigurationFactory dccf = new DataConnectionConfigurationFactory();
dccf.setPassivePorts(passivePorts);
if (externalAddress != null) {
final InetAddress address = InetAddress.getByName(externalAddress);
dccf.setPassiveExternalAddress(address.getHostAddress());
}
factory.setDataConnectionConfiguration(dccf.createDataConnectionConfiguration());
// replace the default listener
serverFactory.addListener("default", factory.createListener());
}
final FileSystemFactory fileSystem = repositoryFileSystemFactory;
serverFactory.setFileSystem(fileSystem);
final ConnectionConfig ftpConnectionConfig = new ConnectionConfig() {
@Override
public boolean isAnonymousLoginEnabled() {
return false;
}
@Override
public int getMaxThreads() {
return maxThreads;
}
@Override
public int getMaxLogins() {
return maxLogins;
}
// The number of failed login attempts before the connection is closed
@Override
public int getMaxLoginFailures() {
return 3;
}
@Override
public int getMaxAnonymousLogins() {
return 0;
}
// The number of milliseconds that the connection is delayed after a failed
// login attempt.
@Override
public int getLoginFailureDelay() {
return 30;
}
};
serverFactory.setConnectionConfig(ftpConnectionConfig);
final Map<String, Ftplet> ftplets = new HashMap<>();
ftplets.put("default", repositoryFtplet());
serverFactory.setFtplets(ftplets);
this.server = serverFactory.createServer();
log.info("Starting FTP server on port {}", this.ftpPort);
server.start();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
if (this.server != null) {
log.info("Shutting down FTP server on port {}", this.ftpPort);
this.server.stop();
}
}
/**
* Repository ftplet.
*
* @return the ftplet
*/
private Ftplet repositoryFtplet() {
return new DefaultFtplet() {
};
}
}