Apache FTP Client decide to use SSL or raw FTP
<p>Where I work we have a vast server estate that transfer files around using in-house software which uses FTP. There is an ongoing plan to upgrade all FTP to use FTPS and SSL certificates. </p>

<p>At the time of writing this question, I am working on a project to modernize our file transfer software (it is quite old) using Java/Apache. </p>

<p>I've written an FTP Client using the Apache software and have also written a very similar FTPS client. Both work as expected. </p>

<p>However, if the FTPS client attempts to connect to a non-FTPS server it throws an SSLException. The FTP client works fine in this situation.</p>

<p>Ultimately I'd like to rationalize both Clients into a single client that can manage FTP &amp; FTPS connections.</p>

<p>My question is simply: </p>

<p>Is there a way using Apache/Java to detect which protocol is in use on the remote server before attempting the SSL connection? </p>

<p>An even better solution would be to emulate cURL and try FTPS and drop down to FTP. </p>

<p>Any help or suggestions would be greatly appreciated. </p>

<p><strong>Code below</strong></p>

<pre><code>package jtm.ftp.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLException;

import jtm.common.JtmConstants;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.util.TrustManagerUtils;
import org.apache.log4j.Logger;

public class JtmFTPSClient
{
    static Logger log = Logger.getLogger( JtmFTPSClient.class.getName( ) );   

    private static int PORT = JtmConstants.FTP_PORT;

    private static FTPSClient ftps = null;

    private static boolean IS_IMPLICIT = false;


    public static boolean getOrPut( String correlationID, String hostname, String user, String pass, String remoteFile, String localFile, String mode, String direction  )
    {        
        ftps = new FTPSClient( IS_IMPLICIT );       //look at using a constructor here perhaps - see the SWIPE FTPSClient

        boolean ftpsSuccess = false;

        log.debug( correlationID + " FTPS GET initiated" );                   

        try
        {  
            if( setSslConfig( ) == false )
            {
                return false;
            }

            String fileTermName = new File( remoteFile ).getName();                                                          

            /*
             * Configuration Section - source this from the FTPConfig object ** that needs to be made more flexible
             */

            log.info( correlationID + " FTPS Server Address : " + hostname );
            log.info( correlationID + " FTPS Port Number    : " + PORT );
            log.info( correlationID + " FTPS remoteFile     : " + remoteFile );
            log.info( correlationID + " FTPS localFile      : " + localFile );
            log.info( correlationID + " FTPS remoteTermName : " + fileTermName ); 


            // try to connect
            ftps.connect( hostname, PORT );                                                      

            /*
             *  Once connected we need to login to the server via username/password
             */
            if ( !ftps.login( user, pass ) )
            {
                log.error( correlationID + " FTPS Unable to log into : " + hostname + " with supplied credentials" );

                return false;
            }

            int reply = ftps.getReplyCode();                      

            log.debug( correlationID + " FTPS Login Reply Code : " + reply );

            /*
             * http://forus.com/csm/ftps/ trying to SSL the crap out of this client
             */
            ftps.execPBSZ( 0 );
            ftps.execPROT( "P" );

            /*
             *  FTPReply stores a set of constants for FTP reply codes.
             */
            if ( !FTPReply.isPositiveCompletion( reply ) )
            {                
                log.error( correlationID + " FTPS Not a positive reply from " + hostname + " : " + reply);                

                return false;
            }

            log.info( correlationID + " FTPS Logged into to Remote Host : " + hostname );

            ftps.enterLocalPassiveMode();

            log.debug( correlationID + " FTPS Entered Local Passive Mode");


            int xferMode = FTP.BINARY_FILE_TYPE;

            if( mode.equalsIgnoreCase("b") ) {

                xferMode = FTP.BINARY_FILE_TYPE;

            }
            else if( mode.equalsIgnoreCase("t")) {

                xferMode = FTP.ASCII_FILE_TYPE;
            }
            else
            {
                xferMode = FTP.BINARY_FILE_TYPE;
            }

            log.debug(correlationID + " Mode for FTP : " + xferMode );


            // Set the buffer size to cope with larger files
            ftps.setBufferSize( 1024 * 1024 );


            log.debug( correlationID + " FTPS Set Buffer Size to  : " + ftps.getBufferSize( ) );
            log.debug( correlationID + " FTPS Remote system type  : " + ftps.getSystemType( ) );
            log.debug( correlationID + " FTPS Remote directory is : " + ftps.printWorkingDirectory( ) );

            log.info( correlationID  + " FTPS Remote file is " + remoteFile );            

            // Get output stream - This is where the file will be downloaded to

            if(direction.equalsIgnoreCase( "get" ) )
            {
                OutputStream downloadedFile = new FileOutputStream( localFile );

                /*
                 * TODO Check that the remote file exists before download
                 * 
                 * TODO Also a check that a local copy of the file does not already exist. 
                 */                     


                /*
                 *  GET the file from the remote system ( remoteFile, downloadedFile )
                 */
                ftpsSuccess = ftps.retrieveFile( remoteFile, downloadedFile );


                /*
                 *  close output stream
                 */
                downloadedFile.close();


                log.info( correlationID + " FTPS Retrieval Complete for " + remoteFile );
            }
            else if( direction.equalsIgnoreCase("put"))
            {
                FileInputStream file = new FileInputStream( localFile );
                ftpsSuccess = ftps.storeFile(remoteFile, file );

                file.close();
            }


            if( ftpsSuccess == false )
            {
                log.error( correlationID + "FTPS Success is False" );                               
            }
        }
        catch( SSLException e )
        {
            log.error("SSLException caught ", e );

            /*
             * Do we drop down to basic FTP here and try the transfer again? 
             */
            //return JtmFTPClient.get(correlationID, hostname, user, pass, remoteFile, localFile, mode);

            ftpsSuccess = false;            

        }
        catch ( FileNotFoundException e )
        {
            log.error( correlationID + " FileNotFoundException caught ", e );


            ftpsSuccess = false;
        }       
        catch ( GeneralSecurityException e )
        {
            log.error( correlationID + " GeneralSecurityException caught ", e );

            ftpsSuccess = false;
        }
        catch ( IOException e )
        {
            log.error( correlationID + " IOException caught ", e );


            ftpsSuccess = false;
        }   
        finally
        {  
            try
            {
                ftps.logout();
                ftps.disconnect();

            }
            catch ( IOException e )
            {
                log.error(correlationID + " IOException caught closing", e );
            }
        }

        log.debug(correlationID + " FTPS result : " + ftpsSuccess );

        return ftpsSuccess;
    }




    /**
     * A Method that configures the SSL requirements when FTP'ing files to/from secure instances of UTM
     * 
     * @param  isSSL
     * @return boolean
     * @throws IOException
     * @throws GeneralSecurityException
     */
    private static boolean setSslConfig( ) throws IOException, GeneralSecurityException
    {

        String trustStorePath = JtmConstants.TRUST_STORE_PATH;
        String trustStorePass = JtmConstants.TRUST_STORE_PASS;

        String keyStorePath = JtmConstants.KEY_STORE_PATH;
        String keyStorePass = JtmConstants.KEY_STORE_PASS;

        String keyPass = JtmConstants.KEY_PASS;
        String keyAlias = JtmConstants.KEY_ALIAS;       

        boolean isSslRequired = true;


        if ( isSslRequired )
        {
            if ( trustStorePath != null &amp;&amp; trustStorePass != null )
            {
                KeyStore ks = KeyStore.getInstance( "JKS" );
                ks.load( new FileInputStream( trustStorePath ), trustStorePass.toCharArray( ) );

                ftps.setTrustManager( TrustManagerUtils.getDefaultTrustManager( ks ) );
            }
            else
            {
                log.error( "Error setting up Trust Store" );
                log.error( "Trust Store path or trust store passord have not been supplied." );

                return false;
            }
        }
        else
        {
            ftps.setTrustManager( TrustManagerUtils.getAcceptAllTrustManager( ) );
        }


        if ( keyStorePath != null &amp;&amp; keyStorePass != null )
        {

            File keyFile = new File( keyStorePath );

            KeyManager keyManager;

            if ( keyAlias != null )
            {
                if ( keyPass != null )
                {
                    keyManager = org.apache.commons.net.util.KeyManagerUtils
                            .createClientKeyManager( "JKS", keyFile,
                                    keyStorePass,
                                    keyAlias,
                                    keyPass );
                }
                else
                {
                    keyManager = org.apache.commons.net.util.KeyManagerUtils
                            .createClientKeyManager( keyFile, keyStorePass, keyAlias );                                            
                }
            }
            else
            {
                keyManager = org.apache.commons.net.util.KeyManagerUtils
                        .createClientKeyManager( keyFile, keyStorePass );
            }

            ftps.setKeyManager( keyManager );

            return true;
        }
        else
        {
            log.error( "Error setting up Key Store" );
            log.error( "Key Store path or key store passord have not been supplied." );

            return false;
        }
    }                    
}
</code></pre>