Compare commits

..

30 Commits

Author SHA1 Message Date
shikhar
c8cfc796af [maven-release-plugin] prepare release v0.9.0 2013-08-11 22:25:46 -04:00
shikhar
d9c0c6725c for 0.9.0 2013-08-11 22:08:26 -04:00
shikhar
b2297c6b44 version bumps 2013-08-11 18:56:46 -04:00
shikhar
e10ad28f2f inherit from sonatype oss parent pom 2013-08-11 18:56:46 -04:00
shikhar
61fc00a90a fix javadoc warning 2013-08-11 18:56:46 -04:00
shikhar
c8ef7ff0ca 0.9.0 snapshot 2013-08-11 17:27:04 -04:00
shikhar
e6c4f6ae69 #90 - only update cwd state if stat succeeds 2013-08-11 17:16:40 -04:00
shikhar
3418df7a56 #114 - visibility issue 2013-08-11 16:04:49 -04:00
shikhar
0ddd1f38c5 Simplify the UserAuth.authenticate(..) interface, move the multi-auth-method trial-and-error into SSHClient API 2013-04-15 22:56:24 -04:00
shikhar
0ec6918d7a minor javadoc 2013-03-24 19:09:21 -04:00
shikhar
88a88c5dba change transport layer to use millisecond timeouts 2013-03-24 17:36:11 -04:00
shikhar
6656214803 change connection layer to use millisecond timeouts 2013-03-24 17:27:36 -04:00
shikhar
c781724028 whitespace 2013-03-24 14:55:09 -04:00
shikhar
eefaa26882 missing flush() during banner exchange 2013-03-24 14:54:38 -04:00
Shikhar Bhushan
0d52441f01 Add 'unconfirmed writes' feature to SFTP RemoteFileOutputStream, allowing for major speedups
Thanks to @romainreuillon for the idea and initial implementation! #97
2013-02-23 18:16:29 -05:00
Shikhar Bhushan
9539ff6b7a In SFTPEngine / Requester, move from using TimeUnit.SECONDS to TimeUnit.MILLISECONDS, and start using some more explicit naming 2013-02-23 16:26:37 -05:00
Shikhar Bhushan
1ced1d4fdc Get rid of Requester.doRequest(), replace with request() method that returns the response promise. Make getTimeout() part of the interface. 2013-02-23 16:22:28 -05:00
Shikhar Bhushan
77924fd0be Revert "Implement concurent write requests."
This reverts commit 9acff6202c.
2013-02-23 16:00:53 -05:00
Shikhar Bhushan
3f195649fa Merge pull request #98 from andreaturli/master
Updated bouncycastle dependency
2013-02-23 03:41:44 -08:00
Shikhar Bhushan
42a4358f5c Merge pull request #104 from mpoindexter/master
ArrayIndexOutOfBounds when writing to a SFTP RemoteFile's OutputStream with large buffer
2013-02-23 03:41:19 -08:00
mpoindexter
61ce0f4868 Fix ArrayIndexOutOfBounds when writing big buffer
If ChannelOutputStream.write(byte[], int, int) was called with a buffer larger 
than bufferSize the loop in that method would call DataBuffer.write with a small len
and a large off.  This would cause the calculation in line 90 to return a negative n
leading to a ArrayIndexOutOfBounds.  The offset should not be taken into account when
calculating the number of bytes to put in the buffer.
2013-02-21 21:05:20 -08:00
Shikhar Bhushan
777995af3b Merge pull request #97 from romainreuillon/master
Make write a lot faster
2013-01-06 12:30:14 -08:00
Andrea Turli
635cf88acd updatet bouncycastle dep to the latest version 2012-12-31 01:44:10 +01:00
Romain Reuillon
ce515fddcd Change the scope back to protected, the change was unwanted. 2012-12-12 08:33:32 +01:00
Romain Reuillon
9acff6202c Implement concurent write requests. 2012-12-12 08:29:26 +01:00
Shikhar Bhushan
cbd118e0b1 fix #84 - debug log good enough 2012-10-21 02:13:55 +05:30
Shikhar Bhushan
a8cf749d95 #87 - include full exception trace when logging transport death 2012-10-21 01:45:13 +05:30
Shikhar Bhushan
f3d4707ef0 fix #89 - use IllegalStateException from SSHClient when sanity-check assertions fail 2012-10-21 01:40:07 +05:30
Shikhar Bhushan
4c5da634ad don't do a looped cond.await(timeout, unit) as that handles spurious wakeups, and it'll be buggy if the wakeup is due to a call to clear() 2012-10-21 01:21:36 +05:30
Shikhar Bhushan
2fdafb76fd [maven-release-plugin] prepare for next development iteration 2012-07-08 09:55:32 -04:00
28 changed files with 336 additions and 322 deletions

93
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>net.schmizz</groupId>
<artifactId>sshj</artifactId>
<packaging>bundle</packaging>
<version>0.8.1</version>
<version>0.9.0</version>
<name>sshj</name>
<description>SSHv2 library for Java</description>
@@ -23,7 +23,8 @@
<connection>scm:git:git://github.com/shikhar/sshj.git</connection>
<developerConnection>scm:git:git@github.com:shikhar/sshj.git</developerConnection>
<url>http://github.com/shikhar/sshj</url>
</scm>
<tag>v0.9.0</tag>
</scm>
<licenses>
<license>
@@ -33,54 +34,65 @@
</license>
</licenses>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
<scope>provided</scope>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.49</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.49</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
<version>1.0.7</version>
<version>1.1.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>0.5.0</version>
<version>0.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>0.9.29</version>
<version>1.0.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.29</version>
<version>1.0.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0-rc1</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -102,7 +114,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<version>3.1</version>
<configuration>
<excludes>
<exclude>examples/*.java</exclude>
@@ -114,29 +126,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.1</version>
<version>2.4.1</version>
<configuration>
<mavenExecutorId>forked-path</mavenExecutorId>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<descriptors>
<descriptor>src/assemble/examples.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
@@ -153,7 +147,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.8</version>
<version>2.9.1</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
@@ -169,15 +163,15 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.6</version>
<version>2.4.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>
!net.schmizz.*,
javax.crypto*,
com.jcraft.jzlib*;version="[1.0,2)",
org.slf4j*;version="[1.6,2)",
com.jcraft.jzlib*;version="[1.1,2)",
org.slf4j*;version="[1.7,5)",
org.bouncycastle*,
*
</Import-Package>
@@ -207,8 +201,8 @@
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.45</version>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.49</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
@@ -218,12 +212,12 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>0.9.24</version>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.24</version>
<version>1.0.13</version>
</dependency>
</dependencies>
</profile>
@@ -240,7 +234,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.3</version>
<version>1.4</version>
<configuration>
<passphrase>${gpg.passphrase}</passphrase>
</configuration>
@@ -255,7 +249,6 @@
</executions>
</plugin>
</plugins>
</build>
</profile>

View File

@@ -162,11 +162,14 @@ public class Promise<V, T extends Throwable> {
if (val != null)
return val;
log.debug("Awaiting <<{}>>", name);
while (val == null && pendingEx == null)
if (timeout == 0)
if (timeout == 0) {
while (val == null && pendingEx == null) {
cond.await();
else if (!cond.await(timeout, unit))
}
} else {
if (!cond.await(timeout, unit))
return null;
}
if (pendingEx != null) {
log.error("<<{}>> woke to: {}", name, pendingEx.toString());
throw pendingEx;

View File

@@ -35,13 +35,10 @@ public abstract class AbstractService
protected final String name;
/** Transport layer */
protected final Transport trans;
/** Timeout for blocking operations */
protected int timeout;
public AbstractService(String name, Transport trans) {
this.name = name;
this.trans = trans;
timeout = trans.getTimeout();
}
@Override
@@ -77,12 +74,6 @@ public abstract class AbstractService
trans.reqService(this);
}
public int getTimeout() {
return this.timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}

View File

@@ -54,16 +54,16 @@ public interface Config {
List<Factory.Named<FileKeyProvider>> getFileKeyProviderFactories();
/**
* Retrieve the list of named factories for <code>KeyExchange</code>.
* Retrieve the list of named factories for {@code KeyExchange}.
*
* @return a list of named <code>KeyExchange</code> factories
* @return a list of named {@code KeyExchange} factories
*/
List<Factory.Named<KeyExchange>> getKeyExchangeFactories();
/**
* Retrieve the list of named factories for <code>MAC</code>.
* Retrieve the list of named factories for {@code MAC}.
*
* @return a list of named <code>MAC</code> factories
* @return a list of named {@code MAC} factories
*/
List<Factory.Named<MAC>> getMACFactories();

View File

@@ -96,7 +96,7 @@ public class DefaultConfig
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String VERSION = "SSHJ_0_8_1_SNAPSHOT";
private static final String VERSION = "SSHJ_0_9_0";
public DefaultConfig() {
setVersion(VERSION);

View File

@@ -68,6 +68,7 @@ import java.net.ServerSocket;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
@@ -175,6 +176,8 @@ public class SSHClient
});
}
// FIXME: there are way too many auth... overrides. Better API needed.
/**
* Authenticate {@code username} using the supplied {@code methods}.
*
@@ -186,7 +189,7 @@ public class SSHClient
*/
public void auth(String username, AuthMethod... methods)
throws UserAuthException, TransportException {
assert isConnected();
checkConnected();
auth(username, Arrays.<AuthMethod>asList(methods));
}
@@ -201,8 +204,17 @@ public class SSHClient
*/
public void auth(String username, Iterable<AuthMethod> methods)
throws UserAuthException, TransportException {
assert isConnected();
auth.authenticate(username, (Service) conn, methods);
checkConnected();
final Deque<UserAuthException> savedEx = new LinkedList<UserAuthException>();
for (AuthMethod method: methods) {
try {
if (auth.authenticate(username, (Service) conn, method, trans.getTimeoutMs()))
return;
} catch (UserAuthException e) {
savedEx.push(e);
}
}
throw new UserAuthException("Exhausted available authentication methods", savedEx.peek());
}
/**
@@ -297,8 +309,7 @@ public class SSHClient
* @throws TransportException if there was a transport-layer error
*/
public void authPublickey(String username, Iterable<KeyProvider> keyProviders)
throws UserAuthException,
TransportException {
throws UserAuthException, TransportException {
final List<AuthMethod> am = new LinkedList<AuthMethod>();
for (KeyProvider kp : keyProviders)
am.add(new AuthPublickey(kp));
@@ -343,13 +354,14 @@ public class SSHClient
public void authPublickey(String username, String... locations)
throws UserAuthException, TransportException {
final List<KeyProvider> keyProviders = new LinkedList<KeyProvider>();
for (String loc : locations)
for (String loc : locations) {
try {
log.debug("Attempting to load key from: {}", loc);
keyProviders.add(loadKeys(loc));
} catch (IOException logged) {
log.warn("Could not load keys due to: {}", logged);
log.info("Could not load keys from {} due to: {}", loc, logged.getMessage());
}
}
authPublickey(username, keyProviders);
}
@@ -365,7 +377,6 @@ public class SSHClient
throws IOException {
trans.disconnect();
super.disconnect();
assert !isConnected();
}
/** @return the associated {@link Connection} instance. */
@@ -391,8 +402,7 @@ public class SSHClient
/**
* @return the associated {@link UserAuth} instance. This allows access to information like the {@link
* UserAuth#getBanner() authentication banner}, whether authentication was at least {@link
* UserAuth#hadPartialSuccess() partially successful}, and any {@link UserAuth#getSavedExceptions() saved
* exceptions} that were ignored because there were more authentication method that could be tried.
* UserAuth#hadPartialSuccess() partially successful}.
*/
public UserAuth getUserAuth() {
return auth;
@@ -607,7 +617,8 @@ public class SSHClient
/** @return Instantiated {@link SCPFileTransfer} implementation. */
public SCPFileTransfer newSCPFileTransfer() {
assert isConnected() && isAuthenticated();
checkConnected();
checkAuthenticated();
return new SCPFileTransfer(this);
}
@@ -619,7 +630,8 @@ public class SSHClient
*/
public SFTPClient newSFTPClient()
throws IOException {
assert isConnected() && isAuthenticated();
checkConnected();
checkAuthenticated();
return new SFTPClient(new SFTPEngine(this).init());
}
@@ -636,10 +648,10 @@ public class SSHClient
@Override
public Session startSession()
throws ConnectionException, TransportException {
assert isConnected() && isAuthenticated();
checkConnected();
checkAuthenticated();
final SessionChannel sess = new SessionChannel(conn);
sess.open();
assert sess.isOpen();
return sess;
}
@@ -679,8 +691,7 @@ public class SSHClient
*/
protected void doKex()
throws TransportException {
assert trans.isRunning();
checkConnected();
final long start = System.currentTimeMillis();
trans.doKex();
log.debug("Key exchange took {} seconds", (System.currentTimeMillis() - start) / 1000.0);
@@ -697,4 +708,16 @@ public class SSHClient
disconnect();
}
private void checkConnected() {
if (!isConnected()) {
throw new IllegalStateException("Not connected");
}
}
private void checkAuthenticated() {
if (!isAuthenticated()) {
throw new IllegalStateException("Not authenticated");
}
}
}

View File

@@ -138,16 +138,16 @@ public interface Connection {
Transport getTransport();
/**
* @return the {@code timeout} in seconds that this connection uses for blocking operations and recommends to any
* {@link Channel other} {@link ForwardedChannelOpener classes} that ask for it.
* @return the {@code timeout} in milliseconds that this connection uses for blocking operations and recommends to
* any {@link Channel other} {@link ForwardedChannelOpener classes} that ask for it.
*/
int getTimeout();
int getTimeoutMs();
/**
* Set the {@code timeout} this connection uses for blocking operations and recommends to any {@link Channel other}
* {@link ForwardedChannelOpener classes} that ask for it.
*
* @param timeout timeout in seconds
* @param timeout timeout in milliseconds
*/
void setTimeout(int timeout);
void setTimeoutMs(int timeout);
}

View File

@@ -54,6 +54,8 @@ public class ConnectionImpl
private long windowSize = 2048 * 1024;
private int maxPacketSize = 32 * 1024;
private volatile int timeoutMs;
/**
* Create with an associated {@link Transport}.
*
@@ -61,6 +63,7 @@ public class ConnectionImpl
*/
public ConnectionImpl(Transport trans) {
super("ssh-connection", trans);
timeoutMs = trans.getTimeoutMs();
}
@Override
@@ -251,4 +254,14 @@ public class ConnectionImpl
channels.clear();
}
@Override
public void setTimeoutMs(int timeoutMs) {
this.timeoutMs = timeoutMs;
}
@Override
public int getTimeoutMs() {
return timeoutMs;
}
}

View File

@@ -265,7 +265,7 @@ public abstract class AbstractChannel
if (!closeEvent.inError())
throw e;
}
closeEvent.await(conn.getTimeout(), TimeUnit.SECONDS);
closeEvent.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
} finally {
openCloseLock.unlock();

View File

@@ -87,7 +87,7 @@ public final class ChannelOutputStream
flush(bufferSize);
return 0;
} else {
final int n = Math.min(len - off, win.getMaxPacketSize() - bufferSize);
final int n = Math.min(len, win.getMaxPacketSize() - bufferSize);
packet.putRawBytes(data, off, n);
return n;
}
@@ -214,4 +214,4 @@ public final class ChannelOutputStream
return "< ChannelOutputStream for Channel #" + chan.getID() + " >";
}
}
}

View File

@@ -65,7 +65,7 @@ public abstract class AbstractDirectChannel
public void open()
throws ConnectionException, TransportException {
trans.write(buildOpenReq());
openEvent.await(conn.getTimeout(), TimeUnit.SECONDS);
openEvent.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
private void gotOpenConfirmation(SSHPacket buf)

View File

@@ -58,13 +58,13 @@ public class SessionChannel
private final ChannelInputStream err = new ChannelInputStream(this, trans, lwin);
private Integer exitStatus;
private volatile Integer exitStatus;
private Signal exitSignal;
private Boolean wasCoreDumped;
private String exitErrMsg;
private volatile Signal exitSignal;
private volatile Boolean wasCoreDumped;
private volatile String exitErrMsg;
private Boolean canDoFlowControl;
private volatile Boolean canDoFlowControl;
private boolean usedUp;
@@ -91,7 +91,7 @@ public class SessionChannel
.putUInt32(width)
.putUInt32(height)
.putBytes(PTYMode.encode(modes))
).await(conn.getTimeout(), TimeUnit.SECONDS);
).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
@Override
@@ -119,7 +119,7 @@ public class SessionChannel
checkReuse();
log.info("Will request to exec `{}`", command);
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command))
.await(conn.getTimeout(), TimeUnit.SECONDS);
.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
usedUp = true;
return this;
}
@@ -175,14 +175,14 @@ public class SessionChannel
.putString(authProto)
.putString(authCookie)
.putUInt32(screen)
).await(conn.getTimeout(), TimeUnit.SECONDS);
).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
@Override
public void setEnvVar(String name, String value)
throws ConnectionException, TransportException {
sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value))
.await(conn.getTimeout(), TimeUnit.SECONDS);
.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
@Override
@@ -195,7 +195,7 @@ public class SessionChannel
public Shell startShell()
throws ConnectionException, TransportException {
checkReuse();
sendChannelRequest("shell", true, null).await(conn.getTimeout(), TimeUnit.SECONDS);
sendChannelRequest("shell", true, null).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
usedUp = true;
return this;
}
@@ -206,7 +206,7 @@ public class SessionChannel
checkReuse();
log.info("Will request `{}` subsystem", name);
sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name))
.await(conn.getTimeout(), TimeUnit.SECONDS);
.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
usedUp = true;
return this;
}

View File

@@ -201,7 +201,7 @@ public class RemotePortForwarder
final byte[] specifics = new Buffer.PlainBuffer().putString(forward.address).putUInt32(forward.port)
.getCompactData();
return conn.sendGlobalRequest(reqName, true, specifics)
.retrieve(conn.getTimeout(), TimeUnit.SECONDS);
.retrieve(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
/** @return the active forwards. */

View File

@@ -103,8 +103,11 @@ public class PacketReader
promise.deliver(resp);
}
public void expectResponseTo(Request req) {
promises.put(req.getRequestID(), req.getResponsePromise());
public Promise<Response, SFTPException> expectResponseTo(long requestId) {
final Promise<Response, SFTPException> promise
= new Promise<Response, SFTPException>("sftp / " + requestId, SFTPException.chainer);
promises.put(requestId, promise);
return promise;
}
}

View File

@@ -20,6 +20,7 @@ import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class RemoteDirectory
extends RemoteResource {
@@ -33,7 +34,8 @@ public class RemoteDirectory
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
loop:
for (; ; ) {
Response res = requester.doRequest(newRequest(PacketType.READDIR));
final Response res = requester.request(newRequest(PacketType.READDIR))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
switch (res.getType()) {
case NAME:

View File

@@ -15,11 +15,15 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
public class RemoteFile
extends RemoteResource {
@@ -28,19 +32,12 @@ public class RemoteFile
super(requester, path, handle);
}
public RemoteFileInputStream getInputStream() {
return new RemoteFileInputStream();
}
public RemoteFileOutputStream getOutputStream() {
return new RemoteFileOutputStream();
}
public FileAttributes fetchAttributes()
throws IOException {
return requester.doRequest(newRequest(PacketType.FSTAT))
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
return requester.request(newRequest(PacketType.FSTAT))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS)
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
}
public long length()
@@ -55,7 +52,9 @@ public class RemoteFile
public int read(long fileOffset, byte[] to, int offset, int len)
throws IOException {
Response res = requester.doRequest(newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len));
final Response res = requester.request(
newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len)
).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
switch (res.getType()) {
case DATA:
int recvLen = res.readUInt32AsInt();
@@ -73,16 +72,27 @@ public class RemoteFile
public void write(long fileOffset, byte[] data, int off, int len)
throws IOException {
requester.doRequest(newRequest(PacketType.WRITE)
.putUInt64(fileOffset)
.putUInt32(len - off)
.putRawBytes(data, off, len)
).ensureStatusPacketIsOK();
checkResponse(asyncWrite(fileOffset, data, off, len));
}
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
throws IOException {
return requester.request(newRequest(PacketType.WRITE)
.putUInt64(fileOffset)
.putUInt32(len - off)
.putRawBytes(data, off, len)
);
}
private void checkResponse(Promise<Response, SFTPException> responsePromise)
throws SFTPException {
responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
}
public void setAttributes(FileAttributes attrs)
throws IOException {
requester.doRequest(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs)).ensureStatusPacketIsOK();
requester.request(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
}
public int getOutgoingPacketOverhead() {
@@ -98,17 +108,25 @@ public class RemoteFile
public class RemoteFileOutputStream
extends OutputStream {
private final byte[] b = new byte[1];
private final int maxUnconfirmedWrites;
private final Queue<Promise<Response, SFTPException>> unconfirmedWrites;
private long fileOffset;
public RemoteFileOutputStream() {
this(0);
}
public RemoteFileOutputStream(long fileOffset) {
this.fileOffset = fileOffset;
public RemoteFileOutputStream(long startingOffset) {
this(startingOffset, 0);
}
public RemoteFileOutputStream(long startingOffset, int maxUnconfirmedWrites) {
this.fileOffset = startingOffset;
this.maxUnconfirmedWrites = maxUnconfirmedWrites;
this.unconfirmedWrites = new LinkedList<Promise<Response, SFTPException>>();
}
@Override
@@ -121,10 +139,27 @@ public class RemoteFile
@Override
public void write(byte[] buf, int off, int len)
throws IOException {
RemoteFile.this.write(fileOffset, buf, off, len);
if (unconfirmedWrites.size() > maxUnconfirmedWrites) {
checkResponse(unconfirmedWrites.remove());
}
unconfirmedWrites.add(RemoteFile.this.asyncWrite(fileOffset, buf, off, len));
fileOffset += len;
}
@Override
public void flush()
throws IOException {
while (!unconfirmedWrites.isEmpty()) {
checkResponse(unconfirmedWrites.remove());
}
}
@Override
public void close()
throws IOException {
flush();
}
}
public class RemoteFileInputStream

View File

@@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public abstract class RemoteResource
implements Closeable {
@@ -49,7 +50,9 @@ public abstract class RemoteResource
public void close()
throws IOException {
log.debug("Closing `{}`", this);
requester.doRequest(newRequest(PacketType.CLOSE)).ensureStatusPacketIsOK();
requester.request(newRequest(PacketType.CLOSE))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS)
.ensureStatusPacketIsOK();
}
@Override

View File

@@ -15,20 +15,16 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
public final class Request
extends SFTPPacket<Request> {
private final PacketType type;
private final long reqID;
private final Promise<Response, SFTPException> responsePromise;
public Request(PacketType type, long reqID) {
super(type);
this.type = type;
this.reqID = reqID;
responsePromise = new Promise<Response, SFTPException>("sftp / " + reqID, SFTPException.chainer);
putUInt32(reqID);
}
@@ -40,10 +36,6 @@ public final class Request
return type;
}
public Promise<Response, SFTPException> getResponsePromise() {
return responsePromise;
}
@Override
public String toString() {
return "Request{" + reqID + ";" + type + "}";

View File

@@ -15,6 +15,8 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
import java.io.IOException;
public interface Requester {
@@ -23,7 +25,9 @@ public interface Requester {
Request newRequest(PacketType type);
Response doRequest(Request req)
Promise<Response, SFTPException> request(Request req)
throws IOException;
int getTimeoutMs();
}

View File

@@ -15,6 +15,7 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
@@ -34,12 +35,12 @@ public class SFTPEngine
implements Requester, Closeable {
public static final int MAX_SUPPORTED_VERSION = 3;
public static final int DEFAULT_TIMEOUT = 30;
public static final int DEFAULT_TIMEOUT_MS = 30 * 1000; // way too long, but it was the original default
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
protected volatile int timeout = DEFAULT_TIMEOUT;
protected volatile int timeoutMs = DEFAULT_TIMEOUT_MS;
protected final PathHelper pathHelper;
@@ -116,12 +117,17 @@ public class SFTPEngine
}
@Override
public Response doRequest(Request req)
public Promise<Response, SFTPException> request(Request req)
throws IOException {
reader.expectResponseTo(req);
final Promise<Response, SFTPException> promise = reader.expectResponseTo(req.getRequestID());
log.debug("Sending {}", req);
transmit(req);
return req.getResponsePromise().retrieve(timeout, TimeUnit.SECONDS);
return promise;
}
private Response doRequest(Request req)
throws IOException {
return request(req).retrieve(getTimeoutMs(), TimeUnit.MILLISECONDS);
}
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa)
@@ -227,12 +233,12 @@ public class SFTPEngine
));
}
public void setTimeout(int timeout) {
this.timeout = timeout;
public void setTimeoutMs(int timeoutMs) {
this.timeoutMs = timeoutMs;
}
public int getTimeout() {
return timeout;
public int getTimeoutMs() {
return timeoutMs;
}
@Override

View File

@@ -141,14 +141,16 @@ public class SFTPFileTransfer
final LocalDestFile adjusted = local.getTargetFile(remote.getName());
final RemoteFile rf = engine.open(remote.getPath());
try {
final RemoteFile.RemoteFileInputStream rfis = rf.new RemoteFileInputStream();
final OutputStream os = adjusted.getOutputStream();
try {
new StreamCopier(rf.getInputStream(), os)
new StreamCopier(rfis, os)
.bufSize(engine.getSubsystem().getLocalMaxPacketSize())
.keepFlushing(false)
.listener(listener)
.copy();
} finally {
rfis.close();
os.close();
}
} finally {
@@ -206,14 +208,16 @@ public class SFTPFileTransfer
OpenMode.TRUNC));
try {
final InputStream fis = local.getInputStream();
final RemoteFile.RemoteFileOutputStream rfos = rf.new RemoteFileOutputStream(0, 16);
try {
new StreamCopier(fis, rf.getOutputStream())
new StreamCopier(fis, rfos)
.bufSize(engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead())
.keepFlushing(false)
.listener(listener)
.copy();
} finally {
fis.close();
rfos.close();
}
} finally {
rf.close();

View File

@@ -40,10 +40,11 @@ public class StatefulSFTPClient
public synchronized void cd(String dirname)
throws IOException {
cwd = cwdify(dirname);
final String targetCwd = cwdify(dirname);
if (statExistence(cwd) == null) {
throw new SFTPException(cwd + ": does not exist");
}
cwd = targetCwd;
log.debug("CWD = {}", cwd);
}

View File

@@ -155,7 +155,7 @@ final class KeyExchanger
* @param waitForDone whether should block till key exchange completed
*
* @throws TransportException if there is an error during key exchange
* @see {@link Transport#setTimeout} for setting timeout for kex
* @see {@link Transport#setTimeoutMs} for setting timeout for kex
*/
void startKex(boolean waitForDone)
throws TransportException {
@@ -169,7 +169,7 @@ final class KeyExchanger
void waitForDone()
throws TransportException {
done.await(transport.getTimeout(), TimeUnit.SECONDS);
done.await(transport.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
private synchronized void ensureKexOngoing()
@@ -360,7 +360,7 @@ final class KeyExchanger
* We block on this event to prevent a race condition where we may have received a SSH_MSG_KEXINIT before
* having sent the packet ourselves (would cause gotKexInit() to fail)
*/
kexInitSent.await(transport.getTimeout(), TimeUnit.SECONDS);
kexInitSent.await(transport.getTimeoutMs(), TimeUnit.MILLISECONDS);
gotKexInit(buf);
expected = Expected.FOLLOWUP;
break;

View File

@@ -86,14 +86,14 @@ public interface Transport
Config getConfig();
/** @return the timeout that is currently set for blocking operations. */
int getTimeout();
int getTimeoutMs();
/**
* Set a timeout for methods that may block.
*
* @param timeout the timeout in seconds
* @param timeout the timeout in milliseconds
*/
void setTimeout(int timeout);
void setTimeoutMs(int timeout);
/** @return the interval in seconds at which a heartbeat message is sent to the server */
int getHeartbeatInterval();

View File

@@ -113,7 +113,7 @@ public final class TransportImpl
/** Client version identification string */
private final String clientID;
private volatile int timeout = 30;
private volatile int timeoutMs = 30 * 1000; // Crazy long, but it was the original default
private volatile boolean authed = false;
@@ -151,6 +151,7 @@ public final class TransportImpl
log.info("Client identity string: {}", clientID);
connInfo.out.write((clientID + "\r\n").getBytes(IOUtils.UTF8));
connInfo.out.flush();
// Read server's ID
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
@@ -178,7 +179,7 @@ public final class TransportImpl
*
* @param buffer
*
* @return
* @return empty string if full ident string has not yet been received
*
* @throws IOException
*/
@@ -240,13 +241,13 @@ public final class TransportImpl
}
@Override
public int getTimeout() {
return timeout;
public int getTimeoutMs() {
return timeoutMs;
}
@Override
public void setTimeout(int timeout) {
this.timeout = timeout;
public void setTimeoutMs(int timeoutMs) {
this.timeoutMs = timeoutMs;
}
@Override
@@ -310,7 +311,7 @@ public final class TransportImpl
try {
serviceAccept.clear();
sendServiceRequest(service.getName());
serviceAccept.await(timeout, TimeUnit.SECONDS);
serviceAccept.await(timeoutMs, TimeUnit.MILLISECONDS);
setService(service);
} finally {
serviceAccept.unlock();
@@ -567,7 +568,7 @@ public final class TransportImpl
try {
if (!close.isSet()) {
log.error("Dying because - {}", ex.toString());
log.error("Dying because - {}", ex);
final SSHException causeOfDeath = SSHException.chainer.chain(ex);

View File

@@ -19,8 +19,6 @@ import net.schmizz.sshj.Service;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.method.AuthMethod;
import java.util.Deque;
/** User authentication API. See RFC 4252. */
public interface UserAuth {
@@ -29,9 +27,7 @@ public interface UserAuth {
* {@link Service} that will be enabled on successful authentication.
* <p/>
* Authentication fails if there are no method available, i.e. if all the method failed or there were method
* available but could not be attempted because the server did not allow them. In this case, a {@code
* UserAuthException} is thrown with its cause as the last authentication failure. Other {@code UserAuthException}'s
* which may have been ignored may be accessed via {@link #getSavedExceptions()}.
* available but could not be attempted because the server did not allow them.
* <p/>
* Further attempts may also be made by catching {@code UserAuthException} and retrying with this method.
*
@@ -39,10 +35,12 @@ public interface UserAuth {
* @param nextService the service to set on successful authentication
* @param methods the {@link AuthMethod}'s to try
*
* @return whether authentication was successful
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
void authenticate(String username, Service nextService, Iterable<AuthMethod> methods)
boolean authenticate(String username, Service nextService, AuthMethod methods, int timeoutMs)
throws UserAuthException, TransportException;
/**
@@ -53,23 +51,13 @@ public interface UserAuth {
*/
String getBanner();
/** @return saved exceptions that might have been ignored because there were more authentication method available. */
Deque<UserAuthException> getSavedExceptions();
/** @return the {@code timeout} for a method to successfully authenticate before it is abandoned. */
int getTimeout();
/**
* @return whether authentication was partially successful. Some server's may be configured to require multiple
* authentications; and this value will be {@code true} if at least one of the method supplied succeeded.
*/
boolean hadPartialSuccess();
/**
* Set the {@code timeout} for any method to successfully authenticate before it is abandoned.
*
* @param timeout the timeout in seconds
*/
void setTimeout(int timeout);
/** The available authentication methods. This is only defined once an unsuccessful authentication has taken place. */
Iterable<String> getAllowedMethods();
}

View File

@@ -15,7 +15,7 @@
*/
package net.schmizz.sshj.userauth;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.Service;
import net.schmizz.sshj.common.DisconnectReason;
@@ -26,11 +26,10 @@ import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.method.AuthMethod;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** {@link UserAuth} implementation. */
@@ -38,85 +37,51 @@ public class UserAuthImpl
extends AbstractService
implements UserAuth {
private final Event<UserAuthException> authenticated
= new Event<UserAuthException>("authenticated", UserAuthException.chainer);
private final Promise<Boolean, UserAuthException> authenticated
= new Promise<Boolean, UserAuthException>("authenticated", UserAuthException.chainer);
// Externally available
private final Deque<UserAuthException> savedEx = new ArrayDeque<UserAuthException>();
private volatile String banner = "";
private volatile boolean partialSuccess;
private volatile boolean partialSuccess = false;
private volatile List<String> allowedMethods = new LinkedList<String>();
// Internal state
private Set<String> allowedMethods;
private AuthMethod currentMethod;
public UserAuthImpl(Transport trans) {
super("ssh-userauth", trans);
}
// synchronized for mutual exclusion; ensure only one authenticate() ever in progress
@Override
public synchronized void authenticate(final String username,
final Service nextService,
final Iterable<AuthMethod> methods)
public boolean authenticate(String username, Service nextService, AuthMethod method, int timeoutMs)
throws UserAuthException, TransportException {
savedEx.clear();
// Request "ssh-userauth" service (if not already active)
super.request();
if (allowedMethods == null) { // Assume all are allowed
allowedMethods = new HashSet<String>();
for (AuthMethod meth : methods)
allowedMethods.add(meth.getName());
}
final boolean outcome;
authenticated.lock();
try {
super.request(); // Request "ssh-userauth" service (if not already active)
final AuthParams authParams = makeAuthParams(username, nextService);
currentMethod = method;
currentMethod.init(makeAuthParams(username, nextService));
authenticated.clear();
log.debug("Trying `{}` auth...", method.getName());
currentMethod.request();
outcome = authenticated.retrieve(timeoutMs, TimeUnit.MILLISECONDS);
for (AuthMethod meth : methods) {
if (!allowedMethods.contains(meth.getName())) {
saveException(new UserAuthException(meth.getName() + " auth not allowed by server"));
continue;
}
log.debug("Trying `{}` auth...", meth.getName());
authenticated.clear();
currentMethod = meth;
try {
currentMethod.init(authParams);
currentMethod.request();
authenticated.await(timeout, TimeUnit.SECONDS);
} catch (UserAuthException e) {
log.debug("`{}` auth failed", meth.getName());
// Give other methods a shot
saveException(e);
continue;
}
log.debug("`{}` auth successful", meth.getName());
if (outcome) {
log.debug("`{}` auth successful", method.getName());
trans.setAuthenticated(); // So it can put delayed compression into force if applicable
trans.setService(nextService); // We aren't in charge anymore, next service is
return;
} else {
log.debug("`{}` auth failed", method.getName());
}
} finally {
currentMethod = null;
authenticated.unlock();
}
log.debug("Had {} saved exception(s)", savedEx.size());
throw new UserAuthException("Exhausted available authentication methods", savedEx.peek());
}
@Override
public synchronized Deque<UserAuthException> getSavedExceptions() {
return savedEx;
return outcome;
}
@Override
@@ -129,45 +94,54 @@ public class UserAuthImpl
return partialSuccess;
}
@Override
public Iterable<String> getAllowedMethods() {
return Collections.unmodifiableList(allowedMethods);
}
@Override
public void handle(Message msg, SSHPacket buf)
throws SSHException {
if (!msg.in(50, 80)) // ssh-userauth packets have message numbers between 50-80
throw new TransportException(DisconnectReason.PROTOCOL_ERROR);
switch (msg) {
authenticated.lock();
try {
switch (msg) {
case USERAUTH_BANNER: {
banner = buf.readString();
}
break;
case USERAUTH_SUCCESS: {
authenticated.set();
}
break;
case USERAUTH_FAILURE: {
allowedMethods.clear();
allowedMethods.addAll(Arrays.<String>asList(buf.readString().split(",")));
partialSuccess |= buf.readBoolean();
if (allowedMethods.contains(currentMethod.getName()) && currentMethod.shouldRetry()) {
currentMethod.request();
} else {
authenticated.deliverError(new UserAuthException(currentMethod.getName() + " auth failed"));
case USERAUTH_BANNER: {
banner = buf.readString();
}
}
break;
break;
default: {
log.debug("Asking `{}` method to handle {} packet", currentMethod.getName(), msg);
try {
currentMethod.handle(msg, buf);
} catch (UserAuthException e) {
authenticated.deliverError(e);
case USERAUTH_SUCCESS: {
authenticated.deliver(true);
}
}
break;
case USERAUTH_FAILURE: {
allowedMethods = Arrays.asList(buf.readString().split(","));
partialSuccess |= buf.readBoolean();
if (allowedMethods.contains(currentMethod.getName()) && currentMethod.shouldRetry()) {
currentMethod.request();
} else {
authenticated.deliver(false);
}
}
break;
default: {
log.debug("Asking `{}` method to handle {} packet", currentMethod.getName(), msg);
try {
currentMethod.handle(msg, buf);
} catch (UserAuthException e) {
authenticated.deliverError(e);
}
}
}
} finally {
authenticated.unlock();
}
}
@@ -198,9 +172,4 @@ public class UserAuthImpl
};
}
private void saveException(UserAuthException e) {
log.debug("Saving for later - {}", e.toString());
savedEx.push(e);
}
}

View File

@@ -16,7 +16,6 @@
package net.schmizz.sshj.transport.verification;
import net.schmizz.sshj.util.KeyUtil;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -25,7 +24,6 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
@@ -33,61 +31,46 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
public class OpenSSHKnownHostsTest {
// static {
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
// }
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Before
public void setUp()
throws IOException, GeneralSecurityException {
// kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
public File writeKnownHosts(String line)
throws IOException {
File known_hosts = temp.newFile("known_hosts");
FileWriter fileWriter = new FileWriter(known_hosts);
BufferedWriter writer = new BufferedWriter(fileWriter);
writer.write(line);
writer.write("\r\n");
writer.flush();
writer.close();
return known_hosts;
}
public File writeKnownHosts(String line) throws IOException {
File known_hosts = temp.newFile("known_hosts");
FileWriter fileWriter = new FileWriter(known_hosts);
BufferedWriter writer = new BufferedWriter(fileWriter);
writer.write(line);
writer.write("\r\n");
writer.flush();
writer.close();
return known_hosts;
}
@Test
public void shouldAddCommentForEmptyLine()
throws IOException {
File file = writeKnownHosts("");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
@Test
public void shouldAddCommentForEmptyLine() throws IOException {
File file = writeKnownHosts("");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
@Test
public void shouldAddCommentForCommentLine()
throws IOException {
File file = writeKnownHosts("# this is a comment");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
@Test
public void shouldAddCommentForCommentLine() throws IOException {
File file = writeKnownHosts("# this is a comment");
OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(file);
assertThat(openSSHKnownHosts.entries().size(), equalTo(1));
assertThat(openSSHKnownHosts.entries().get(0), instanceOf(OpenSSHKnownHosts.CommentEntry.class));
}
//
// @Test
// public void testLocalhostEntry()
// throws UnknownHostException, GeneralSecurityException {
//
// }
//
@Test
public void testSchmizzEntry()
throws IOException, GeneralSecurityException {
OpenSSHKnownHosts kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
throws IOException, GeneralSecurityException {
OpenSSHKnownHosts kh = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts"));
final PublicKey key = KeyUtil
.newRSAPublicKey(
"e8ff4797075a861db9d2319960a836b2746ada3da514955d2921f2c6a6c9895cbd557f604e43772b6303e3cab2ad82d83b21acdef4edb72524f9c2bef893335115acacfe2989bcbb2e978e4fedc8abc090363e205d975c1fdc35e55ba4daa4b5d5ab7a22c40f547a4a0fd1c683dfff10551c708ff8c34ea4e175cb9bf2313865308fa23601e5a610e2f76838be7ded3b4d3a2c49d2d40fa20db51d1cc8ab20d330bb0dadb88b1a12853f0ecb7c7632947b098dcf435a54566bcf92befd55e03ee2a57d17524cd3d59d6e800c66059067e5eb6edb81946b3286950748240ec9afa4389f9b62bc92f94ec0fba9e64d6dc2f455f816016a4c5f3d507382ed5d3365",