    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
    018    package org.apache.commons.net.ftp;
    019
    020    import java.io.BufferedReader;
    021    import java.io.BufferedWriter;
    022    import java.io.IOException;
    023    import java.io.InputStreamReader;
    024    import java.io.OutputStreamWriter;
    025    import java.net.Socket;
    026    import java.security.KeyManagementException;
    027    import java.security.NoSuchAlgorithmException;
    028
    029    import javax.net.ssl.KeyManager;
    030    import javax.net.ssl.SSLContext;
    031    import javax.net.ssl.SSLException;
    032    import javax.net.ssl.SSLSocket;
    033    import javax.net.ssl.SSLSocketFactory;
    034    import javax.net.ssl.TrustManager;
    035
    036    /**
    037     * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to
    038     * see wire-level SSL details.
    039     *
    040     * @version $Id: FTPSClient.java 999437 2010-09-21 14:37:22Z sebb $
    041     * @since 2.0
    042     */
    043    public class FTPSClient extends FTPClient {
    044
    045        /** @deprecated - not used - will be removed in next major release */
    046        @Deprecated
    047        public static String KEYSTORE_ALGORITHM;
    048
    049        /** @deprecated - not used - will be removed in next major release */
    050        @Deprecated
    051        public static String TRUSTSTORE_ALGORITHM;
    052
    053        /** @deprecated - not used - will be removed in next major release */
    054        @Deprecated
    055        public static String PROVIDER;
    056
    057        /** @deprecated - not used - will be removed in next major release */
    058        @Deprecated
    059        public static String STORE_TYPE;
    060
    061        /** The value that I can set in PROT command  (C = Clear, P = Protected) */
    062        private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"};
    063        /** Default PROT Command */
    064        private static final String DEFAULT_PROT = "C";
    065        /** Default secure socket protocol name, i.e. TLS */
    066        private static final String DEFAULT_PROTOCOL = "TLS";
    067
    068        /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
    069        private final boolean isImplicit;
    070        /** The secure socket protocol to be used, e.g. SSL/TLS. */
    071        private final String protocol;
    072        /** The AUTH Command value */
    073        private String auth = DEFAULT_PROTOCOL;
    074        /** The context object. */
    075        private SSLContext context;
    076        /** The socket object. */
    077        private Socket plainSocket;
    078        /** The established socket flag. */
    079        private boolean isCreation = true;
    080        /** The use client mode flag. */
    081        private boolean isClientMode = true;
    082        /** The need client auth flag. */
    083        private boolean isNeedClientAuth = false;
    084        /** The want client auth flag. */
    085        private boolean isWantClientAuth = false;
    086        /** The cipher suites */
    087        private String[] suites = null;
    088        /** The protocol versions */
    089        private String[] protocols = null;
    090
    091        /** The FTPS {@link TrustManager} implementation. */
    092        private TrustManager trustManager = new FTPSTrustManager();
    093
    094        /** The {@link KeyManager} */
    095        private KeyManager keyManager;
    096
    097        /**
    098         * Constructor for FTPSClient.
    099         * Sets security mode to explicit (isImplicit = false)
    100         * @throws NoSuchAlgorithmException A requested cryptographic algorithm
    101         * is not available in the environment.
    102         */
    103        public FTPSClient() throws NoSuchAlgorithmException {
    104            this.protocol = DEFAULT_PROTOCOL;
    105            this.isImplicit = false;
    106        }
    107
    108        /**
    109         * Constructor for FTPSClient.
    110         * @param isImplicit The security mode (Implicit/Explicit).
    111         * @throws NoSuchAlgorithmException A requested cryptographic algorithm
    112         * is not available in the environment.
    113         */
    114        public FTPSClient(boolean isImplicit) throws NoSuchAlgorithmException {
    115            this.protocol = DEFAULT_PROTOCOL;
    116            this.isImplicit = isImplicit;
    117        }
    118
    119        /**
    120         * Constructor for FTPSClient.
    121         * @param protocol the protocol
    122         * @throws NoSuchAlgorithmException A requested cryptographic algorithm
    123         * is not available in the environment.
    124         */
    125        public FTPSClient(String protocol) throws NoSuchAlgorithmException {
    126            this.protocol = protocol;
    127            this.isImplicit = false;
    128        }
    129
    130        /**
    131         * Constructor for FTPSClient.
    132         * @param protocol the protocol
    133         * @param isImplicit The security mode(Implicit/Explicit).
    134         * @throws NoSuchAlgorithmException A requested cryptographic algorithm
    135         * is not available in the environment.
    136         */
    137        public FTPSClient(String protocol, boolean isImplicit)
    138                throws NoSuchAlgorithmException {
    139            this.protocol = protocol;
    140            this.isImplicit = isImplicit;
    141        }
    142
    143        /**
    144         * Constructor for FTPSClient.
    145         * @param isImplicit The security mode(Implicit/Explicit).
    146         * @param context A pre-configured SSL Context
    147         */
    148        public FTPSClient(boolean isImplicit, SSLContext context) {
    149            this.isImplicit = isImplicit;
    150            this.context = context;
    151            this.protocol = DEFAULT_PROTOCOL;
    152        }
    153
    154        /**
    155         * Constructor for FTPSClient.
    156         * @param context A pre-configured SSL Context
    157         */
    158        public FTPSClient(SSLContext context) {
    159            this(false, context);
    160        }
    161
    162
    163        /**
    164         * Set AUTH command use value.
    165         * This processing is done before connected processing.
    166         * @param auth AUTH command use value.
    167         */
    168        public void setAuthValue(String auth) {
    169            this.auth = auth;
    170        }
    171
    172        /**
    173         * Return AUTH command use value.
    174         * @return AUTH command use value.
    175         */
    176        public String getAuthValue() {
    177            return this.auth;
    178        }
    179
    180
    181        /**
    182         * Because there are so many connect() methods,
    183         * the _connectAction_() method is provided as a means of performing
    184         * some action immediately after establishing a connection,
    185         * rather than reimplementing all of the connect() methods.
    186         * @throws IOException If it throw by _connectAction_.
    187         * @see org.apache.commons.net.SocketClient#_connectAction_()
    188         */
    189        @Override
    190        protected void _connectAction_() throws IOException {
    191            // Implicit mode.
    192            if (isImplicit) sslNegotiation();
    193            super._connectAction_();
    194            // Explicit mode.
    195            if (!isImplicit) {
    196                execAUTH();
    197                sslNegotiation();
    198            }
    199        }
    200
    201        /**
    202         * AUTH command.
    203         * @throws SSLException If it server reply code not equal "234" and "334".
    204         * @throws IOException If an I/O error occurs while either sending
    205         * the command.
    206         */
    207        private void execAUTH() throws SSLException, IOException {
    208            int replyCode = sendCommand(
    209                    FTPSCommand._commands[FTPSCommand.AUTH], auth);
    210            if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
    211                // replyCode = 334
    212                // I carry out an ADAT command.
    213            } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
    214                throw new SSLException(getReplyString());
    215            }
    216        }
    217
    218        /**
    219         * Performs a lazy init of the SSL context
    220         * @throws IOException
    221         */
    222        private void initSslContext() throws IOException {
    223            if(context == null) {
    224                try  {
    225                    context = SSLContext.getInstance(protocol);
    226                    context.init(new KeyManager[] { getKeyManager() } , new TrustManager[] { getTrustManager() } , null);
    227                } catch (KeyManagementException e) {
    228                    IOException ioe = new IOException("Could not initialize SSL context");
    229                    ioe.initCause(e);
    230                    throw ioe;
    231                } catch (NoSuchAlgorithmException e) {
    232                    IOException ioe = new IOException("Could not initialize SSL context");
    233                    ioe.initCause(e);
    234                    throw ioe;
    235                }
    236            }
    237        }
    238
    239        /**
    240         * SSL/TLS negotiation. Acquires an SSL socket of a control
    241         * connection and carries out handshake processing.
    242         * @throws IOException If server negotiation fails
    243         */
    244        private void sslNegotiation() throws IOException {
    245            plainSocket = _socket_;
    246            initSslContext();
    247
    248            SSLSocketFactory ssf = context.getSocketFactory();
    249            String ip = _socket_.getInetAddress().getHostAddress();
    250            int port = _socket_.getPort();
    251            SSLSocket socket =
    252                (SSLSocket) ssf.createSocket(_socket_, ip, port, true);
    253            socket.setEnableSessionCreation(isCreation);
    254            socket.setUseClientMode(isClientMode);
    255            // server mode
    256            if (!isClientMode) {
    257                socket.setNeedClientAuth(isNeedClientAuth);
    258                socket.setWantClientAuth(isWantClientAuth);
    259            }
    260
    261            if (protocols != null) socket.setEnabledProtocols(protocols);
    262            if (suites != null) socket.setEnabledCipherSuites(suites);
    263            socket.startHandshake();
    264
    265            _socket_ = socket;
    266            _controlInput_ = new BufferedReader(new InputStreamReader(
    267                    socket .getInputStream(), getControlEncoding()));
    268            _controlOutput_ = new BufferedWriter(new OutputStreamWriter(
    269                    socket.getOutputStream(), getControlEncoding()));
    270        }
    271
    272        /**
    273         * Get the {@link KeyManager} instance.
    274         * @return The {@link KeyManager} instance
    275         */
    276        private KeyManager getKeyManager() {
    277            return keyManager;
    278        }
    279
    280        /**
    281        * Set a {@link KeyManager} to use
    282        *
    283        * @param keyManager The KeyManager implementation to set.
    284        */
    285        public void setKeyManager(KeyManager keyManager) {
    286            this.keyManager = keyManager;
    287        }
    288
    289        /**
    290         * Controls whether new a SSL session may be established by this socket.
    291         * @param isCreation The established socket flag.
    292         */
    293        public void setEnabledSessionCreation(boolean isCreation) {
    294            this.isCreation = isCreation;
    295        }
    296
    297        /**
    298         * Returns true if new SSL sessions may be established by this socket.
    299         * When the underlying {@link Socket} instance is not SSL-enabled (i.e. an
    300         * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled,
    301         * this returns False.
    302         * @return true - Indicates that sessions may be created;
    303         * this is the default.
    304         * false - indicates that an existing session must be resumed.
    305         */
    306        public boolean getEnableSessionCreation() {
    307            if (_socket_ instanceof SSLSocket)
    308                return ((SSLSocket)_socket_).getEnableSessionCreation();
    309            return false;
    310        }
    311
    312        /**
    313         * Configures the socket to require client authentication.
    314         * @param isNeedClientAuth The need client auth flag.
    315         */
    316        public void setNeedClientAuth(boolean isNeedClientAuth) {
    317            this.isNeedClientAuth = isNeedClientAuth;
    318        }
    319
    320        /**
    321         * Returns true if the socket will require client authentication.
    322         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    323         * @return true - If the server mode socket should request
    324         * that the client authenticate itself.
    325         */
    326        public boolean getNeedClientAuth() {
    327            if (_socket_ instanceof SSLSocket)
    328                return ((SSLSocket)_socket_).getNeedClientAuth();
    329            return false;
    330        }
    331
    332        /**
    333         * Configures the socket to request client authentication,
    334         * but only if such a request is appropriate to the cipher
    335         * suite negotiated.
    336         * @param isWantClientAuth The want client auth flag.
    337         */
    338        public void setWantClientAuth(boolean isWantClientAuth) {
    339            this.isWantClientAuth = isWantClientAuth;
    340        }
    341
    342        /**
    343         * Returns true if the socket will request client authentication.
    344         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    345         * @return true - If the server mode socket should request
    346         * that the client authenticate itself.
    347         */
    348        public boolean getWantClientAuth() {
    349            if (_socket_ instanceof SSLSocket)
    350                return ((SSLSocket)_socket_).getWantClientAuth();
    351            return false;
    352        }
    353
    354        /**
    355         * Configures the socket to use client (or server) mode in its first
    356         * handshake.
    357         * @param isClientMode The use client mode flag.
    358         */
    359        public void setUseClientMode(boolean isClientMode) {
    360            this.isClientMode = isClientMode;
    361        }
    362
    363        /**
    364         * Returns true if the socket is set to use client mode
    365         * in its first handshake.
    366         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    367         * @return true - If the socket should start its first handshake
    368         * in "client" mode.
    369         */
    370        public boolean getUseClientMode() {
    371            if (_socket_ instanceof SSLSocket)
    372                return ((SSLSocket)_socket_).getUseClientMode();
    373            return false;
    374        }
    375
    376        /**
    377         * Controls which particular cipher suites are enabled for use on this
    378         * connection. Called before server negotiation.
    379         * @param cipherSuites The cipher suites.
    380         */
    381        public void setEnabledCipherSuites(String[] cipherSuites) {
    382            suites = new String[cipherSuites.length];
    383            System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
    384        }
    385
    386        /**
    387         * Returns the names of the cipher suites which could be enabled
    388         * for use on this connection.
    389         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
    390         * @return An array of cipher suite names, or <code>null</code>
    391         */
    392        public String[] getEnabledCipherSuites() {
    393            if (_socket_ instanceof SSLSocket)
    394                return ((SSLSocket)_socket_).getEnabledCipherSuites();
    395            return null;
    396        }
    397
    398        /**
    399         * Controls which particular protocol versions are enabled for use on this
    400         * connection. I perform setting before a server negotiation.
    401         * @param protocolVersions The protocol versions.
    402         */
    403        public void setEnabledProtocols(String[] protocolVersions) {
    404            protocols = new String[protocolVersions.length];
    405            System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
    406        }
    407
    408        /**
    409         * Returns the names of the protocol versions which are currently
    410         * enabled for use on this connection.
    411         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
    412         * @return An array of protocols, or <code>null</code>
    413         */
    414        public String[] getEnabledProtocols() {
    415            if (_socket_ instanceof SSLSocket)
    416                return ((SSLSocket)_socket_).getEnabledProtocols();
    417            return null;
    418        }
    419
    420        /**
    421         * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
    422         * @param pbsz Protection Buffer Size.
    423         * @throws SSLException If the server reply code does not equal "200".
    424         * @throws IOException If an I/O error occurs while sending
    425         * the command.
    426         */
    427        public void execPBSZ(long pbsz) throws SSLException, IOException {
    428            if (pbsz < 0 || 4294967295L < pbsz)
    429                throw new IllegalArgumentException();
    430            if (FTPReply.COMMAND_OK != sendCommand(
    431                    FTPSCommand._commands[FTPSCommand.PBSZ],String.valueOf(pbsz)))
    432                throw new SSLException(getReplyString());
    433        }
    434
    435        /**
    436         * PROT command.</br>
    437         * C - Clear</br>
    438         * S - Safe(SSL protocol only)</br>
    439         * E - Confidential(SSL protocol only)</br>
    440         * P - Private
    441         * @param prot Data Channel Protection Level.
    442         * @throws SSLException If the server reply code does not equal "200".
    443         * @throws IOException If an I/O error occurs while sending
    444         * the command.
    445         */
    446        public void execPROT(String prot) throws SSLException, IOException {
    447            if (prot == null) prot = DEFAULT_PROT;
    448            if (!checkPROTValue(prot)) throw new IllegalArgumentException();
    449            if (FTPReply.COMMAND_OK != sendCommand(
    450                    FTPSCommand._commands[FTPSCommand.PROT], prot))
    451                throw new SSLException(getReplyString());
    452            if (DEFAULT_PROT.equals(prot)) {
    453                setSocketFactory(null);
    454                setServerSocketFactory(null);
    455            } else {
    456                setSocketFactory(new FTPSSocketFactory(context));
    457                setServerSocketFactory(new FTPSServerSocketFactory(context));
    458                initSslContext();
    459            }
    460        }
    461
    462        /**
    463         * Check the value that can be set in PROT Command value.
    464         * @param prot Data Channel Protection Level.
    465         * @return True - A set point is right / False - A set point is not right
    466         */
    467        private boolean checkPROTValue(String prot) {
    468            for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) {
    469                if (PROT_COMMAND_VALUE[p].equals(prot)) return true;
    470            }
    471            return false;
    472        }
    473
    474        /**
    475         * Send an FTP command.
    476         * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance  to be assigned
    477         * to a plain {@link Socket} instances
    478         * @param command The FTP command.
    479         * @return server reply.
    480         * @throws IOException If an I/O error occurs while sending
    481         * the command.
    482         * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String)
    483         */
    484        @Override
    485        public int sendCommand(String command, String args) throws IOException {
    486            int repCode = super.sendCommand(command, args);
    487            /* If CCC is issued, restore socket i/o streams to unsecured versions */
    488            if (FTPSCommand._commands[FTPSCommand.CCC].equals(command)) {
    489                if (FTPReply.COMMAND_OK == repCode) {
    490                    _socket_ = plainSocket;
    491                    _controlInput_ = new BufferedReader(
    492                        new InputStreamReader(
    493                            _socket_ .getInputStream(), getControlEncoding()));
    494                    _controlOutput_ = new BufferedWriter(
    495                        new OutputStreamWriter(
    496                            _socket_.getOutputStream(), getControlEncoding()));
    497                    setSocketFactory(null);
    498                } else {
    499                    throw new SSLException(getReplyString());
    500                }
    501            }
    502            return repCode;
    503        }
    504
    505        /**
    506         * Returns a socket of the data connection.
    507         * Wrapped as an {@link SSLSocket}, which carries out handshake processing.
    508         * @param command The textual representation of the FTP command to send.
    509         * @param arg The arguments to the FTP command.
    510         * If this parameter is set to null, then the command is sent with
    511         * no arguments.
    512         * @return corresponding to the established data connection.
    513         * Null is returned if an FTP protocol error is reported at any point
    514         * during the establishment and initialization of the connection.
    515         * @throws IOException If there is any problem with the connection.
    516         * @see FTPClient#_openDataConnection_(int, String)
    517         */
    518        @Override
    519        protected Socket _openDataConnection_(int command, String arg)
    520                throws IOException {
    521            Socket socket = super._openDataConnection_(command, arg);
    522            if (socket != null && socket instanceof SSLSocket) {
    523                SSLSocket sslSocket = (SSLSocket)socket;
    524
    525                sslSocket.setUseClientMode(isClientMode);
    526                sslSocket.setEnableSessionCreation(isCreation);
    527
    528                // server mode
    529                if (!isClientMode) {
    530                    sslSocket.setNeedClientAuth(isNeedClientAuth);
    531                    sslSocket.setWantClientAuth(isWantClientAuth);
    532                }
    533                if (suites != null)
    534                    sslSocket.setEnabledCipherSuites(suites);
    535                if (protocols != null)
    536                    sslSocket.setEnabledProtocols(protocols);
    537                sslSocket.startHandshake();
    538            }
    539
    540            return socket;
    541        }
    542
    543        /**
    544         * Get the currently configured {@link TrustManager}.
    545         *
    546         * @return A TrustManager instance.
    547         */
    548        public TrustManager getTrustManager() {
    549            return trustManager;
    550        }
    551
    552        /**
    553         * Override the default {@link TrustManager} to use.
    554         *
    555         * @param trustManager The TrustManager implementation to set.
    556         */
    557        public void setTrustManager(TrustManager trustManager) {
    558            this.trustManager = trustManager;
    559        }
    560    }