    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 javax.net.ssl.KeyManager;
    027    import javax.net.ssl.SSLContext;
    028    import javax.net.ssl.SSLException;
    029    import javax.net.ssl.SSLSocket;
    030    import javax.net.ssl.SSLSocketFactory;
    031    import javax.net.ssl.TrustManager;
    032
    033    import org.apache.commons.net.util.Base64;
    034    import org.apache.commons.net.util.SSLContextUtils;
    035    import org.apache.commons.net.util.TrustManagerUtils;
    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 1125962 2011-05-22 13:34:53Z sebb $
    042     * @since 2.0
    043     */
    044    public class FTPSClient extends FTPClient {
    045
    046    // From http://www.iana.org/assignments/port-numbers
    047
    048    //    ftps-data   989/tcp    ftp protocol, data, over TLS/SSL
    049    //    ftps-data   989/udp    ftp protocol, data, over TLS/SSL
    050    //    ftps        990/tcp    ftp protocol, control, over TLS/SSL
    051    //    ftps        990/udp    ftp protocol, control, over TLS/SSL
    052
    053        public static final int DEFAULT_FTPS_DATA_PORT = 989;
    054        public static final int DEFAULT_FTPS_PORT = 990;
    055
    056        /** The value that I can set in PROT command  (C = Clear, P = Protected) */
    057        private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"};
    058        /** Default PROT Command */
    059        private static final String DEFAULT_PROT = "C";
    060        /** Default secure socket protocol name, i.e. TLS */
    061        private static final String DEFAULT_PROTOCOL = "TLS";
    062
    063        /** The AUTH (Authentication/Security Mechanism) command. */
    064        private static final String CMD_AUTH = "AUTH";
    065        /**  The ADAT (Authentication/Security Data) command. */
    066        private static final String CMD_ADAT = "ADAT";
    067        /**  The PROT (Data Channel Protection Level) command. */
    068        private static final String CMD_PROT = "PROT";
    069        /**  The PBSZ (Protection Buffer Size) command. */
    070        private static final String CMD_PBSZ = "PBSZ";
    071        /**  The MIC (Integrity Protected Command) command. */
    072        private static final String CMD_MIC = "MIC";
    073        /**  The CONF (Confidentiality Protected Command) command. */
    074        private static final String CMD_CONF = "CONF";
    075        /**  The ENC (Privacy Protected Command) command. */
    076        private static final String CMD_ENC = "ENC";
    077        /**  The CCC (Clear Command Channel) command. */
    078        private static final String CMD_CCC = "CCC";
    079
    080        /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
    081        private final boolean isImplicit;
    082        /** The secure socket protocol to be used, e.g. SSL/TLS. */
    083        private final String protocol;
    084        /** The AUTH Command value */
    085        private String auth = DEFAULT_PROTOCOL;
    086        /** The context object. */
    087        private SSLContext context;
    088        /** The socket object. */
    089        private Socket plainSocket;
    090        /** Controls whether a new SSL session may be established by this socket. Default true. */
    091        private boolean isCreation = true;
    092        /** The use client mode flag. */
    093        private boolean isClientMode = true;
    094        /** The need client auth flag. */
    095        private boolean isNeedClientAuth = false;
    096        /** The want client auth flag. */
    097        private boolean isWantClientAuth = false;
    098        /** The cipher suites */
    099        private String[] suites = null;
    100        /** The protocol versions */
    101        private String[] protocols = null;
    102
    103        /** The FTPS {@link TrustManager} implementation, default validate only: {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. */
    104        private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager();
    105
    106        /** The {@link KeyManager}, default null (i.e. use system default). */
    107        private KeyManager keyManager = null;
    108
    109        /**
    110         * Constructor for FTPSClient.
    111         * Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false)
    112         */
    113        public FTPSClient() {
    114            this(DEFAULT_PROTOCOL, false);
    115        }
    116
    117        /**
    118         * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
    119         * @param isImplicit The security mode (Implicit/Explicit).
    120         */
    121        public FTPSClient(boolean isImplicit) {
    122            this(DEFAULT_PROTOCOL, isImplicit);
    123        }
    124
    125        /**
    126         * Constructor for FTPSClient, using explict mode
    127         * @param protocol the protocol to use
    128         */
    129        public FTPSClient(String protocol) {
    130            this(protocol, false);
    131        }
    132
    133        /**
    134         * Constructor for FTPSClient allowing specification of protocol
    135         * and security mode. If isImplicit is true, the port is set to
    136         * {@link #DEFAULT_FTPS_PORT} i.e. 990.
    137         *
    138         * @param protocol the protocol
    139         * @param isImplicit The security mode(Implicit/Explicit).
    140         */
    141        public FTPSClient(String protocol, boolean isImplicit) {
    142            super();
    143            this.protocol = protocol;
    144            this.isImplicit = isImplicit;
    145            if (isImplicit) {
    146                setDefaultPort(DEFAULT_FTPS_PORT);
    147            }
    148        }
    149
    150        /**
    151         * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
    152         * @param isImplicit The security mode(Implicit/Explicit).
    153         * @param context A pre-configured SSL Context
    154         */
    155        public FTPSClient(boolean isImplicit, SSLContext context) {
    156            this(DEFAULT_PROTOCOL, isImplicit);
    157            this.context = context;
    158        }
    159
    160        /**
    161         * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
    162         * and isImplicit {@code false}
    163         *
    164         * @param context A pre-configured SSL Context
    165         */
    166        public FTPSClient(SSLContext context) {
    167            this(false, context);
    168        }
    169
    170
    171        /**
    172         * Set AUTH command use value.
    173         * This processing is done before connected processing.
    174         * @param auth AUTH command use value.
    175         */
    176        public void setAuthValue(String auth) {
    177            this.auth = auth;
    178        }
    179
    180        /**
    181         * Return AUTH command use value.
    182         * @return AUTH command use value.
    183         */
    184        public String getAuthValue() {
    185            return this.auth;
    186        }
    187
    188
    189        /**
    190         * Because there are so many connect() methods,
    191         * the _connectAction_() method is provided as a means of performing
    192         * some action immediately after establishing a connection,
    193         * rather than reimplementing all of the connect() methods.
    194         * @throws IOException If it throw by _connectAction_.
    195         * @see org.apache.commons.net.SocketClient#_connectAction_()
    196         */
    197        @Override
    198        protected void _connectAction_() throws IOException {
    199            // Implicit mode.
    200            if (isImplicit) sslNegotiation();
    201            super._connectAction_();
    202            // Explicit mode.
    203            if (!isImplicit) {
    204                execAUTH();
    205                sslNegotiation();
    206            }
    207        }
    208
    209        /**
    210         * AUTH command.
    211         * @throws SSLException If it server reply code not equal "234" and "334".
    212         * @throws IOException If an I/O error occurs while either sending
    213         * the command.
    214         */
    215        protected void execAUTH() throws SSLException, IOException {
    216            int replyCode = sendCommand(CMD_AUTH, auth);
    217            if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
    218                // replyCode = 334
    219                // I carry out an ADAT command.
    220            } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
    221                throw new SSLException(getReplyString());
    222            }
    223        }
    224
    225        /**
    226         * Performs a lazy init of the SSL context
    227         * @throws IOException
    228         */
    229        private void initSslContext() throws IOException {
    230            if(context == null) {
    231                context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
    232            }
    233        }
    234
    235        /**
    236         * SSL/TLS negotiation. Acquires an SSL socket of a control
    237         * connection and carries out handshake processing.
    238         * @throws IOException If server negotiation fails
    239         */
    240        protected void sslNegotiation() throws IOException {
    241            plainSocket = _socket_;
    242            initSslContext();
    243
    244            SSLSocketFactory ssf = context.getSocketFactory();
    245            String ip = _socket_.getInetAddress().getHostAddress();
    246            int port = _socket_.getPort();
    247            SSLSocket socket =
    248                (SSLSocket) ssf.createSocket(_socket_, ip, port, false);
    249            socket.setEnableSessionCreation(isCreation);
    250            socket.setUseClientMode(isClientMode);
    251            // server mode
    252            if (!isClientMode) {
    253                socket.setNeedClientAuth(isNeedClientAuth);
    254                socket.setWantClientAuth(isWantClientAuth);
    255            }
    256
    257            if (protocols != null) socket.setEnabledProtocols(protocols);
    258            if (suites != null) socket.setEnabledCipherSuites(suites);
    259            socket.startHandshake();
    260
    261            _socket_ = socket;
    262            _controlInput_ = new BufferedReader(new InputStreamReader(
    263                    socket .getInputStream(), getControlEncoding()));
    264            _controlOutput_ = new BufferedWriter(new OutputStreamWriter(
    265                    socket.getOutputStream(), getControlEncoding()));
    266        }
    267
    268        /**
    269         * Get the {@link KeyManager} instance.
    270         * @return The {@link KeyManager} instance
    271         */
    272        private KeyManager getKeyManager() {
    273            return keyManager;
    274        }
    275
    276        /**
    277        * Set a {@link KeyManager} to use
    278        *
    279        * @param keyManager The KeyManager implementation to set.
    280        * @see org.apache.commons.net.util.KeyManagerUtils
    281        */
    282        public void setKeyManager(KeyManager keyManager) {
    283            this.keyManager = keyManager;
    284        }
    285
    286        /**
    287         * Controls whether a new SSL session may be established by this socket.
    288         * @param isCreation The established socket flag.
    289         */
    290        public void setEnabledSessionCreation(boolean isCreation) {
    291            this.isCreation = isCreation;
    292        }
    293
    294        /**
    295         * Returns true if new SSL sessions may be established by this socket.
    296         * When the underlying {@link Socket} instance is not SSL-enabled (i.e. an
    297         * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled,
    298         * this returns False.
    299         * @return true - Indicates that sessions may be created;
    300         * this is the default.
    301         * false - indicates that an existing session must be resumed.
    302         */
    303        public boolean getEnableSessionCreation() {
    304            if (_socket_ instanceof SSLSocket)
    305                return ((SSLSocket)_socket_).getEnableSessionCreation();
    306            return false;
    307        }
    308
    309        /**
    310         * Configures the socket to require client authentication.
    311         * @param isNeedClientAuth The need client auth flag.
    312         */
    313        public void setNeedClientAuth(boolean isNeedClientAuth) {
    314            this.isNeedClientAuth = isNeedClientAuth;
    315        }
    316
    317        /**
    318         * Returns true if the socket will require client authentication.
    319         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    320         * @return true - If the server mode socket should request
    321         * that the client authenticate itself.
    322         */
    323        public boolean getNeedClientAuth() {
    324            if (_socket_ instanceof SSLSocket)
    325                return ((SSLSocket)_socket_).getNeedClientAuth();
    326            return false;
    327        }
    328
    329        /**
    330         * Configures the socket to request client authentication,
    331         * but only if such a request is appropriate to the cipher
    332         * suite negotiated.
    333         * @param isWantClientAuth The want client auth flag.
    334         */
    335        public void setWantClientAuth(boolean isWantClientAuth) {
    336            this.isWantClientAuth = isWantClientAuth;
    337        }
    338
    339        /**
    340         * Returns true if the socket will request client authentication.
    341         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    342         * @return true - If the server mode socket should request
    343         * that the client authenticate itself.
    344         */
    345        public boolean getWantClientAuth() {
    346            if (_socket_ instanceof SSLSocket)
    347                return ((SSLSocket)_socket_).getWantClientAuth();
    348            return false;
    349        }
    350
    351        /**
    352         * Configures the socket to use client (or server) mode in its first
    353         * handshake.
    354         * @param isClientMode The use client mode flag.
    355         */
    356        public void setUseClientMode(boolean isClientMode) {
    357            this.isClientMode = isClientMode;
    358        }
    359
    360        /**
    361         * Returns true if the socket is set to use client mode
    362         * in its first handshake.
    363         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
    364         * @return true - If the socket should start its first handshake
    365         * in "client" mode.
    366         */
    367        public boolean getUseClientMode() {
    368            if (_socket_ instanceof SSLSocket)
    369                return ((SSLSocket)_socket_).getUseClientMode();
    370            return false;
    371        }
    372
    373        /**
    374         * Controls which particular cipher suites are enabled for use on this
    375         * connection. Called before server negotiation.
    376         * @param cipherSuites The cipher suites.
    377         */
    378        public void setEnabledCipherSuites(String[] cipherSuites) {
    379            suites = new String[cipherSuites.length];
    380            System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
    381        }
    382
    383        /**
    384         * Returns the names of the cipher suites which could be enabled
    385         * for use on this connection.
    386         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
    387         * @return An array of cipher suite names, or <code>null</code>
    388         */
    389        public String[] getEnabledCipherSuites() {
    390            if (_socket_ instanceof SSLSocket)
    391                return ((SSLSocket)_socket_).getEnabledCipherSuites();
    392            return null;
    393        }
    394
    395        /**
    396         * Controls which particular protocol versions are enabled for use on this
    397         * connection. I perform setting before a server negotiation.
    398         * @param protocolVersions The protocol versions.
    399         */
    400        public void setEnabledProtocols(String[] protocolVersions) {
    401            protocols = new String[protocolVersions.length];
    402            System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
    403        }
    404
    405        /**
    406         * Returns the names of the protocol versions which are currently
    407         * enabled for use on this connection.
    408         * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
    409         * @return An array of protocols, or <code>null</code>
    410         */
    411        public String[] getEnabledProtocols() {
    412            if (_socket_ instanceof SSLSocket)
    413                return ((SSLSocket)_socket_).getEnabledProtocols();
    414            return null;
    415        }
    416
    417        /**
    418         * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
    419         * @param pbsz Protection Buffer Size.
    420         * @throws SSLException If the server reply code does not equal "200".
    421         * @throws IOException If an I/O error occurs while sending
    422         * the command.
    423         * @see #parsePBSZ(long)
    424         */
    425        public void execPBSZ(long pbsz) throws SSLException, IOException {
    426            if (pbsz < 0 || 4294967295L < pbsz) // 32-bit unsigned number
    427                throw new IllegalArgumentException();
    428            int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz));
    429            if (FTPReply.COMMAND_OK != status) {
    430                throw new SSLException(getReplyString());
    431            }
    432        }
    433
    434        /**
    435         * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
    436         * Issues the command and parses the response to return the negotiated value.
    437         *
    438         * @param pbsz Protection Buffer Size.
    439         * @throws SSLException If the server reply code does not equal "200".
    440         * @throws IOException If an I/O error occurs while sending
    441         * the command.
    442         * @return the negotiated value.
    443         * @see #execPBSZ(long)
    444         * @since 3.0
    445         */
    446        public long parsePBSZ(long pbsz) throws SSLException, IOException {
    447            execPBSZ(pbsz);
    448            long minvalue = pbsz;
    449            String remainder = extractPrefixedData("PBSZ=", getReplyString());
    450            if (remainder != null) {
    451                long replysz = Long.parseLong(remainder);
    452                if (replysz < minvalue) {
    453                    minvalue = replysz;
    454                }
    455            }
    456            return minvalue;
    457        }
    458
    459        /**
    460         * PROT command.</br>
    461         * C - Clear</br>
    462         * S - Safe(SSL protocol only)</br>
    463         * E - Confidential(SSL protocol only)</br>
    464         * P - Private
    465         * <p>
    466         * <b>N.B.</b> the method calls
    467         *  {@link #setSocketFactory(javax.net.SocketFactory)} and
    468         *  {@link #setServerSocketFactory(javax.net.ServerSocketFactory)}
    469         *
    470         * @param prot Data Channel Protection Level.
    471         * @throws SSLException If the server reply code does not equal "200".
    472         * @throws IOException If an I/O error occurs while sending
    473         * the command.
    474         */
    475        public void execPROT(String prot) throws SSLException, IOException {
    476            if (prot == null) prot = DEFAULT_PROT;
    477            if (!checkPROTValue(prot)) throw new IllegalArgumentException();
    478            if (FTPReply.COMMAND_OK != sendCommand(CMD_PROT, prot))
    479                throw new SSLException(getReplyString());
    480            if (DEFAULT_PROT.equals(prot)) {
    481                setSocketFactory(null);
    482                setServerSocketFactory(null);
    483            } else {
    484                setSocketFactory(new FTPSSocketFactory(context));
    485                setServerSocketFactory(new FTPSServerSocketFactory(context));
    486                initSslContext();
    487            }
    488        }
    489
    490        /**
    491         * Check the value that can be set in PROT Command value.
    492         * @param prot Data Channel Protection Level.
    493         * @return True - A set point is right / False - A set point is not right
    494         */
    495        private boolean checkPROTValue(String prot) {
    496            for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) {
    497                if (PROT_COMMAND_VALUE[p].equals(prot)) return true;
    498            }
    499            return false;
    500        }
    501
    502        /**
    503         * Send an FTP command.
    504         * A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket}
    505         * instance to be assigned to a plain {@link Socket}
    506         * @param command The FTP command.
    507         * @return server reply.
    508         * @throws IOException If an I/O error occurs while sending the command.
    509         * @throws SSLException if a CCC command fails
    510         * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String)
    511         */
    512        // Would like to remove this method, but that will break any existing clients that are using CCC
    513        @Override
    514        public int sendCommand(String command, String args) throws IOException {
    515            int repCode = super.sendCommand(command, args);
    516            /* If CCC is issued, restore socket i/o streams to unsecured versions */
    517            if (CMD_CCC.equals(command)) {
    518                if (FTPReply.COMMAND_OK == repCode) {
    519                    _socket_.close();
    520                    _socket_ = plainSocket;
    521                    _controlInput_ = new BufferedReader(
    522                        new InputStreamReader(
    523                            _socket_ .getInputStream(), getControlEncoding()));
    524                    _controlOutput_ = new BufferedWriter(
    525                        new OutputStreamWriter(
    526                            _socket_.getOutputStream(), getControlEncoding()));
    527                } else {
    528                    throw new SSLException(getReplyString());
    529                }
    530            }
    531            return repCode;
    532        }
    533
    534        /**
    535         * Returns a socket of the data connection.
    536         * Wrapped as an {@link SSLSocket}, which carries out handshake processing.
    537         * @param command The textual representation of the FTP command to send.
    538         * @param arg The arguments to the FTP command.
    539         * If this parameter is set to null, then the command is sent with
    540         * no arguments.
    541         * @return corresponding to the established data connection.
    542         * Null is returned if an FTP protocol error is reported at any point
    543         * during the establishment and initialization of the connection.
    544         * @throws IOException If there is any problem with the connection.
    545         * @see FTPClient#_openDataConnection_(int, String)
    546         */
    547        @Override
    548        protected Socket _openDataConnection_(int command, String arg)
    549                throws IOException {
    550            Socket socket = super._openDataConnection_(command, arg);
    551            if (socket instanceof SSLSocket) {
    552                SSLSocket sslSocket = (SSLSocket)socket;
    553
    554                sslSocket.setUseClientMode(isClientMode);
    555                sslSocket.setEnableSessionCreation(isCreation);
    556
    557                // server mode
    558                if (!isClientMode) {
    559                    sslSocket.setNeedClientAuth(isNeedClientAuth);
    560                    sslSocket.setWantClientAuth(isWantClientAuth);
    561                }
    562                if (suites != null) {
    563                    sslSocket.setEnabledCipherSuites(suites);
    564                }
    565                if (protocols != null) {
    566                    sslSocket.setEnabledProtocols(protocols);
    567                }
    568                sslSocket.startHandshake();
    569            }
    570
    571            return socket;
    572        }
    573
    574        /**
    575         * Get the currently configured {@link TrustManager}.
    576         *
    577         * @return A TrustManager instance.
    578         */
    579        public TrustManager getTrustManager() {
    580            return trustManager;
    581        }
    582
    583        /**
    584         * Override the default {@link TrustManager} to use.
    585         *
    586         * @param trustManager The TrustManager implementation to set.
    587        * @see org.apache.commons.net.util.TrustManagerUtils
    588         */
    589        public void setTrustManager(TrustManager trustManager) {
    590            this.trustManager = trustManager;
    591        }
    592
    593        /**
    594         * Closes the connection to the FTP server and restores
    595         * connection parameters to the default values.
    596         * <p>
    597         * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)}
    598         * to reset the factories that may have been changed during the session,
    599         * e.g. by {@link #execPROT(String)}
    600         * @exception IOException If an error occurs while disconnecting.
    601         * @since 3.0
    602         */
    603        @Override
    604        public void disconnect() throws IOException
    605        {
    606            super.disconnect();
    607            setSocketFactory(null);
    608            setServerSocketFactory(null);
    609        }
    610
    611        /**
    612         * Send the AUTH command with the specified mechanism.
    613         * @param mechanism The mechanism name to send with the command.
    614         * @return server reply.
    615         * @throws IOException If an I/O error occurs while sending
    616         * the command.
    617         * @since 3.0
    618         */
    619        public int execAUTH(String mechanism) throws IOException
    620        {
    621            return sendCommand(CMD_AUTH, mechanism);
    622        }
    623
    624        /**
    625         * Send the ADAT command with the specified authentication data.
    626         * @param data The data to send with the command.
    627         * @return server reply.
    628         * @throws IOException If an I/O error occurs while sending
    629         * the command.
    630         * @since 3.0
    631         */
    632        public int execADAT(byte[] data) throws IOException
    633        {
    634            if (data != null)
    635            {
    636                return sendCommand(CMD_ADAT, new String(Base64.encodeBase64(data)));
    637            }
    638            else
    639            {
    640                return sendCommand(CMD_ADAT);
    641            }
    642        }
    643
    644        /**
    645         * Send the CCC command to the server.
    646         * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance  to be assigned
    647         * to a plain {@link Socket} instances
    648         * @return server reply.
    649         * @throws IOException If an I/O error occurs while sending
    650         * the command.
    651         * @since 3.0
    652         */
    653        public int execCCC() throws IOException
    654        {
    655            int repCode = sendCommand(CMD_CCC);
    656    // This will be performed by sendCommand(String, String)
    657    //        if (FTPReply.isPositiveCompletion(repCode)) {
    658    //            _socket_.close();
    659    //            _socket_ = plainSocket;
    660    //            _controlInput_ = new BufferedReader(
    661    //                new InputStreamReader(
    662    //                    _socket_.getInputStream(), getControlEncoding()));
    663    //            _controlOutput_ = new BufferedWriter(
    664    //                new OutputStreamWriter(
    665    //                    _socket_.getOutputStream(), getControlEncoding()));
    666    //        }
    667            return repCode;
    668        }
    669
    670        /**
    671         * Send the MIC command with the specified data.
    672         * @param data The data to send with the command.
    673         * @return server reply.
    674         * @throws IOException If an I/O error occurs while sending
    675         * the command.
    676         * @since 3.0
    677         */
    678        public int execMIC(byte[] data) throws IOException
    679        {
    680            if (data != null)
    681            {
    682                return sendCommand(CMD_MIC, new String(Base64.encodeBase64(data)));
    683            }
    684            else
    685            {
    686                return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)?
    687            }
    688        }
    689
    690        /**
    691         * Send the CONF command with the specified data.
    692         * @param data The data to send with the command.
    693         * @return server reply.
    694         * @throws IOException If an I/O error occurs while sending
    695         * the command.
    696         * @since 3.0
    697         */
    698        public int execCONF(byte[] data) throws IOException
    699        {
    700            if (data != null)
    701            {
    702                return sendCommand(CMD_CONF, new String(Base64.encodeBase64(data)));
    703            }
    704            else
    705            {
    706                return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)?
    707            }
    708        }
    709
    710        /**
    711         * Send the ENC command with the specified data.
    712         * @param data The data to send with the command.
    713         * @return server reply.
    714         * @throws IOException If an I/O error occurs while sending
    715         * the command.
    716         * @since 3.0
    717         */
    718        public int execENC(byte[] data) throws IOException
    719        {
    720            if (data != null)
    721            {
    722                return sendCommand(CMD_ENC, new String(Base64.encodeBase64(data)));
    723            }
    724            else
    725            {
    726                return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)?
    727            }
    728        }
    729
    730        /**
    731         * Parses the given ADAT response line and base64-decodes the data.
    732         * @param reply The ADAT reply to parse.
    733         * @return the data in the reply, base64-decoded.
    734         * @since 3.0
    735         */
    736        public byte[] parseADATReply(String reply)
    737        {
    738            if (reply == null) return null;
    739            else {
    740                return Base64.decodeBase64(extractPrefixedData("ADAT=", reply));
    741            }
    742        }
    743
    744        /**
    745         * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234
    746         * @param prefix the prefix to find
    747         * @param reply where to find the prefix
    748         * @return the remainder of the string after the prefix, or null if the prefix was not present.
    749         */
    750        private String extractPrefixedData(String prefix, String reply) {
    751            int idx = reply.indexOf(prefix);
    752            if (idx == -1) {
    753                return null;
    754            }
    755            // N.B. Cannot use trim before substring as leading space would affect the offset.
    756            return reply.substring(idx+prefix.length()).trim();
    757        }
    758
    759        // DEPRECATED - for API compatibility only - DO NOT USE
    760
    761        /** @deprecated - not used - may be removed in a future release */
    762        @Deprecated
    763        public static String KEYSTORE_ALGORITHM;
    764
    765        /** @deprecated - not used - may be removed in a future release */
    766        @Deprecated
    767        public static String TRUSTSTORE_ALGORITHM;
    768
    769        /** @deprecated - not used - may be removed in a future release */
    770        @Deprecated
    771        public static String PROVIDER;
    772
    773        /** @deprecated - not used - may be removed in a future release */
    774        @Deprecated
    775        public static String STORE_TYPE;
    776
    777    }
    778    /* kate: indent-width 4; replace-tabs on; */