JSch ChannelSftp exit status is always -1 - java

I use the JSch library to develop a SFTP client.
The problem is both get and put methods' status is -1.
Here is my code:
class SftpClient {
private static final Logger LOG = Logger.getLogger(SftpClient.class);
/** Connection port number */
public static final int PORT = 22;
/** SECURED protocol name */
private static final String PROTOCOL = "sftp";
/** Connection time out in milliseconds */
public static final int TIME_OUT = 5000;
/** This class serves as a central configuration point, and as a factory for Session objects configured with these settings */
private JSch _client;
/** A session represents a connection to a SSH server */
private Session _session;
/** Channel connected to a SECURED server (as a subsystem of the SSH server) */
private ChannelSftp _channelSftp;
/**
* Value returned by the last executed command.
*/
private int _exitValue;
/**
* Computer contains the url, the login and the password to connect.
*/
private Computer _computer;
/**
* Initialize a SECURED client
* #param target - Machine we want to connect to
*/
public SftpClient(Computer target) {
_client = new JSch();
_computer = target;
}
protected void connect() throws Exception {
try {
if (_client == null) {
_client = new JSch();
}
if (_session == null) {
_session = _client.getSession(_computer.getLogin(), _computer.getUrl(), PORT);
Properties props = new Properties();
props.put("StrictHostKeyChecking", "no");
props.put("compression.s2c", "zlib,none");
props.put("compression.c2s", "zlib,none");
_session.setConfig(props);
_session.setPassword(_computer.getPassword());
if (LOG.isDebugEnabled()) {
LOG.debug("Connecting to "+_computer.getUrl()+" with login "+_computer.getLogin()+"...");
}
}
if (!_session.isConnected()) {
_session.connect(TIME_OUT);
}
// disconnect previous channel if it has not been killed properly
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
}
_channelSftp = (ChannelSftp) _session.openChannel(PROTOCOL);
_channelSftp.connect();
if (LOG.isInfoEnabled()) {
LOG.info("Connected to "+_computer.getUrl()+" with login "+_computer.getLogin());
}
} catch(JSchException e) {
LOG.error("Auth failed", e);
throw e;
}
}
protected void connect(String path) throws Exception {
connect();
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.cd(path);
}
}
public boolean get(final String remoteDirectory, final String remoteFile, final String localDirectory) throws Exception {
boolean res = false;
if (LOG.isInfoEnabled()) {
LOG.info("Download file "+remoteDirectory+"/"+remoteFile+" from "+_computer+" in "+localDirectory);
}
if (remoteDirectory != null && remoteFile != null && !remoteFile.isEmpty() && localDirectory != null) {
try {
// connect to the server and change directory
connect(remoteDirectory);
// change local directory
_channelSftp.lcd(localDirectory);
// download the file, keeping the same name
_channelSftp.get(remoteFile, remoteFile);
// update exit value
_exitValue = _channelSftp.getExitStatus();
if (_exitValue == 0) {
res = true;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Exit status is: "+_exitValue);
}
} catch(SftpException e){
LOG.error("Auth failed", e);
throw e;
} finally {
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
_channelSftp.exit();
}
}
} else {
LOG.warn("Check remoteDirectory ('"+remoteDirectory+"') or remoteFile ('"+remoteFile+"') or localDirectory ('"+localDirectory+"').");
}
return res;
}
public void put(final File localFile, final String destPath) throws Exception {
if (LOG.isInfoEnabled()) {
LOG.info("Send file "+localFile+" to "+_computer+" in "+destPath);
}
if (localFile == null) {
_exitValue = -1;
LOG.error("The given local file is null. Aborting tranfer.");
return;
}
if (!localFile.exists()) {
_exitValue = -1;
LOG.error("The given local file '"+localFile+"' does not exist. Aborting tranfer.");
return;
}
final InputStream input = new FileInputStream(localFile);
if (input == null || input.available() <= 0) {
_exitValue = -1;
LOG.error("Cannot read file "+localFile);
return;
}
try {
connect(destPath);
_channelSftp.put(input, localFile.getName());
_exitValue = _channelSftp.getExitStatus();
if (LOG.isDebugEnabled()) {
LOG.debug("Exit status is: "+_exitValue);
}
} catch(SftpException e){
LOG.error("Auth failed", e);
throw e;
} finally {
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
_channelSftp.exit();
}
IOUtils.closeQuietly(input);
}
}
public void disconnect() {
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
_channelSftp.exit();
}
if (_session != null && _session.isConnected()) {
_session.disconnect();
if (LOG.isInfoEnabled()) {
LOG.info("SECURED FTP disconnected");
}
}
}
}
No exception is thrown.
By the way, the files I want to upload and download are tar files. Does Jsch have a FTP binary mode?
Files are well transferred. I can use them properly but I am worried about the exit status.
I had a look to the Jsch API and it says:
The exit status is only available for certain types of channels, and only after the channel was closed (more exactly, just before the channel is closed).
How can I know if the exit status is available for ChannelSftp?

Looking at the source code it looks ExitStatus is not implemented for SFTP
channel.setExitStatus(reason_code);
is implemented for only below two cases in Session class
case SSH_MSG_CHANNEL_OPEN_FAILURE:
case SSH_MSG_CHANNEL_REQUEST:
and both the cases are not called atleast for successful scenarios that I tested.

Related

Discontinuous FTP download throws "Read timed out" or "Connection reset"

I used FTP and FTPClient in package 'org.apache.commons.net.ftp' to download files from FTP server.
Here is my total example code
public class FtpInput {
private static final Logger LOG = Logger.getLogger(FtpInput.class);
private static final int TIMEOUT = 120000;
private static final String SIZE_COMMAND_REPLY_CODE = "213 ";
/**
* FTPClient
*/
private FTPClient ftpClient;
/**
* FTP size
*/
private long completeFileSize = 0;
protected String ip = "";
protected int port = 21;
protected String user = "";
protected String passwd = "";
protected String path = "";
protected String fileName = "";
/**
* count input bytes
*/
private CountingInputStream is;
/**
* the bytes already processed
*/
private long processedBytesNum;
private byte[] inputBuffer = new byte[1024];
/**
* connect to ftp server and fetch inputStream
*/
public void connect() {
this.ftpClient = new FTPClient();
ftpClient.setRemoteVerificationEnabled(false);
try {
ftpClient.connect(ip, port);
if (!ftpClient.login(user, passwd)) {
throw new IOException("ftp login failed!");
}
if (StringUtils.isNotBlank(path)) {
if (!ftpClient.changeWorkingDirectory(path)) {
ftpClient.mkd(path);
if (!ftpClient.changeWorkingDirectory(path)) {
throw new IOException("ftp change working dir failed! path:" + path);
}
}
}
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.setSoTimeout(TIMEOUT);
ftpClient.setConnectTimeout(TIMEOUT);
ftpClient.setDataTimeout(TIMEOUT);
ftpClient.enterLocalPassiveMode();
// keep control channel keep-alive when download large file
ftpClient.setControlKeepAliveTimeout(120);
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("ftp login failed!", e);
}
// get complete ftp size
completeFileSize = getFtpFileSize();
LOG.info(String.format("ftp file size: %d", completeFileSize));
try {
InputStream ftpis = this.ftpClient.retrieveFileStream(this.fileName);
if (ftpis == null) {
LOG.error("cannot fetch source file.");
}
this.is = new CountingInputStream(ftpis);
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* readBytes
*
* #return
*/
public byte[] readBytes() {
byte[] bytes = readBytesFromStream(is, inputBuffer);
// the bytes processed
processedBytesNum = is.getCount();
return bytes;
}
/**
* readBytesFromStream
*
* #param stream
* #param inputBuffer
* #return
*/
protected byte[] readBytesFromStream(InputStream stream, byte[] inputBuffer) {
Preconditions.checkNotNull(stream != null, "InputStream has not been inited yet.");
Preconditions.checkArgument(inputBuffer != null && inputBuffer.length > 0);
int readBytes;
try {
readBytes = stream.read(inputBuffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (readBytes == inputBuffer.length) {
// inputBuffer is filled full.
return inputBuffer;
} else if (readBytes > 0 && readBytes < inputBuffer.length) {
// inputBuffer is not filled full.
byte[] tmpBytes = new byte[readBytes];
System.arraycopy(inputBuffer, 0, tmpBytes, 0, readBytes);
return tmpBytes;
} else if (readBytes == -1) {
// Read end.
return null;
} else {
// may other situation happens?
throw new RuntimeException(String.format("readBytesFromStream: readBytes=%s inputBuffer.length=%s",
readBytes, inputBuffer.length));
}
}
/**
* fetch the byte size of remote file size
*/
private long getFtpFileSize() {
try {
ftpClient.sendCommand("SIZE", this.fileName);
String reply = ftpClient.getReplyString().trim();
LOG.info(String.format("ftp file %s size reply : %s", fileName, reply));
Preconditions.checkArgument(reply.startsWith(SIZE_COMMAND_REPLY_CODE),
"ftp file size reply: %s is not success", reply);
String sizeSubStr = reply.substring(SIZE_COMMAND_REPLY_CODE.length());
long actualFtpSize = Long.parseLong(sizeSubStr);
return actualFtpSize;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
public void close() {
try {
if (is != null) {
LOG.info(String.format("already read %d bytes from ftp file %s", is.getCount(), fileName));
is.close();
}
if (ftpClient != null) {
// Must call completePendingCommand() to finish command.
boolean isSuccessTransfer = ftpClient.completePendingCommand();
if (!isSuccessTransfer) {
LOG.error("error happened when complete transfer of ftp");
}
ftpClient.logout();
ftpClient.disconnect();
}
} catch (Throwable e) {
e.printStackTrace();
LOG.error(String.format("Close ftp input failed:%s,%s", e.getMessage(), e.getCause()));
} finally {
is = null;
ftpClient = null;
}
}
public void validInputComplete() {
Preconditions.checkArgument(processedBytesNum == completeFileSize, "ftp file transfer is not complete");
}
/**
* main
*
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String ip = "***.***.***.****";
int port = 21;
String user = "***";
String passwd = "***";
String path = "/home/work";
String fileName = "b.txt";
FtpInput input = new FtpInput();
try {
input.fileName = fileName;
input.path = path;
input.ip = ip;
input.port = port;
input.user = user;
input.passwd = passwd;
// connect to FTP server
input.connect();
while (true) {
// read bytes
byte[] bytes = input.readBytes();
if (bytes == null) {
break;
}
LOG.info("read " + bytes.length + " bytes at :" + new Date(System.currentTimeMillis()));
// Attention: this is used for simulating the process of writing data into hive table
// it maybe consume more than 1 minute;
Thread.sleep(3000);
}
input.validInputComplete();
} catch (Exception e) {
e.printStackTrace();
} finally {
input.close();
}
}
}
here is the exception message:
java.net.SocketTimeoutException: Read timed out
or
java.net.SocketException: Connection reset
at stream.readBytes in method readBytesFromStream
At first, i think it probably caused by writing into hive table slowly, and then the FTP Server closed the connection.
But actually, the speed of writing into hive table is fast enough.
Now, i need your help, how can i fix this problem.
From your comments, it looks like it can take hours before you finish downloading the file.
You cannot reasonably expect an FTP server to wait for you for hours to finish the transfer. Particularly if you are not transferring anything most of the time. You waste server resources and most servers will protect themselves against such abuse.
Your design is flawed.
You should redesign your application to first fully download the file; and import the file only after the download finishes.

How to do multiple attempts to upload using SFTP protocol?

In case of file upload fails then how to do multiple attempts while doing SFTP by using JSCH API?
How to ensure file is uploaded successfully?
How to create thread-safe file upload utility?
Create a common static utility method which can be invoked from the external class. This method has a map argument to persist the values of sFTPUser, sFTPHost, sFTPPort, sFTPPwd, destinationLocation and uploadedFileName :
public static void doSFTP(Map<String, String> ftpParameters) {
if (ftpParameters.get("ID_NAME").equals(
NAPSCommonConstants.MFT_NAPSGPCS_INTF)) {
// do sftp for NAPS GPCS Interface.
uploadUsingSFTP(ftpParameters);
}
}
Use synchronized method to ensure thread safety:
private static synchronized void uploadUsingSFTP(
Map<String, String> ftpPrameterList) {
new SFTPUtility().uploadFileMFT(ftpPrameterList.get("sFTPUser"),
ftpPrameterList.get("sFTPHost"), new Integer(ftpPrameterList
.get("sFTPPort")), ftpPrameterList.get("sFTPPwd"),
ftpPrameterList.get("sourceLocation"), ftpPrameterList
.get("destinationLocation"), ftpPrameterList
.get("uploadedFileName"));
}
Responsible method to upload files using SFTP with 5 attempts:
private void uploadFileMFT(String sFTPUser, String sFTPHost, int sFTPPort,
String sFTPPwd, String sourceLocation, String destinationLocation,
String uploadedFileName) {
LOG.info("Inside uploadFileMFT to upload and verify the file.");
JSch jsch = new JSch();
Vector<String> fileList = null;
/** 5 re-attempt logic to get session */
int attempts = 0;
boolean successfulConnect;
do {
try {
successfulConnect = true;
session = jsch.getSession(sFTPUser, sFTPHost, sFTPPort);
LOG.debug("session connected ...");
session.setPassword(sFTPPwd);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
LOG.debug("Sftp Session connected ...");
channel = session.openChannel("sftp");
LOG.debug("Sftp Channel opened ...");
channelSftp = (ChannelSftp) channel;
channelSftp.connect();
LOG.info(" Sftp channel opened and connected ...");
channelSftp.put(sourceLocation, destinationLocation);
fileList = channelSftp.ls(destinationLocation);
} catch (JSchException e) {
++attempts;
successfulConnect = false;
LOG.error(e);
} catch (SftpException e) {
++attempts;
successfulConnect = false;
LOG.error(e);
} finally {
if (null != channelSftp) {
channelSftp.exit();
LOG.debug(" sftp Channel exited.");
}
if (null != channel) {
channel.disconnect();
LOG.debug(" Channel disconnected.");
}
if (null != session) {
session.disconnect();
LOG.debug(" Host Session disconnected.");
}
}
} while (attempts < 5 && successfulConnect == false);
fileUploadValidation(fileList, uploadedFileName);
LOG.info("Exiting from method - uploadFileMFT ...");
}
Finally uploaded file can be validated:
private void fileUploadValidation (Vector<String> fileList, String uploadedFileName){
boolean isFileExistis = false;
Object[] objArr = fileList.toArray();
for (int i = 0; i < objArr.length; i++) {
String fileName = objArr[i].toString();
isFileExistis = fileName.contains(uploadedFileName);
if (isFileExistis) {
LOG.info("Uploaded file '" + uploadedFileName + "' was transferred successfull ...");
break;
}else if(i >= objArr.length){
LOG.info("Uploaded file '" + uploadedFileName + "' was failed to transfer ...");
}
}
}

JSch sftp job summary

private final String host;
private final String userAccount;
private final String keyDir;
private ChannelSftp sftpChannel;
private Session session;
private Channel channel
public void send(List<Path> filesToSend, String destination) throws SftpException, IOException {
if (sftpChannel == null) {
logger.error("Failed to create SFTP channel");
}
for (Path file : filesToSend) {
send(file, destination);
}
//summary of sent files over sftpchannel
}
public void send(Path file, String destination) throws SftpException, IOException {
if (sftpChannel == null) {
logger.error("Failed to create SFTP channel");
}
sftpChannel.put(Files.newInputStream(file), destination + File.separator + file.getFileName());
} //end send
}//end class
After all the files have been sent, can somebody show me how I can get a count of how many number of files have successfully been sent. Not important but also if any failed or any kind of monitoring. How can I do this with Jsch library.
I would like something in my log such as :
Preparing to send [14] files
Number of files sent is [14]...
public void send(List<Path> filesToSend, String destination) {
logger.debug("Preparing to send ["+filesToSend.size()+"] files");
if (sftpChannel == null) {
logger.error("Failed to create SFTP channel");
}
int successCount = 0;
int failedCount = 0;
for (Path file : filesToSend) {
try {
send(file, destination);
successCount++;
} catch (Exception e) {
failedCount++;
}
}
//summary of sent files over sftpchannel
logger.debug("Successfully sent " + successCount);
logger.debug("Failed to sent " + failedCount);
}
Given that jsch throws exceptions when an error occurs, why don't you just implement your own counter (just an int would work)?

JSch upload with progress monitor

I try to implement a SFTP upload with a progress bar.
Unfortunately the progress bar is not updated...
Here is a part of my code:
public class MainView extends JFrame implements SftpProgressMonitor {
private static final Logger LOG = Logger.getLogger(MainView.class);
private String _source;
private JProgressBar _progressBar;
private JButton _button;
public MainView() {
initComponents();
}
void initComponents() {
_button = new JButton("Send");
_button.addActionListener(new ActionListener() {
// If clicked, send event to controller...
});
_progressBar = new JProgressBar();
// Do init stuff here
// ...
}
#Override
public boolean count(long byteTransfered) {
int transfered = _progressBar.getValue();
transfered += byteTransfered;
_progressBar.setValue(transfered);
return true;
}
#Override
public void end() {
LOG.info("Transfer of "+_source+" finished!");
}
#Override
public void init(int op, String src, String dest, long max) {
_progressBar.setValue(0);
_progressBar.setMinimum(0);
_progressBar.setMaximum((int) max);
_source = src;
}
}
public class Controller {
private final MainView _view;
private final SftpClient _ftp;
private final Server _server;
public Controller() {
_server = new Server("192.168.0.1");
_view = new MainView();
_ftp = new SftpClient(_server);
_view.setVisible(true);
}
public void send() {
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
public void run() {
File testFile = new File("/PathToFile/file.txt");
String remoteDir = "/MyRemoteDir/";
_ftp.put(testFile, remoteDir, testFile.getName(), _view);
}
});
}
public static void main(String[] args) {
Controller controller = new Controller();
}
}
public class SftpClient {
private static final Logger LOG = Logger.getLogger(SftpClient.class);
/** Connection port number */
public static final int PORT = 22;
/** SECURED protocol name */
public static final String PROTOCOL = "sftp";
/** Connection time out in milliseconds */
public static final int TIME_OUT = 3000;
private Server _server;
/** This class serves as a central configuration point, and as a factory for Session objects configured with these settings */
private JSch _client;
/** A session represents a connection to a SSH server */
private Session _session;
/** Channel connected to a SECURED server (as a subsystem of the SSH server) */
private ChannelSftp _channelSftp;
/**
* Value returned by the last executed command.
*/
private int _exitValue;
public SftpClient(Server server) {
_client = new JSch();
_server = server;
}
protected void connect() throws AuthenticationException, Exception {
try {
if (_client == null) {
_client = new JSch();
}
if (_session == null) {
_session = _client.getSession(_server.getLogin(), _server.getAddress(), PORT);
_session.setConfig("StrictHostKeyChecking", "no");
_session.setPassword(_server.getPassword());
if (LOG.isDebugEnabled()) {
LOG.debug("Connecting to "+_server.getAddress()+" with login "+_server.getLogin()+"...");
}
}
if (!_session.isConnected()) {
_session.connect(TIME_OUT);
}
if(_channelSftp == null || _channelSftp.isConnected() == false) {
Channel c = _session.openChannel(PROTOCOL);
c.connect();
// disconnect previous channel if it has not been killed properly
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
}
_channelSftp = (ChannelSftp) c;
}
if (LOG.isInfoEnabled()) {
LOG.info("Connected to "+_server.getAddress()+" with login "+_server.getLogin());
}
} catch(JSchException e) {
if ("Auth fail".equals(e.getMessage())) {
throw new AuthenticationException(e);
} else {
throw new Exception(e);
}
}
}
protected void connect(String path) throws AuthenticationException, Exception {
connect();
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.cd(path);
}
}
#Override
public void disconnect() {
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
_channelSftp.exit();
}
if (_session != null && _session.isConnected()) {
_session.disconnect();
if (LOG.isInfoEnabled()) {
LOG.info("SECURED FTP disconnected");
}
}
}
#Override
public void put(File localFile, String destPath, SftpProgressMonitor monitor) throws Exception {
put(localFile, destPath, localFile.getName(), monitor);
}
#Override
public void put(File localFile, String destPath, String remoteFileName, SftpProgressMonitor monitor) throws Exception {
if (LOG.isInfoEnabled()) {
LOG.info("Send file "+localFile+" to "+_server+" in "+destPath);
}
if (localFile == null) {
_exitValue = -1;
LOG.error("The given local file is null. Aborting tranfer.");
return;
}
if (!localFile.exists()) {
_exitValue = -1;
LOG.error("'"+localFile+"' doesn't exist. Aborting tranfer.");
return;
}
if(!localFile.canRead()) {
_exitValue = -1;
LOG.error("Cannot read '"+localFile+"'. Aborting tranfer.");
return;
}
final InputStream input = new BufferedInputStream(new FileInputStream(localFile));
if (input == null || input.available() <= 0) {
_exitValue = -1;
LOG.error("Cannot read file "+localFile);
return;
}
try {
connect(destPath);
_channelSftp.put(input, remoteFileName, monitor);
_exitValue = _channelSftp.getExitStatus();
} catch(SftpException e){
throw new IOException(e);
} finally {
if (_channelSftp != null && _channelSftp.isConnected()) {
_channelSftp.disconnect();
_channelSftp.exit();
}
IOUtils.closeQuietly(input);
}
}
}
The count() method is never called. And the source and destination strings of the init contain both -. Am I doing it wrong?
I've changed my code and it works now. I don't use put(InputStream src, String dst, int mode) anymore but put(String src, String dst, SftpProgressMonitor monitor).
I've also implemented a DefaultBoundedRangeModel class. It modifies directly the JProgressBar and it is more interesting for me because I have several files to transfer.
public class ProgressModel extends DefaultBoundedRangeModel implements SftpProgressMonitor {
/** Logger */
private static Logger LOG = Logger.getLogger(ProgressModel.class);
private String _fileBeingTransfered;
/**
* Constructs the model.
*/
public ProgressModel() {
_fileBeingTransfered = "";
}
#Override
public boolean count(long count) {
int value = (int) (getValue() + count);
setValue(value);
fireStateChanged();
if(value < getMaximum()) {
return true;
} else {
return false;
}
}
#Override
public void end() {
LOG.info(_fileBeingTransfered+" transfert finished.");
if(getValue() == getMaximum()) {
LOG.info("All transfers are finished!");
}
}
#Override
public void init(int op, String src, String dest, long max) {
LOG.info("Transfering "+src+" to "+dest+" | size: "+max);
_fileBeingTransfered = src;
}
}
I don't know what caused my problem. Maybe it was the put method.
In order to get updates you have to send reference to monitor to FTP client. And you do this:
_ftp.put(testFile, remoteDir, testFile.getName(), _view);
However what _view is? It is a private final field of class Controller that is never initialized. Therefore it is null. You implemented your callback method count() into class MainView but do not send reference to it to the FTP client. I do not know where do you create instance of Controller but you should pass reference to instance of MainView to it.

SSHD Java example

Can anyone point me to some example code for using SSHD to access a server and execute some commands from a JAVA application. I have looked through the Apache SSHD website and downloads and have not found anything useful yet as far as documentation and example code. I also googled SSHD example code and was unsuccessful.
This one can run,I have checked it.I just delete the import.
version apache sshd-core-0.7.0.jar
public class SshClient extends AbstractFactoryManager implements ClientFactoryManager {
protected IoConnector connector;
protected SessionFactory sessionFactory;
private ServerKeyVerifier serverKeyVerifier;
public SshClient() {
}
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public ServerKeyVerifier getServerKeyVerifier() {
return serverKeyVerifier;
}
public void setServerKeyVerifier(ServerKeyVerifier serverKeyVerifier) {
this.serverKeyVerifier = serverKeyVerifier;
}
public void start() {
// Register the additional agent forwarding channel if needed
if (getAgentFactory() != null) {
List<NamedFactory<Channel>> factories = getChannelFactories();
if (factories == null) {
factories = new ArrayList<NamedFactory<Channel>>();
} else {
factories = new ArrayList<NamedFactory<Channel>>(factories);
}
factories.add(getAgentFactory().getChannelForwardingFactory());
setChannelFactories(factories);
}
connector = createAcceptor();
if (sessionFactory == null) {
sessionFactory = new SessionFactory();
}
sessionFactory.setClient(this);
connector.setHandler(sessionFactory);
}
protected NioSocketConnector createAcceptor() {
return new NioSocketConnector(getNioWorkers());
}
public void stop() {
connector.dispose();
connector = null;
}
public ConnectFuture connect(String host, int port) throws Exception {
assert host != null;
assert port >= 0;
if (connector == null) {
throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
}
SocketAddress address = new InetSocketAddress(host, port);
return connect(address);
}
public ConnectFuture connect(SocketAddress address) throws Exception {
assert address != null;
if (connector == null) {
throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
}
final ConnectFuture connectFuture = new DefaultConnectFuture(null);
connector.connect(address).addListener(new IoFutureListener<org.apache.mina.core.future.ConnectFuture>() {
public void operationComplete(org.apache.mina.core.future.ConnectFuture future) {
if (future.isCanceled()) {
connectFuture.cancel();
} else if (future.getException() != null) {
connectFuture.setException(future.getException());
} else {
ClientSessionImpl session = (ClientSessionImpl) AbstractSession.getSession(future.getSession());
connectFuture.setSession(session);
}
}
});
return connectFuture;
}
/**
* Setup a default client. The client does not require any additional setup.
*
* #return a newly create SSH client
*/
public static SshClient setUpDefaultClient() {
SshClient client = new SshClient();
// DHG14 uses 2048 bits key which are not supported by the default JCE provider
if (SecurityUtils.isBouncyCastleRegistered()) {
client.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
new DHG14.Factory(),
new DHG1.Factory()));
client.setRandomFactory(new SingletonRandomFactory(new BouncyCastleRandom.Factory()));
} else {
client.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
new DHG1.Factory()));
client.setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory()));
}
setUpDefaultCiphers(client);
// Compression is not enabled by default
// client.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
// new CompressionNone.Factory(),
// new CompressionZlib.Factory(),
// new CompressionDelayedZlib.Factory()));
client.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
new CompressionNone.Factory()));
client.setMacFactories(Arrays.<NamedFactory<Mac>>asList(
new HMACMD5.Factory(),
new HMACSHA1.Factory(),
new HMACMD596.Factory(),
new HMACSHA196.Factory()));
client.setSignatureFactories(Arrays.<NamedFactory<Signature>>asList(
new SignatureDSA.Factory(),
new SignatureRSA.Factory()));
return client;
}
private static void setUpDefaultCiphers(SshClient client) {
List<NamedFactory<Cipher>> avail = new LinkedList<NamedFactory<Cipher>>();
avail.add(new AES128CBC.Factory());
avail.add(new TripleDESCBC.Factory());
avail.add(new BlowfishCBC.Factory());
avail.add(new AES192CBC.Factory());
avail.add(new AES256CBC.Factory());
for (Iterator<NamedFactory<Cipher>> i = avail.iterator(); i.hasNext();) {
final NamedFactory<Cipher> f = i.next();
try {
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (InvalidKeyException e) {
i.remove();
} catch (Exception e) {
i.remove();
}
}
client.setCipherFactories(avail);
}
/*=================================
Main class implementation
*=================================*/
public static void main(String[] args) throws Exception {
int port = 22;
String host = null;
String login = System.getProperty("user.name");
boolean agentForward = false;
List<String> command = null;
int logLevel = 0;
boolean error = false;
for (int i = 0; i < args.length; i++) {
if (command == null && "-p".equals(args[i])) {
if (i + 1 >= args.length) {
System.err.println("option requires an argument: " + args[i]);
error = true;
break;
}
port = Integer.parseInt(args[++i]);
} else if (command == null && "-l".equals(args[i])) {
if (i + 1 >= args.length) {
System.err.println("option requires an argument: " + args[i]);
error = true;
break;
}
login = args[++i];
} else if (command == null && "-v".equals(args[i])) {
logLevel = 1;
} else if (command == null && "-vv".equals(args[i])) {
logLevel = 2;
} else if (command == null && "-vvv".equals(args[i])) {
logLevel = 3;
} else if ("-A".equals(args[i])) {
agentForward = true;
} else if ("-a".equals(args[i])) {
agentForward = false;
} else if (command == null && args[i].startsWith("-")) {
System.err.println("illegal option: " + args[i]);
error = true;
break;
} else {
if (host == null) {
host = args[i];
} else {
if (command == null) {
command = new ArrayList<String>();
}
command.add(args[i]);
}
}
}
if (host == null) {
System.err.println("hostname required");
error = true;
}
if (error) {
System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-l login] [-p port] hostname [command]");
System.exit(-1);
}
// TODO: handle log level
SshClient client = SshClient.setUpDefaultClient();
client.start();
try {
boolean hasKeys = false;
/*
String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
if (authSock == null) {
KeyPair[] keys = null;
AgentServer server = new AgentServer();
authSock = server.start();
List<String> files = new ArrayList<String>();
File f = new File(System.getProperty("user.home"), ".ssh/id_dsa");
if (f.exists() && f.isFile() && f.canRead()) {
files.add(f.getAbsolutePath());
}
f = new File(System.getProperty("user.home"), ".ssh/id_rsa");
if (f.exists() && f.isFile() && f.canRead()) {
files.add(f.getAbsolutePath());
}
try {
if (files.size() > 0) {
keys = new FileKeyPairProvider(files.toArray(new String[0]), new PasswordFinder() {
public char[] getPassword() {
try {
System.out.println("Enter password for private key: ");
BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
String password = r.readLine();
return password.toCharArray();
} catch (IOException e) {
return null;
}
}
}).loadKeys();
}
} catch (Exception e) {
}
SshAgent agent = new AgentClient(authSock);
for (KeyPair key : keys) {
agent.addIdentity(key, "");
}
agent.close();
}
if (authSock != null) {
SshAgent agent = new AgentClient(authSock);
hasKeys = agent.getIdentities().size() > 0;
}
client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
*/
ClientSession session = client.connect(host, port).await().getSession();
int ret = ClientSession.WAIT_AUTH;
while ((ret & ClientSession.WAIT_AUTH) != 0) {
if (hasKeys) {
session.authAgent(login);
ret = session.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0);
} else {
System.out.print("Password:");
BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
String password = r.readLine();
session.authPassword(login, password);
ret = session.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0);
}
}
if ((ret & ClientSession.CLOSED) != 0) {
System.err.println("error");
System.exit(-1);
}
ClientChannel channel;
if (command == null) {
channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
((ChannelShell) channel).setAgentForwarding(agentForward);
channel.setIn(new NoCloseInputStream(System.in));
} else {
channel = session.createChannel(ClientChannel.CHANNEL_EXEC);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer w = new OutputStreamWriter(baos);
for (String cmd : command) {
w.append(cmd).append(" ");
}
w.append("\n");
w.close();
channel.setIn(new ByteArrayInputStream(baos.toByteArray()));
}
channel.setOut(new NoCloseOutputStream(System.out));
channel.setErr(new NoCloseOutputStream(System.err));
channel.open().await();
channel.waitFor(ClientChannel.CLOSED, 0);
session.close(false);
} finally {
client.stop();
}
}
}
public static void main(String[] args) throws IOException, InterruptedException
{
SshClient client = SshClient.setUpDefaultClient();
client.start();
final ClientSession session = client.connect("bob", "bob.mynetwork.com", 22).await().getSession();
int authState = ClientSession.WAIT_AUTH;
while ((authState & ClientSession.WAIT_AUTH) != 0) {
session.addPasswordIdentity("bobspassword");
System.out.println("authenticating...");
final AuthFuture authFuture = session.auth();
authFuture.addListener(new SshFutureListener<AuthFuture>()
{
#Override
public void operationComplete(AuthFuture arg0)
{
System.out.println("Authentication completed with " + ( arg0.isSuccess() ? "success" : "failure"));
}
});
authState = session.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0);
}
if ((authState & ClientSession.CLOSED) != 0) {
System.err.println("error");
System.exit(-1);
}
final ClientChannel channel = session.createShellChannel();
channel.setOut(new NoCloseOutputStream(System.out));
channel.setErr(new NoCloseOutputStream(System.err));
channel.open();
executeCommand(channel, "pwd\n");
executeCommand(channel, "ll\n");
channel.waitFor(ClientChannel.CLOSED, 0);
session.close(false);
client.stop();
}
private static void executeCommand(final ClientChannel channel, final String command) throws IOException
{
final InputStream commandInput = new ByteArrayInputStream(command.getBytes());
channel.setIn(new NoCloseInputStream(commandInput));
}
Having tracked development regarding Java support for SSH for many years now, I strongly recommend against using any Java SSH implementation. Noone cares to develop them any further. None of them has proper SSH-Agent support. And SSH without SSH-Agent, especially when running code on top of it, makes those implementations rather useless - unless you are willing to put unencrypted keys or passwords everywhere.
The best solution IMHO is to write a wrapper around the rock-solid OpenSSH implementations.

Categories

Resources