I am currently working on a java automation application incorporating Jsch. When I run my code however, it passes back an error saying that the TERM environment is not set up.
I already tried to manually add the environment in intellij by choosing environment variables. Then I add TERM=xterm. Though when I run that, it still fails.
import com.jcraft.jsch.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Driver {
public static void main(String[] args) throws Exception {
JSch jsch = new JSch();
Session session;
try {
// Open a Session to remote SSH server and Connect.
// Set User and IP of the remote host and SSH port.
session = jsch.getSession("username", "host", 22);
// When we do SSH to a remote host for the 1st time or if key at the remote host
// changes, we will be prompted to confirm the authenticity of remote host.
// This check feature is controlled by StrictHostKeyChecking ssh parameter.
// By default StrictHostKeyChecking is set to yes as a security measure.
session.setConfig("StrictHostKeyChecking", "no");
//Set password
session.setPassword("password");
session.connect();
// create the execution channel over the session
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
// Set the command to execute on the channel and execute the command
channelExec.setCommand("./script.sh");
channelExec.connect();
// Get an InputStream from this channel and read messages, generated
// by the executing command, from the remote side.
InputStream in = channelExec.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Command execution completed here.
// Retrieve the exit status of the executed command
int exitStatus = channelExec.getExitStatus();
if (exitStatus > 0) {
System.out.println("Remote script exec error! " + exitStatus);
}
//Disconnect the Session
session.disconnect();
} catch (JSchException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Make sure that the variable is set in your current shell by either exporting it or running your code with a set variable for TERM.
Something similar to the following should work:
TERM=linux /path/to/your/executable --some-arguments
The following could possibly only be relevant to bash but there is also a way to export a variable so as to make it global.
After exporting a variable, you can verify its value using:
echo $TERM
Empty response means variable is not set. Else, well... you get it i am sure. In order to export it globally, in bash that is, you can use the command line directly or add the export command into your dotfiles, which should be loaded upon login
export TERM=linux
Either way you choose, the command stays the same. There are multiple terminals and types, 'linux' being a very very generic one. A more color-friendly solution could be to try using 'xterm-256color' instead.
export TERM=xterm-256color
You should check out the basics of terminal if you wish to learn more. I hope this can help you achive your desired outcome.
Cheers
IntelliJ IDEA Run console is not a real Terminal, hence the problem.
You can run the code manually outside of IntelliJ IDEA or in the Terminal tool window.
For debugging you can use the Remote debug.
Related request: Add option to run configuration to launch in real console.
Related
I have a piece of code which connects to a Unix server and executes commands.
I have been trying with simple commands and they work fine.
I am able to login and get the output of the commands.
I need to run an Ab-initio graph through Java.
I am using the air sandbox run graph command for this.
It runs fine, when I login using SSH client and run the command. I am able to run the graph. However, when I try to run the command through Java it gives me a "air not found" error.
Is there any kind of limit on what kind of Unix commands JSch supports?
Any idea why I'm not able to run the command through my Java code?
Here's the code:
public static void connect(){
try{
JSch jsch=new JSch();
String host="*****";
String user="*****";
String config =
"Host foo\n"+
" User "+user+"\n"+
" Hostname "+host+"\n";
ConfigRepository configRepository =
com.jcraft.jsch.OpenSSHConfig.parse(config);
jsch.setConfigRepository(configRepository);
Session session=jsch.getSession("foo");
String passwd ="*****";
session.setPassword(passwd);
UserInfo ui = new MyUserInfo(){
public boolean promptYesNo(String message){
int foo = 0;
return foo==0;
}
};
session.setUserInfo(ui);
session.connect();
String command="air sandbox run <graph-path>";
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec)channel).setErrStream(System.err);
InputStream in=channel.getInputStream();
channel.connect();
byte[] tmp=new byte[1024];
while(true){
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
page_message=new String(tmp, 0, i);
System.out.print(page_message);
}
if(channel.isClosed()){
if(in.available()>0) continue;
System.out.println("exit-status: "+channel.getExitStatus());
break;
}
try{Thread.sleep(1000);}catch(Exception ee){}
}
channel.disconnect();
session.disconnect();
}
catch(Exception e){
System.out.println(e);
}
}
public static void main(String arg[]){
connect();
}
public String return_message(){
String ret_message=page_message;
return ret_message;
}
public static abstract class MyUserInfo
implements UserInfo, UIKeyboardInteractive{
public String getPassword(){ return null; }
public boolean promptYesNo(String str){ return false; }
public String getPassphrase(){ return null; }
public boolean promptPassphrase(String message){ return false; }
public boolean promptPassword(String message){ return false; }
public void showMessage(String message){ }
public String[] promptKeyboardInteractive(String destination,
String name,
String instruction,
String[] prompt,
boolean[] echo){
return null;
}
}
The "exec" channel in the JSch (rightfully) does not allocate a pseudo terminal (PTY) for the session. As a consequence a different set of startup scripts is (might be) sourced (particularly for non-interactive sessions, .bash_profile is not sourced). And/or different branches in the scripts are taken, based on absence/presence of the TERM environment variable. So the environment might differ from the interactive session, you use with your SSH client.
So, in your case, the PATH is probably set differently; and consequently the air executable cannot be found.
To verify that this is the root cause, disable the pseudo terminal allocation in your SSH client. For example in PuTTY, it's Connection > SSH > TTY > Don't allocate a pseudo terminal. Then, go to Connection > SSH > Remote command and enter your air ... command. Check Session > Close window on exit > Never and open the session. You should get the same "air not found" error.
Ways to fix this, in preference order:
Fix the command not to rely on a specific environment. Use a full path to air in the command. E.g.:
/bin/air sandbox run <graph-path>
If you do not know the full path, on common *nix systems, you can use which air command in your interactive SSH session.
Fix your startup scripts to set the PATH the same for both interactive and non-interactive sessions.
Try running the script explicitly via login shell (use --login switch with common *nix shells):
bash --login -c "air sandbox run sandbox run <graph-path>"
If the command itself relies on a specific environment setup and you cannot fix the startup scripts, you can change the environment in the command itself. Syntax for that depends on the remote system and/or the shell. In common *nix systems, this works:
String command="PATH=\"$PATH;/path/to/air\" && air sandbox run <graph-path>";
Another (not recommended) approach is to force the pseudo terminal allocation for the "exec" channel using the .setPty method:
Channel channel = session.openChannel("exec");
((ChannelExec)channel).setPty(true);
Using the pseudo terminal to automate a command execution can bring you nasty side effects. See for example Is there a simple way to get rid of junk values that come when you SSH using Python's Paramiko library and fetch output from CLI of a remote machine?
For a similar issues, see
Certain Unix commands fail with "... not found", when executed through Java using JSch even with setPty enabled
Commands executed using JSch behaves differently than in SSH terminal (bypasses confirm prompt message of "yes/"no")
JSch: Is there a way to expose user environment variables to "exec" channel?
Command (.4gl) executed with SSH.NET SshClient.RunCommand fails with "No such file or directory"
you could try to find out where "air" resides with
whereis air
and then use this outcome.
something like
/usr/bin/air sandbox run graph
You can use an ~/.ssh/environment file to set your AB_HOME and PATH variables.
I have a piece of code which connects to a Unix server and executes commands.
I have been trying with simple commands and they work fine.
I am able to login and get the output of the commands.
I need to run an Ab-initio graph through Java.
I am using the air sandbox run graph command for this.
It runs fine, when I login using SSH client and run the command. I am able to run the graph. However, when I try to run the command through Java it gives me a "air not found" error.
Is there any kind of limit on what kind of Unix commands JSch supports?
Any idea why I'm not able to run the command through my Java code?
Here's the code:
public static void connect(){
try{
JSch jsch=new JSch();
String host="*****";
String user="*****";
String config =
"Host foo\n"+
" User "+user+"\n"+
" Hostname "+host+"\n";
ConfigRepository configRepository =
com.jcraft.jsch.OpenSSHConfig.parse(config);
jsch.setConfigRepository(configRepository);
Session session=jsch.getSession("foo");
String passwd ="*****";
session.setPassword(passwd);
UserInfo ui = new MyUserInfo(){
public boolean promptYesNo(String message){
int foo = 0;
return foo==0;
}
};
session.setUserInfo(ui);
session.connect();
String command="air sandbox run <graph-path>";
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec)channel).setErrStream(System.err);
InputStream in=channel.getInputStream();
channel.connect();
byte[] tmp=new byte[1024];
while(true){
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
page_message=new String(tmp, 0, i);
System.out.print(page_message);
}
if(channel.isClosed()){
if(in.available()>0) continue;
System.out.println("exit-status: "+channel.getExitStatus());
break;
}
try{Thread.sleep(1000);}catch(Exception ee){}
}
channel.disconnect();
session.disconnect();
}
catch(Exception e){
System.out.println(e);
}
}
public static void main(String arg[]){
connect();
}
public String return_message(){
String ret_message=page_message;
return ret_message;
}
public static abstract class MyUserInfo
implements UserInfo, UIKeyboardInteractive{
public String getPassword(){ return null; }
public boolean promptYesNo(String str){ return false; }
public String getPassphrase(){ return null; }
public boolean promptPassphrase(String message){ return false; }
public boolean promptPassword(String message){ return false; }
public void showMessage(String message){ }
public String[] promptKeyboardInteractive(String destination,
String name,
String instruction,
String[] prompt,
boolean[] echo){
return null;
}
}
The "exec" channel in the JSch (rightfully) does not allocate a pseudo terminal (PTY) for the session. As a consequence a different set of startup scripts is (might be) sourced (particularly for non-interactive sessions, .bash_profile is not sourced). And/or different branches in the scripts are taken, based on absence/presence of the TERM environment variable. So the environment might differ from the interactive session, you use with your SSH client.
So, in your case, the PATH is probably set differently; and consequently the air executable cannot be found.
To verify that this is the root cause, disable the pseudo terminal allocation in your SSH client. For example in PuTTY, it's Connection > SSH > TTY > Don't allocate a pseudo terminal. Then, go to Connection > SSH > Remote command and enter your air ... command. Check Session > Close window on exit > Never and open the session. You should get the same "air not found" error.
Ways to fix this, in preference order:
Fix the command not to rely on a specific environment. Use a full path to air in the command. E.g.:
/bin/air sandbox run <graph-path>
If you do not know the full path, on common *nix systems, you can use which air command in your interactive SSH session.
Fix your startup scripts to set the PATH the same for both interactive and non-interactive sessions.
Try running the script explicitly via login shell (use --login switch with common *nix shells):
bash --login -c "air sandbox run sandbox run <graph-path>"
If the command itself relies on a specific environment setup and you cannot fix the startup scripts, you can change the environment in the command itself. Syntax for that depends on the remote system and/or the shell. In common *nix systems, this works:
String command="PATH=\"$PATH;/path/to/air\" && air sandbox run <graph-path>";
Another (not recommended) approach is to force the pseudo terminal allocation for the "exec" channel using the .setPty method:
Channel channel = session.openChannel("exec");
((ChannelExec)channel).setPty(true);
Using the pseudo terminal to automate a command execution can bring you nasty side effects. See for example Is there a simple way to get rid of junk values that come when you SSH using Python's Paramiko library and fetch output from CLI of a remote machine?
For a similar issues, see
Certain Unix commands fail with "... not found", when executed through Java using JSch even with setPty enabled
Commands executed using JSch behaves differently than in SSH terminal (bypasses confirm prompt message of "yes/"no")
JSch: Is there a way to expose user environment variables to "exec" channel?
Command (.4gl) executed with SSH.NET SshClient.RunCommand fails with "No such file or directory"
you could try to find out where "air" resides with
whereis air
and then use this outcome.
something like
/usr/bin/air sandbox run graph
You can use an ~/.ssh/environment file to set your AB_HOME and PATH variables.
hopefully someone might be able to show me what I'm doing wrong.
I'm writing a small android app to send commands to SSH servers. I have the following code in a separate method running on its own thread into which I pass username, server, and command.
I send a command and it puts a toast on the screen showing the output of that command. Now the code works with my windows machine running an SSH server. It also works (sort of) with my raspberry pi. However, if I send a command like "pwd;" to the pi I get the current directory back. Cool. If i send "sudo touch /media/MYUSBSTICK/testfile" it will create a file in the path specified. If I send "ls /" I get a list of everything in the root directory. "sudo ls /" also works.
HOWEVER... if I send "sudo shutdown -h now", or even just "shutdown" as a command using the exact same code I get nothing. It doesn't seem to execute and I get nothing back in my input buffer. If i do it from a SSH client connected directly with the Pi this works. I tried "sudo halt" and this also did nothing and returned nothing.
How come some commands work and other don't? Am I missing something obvious here? Is there some obvious mistake in my code?
Thanks
Nat
I there something special about
try {
JSch jsch = new JSch();
Session session = jsch.getSession(username, server, 22);
session.setPassword(password);
// Avoid asking for key confirmation
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);
session.connect(10000);
ChannelExec channel=(ChannelExec) session.openChannel("exec");
BufferedReader in=new BufferedReader(new InputStreamReader(channel.getInputStream()));
channel.setCommand(command); //could be "pwd;" or "sudo shutdown -h now"
channel.connect();
String msg=null;
String wholemessage = "";
while ((msg = in.readLine()) != null) {
wholemessage += msg + "\n";
}
Thread.sleep(8000);
channel.disconnect();
session.disconnect();
final String thereturnresult = wholemessage;
final String tempcommand = command;
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), thereturnresult ,Toast.LENGTH_SHORT).show();
}
});
}
Ok worked this out myself.
The trick is to capture the error data as well with channel.getErrStream() then all becomes clear...
Hope this helps someone.
I try to use the library JSch - Java Secure Channel make an ssh connection in my Android app, it works.
Now I would like to execute a command and retrieve the result.
I tried several methods that works best is this. However, this method works only in part, because for some reason I can not explain, my program stops at the end of my while loop, yet I'm the result of the command that appears in my log.
Here is my code :
public static String executeRemoteCommand(String username, String password, String hostname, int port) throws Exception {
JSch jsch = new JSch();
Session session = jsch.getSession(username, hostname, port);
session.setPassword(password);
// Avoid asking for key confirmation
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);
session.connect();
Channel channel = session.openChannel("shell");
channel.connect();
DataInputStream dataIn = new DataInputStream(channel.getInputStream());
DataOutputStream dataOut = new DataOutputStream(channel.getOutputStream());
// send ls command to the server
dataOut.writeBytes("ls\r\n");
dataOut.flush();
// and print the response
String line = dataIn.readLine();
String result = line + "\n";
while ((line = dataIn.readLine()) != null) {
result += line + "\n";
Log.i("TAG", "Line: "+line);
}
dataIn.close();
dataOut.close();
channel.disconnect();
session.disconnect();
return result;
}
Does anyone else have a better way to run a command sheel with JSch?
Thank you in advance !
Your method stops in the loop (instead of finishing it) because the remote shell doesn't close the output stream.
It has no reason to do this, since there you could send more commands.
If you only want to execute a single command (or a series of commands known before), you shouldn't use a Shell channel, but an "exec" channel.
This way the remote shell (which executes your command) will finish when your command is finished, and then the server will close the stream. So your loop will finish, and then you can close the streams.
If you think you need a shell channel (for example, if you need to fire up multiple commands in the same context, and react to one's output before deciding what would be the next one), you'll need some way to know when one command is finished (e.g. by recognizing the prompt), and then send the next one. To quit, either close the output stream or send a "logout" or "exit" command (both work with any standard unix shell, other shells might need different commands), then the remote site should close the other stream, too.
By the way, while disabling strict host key checking is convenient, it also opens up your connection to a man-in-the-middle attack, and in case of password authentication, the attacker can grab your password. The right way to do this would be to set up a correctly initialized host key repository to recognize the remote host's key.
As far as I am aware JSch is the only real option.
I have found that Jsch errors tend to take some digging. But in the first instance you will want to catch and print out the errors as a minimum.
try{
JSch jsch = new JSch();
Session session = jsch.getSession(username, hostname, port);
... omitted
} catch (Exception e) {
System.out.println(e)
}
also have a look a the example code on the site
I have used JSch Sudo example under following link:
http://www.jcraft.com/jsch/examples/Sudo.java.html
And changed it a bit and get rid of all the dialogs as I have to use it for EC2 instances using PuTTY.
Now my code looks like this:
import com.jcraft.jsch.*;
import java.awt.*;
import javax.swing.*;
import java.io.*;
public class sudo{
public static void main(String[] arg){
try{
JSch jsch=new JSch();
String host=null;
if(arg.length>0){
host=arg[0];
}
String privateKey = "my private key.pem";
jsch.addIdentity(privateKey, "");
Session session=jsch.getSession("ec2-user", "xx.xx.xx.xx", 22);
session.setPassword("");
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
String command="sudo mkdir /data";
String sudo_pass="";
Channel channel=session.openChannel("exec");
// man sudo
// -S The -S (stdin) option causes sudo to read the password from the
// standard input instead of the terminal device.
// -p The -p (prompt) option allows you to override the default
// password prompt and use a custom one.
((ChannelExec)channel).setCommand("sudo -S -p '' "+command);
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){}
}
channel.disconnect();
session.disconnect();
}
catch(Exception e){
System.out.println(e);
}
}
}
But I am getting the error
sorry you must have a tty to run sudo
I also tried to use ((ChannelExec) channel).setPty(true) but I can run the program once but next time, for same EC2 instance, I get the following error but for new instance it works fine for first time again.
com.jcraft.jsch.JSchException: Session.connect: java.io.IOException: End of IO Stream Read
sudo mkdir /data
Exception in thread "main" com.jcraft.jsch.JSchException: session is down
at com.jcraft.jsch.Session.openChannel(Session.java:791)
And then also I could not ssh the remote host from command line as well.
Can someone please guide me that what I need to do to run sudo command on remote host.
I have similar code in a project I am working on, and was getting the same error. I resolved this using the setPty(true) as you did.
I think you're getting this error because you don't close out the streams in your code. If you are using Java 1.7, you can use a resource block as follows:
try( InputStream in=channel.getInputStream() ) {
try( OutputStream out = channel.getOutputStream() ) {
...
}
}
or the try...finally block pattern from past versions.
After setting
((ChannelExec) channel).setPty(true)
the reported problem have in my code got solved.
By default, SUDO is configured to require a TTY. That is, SUDO is expected to be run from a login shell.
That's probably because your /etc/sudoers file (or any file it includes) has:
Defaults requiretty
But the option -S in sudo will make it read from Standard input.
-S The -S (stdin) option causes sudo to read the password from
the standard input instead of the terminal device. The pass-
word must be followed by a newline character.
Jsch exploits the same and tries to send the password. If you want to Jsch to work without prompting the password , you need to disable the requiretty from the sudoers file...using visudo command as follows
Defaults !requiretty
or
#Defaults requiretty