AbstractFtpsTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.apache.commons.net.ftp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.net.URL;
import java.time.Duration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.net.PrintCommandListener;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.ssl.SslConfiguration;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.apache.ftpserver.usermanager.Md5PasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.junit.Assert;
/**
* Tests {@link FTPSClient}.
* <p>
* To get our test certificate to work on Java 11, this test must be run with:
* </p>
*
* <pre>
* -Djdk.tls.client.protocols="TLSv1.1"
* </pre>
* <p>
* This test does the above programmatically.
* </p>
*/
public abstract class AbstractFtpsTest {
private static int SocketPort;
private static FtpServer EmbeddedFtpServer;
protected static final boolean IMPLICIT = false;
protected static final long TEST_TIMEOUT = 10000; // individual test timeout
private static final boolean TRACE_CALLS = Boolean.parseBoolean(System.getenv("TRACE_CALLS"));
private static final boolean ADD_LISTENER = Boolean.parseBoolean(System.getenv("ADD_LISTENER"));
private static final long startTime = System.nanoTime();
/**
* Returns the test directory as a String.
* @param defaultHome A default value.
* @return the test directory as a String
*/
protected static String getTestHomeDirectory(final String defaultHome) {
return System.getProperty("test.basedir", defaultHome);
}
/**
* Creates and starts an embedded Apache MINA FTP Server.
*
* @param implicit FTPS connection mode.
* @param userPropertiesResource resource path to user properties file.
* @param serverJksResourceResource resource path to server JKS file.
* @param defaultHome default home folder
* @throws FtpException Thrown when the FTP classes cannot fulfill a request.
*/
protected static synchronized void setupServer(final boolean implicit, final String userPropertiesResource, final String serverJksResourceResource,
final String defaultHome) throws FtpException {
if (EmbeddedFtpServer != null) {
return;
}
// Let the OS find use an ephemeral port by using 0.
SocketPort = 0;
final FtpServerFactory serverFactory = new FtpServerFactory();
final PropertiesUserManagerFactory propertiesUserManagerFactory = new PropertiesUserManagerFactory();
// TODO Update to SHA512
propertiesUserManagerFactory.setPasswordEncryptor(new Md5PasswordEncryptor());
final URL userPropsResource = ClassLoader.getSystemClassLoader().getResource(userPropertiesResource);
Assert.assertNotNull(userPropertiesResource, userPropsResource);
propertiesUserManagerFactory.setUrl(userPropsResource);
final UserManager userManager = propertiesUserManagerFactory.createUserManager();
final BaseUser user = (BaseUser) userManager.getUserByName("test");
// Pickup the home dir value at runtime even though we have it set in the userprop file
// The user prop file requires the "homedirectory" to be set
user.setHomeDirectory(getTestHomeDirectory(defaultHome));
serverFactory.setUserManager(userManager);
final ListenerFactory factory = new ListenerFactory();
factory.setPort(SocketPort);
// define SSL configuration
final URL serverJksResource = ClassLoader.getSystemClassLoader().getResource(serverJksResourceResource);
Assert.assertNotNull(serverJksResourceResource, serverJksResource);
// System.out.println("Loading " + serverJksResource);
final SslConfigurationFactory sllConfigFactory = new SslConfigurationFactory();
final File keyStoreFile = FileUtils.toFile(serverJksResource);
Assert.assertTrue(keyStoreFile.toString(), keyStoreFile.exists());
sllConfigFactory.setKeystoreFile(keyStoreFile);
sllConfigFactory.setKeystorePassword("password");
// set the SSL configuration for the listener
final SslConfiguration sslConfiguration = sllConfigFactory.createSslConfiguration();
final NoProtocolSslConfigurationProxy noProtocolSslConfigurationProxy = new NoProtocolSslConfigurationProxy(sslConfiguration);
factory.setSslConfiguration(noProtocolSslConfigurationProxy);
factory.setImplicitSsl(implicit);
// replace the default listener
serverFactory.addListener("default", factory.createListener());
// start the server
EmbeddedFtpServer = serverFactory.createServer();
EmbeddedFtpServer.start();
SocketPort = ((org.apache.ftpserver.impl.DefaultFtpServer) EmbeddedFtpServer).getListener("default").getPort();
// System.out.printf("jdk.tls.disabledAlgorithms = %s%n", System.getProperty("jdk.tls.disabledAlgorithms"));
trace("Server started");
}
protected static void trace(final String msg) {
if (TRACE_CALLS) {
System.err.println(msg + " " + (System.nanoTime() - startTime));
}
}
private final boolean endpointCheckingEnabled;
public AbstractFtpsTest(final boolean endpointCheckingEnabled, final String userPropertiesResource, final String serverJksResource) {
this.endpointCheckingEnabled = endpointCheckingEnabled;
}
protected void assertClientCode(final FTPSClient client) {
final int replyCode = client.getReplyCode();
assertTrue(FTPReply.isPositiveCompletion(replyCode));
}
protected FTPSClient loginClient() throws SocketException, IOException {
trace(">>loginClient");
final FTPSClient client = new FTPSClient(IMPLICIT);
if (ADD_LISTENER) {
client.addProtocolCommandListener(new PrintCommandListener(System.err));
}
//
client.setControlKeepAliveReplyTimeout(null);
assertEquals(0, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
client.setControlKeepAliveReplyTimeout(Duration.ofSeconds(60));
assertEquals(60, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
//
client.setControlKeepAliveTimeout(null);
assertEquals(0, client.getControlKeepAliveTimeoutDuration().getSeconds());
client.setControlKeepAliveTimeout(Duration.ofSeconds(61));
assertEquals(61, client.getControlKeepAliveTimeoutDuration().getSeconds());
//
client.setDataTimeout(null);
assertEquals(0, client.getDataTimeout().getSeconds());
client.setDataTimeout(Duration.ofSeconds(62));
assertEquals(62, client.getDataTimeout().getSeconds());
//
client.setEndpointCheckingEnabled(endpointCheckingEnabled);
client.connect("localhost", SocketPort);
//
assertClientCode(client);
assertEquals(SocketPort, client.getRemotePort());
//
try {
// HACK: Without this sleep, the user command sometimes does not reach the ftpserver
// This only seems to affect GitHub builds, and only Java 11+
Thread.sleep(200); // 100 seems to be not always enough
} catch (final InterruptedException ignore) {
// ignore
}
assertTrue(client.login("test", "test"));
assertClientCode(client);
//
client.setFileType(FTP.BINARY_FILE_TYPE);
assertClientCode(client);
//
client.execPBSZ(0);
assertClientCode(client);
//
client.execPROT("P");
assertClientCode(client);
trace("<<loginClient");
return client;
}
protected void retrieveFile(final String pathname) throws SocketException, IOException {
final FTPSClient client = loginClient();
try {
// Do it twice.
// Just testing that we are not getting an SSL error (the file MUST be present).
assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.INSTANCE));
assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.INSTANCE));
} finally {
client.disconnect();
}
}
}