Multiple commands using JSch
Asked Answered
B

3

8

My requirement is as follow:
I have to login to Unix box using my credentials and once login, I have to do sudo to different user. Once sudo is successful, I have to invoke shell in nohup. On completion of executions, close channel and session both.

I tried the first step which is connect using sudo command, but I don't know how to invoke shell script after the sudo command.

In the below code I am able to execute sudo command, but after getting sudo access how can I execute a shell in nohup with user masteruser. So that required files created my shell has owner as masteruser.

public class SSHUploader {

    Session session = null;

    public SSHUploader(){

    }

    public void connect(){
    try {

            JSch jsch = new JSch();
            session = jsch.getSession("user", "xxx.xxx.xx.xx", 22);
            session.setPassword("test");
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void executeCommand(String script) throws JSchException, IOException{
        System.out.println("Execute sudo");
        String sudo_pass = "test";
        ChannelExec channel = (ChannelExec) session.openChannel("exec");
        ((ChannelExec) channel).setCommand( script);

        InputStream in = channel.getInputStream();
        OutputStream out = channel.getOutputStream();
        ((ChannelExec) channel).setErrStream(System.err);

        channel.connect();
        out.write((sudo_pass + "\n").getBytes());
        out.flush();

        byte[] tmp = new byte[1024];
        while (true) {
            while (in.available() > 0) {
                int i = in.read(tmp, 0, 1024);
                if (i < 0)
                    break;
                System.out.print(new String(tmp, 0, i));
            }
            if (channel.isClosed()) {
                System.out.println("exit-status: " + channel.getExitStatus());
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (Exception ee) {
                System.out.println(ee);
            }
        }
        channel.disconnect();
        System.out.println("Sudo disconnect");
    }

    public void disconnect(){
        session.disconnect();
    }


    public static void main(String... args) throws JSchException, IOException {

        SSHUploader up = new SSHUploader();
        up.connect();

        up.executeCommand("sudo -u masteruser bash");

        up.disconnect();
    }

}
Bangtail answered 27/6, 2013 at 20:40 Comment(1)
Possible duplicate of How to perform multiple operations with JSchBradbradan
H
20

For executing multiple commands in sequence, you can create a command string like below:

String script ="pbrun su - user; cd /home/scripts;./sample_script.sh”

Execute it and pass this string to your method above.

Habsburg answered 10/10, 2013 at 20:16 Comment(3)
Also, adding "echo Running script_X; ./script_X" seems nice to have with multiple commands.Immensity
Just note that this is *nix shell specific solution - It's neither SSH nor JSch feature - So on other systems (like Windows), a different syntax might be needed. +1Schmitt
I had issues with this type of implementation. instead I created 2 channels instead of 1(here I have 2 commands to run)Cleek
P
2

The post may be old, but I found another easy way that allows you to retrieve the output of each command separately. Note that this code has to be executed once the session has been opened, as shown in the examples (http://www.jcraft.com/jsch/examples/Exec.java.html):

for (String command : commands) {
    ChannelExec channel = (ChannelExec) session.openChannel("exec");
    channel.setInputStream(null);
    channel.setErrStream(System.err);
    channel.setCommand(command);
    channel.connect();
    printOutput(channel);
    channel.disconnect();
}

Where printOutput uses channel.getInputStream() to read the result of the command.

Polyp answered 5/2, 2018 at 11:25 Comment(0)
I
0

Another solution which I find elegant is to use connection from type shell instead of exec. But you will need to wait for the correct prompt to appear after each command like shown in the following example:

public static void main(String[] args) throws Exception{
    java.util.Properties config = new java.util.Properties();
    config.put("StrictHostKeyChecking", "no");
    Session session = null;
    ChannelShell channel = null;
    try {
        JSch jsch = new JSch();
        session = jsch.getSession("username", "192.168.1.1", 22);
        session.setConfig(config);
        session.setPassword("password");
        session.connect();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        channel = (ChannelShell) session.openChannel("shell");
        channel.setOutputStream(outputStream);
        PrintStream stream = new PrintStream(channel.getOutputStream());
        channel.connect();

        stream.println("touch delme.txt");
        stream.flush();
        String response = waitForPrompt(outputStream, "$");
        System.out.println(response);

        stream.println("sudo chown root delme.txt");
        stream.flush();
        response = waitForPrompt(outputStream, ":");
        System.out.println(response);

        stream.println("mysecretrootpassword");
        stream.flush();
        response = waitForPrompt(outputStream, "$");
        System.out.println(response);


        stream.println("ls -la delme.txt");
        stream.flush();
        response = waitForPrompt(outputStream, "$");
        System.out.println(response);

    } finally {
        if (channel != null) {
            channel.disconnect();
        }
        if (session != null) {
            session.disconnect();
        }
    }
}

static public String waitForPrompt(ByteArrayOutputStream outputStream, String prompt) throws Exception {
    int retries = NUMBER_OF_RETRIES;
    for (int x = 1; x < retries; x++) {
        TimeUnit.SECONDS.sleep(1);
        if (outputStream.toString().indexOf(prompt) > 0) {
            String responseString = outputStream.toString();
            outputStream.reset();
            return responseString;
        }
    }
    throw new Exception("Prompt failed to show after specified timeout");
}
Impuissant answered 12/2, 2023 at 16:8 Comment(2)
interesting code, thewaitForPrompt though will be very slow if you have to send 10k plus commands. increasing retries with a smaller timeunit may be a better approachSiberson
I agree. This code was written for test automation project so the performance was not the main priority. But I think it is easy enough to adapt to different use cases.Impuissant

© 2022 - 2024 — McMap. All rights reserved.