Search code examples
javaunixsshautomationjsch

JSch Channel.getExitStatus does not return status of command executed in "shell" channel


I'm trying to compare file by JSch – diff command. I want to get the status by Java code to know if the file equals or not i.e. echo $status. How can I do that in java?

This is my class to run SSH commands

public class SSH {

    static Logger logger = LogManager.getLogger("sshLogger");
    static Process p;
    static String s;
    int exitStatus = -1;
    String user;
    String pass;
    String server;
    String command;
    public Channel channel;
    public Session session;
    public InputStream in;

    public SSH(String user, String pass, String server) {
        this.user = user;
        this.pass = pass;
        this.server = server;
        logger.debug("instance: " + user + "@" + server);
    }

    private Session getConnectedSession(String user, String pass, String server) throws JSchException {

        session = new JSch().getSession(user, server, 22);
        java.util.Properties config = new java.util.Properties();
        session.setPassword(pass);
        config.put("StrictHostKeyChecking", "no");
        config.put("PreferredAuthentications","publickey,keyboard-interactive,password");
        session.setConfig(config);

        logger.debug("get connected session at: " + server);
        session.connect();
        logger.debug("connected to session");

        return session;

    }

    public Channel getChannel() {

        // ByteArrayOutputStream byteArrOutSt = new ByteArrayOutputStream();

        try {
            session = getConnectedSession(user, pass, server);
            logger.debug("opening channel at: " + server);
            channel = session.openChannel("shell");
        } catch (JSchException e) {
            logger.error("failed to connect to: "+ user + "@"+ server);
            e.printStackTrace();
        }
        // channel.setOutputStream(baos);
        channel.setOutputStream(System.out);
        channel.setExtOutputStream(System.out, true);
        // channel.setPty(Boolean.FALSE);

        return channel;

    }

    public String commandBuilder(String... commands)  {

        StringBuilder commandString = new StringBuilder();
        for (String command : commands) {
            logger.debug("adding command: " + command);
            commandString.append(command + "\\n");
        }
        logger.debug("adding command: exit");
        commandString.append("exit\\n");
        String myCommand = commandString.toString().replace("\\n", "\n");
        return myCommand;
    }
    
    
    public String executeCommand(String myCommand) {
        channel = getChannel();
        //String finishedCommand = commandBuilder(myCommand);
        logger.debug(myCommand);
    
        StringBuilder outBuff = new StringBuilder();
        String outComm = null;
        channel.setInputStream(new ByteArrayInputStream(myCommand.getBytes(StandardCharsets.UTF_8)));
        try {
            in = channel.getInputStream();
            channel.connect();
            // read from the output
            System.out.println("printing");
            while (true) {
                for (int c; ((c = in.read()) >= 0);) {
                    outBuff.append((char) c);
                }

                if (channel.isClosed()) {
                    if (in.available() > 0)
                        continue;
                    exitStatus = channel.getExitStatus();
                    break;
                }
            }
        } catch (IOException e) {
            logger.error("failed while running or reading output of command: " + myCommand);
        } catch (JSchException e) {
            
            logger.error("failed to connect to server while running command: " + myCommand);
        }
        

        logger.info(outBuff.toString());
        return outBuff.toString();
    }

    
    public void endSession() {

        logger.debug("ending session");
        channel.disconnect();
        session.disconnect();
        logger.debug("disconnected channel and session");

        // print exit status
        logger.debug("Exit status of the execution: " + exitStatus);
        if (exitStatus == 0) {
            logger.debug(" (OK)\n");
        } else {
            logger.debug(" (NOK)\n");
        }
    }
    
    public String runCommand(String... commands) {
        
        // many commands as you want
        
        
        String output = null;
        String myCommands;
        myCommands = commandBuilder(commands);
        try {
            output = executeCommand(myCommands);
            endSession();
        } catch (Exception e) {
            logger.error("failed to run ssh comands: "+ commands);
            e.printStackTrace();
        } 
        return output;
        
    }
    
}

using SSH for diff example:

SSH session = new SSH(user, pass, server);
session.runCommand("diff file1.xml file2.xml");

Solution

  • Do not execute commands in "shell" channel in the first place. The "shell" is a black box with an input and output only. See What is the difference between the 'shell' channel and the 'exec' channel in JSch

    Use the "exec" channel. In the "exec" channel, the Channel.getExitStatus works.

    For a code example, see: How to read JSch command output?


    Obligatory warning: Do not use StrictHostKeyChecking=no to blindly accept all host keys. That is a security flaw. You lose a protection against MITM attacks. For the correct (and secure) approach, see: How to resolve Java UnknownHostKey, while using JSch SFTP library?