View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.ftp;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.net.SocketException;
26  import java.net.URL;
27  import java.time.Duration;
28  
29  import org.apache.commons.io.FileUtils;
30  import org.apache.commons.io.output.NullOutputStream;
31  import org.apache.commons.net.PrintCommandListener;
32  import org.apache.ftpserver.FtpServer;
33  import org.apache.ftpserver.FtpServerFactory;
34  import org.apache.ftpserver.ftplet.FtpException;
35  import org.apache.ftpserver.ftplet.UserManager;
36  import org.apache.ftpserver.listener.ListenerFactory;
37  import org.apache.ftpserver.ssl.SslConfiguration;
38  import org.apache.ftpserver.ssl.SslConfigurationFactory;
39  import org.apache.ftpserver.usermanager.Md5PasswordEncryptor;
40  import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
41  import org.apache.ftpserver.usermanager.impl.BaseUser;
42  import org.junit.Assert;
43  
44  /**
45   * Tests {@link FTPSClient}.
46   * <p>
47   * To get our test certificate to work on Java 11, this test must be run with:
48   * </p>
49   *
50   * <pre>
51   * -Djdk.tls.client.protocols="TLSv1.1"
52   * </pre>
53   * <p>
54   * This test does the above programmatically.
55   * </p>
56   */
57  public abstract class AbstractFtpsTest {
58  
59      private static int SocketPort;
60      private static FtpServer EmbeddedFtpServer;
61      protected static final boolean IMPLICIT = false;
62      protected static final long TEST_TIMEOUT = 10000; // individual test timeout
63      private static final boolean TRACE_CALLS = Boolean.parseBoolean(System.getenv("TRACE_CALLS"));
64      private static final boolean ADD_LISTENER = Boolean.parseBoolean(System.getenv("ADD_LISTENER"));
65      private static final long startTime = System.nanoTime();
66  
67      /**
68       * Returns the test directory as a String.
69       * @param defaultHome A default value.
70       * @return the test directory as a String
71       */
72      protected static String getTestHomeDirectory(final String defaultHome) {
73          return System.getProperty("test.basedir", defaultHome);
74      }
75  
76      /**
77       * Creates and starts an embedded Apache MINA FTP Server.
78       *
79       * @param implicit FTPS connection mode.
80       * @param userPropertiesResource resource path to user properties file.
81       * @param serverJksResourceResource resource path to server JKS file.
82       * @param defaultHome default home folder
83       * @throws FtpException Thrown when the FTP classes cannot fulfill a request.
84       */
85      protected static synchronized void setupServer(final boolean implicit, final String userPropertiesResource, final String serverJksResourceResource,
86              final String defaultHome) throws FtpException {
87          if (EmbeddedFtpServer != null) {
88              return;
89          }
90          // Let the OS find use an ephemeral port by using 0.
91          SocketPort = 0;
92          final FtpServerFactory serverFactory = new FtpServerFactory();
93          final PropertiesUserManagerFactory propertiesUserManagerFactory = new PropertiesUserManagerFactory();
94          // TODO Update to SHA512
95          propertiesUserManagerFactory.setPasswordEncryptor(new Md5PasswordEncryptor());
96          final URL userPropsResource = ClassLoader.getSystemClassLoader().getResource(userPropertiesResource);
97          Assert.assertNotNull(userPropertiesResource, userPropsResource);
98          propertiesUserManagerFactory.setUrl(userPropsResource);
99          final UserManager userManager = propertiesUserManagerFactory.createUserManager();
100         final BaseUser user = (BaseUser) userManager.getUserByName("test");
101         // Pickup the home dir value at runtime even though we have it set in the userprop file
102         // The user prop file requires the "homedirectory" to be set
103         user.setHomeDirectory(getTestHomeDirectory(defaultHome));
104         serverFactory.setUserManager(userManager);
105         final ListenerFactory factory = new ListenerFactory();
106         factory.setPort(SocketPort);
107 
108         // define SSL configuration
109         final URL serverJksResource = ClassLoader.getSystemClassLoader().getResource(serverJksResourceResource);
110         Assert.assertNotNull(serverJksResourceResource, serverJksResource);
111         // System.out.println("Loading " + serverJksResource);
112         final SslConfigurationFactory sllConfigFactory = new SslConfigurationFactory();
113         final File keyStoreFile = FileUtils.toFile(serverJksResource);
114         Assert.assertTrue(keyStoreFile.toString(), keyStoreFile.exists());
115         sllConfigFactory.setKeystoreFile(keyStoreFile);
116         sllConfigFactory.setKeystorePassword("password");
117 
118         // set the SSL configuration for the listener
119         final SslConfiguration sslConfiguration = sllConfigFactory.createSslConfiguration();
120         final NoProtocolSslConfigurationProxy noProtocolSslConfigurationProxy = new NoProtocolSslConfigurationProxy(sslConfiguration);
121         factory.setSslConfiguration(noProtocolSslConfigurationProxy);
122         factory.setImplicitSsl(implicit);
123 
124         // replace the default listener
125         serverFactory.addListener("default", factory.createListener());
126 
127         // start the server
128         EmbeddedFtpServer = serverFactory.createServer();
129         EmbeddedFtpServer.start();
130         SocketPort = ((org.apache.ftpserver.impl.DefaultFtpServer) EmbeddedFtpServer).getListener("default").getPort();
131         // System.out.printf("jdk.tls.disabledAlgorithms = %s%n", System.getProperty("jdk.tls.disabledAlgorithms"));
132         trace("Server started");
133     }
134 
135     protected static void trace(final String msg) {
136         if (TRACE_CALLS) {
137             System.err.println(msg + " " + (System.nanoTime() - startTime));
138         }
139     }
140 
141     private final boolean endpointCheckingEnabled;
142 
143     public AbstractFtpsTest(final boolean endpointCheckingEnabled, final String userPropertiesResource, final String serverJksResource) {
144         this.endpointCheckingEnabled = endpointCheckingEnabled;
145     }
146 
147     protected void assertClientCode(final FTPSClient client) {
148         final int replyCode = client.getReplyCode();
149         assertTrue(FTPReply.isPositiveCompletion(replyCode));
150     }
151 
152     protected FTPSClient loginClient() throws SocketException, IOException {
153         trace(">>loginClient");
154         final FTPSClient client = new FTPSClient(IMPLICIT);
155         if (ADD_LISTENER) {
156             client.addProtocolCommandListener(new PrintCommandListener(System.err));
157         }
158         //
159         client.setControlKeepAliveReplyTimeout(null);
160         assertEquals(0, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
161         client.setControlKeepAliveReplyTimeout(Duration.ofSeconds(60));
162         assertEquals(60, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
163         //
164         client.setControlKeepAliveTimeout(null);
165         assertEquals(0, client.getControlKeepAliveTimeoutDuration().getSeconds());
166         client.setControlKeepAliveTimeout(Duration.ofSeconds(61));
167         assertEquals(61, client.getControlKeepAliveTimeoutDuration().getSeconds());
168         //
169         client.setDataTimeout(null);
170         assertEquals(0, client.getDataTimeout().getSeconds());
171         client.setDataTimeout(Duration.ofSeconds(62));
172         assertEquals(62, client.getDataTimeout().getSeconds());
173         //
174         client.setEndpointCheckingEnabled(endpointCheckingEnabled);
175         client.connect("localhost", SocketPort);
176         //
177         assertClientCode(client);
178         assertEquals(SocketPort, client.getRemotePort());
179         //
180         try {
181             // HACK: Without this sleep, the user command sometimes does not reach the ftpserver
182             // This only seems to affect GitHub builds, and only Java 11+
183             Thread.sleep(200); // 100 seems to be not always enough
184         } catch (final InterruptedException ignore) {
185             // ignore
186         }
187         assertTrue(client.login("test", "test"));
188         assertClientCode(client);
189         //
190         client.setFileType(FTP.BINARY_FILE_TYPE);
191         assertClientCode(client);
192         //
193         client.execPBSZ(0);
194         assertClientCode(client);
195         //
196         client.execPROT("P");
197         assertClientCode(client);
198         trace("<<loginClient");
199         return client;
200     }
201 
202     protected void retrieveFile(final String pathname) throws SocketException, IOException {
203         final FTPSClient client = loginClient();
204         try {
205             // Do it twice.
206             // Just testing that we are not getting an SSL error (the file MUST be present).
207             assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.INSTANCE));
208             assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.INSTANCE));
209         } finally {
210             client.disconnect();
211         }
212     }
213 }