This FAQ includes information regarding topics that were discussed in e-mails between developers and users of the Ganymed SSH-2 for Java library.
Ganymed SSH-2 for Java homepage: http://www.cleondris.ch/opensource/ssh2/
Last update of FAQ: apr-16-2010.
Please report bugs, typos and any kind of suggestions to Christian Plattner (christian.plattner at cleondris.ch).
The most often source of problems when executing a command with Session.execCommand() are missing/wrong set environment variables on the remote machine. Make sure that the minimum needed environment for XYZ is the same, independentely on how the shell is being invoked.
Example quickfix for bash users:
Note: If you really want to mimic the behavior of putty, then don't use Session.execCommand(), instead aquire a pty (pseudo terminal) and then start a shell (use Session.requestPTY() and Session.startShell()). You then have to communicate with the shell process at the other end through stdin and stdout. However, you also have to implement terminal logic (e.g., escape sequence handling (unless you use a "dumb" pty), "expect-send" logic (output parsing, shell prompt detection), etc.).
If you login by using putty, then putty will normally request a "xterm" pty and your assigned shell (e.g., bash) will be started (a so called "interactive login shell"). In contrast, if you use Session.execCommand() to start a command then (unless you ask for it) no pty will be aquired and the command will be given to the shell as an argument (with the shell's "-c" option).
The way a shell is being invoked has an effect on the set of initialization files which will be read be the shell.
To demonstrate the difference, try the following (from the command line, e.g., with an OpenSSH client):
If you compare the two outputs, then you will (unless you have adjusted your shell's settings) observe different environments.
If you are interested in the details, then please read the INVOCATION section in man page for the bash shell. You may notice that the definitions of "interactive" and "non-interactive" (and combinations with "login") are little bit tricky.
[TOP]In the SSH-2 low level protocol, each channel (e.g., session) has a receive window. When the remote SSH daemon has filled up our receive window, it must wait until we have consumed the input and are ready to accept new data.
Unfortunately, the SSH-2 protocol defines a shared window for stderr and stdout. As a consequence, if, for example, the remote process produces a lot of stderr data and you never consume it, then after some time the local receive window will be full and the sender is blocked. If you then try to read() from stdout, your call will be blocked: there is no stdout data (locally) available and the SSH daemon cannot send you any, since the receive window is full (you would have to read some stderr data first to "free" up space in the receive window).
Fortunately, Ganymed SSH-2 uses a 30KB window - the above described scenario should be very rare.
Many other SSH-2 client implementations just blindly consume any remotely produced data into a buffer which gets automatically extended - however, this can lead to another problem: in the extreme case the remote side can overflow you with data (e.g., leading to out of memory errors).
What can you do about this?
InputStream stdout = new StreamGobbler(mysession.getStdout()); InputStream stderr = new StreamGobbler(mysession.getStderr());You then can access stdout and stderr in any order, in the background the StreamGobblers will automatically consume all data from the remote side and store in an internal buffer.
If you need it, then this library offers quite a raw type of access to the SSH-2 protocol stack. Of course, many people don't need that kind of low level access. If you need buffered streams, then you should the do the same thing as you would probably do with the streams of a TCP socket: wrap them with instances of BufferedInputStream and BufferedOutputStream. In case you use StreamGobblers for the InputStreams, then you don't need any additional wrappers, since the StreamGobblers implement buffering already.
This code snippet will probably work well for most people:
InputStream stdout = new StreamGobbler(mysession.getStdout());
InputStream stderr = new StreamGobbler(mysession.getStderr());
OutputStream stdin = new BufferedOutputStream(mysession.getStdin(), 8192);
If you use Session.execCommand(), then you indeed can only execute only one command per session. This is not a restriction of the library, but rather an enforcement by the underlying SSH-2 protocol (a Session object models the underlying SSH-2 session).
There are several solutions:
You are probably using OpenSSH. By looking at their source code you will find out that there is a hard-coded constant called MAX_SESSIONS in the session.c file which is set to "10" by default. This is a per connection limit. Unfortunately, it is not a run-time tunable parameter. However, this limit has no effect on the number of concurrent port forwardings. Please note: this information is based on the OpenSSH 4.3 release.
Possible solutions:
Just for completeness: starting from release 210, the thrown exception may look as follows:
java.io.IOException: Could not open channel (The server refused to open the channel (SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, 'open failed'))
Many default SSH server installations are configured to refuse the authentication type "password". Often, they only accept "publickey" and "keyboard-interactive". You have different options:
/etc/sshd/sshd_config
and change the value of "PasswordAuthentication" to "yes",
then send a HUP signal to the daemon so that it re-reads its configuration.
In general it is a good idea to call either Connection.getRemainingAuthMethods()
or Connection.isAuthMethodAvailable()
before using a certain authentication method.
Please note that most servers let you in after one successful authentication step. However, in rare cases
you may encounter servers that need several steps. I.e., if one of the Connection.authenticateWithXXX()
methods returns false
and Connection.isAuthenticationPartialSuccess()
returns
true
, then further authentication is needed. For each step, to find out which authentication methods
may proceed, you can use either the Connection.getRemainingAuthMethods()
or the Connection.isAuthMethodAvailable()
method. Again, please have a look into the
SwingShell.java example.
When using putty private keys (e.g., .ppk files) with public key authentication, you get a "Publickey authentication failed" exception. The reason is that the library currently is not able to directly handle private keys in the proprietary format used by putty. However, you can use the "puttygen" tool (from the putty website) to convert your key to the desired format: load your key, then go to the conversions menu and select "Save OpenSSH key" (which saves the key in openssl PEM format, e.g., call it "private.pem").
[TOP]Please read carefully the answer to the following question.
[TOP]The SCP protocol communicates the amount of data to be sent at the start of the transfer, so SCP remains out of consideration. Possible other solutions:
Be careful if you use the "cat" approach, as it may happen that not all your data will be written. If you close the stdin stream and immediatelly close the session (or the whole connection) then some SSH servers do not send the pending data to the process being executed ("cat" in this case). You have to wait until "cat" has received the EOF and terminates before closing the session. However, waiting for the termination may not always work, since SSH servers sometimes "forget" to send the exit code of the remote process. The following code MAY work:
Session sess = conn.openSession();
sess.execCommand("cat > test.txt");
OutputStream stdin = sess.getStdin();
... out.write(...) ... out.write(...) ...
/* The following flush() is only needed if you wrap the */
/* stdin stream (e.g., with a BufferedOutputStream). */
out.flush();
/* Now let's send EOF */
out.close();
/* Let's wait until cat has finished */
sess.waitForCondition(ChannelCondition.EXIT_STATUS, 2000);
/* Better: put the above statement into a while loop! */
/* In ANY CASE: read the Javadocs for waitForCondition() */
/* Show exit status, if available (otherwise "null") */
System.out.println("ExitCode: " + sess.getExitStatus());
/* Now its hopefully safe to close the session */
sess.close();
(Just a thought for another solution: execute cat > test.txt && echo "FINISHED"
and wait until you get "FINISHED" on stdout... - try it on your own risk =)
Please have at look at the examples section in the distribution, especially at the SwingShell.java example.
[TOP]We regulary get requests for a Maven repository. Please note that there is no such thing as an official Ganymed SSH-2 for Java Maven repository. At the moment, we do not have the resources to support specific build systems (be it Maven or anything else). We know that others have setup (and not maintained) such repositories. However, we believe that you should download security related software only from a trusted source - in other words, download the precompiled .jar file from our website and add it to your project. This generic approach will work with every java development enviroment and build system. Last warning: please think twice before you use a foreign "repository" to "auto-update" security related components of your project.
[TOP]