/**
* Copyright (c) 2014 by the original author or authors.
*
* This code is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package ch.sdi.core.impl.ftp;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPHTTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.CopyStreamListener;
import org.apache.commons.net.util.TrustManagerUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.StringUtils;
import ch.sdi.core.exc.SdiException;
import ch.sdi.core.impl.ftp.PrintCommandToLoggerListener;
/**
* The base of this example is copied from
* (http://commons.apache.org/proper/commons-net/examples/ftp/FTPClientExample.java) and adapted for
* using it embedded.<p>
*
* This is an example program demonstrating how to use the FTPClient class.
* This program connects to an FTP server and retrieves the specified
* file. If the -s flag is used, it stores the local file at the FTP server.
* Just so you can see what's happening, all reply strings are printed.
* If the -b flag is used, a binary transfer is assumed (default is ASCII).
* See below for further options.
*/
public final class FTPClientExample
{
/** logger for this class */
private static Logger myLog = LogManager.getLogger( FTPClientExample.class );
private boolean myStoreFile = false;
private boolean myBinaryTransfer = false;
private boolean myListFiles = false;
private boolean myListNames = false;
private boolean myHidden = false;
private boolean myLocalActive = false;
private boolean myUseEpsvWithIPv4 = false;
private boolean myFeat = false;
private boolean myPrintHash = false;
private boolean myMlst = false;
private boolean myMlsd = false;
private boolean myLenient = false;
private long myKeepAliveTimeout = -1;
private int myControlKeepAliveReplyTimeout = -1;
private String myProtocol = null; // SSL protocol
private String myDoCommand = null;
private String myTrustmgr = null;
private String myProxyHost = null;
private int myProxyPort = 80;
private String myProxyUser = null;
private String myProxyPassword = null;
private String myUsername = null;
private String myPassword = null;
private String myRemote;
private String myLocal;
private int myPort;
private String myServer;
private FTPClient myFtp;
private PrintCommandToLoggerListener myPrintCommandToLoggerListener;
public static final String USAGE =
"Usage: ftp [options] <hostname> <username> <password> [<remote file> [<local file>]]\n"
+ "\nDefault behavior is to download a file and use ASCII transfer mode.\n"
+ "\t-a - use local active mode (default is local passive)\n"
+ "\t-A - anonymous login (omit username and password parameters)\n"
+ "\t-b - use binary transfer mode\n"
+ "\t-c cmd - issue arbitrary command (remote is used as a parameter if provided) \n"
+ "\t-d - list directory details using MLSD (remote is used as the pathname if provided)\n"
+ "\t-e - use EPSV with IPv4 (default false)\n"
+ "\t-f - issue FEAT command (remote and local files are ignored)\n"
+ "\t-h - list hidden files (applies to -l and -n only)\n"
+ "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n"
+ "\t-l - list files using LIST (remote is used as the pathname if provided)\n"
+ "\t Files are listed twice: first in raw mode, then as the formatted parsed data.\n"
+ "\t-L - use lenient future dates (server dates may be up to 1 day into future)\n"
+ "\t-n - list file names using NLST (remote is used as the pathname if provided)\n"
+ "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n"
+ "\t-s - store file on server (upload)\n"
+ "\t-t - list file details using MLST (remote is used as the pathname if provided)\n"
+ "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n"
+ "\t-T all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n"
+ "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n"
+ "\t-PrU user - HTTP Proxy server username\n" + "\t-PrP password - HTTP Proxy server password\n"
+ "\t-# - add hash display during transfers\n";
public static void main( String[] aArgs ) throws UnknownHostException
{
List<String> args = new ArrayList<String>( Arrays.asList( aArgs ) );
args.add( "-s" ); // store file on sesrver
args.add( "-b" ); // binary transfer mode
args.add( "-#" );
args.add( "192.168.99.1" );
args.add( "heri" ); // user
args.add( "heri" ); // pw
args.add( "/var/www/log4j2.xml" );
URL url = ClassLoader.getSystemResource( "sdimain_test.properties" );
// URL url = ClassLoader.getSystemResource( "log4j2.xml" );
args.add( url.getFile() );
FTPClientExample example = new FTPClientExample();
try
{
example.init( args.toArray( new String[args.size()] ) );
example.run();
}
catch ( Throwable t )
{
myLog.error( "Exception caught", t );
myLog.info( USAGE );
System.exit( 1 );
}
} // end main
/**
* @param aArgs
*/
private void init( String[] aArgs ) throws Throwable
{
int base = 0;
int minParams = 5; // listings require 3 params
for ( base = 0; base < aArgs.length; base++ )
{
if ( aArgs[base].equals( "-s" ) )
{
myStoreFile = true;
}
else if ( aArgs[base].equals( "-a" ) )
{
myLocalActive = true;
}
else if ( aArgs[base].equals( "-A" ) )
{
myPassword = System.getProperty( "user.name" ) + "@" + InetAddress.getLocalHost().getHostName();
myUsername = "anonymous";
}
else if ( aArgs[base].equals( "-b" ) )
{
myBinaryTransfer = true;
}
else if ( aArgs[base].equals( "-c" ) )
{
myDoCommand = aArgs[++base];
minParams = 3;
}
else if ( aArgs[base].equals( "-d" ) )
{
myMlsd = true;
minParams = 3;
}
else if ( aArgs[base].equals( "-e" ) )
{
myUseEpsvWithIPv4 = true;
}
else if ( aArgs[base].equals( "-f" ) )
{
myFeat = true;
minParams = 3;
}
else if ( aArgs[base].equals( "-h" ) )
{
myHidden = true;
}
else if ( aArgs[base].equals( "-k" ) )
{
myKeepAliveTimeout = Long.parseLong( aArgs[++base] );
}
else if ( aArgs[base].equals( "-l" ) )
{
myListFiles = true;
minParams = 3;
}
else if ( aArgs[base].equals( "-L" ) )
{
myLenient = true;
}
else if ( aArgs[base].equals( "-n" ) )
{
myListNames = true;
minParams = 3;
}
else if ( aArgs[base].equals( "-p" ) )
{
myProtocol = aArgs[++base];
}
else if ( aArgs[base].equals( "-t" ) )
{
myMlst = true;
minParams = 3;
}
else if ( aArgs[base].equals( "-w" ) )
{
myControlKeepAliveReplyTimeout = Integer.parseInt( aArgs[++base] );
}
else if ( aArgs[base].equals( "-T" ) )
{
myTrustmgr = aArgs[++base];
}
else if ( aArgs[base].equals( "-PrH" ) )
{
myProxyHost = aArgs[++base];
String parts[] = myProxyHost.split( ":" );
if ( parts.length == 2 )
{
myProxyHost = parts[0];
myProxyPort = Integer.parseInt( parts[1] );
}
}
else if ( aArgs[base].equals( "-PrU" ) )
{
myProxyUser = aArgs[++base];
}
else if ( aArgs[base].equals( "-PrP" ) )
{
myProxyPassword = aArgs[++base];
}
else if ( aArgs[base].equals( "-#" ) )
{
myPrintHash = true;
}
else
{
break;
}
}
int remain = aArgs.length - base;
if ( myUsername != null )
{
minParams -= 2;
}
if ( remain < minParams ) // server, user, pass, remote, local [protocol]
{
throw new Exception( "Error: Too less params" );
}
myServer = aArgs[base++];
myPort = 0;
String parts[] = myServer.split( ":" );
if ( parts.length == 2 )
{
myServer = parts[0];
myPort = Integer.parseInt( parts[1] );
}
if ( myUsername == null )
{
myUsername = aArgs[base++];
myPassword = aArgs[base++];
}
myRemote = null;
if ( aArgs.length - base > 0 )
{
myRemote = aArgs[base++];
}
myLocal = null;
if ( aArgs.length - base > 0 )
{
myLocal = aArgs[base++];
}
}
/**
*
*/
private void run() throws Throwable
{
if ( myProtocol == null )
{
if ( myProxyHost != null )
{
myLog.debug( "Using HTTP proxy server: " + myProxyHost );
myFtp = new FTPHTTPClient( myProxyHost, myProxyPort, myProxyUser, myProxyPassword );
}
else
{
myFtp = new FTPClient();
}
}
else
{
FTPSClient ftps;
if ( myProtocol.equals( "true" ) )
{
ftps = new FTPSClient( true );
}
else if ( myProtocol.equals( "false" ) )
{
ftps = new FTPSClient( false );
}
else
{
String prot[] = myProtocol.split( "," );
if ( prot.length == 1 )
{ // Just protocol
ftps = new FTPSClient( myProtocol );
}
else
{ // protocol,true|false
ftps = new FTPSClient( prot[0], Boolean.parseBoolean( prot[1] ) );
}
}
myFtp = ftps;
if ( "all".equals( myTrustmgr ) )
{
ftps.setTrustManager( TrustManagerUtils.getAcceptAllTrustManager() );
}
else if ( "valid".equals( myTrustmgr ) )
{
ftps.setTrustManager( TrustManagerUtils.getValidateServerCertificateTrustManager() );
}
else if ( "none".equals( myTrustmgr ) )
{
ftps.setTrustManager( null );
}
}
if ( myPrintHash )
{
myFtp.setCopyStreamListener( createListener() );
}
if ( myKeepAliveTimeout >= 0 )
{
myFtp.setControlKeepAliveTimeout( myKeepAliveTimeout );
}
if ( myControlKeepAliveReplyTimeout >= 0 )
{
myFtp.setControlKeepAliveReplyTimeout( myControlKeepAliveReplyTimeout );
}
myFtp.setListHiddenFiles( myHidden );
// intercept commands and write it to our logger
myPrintCommandToLoggerListener = new PrintCommandToLoggerListener( myLog );
PrintWriter writer = myPrintCommandToLoggerListener.getPrintWriter();
myFtp.addProtocolCommandListener( new PrintCommandListener( writer, true, '\n', true ) );
// myFtp.addProtocolCommandListener( new PrintCommandListener( new PrintWriter( System.out ), true ) );
try
{
int reply;
if ( myPort > 0 )
{
myFtp.connect( myServer, myPort );
}
else
{
myFtp.connect( myServer );
}
myLog.debug( "Connected to " + myServer + " on " + ( myPort > 0 ? myPort : myFtp.getDefaultPort() ) );
// After connection attempt, you should check the reply code to verify
// success.
reply = myFtp.getReplyCode();
if ( !FTPReply.isPositiveCompletion( reply ) )
{
myFtp.disconnect();
throw new Exception( "FTP server refused connection." );
}
}
catch ( IOException e )
{
if ( myFtp.isConnected() )
{
try
{
myFtp.disconnect();
}
catch ( IOException f )
{
// do nothing
}
}
throw new Exception( "Could not connect to server.", e );
}
try
{
if ( !myFtp.login( myUsername, myPassword ) )
{
myFtp.logout();
throw createFtpException( "Problems on login" );
}
myLog.debug( "Remote system is " + myFtp.getSystemType() );
if ( myBinaryTransfer )
{
myFtp.setFileType( FTP.BINARY_FILE_TYPE );
}
else
{
// in theory this should not be necessary as servers should default to ASCII
// but they don't all do so - see NET-500
myFtp.setFileType( FTP.ASCII_FILE_TYPE );
}
// Use passive mode as default because most of us are
// behind firewalls these days.
if ( myLocalActive )
{
myFtp.enterLocalActiveMode();
}
else
{
myFtp.enterLocalPassiveMode();
}
myFtp.setUseEPSVwithIPv4( myUseEpsvWithIPv4 );
if ( myStoreFile )
{
InputStream input;
input = new FileInputStream( myLocal );
myFtp.storeFile( myRemote, input );
input.close();
}
else if ( myListFiles )
{
if ( myLenient )
{
FTPClientConfig config = new FTPClientConfig();
config.setLenientFutureDates( true );
myFtp.configure( config );
}
for ( FTPFile f : myFtp.listFiles( myRemote ) )
{
myLog.debug( f.getRawListing() );
myLog.debug( f.toFormattedString() );
}
}
else if ( myMlsd )
{
for ( FTPFile f : myFtp.mlistDir( myRemote ) )
{
myLog.debug( f.getRawListing() );
myLog.debug( f.toFormattedString() );
}
}
else if ( myMlst )
{
FTPFile f = myFtp.mlistFile( myRemote );
if ( f != null )
{
myLog.debug( f.toFormattedString() );
}
}
else if ( myListNames )
{
for ( String s : myFtp.listNames( myRemote ) )
{
myLog.debug( s );
}
}
else if ( myFeat )
{
// boolean feature check
if ( myRemote != null )
{ // See if the command is present
if ( myFtp.hasFeature( myRemote ) )
{
myLog.debug( "Has feature: " + myRemote );
}
else
{
if ( FTPReply.isPositiveCompletion( myFtp.getReplyCode() ) )
{
myLog.debug( "FEAT " + myRemote + " was not detected" );
}
else
{
throw createFtpException( "Command failed" );
}
}
// Strings feature check
String[] features = myFtp.featureValues( myRemote );
if ( features != null )
{
for ( String f : features )
{
myLog.debug( "FEAT " + myRemote + "=" + f + "." );
}
}
else
{
if ( FTPReply.isPositiveCompletion( myFtp.getReplyCode() ) )
{
myLog.warn( "FEAT " + myRemote + " is not present" );
}
else
{
throw createFtpException( "Command failed" );
}
}
}
else
{
if ( myFtp.features() )
{
// Command listener has already printed the output
}
else
{
throw createFtpException( "Command failed" );
}
}
}
else if ( myDoCommand != null )
{
if ( myFtp.doCommand( myDoCommand, myRemote ) )
{
// Command listener has already printed the output
}
else
{
throw createFtpException( "Command failed" );
}
}
else
{
OutputStream output;
output = new FileOutputStream( myLocal );
myFtp.retrieveFile( myRemote, output );
output.close();
}
myFtp.noop(); // check that control connection is working OK
myFtp.logout();
}
catch ( FTPConnectionClosedException e )
{
throw createFtpException( "Server closed connection." );
}
catch ( IOException e )
{
throw createFtpException( "IOException caught" );
}
finally
{
myPrintCommandToLoggerListener.flushRest();
if ( myFtp.isConnected() )
{
try
{
myFtp.disconnect();
}
catch ( IOException f )
{
// do nothing
}
}
}
}
private CopyStreamListener createListener()
{
return new CopyStreamListener()
{
private long megsTotal = 0;
@Override
public void bytesTransferred( CopyStreamEvent event )
{
bytesTransferred( event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize() );
}
@Override
public void bytesTransferred( long totalBytesTransferred, int bytesTransferred, long streamSize )
{
// ??? what does this algo? Print a # for each transferred MB?
long megs = totalBytesTransferred / 1000000;
for ( long l = megsTotal; l < megs; l++ )
{
myLog.warn( "#" );
}
megsTotal = megs;
}
};
}
private SdiException createFtpException( String aMessage )
{
return createFtpException( aMessage, null );
}
private SdiException createFtpException( String aMessage, Throwable aThrowable )
{
StringBuilder sb = new StringBuilder( aMessage );
if ( myFtp != null )
{
sb.append( "ReplyCode: " ).append( myFtp.getReplyCode() );
sb.append( "\n Reply-Message:" );
sb.append( "\n " ).append( StringUtils.collectionToDelimitedString( Arrays.asList( myFtp
.getReplyStrings() ), "\n " ) );
} // if myFtp != null
return new SdiException( sb.toString(), aThrowable, SdiException.EXIT_CODE_FTP_ERROR );
}
}