This note describes how to write a simple remote shell in Java.

Remote shell

A remote shell application obviously need to be able to run a command on the host computer, it also need some kind of socket server in order for clients to run commands remotely.

Executing command

The application need to execute a command and grab its output. The output should then be sent to the remote client.

Process command = Runtime.getRuntime().exec(commandStr);
BufferedReader reader = new BufferedReader(
                        new InputStreamReader(command.getInputStream()));

So this code will run the command in commandStr and then get the InputStream from the Process object (where input means input to the application, i.e. the output from the command). A reader is created for the stream and will later be used to grab the output from the command.

Reading command output

To read the command output just use the reader created earlier:

String line = "";
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
command.waitFor();

The loop will read all outputs from the command and terminates on end of line. The command.waitFor() waits for the command to exit.

Starting the server

Start a server listening on port.

ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();

When a client connects the blocking accept() call will return a Socket which is used for communicating with the client.

Writing command output to client

Writing the output from the command to the client means writing the command input stream to the socket output stream. We create an OutputStream from the socket:

// Write output to client
OutputStream output = socket.getOutputStream();

Then write the command output to the socket output stream. Modifying the loop above when reading command output:

String line = "";
while ((line = reader.readLine()) != null) {
    System.out.println(line);
    line += "\n";
    output.write(line.getBytes());
}

Now the simple case with a one-off command is complete. The application accepts a connection, sends the command output to the client and exits. However, if we run an interactive command, like for example bash there is yet no way to write input to the running command. That’s the next task to add.

Writing client input to an executing command

With the simple case done, its time to also read input from the connected client and write to the command output stream. Get the input stream from the socket (i.e. this is where the client command will be received)

// Read from client
InputStream input = socket.getInputStream();

Create a writer for the command stream

OutputStream commandStream = command.getOutputStream();
BufferedWriter writer = new BufferedWriter(
                             new OutputStreamWriter(commandStream));

Then if the command is still running read from the client input stream and write to the command ouput stream.

byte[] buffer = new byte[1024];
while (command.isAlive() &&
    ((bytesRead = input.read(buffer)) != -1))
{
    buffer[bytesRead] = '\n';
    if (command.isAlive()) {
        writer.write(new String(buffer));
        writer.flush();
    } else {
        break;
    }
}

The way this loop is constructed the loop will not exit until the command exits. Reading the input stream is a blocking call, hence the other loop writing the command output to the client will never be reached. So this loop is meant to be run in a sepearate thread, in parallel with the loop writing to client output stream. So put the above in a Runnable

new Thread(new Runnable() {
    @Override
    public void run() {
        // read socket and write to command stream
    }
}).start()

Running the Remote Shell

The remote shell can now be run as a one-off command like this:

$ java RemoteShell 3333 uptime

When connecting with a client the output of the command (here uptime) will be sent to the client and the application will exit:

$ netcat localhost 3333
 17:00:29 up 7 days,  9:05,  1 user,  load average: 1.87, 1.51, 1.34

If the remote shell is given the parameter to start bash it is possible for the client to write commands to the bash command i.e. basically run any command. This is the real remote shell.

$ java ExecCommand 3333 bash

On the client, note that there will be no prompt. Just type the command and see its output.

$ netcat localhost 3333
date
Thu Jan 16 17:14:09 CET 2020
uptime
 17:14:12 up 7 days,  9:19,  1 user,  load average: 1.76, 1.50, 1.45
ls -l /media	
total 4
drwxr-x---+ 2 root root 4096 May  2  2018 bjorn

GitHub source code

The code for this small application can be found here. Enjoy!