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

	}

}
  • Eileen

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

  • Adedib

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

  • Abhik

    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!

  • Blacktek

    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…

    • sathish

      Hi,

      I need help in automation of ssh. I am trying to send commands automatically so that it will fire commands and give me the output of it. As we all know that, ssh is for getting into the node and fire some commands and give output for that command.

      I am using ssh factory jar file and i am trying to send commands automatically.
      kindly check for the below code which i have tried:

      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;

      import com.jscape.inet.ssh.Ssh;
      import com.jscape.inet.ssh.SshAdapter;
      import com.jscape.inet.ssh.SshConnectedEvent;
      import com.jscape.inet.ssh.SshDataReceivedEvent;
      import com.jscape.inet.ssh.SshDisconnectedEvent;
      import com.jscape.inet.ssh.SshException;
      import com.jscape.inet.ssh.SshScript;
      import com.jscape.inet.ssh.SshTask;
      import com.jscape.inet.ssh.SshTaskEndEvent;
      import com.jscape.inet.ssh.SshTaskStartEvent;
      import com.jscape.inet.ssh.SshTaskTimeoutException;

      import com.jscape.inet.ssh.connection.channels.SessionCli ent;
      import com.jscape.inet.ssh.util.SshParameters;

      public class SshScriptTutorial extends SshAdapter {
      public SshScriptTutorial() {}

      public void executeSshScript(String hostname, String username, String password)
      throws SshException, IOException, InterruptedException
      {
      // assumes that SSH shell prompt is “$” .. this MUST match exactly
      String shellPrompt = “>”;

      // initialize and create new Ssh instance
      SshParameters sshParams = new SshParameters(hostname,username,password);

      Ssh ssh = new Ssh(sshParams);

      // register this class to receive Ssh events
      ssh.addSshListener(this);

      // create new script object and bind to the given ssh object
      SshScript script = new SshScript(ssh);

      // add tasks to script object
      script.addTask(new SshTask(shellPrompt, “show host”, shellPrompt));
      script.addTask(new SshTask(shellPrompt, “ssh ssgpun”, shellPrompt));

      // while sending password, it is not able to fire this.
      script.addTask(new SshTask(shellPrompt, “password”, “:”)); // trying to send password to the server.

      // connect to SSH server and execute script
      ssh.connect();

      // wait until last task is complete
      while(!script.isComplete()) {
      try {
      Thread.sleep(500);
      } catch(Exception e) {}
      }

      // disconnect from server
      // ssh.disconnect();
      }

      public void connected(SshConnectedEvent event) {
      System.out.println(“Connected to host: ” + event.getHost());
      }

      public void disconnected(SshDisconnectedEvent event) {
      System.out.println(“Disconnected from host: ” + event.getHost());
      }

      public void dataReceived(SshDataReceivedEvent event) {
      System.out.print(event.getData());
      }

      public static void main(String[] args) {
      try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
      // System.out.print(“Enter SSH hostname: “);
      // String hostname = reader.readLine();
      // System.out.print(“Enter SSH username: “);
      // String username = reader.readLine();
      String hostname = “hostname”; // ip of server
      String username = “username”;

      System.out.print(“Enter SSH password: “);
      String password = reader.readLine();

      SshScriptTutorial tutorial = new SshScriptTutorial();
      tutorial.executeSshScript(hostname, username, password);

      // System.out.print(“Hi”);
      String cus_pass = reader.readLine();

      } catch(Exception e) {
      e.printStackTrace();
      }
      }
      }

      Output log appears as,

      Enter SSH password: 4732885288
      Connected to host: 150.236.14.11
      egw-pnq > show host
      Node IP number Reverse SSH tunnel
      __________________________________________________ _____________________
      ssgpun 192.168.181.2 yes

      egw-pnq > ssh ssgpun
      Connecting to ssgpun as user
      Password:

      Till this i am able to send commands automatically. when i am trying to send password, it is not able to fire it.

      I feel that this might be due to setting shell prompt or delay problem. I am not sure. I dont know how to solve it.

      If any1 knows, kindly help me out in this step.

      email id: mg.sathish2@gmail.com

  • Rai

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

  • dip

    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

  • http://blog.james-carr.org James Carr

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

  • dip

    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.

  • dip

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

    but it never executes this trace

  • http://openskysolutions.ca james

    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

  • James Adams

    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?

  • James Adams

    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

  • Senthil

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

  • Khoa

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

  • James Hunt

    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.

  • David Alves

    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();
    }

  • David Alves

    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)

  • Simon

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

  • Andy

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

    any ideas please???

  • http://blog.james-carr.org James Carr

    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

  • http://morzdesign.com Morz Design

    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

      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 !! !!

  • Mani

    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 !!

  • Mani

    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” “

  • Pat

    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.

  • Jai Shankar

    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);

  • krishna Ravuri

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

  • Debashish Bhattacharjee

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

  • Ashish Dugar

    Thanks for above code It helped me alot.

    One doubt.. Above code help me in login on the server but after login I have to add myself as superuser.

    I have to execute the /usr/seos/bin/sesu command after this command is hit on Unix it will ask for password.

    what changes are required for this.

    • Shinto Philip

      For that u need to read each character by character.. This is the way SSH clients like PuTTy works…