0

There is a device that is running some proprietrary, undocumented ssh daemon, for which I need to write and ssh client that can send & execute commands from a file. Jsch turned out to be unsuitable for this particular project, so we went for plink.

The problem is, that after connecting to the device, the prompt is "halted" by the OS of the device, and it is only released after 2-3 seconds, when some welcome message and some alarms have been collected and displayed. But by the time this happens, plink already releases the session.

The command itself is nothing extraordinary: plink.exe -ssh user@host -pw password help

When I execute it on a standard linux server, it works flawlessly. But the device in question "swallows" the command, as the prompt is not ready when the connection is established.

As I came to understand, plink is a one-off kind of thing, meaning it will issue the command, wait for the response, then terminate the connection - but it has a -shareexists argument, that is supposed to search for existing connections to the same host, and use that to send the commands.

Using this, I'm trying to create a multi-threaded approach, where the login thread is created first, and while it is alive, the command execution thread should hook into it via -shareexists.

If I understood correctly, it should be used like this: plink.exe -shareexists -ssh user@host -pw password help

The result is half working, as I can see that the login thread is successful, and is supposedly alive when the command execution starts:

SSHConnectionHandler.main: starting ssh test
PLinkLoginThread.PLinkLoginThread: plink login thread created
PLinkLoginThread.PLinkLoginThread: Thread[Thread-0,5,main]
PLinkLoginThread.doLogin: starting login procedure
PLinkLoginThread.doLogin: plink.exe -ssh user@host -pw password
PLinkLoginThread.doLogin: login procedure finished
Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.13.0-55-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Tue Dec  1 14:42:43 CET 2020

  System load:  0.01                Processes:           201
  Usage of /:   49.1% of 113.81GB   Users logged in:     1
  Memory usage: 36%                 IP address for eth0: x.x.x.x
  Swap usage:   2%

  => /mnt/backup/dkcad2 is using 88.4% of 98.30GB

  Graph this data and manage this system at:
    https://landscape.canonical.com/

301 packages can be updated.
231 updates are security updates.

Last login: Tue Dec  1 14:16:52 2020 from y.y.y.y
$ 
PLink Thread is alive: true
SSHConnectionHandler.main: executing command while login thread is alive
SSHConnectionHandler.main: pLinkLoginThread is alive: true
SSHConnectionHandler.main: sehr gut, sehr gut
PLinkCommandExecutorThread.pLinkCommandExecutorThread: plink executor thread created
PLinkCommandExecutorThread.pLinkCommandExecutorThread: Thread[Thread-1,5,main]
PLinkCommandExecutorThread.run: executing command
plink.exe -shareexists -ssh user@host -pw password help
PLinkCommandExecutorThread.run: command executed

But for some reason, either the command is not sent to the login thread session, or the response is swallowed.

Here is the code of the thread execution:

public static void main(String[] args) {

        String sshStream = null;
        String command = "ls";

        System.out.println("SSHConnectionHandler.main: starting ssh test");
        pLinkLoginThread = new PLinkLoginThread(testHost, 22, testUser, testPassword);
        try {
            pLinkLoginThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            while (pLinkLoginThread.isAlive()) {
                System.out.println("SSHConnectionHandler.main: executing command while login thread is alive");
                System.out.println("SSHConnectionHandler.main: pLinkLoginThread is alive: " + pLinkLoginThread.isAlive());
                if (pLinkLoginThread.isAlive()) {
                    System.out.println("SSHConnectionHandler.main: sehr gut, sehr gut");
                    pLinkCommandExecutorThread = new PLinkCommandExecutorThread(command, testHost, 22, testUser, testPassword);
                    pLinkCommandExecutorThread.start();
                    pLinkCommandExecutorThread.join();
                } else {
                    System.out.println("SSHConnectionHandler.main: das ist kein Kakao, Günther!");
                }
            }
        } catch (Exception e) {
            System.out.println("SSHConnectionHandler.main: exception during command execution");
            e.printStackTrace();
        }
    }

And here are the two threads:

Login thread

public class PLinkLoginThread extends Thread {

    PLinkLoginThread(String host, int port, String user, String password) {

        System.out.println("PLinkLoginThread.PLinkLoginThread: plink login thread created");
        System.out.println("PLinkLoginThread.PLinkLoginThread: " + this);
        run(host, port, user, password);
    }

    public void run(String host, int port, String user, String password) {

        Process process;
        String login = "plink.exe -ssh " + user + "@" + host + " -pw " + password;

        try {
            Runtime r = Runtime.getRuntime();
            System.out.println("PLinkLoginThread.doLogin: starting login procedure");
            System.out.println("PLinkLoginThread.doLogin: " + login);
            process = r.exec(login);
            process.waitFor(3, TimeUnit.SECONDS);
            System.out.println("PLinkLoginThread.doLogin: login procedure finished");
            System.out.println(SSHConnectionHandler.readInputStream(process.getInputStream()));
            System.out.println("PLink Thread is alive: " + process.isAlive());
        } catch (Exception e) {
            System.out.println("PLinkLoginThread.doLogin");
            e.printStackTrace();
        }

    }
}

Command execution thread (this should hook into the already open connection):

public class PLinkCommandExecutorThread extends Thread {

    PLinkCommandExecutorThread (String command, String host, int port, String user, String password) {

        System.out.println("PLinkCommandExecutorThread.pLinkCommandExecutorThread: plink executor thread created");
        System.out.println("PLinkCommandExecutorThread.pLinkCommandExecutorThread: " + this);
        run(command, user, host, password);
    }

    public void run(String command, String user, String host, String password) {

        Process process;
        String commandToExecute = "plink.exe -shareexists -ssh " + user + "@" + host + " -pw " + password + command;

        try {
            Runtime r = Runtime.getRuntime();
            System.out.println("PLinkCommandExecutorThread.run: executing command");
            System.out.println(commandToExecute);
            process = r.exec(commandToExecute);
            process.waitFor(3, TimeUnit.SECONDS);
            System.out.println("PLinkCommandExecutorThread.run: command executed");
            System.out.println(SSHConnectionHandler.readInputStream(process.getInputStream()));
            System.out.println(SSHConnectionHandler.readInputStream(process.getErrorStream()));
        } catch (Exception e) {
            System.out.println("PLinkCommandExecutorThread.run");
            e.printStackTrace();
        }
    }
}

Using saved PuTTY sessions isn't an option either, as there are 300+ such devices and no naming convention for the sessions.

Is there anything else I could try?

Zoltán Györkei
  • 1,025
  • 5
  • 13
  • 21

1 Answers1

0

I do not think you understand the purpose and workings of the PuTTY connection sharing feature. It won't help you. It would behave exactly the same as your plink.exe -ssh user@host -pw password help command.

It seems that your actual problem is that the SSH "exec" channel of the device does not really work.

You will have to use the "shell" channel.

For that use:

echo command | plink.exe -ssh user@host -pw password

Of course, that's for testing. If you are going to invoke plink from Java, you can feed the commands using native Java features. That would even allow you to delay the command input, if you really need that. Though it's (at least for testing) possible with Plink and plain batch file too. See Wait between sending login and commands to serial port using Plink (it's for a serial port connection, but the same will work for SSH too). For a test, try this in a Windows batch file:

(
  echo command1
  timeout /t 5 > nul
  echo command2
  timeout /t 5 > nul
  echo command3
  timeout /t 5 > nul
) | plink.exe ...

Note that using the "shell" channel is not recommended normally. But for many "devices" with limited SSH servers, it's often the only working solution.

Actually, it is possible that it's the very reason you have failed with using JSch originally. So maybe, you can actually use JSch, if you use the "shell" channel. See also What is the difference between the 'shell' channel and the 'exec' channel in JSch.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
  • Thank you, I will check that first link. We actually used every combination of shell and exec channels with Jsch, with some half wokring and more not working results. We could get it working for 1 ssh daemon, but it turned out a 2nd one is used on a different device in the same system where it was not working at all. All the device documentations offer are the mere lines that you should only use PuTTY to connect via ssh. But the client wants a custom client implemented. – Zoltán Györkei Dec 01 '20 at 15:23
  • Unfortunately, no. I tried creating the chain of command with ProcessBuilder as processBuilder.command("cmd", "/c", login, "TIMEOUT /T 3 /NOBREAK", "ls -l"); but it seems to disconnect the session after the login command finishes and doesn't keep it alive for the TIMEOUT and the ls -l to be able to be performed. – Zoltán Györkei Dec 07 '20 at 11:13
  • Yes, but since then I've revised it and have gotten much better results on a standard linux server. Now I'll have to take it to the client's lab and see if their ssh daemon will be able to handle it (I'm not sure it even supports timeout): "plink.exe ...... -batch \"echo timeout 5 ls; timeout 5; ls; echo timeout 5 ls -l; timeout 5; ls -l;\"" – Zoltán Györkei Dec 08 '20 at 14:12
  • Martin Prikryl thank you for your comments & help, they actually gave me the proper solution (using -batch "timeout 5; ls -l; timeout ...;), but the client system doesn't support any "sleep" commands, and their prompt handling is just not suitable for direkt command injection. As I have seen, KiTTY might have the solution with a configurable first delay between login and command sending. – Zoltán Györkei Dec 16 '20 at 13:28
  • I never suggested remote-side timeout (`plink .... -batch "timeout ..."`). I suggested local-side timeout using the **local Windows `timeout` command**. Again, see the example in my answer!! – Martin Prikryl Dec 16 '20 at 13:35