001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net.ftp;
019
020import java.io.BufferedReader;
021import java.io.BufferedWriter;
022import java.io.IOException;
023import java.io.InputStreamReader;
024import java.io.OutputStreamWriter;
025import java.net.Socket;
026import java.security.KeyManagementException;
027import java.security.NoSuchAlgorithmException;
028
029import javax.net.ssl.KeyManager;
030import javax.net.ssl.SSLContext;
031import javax.net.ssl.SSLException;
032import javax.net.ssl.SSLServerSocketFactory;
033import javax.net.ssl.SSLSocket;
034import javax.net.ssl.SSLSocketFactory;
035import javax.net.ssl.TrustManager;
036
037/**
038 * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to 
039 * see wire-level SSL details.
040 * 
041 * @version $Id: FTPSClient.java 658520 2008-05-21 01:14:11Z sebb $
042 * @since 2.0
043 */
044public class FTPSClient extends FTPClient {
045
046    /** keystore algorithm name. */
047    public static String KEYSTORE_ALGORITHM;
048    /** truststore algorithm name. */
049    public static String TRUSTSTORE_ALGORITHM;
050    /** provider name. */
051    public static String PROVIDER;
052    /** truststore type. */
053    public static String STORE_TYPE;
054
055    /** The value that I can set in PROT command */
056    private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; 
057    /** Default PROT Command */
058    private static final String DEFAULT_PROT = "C";
059    /** Default protocol name */
060    private static final String DEFAULT_PROTOCOL = "TLS";
061
062    /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
063    private boolean isImplicit;
064    /** The use SSL/TLS protocol. */
065    private String protocol = DEFAULT_PROTOCOL;
066    /** The AUTH Command value */
067    private String auth = DEFAULT_PROTOCOL;
068    /** The context object. */
069    private SSLContext context;
070    /** The socket object. */
071    private Socket planeSocket;
072    /** The established socket flag. */
073    private boolean isCreation = true;
074    /** The use client mode flag. */
075    private boolean isClientMode = true;
076    /** The need client auth flag. */
077    private boolean isNeedClientAuth = false;
078    /** The want client auth flag. */
079    private boolean isWantClientAuth = false;
080    /** The cipher suites */
081    private String[] suites = null;
082    /** The protocol versions */
083    private String[] protocols = null;
084    
085    /** The FTPS {@link TrustManager} implementation. */
086    private TrustManager trustManager = new FTPSTrustManager();
087    
088    /** The {@link KeyManager} */
089    private KeyManager keyManager;
090
091    /**
092     * Constructor for FTPSClient.
093     * @throws NoSuchAlgorithmException A requested cryptographic algorithm 
094     * is not available in the environment.
095     */
096    public FTPSClient() throws NoSuchAlgorithmException {
097        this.protocol = DEFAULT_PROTOCOL;
098        this.isImplicit = false;
099    }
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}