/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
/**
*
*/
package org.jboss.internal.soa.esb.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.addressing.eprs.FileEpr;
import org.jboss.soa.esb.addressing.eprs.SFTPEpr;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.util.ClassUtil;
import org.jboss.soa.esb.util.FileUtil;
import org.jboss.soa.esb.util.RemoteFileSystem;
import org.jboss.soa.esb.util.RemoteFileSystemException;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UserInfo;
import com.jcraft.jsch.ChannelSftp.LsEntry;
/**
*
* Implementation of sftp (Secure FTP over SSH) Based on JSch from JCraft
* http://www.jcraft.com/
*
* @author b_georges
*/
public class SecureFtpImpl implements RemoteFileSystem
{
private static final Logger _logger = Logger.getLogger(SecureFtpImpl.class);
private static final String TMP_SUFFIX = ".rosettaPart";
private static final String SECURE_CHANNEL = "sftp";
// The objects implementing secure FTP over ssh
private JSch m_oJSch = new JSch();
private Session session = null;
private ChannelSftp m_oSftpChannel = null;
private int m_iPort;
private SFTPEpr m_oEpr;
private ConfigTree m_oParms;
private String m_sFtpServer, m_sUser, m_sPasswd;
private String m_sRemoteDir, m_sLocalDir;
private URI m_oCertificate;
private String m_sPassphrase;
/*
* Constructor
*
* @param p_oP Is an EPR used to initialize the object
*
* @param connect If true create a new sftp session
*
*/
public SecureFtpImpl(SFTPEpr p_oP, boolean p_bConnect) throws ConfigurationException, RemoteFileSystemException
{
m_oEpr = p_oP;
final URI uri ;
try {
uri = m_oEpr.getURI();
} catch (URISyntaxException e) {
throw new RemoteFileSystemException(e);
}
m_sFtpServer = uri.getHost();
String[] sa = null;
if (uri.getUserInfo() != null)
sa = uri.getUserInfo().split(":");
final int saLen = (sa == null ? 0 : sa.length) ;
switch(saLen)
{
case 2:
m_sPasswd = sa[1] ;
case 1:
m_sUser = sa[0] ;
}
m_sRemoteDir = uri.getPath();
if ((m_sRemoteDir == null) || (m_sRemoteDir.equals("")))
m_sRemoteDir = FtpUtils.getRemoteDir();
m_iPort = uri.getPort();
m_sLocalDir = FtpUtils.getLocalDir();
try
{
m_oCertificate = p_oP.getCertificateURI() ;
}
catch (final URISyntaxException urise)
{
throw new RemoteFileSystemException(urise);
}
m_sPassphrase = p_oP.getPassphrase() ;
configTreeFromEpr();
initialize(p_bConnect);
}
private void initialize(boolean bConnect) throws ConfigurationException, RemoteFileSystemException
{
checkParms();
if (bConnect)
{
_logger.debug("Connecting to SFTP server") ;
try
{
if (m_iPort > 0)
session = m_oJSch.getSession(m_sUser, m_sFtpServer, m_iPort);
else
session = m_oJSch.getSession(m_sUser, m_sFtpServer);
if (m_sPasswd != null)
{
final UserInfo ui = new SecureFtpUserInfo(null, m_sPasswd) ;
session.setUserInfo(ui) ;
session.setConfig("PreferredAuthentications", "password,keyboard-interactive") ;
}
else if (m_oCertificate != null)
{
final String certificate = m_oCertificate.toString() ;
final InputStream is ;
final String certificateName ;
final InputStream resourceIS = ClassUtil.getResourceAsStream(certificate, getClass()) ;
if (resourceIS != null)
{
is = resourceIS ;
certificateName = getSimpleName(certificate) ;
}
else
{
certificateName = getSimpleName(m_oCertificate.getPath()) ;
try
{
if (m_oCertificate.isAbsolute())
{
is = m_oCertificate.toURL().openStream() ;
}
else
{
final File file = new File(m_oCertificate.getPath()) ;
is = file.toURL().openStream() ;
}
}
catch (final IOException ioe)
{
throw new ConfigurationException("Unexpected IOException accessing: " + certificate, ioe) ;
}
}
final byte[] privateKey = StreamUtils.readStream(is) ;
m_oJSch.addIdentity(certificateName, privateKey, null, null) ;
final UserInfo ui = new SecureFtpUserInfo(m_sPassphrase, null);
session.setUserInfo(ui);
session.setConfig("PreferredAuthentications", "publickey") ;
}
session.setConfig("StrictHostKeyChecking", "no") ;
session.connect();
final Channel channel = session.openChannel(SECURE_CHANNEL);
channel.connect();
m_oSftpChannel = (ChannelSftp) channel;
if (!session.isConnected())
throw new RemoteFileSystemException("Can't connect to FTP server");
}
catch (JSchException ex)
{
if ((session != null) && session.isConnected())
{
session.disconnect() ;
}
_logger.error("Caught Secure FTP Exception.");
_logger.debug("Caught Secure FTP Exception.", ex);
throw new RemoteFileSystemException(ex);
}
}
}
private static String getSimpleName(final String name)
throws ConfigurationException
{
if (name == null)
{
throw new ConfigurationException("Null certificate name") ;
}
final int lastIndex = name.lastIndexOf('/') ;
if (lastIndex >= 0)
{
final int startIndex = lastIndex+1 ;
if (startIndex == name.length())
{
throw new ConfigurationException("Invalid certificate name: " + name) ;
}
return name.substring(startIndex) ;
}
return name ;
}
private void checkParms() throws ConfigurationException
{
String att = m_oParms.getAttribute(FileEpr.URL_TAG);
URI uri = null;
try
{
if (att != null)
uri = new URI(att);
}
catch (URISyntaxException ex)
{
throw new ConfigurationException(ex);
}
m_sFtpServer = (null != uri) ? uri.getHost() : m_oParms.getAttribute(PARMS_FTP_SERVER);
if (null == m_sFtpServer)
throw new ConfigurationException("No SFTP server specified");
String[] sa = (null == uri) ? null : uri.getUserInfo().split(":");
m_sUser = (null != sa) ? sa[0] : m_oParms.getAttribute(PARMS_USER);
if (null == m_sUser)
throw new ConfigurationException("No username specified for SFTP");
m_sPasswd = ((null != sa) && (sa.length > 1)) ? sa[1] : m_oParms.getAttribute(PARMS_PASSWD);
m_sRemoteDir = (null != uri) ? uri.getPath() : m_oParms.getAttribute(PARMS_REMOTE_DIR);
if (null == m_sRemoteDir)
m_sRemoteDir = "";
m_sLocalDir = m_oParms.getAttribute(PARMS_LOCAL_DIR);
if (null == m_sLocalDir)
m_sLocalDir = ".";
String sAux = m_oParms.getAttribute(PARMS_PORT);
try
{
m_iPort = (null != uri) ? uri.getPort() : (null == sAux) ? 22 : Integer.parseInt(sAux);
final String certificate = m_oParms.getAttribute(PARMS_CERTIFICATE) ;
if (certificate != null)
{
m_oCertificate = new URI(certificate) ;
}
}
catch (Exception ex)
{
throw new ConfigurationException(ex);
}
m_sPassphrase = m_oParms.getAttribute(PARMS_PASSPHRASE) ;
}
/*
* Deletes a file on the SFTP-Server
*
* @param fileName The file's Name to be removed from the SFTP-Server
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#deleteRemoteFile(java.lang.String)
*/
public void deleteRemoteFile(String p_sFile) throws RemoteFileSystemException
{
if (_logger.isDebugEnabled())
{
_logger.debug("deleteRemoteFile(" + p_sFile + "), remote dir " + getRemoteDir()) ;
}
try
{
m_oSftpChannel.cd(getRemoteDir()) ;
m_oSftpChannel.rm(p_sFile);
}
catch (SftpException ex)
{
throw new RemoteFileSystemException(ex);
}
}
/*
* Deletes a file on the SFTP-Server
*
* @param fileName The file to be removed from the SFTP-Server
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#remoteDelete(java.io.File)
*/
public void remoteDelete(File p_oFile) throws RemoteFileSystemException
{
final String remoteFile = FtpUtils.fileToFtpString(p_oFile) ;
if (_logger.isDebugEnabled())
{
_logger.debug("remoteDelete(" + remoteFile + ")") ;
}
try
{
m_oSftpChannel.rm(remoteFile);
}
catch (SftpException ex)
{
throw new RemoteFileSystemException(ex);
}
}
/*
* Returns a list of Filenames for the directory specified in p_Suffix
*
* @param p_sSuffix The remote directory path from the SFTP-Server
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#getFileListFromRemoteDir(java.lang.String)
*/
public String[] getFileListFromRemoteDir(String p_sSuffix) throws RemoteFileSystemException
{
if (_logger.isDebugEnabled())
{
_logger.debug("getFileListFromRemoteDir(" + p_sSuffix + "), remote dir " + getRemoteDir()) ;
}
try
{
m_oSftpChannel.cd(getRemoteDir()) ;
}
catch (SftpException ex)
{
throw new RemoteFileSystemException(ex);
}
String sSuffix = (null == p_sSuffix) ? "*" : "*" + p_sSuffix;
final Vector vFileList ;
try
{
vFileList = m_oSftpChannel.ls(sSuffix);
}
catch (SftpException ex)
{
if (ex.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
{
return null ;
}
throw new RemoteFileSystemException(ex);
}
List<String> lFileList = new ArrayList<String>();
if (vFileList != null)
{
for (int i = 0; i < vFileList.size(); i++)
{
Object obj = vFileList.elementAt(i);
if (obj instanceof LsEntry)
{
SftpATTRS oSftAttr = ((LsEntry) obj).getAttrs();
if (!oSftAttr.isDir())
{
lFileList.add(((LsEntry) obj).getFilename());
}
}
}
}
return (String[]) lFileList.toArray(new String[lFileList.size()]);
}
/*
* Set the new directory to p_SDir
*
* @param p_sDir The remote directory path name we want to "cd" to.
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#setRemoteDir(java.lang.String)
*/
public void setRemoteDir(String p_sDir) throws RemoteFileSystemException
{
m_sRemoteDir = p_sDir ;
}
/*
* Rename the Remote Directory name p_sFrom with p_sTo
*
* @param p_sFrom The remote directory name we want to rename
*
* @param p_sTo The new remote directory name
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#renameInRemoteDir(java.lang.String,
* java.lang.String)
*/
public void renameInRemoteDir(String p_sFrom, String p_sTo)
throws RemoteFileSystemException
{
if (_logger.isDebugEnabled())
{
_logger.debug("renameInRemoteDir(" + p_sFrom + ", " + p_sTo + "), remote dir " + getRemoteDir()) ;
}
try
{
m_oSftpChannel.cd(getRemoteDir()) ;
m_oSftpChannel.rename(p_sFrom, p_sTo) ;
}
catch (SftpException se)
{
throw new RemoteFileSystemException("Faile to rename file", se) ;
}
}
/*
* Rename the Remote File name p_sFrom with p_sTo
*
* @param p_oFrom The remote file name we want to rename
*
* @param p_oTo The new remote file name
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#remoteRename(java.io.File,
* java.io.File)
*/
public void remoteRename(File p_oFrom, File p_oTo) throws RemoteFileSystemException
{
final String from = FtpUtils.fileToFtpString(p_oFrom) ;
final String to = FtpUtils.fileToFtpString(p_oTo) ;
if (_logger.isDebugEnabled())
{
_logger.debug("remoteRename(" + from + ", " + to + "), remote dir " + getRemoteDir()) ;
}
try
{
m_oSftpChannel.cd(getRemoteDir()) ;
m_oSftpChannel.rename(from, to);
}
catch (SftpException se)
{
throw new RemoteFileSystemException("Faile to rename file", se) ;
}
}
/*
* Upload the local File p_ofile to p_sRemoteName
*
* @param p_oFile The local file name we want to upload
*
* @param p_sRemoteName The remote file name [can be the same as p_oFile of
* course]
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#uploadFile(java.io.File,
* java.lang.String)
*/
public void uploadFile(File p_oFile, String p_sRemoteName) throws RemoteFileSystemException
{
if (_logger.isDebugEnabled())
{
_logger.debug("uploadFile(" + p_oFile + ", " + p_sRemoteName + "), remote dir " + getRemoteDir()) ;
}
try
{
m_oSftpChannel.cd(getRemoteDir()) ;
final String sRemoteTmp = p_sRemoteName + TMP_SUFFIX;
final OutputStream os = m_oSftpChannel.put(sRemoteTmp) ;
try
{
final FileInputStream fis = new FileInputStream(p_oFile) ;
try
{
copyStream(fis, os) ;
}
finally
{
fis.close() ;
}
}
finally
{
os.flush() ;
os.close() ;
}
m_oSftpChannel.rename(sRemoteTmp, p_sRemoteName);
}
catch (final IOException ioe)
{
throw new RemoteFileSystemException(ioe) ;
}
catch (SftpException ex)
{
throw new RemoteFileSystemException(ex);
}
}
/*
* Download the remote File p_sFile to p_sFile.
*
* @param p_sFile The remote file name we want to download
*
* @param p_sFinalName The local file name
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#downloadFile(java.lang.String,
* java.lang.String)
*/
public void downloadFile(String p_sFile, String p_sFinalName) throws IOException, RemoteFileSystemException
{
if (_logger.isDebugEnabled())
{
_logger.debug("downloadFile(" + p_sFile + ", " + p_sFinalName + "), remote dir " + getRemoteDir()) ;
}
try
{
final File to = new File(p_sFinalName) ;
final File oLocalDir = new File(m_sLocalDir);
final File oNew = (to.isAbsolute() ? to : new File(oLocalDir, p_sFinalName)) ;
if (oNew.exists())
oNew.delete();
final File toTmp = new File(p_sFinalName + TMP_SUFFIX) ;
final File oNewTmp = (toTmp.isAbsolute() ? toTmp : new File(oLocalDir, p_sFinalName + TMP_SUFFIX)) ;
if (oNewTmp.exists())
oNewTmp.delete();
m_oSftpChannel.cd(getRemoteDir()) ;
final InputStream is = m_oSftpChannel.get(p_sFile) ;
try
{
final FileOutputStream fos = new FileOutputStream(oNewTmp) ;
try
{
copyStream(is, fos) ;
}
finally
{
fos.close() ;
}
}
finally
{
is.close() ;
}
FileUtil.renameTo(oNewTmp, oNew) ;
}
catch (SftpException ex)
{
throw new RemoteFileSystemException(ex);
}
}
/*
* Returns the current remote directory
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#getRemoteDir()
*/
public String getRemoteDir()
{
return m_sRemoteDir;
}
/*
* Terminates the sftp session.
*
* @see org.jboss.soa.esb.util.RemoteFileSystem#quit()
*/
public void quit()
{
_logger.debug("quitting") ;
m_oSftpChannel.disconnect() ;
session.disconnect() ;
}
private void configTreeFromEpr() throws ConfigurationException
{
m_oParms = new ConfigTree("fromEpr");
try
{
m_oParms.setAttribute(RemoteFileSystem.PARMS_FTP_SERVER,
m_sFtpServer);
m_oParms.setAttribute(RemoteFileSystem.PARMS_USER, m_sUser);
if (m_sPasswd != null)
m_oParms.setAttribute(RemoteFileSystem.PARMS_PASSWD, m_sPasswd);
m_oParms.setAttribute(RemoteFileSystem.PARMS_REMOTE_DIR,
m_sRemoteDir);
if (m_iPort > 0)
m_oParms.setAttribute(RemoteFileSystem.PARMS_PORT, Integer
.toString(m_iPort));
m_oParms.setAttribute(RemoteFileSystem.PARMS_LOCAL_DIR, m_sLocalDir);
m_oParms.setAttribute(RemoteFileSystem.PARMS_ASCII, Boolean
.toString(false));
if (m_oCertificate != null)
m_oParms.setAttribute(RemoteFileSystem.PARMS_CERTIFICATE, m_oCertificate.toString()) ;
if (m_sPassphrase != null)
m_oParms.setAttribute(RemoteFileSystem.PARMS_PASSPHRASE, m_sPassphrase) ;
}
catch (Exception e)
{
throw new ConfigurationException(e);
}
}
private void copyStream(final InputStream is, final OutputStream os)
throws IOException
{
final BufferedInputStream bis = new BufferedInputStream(is) ;
final BufferedOutputStream bos = new BufferedOutputStream(os) ;
final byte[] buffer = new byte[256] ;
while(true)
{
final int count = bis.read(buffer) ;
if (count <= 0)
{
break ;
}
bos.write(buffer, 0, count) ;
}
bos.flush() ;
}
}