SSH Over Java

Recently I’ve had the need from java to run a job on a remote server that can take a variable amount of time (anywhere from a few seconds to a few hours) and needed to detect when it is complete and get a file it generated when completed. This was a little challanging at first, as I had never used ssh from java, but I knew a little searching would yield results.

After a little searching, I found Java Secure Channel (JSch), a handy library with a BSD license and used in Ant and eclipse. The only sad thing is that there is zero documentation outside of a few examples, and my knowledge of streams (specifically PipedInputStream) was sorely behind as I hadn’t used them directly, since, well, college.

A little confusion abounded for awhile … how the heck am I supposed to detect the end of data coming over a pipe!? I mean, you can detect when it closes, but in my case I want to stay connected and issue more commands and close when I please, which means I cannot simply detect when the pipe is closed.

I struggled for a bit, then decided … I’ll just append ” && echo ” and read until I see that token, escape, remove the token, and return the data. So I wrote a small and dirty SshClient class that would allow me to send and recieve responses. The send method simply added the appropriate marker and saves the command so that it knows to strip it from the response:

public void send(String command) throws IOException {
		command += " && echo \"" + TERMINATOR + "\"\n";
		toServer.write(command.getBytes());
		lastCommand = new String(command);
	}

Getting the server response was a bit tricky. Since the command shows up when reading from the server, I need to detect the 2nd time it shows up and break out of the loop on that. After a little time I hacked up the following:

public String getServerResponse() throws IOException, InterruptedException {
		StringBuilder builder = new StringBuilder();
		int count = 0;
		String line = "";
		for (int i = 0; true; i++) {

			line = fromServer.readLine();
			builder.append(line).append("\n");
			if (line.contains(TERMINATOR) && (++count > 1)) {

				break;
			}

		}
		String result = builder.toString();

		int beginIndex = result.indexOf(TERMINATOR+"\"") + ((TERMINATOR+"\"").length());
		result = result.substring(beginIndex);
		return result.replaceAll(escape(TERMINATOR), "").trim();
	}

This makes so many assumptions, but I guess that as I keep working on it I can find a better way. Anyway, below is the final result of the SshClient class I came up with:

package jamesstuff.ssh;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.regex.Pattern;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

public class SshClient {
	BufferedReader fromServer;
	Pattern alphaNumeric = Pattern.compile("([^a-zA-z0-9])");
	OutputStream toServer;
	String lastCommand = "";
	Channel channel;

	private static final String TERMINATOR = "zDonez";

	public void connect(String username, String password, String host, int port)
			throws JSchException, IOException {
		JSch shell = new JSch();
		Session session = shell.getSession(username, host, port);
		MyUserInfo ui = new MyUserInfo();
		ui.setPassword(password);
		session.setUserInfo(ui);
		session.connect();

		channel = session.openChannel("shell");
		fromServer = new BufferedReader(new InputStreamReader(channel.getInputStream()));
		toServer = channel.getOutputStream();
		channel.connect();
		if(isConnected()){
			send("echo \"\"");
		}
	}

	public boolean isConnected() {
		// TODO Auto-generated method stub
		return (channel != null && channel.isConnected());
	}

	public void disconnect() {
		if (isConnected()) {
			channel.disconnect();
		}

	}

	public void send(String command) throws IOException {
		command += "; echo \"" + TERMINATOR + "\"\n";
		toServer.write(command.getBytes());
		lastCommand = new String(command);
	}

	public String getServerResponse() throws IOException, InterruptedException {
		StringBuilder builder = new StringBuilder();
		int count = 0;
		String line = "";
		for (int i = 0; true; i++) {

			line = fromServer.readLine();
			builder.append(line).append("\n");
			if (line.contains(TERMINATOR) && (++count > 1)) {

				break;
			}

		}
		String result = builder.toString();

		int beginIndex = result.indexOf(TERMINATOR+"\"") + ((TERMINATOR+"\"").length());
		result = result.substring(beginIndex);
		return result.replaceAll(escape(TERMINATOR), "").trim();
	}
	private String escape(String subjectString){
		return alphaNumeric.matcher(subjectString).replaceAll("\\\\$1");
	}
	private static class MyUserInfo implements UserInfo {
		private String password;

		public void setPassword(String password) {
			this.password = password;

		}

		public String getPassphrase() {
			// TODO Auto-generated method stub
			return null;
		}

		public String getPassword() {
			// TODO Auto-generated method stub
			return password;
		}

		public boolean promptPassword(String arg0) {
			// TODO Auto-generated method stub
			return true;
		}

		public boolean promptPassphrase(String arg0) {
			// TODO Auto-generated method stub
			return true;
		}

		public boolean promptYesNo(String arg0) {
			// TODO Auto-generated method stub
			return true;
		}

		public void showMessage(String arg0) {
			// TODO Auto-generated method stub
			System.out.println(arg0);
		}

	}

}
You can leave a response, or trackback from your own site.

Facebook comments:

28 Responses to “SSH Over Java”

  1. Eileen says:

    thanks for posting this code. the echo is a great idea.

  2. Adedib says:

    I don’t really understand where you will call the getServerResponse method ?

  3. Abhik says:

    Ah! At last an example which demystifies the JSch Shell IO streams! I have been trying to crack this one for the last two days as I have to do something very similar to what you are doing!

    Thanks very much James!

  4. Blacktek says:

    This script doesn’t execute the commands on the remote server. At least not when I’m testing it with something like this:


    ssh.connect(“usr”, “pwd”, “host”, 22);
    ssh.sendSimple(“myservice start”);
    System.out.println(ssh.getServerResponse());
    ssh.disconnect();
    System.exit(0);

    I’ve also used the Exec.java example in jsch and it works otherwise, but then the scripts won’t see the needed system variables and I don’t know how to convert it to using shell channel instead of exec…

  5. Rai says:

    Thanks for the example! Curl does not support an SSH shell interface. I like your method for handling the response.
    Thanks again :)

  6. dip says:

    What is the TERMILATOR doing?? Is it the command prompt?
    I could not get the command prompt in “getServerResponse” method.

    Can you plz let me know how to do that

  7. James Carr says:

    @dip TERMINATOR is simply a constant that defines the terminating string in the server output,used to indicate that output is done.

  8. dip says:

    Thanks James for your kind reply.
    I am a new to Java and trying to implement SSH using your example.

    I am using your code like—-
    con.connect(“user”, “passwd”, “server”, 22);
    System.out.println(con.getServerResponse());
    if (con.isConnected()) {
    con.send(“ls -l”);
    }
    System.out.println(con.getServerResponse());

    But I am not receiving any response for the cmd “ls -l”
    Plz let me know if my understanding is correct.

  9. dip says:

    I have put some traces in
    line no 68. if (line.contains(TERMINATOR) && (++count > 1)) {

    but it never executes this trace

  10. james says:

    I think you need

    toServer.flush()

    after:

    toServer.write(command.getBytes());

    in your send method.

    Also, not sure what the i counter in:

    for (int i = 0; true; i++)

    in your getServerResponse method is for?

    Otherwise, your code was very helpful. Thank you. james

  11. James Adams says:

    I think I have this working as advertised except for the fact that I’m not sure what to use as my TERMINATOR value. For example I am trying to execute a simple ls command — what is the terminator in that case?

  12. James Adams says:

    I am not getting the expected output from an ls command executed using the SshClient code above.

    My test code looks like this:

    SshClient sshClient = new SshClient();
    sshClient.connect(“username”, “passwd”, “host.com”, 22);
    if (sshClient.isConnected()) {
    sshClient.send(“ls -l”);
    System.out.println(“RESPONSE: ” +
    sshClient.getServerResponse());
    }

    The output I am seeing looks like this:

    RESPONSE: ls -l; echo “”

    So it appears that I am getting the original command that I sent as the response, instead of the actual output of the ls command.

    Can anyone suggest what’s going wrong? Thanks in advance for your help!

    –James

  13. Senthil says:

    Thanks for the nice working code. As someone posted already I too need to add toServer.flush() to make it work in my case.

  14. Khoa says:

    When I refactored your class like this, It’s work. The key is use Piped streams, thanks for your ideas:

    public class SshClient {

    public static void main(String[] args) throws JSchException, IOException, InterruptedException {
    SshClient client = new SshClient();
    client.connect(“dakhoa”, “1qaz@WSX”, “zw3-easvtm-01″, 22);

    client.send(“date /t /r”);
    System.out.println(client.getServerResponse());

    // client.send(“time /t”);
    // System.out.println(client.getServerResponse());

    client.disconnect();
    }

    PipedInputStream fromServer;
    Pattern alphaNumeric = Pattern.compile(“([^a-zA-z0-9])”);
    PipedOutputStream toServer;
    String lastCommand = “”;
    Channel channel;
    Session session;
    private static final String TERMINATOR = “zDonez”;

    public void connect(String username, String password, String host, int port)
    throws JSchException, IOException, InterruptedException {
    JSch shell = new JSch();
    session = shell.getSession(username, host, port);
    MyUserInfo ui = new MyUserInfo();
    ui.setPassword(password);
    session.setUserInfo(ui);
    session.connect();

    channel = session.openChannel(“shell”);

    PipedOutputStream po = new PipedOutputStream();
    fromServer = new PipedInputStream(po);
    channel.setOutputStream(po);

    toServer = new PipedOutputStream();
    PipedInputStream pi = new PipedInputStream(toServer);
    channel.setInputStream(pi);

    channel.connect();

    Thread.sleep(100);
    getServerResponse();
    }

    public boolean isConnected() {
    // TODO Auto-generated method stub
    return (channel != null && channel.isConnected());
    }

    public void disconnect() {
    if (isConnected()) {
    channel.disconnect();
    session.disconnect();
    }
    }

    public void send(String command) throws IOException, InterruptedException {
    command += “; echo \”" + TERMINATOR + “\” \n”;
    toServer.write(command.getBytes());
    Thread.sleep(100);
    lastCommand = new String(command);
    }

    public String getServerResponse() throws IOException, InterruptedException {
    StringBuffer builder = new StringBuffer();
    int count = 0;
    String line = “”;

    BufferedReader reader = new BufferedReader(new InputStreamReader(fromServer));
    for (int i = 0; true; i++) {

    line = reader.readLine();
    builder.append(line).append(“\n”);
    if (line.length() == 0) { //|| line.indexOf(TERMINATOR) != -1 && (++count > 1)) {

    break;
    }

    }
    String result = builder.toString();

    int beginIndex = result.indexOf(TERMINATOR + “\”") + ((TERMINATOR + “\”").length());
    result = result.substring(beginIndex);
    return result.replaceAll(escape(TERMINATOR), “”).trim();
    }

    private String escape(String subjectString) {
    return alphaNumeric.matcher(subjectString).replaceAll(“\\\\$1″);
    }

    private static class MyUserInfo implements UserInfo {

    private String password;

    public void setPassword(String password) {
    this.password = password;

    }

    public String getPassphrase() {
    // TODO Auto-generated method stub
    return null;
    }

    public String getPassword() {
    // TODO Auto-generated method stub
    return password;
    }

    public boolean promptPassword(String arg0) {
    // TODO Auto-generated method stub
    return true;
    }

    public boolean promptPassphrase(String arg0) {
    // TODO Auto-generated method stub
    return true;
    }

    public boolean promptYesNo(String arg0) {
    // TODO Auto-generated method stub
    return true;
    }

    public void showMessage(String arg0) {
    // TODO Auto-generated method stub
    System.out.println(arg0);
    }
    }
    }

  15. James Hunt says:

    With the piped version, there appears to be a buffer size cap of 1024 … is there anyway to get around it? I am trying to use this on a Cisco device and the results from a \show version\ exceeds 1024.

  16. David Alves says:

    Just FYI I found a simpler and more reliable way of sending a command and receiving a response:
    (presumes the session exists and is open)
    public String execute(String command) throws IOException, JSchException {
    ChannelExec channel = (ChannelExec) session.openChannel(“exec”);
    channel.setCommand(command);
    channel.connect();
    BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
    StringBuilder responseBuilder = new StringBuilder();
    while (!channel.isClosed() && !channel.isEOF()) {
    String line = reader.readLine();
    if (line != null) {
    responseBuilder.append(line).append(“\n”);
    }
    }
    return responseBuilder.toString();
    }

  17. David Alves says:

    As I can’t edit the previous comment…

    I forgot to thank the author for pointing me in the right direction.
    JSch is cool but it really lacks documentation.
    BTW the fact that it always opens a channel does not seem to affect performance (in my tests)

  18. Simon says:

    I tried David Alves’s version. It works. Thanks.

  19. Andy says:

    while trying to delete a remote file using “rm” the exception SftpException is thrown with the message: 4:

    any ideas please???

  20. James Carr says:

    Sorry guys, I really can’t help with any problems you might have with this library. This post was written over 4 years ago and the last time I even used this library was exactly that… 4 years ago.

    Perhaps contacting the author or using any mailing list for the library if it’s available can help you with any problems you have. The ant guys use this for one of their bundled ant tasks, so you may get answers from their mailing lists as well.

    Regards,
    James

  21. Morz Design says:

    I just modify the code above and it works. Here is the modification :

    SshClient.java

    package com.rozi;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.util.regex.Pattern;

    import com.jcraft.jsch.Channel;
    import com.jcraft.jsch.JSch;
    import com.jcraft.jsch.JSchException;
    import com.jcraft.jsch.Session;
    import com.jcraft.jsch.UserInfo;
    import java.io.*;
    import com.jcraft.jsch.*;

    public class SshClient {
    private JSch shell;
    private Session session;
    private Channel channel;
    private static OutputStream out;
    private static InputStream in;

    public void connect(String username, String password, String host, int port)
    throws JSchException, IOException, InterruptedException {
    shell = new JSch();
    session = shell.getSession(username, host, port);
    session.setPassword(password);
    session.setConfig(“StrictHostKeyChecking”, “no”);
    session.connect();

    channel=session.openChannel(“shell”);
    channel.setInputStream(null);
    channel.setOutputStream(null);

    in=channel.getInputStream();
    out =channel.getOutputStream();
    ((ChannelShell)channel).setPtyType(“vt102″);
    channel.connect();
    }

    public String send(String command) throws IOException, InterruptedException {
    byte[] tmp=new byte[1024];

    out.write((command+”;echo \”z4a3ce4f3317Z\”").getBytes());
    out.write((“\n”).getBytes());
    out.flush();

    String result = “”;
    while(true){
    while(in.available()>0){
    int i=in.read(tmp, 0, 1024);
    if(i<0)
    break;

    result = result + (new String(tmp, 0, i));
    }
    if(result.indexOf(“z4a3ce4f3317Z”) != -1){
    break;
    }
    try{Thread.sleep(100);}catch(Exception ee){}
    }
    return result;
    }

    public boolean isConnected() {
    return (channel != null && channel.isConnected());
    }

    public void disconnect() {
    if (isConnected()) {
    channel.disconnect();
    session.disconnect();
    }
    }
    }

    —————————————————
    Here is the test file. Test.java

    import com.rozi.SshClient;

    public class Test {
    public static void main(String[] args) {
    try{
    SshClient client = new SshClient();
    client.connect(“avsg”, “xbbnm”, “192.168.8.302″, 22);
    client.send(“cd /home/vik/mori”);
    System.out.println(client.send(“ls -lar”));
    System.out.println(client.send(“touch kl”));
    System.out.println(client.send(“chmod 754 kl”));
    client.disconnect();
    }catch(Exception e){
    System.out.println(e);
    }
    }
    }

    • Mani says:

      Thanks a lot for the code !! Morz Design !!
      I used the code provided by ” Morz Design ” and in the output I get the following
      $ pwd;echo”z4a3ce4f3317Z”
      ksh: echoz4a3ce4f3317Z: not found.
      $
      bs.sh abc;echo”z4a3ce4f3317Z

      In main method I have provided the commands like pwd ,bs.sh abc

      I am unable to print the result of bs.sh abc The Build is successfull after “bs.sh abc;echo”z4a3ce4f3317Z”

      I need to know what is “z4a3ce4f3317Z” and when I comment this line “out.write((command+”;echo \”z4a3ce4f3317Z\””).getBytes());” and “if(result.indexOf(“z4a3ce4f3317Z”) != -1){
      break;
      }”
      in send method in class SshClient its in run mode for a long time I need to manually terminate it !!
      Kindly help me in understanding whats the problem.
      I need to print the full ouptput of this shell script “bs.sh abc” [where abc is uiinput parameter]

      Awaiting for reply through my mail or here !!

      Thanks in advance !! !!

  22. Mani says:

    I think the ouput of bs.sh abc is terminated by “;echo”z4a3ce4f3317Z”
    I am unable to grasp what is “;echo”z4a3ce4f3317Z”
    Kindly enlighten me !!

    And sorry for double posting I am unable to edit my post !!

  23. Mani says:

    Small Update what I found and what I need :
    1.”z4a3ce4f3317Z”is a delimiter to detect the end of the data ! Sorry I read the post in a Haste [later on read calmly].I made a mistake by ommiting a whitespace in “”;echo”z4a3ce4f3317Z” edited like this : “out.write((command + “; echo \”z4a3ce4f3317Z\”").getBytes());”

    2.
    The Below Block works fine if the output of the Command is small
    try{Thread.sleep(100);}catch(Exception ee){}
    }
    When I tried this
    try{Thread.sleep(60000);}catch(Exception ee){}
    }

    I need to wait for a minute [because I have specified Thread.sleep(60000); ]to get the full output of the command bs.sh abc along with the delimieter “z4a3ce4f3317Z”

    The Thread.sleep(60000) . makes my module to sleep for 1Min and later on the entire output is displayed.I need to display the output line by line .I tried all the possiblities of printing the output but still it prints after waiting for 1Minute.

    3.
    Is there any way to prevent the display of the escape string “z4a3ce4f3317Z” it gets displayed like this “bs.sh abc; echo “z4a3ce4f3317Z” “

  24. Pat says:

    This is more for people that stumble across this website in hopes of trying to get this to work. Anyways, you might want to change the following lines in Morz Design’s code:

    out.write((command+”;echo \”z4a3ce4f3317Z\””).getBytes());
    if(result.indexOf(“z4a3ce4f3317Z”) != -1){

    to this:

    out.write((command+”;echo $((1+1))\”z4a3ce4f3317Z\””).getBytes());
    if(result.indexOf(“2z4a3ce4f3317Z”) != -1){

    That way, you can be sure that you don’t catch your command being echoed in the output, and return after sleeping 100 seconds.

  25. Jai Shankar says:

    I tried to change the code little bit and i was able to get results.

    change this:
    out.write((command+”;echo $((1+1))\”z4a3ce4f3317Z\””).getBytes());
    if(result.indexOf(“2z4a3ce4f3317Z”) != -1){

    To this:
    out.write((command).getBytes());
    if(result.indexOf(command) != -1){

    and also increase the Thread.sleep(500);

  26. krishna Ravuri says:

    Great work by you people…..Its helps me a lot….Cheers..

  27. Debashish Bhattacharjee says:

    Thanks for the post. I was able to make a utility in my organisation based on the concepts used in the code. Thank you. :)

Leave a Reply

Subscribe to RSS Feed Follow me on Twitter!