Remote SSH: Using JSCH with Expect4j

Now-a-days, we can see that whole world is moving around Clouds and virtualization. More and more applications are building for managing datacentre servers and other stuff. I have been part of one of such a module. I developed one module for monitoring and managing Linux servers remotely. We used JCraft’s Jsch with Google’s Expect4j for the same. Let’s get some idea about those API in brief.

JSCH

As the website suggests, it is pure implementation of SSH2. It is very easy to use and integrate into your program. It is a fact that it is not well documented. You can read more about JSch from its website.

Expect4j

Expect is the kitchen sink of IO control. It supports control of processes and sockets, and a complex method of match multiple patterns at the same time. This is what Google code has to say about Expect4j. When you executes commands on remote machines one after other, your program needs to know when to execute next command. It’s like send your command and wait for execution of the same. Or we can say that wait for the command prompt. Expect4j does similar stuff(as far as I know). It also provides closures for getting complete output log of executed commands. I don’t know if there is some other use of closures.

Now, let’s start with an example. First open up SSH connection on remote machine.

 JSch jsch = new JSch();
Session session = jsch.getSession(username, hostname, port);
session.setPassword(password);

Hashtable<String,String> config = new Hashtable<String,String>();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect(60000);
ChannelShell channel = (ChannelShell) session.openChannel("shell");
Expect4j expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
channel.connect();

You can see that we have opened “shell” channel. That is because we want to execute sequence of commands on linux shell. You can see that Expect4j is initialized from JSCH session.

Now, we will prepare RegX patterns of command prompts of targeted machine. Be careful here because if these are not right then you might end up not executing commands. You can also provide closure provided by Expect4j to get output of your commands. (There might be some other use of it.)

StringBuilder buffer = new StringBuilder();
Closure closure = new Closure() {
			public void run(ExpectState expectState) throws Exception {
				buffer.append(expectState.getBuffer());//string buffer for appending output of executed command
			}
};
String[] linuxPromptRegEx = new String[]{"\\>","#"};
List<Match> lstPattern =  new ArrayList<Match>();
		for (String regexElement : linuxPromptRegEx) {
			try {
				Match mat = new RegExpMatch(regexElement, closure);
				lstPattern.add(mat);
			} catch (MalformedPatternException e) {
				e.printStackTrace();
			} catch(Exception e) {
				e.printStackTrace();
			}
}

You can see that we have defined a closure that appends output of every command executed. We are providing this closure to every command pattern. I have my linux shell prompt working at “/>” and “#”. You can define your prompts as per your linux box.

Now, start executing commands.

		List<String> lstCmds = new ArrayList<String>();
		lstCmds.add("ls");
		lstCmds.add("pwd");
		lstCmds.add("mkdir testdir");

		for(String strCmd : lstCmds) {
			int returnVal = expect.expect(objPattern);
			if (returnVal == -2) {
				expect.send(strCommandPattern);
				expect.send("\r");//enter character
			}
		}
		

We have three commands to execute in a single continuous SSH session.  First it expects one of its prompt patterns to match. If right prompt has encountered then send one command over the shell to execute.  Immediately after the command, send an enter character to execute the command. After that again wait for one of your command prompt to occur and then send other command. So, we can see that it send/wait kind of mechanism.

Now, put all together and here is a SSH client that can execute sequence of command on remote linux box. You will need following libraries in your project to execute this test class.

  • expect4j-1.0.jar
  • jakarta-oro.jar
  • jsch-0.1.44.jar
import org.apache.oro.text.regex.MalformedPatternException;

import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

import expect4j.Closure;
import expect4j.Expect4j;
import expect4j.ExpectState;
import expect4j.matches.Match;
import expect4j.matches.RegExpMatch;

public class SSHClient {

	private static final int COMMAND_EXECUTION_SUCCESS_OPCODE = -2;
	private static String ENTER_CHARACTER = "\r";
	private static final int SSH_PORT = 22;
	private List<String> lstCmds = new ArrayList<String>();
	private static String[] linuxPromptRegEx = new String[]{"\\>","#", "~#"};

	private Expect4j expect = null;
	private StringBuilder buffer = new StringBuilder();
	private String userName;
	private String password;
	private String host;

	/**
	 *
	 * @param host
	 * @param userName
	 * @param password
	 */
	public SSHClient(String host, String userName, String password) {
		this.host = host;
		this.userName = userName;
		this.password = password;
	}
	/**
	 *
	 * @param cmdsToExecute
	 */
	public String execute(List<String> cmdsToExecute) {
		this.lstCmds = cmdsToExecute;

		Closure closure = new Closure() {
			public void run(ExpectState expectState) throws Exception {
				buffer.append(expectState.getBuffer());
			}
		};
		List<Match> lstPattern =  new ArrayList<Match>();
		for (String regexElement : linuxPromptRegEx) {
			try {
				Match mat = new RegExpMatch(regexElement, closure);
				lstPattern.add(mat);
			} catch (MalformedPatternException e) {
				e.printStackTrace();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

		try {
			expect = SSH();
			boolean isSuccess = true;
			for(String strCmd : lstCmds) {
				isSuccess = isSuccess(lstPattern,strCmd);
				if (!isSuccess) {
					isSuccess = isSuccess(lstPattern,strCmd);
				}
			}

			checkResult(expect.expect(lstPattern));
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			closeConnection();
		}
		return buffer.toString();
	}
	/**
	 *
	 * @param objPattern
	 * @param strCommandPattern
	 * @return
	 */
	private boolean isSuccess(List<Match> objPattern,String strCommandPattern) {
		try {
			boolean isFailed = checkResult(expect.expect(objPattern));

			if (!isFailed) {
				expect.send(strCommandPattern);
				expect.send(ENTER_CHARACTER);
				return true;
			}
			return false;
		} catch (MalformedPatternException ex) {
			ex.printStackTrace();
			return false;
		} catch (Exception ex) {
			ex.printStackTrace();
			return false;
		}
	}
	/**
	 *
	 * @param hostname
	 * @param username
	 * @param password
	 * @param port
	 * @return
	 * @throws Exception
	 */
	private Expect4j SSH() throws Exception {
		JSch jsch = new JSch();
		Session session = jsch.getSession(userName, host, SSH_PORT);
		if (password != null) {
			session.setPassword(password);
		}
		Hashtable<String,String> config = new Hashtable<String,String>();
		config.put("StrictHostKeyChecking", "no");
		session.setConfig(config);
		session.connect(60000);
		ChannelShell channel = (ChannelShell) session.openChannel("shell");
		Expect4j expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
		channel.connect();
		return expect;
	}
	/**
	 *
	 * @param intRetVal
	 * @return
	 */
	private boolean checkResult(int intRetVal) {
		if (intRetVal == COMMAND_EXECUTION_SUCCESS_OPCODE) {
			return true;
		}
		return false;
	}
	/**
	 *
	 */
	private void closeConnection() {
		if (expect!=null) {
			expect.close();
		}
	}
	/**
	 *
	 * @param args
	 */
	public static void main(String[] args) {
		SSHClient ssh = new SSHClient("linux_host", "root", "password");
		List<String> cmdsToExecute = new ArrayList<String>();
		cmdsToExecute.add("ls");
		cmdsToExecute.add("pwd");
		cmdsToExecute.add("mkdir testdir");
		String outputLog = ssh.execute(cmdsToExecute);
		System.out.println(outputLog);
	}
}

If you find any difficulties to execute it, just play around execute command loop and command prompts RegX patterns.


					
Advertisements
  1. Very useful example for people who work with networks.
    The ssh protocol is widely used for remote configuration.
    Thank you.

    • karna
    • September 27th, 2011

    Hi,

    Thanks for the excellent briefing of Expect4j. I tried using this, I can run the commands but unable to log the command results. Stringbuffer is returning only last login information. String buffer is a class variable.. can you please advice if I am doing something wrong ?

    • waruna
    • October 10th, 2011

    A life saver, need to run a shell command pro-grammatically from a SFTP client

    • Yogesh
    • December 14th, 2011

    first of all thanx for ur post
    can u tell me how to read and identify error response of executed shell command

    • I think, there is no way to identify output as exactly as an error. Because , error of a command execution is string message. So, you have to parse the response and based on some words in the message, decide whether it is an error or not.

        • Deepti
        • February 29th, 2012

        Hi NiKUNJ,
        I am trying to login to remote machine through ssh, thats working.
        After logging in i have to do sudo su – user, this will expect password, which is same as used for connecting to remote machine.
        how can we send password?
        buffer output is
        “Last login: Wed Feb 29 02:10:42 2012 from xin009oddrupere.ad.qintra.com

        $ sudo su – qctwixopQcontrol1234
        [sudo] password for drupere: ”
        code is attched
        import java.util.ArrayList;
        import java.util.Collection;
        import java.util.Hashtable;
        import java.util.List;

        import org.apache.oro.text.regex.MalformedPatternException;

        import com.jcraft.jsch.ChannelShell;
        import com.jcraft.jsch.JSch;
        import com.jcraft.jsch.Session;

        import expect4j.Closure;
        import expect4j.Expect4j;
        import expect4j.ExpectState;
        import expect4j.matches.EofMatch;
        import expect4j.matches.Match;
        import expect4j.matches.RegExpMatch;
        import expect4j.matches.TimeoutMatch;

        public class SSHClient {

        private static final int COMMAND_EXECUTION_SUCCESS_OPCODE = -2;
        private static String ENTER_CHARACTER = “\r”;
        private static final int SSH_PORT = 22;
        private List lstCmds = new ArrayList();
        private static String[] linuxPromptRegEx = new String[]{“$”,”[sudo] password for drupere:”};

        private Expect4j expect = null;
        private StringBuilder buffer = new StringBuilder();
        private String userName;
        private String password;
        private String host;

        /**
        *
        * @param host
        * @param userName
        * @param password
        */
        public SSHClient(String host, String userName, String password) {
        this.host = host;
        this.userName = userName;
        this.password = password;
        }
        /**
        *
        * @param cmdsToExecute
        */
        public String execute(List cmdsToExecute) {
        this.lstCmds = cmdsToExecute;

        Closure closure = new Closure() {
        public void run(ExpectState expectState) throws Exception {
        buffer.append(expectState.getBuffer());
        expectState.exp_continue();
        }
        };
        List lstPattern = new ArrayList();
        for (String regexElement : linuxPromptRegEx) {
        try {
        Match mat = new RegExpMatch(regexElement, closure);
        lstPattern.add(mat);
        } catch (MalformedPatternException e) {
        e.printStackTrace();
        } catch(Exception e) {
        e.printStackTrace();
        }
        }

        try {
        expect = SSH();
        boolean isSuccess = true;
        for(String strCmd : lstCmds) {
        isSuccess = isSuccess(lstPattern,strCmd);
        if (!isSuccess) {
        isSuccess = isSuccess(lstPattern,strCmd);
        }
        }

        checkResult(expect.expect(lstPattern));
        } catch (Exception ex) {
        ex.printStackTrace();
        } finally {
        closeConnection();
        }
        return buffer.toString();
        }
        /**
        *
        * @param objPattern
        * @param strCommandPattern
        * @return
        */
        private boolean isSuccess(List objPattern,String strCommandPattern) {
        try {
        boolean isFailed = checkResult(expect.expect(objPattern));
        for(String strCmd : lstCmds) {

        int returnVal = expect.expect(objPattern);
        if (returnVal == -2) {
        expect.send(strCommandPattern);
        expect.send(password);
        expect.send(“\r”);//enter character
        return true;
        }
        }

        /*if (!isFailed) {
        expect.send(strCommandPattern);
        expect.send(ENTER_CHARACTER);
        expect.send(password);
        return true;
        }*/
        return false;
        } catch (MalformedPatternException ex) {
        ex.printStackTrace();
        return false;
        } catch (Exception ex) {
        ex.printStackTrace();
        return false;
        }
        }
        /**
        *
        * @param hostname
        * @param username
        * @param password
        * @param port
        * @return
        * @throws Exception
        */
        private Expect4j SSH() throws Exception {
        JSch jsch = new JSch();
        Session session = jsch.getSession(userName, host, SSH_PORT);
        if (password != null) {
        session.setPassword(password);
        }
        Hashtable config = new Hashtable();
        config.put(“StrictHostKeyChecking”, “no”);
        session.setConfig(config);
        session.connect(60000);
        ChannelShell channel = (ChannelShell) session.openChannel(“shell”);
        Expect4j expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
        channel.connect();
        return expect;
        }
        /**
        *
        * @param intRetVal
        * @return
        */
        private boolean checkResult(int intRetVal) {
        if (intRetVal == COMMAND_EXECUTION_SUCCESS_OPCODE) {
        return true;
        }
        return false;
        }
        /**
        *
        */
        private void closeConnection() {
        if (expect!=null) {
        expect.close();
        }
        }
        /**
        *
        * @param args
        */
        public static void main(String[] args) {
        SSHClient ssh = new SSHClient(“qtdenvmdt074.dev.qintra.com”, “drupere”, “Qcontl1234”);
        List cmdsToExecute = new ArrayList();
        cmdsToExecute.add(“sudo su – qctwixop”);
        //cmdsToExecute.add(“ls”);
        String outputLog = ssh.execute(cmdsToExecute);
        System.out.println(outputLog);
        //boolean val = ssh.executeCommands();
        }
        }

    • Lan Ledin
    • January 4th, 2012

    Thanks for your post. It is really a good one. Very much appreciated.

    • praveenk123praveen
    • January 10th, 2012

    very nice post . thnks a lot. can you please advice me , I want to run command “show time ” for router but above code is not giving result.

    • kr
    • February 14th, 2012

    Does Expect4j make blocking calls when it is waiting for a reply? ANy idea where I can find more information about it

    • Shiva
    • May 2nd, 2012

    Thx. nIKUNJ. It really helps me a lot 🙂

    • Anthony
    • June 23rd, 2012

    Is there a way to use a single set of expect.expect and expect.send, then get the response BEFORE launching into the rest of this code? I need to send 6 digits to my server, get a response to find out where my server has redirected me, adjust my remaining expect.expects and .sends according, then proceed. Thanks so much for publishing this!

    • alexey1gavrilov
    • January 10th, 2014

    Self-promoting: give a try to yet another ‘Expect for Java’ implementation: https://github.com/Alexey1Gavrilov/expectit

    It doesn’t depend on third-party libraries, available on the Maven central and Apache licensed.

    Here is a code example of interacting with a public ssh service capturing the server output using regular expressions.

    JSch jSch = new JSch();
    Session session = jSch.getSession(“new”, “sdf.org”);
    Properties config = new Properties();
    config.put(“StrictHostKeyChecking”, “no”);
    session.setConfig(config);
    session.connect();
    Channel channel = session.openChannel(“shell”);
    // jsch is ready
    Expect expect = new ExpectBuilder()
    .withOutput(channel.getOutputStream())
    .withInputs(channel.getInputStream(), channel.getExtInputStream())
    // trace all the I/O activity to the standard output stream
    .withEchoOutput(new PrintWriter(System.out))
    // remove ANSI color escape sequences and non-printable chars
    .withInputFilter(removeColors(), printableOnly())
    .build();
    channel.connect();
    expect.expect(contains(“[RETURN]”));
    expect.sendLine();
    String ipAddress = expect.expect(regexp(“Trying (.*)\\.\\.\\.”)).group(1);
    System.out.println(“Captured IP: ” + ipAddress);
    session.disconnect();
    expect.close();

    • Prabhu R C
    • February 28th, 2014

    Thank you! It was very helpful 🙂

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: