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 java.io.BufferedReader;
21  import java.io.BufferedWriter;
22  import java.io.IOException;
23  import java.io.InputStreamReader;
24  import java.io.OutputStreamWriter;
25  import java.net.Socket;
26  import java.security.KeyManagementException;
27  import java.security.NoSuchAlgorithmException;
28  
29  import javax.net.ssl.KeyManager;
30  import javax.net.ssl.SSLContext;
31  import javax.net.ssl.SSLException;
32  import javax.net.ssl.SSLServerSocketFactory;
33  import javax.net.ssl.SSLSocket;
34  import javax.net.ssl.SSLSocketFactory;
35  import javax.net.ssl.TrustManager;
36  
37  /**
38   * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to 
39   * see wire-level SSL details.
40   * 
41   * @version $Id: FTPSClient.java 658520 2008-05-21 01:14:11Z sebb $
42   * @since 2.0
43   */
44  public class FTPSClient extends FTPClient {
45  
46      /** keystore algorithm name. */
47      public static String KEYSTORE_ALGORITHM;
48      /** truststore algorithm name. */
49      public static String TRUSTSTORE_ALGORITHM;
50      /** provider name. */
51      public static String PROVIDER;
52      /** truststore type. */
53      public static String STORE_TYPE;
54  
55      /** The value that I can set in PROT command */
56      private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; 
57      /** Default PROT Command */
58      private static final String DEFAULT_PROT = "C";
59      /** Default protocol name */
60      private static final String DEFAULT_PROTOCOL = "TLS";
61  
62      /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
63      private boolean isImplicit;
64      /** The use SSL/TLS protocol. */
65      private String protocol = DEFAULT_PROTOCOL;
66      /** The AUTH Command value */
67      private String auth = DEFAULT_PROTOCOL;
68      /** The context object. */
69      private SSLContext context;
70      /** The socket object. */
71      private Socket planeSocket;
72      /** The established socket flag. */
73      private boolean isCreation = true;
74      /** The use client mode flag. */
75      private boolean isClientMode = true;
76      /** The need client auth flag. */
77      private boolean isNeedClientAuth = false;
78      /** The want client auth flag. */
79      private boolean isWantClientAuth = false;
80      /** The cipher suites */
81      private String[] suites = null;
82      /** The protocol versions */
83      private String[] protocols = null;
84      
85      /** The FTPS {@link TrustManager} implementation. */
86      private TrustManager trustManager = new FTPSTrustManager();
87      
88      /** The {@link KeyManager} */
89      private KeyManager keyManager;
90  
91      /**
92       * Constructor for FTPSClient.
93       * @throws NoSuchAlgorithmException A requested cryptographic algorithm 
94       * is not available in the environment.
95       */
96      public FTPSClient() throws NoSuchAlgorithmException {
97          this.protocol = DEFAULT_PROTOCOL;
98          this.isImplicit = false;
99      }
100 
101     /**
102      * Constructor for FTPSClient.
103      * @param isImplicit The secutiry mode(Implicit/Explicit).
104      * @throws NoSuchAlgorithmException A requested cryptographic algorithm 
105      * is not available in the environment.
106      */
107     public FTPSClient(boolean isImplicit) throws NoSuchAlgorithmException {
108         this.protocol = DEFAULT_PROTOCOL;
109         this.isImplicit = isImplicit;
110     }
111 
112     /**
113      * Constructor for FTPSClient.
114      * @param protocol the protocol
115      * @throws NoSuchAlgorithmException A requested cryptographic algorithm 
116      * is not available in the environment.
117      */
118     public FTPSClient(String protocol) throws NoSuchAlgorithmException {
119         this.protocol = protocol;
120         this.isImplicit = false;
121     }
122 
123     /**
124      * Constructor for FTPSClient.
125      * @param protocol the protocol
126      * @param isImplicit The secutiry mode(Implicit/Explicit).
127      * @throws NoSuchAlgorithmException A requested cryptographic algorithm 
128      * is not available in the environment.
129      */
130     public FTPSClient(String protocol, boolean isImplicit) 
131             throws NoSuchAlgorithmException {
132         this.protocol = protocol;
133         this.isImplicit = isImplicit;
134     }
135 
136 
137     /**
138      * Set AUTH command use value.
139      * This processing is done before connected processing.
140      * @param auth AUTH command use value.
141      */
142     public void setAuthValue(String auth) {
143         this.auth = auth;
144     }
145 
146     /**
147      * Return AUTH command use value.
148      * @return AUTH command use value.
149      */
150     public String getAuthValue() {
151         return this.auth;
152     }
153 
154     
155     /**
156      * Because there are so many connect() methods, 
157      * the _connectAction_() method is provided as a means of performing 
158      * some action immediately after establishing a connection, 
159      * rather than reimplementing all of the connect() methods.
160      * @throws IOException If it throw by _connectAction_.
161      * @see org.apache.commons.net.SocketClient#_connectAction_()
162      */
163     @Override
164     protected void _connectAction_() throws IOException {
165         // Implicit mode.
166         if (isImplicit) sslNegotiation();
167         super._connectAction_();
168         // Explicit mode.
169         if (!isImplicit) {
170             execAUTH();
171             sslNegotiation();
172         }
173     }
174 
175     /**
176      * AUTH command.
177      * @throws SSLException If it server reply code not equal "234" and "334".
178      * @throws IOException If an I/O error occurs while either sending 
179      * the command.
180      */
181     private void execAUTH() throws SSLException, IOException {
182         int replyCode = sendCommand(
183                 FTPSCommand._commands[FTPSCommand.AUTH], auth);
184         if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
185             // replyCode = 334
186             // I carry out an ADAT command.
187         } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
188             throw new SSLException(getReplyString());
189         }
190     }
191 
192     /**
193      * Performs a lazy init of the SSL context 
194      * @throws IOException 
195      */
196     private void initSslContext() throws IOException {
197         if(context == null) {
198             try  {
199                 context = SSLContext.getInstance(protocol);
200     
201                 context.init(new KeyManager[] { getKeyManager() } , new TrustManager[] { getTrustManager() } , null);
202             } catch (KeyManagementException e) {
203                 IOException ioe = new IOException("Could not initialize SSL context");
204                 ioe.initCause(e);
205                 throw ioe;
206             } catch (NoSuchAlgorithmException e) {
207                 IOException ioe = new IOException("Could not initialize SSL context");
208                 ioe.initCause(e);
209                 throw ioe;
210             }
211         }
212     }
213     
214     /**
215      * SSL/TLS negotiation. Acquires an SSL socket of a control 
216      * connection and carries out handshake processing.
217      * @throws IOException A handicap breaks out by sever negotiation.
218      */
219     private void sslNegotiation() throws IOException {
220         // Evacuation not ssl socket.
221         planeSocket = _socket_;
222         
223         initSslContext();
224 
225         SSLSocketFactory ssf = context.getSocketFactory();
226         String ip = _socket_.getInetAddress().getHostAddress();
227         int port = _socket_.getPort();
228         SSLSocket socket = 
229             (SSLSocket) ssf.createSocket(_socket_, ip, port, true);
230         socket.setEnableSessionCreation(isCreation);
231         socket.setUseClientMode(isClientMode);
232         // server mode
233         if (!isClientMode) {
234             socket.setNeedClientAuth(isNeedClientAuth);
235             socket.setWantClientAuth(isWantClientAuth);
236         }
237         if (protocols != null) socket.setEnabledProtocols(protocols);
238         if (suites != null) socket.setEnabledCipherSuites(suites);
239 
240         socket.startHandshake();
241 
242         _socket_ = socket;
243         _controlInput_ = new BufferedReader(new InputStreamReader(
244                 socket .getInputStream(), getControlEncoding()));
245         _controlOutput_ = new BufferedWriter(new OutputStreamWriter(
246                 socket.getOutputStream(), getControlEncoding()));
247     }
248     
249     /**
250      * Get the {@link KeyManager} instance.
251      * @return The {@link KeyManager} instance
252      */
253     private KeyManager getKeyManager() {
254         return keyManager;
255     }
256     
257     /**
258     * Set a {@link KeyManager} to use
259     * 
260     * @param keyManager The KeyManager implementation to set.
261     */
262     public void setKeyManager(KeyManager keyManager) {
263         this.keyManager = keyManager;
264     }
265 
266     /**
267      * Controls whether new a SSL session may be established by this socket.
268      * @param isCreation The established socket flag.
269      */
270     public void setEnabledSessionCreation(boolean isCreation) {
271         this.isCreation = isCreation;
272     }
273 
274     /**
275      * Returns true if new SSL sessions may be established by this socket.
276      * When a socket does not have a ssl socket, This return False.
277      * @return true - Indicates that sessions may be created;
278      * this is the default. 
279      * false - indicates that an existing session must be resumed.
280      */
281     public boolean getEnableSessionCreation() {
282         if (_socket_ instanceof SSLSocket) 
283             return ((SSLSocket)_socket_).getEnableSessionCreation();
284         return false;
285     }
286 
287     /**
288      * Configures the socket to require client authentication.
289      * @param isNeedClientAuth The need client auth flag.
290      */
291     public void setNeedClientAuth(boolean isNeedClientAuth) {
292         this.isNeedClientAuth = isNeedClientAuth;
293     }
294 
295     /**
296      * Returns true if the socket will require client authentication.
297      * When a socket does not have a ssl socket, This return False.
298      * @return true - If the server mode socket should request 
299      * that the client authenticate itself.
300      */
301     public boolean getNeedClientAuth() {
302         if (_socket_ instanceof SSLSocket) 
303             return ((SSLSocket)_socket_).getNeedClientAuth();
304         return false;
305     }
306 
307     /**
308      * Configures the socket to request client authentication, 
309      * but only if such a request is appropriate to the cipher 
310      * suite negotiated.
311      * @param isWantClientAuth The want client auth flag.
312      */
313     public void setWantClientAuth(boolean isWantClientAuth) {
314         this.isWantClientAuth = isWantClientAuth;
315     }
316 
317     /**
318      * Returns true if the socket will request client authentication.
319      * When a socket does not have a ssl socket, This return False.
320      * @return true - If the server mode socket should request 
321      * that the client authenticate itself.
322      */
323     public boolean getWantClientAuth() {
324         if (_socket_ instanceof SSLSocket) 
325             return ((SSLSocket)_socket_).getWantClientAuth();
326         return false;
327     }
328 
329     /**
330      * Configures the socket to use client (or server) mode in its first 
331      * handshake.
332      * @param isClientMode The use client mode flag.
333      */
334     public void setUseClientMode(boolean isClientMode) {
335         this.isClientMode = isClientMode;
336     }
337 
338     /**
339      * Returns true if the socket is set to use client mode 
340      * in its first handshake.
341      * When a socket does not have a ssl socket, This return False.
342      * @return true - If the socket should start its first handshake 
343      * in "client" mode.
344      */
345     public boolean getUseClientMode() {
346         if (_socket_ instanceof SSLSocket) 
347             return ((SSLSocket)_socket_).getUseClientMode();
348         return false;
349     }
350 
351     /**
352      * Controls which particular cipher suites are enabled for use on this 
353      * connection. I perform setting before a server negotiation.
354      * @param cipherSuites The cipher suites.
355      */
356     public void setEnabledCipherSuites(String[] cipherSuites) {
357         suites = new String[cipherSuites.length];
358         System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
359     }
360 
361     /**
362      * Returns the names of the cipher suites which could be enabled 
363      * for use on this connection.
364      * When a socket does not have a ssl socket, This return null.
365      * @return An array of cipher suite names.
366      */
367     public String[] getEnabledCipherSuites() {
368         if (_socket_ instanceof SSLSocket) 
369             return ((SSLSocket)_socket_).getEnabledCipherSuites();
370         return null;
371     }
372 
373     /**
374      * Controls which particular protocol versions are enabled for use on this
375      * connection. I perform setting before a server negotiation.
376      * @param protocolVersions The protocol versions.
377      */
378     public void setEnabledProtocols(String[] protocolVersions) {
379         protocols = new String[protocolVersions.length];
380         System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
381     }
382 
383     /**
384      * Returns the names of the protocol versions which are currently 
385      * enabled for use on this connection.
386      * When a socket does not have a ssl socket, This return null.
387      * @return An array of protocols.
388      */
389     public String[] getEnabledProtocols() {
390         if (_socket_ instanceof SSLSocket) 
391             return ((SSLSocket)_socket_).getEnabledProtocols();
392         return null;
393     }
394 
395     /**
396      * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
397      * @param pbsz Protection Buffer Size.
398      * @throws SSLException If it server reply code not equal "200".
399      * @throws IOException If an I/O error occurs while either sending 
400      * the command.
401      */
402     public void execPBSZ(long pbsz) throws SSLException, IOException {
403         if (pbsz < 0 || 4294967295L < pbsz) 
404             throw new IllegalArgumentException();
405         if (FTPReply.COMMAND_OK != sendCommand(
406                 FTPSCommand._commands[FTPSCommand.PBSZ],String.valueOf(pbsz)))
407             throw new SSLException(getReplyString());
408     }
409 
410     /**
411      * PROT command.</br>
412      * C - Clear</br>
413      * S - Safe(SSL protocol only)</br>
414      * E - Confidential(SSL protocol only)</br>
415      * P - Private
416      * @param prot Data Channel Protection Level.
417      * @throws SSLException If it server reply code not equal "200".
418      * @throws IOException If an I/O error occurs while either sending 
419      * the command.
420      */
421     public void execPROT(String prot) throws SSLException, IOException {
422         if (prot == null) prot = DEFAULT_PROT;
423         if (!checkPROTValue(prot)) throw new IllegalArgumentException();
424         if (FTPReply.COMMAND_OK != sendCommand(
425                 FTPSCommand._commands[FTPSCommand.PROT], prot)) 
426             throw new SSLException(getReplyString());
427         if (DEFAULT_PROT.equals(prot)) {
428             setSocketFactory(null);
429             setServerSocketFactory(null);
430         } else {
431             setSocketFactory(new FTPSSocketFactory(context));
432 
433             initSslContext();
434             
435             SSLServerSocketFactory ssf = context.getServerSocketFactory();
436 
437             setServerSocketFactory(ssf);
438         }
439     }
440 
441     /**
442      * I check the value that I can set in PROT Command value.
443      * @param prot Data Channel Protection Level.
444      * @return True - A set point is right / False - A set point is not right
445      */
446     private boolean checkPROTValue(String prot) {
447         for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) {
448             if (PROT_COMMAND_VALUE[p].equals(prot)) return true;
449         }
450         return false;
451     }
452 
453     /**
454      * I carry out an ftp command.
455      * When a CCC command was carried out, I steep socket and SocketFactory 
456      * in a state of not ssl.
457      * @parm command ftp command.
458      * @return server reply.
459      * @throws IOException If an I/O error occurs while either sending 
460      * the command.
461      * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String)
462      */
463     @Override
464     public int sendCommand(String command, String args) throws IOException {
465         int repCode = super.sendCommand(command, args);
466         if (FTPSCommand._commands[FTPSCommand.CCC].equals(command)) {
467             if (FTPReply.COMMAND_OK == repCode) {
468                     // TODO Check this - is this necessary at all?
469                 _socket_ = planeSocket;
470                 setSocketFactory(null);
471             } else {
472                 throw new SSLException(getReplyString());
473             }
474         }
475         return repCode;
476     }
477 
478     /**
479      * Returns a socket of the data connection. 
480      * Wrapped as an {@link SSLSocket}, which carries out handshake processing.
481      * @pram command The text representation of the FTP command to send.
482      * @param arg The arguments to the FTP command. 
483      * If this parameter is set to null, then the command is sent with 
484      * no argument.
485      * @return A Socket corresponding to the established data connection. 
486      * Null is returned if an FTP protocol error is reported at any point 
487      * during the establishment and initialization of the connection.
488      * @throws IOException If there is any problem with the connection.
489      * @see org.apache.commons.net.ftp.FTPClient#_openDataConnection_(java.lang.String, int)
490      */
491     @Override
492     protected Socket _openDataConnection_(int command, String arg)
493             throws IOException {
494         Socket socket = super._openDataConnection_(command, arg);
495         if (socket != null && socket instanceof SSLSocket) {
496             SSLSocket sslSocket = (SSLSocket)socket;
497             sslSocket.setUseClientMode(isClientMode);
498             sslSocket.setEnableSessionCreation(isCreation);
499             // server mode
500             if (!isClientMode) {
501                 sslSocket.setNeedClientAuth(isNeedClientAuth);
502                 sslSocket.setWantClientAuth(isWantClientAuth);
503             }
504             if (suites != null)
505                 sslSocket.setEnabledCipherSuites(suites);
506             if (protocols != null)
507                 sslSocket.setEnabledProtocols(protocols);
508             sslSocket.startHandshake();
509         }
510         return socket;
511     }
512 
513     /**
514      * Get the currently configured {@link TrustManager}.
515      * 
516      * @return A TrustManager instance.
517      */
518     public TrustManager getTrustManager() {
519         return trustManager;
520     }
521 
522     /**
523      * Override the default {@link TrustManager} to use.
524      * 
525      * @param trustManager The TrustManager implementation to set.
526      */
527     public void setTrustManager(TrustManager trustManager) {
528         this.trustManager = trustManager;
529     }
530     
531     
532     
533 }