This is an automated email from the ASF dual-hosted git repository. elecharny pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-ftpserver.git
The following commit(s) were added to refs/heads/master by this push: new 7b84f86 Made it so that we can inject more tha one TLS protocol version 7b84f86 is described below commit 7b84f8648fb23312c6c796fbbc3910f50e35021b Author: emmanuel lecharny <elecha...@apache.org> AuthorDate: Fri Jan 14 15:26:37 2022 +0100 Made it so that we can inject more tha one TLS protocol version --- checkstyle.xml | 4 + .../org/apache/ftpserver/command/impl/AUTH.java | 4 +- .../spring/ListenerBeanDefinitionParser.java | 1 + .../ftpserver/impl/IODataConnectionFactory.java | 418 +++++++++++---------- .../apache/ftpserver/listener/nio/NioListener.java | 4 +- .../org/apache/ftpserver/ssl/SslConfiguration.java | 2 +- .../ftpserver/ssl/SslConfigurationFactory.java | 52 +-- .../ssl/impl/DefaultSslConfiguration.java | 80 ++-- .../org/apache/ftpserver/util/StringUtils.java | 66 +++- .../spring-config/config-property-placeholder.xml | 2 +- .../resources/spring-config/config-spring-1.xml | 17 +- distribution/res/conf/ftpd-full.xml | 5 +- distribution/res/conf/ftpd-typical.xml | 2 +- 13 files changed, 351 insertions(+), 306 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index 864649b..d695476 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -25,6 +25,10 @@ limitations under the License. <property name="localeLanguage" value="en"/> <module name="JavadocPackage"/> + <module name="ArrayTypeStyle"> + <property name="javaStyle" value="true"/> + </module> + <module name="LineLength"> <property name="fileExtensions" value="java"/> <property name="max" value="120"/> diff --git a/core/src/main/java/org/apache/ftpserver/command/impl/AUTH.java b/core/src/main/java/org/apache/ftpserver/command/impl/AUTH.java index b5956cd..3e7fa1a 100644 --- a/core/src/main/java/org/apache/ftpserver/command/impl/AUTH.java +++ b/core/src/main/java/org/apache/ftpserver/command/impl/AUTH.java @@ -139,8 +139,8 @@ public class AUTH extends AbstractCommand { sslFilter.setEnabledCipherSuites(ssl.getEnabledCipherSuites()); } - if (ssl.getEnabledProtocol() != null) { - sslFilter.setEnabledProtocols(new String[] { ssl.getEnabledProtocol() }); + if (ssl.getEnabledProtocols() != null) { + sslFilter.setEnabledProtocols(ssl.getEnabledProtocols()); } session.getFilterChain().addFirst(SSL_SESSION_FILTER_NAME, sslFilter); diff --git a/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java b/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java index 9c668b3..428ddc2 100644 --- a/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java +++ b/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java @@ -215,6 +215,7 @@ public class ListenerBeanDefinitionParser extends } String protocol = SpringUtil.parseString(sslElm, "protocol"); + if (protocol != null) { ssl.setSslProtocol(protocol); } diff --git a/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java b/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java index bf1b792..180800f 100644 --- a/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java +++ b/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java @@ -72,11 +72,11 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { FtpIoSession session; public IODataConnectionFactory(final FtpServerContext serverContext, final FtpIoSession session) { - this.session = session; - this.serverContext = serverContext; - if ((session != null) && (session.getListener() != null) && session.getListener().getDataConnectionConfiguration().isImplicitSsl()) { - secure = true; - } + this.session = session; + this.serverContext = serverContext; + if ((session != null) && (session.getListener() != null) && session.getListener().getDataConnectionConfiguration().isImplicitSsl()) { + secure = true; + } } /** @@ -120,87 +120,88 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * Port command. */ public synchronized void initActiveDataConnection(final InetSocketAddress address) { - - // close old sockets if any - closeDataConnection(); - - // set variables - passive = false; - this.address = address.getAddress(); - port = address.getPort(); - requestTime = System.currentTimeMillis(); - } - - private SslConfiguration getSslConfiguration() { - DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); - - SslConfiguration configuration = dataCfg.getSslConfiguration(); - - // fall back if no configuration has been provided on the data connection config - if (configuration == null) { - configuration = session.getListener().getSslConfiguration(); - } - - return configuration; + // close old sockets if any + closeDataConnection(); + + // set variables + passive = false; + this.address = address.getAddress(); + port = address.getPort(); + requestTime = System.currentTimeMillis(); + } + + private SslConfiguration getSslConfiguration() { + DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); + + SslConfiguration configuration = dataCfg.getSslConfiguration(); + + // fall back if no configuration has been provided on the data connection config + if (configuration == null) { + configuration = session.getListener().getSslConfiguration(); + } + + return configuration; } /** * Initiate a data connection in passive mode (server listening). */ public synchronized InetSocketAddress initPassiveDataConnection() throws DataConnectionException { - LOG.debug("Initiating passive data connection"); - // close old sockets if any - closeDataConnection(); - - // get the passive port - int passivePort = session.getListener().getDataConnectionConfiguration().requestPassivePort(); - if (passivePort == -1) { - servSoc = null; - throw new DataConnectionException("Cannot find an available passive port."); - } - - // open passive server socket and get parameters - try { - DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); - - String passiveAddress = dataCfg.getPassiveAddress(); - - if (passiveAddress == null) { - address = serverControlAddress; - } else { - address = resolveAddress(dataCfg.getPassiveAddress()); - } - - if (secure) { - LOG.debug("Opening SSL passive data connection on address \"{}\" and port {}", address, passivePort); - SslConfiguration ssl = getSslConfiguration(); - if (ssl == null) { - throw new DataConnectionException("Data connection SSL required but not configured."); + LOG.debug("Initiating passive data connection"); + // close old sockets if any + closeDataConnection(); + + // get the passive port + int passivePort = session.getListener().getDataConnectionConfiguration().requestPassivePort(); + if (passivePort == -1) { + servSoc = null; + throw new DataConnectionException("Cannot find an available passive port."); } - - // this method does not actually create the SSL socket, due to a JVM bug - // (https://issues.apache.org/jira/browse/FTPSERVER-241). - // Instead, it creates a regular - // ServerSocket that will be wrapped as a SSL socket in createDataSocket() - servSoc = new ServerSocket(passivePort, 0, address); - LOG.debug("SSL Passive data connection created on address \"{}\" and port {}", address, passivePort); - } else { - LOG.debug("Opening passive data connection on address \"{}\" and port {}", address, passivePort); - servSoc = new ServerSocket(passivePort, 0, address); - LOG.debug("Passive data connection created on address \"{}\" and port {}", address, passivePort); + + // open passive server socket and get parameters + try { + DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); + + String passiveAddress = dataCfg.getPassiveAddress(); + + if (passiveAddress == null) { + address = serverControlAddress; + } else { + address = resolveAddress(dataCfg.getPassiveAddress()); + } + + if (secure) { + LOG.debug("Opening SSL passive data connection on address \"{}\" and port {}", address, passivePort); + SslConfiguration ssl = getSslConfiguration(); + + if (ssl == null) { + throw new DataConnectionException("Data connection SSL required but not configured."); + } + + // this method does not actually create the SSL socket, due to a JVM bug + // (https://issues.apache.org/jira/browse/FTPSERVER-241). + // Instead, it creates a regular + // ServerSocket that will be wrapped as a SSL socket in createDataSocket() + servSoc = new ServerSocket(passivePort, 0, address); + LOG.debug("SSL Passive data connection created on address \"{}\" and port {}", address, passivePort); + } else { + LOG.debug("Opening passive data connection on address \"{}\" and port {}", address, passivePort); + servSoc = new ServerSocket(passivePort, 0, address); + LOG.debug("Passive data connection created on address \"{}\" and port {}", address, passivePort); + } + + port = servSoc.getLocalPort(); + servSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); + + // set different state variables + passive = true; + requestTime = System.currentTimeMillis(); + + return new InetSocketAddress(address, port); + } catch (Exception ex) { + closeDataConnection(); + throw new DataConnectionException("Failed to initate passive data connection: " + ex.getMessage(), ex); } - port = servSoc.getLocalPort(); - servSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); - - // set different state variables - passive = true; - requestTime = System.currentTimeMillis(); - - return new InetSocketAddress(address, port); - } catch (Exception ex) { - closeDataConnection(); - throw new DataConnectionException("Failed to initate passive data connection: " + ex.getMessage(), ex); - } } /* @@ -209,7 +210,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * @see org.apache.ftpserver.FtpDataConnectionFactory2#getInetAddress() */ public InetAddress getInetAddress() { - return address; + return address; } /* @@ -218,7 +219,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * @see org.apache.ftpserver.FtpDataConnectionFactory2#getPort() */ public int getPort() { - return port; + return port; } /* @@ -227,154 +228,155 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * @see org.apache.ftpserver.FtpDataConnectionFactory2#openConnection() */ public DataConnection openConnection() throws Exception { - return new IODataConnection(createDataSocket(), session, this); + return new IODataConnection(createDataSocket(), session, this); } /** * Get the data socket. In case of error returns null. */ private synchronized Socket createDataSocket() throws Exception { - - // get socket depending on the selection - dataSoc = null; - DataConnectionConfiguration dataConfig = session.getListener().getDataConnectionConfiguration(); - try { - if (!passive) { - if (secure) { - LOG.debug("Opening secure active data connection"); - SslConfiguration ssl = getSslConfiguration(); - if (ssl == null) { - throw new FtpException("Data connection SSL not configured"); - } - - // get socket factory - SSLSocketFactory socFactory = ssl.getSocketFactory(); - - // create socket - SSLSocket ssoc = (SSLSocket) socFactory.createSocket(); - ssoc.setUseClientMode(false); - - // initialize socket - if (ssl.getEnabledCipherSuites() != null) { - ssoc.setEnabledCipherSuites(ssl.getEnabledCipherSuites()); - } - - if (ssl.getEnabledProtocol() != null) { - ssoc.setEnabledProtocols(new String[] { ssl.getEnabledProtocol() }); - } - dataSoc = ssoc; - } else { - LOG.debug("Opening active data connection"); - dataSoc = new Socket(); - } - - dataSoc.setReuseAddress(true); - - InetAddress localAddr = resolveAddress(dataConfig.getActiveLocalAddress()); - - // if no local address has been configured, make sure we use the same as the client connects from - if (localAddr == null) { - localAddr = ((InetSocketAddress) session.getLocalAddress()).getAddress(); - } - - SocketAddress localSocketAddress = new InetSocketAddress(localAddr, dataConfig.getActiveLocalPort()); - - LOG.debug("Binding active data connection to {}", localSocketAddress); - dataSoc.bind(localSocketAddress); - - dataSoc.connect(new InetSocketAddress(address, port)); - } else { - - if (secure) { - LOG.debug("Opening secure passive data connection"); - // this is where we wrap the unsecured socket as a SSLSocket. This is - // due to the JVM bug described in FTPSERVER-241. - - // get server socket factory - SslConfiguration ssl = getSslConfiguration(); - - // we've already checked this, but let's do it again - if (ssl == null) { - throw new FtpException("Data connection SSL not configured"); + // get socket depending on the selection + dataSoc = null; + DataConnectionConfiguration dataConfig = session.getListener().getDataConnectionConfiguration(); + try { + if (!passive) { + if (secure) { + LOG.debug("Opening secure active data connection"); + SslConfiguration ssl = getSslConfiguration(); + + if (ssl == null) { + throw new FtpException("Data connection SSL not configured"); + } + + // get socket factory + SSLSocketFactory socFactory = ssl.getSocketFactory(); + + // create socket + SSLSocket ssoc = (SSLSocket) socFactory.createSocket(); + ssoc.setUseClientMode(false); + + // initialize socket + if (ssl.getEnabledCipherSuites() != null) { + ssoc.setEnabledCipherSuites(ssl.getEnabledCipherSuites()); + } + + if (ssl.getEnabledProtocols() != null) { + ssoc.setEnabledProtocols(ssl.getEnabledProtocols()); + } + + dataSoc = ssoc; + } else { + LOG.debug("Opening active data connection"); + dataSoc = new Socket(); + } + + dataSoc.setReuseAddress(true); + + InetAddress localAddr = resolveAddress(dataConfig.getActiveLocalAddress()); + + // if no local address has been configured, make sure we use the same as the client connects from + if (localAddr == null) { + localAddr = ((InetSocketAddress) session.getLocalAddress()).getAddress(); + } + + SocketAddress localSocketAddress = new InetSocketAddress(localAddr, dataConfig.getActiveLocalPort()); + + LOG.debug("Binding active data connection to {}", localSocketAddress); + dataSoc.bind(localSocketAddress); + + dataSoc.connect(new InetSocketAddress(address, port)); + } else { + + if (secure) { + LOG.debug("Opening secure passive data connection"); + // this is where we wrap the unsecured socket as a SSLSocket. This is + // due to the JVM bug described in FTPSERVER-241. + + // get server socket factory + SslConfiguration ssl = getSslConfiguration(); + + // we've already checked this, but let's do it again + if (ssl == null) { + throw new FtpException("Data connection SSL not configured"); + } + + SSLSocketFactory ssocketFactory = ssl.getSocketFactory(); + + Socket serverSocket = servSoc.accept(); + + SSLSocket sslSocket = (SSLSocket) ssocketFactory.createSocket(serverSocket, serverSocket.getInetAddress().getHostAddress(), serverSocket.getPort(), true); + sslSocket.setUseClientMode(false); + + // initialize server socket + if (ssl.getClientAuth() == ClientAuth.NEED) { + sslSocket.setNeedClientAuth(true); + } else if (ssl.getClientAuth() == ClientAuth.WANT) { + sslSocket.setWantClientAuth(true); + } + + if (ssl.getEnabledCipherSuites() != null) { + sslSocket.setEnabledCipherSuites(ssl.getEnabledCipherSuites()); + } + + if (ssl.getEnabledProtocols() != null) { + sslSocket.setEnabledProtocols(ssl.getEnabledProtocols()); + } + + dataSoc = sslSocket; + } else { + LOG.debug("Opening passive data connection"); + + dataSoc = servSoc.accept(); } - - SSLSocketFactory ssocketFactory = ssl.getSocketFactory(); - - Socket serverSocket = servSoc.accept(); - - SSLSocket sslSocket = (SSLSocket) ssocketFactory.createSocket(serverSocket, serverSocket.getInetAddress().getHostAddress(), serverSocket.getPort(), true); - sslSocket.setUseClientMode(false); - - // initialize server socket - if (ssl.getClientAuth() == ClientAuth.NEED) { - sslSocket.setNeedClientAuth(true); - } else if (ssl.getClientAuth() == ClientAuth.WANT) { - sslSocket.setWantClientAuth(true); + + if (dataConfig.isPassiveIpCheck()) { + // Let's make sure we got the connection from the same + // client that we are expecting + InetAddress remoteAddress = ((InetSocketAddress) session.getRemoteAddress()).getAddress(); + InetAddress dataSocketAddress = dataSoc.getInetAddress(); + if (!dataSocketAddress.equals(remoteAddress)) { + LOG.warn("Passive IP Check failed. Closing data connection from " + dataSocketAddress + " as it does not match the expected address " + remoteAddress); + closeDataConnection(); + return null; + } } - - if (ssl.getEnabledCipherSuites() != null) { - sslSocket.setEnabledCipherSuites(ssl.getEnabledCipherSuites()); + + DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); + + dataSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); + LOG.debug("Passive data connection opened"); } - - if (ssl.getEnabledProtocol() != null) { - sslSocket.setEnabledProtocols(new String[] { ssl.getEnabledProtocol() }); - } - - dataSoc = sslSocket; - } else { - LOG.debug("Opening passive data connection"); - - dataSoc = servSoc.accept(); - } - - if (dataConfig.isPassiveIpCheck()) { - // Let's make sure we got the connection from the same - // client that we are expecting - InetAddress remoteAddress = ((InetSocketAddress) session.getRemoteAddress()).getAddress(); - InetAddress dataSocketAddress = dataSoc.getInetAddress(); - if (!dataSocketAddress.equals(remoteAddress)) { - LOG.warn("Passive IP Check failed. Closing data connection from " + dataSocketAddress + " as it does not match the expected address " + remoteAddress); + } catch (Exception ex) { closeDataConnection(); - return null; - } + LOG.warn("FtpDataConnection.getDataSocket()", ex); + throw ex; } - - DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); - - dataSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); - LOG.debug("Passive data connection opened"); + dataSoc.setSoTimeout(dataConfig.getIdleTime() * 1000); + + // Make sure we initiate the SSL handshake, or we'll + // get an error if we turn out not to send any data + // e.g. during the listing of an empty directory + if (dataSoc instanceof SSLSocket) { + ((SSLSocket) dataSoc).startHandshake(); } - } catch (Exception ex) { - closeDataConnection(); - LOG.warn("FtpDataConnection.getDataSocket()", ex); - throw ex; - } - dataSoc.setSoTimeout(dataConfig.getIdleTime() * 1000); - - // Make sure we initiate the SSL handshake, or we'll - // get an error if we turn out not to send any data - // e.g. during the listing of an empty directory - if (dataSoc instanceof SSLSocket) { - ((SSLSocket) dataSoc).startHandshake(); - } - - return dataSoc; + + return dataSoc; } /* * (non-Javadoc) Returns an InetAddress object from a hostname or IP address. */ private InetAddress resolveAddress(String host) throws DataConnectionException { - if (host == null) { - return null; - } else { - try { - return InetAddress.getByName(host); - } catch (UnknownHostException ex) { - throw new DataConnectionException("Failed to resolve address", ex); + if (host == null) { + return null; + } else { + try { + return InetAddress.getByName(host); + } catch (UnknownHostException ex) { + throw new DataConnectionException("Failed to resolve address", ex); + } } } - } /* * (non-Javadoc) diff --git a/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java b/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java index 0b1e5a7..cfa5d45 100644 --- a/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java +++ b/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java @@ -146,8 +146,8 @@ public class NioListener extends AbstractListener { ssl_filter.setWantClientAuth(true); } - if (ssl_conf.getEnabledProtocol() != null) { - ssl_filter.setEnabledProtocols(new String[] { ssl_conf.getEnabledProtocol() }); + if (ssl_conf.getEnabledProtocols() != null) { + ssl_filter.setEnabledProtocols(ssl_conf.getEnabledProtocols()); } if (ssl_conf.getEnabledCipherSuites() != null) { diff --git a/core/src/main/java/org/apache/ftpserver/ssl/SslConfiguration.java b/core/src/main/java/org/apache/ftpserver/ssl/SslConfiguration.java index c4da591..1a338ea 100644 --- a/core/src/main/java/org/apache/ftpserver/ssl/SslConfiguration.java +++ b/core/src/main/java/org/apache/ftpserver/ssl/SslConfiguration.java @@ -72,7 +72,7 @@ public interface SslConfiguration { * * @return The name of the protocol as a String */ - String getEnabledProtocol(); + String[] getEnabledProtocols(); /** * Return the required client authentication setting diff --git a/core/src/main/java/org/apache/ftpserver/ssl/SslConfigurationFactory.java b/core/src/main/java/org/apache/ftpserver/ssl/SslConfigurationFactory.java index b6778d7..6ee613a 100644 --- a/core/src/main/java/org/apache/ftpserver/ssl/SslConfigurationFactory.java +++ b/core/src/main/java/org/apache/ftpserver/ssl/SslConfigurationFactory.java @@ -60,7 +60,7 @@ public class SslConfigurationFactory { private String trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - private String sslProtocol = "TLS"; + private String[] sslProtocols = new String[] {"TLSv1.2"}; private ClientAuth clientAuth = ClientAuth.NONE; @@ -158,20 +158,22 @@ public class SslConfigurationFactory { * * @return The SSL protocol */ - public String getSslProtocol() { - return sslProtocol; + public String[] getSslProtocols() { + return sslProtocols; } /** - * Set the SSL protocol used for this channel. Supported values are "SSL" and "TLS". Defaults to "TLS". + * Set the SSL protocols used for this channel. Defaults to "TLSv1.2". * - * @param sslProtocol - * The SSL protocol + * @param sslProtocols + * The SSL protocols */ - public void setSslProtocol(String sslProtocol) { - if (sslProtocol == null || sslProtocol.length() == 0) - throw new FtpServerConfigurationException("SslProcotol must not be null or zero length"); - this.sslProtocol = sslProtocol; + public void setSslProtocol(String... sslProtocols) { + if (sslProtocols == null || sslProtocols.length == 0) { + throw new FtpServerConfigurationException("SslProcotol must not be null or zero length"); + } + + this.sslProtocols = sslProtocols; } /** @@ -182,13 +184,13 @@ public class SslConfigurationFactory { * The desired authentication level */ public void setClientAuthentication(String clientAuthReqd) { - if ("true".equalsIgnoreCase(clientAuthReqd) || "yes".equalsIgnoreCase(clientAuthReqd) || "need".equalsIgnoreCase(clientAuthReqd)) { - this.clientAuth = ClientAuth.NEED; - } else if ("want".equalsIgnoreCase(clientAuthReqd)) { - this.clientAuth = ClientAuth.WANT; - } else { - this.clientAuth = ClientAuth.NONE; - } + if ("true".equalsIgnoreCase(clientAuthReqd) || "yes".equalsIgnoreCase(clientAuthReqd) || "need".equalsIgnoreCase(clientAuthReqd)) { + this.clientAuth = ClientAuth.NEED; + } else if ("want".equalsIgnoreCase(clientAuthReqd)) { + this.clientAuth = ClientAuth.WANT; + } else { + this.clientAuth = ClientAuth.NONE; + } } /** @@ -324,19 +326,22 @@ public class SslConfigurationFactory { KeyStore keyStore = loadStore(keystoreFile, keystoreType, keystorePass); KeyStore trustStore; + if (trustStoreFile != null) { - LOG.debug("Loading trust store from \"{}\", using the key store type \"{}\"", trustStoreFile.getAbsolutePath(), trustStoreType); - trustStore = loadStore(trustStoreFile, trustStoreType, trustStorePass); + LOG.debug("Loading trust store from \"{}\", using the key store type \"{}\"", trustStoreFile.getAbsolutePath(), trustStoreType); + trustStore = loadStore(trustStoreFile, trustStoreType, trustStorePass); } else { - trustStore = keyStore; + trustStore = keyStore; } String keyPassToUse; + if (keyPass == null) { - keyPassToUse = keystorePass; + keyPassToUse = keystorePass; } else { - keyPassToUse = keyPass; + keyPassToUse = keyPass; } + // initialize key manager factory KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keystoreAlgorithm); keyManagerFactory.init(keyStore, keyPassToUse.toCharArray()); @@ -345,7 +350,8 @@ public class SslConfigurationFactory { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm); trustManagerFactory.init(trustStore); - return new DefaultSslConfiguration(keyManagerFactory, trustManagerFactory, clientAuth, sslProtocol, enabledCipherSuites, keyAlias); + return new DefaultSslConfiguration(keyManagerFactory, trustManagerFactory, clientAuth, sslProtocols, + enabledCipherSuites, keyAlias); } catch (Exception ex) { LOG.error("DefaultSsl.configure()", ex); throw new FtpServerConfigurationException("DefaultSsl.configure()", ex); diff --git a/core/src/main/java/org/apache/ftpserver/ssl/impl/DefaultSslConfiguration.java b/core/src/main/java/org/apache/ftpserver/ssl/impl/DefaultSslConfiguration.java index df960ef..9f17e9d 100644 --- a/core/src/main/java/org/apache/ftpserver/ssl/impl/DefaultSslConfiguration.java +++ b/core/src/main/java/org/apache/ftpserver/ssl/impl/DefaultSslConfiguration.java @@ -48,7 +48,7 @@ public class DefaultSslConfiguration implements SslConfiguration { private final TrustManagerFactory trustManagerFactory; - private String enabledProtocol = "TLS"; + private String[] enabledProtocols = new String[] {"TLSv1.2"}; private final ClientAuth clientAuth;// = ClientAuth.NONE; @@ -65,78 +65,80 @@ public class DefaultSslConfiguration implements SslConfiguration { * * @throws GeneralSecurityException */ - public DefaultSslConfiguration(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustManagerFactory, ClientAuth clientAuthReqd, String sslProtocol, String[] enabledCipherSuites, String keyAlias) throws GeneralSecurityException { - super(); - this.clientAuth = clientAuthReqd; - this.enabledCipherSuites = enabledCipherSuites; - this.keyAlias = keyAlias; - this.keyManagerFactory = keyManagerFactory; - this.enabledProtocol = sslProtocol; - this.trustManagerFactory = trustManagerFactory; - this.sslContext = initContext(); - this.socketFactory = sslContext.getSocketFactory(); + public DefaultSslConfiguration(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustManagerFactory, + ClientAuth clientAuthReqd, String[] sslProtocols, String[] enabledCipherSuites, String keyAlias) throws GeneralSecurityException { + super(); + this.clientAuth = clientAuthReqd; + this.enabledCipherSuites = enabledCipherSuites; + this.keyAlias = keyAlias; + this.keyManagerFactory = keyManagerFactory; + this.enabledProtocols = sslProtocols; + this.trustManagerFactory = trustManagerFactory; + this.sslContext = initContext(); + this.socketFactory = sslContext.getSocketFactory(); } public SSLSocketFactory getSocketFactory() throws GeneralSecurityException { - return socketFactory; + return socketFactory; } /** * @see SslConfiguration#getSSLContext(String) */ - public SSLContext getSSLContext(String protocol) throws GeneralSecurityException { - return sslContext; + public SSLContext getSSLContext(String enabledProtocol) throws GeneralSecurityException { + return sslContext; } /** * @see SslConfiguration#getEnabledProtocol() */ - public String getEnabledProtocol() { - return enabledProtocol; + public String[] getEnabledProtocols() { + return enabledProtocols; } /** * @see SslConfiguration#getClientAuth() */ public ClientAuth getClientAuth() { - return clientAuth; + return clientAuth; } /** * @see SslConfiguration#getSSLContext() */ public SSLContext getSSLContext() throws GeneralSecurityException { - return getSSLContext(enabledProtocol); + return getSSLContext(enabledProtocols[0]); } /** * @see SslConfiguration#getEnabledCipherSuites() */ public String[] getEnabledCipherSuites() { - if (enabledCipherSuites != null) { - return enabledCipherSuites.clone(); - } else { - return null; - } + if (enabledCipherSuites != null) { + return enabledCipherSuites.clone(); + } else { + return null; + } } private SSLContext initContext() throws GeneralSecurityException { - KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); - - // wrap key managers to allow us to control their behavior - // (FTPSERVER-93) - for (int i = 0; i < keyManagers.length; i++) { - if (ClassUtils.extendsClass(keyManagers[i].getClass(), "javax.net.ssl.X509ExtendedKeyManager")) { - keyManagers[i] = new ExtendedAliasKeyManager(keyManagers[i], keyAlias); - } else if (keyManagers[i] instanceof X509KeyManager) { - keyManagers[i] = new AliasKeyManager(keyManagers[i], keyAlias); + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + + // wrap key managers to allow us to control their behavior + // (FTPSERVER-93) + for (int i = 0; i < keyManagers.length; i++) { + if (ClassUtils.extendsClass(keyManagers[i].getClass(), "javax.net.ssl.X509ExtendedKeyManager")) { + keyManagers[i] = new ExtendedAliasKeyManager(keyManagers[i], keyAlias); + } else if (keyManagers[i] instanceof X509KeyManager) { + keyManagers[i] = new AliasKeyManager(keyManagers[i], keyAlias); + } } - } - - // create and initialize the SSLContext - SSLContext ctx = SSLContext.getInstance(enabledProtocol); - ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null); - // Create the socket factory - return ctx; + + // create and initialize the SSLContext + SSLContext ctx = SSLContext.getInstance(enabledProtocols[0]); + ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null); + + // Create the socket factory + return ctx; } } diff --git a/core/src/main/java/org/apache/ftpserver/util/StringUtils.java b/core/src/main/java/org/apache/ftpserver/util/StringUtils.java index 8642079..7bba65d 100644 --- a/core/src/main/java/org/apache/ftpserver/util/StringUtils.java +++ b/core/src/main/java/org/apache/ftpserver/util/StringUtils.java @@ -28,12 +28,20 @@ import java.util.Map; * * @author <a href="http://mina.apache.org">Apache MINA Project</a> */ -public class StringUtils { +public final class StringUtils { + private StringUtils() { + // Nothing to do + } /** * This is a string replacement method. + * + * @param source The original string + * @param oldStr The substring to replace + * @param newStr The replacement string + * @return The modified string */ - public static final String replaceString(String source, String oldStr, + public static String replaceString(String source, String oldStr, String newStr) { StringBuilder sb = new StringBuilder(source.length()); int sind = 0; @@ -48,9 +56,19 @@ public class StringUtils { } /** - * Replace string. + * Replace strings using placeholders. Each occurence of {} in the original + * string will be replaced with the object at the same index. For instance: + * <br> + * <code>replaceString( "This {} a {}", ["is', "string"])</code><br> + * will produce:<br> + * <code>"This is a string"</code><br> + * + * @param source The original string + * @param args The array of object to replace the placeholders in + * the original string + * @return The modified string */ - public static final String replaceString(String source, Object[] args) { + public static String replaceString(String source, Object[] args) { int startIndex = 0; int openIndex = source.indexOf('{', startIndex); if (openIndex == -1) { @@ -87,9 +105,19 @@ public class StringUtils { } /** - * Replace string. + * Replace strings using placeholders. Each occurence of {} in the original + * string will be replaced with the object at the same index. For instance: + * <br> + * <code>replaceString( "This {} a {}", Map("is', "string"))</code><br> + * will produce:<br> + * <code>"This is a string"</code><br> + * + * @param source The original string + * @param args The map of object to replace the placeholders in + * the original string + * @return The modified string */ - public static final String replaceString(String source, + public static String replaceString(String source, Map<String, Object> args) { int startIndex = 0; int openIndex = source.indexOf('{', startIndex); @@ -129,15 +157,15 @@ public class StringUtils { } /** - * This method is used to insert HTML block dynamically. - * - * @param source the HTML code to be processes - * @param bReplaceNl if true '\n' will be replaced by <br> - * @param bReplaceTag if true '<' will be replaced by < and - * '>' will be replaced by > - * @param bReplaceQuote if true '\"' will be replaced by " - */ - public static final String formatHtml(String source, boolean bReplaceNl, + * This method is used to insert HTML block dynamically. + * + * @param source the HTML code to be processes + * @param bReplaceNl if true '\n' will be replaced by <br> + * @param bReplaceTag if true '<' will be replaced by < and + * '>' will be replaced by > + * @param bReplaceQuote if true '\"' will be replaced by " + */ + public static String formatHtml(String source, boolean bReplaceNl, boolean bReplaceTag, boolean bReplaceQuote) { StringBuilder sb = new StringBuilder(); @@ -169,7 +197,7 @@ public class StringUtils { } else { sb.append(c); } - + break; case '\n': @@ -203,7 +231,7 @@ public class StringUtils { /** * Pad string object. */ - public static final String pad(String src, char padChar, boolean rightPad, + public static String pad(String src, char padChar, boolean rightPad, int totalLength) { int srcLength = src.length(); @@ -227,7 +255,7 @@ public class StringUtils { /** * Get hex string from byte array. */ - public static final String toHexString(byte[] res) { + public static String toHexString(byte[] res) { StringBuilder sb = new StringBuilder(res.length << 1); for (int i = 0; i < res.length; i++) { String digit = Integer.toHexString(0xFF & res[i]); @@ -242,7 +270,7 @@ public class StringUtils { /** * Get byte array from hex string. */ - public static final byte[] toByteArray(String hexString) { + public static byte[] toByteArray(String hexString) { int arrLength = hexString.length() >> 1; byte buff[] = new byte[arrLength]; for (int i = 0; i < arrLength; i++) { diff --git a/core/src/test/resources/spring-config/config-property-placeholder.xml b/core/src/test/resources/spring-config/config-property-placeholder.xml index 49ee22d..e32682d 100644 --- a/core/src/test/resources/spring-config/config-property-placeholder.xml +++ b/core/src/test/resources/spring-config/config-property-placeholder.xml @@ -24,7 +24,7 @@ xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd - http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd + http://mina.apache.org/ftpserver/spring/v1 https://mina.apache.org/ftpserver-project/ftpserver-1.0.xsd "> <context:property-placeholder location="src/test/resources/spring-config/placeholder.properties"/> diff --git a/core/src/test/resources/spring-config/config-spring-1.xml b/core/src/test/resources/spring-config/config-spring-1.xml index b836cf6..d5bf7f9 100644 --- a/core/src/test/resources/spring-config/config-spring-1.xml +++ b/core/src/test/resources/spring-config/config-spring-1.xml @@ -21,9 +21,10 @@ xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" - http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd - " + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://mina.apache.org/ftpserver/spring/v1 + https://mina.apache.org/ftpserver-project/ftpserver-1.0.xsd" max-logins="500" anon-enabled="false" max-anon-logins="123" @@ -31,12 +32,11 @@ login-failure-delay="125" > - <listeners> <nio-listener name="listener0" port="2222" local-address="1.2.3.4"> - <ssl> - <keystore file="src/test/resources/ftpserver.jks" password="password"/> - </ssl> + <ssl> + <keystore file="src/test/resources/ftpserver.jks" password="password"/> + </ssl> <data-connection idle-timeout="100" implicit-ssl="true"> <active enabled="true" local-address="1.2.3.4"/> @@ -44,6 +44,7 @@ </data-connection> <blacklist>1.2.3.0/16, 1.2.4.0/16, 1.2.3.4</blacklist> </nio-listener> + <listener name="listener1"> <beans:bean id="listener1" class="org.apache.ftpserver.config.spring.MyCustomListener"> <beans:property name="port" value="2223"/> @@ -75,7 +76,9 @@ <user-manager> <beans:bean class="org.apache.ftpserver.config.spring.MockUserManager"/> </user-manager> + <native-filesystem case-insensitive="true" create-home="true" /> + <commands use-default="false"> <command name="FOO"> <beans:bean class="org.apache.ftpserver.command.impl.HELP" /> diff --git a/distribution/res/conf/ftpd-full.xml b/distribution/res/conf/ftpd-full.xml index 747ee14..8f690c7 100644 --- a/distribution/res/conf/ftpd-full.xml +++ b/distribution/res/conf/ftpd-full.xml @@ -17,7 +17,7 @@ xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd - http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd + http://mina.apache.org/ftpserver/spring/v1 https://mina.apache.org/ftpserver-project/ftpserver-1.0.xsd " id="myServer"> <!-- @@ -33,8 +33,7 @@ <truststore file="mytruststore.jks" password="secret" /> </ssl> <data-connection idle-timeout="60"> - <active enabled="true" local-address="1.2.3.4" local-port="2323" - ip-check="true" /> + <active enabled="true" local-address="1.2.3.4" local-port="2323" ip-check="true" /> <passive ports="123-125" address="1.2.3.4" external-address="1.2.3.4" /> </data-connection> <blacklist>1.2.3.0/16, 1.2.4.0/16, 1.2.3.4</blacklist> diff --git a/distribution/res/conf/ftpd-typical.xml b/distribution/res/conf/ftpd-typical.xml index 68f3569..d78db46 100644 --- a/distribution/res/conf/ftpd-typical.xml +++ b/distribution/res/conf/ftpd-typical.xml @@ -16,7 +16,7 @@ <server xmlns="http://mina.apache.org/ftpserver/spring/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" - http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver-project/ftpserver-1.0.xsd + http://mina.apache.org/ftpserver/spring/v1 https://mina.apache.org/ftpserver-project/ftpserver-1.0.xsd " id="myServer"> <listeners>