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    *      http://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.PropertiesUserManagerFactory;
40  import org.apache.ftpserver.usermanager.impl.BaseUser;
41  import org.junit.Assert;
42  
43  /**
44   * Tests {@link FTPSClient}.
45   * <p>
46   * To get our test certificate to work on Java 11, this test must be run with:
47   * </p>
48   *
49   * <pre>
50   * -Djdk.tls.client.protocols="TLSv1.1"
51   * </pre>
52   * <p>
53   * This test does the above programmatically.
54   * </p>
55   */
56  public abstract class AbstractFtpsTest {
57  
58      private static int SocketPort;
59      private static FtpServer EmbeddedFtpServer;
60      protected static final boolean IMPLICIT = false;
61      protected static final long TEST_TIMEOUT = 10000; // individual test timeout
62      private static final boolean TRACE_CALLS = Boolean.parseBoolean(System.getenv("TRACE_CALLS"));
63      private static final boolean ADD_LISTENER = Boolean.parseBoolean(System.getenv("ADD_LISTENER"));
64      private static final long startTime = System.nanoTime();
65  
66      /**
67       * Returns the test directory as a String.
68       * @param defaultHome A default value.
69       *
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 synchronized static void setupServer(final boolean implicit, final String userPropertiesResource, final String serverJksResourceResource, final String defaultHome)
86              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          final URL userPropsResource = ClassLoader.getSystemClassLoader().getResource(userPropertiesResource);
95          Assert.assertNotNull(userPropertiesResource, userPropsResource);
96          propertiesUserManagerFactory.setUrl(userPropsResource);
97          final UserManager userManager = propertiesUserManagerFactory.createUserManager();
98          final BaseUser user = (BaseUser) userManager.getUserByName("test");
99          // Pickup the home dir value at runtime even though we have it set in the userprop file
100         // The user prop file requires the "homedirectory" to be set
101         user.setHomeDirectory(getTestHomeDirectory(defaultHome));
102         serverFactory.setUserManager(userManager);
103         final ListenerFactory factory = new ListenerFactory();
104         factory.setPort(SocketPort);
105 
106         // define SSL configuration
107         final URL serverJksResource = ClassLoader.getSystemClassLoader().getResource(serverJksResourceResource);
108         Assert.assertNotNull(serverJksResourceResource, serverJksResource);
109         // System.out.println("Loading " + serverJksResource);
110         final SslConfigurationFactory sllConfigFactory = new SslConfigurationFactory();
111         final File keyStoreFile = FileUtils.toFile(serverJksResource);
112         Assert.assertTrue(keyStoreFile.toString(), keyStoreFile.exists());
113         sllConfigFactory.setKeystoreFile(keyStoreFile);
114         sllConfigFactory.setKeystorePassword("password");
115 
116         // set the SSL configuration for the listener
117         final SslConfiguration sslConfiguration = sllConfigFactory.createSslConfiguration();
118         final NoProtocolSslConfigurationProxy noProtocolSslConfigurationProxy = new NoProtocolSslConfigurationProxy(sslConfiguration);
119         factory.setSslConfiguration(noProtocolSslConfigurationProxy);
120         factory.setImplicitSsl(implicit);
121 
122         // replace the default listener
123         serverFactory.addListener("default", factory.createListener());
124 
125         // start the server
126         EmbeddedFtpServer = serverFactory.createServer();
127         EmbeddedFtpServer.start();
128         SocketPort = ((org.apache.ftpserver.impl.DefaultFtpServer) EmbeddedFtpServer).getListener("default").getPort();
129         // System.out.printf("jdk.tls.disabledAlgorithms = %s%n", System.getProperty("jdk.tls.disabledAlgorithms"));
130         trace("Server started");
131     }
132 
133     protected static void trace(final String msg) {
134         if (TRACE_CALLS) {
135             System.err.println(msg + " " + (System.nanoTime() - startTime));
136         }
137     }
138 
139     private final boolean endpointCheckingEnabled;
140 
141     public AbstractFtpsTest(final boolean endpointCheckingEnabled, final String userPropertiesResource, final String serverJksResource) {
142         this.endpointCheckingEnabled = endpointCheckingEnabled;
143     }
144 
145     protected void assertClientCode(final FTPSClient client) {
146         final int replyCode = client.getReplyCode();
147         assertTrue(FTPReply.isPositiveCompletion(replyCode));
148     }
149 
150     protected FTPSClient loginClient() throws SocketException, IOException {
151         trace(">>loginClient");
152         final FTPSClient client = new FTPSClient(IMPLICIT);
153         if (ADD_LISTENER) {
154             client.addProtocolCommandListener(new PrintCommandListener(System.err));
155         }
156         //
157         client.setControlKeepAliveReplyTimeout(null);
158         assertEquals(0, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
159         client.setControlKeepAliveReplyTimeout(Duration.ofSeconds(60));
160         assertEquals(60, client.getControlKeepAliveReplyTimeoutDuration().getSeconds());
161         //
162         client.setControlKeepAliveTimeout(null);
163         assertEquals(0, client.getControlKeepAliveTimeoutDuration().getSeconds());
164         client.setControlKeepAliveTimeout(Duration.ofSeconds(61));
165         assertEquals(61, client.getControlKeepAliveTimeoutDuration().getSeconds());
166         //
167         client.setDataTimeout(null);
168         assertEquals(0, client.getDataTimeout().getSeconds());
169         client.setDataTimeout(Duration.ofSeconds(62));
170         assertEquals(62, client.getDataTimeout().getSeconds());
171         //
172         client.setEndpointCheckingEnabled(endpointCheckingEnabled);
173         client.connect("localhost", SocketPort);
174         //
175         assertClientCode(client);
176         assertEquals(SocketPort, client.getRemotePort());
177         //
178         try {
179             // HACK: Without this sleep, the user command sometimes does not reach the ftpserver
180             // This only seems to affect GitHub builds, and only Java 11+
181             Thread.sleep(200); // 100 seems to be not always enough
182         } catch (final InterruptedException ignore) {
183             // ignore
184         }
185         assertTrue(client.login("test", "test"));
186         assertClientCode(client);
187         //
188         client.setFileType(FTP.BINARY_FILE_TYPE);
189         assertClientCode(client);
190         //
191         client.execPBSZ(0);
192         assertClientCode(client);
193         //
194         client.execPROT("P");
195         assertClientCode(client);
196         trace("<<loginClient");
197         return client;
198     }
199 
200     protected void retrieveFile(final String pathname) throws SocketException, IOException {
201         final FTPSClient client = loginClient();
202         try {
203             // Do it twice.
204             // Just testing that we are not getting an SSL error (the file MUST be present).
205             assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM));
206             assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM));
207         } finally {
208             client.disconnect();
209         }
210     }
211 }