This is an automated email from the ASF dual-hosted git repository. elecharny pushed a commit to branch 1.1.X in repository https://gitbox.apache.org/repos/asf/mina-ftpserver.git
The following commit(s) were added to refs/heads/1.1.X by this push: new c1a29e25 o Declare 2 constants and use them o Applied Arturo Bernarl patch (PR 17) o Code refactoring o Exclude ftpserevr.properties from the packages c1a29e25 is described below commit c1a29e25ab680127b32a410f4bd8ae169b29384a Author: emmanuel lecharny <elecha...@apache.org> AuthorDate: Mon Apr 4 23:38:44 2022 +0200 o Declare 2 constants and use them o Applied Arturo Bernarl patch (PR 17) o Code refactoring o Exclude ftpserevr.properties from the packages --- .../ftpserver/command/CommandFactoryFactory.java | 4 +- .../org/apache/ftpserver/command/impl/MD5.java | 26 ++- .../ftpserver/impl/IODataConnectionFactory.java | 237 +++++++++++---------- .../org/apache/ftpserver/util/EncryptUtils.java | 4 +- 4 files changed, 143 insertions(+), 128 deletions(-) diff --git a/core/src/main/java/org/apache/ftpserver/command/CommandFactoryFactory.java b/core/src/main/java/org/apache/ftpserver/command/CommandFactoryFactory.java index 0ac2d247..d2149110 100644 --- a/core/src/main/java/org/apache/ftpserver/command/CommandFactoryFactory.java +++ b/core/src/main/java/org/apache/ftpserver/command/CommandFactoryFactory.java @@ -99,9 +99,9 @@ public class CommandFactoryFactory { DEFAULT_COMMAND_MAP.put("HELP", new HELP()); DEFAULT_COMMAND_MAP.put("LANG", new LANG()); DEFAULT_COMMAND_MAP.put("LIST", new LIST()); - DEFAULT_COMMAND_MAP.put("MD5", new MD5()); + DEFAULT_COMMAND_MAP.put(MD5.MD5, new MD5()); DEFAULT_COMMAND_MAP.put("MFMT", new MFMT()); - DEFAULT_COMMAND_MAP.put("MMD5", new MD5()); + DEFAULT_COMMAND_MAP.put(MD5.MMD5, new MD5()); DEFAULT_COMMAND_MAP.put("MDTM", new MDTM()); DEFAULT_COMMAND_MAP.put("MLST", new MLST()); DEFAULT_COMMAND_MAP.put("MKD", new MKD()); diff --git a/core/src/main/java/org/apache/ftpserver/command/impl/MD5.java b/core/src/main/java/org/apache/ftpserver/command/impl/MD5.java index 881951a8..f98c5141 100644 --- a/core/src/main/java/org/apache/ftpserver/command/impl/MD5.java +++ b/core/src/main/java/org/apache/ftpserver/command/impl/MD5.java @@ -48,6 +48,11 @@ import org.slf4j.LoggerFactory; * @author <a href="http://mina.apache.org">Apache MINA Project</a> */ public class MD5 extends AbstractCommand { + /** The MD5 String constant */ + public static final String MD5 = "MD5"; + + /** The MMD5 String constant */ + public static final String MMD5 = "MMD5"; private final Logger LOG = LoggerFactory.getLogger(MD5.class); @@ -61,11 +66,7 @@ public class MD5 extends AbstractCommand { // reset state variables session.resetState(); - boolean isMMD5 = false; - - if ("MMD5".equals(request.getCommand())) { - isMMD5 = true; - } + boolean isMMD5 = MMD5.equals(request.getCommand()); // print file information String argument = request.getArgument(); @@ -83,6 +84,7 @@ public class MD5 extends AbstractCommand { } String[] fileNames = null; + if (isMMD5) { fileNames = argument.split(","); } else { @@ -90,6 +92,7 @@ public class MD5 extends AbstractCommand { } StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fileNames.length; i++) { String fileName = fileNames[i].trim(); @@ -128,6 +131,7 @@ public class MD5 extends AbstractCommand { } InputStream is = null; + try { is = file.createInputStream(0); String md5Hash = md5(is); @@ -135,14 +139,19 @@ public class MD5 extends AbstractCommand { if (i > 0) { sb.append(", "); } + boolean nameHasSpaces = fileName.indexOf(' ') >= 0; + if(nameHasSpaces) { sb.append('"'); } + sb.append(fileName); + if(nameHasSpaces) { sb.append('"'); } + sb.append(' '); sb.append(md5Hash); @@ -157,10 +166,10 @@ public class MD5 extends AbstractCommand { } if (isMMD5) { session.write(LocalizedFtpReply.translate(session, request, context, - 252, "MMD5", sb.toString())); + 252, MMD5, sb.toString())); } else { session.write(LocalizedFtpReply.translate(session, request, context, - 251, "MD5", sb.toString())); + 251, MD5, sb.toString())); } } @@ -173,12 +182,13 @@ public class MD5 extends AbstractCommand { */ private String md5(InputStream is) throws IOException, NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("MD5"); + MessageDigest digest = MessageDigest.getInstance(MD5); DigestInputStream dis = new DigestInputStream(is, digest); byte[] buffer = new byte[1024]; int read = dis.read(buffer); + while (read > -1) { read = dis.read(buffer); } 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 ba86514d..42b97454 100644 --- a/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java +++ b/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java @@ -74,6 +74,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { 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; } @@ -83,37 +84,37 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * Close data socket. This method must be idempotent as we might call it multiple times during disconnect. */ public synchronized void closeDataConnection() { - - // close client socket if any - if (dataSoc != null) { - try { - dataSoc.close(); - } catch (Exception ex) { - LOG.warn("FtpDataConnection.closeDataSocket()", ex); - } - dataSoc = null; - } - - // close server socket if any - if (servSoc != null) { - try { - servSoc.close(); - } catch (Exception ex) { - LOG.warn("FtpDataConnection.closeDataSocket()", ex); + // close client socket if any + if (dataSoc != null) { + try { + dataSoc.close(); + } catch (Exception ex) { + LOG.warn("FtpDataConnection.closeDataSocket()", ex); + } + dataSoc = null; } + + // close server socket if any + if (servSoc != null) { + try { + servSoc.close(); + } catch (Exception ex) { + LOG.warn("FtpDataConnection.closeDataSocket()", ex); + } + + if (session != null) { + DataConnectionConfiguration dcc = session.getListener().getDataConnectionConfiguration(); - if (session != null) { - DataConnectionConfiguration dcc = session.getListener().getDataConnectionConfiguration(); - if (dcc != null) { - dcc.releasePassivePort(port); - } + if (dcc != null) { + dcc.releasePassivePort(port); + } + } + + servSoc = null; } - - servSoc = null; - } - - // reset request time - requestTime = 0L; + + // reset request time + requestTime = 0L; } /** @@ -128,9 +129,9 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { this.address = address.getAddress(); port = address.getPort(); requestTime = System.currentTimeMillis(); - } + } - private SslConfiguration getSslConfiguration() { + private SslConfiguration getSslConfiguration() { DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); SslConfiguration configuration = dataCfg.getSslConfiguration(); @@ -153,6 +154,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { // get the passive port int passivePort = session.getListener().getDataConnectionConfiguration().requestPassivePort(); + if (passivePort == -1) { servSoc = null; throw new DataConnectionException("Cannot find an available passive port."); @@ -200,6 +202,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { return new InetSocketAddress(address, port); } catch (Exception ex) { closeDataConnection(); + throw new DataConnectionException("Failed to initate passive data connection: " + ex.getMessage(), ex); } } @@ -238,6 +241,7 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { // get socket depending on the selection dataSoc = null; DataConnectionConfiguration dataConfig = session.getListener().getDataConnectionConfiguration(); + try { if (!passive) { if (secure) { @@ -286,71 +290,74 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { 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.getEnabledProtocol() != null) { - sslSocket.setEnabledProtocols(new String[] {ssl.getEnabledProtocol()}); + 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.getEnabledProtocol() != null) { + sslSocket.setEnabledProtocols(new String[] {ssl.getEnabledProtocol()}); + } + + dataSoc = sslSocket; + } else { + LOG.debug("Opening passive data connection"); + + dataSoc = servSoc.accept(); } - - 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); - closeDataConnection(); - return null; + + 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; + } } - } - - DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); - - dataSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); - LOG.debug("Passive data connection opened"); + + DataConnectionConfiguration dataCfg = session.getListener().getDataConnectionConfiguration(); + + dataSoc.setSoTimeout(dataCfg.getIdleTime() * 1000); + LOG.debug("Passive data connection opened"); } } 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 @@ -371,9 +378,9 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { return null; } else { try { - return InetAddress.getByName(host); + return InetAddress.getByName(host); } catch (UnknownHostException ex) { - throw new DataConnectionException("Failed to resolve address", ex); + throw new DataConnectionException("Failed to resolve address", ex); } } } @@ -384,14 +391,14 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * @see org.apache.ftpserver.DataConnectionFactory#isSecure() */ public boolean isSecure() { - return secure; + return secure; } /** * Set the security protocol. */ public void setSecure(final boolean secure) { - this.secure = secure; + this.secure = secure; } /* @@ -400,56 +407,52 @@ public class IODataConnectionFactory implements ServerDataConnectionFactory { * @see org.apache.ftpserver.DataConnectionFactory#isZipMode() */ public boolean isZipMode() { - return isZip; + return isZip; } /** * Set zip mode. */ public void setZipMode(final boolean zip) { - isZip = zip; + isZip = zip; } /** * Check the data connection idle status. */ public synchronized boolean isTimeout(final long currTime) { + // data connection not requested - not a timeout + if (requestTime == 0L) { + return false; + } + + // data connection active - not a timeout + if (dataSoc != null) { + return false; + } + + // no idle time limit - not a timeout + int maxIdleTime = session.getListener().getDataConnectionConfiguration().getIdleTime() * 1000; - // data connection not requested - not a timeout - if (requestTime == 0L) { - return false; - } - - // data connection active - not a timeout - if (dataSoc != null) { - return false; - } - - // no idle time limit - not a timeout - int maxIdleTime = session.getListener().getDataConnectionConfiguration().getIdleTime() * 1000; - if (maxIdleTime == 0) { - return false; - } - - // idle time is within limit - not a timeout - if ((currTime - requestTime) < maxIdleTime) { - return false; - } - - return true; + if (maxIdleTime == 0) { + return false; + } + + // idle time is within limit - not a timeout + return ((currTime - requestTime) >= maxIdleTime); } /** * Dispose data connection - close all the sockets. */ public void dispose() { - closeDataConnection(); + closeDataConnection(); } /** * Sets the server's control address. */ public void setServerControlAddress(final InetAddress serverControlAddress) { - this.serverControlAddress = serverControlAddress; + this.serverControlAddress = serverControlAddress; } } diff --git a/core/src/main/java/org/apache/ftpserver/util/EncryptUtils.java b/core/src/main/java/org/apache/ftpserver/util/EncryptUtils.java index 77077262..04be9fe5 100644 --- a/core/src/main/java/org/apache/ftpserver/util/EncryptUtils.java +++ b/core/src/main/java/org/apache/ftpserver/util/EncryptUtils.java @@ -22,6 +22,8 @@ package org.apache.ftpserver.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.apache.ftpserver.command.impl.MD5; + /** * <strong>Internal class, do not use directly.</strong> * @@ -61,7 +63,7 @@ public class EncryptUtils { String result = ""; try { - result = encrypt(source, "MD5"); + result = encrypt(source, MD5.MD5); } catch (NoSuchAlgorithmException ex) { // this should never happen throw new RuntimeException(ex);