Implementing SSH shell terminal in webapp using JSch - java

I am trying to implement a shell terminal in a webapp using websocket in spring. I am able to send a single command to the JSch "exec" channel and send the output back to the websocket.
The problem I am having is:
I am not able to retain the state of the shell like working directory, when I send a second command. How can I retain the previous state? I have tried using the same session but it does not work.
public String sendCommand(String command) {
StringBuilder outputBuffer = new StringBuilder();
try {
Channel channel = sesConnection.openChannel("exec");
((ChannelExec) channel).setCommand(command);
InputStream commandOutput = channel.getInputStream();
channel.connect();
int readByte = commandOutput.read();
while (readByte != 0xffffffff) {
outputBuffer.append((char) readByte);
readByte = commandOutput.read();
}
channel.disconnect();
} catch (IOException ioX) {
logWarning(ioX.getMessage());
return null;
} catch (JSchException jschX) {
logWarning(jschX.getMessage());
return null;
}
return outputBuffer.toString();
}
To send back to the websocket, in the controller I have :
private SSHManager getSSHInstance() {
String errorMessage = null;
if (sshInstance == null) {
sshInstance = new SSHManager(username, password, host, "", port);
errorMessage = sshInstance.connect();
System.out.println("Instance created");
if (errorMessage != null) {
throw new RuntimeException("Could not create an ssh connection");
}
}
System.out.println("Returning created instance");
return sshInstance;
}
#MessageMapping("/user")
#SendTo("/topic/user")
public UserResponse getResponse(String command) {
SSHManager currInstance = getSSHInstance();
String result = currInstance.sendCommand(command);
return new UserResponse(result);
}
I tried using the "shell" channel instead of "exec" which worked for getting the input and output through standard input and output stream but I could not get the real-time input and output from/back to the websocket and UI. I am not sure how to proceed from here. Any direction on where/what to look would be very helpful.
Here is my code for the SSH terminal through standard input/output stream:
import com.jcraft.jsch.*;
public class Terminal{
public static void main(String[] args){
try{
JSch jsch=new JSch();
String host = "127.0.0.1";
String user = "user";
String password = "pass";
Session session=jsch.getSession(user, host, 5679);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(10000);
Channel channel=session.openChannel("shell");
channel.setInputStream(System.in);
channel.setOutputStream(System.out);
channel.connect(3*1000);
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}
To send the command from the UI, I have the following:
function sendCommand() {
if (stompClient != null) {
stompClient.send("/app/user", {}, JSON.stringify({'command': $("#command").val()}));
}
}

If you want to implement an interactive shell, you have to use the "shell" channel, not the "exec" channel. The "exec" channel is intended for automating individual commands.
Some references:
What is the difference between the 'shell' channel and the 'exec' channel in JSch
JSch Shell.java example

Related

how to use SSH in Java to return the result of command input of ubuntu's shell script to java [duplicate]

I am using a JSCH -SSH library to execute command in "shell" channel, but unable to find a way
to do 2 things:-
1) How to find whether the command is completely executed on remote unix box ?
2) How to capture the command output in String , instead of printing it on System.out console ?
Below is my code snippet which works fine to display shell command output on system.out
NOTE :I do NOT want to use ,"exec" channel, as it starts a new process for each command and does not remember "session" variables which were exported. I must use "shell" channel.
Below is my code snippet. Any help is appreciated.Thanks for your time.
try{
String commandToRun = "ls /tmp/*.log \n";
if(channel.isClosed())
channel=session.openChannel("shell");
byte[] bytes = commandToRun.getBytes();
ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
channel.setInputStream(bais);
InputStream ins=channel.getInputStream();
channel.connect();
channel.setOutputStream(System.out);//This prints on console. Need 2 capture in String somehow?
//in-efficient way to allow command to execute completely on remote Unix machine
//DO NOT know a better way, to know when command is executed completely
Thread.sleep(5000L);
}
catch(Exception e){
System.out.println("Exception in executeCommand() --->"+ e.getMessage());
e.printStackTrace();
}
My solution may not be needed anymore for the OP, but anyone else who is searching for a solution to cover both conditions 1) waiting for the commands to finish on remote machine; and 2) capturing output as string; you can try this:
public class SshConnectionManager {
private static Session session;
private static ChannelShell channel;
private static String username = "";
private static String password = "";
private static String hostname = "";
private static Session getSession(){
if(session == null || !session.isConnected()){
session = connect(hostname,username,password);
}
return session;
}
private static Channel getChannel(){
if(channel == null || !channel.isConnected()){
try{
channel = (ChannelShell)getSession().openChannel("shell");
channel.connect();
}catch(Exception e){
System.out.println("Error while opening channel: "+ e);
}
}
return channel;
}
private static Session connect(String hostname, String username, String password){
JSch jSch = new JSch();
try {
session = jSch.getSession(username, hostname, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
System.out.println("Connecting SSH to " + hostname + " - Please wait for few seconds... ");
session.connect();
System.out.println("Connected!");
}catch(Exception e){
System.out.println("An error occurred while connecting to "+hostname+": "+e);
}
return session;
}
private static void executeCommands(List<String> commands){
try{
Channel channel=getChannel();
System.out.println("Sending commands...");
sendCommands(channel, commands);
readChannelOutput(channel);
System.out.println("Finished sending commands!");
}catch(Exception e){
System.out.println("An error ocurred during executeCommands: "+e);
}
}
private static void sendCommands(Channel channel, List<String> commands){
try{
PrintStream out = new PrintStream(channel.getOutputStream());
out.println("#!/bin/bash");
for(String command : commands){
out.println(command);
}
out.println("exit");
out.flush();
}catch(Exception e){
System.out.println("Error while sending commands: "+ e);
}
}
private static void readChannelOutput(Channel channel){
byte[] buffer = new byte[1024];
try{
InputStream in = channel.getInputStream();
String line = "";
while (true){
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) {
break;
}
line = new String(buffer, 0, i);
System.out.println(line);
}
if(line.contains("logout")){
break;
}
if (channel.isClosed()){
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee){}
}
}catch(Exception e){
System.out.println("Error while reading channel output: "+ e);
}
}
public static void close(){
channel.disconnect();
session.disconnect();
System.out.println("Disconnected channel and session");
}
public static void main(String[] args){
List<String> commands = new ArrayList<String>();
commands.add("ls -l");
executeCommands(commands);
close();
}
}
This solution is also useful if you need to send multiple commands at a time and keep the channel open to reuse it later.
For 2) u can use ByteArrayOutputStream
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
channel.setOutputStream(baos);
and then create new string from new String(baos.toByteArray())
For 1 have you tried to use 2>&1 at the end of your command?
String commandToRun = "ls /tmp/*.log 2>&1 \n";
Taking the example provided by Mihail, other info on the internets, and the feedback from Martin, here's a reworked solution using exec. Note that opening a session allows multiple commands to be sent, each one opening it's own channel for input/output.
Rant:I really dislike having to get the process' OUTPUT stream to write to. What an annoying paradigm (at least for me). What I wanted is the processes input stream to write my output to, and had an amazingly difficult time working out that it's inverted. Is it just me or does the following (pseudocode) not make way more sense??
channel.getInputStream().write("here's some text to write into my channel.");
String ret = channel.getOutputStream().getOutput();
Anyways, thanks to Mihail and Martin for their comments / input.
public class SSHConnectionManager {
private Session session;
private String username = "user";
private String password = "password";
private String hostname = "myhost";
public SSHConnectionManager() { }
public SSHConnectionManager(String hostname, String username, String password) {
this.hostname = hostname;
this.username = username;
this.password = password;
}
public void open() throws JSchException {
open(this.hostname, this.username, this.password);
}
public void open(String hostname, String username, String password) throws JSchException{
JSch jSch = new JSch();
session = jSch.getSession(username, hostname, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // not recommended
session.setConfig(config);
session.setPassword(password);
System.out.println("Connecting SSH to " + hostname + " - Please wait for few seconds... ");
session.connect();
System.out.println("Connected!");
}
public String runCommand(String command) throws JSchException, IOException {
String ret = "";
if (!session.isConnected())
throw new RuntimeException("Not connected to an open session. Call open() first!");
ChannelExec channel = null;
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.setInputStream(null);
PrintStream out = new PrintStream(channel.getOutputStream());
InputStream in = channel.getInputStream(); // channel.getInputStream();
channel.connect();
// you can also send input to your running process like so:
// String someInputToProcess = "something";
// out.println(someInputToProcess);
// out.flush();
ret = getChannelOutput(channel, in);
channel.disconnect();
System.out.println("Finished sending commands!");
return ret;
}
private String getChannelOutput(Channel channel, InputStream in) throws IOException{
byte[] buffer = new byte[1024];
StringBuilder strBuilder = new StringBuilder();
String line = "";
while (true){
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) {
break;
}
strBuilder.append(new String(buffer, 0, i));
System.out.println(line);
}
if(line.contains("logout")){
break;
}
if (channel.isClosed()){
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee){}
}
return strBuilder.toString();
}
public void close(){
session.disconnect();
System.out.println("Disconnected channel and session");
}
public static void main(String[] args){
SSHConnectionManager ssh = new SSHConnectionManager();
try {
ssh.open();
String ret = ssh.runCommand("ls -l");
System.out.println(ret);
ssh.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

How to use JSch with ProxyJump in .ssh/confg [duplicate]

This question already has an answer here:
"reject HostKey" when connecting to remote host through jumphost with JSch
(1 answer)
Closed 3 years ago.
I have config file under ~/.ssh/config contains :
Host jumbHost
HostName x.domain.com
Host host1
HostName y1.domain.com
ProxyJump jumbHost
How could I use JSch to tunnel to x.domain.com server by host1 in order to execute commands ?
Note: y1.domain.com doesn't have public IP
I tried to use jsch.setConfigRepository(config_file_path) but it seems there are more config I need to set or methods to use.
It will be very useful if described using example.
EDIT:
So I can ssh to x.domain.com server when I get session using jsch.getSession(user,"host1"); or something like that
Thanks in advance
Thanks KevinO and Martin Prikryl for your value tips and help
Based on your descriptions and links I was able to apply needed config to enable access to x.domain.com using host1 name and then execute commands on y1.domain.com
For record, this is how I end up with
public class Main {
public static void main(String args[]) throws Exception {
try {
String user = "user_name";
String host = "host1";
String command = "hostname";
JSch jsch = new JSch();
ConfigRepository configRepository = OpenSSHConfig.parseFile(System.getProperty("user.home") + "/.ssh/config");
jsch.setConfigRepository(configRepository);
jsch.addIdentity(new File(System.getProperty("user.home") + "/.ssh/id_rsa").getAbsolutePath());
Session sessionProxy = jsch.getSession(user,configRepository.getConfig(host).getValue("ProxyJump"),22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sessionProxy.setConfig(config);
sessionProxy.connect();
int assignedPort = sessionProxy.setPortForwardingL(0, configRepository.getConfig(host).getHostname(), 22);
Session sessionTunnel = jsch.getSession(user, "127.0.0.1", assignedPort);
sessionTunnel.connect();
Channel channel = sessionTunnel.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
InputStream input = channel.getInputStream();
channel.connect();
try {
InputStreamReader inputReader = new InputStreamReader(input);
BufferedReader bufferedReader = new BufferedReader(inputReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(">>> output: " + line);
}
bufferedReader.close();
inputReader.close();
} catch (IOException ex) {
ex.printStackTrace();
}
channel.disconnect();
sessionTunnel.disconnect();
sessionProxy.disconnect();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

Java program does not terminate after jsch ssh client executes command

I am using java ssh client (http://www.jcraft.com/jsch/) to connect to remote machine and execute the command. The code is working fine till i connect to remote machine and execute the command. however, the issue is , the channel and session are not getting disconnected even after command executed successfully.
I have called session.disconnect and channel.disconnect as well but still the issue.
Here is my code:
JSch jsch = new JSch();
String privateKey = "C:\\test\\key";
jsch.addIdentity(privateKey);
String host = null;
host = "192.168.102.211";
Session session = jsch.getSession("chef-admin", host, 22);
Channel channel = session.openChannel("shell");
try
{
String cmd = "sudo su";
String command = "chef-client";
UserInfo ui = new MyUserInfo() {
public boolean promptYesNo(String message) {
return true;
}
};
session.setUserInfo(ui);
session.connect(30000);
OutputStream ops = channel.getOutputStream();
PrintStream ps = new PrintStream(ops, true);
channel.connect();
ps.println(cmd);
ps.println(command);
ps.close();
InputStream in = channel.getInputStream();
byte[] bt = new byte[1024];
while (in.available() > 0) {
int i = in.read(bt, 0, 1024);
if (i < 0)
break;
String str = new String(bt, 0, i);
System.out.println("valueeeeeeeeeeeeeeee is " + i);
System.out.print(str);
}
System.out.println("before channel");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (channel != null) {
System.out.println("finally");
channel.disconnect();
session.disconnect();
System.out.println(channel.isConnected());
}
}

java jsch su- root (how can i?)

I want to connect a Linux server and execute a command, but when I send the command sh and su - it requires password, but my program sends password just before it asks. How can i solve this problem?
public class Stream
{
public void getStream(String fileName)
{
String user = usernametext.getText();
String host = hostiptext.getText();
String password = pass.getText();
String command4 = "sh\nsu -\nmypassword\n";
try
{
System.out.println("preparing the host information for stream.");
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, 22);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(10 * 1000);
Channel channel = session.openChannel("shell");
InputStream is = new ByteArrayInputStream(command4.getBytes());
channel.setInputStream(is);
channel.setOutputStream(System.out);
channel.connect(15 * 1000);
session.connect(10 * 10);
channel.disconnect();
session.disconnect();
}
catch (Exception ex)
{
System.out.println("Exception found while streaming.");
}
}
}
As you are using a shell channel why do you need to call another sh?
Any how I do the above is to pass individual command and then read the inputStream until you get back to the prompt and then you can set the next input e.g. the password or what ever.
Pseudo-code being
write "su -"
readUntil "password:"
write "******"
readUntil prompt
etc.

Multiple commands using JSch

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();
}
}
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.
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.
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");
}

Categories