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