/** * 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 ); } }