Compare commits

..

40 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
Shikhar Bhushan
80b164a299 [maven-release-plugin] prepare release v0.8.1 2012-07-08 09:55:22 -04:00
Shikhar Bhushan
75418f33b7 Next release to be 0.8.1 2012-07-08 09:50:43 -04:00
Shikhar Bhushan
732de2b605 make logs less chatty
#80
2012-07-05 00:10:40 +05:30
Shikhar Bhushan
4fb56b868f Per #77 use regex matching inside PasswordResponseProvider. Also remove the 'gaveAlready' state, we can leave such logic to the PasswordFinder to implement if needed. 2012-06-06 23:59:41 +01:00
Shikhar Bhushan
a877ec1448 AbstractChannel#close() should be no-op if already closed. Fixes #53. 2012-06-06 22:57:27 +01:00
Shikhar Bhushan
b44631ea97 Better naming for some AbstractChannel's lock/event members 2012-06-06 22:51:25 +01:00
Shikhar Bhushan
a50962ba2f Small cleanup 2012-05-19 11:18:30 +01:00
Shikhar Bhushan
e8215e4af2 Update NOTICE 2012-05-14 11:33:05 +02:00
Shikhar Bhushan
3c2bda3196 docfix - not part of the contract 2012-05-12 22:13:22 +01:00
Shikhar Bhushan
b13e22084b [maven-release-plugin] prepare for next development iteration 2012-05-12 21:48:45 +01:00
43 changed files with 434 additions and 428 deletions

2
NOTICE
View File

@@ -1,5 +1,5 @@
sshj - SSHv2 library for Java
Copyright 2010-2011 sshj contributors
Copyright 2010-2012 sshj contributors
This product includes code derived from software developed at
The Apache Software Foundation (http://www.apache.org/):

93
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>net.schmizz</groupId>
<artifactId>sshj</artifactId>
<packaging>bundle</packaging>
<version>0.8.0</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";
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,11 +691,10 @@ public class SSHClient
*/
protected void doKex()
throws TransportException {
assert trans.isRunning();
checkConnected();
final long start = System.currentTimeMillis();
trans.doKex();
log.info("Key exchange took {} seconds", (System.currentTimeMillis() - start) / 1000.0);
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

@@ -62,11 +62,11 @@ public class SecurityUtils {
public void run()
throws Exception {
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
LOG.info("Trying to register BouncyCastle as a JCE provider");
LOG.debug("Trying to register BouncyCastle as a JCE provider");
java.security.Security.addProvider(new BouncyCastleProvider());
MessageDigest.getInstance("MD5", BOUNCY_CASTLE);
KeyAgreement.getInstance("DH", BOUNCY_CASTLE);
LOG.info("Registration succeeded");
LOG.info("BouncyCastle registration succeeded");
} else
LOG.info("BouncyCastle already registered as a JCE provider");
securityProvider = BOUNCY_CASTLE;

View File

@@ -136,7 +136,7 @@ public class StreamCopier {
final double timeSeconds = (System.currentTimeMillis() - startTime) / 1000.0;
final double sizeKiB = count / 1024.0;
log.info("{} KiB transferred in {} seconds ({} KiB/s)", new Object[] { sizeKiB, timeSeconds, (sizeKiB / timeSeconds) });
log.debug("{} KiB transferred in {} seconds ({} KiB/s)", new Object[] { sizeKiB, timeSeconds, (sizeKiB / timeSeconds) });
if (length != -1 && read == -1)
throw new IOException("Encountered EOF, could not transfer " + length + " bytes");

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,11 +63,12 @@ public class ConnectionImpl
*/
public ConnectionImpl(Transport trans) {
super("ssh-connection", trans);
timeoutMs = trans.getTimeoutMs();
}
@Override
public void attach(Channel chan) {
log.info("Attaching `{}` channel (#{})", chan.getType(), chan.getID());
log.debug("Attaching `{}` channel (#{})", chan.getType(), chan.getID());
channels.put(chan.getID(), chan);
}
@@ -81,7 +84,7 @@ public class ConnectionImpl
@Override
public void forget(Channel chan) {
log.info("Forgetting `{}` channel (#{})", chan.getType(), chan.getID());
log.debug("Forgetting `{}` channel (#{})", chan.getType(), chan.getID());
channels.remove(chan.getID());
synchronized (internalSynchronizer) {
if (channels.isEmpty())
@@ -91,13 +94,13 @@ public class ConnectionImpl
@Override
public void forget(ForwardedChannelOpener opener) {
log.info("Forgetting opener for `{}` channels: {}", opener.getChannelType(), opener);
log.debug("Forgetting opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.remove(opener.getChannelType());
}
@Override
public void attach(ForwardedChannelOpener opener) {
log.info("Attaching opener for `{}` channels: {}", opener.getChannelType(), opener);
log.debug("Attaching opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.put(opener.getChannelType(), opener);
}
@@ -187,7 +190,7 @@ public class ConnectionImpl
byte[] specifics)
throws TransportException {
synchronized (globalReqPromises) {
log.info("Making global request for `{}`", name);
log.debug("Making global request for `{}`", name);
trans.write(new SSHPacket(Message.GLOBAL_REQUEST).putString(name)
.putBoolean(wantReply)
.putRawBytes(specifics));
@@ -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

@@ -81,11 +81,11 @@ public abstract class AbstractChannel
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new LinkedList<Event<ConnectionException>>();
/* The lock used by to create the open & close events */
private final ReentrantLock lock = new ReentrantLock();
private final ReentrantLock openCloseLock = new ReentrantLock();
/** Channel open event */
protected final Event<ConnectionException> open;
protected final Event<ConnectionException> openEvent;
/** Channel close event */
protected final Event<ConnectionException> close;
protected final Event<ConnectionException> closeEvent;
/* Access to these fields should be synchronized using this object */
private boolean eofSent;
@@ -114,15 +114,15 @@ public abstract class AbstractChannel
lwin = new Window.Local(conn.getWindowSize(), conn.getMaxPacketSize());
in = new ChannelInputStream(this, trans, lwin);
open = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, lock);
close = new Event<ConnectionException>("chan#" + id + " / " + "close", ConnectionException.chainer, lock);
openEvent = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, openCloseLock);
closeEvent = new Event<ConnectionException>("chan#" + id + " / " + "close", ConnectionException.chainer, openCloseLock);
}
protected void init(int recipient, long remoteWinSize, long remoteMaxPacketSize) {
this.recipient = recipient;
rwin = new Window.Remote(remoteWinSize, (int) Math.min(remoteMaxPacketSize, REMOTE_MAX_PACKET_SIZE_CEILING));
out = new ChannelOutputStream(this, trans, rwin);
log.info("Initialized - {}", this);
log.debug("Initialized - {}", this);
}
@Override
@@ -220,7 +220,7 @@ public abstract class AbstractChannel
private void gotClose()
throws TransportException {
log.info("Got close");
log.debug("Got close");
try {
closeAllStreams();
sendClose();
@@ -238,7 +238,7 @@ public abstract class AbstractChannel
public void notifyError(SSHException error) {
log.debug("Channel #{} got notified of {}", getID(), error.toString());
ErrorDeliveryUtil.alertEvents(error, open, close);
ErrorDeliveryUtil.alertEvents(error, openEvent, closeEvent);
ErrorDeliveryUtil.alertEvents(error, chanReqResponseEvents);
in.notifyError(error);
@@ -256,35 +256,37 @@ public abstract class AbstractChannel
@Override
public void close()
throws ConnectionException, TransportException {
lock.lock();
openCloseLock.lock();
try {
try {
sendClose();
} catch (TransportException e) {
if (!close.inError())
throw e;
if (isOpen()) {
try {
sendClose();
} catch (TransportException e) {
if (!closeEvent.inError())
throw e;
}
closeEvent.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
close.await(conn.getTimeout(), TimeUnit.SECONDS);
} finally {
lock.unlock();
openCloseLock.unlock();
}
}
public void join()
throws ConnectionException {
close.await();
closeEvent.await();
}
public void join(int timeout, TimeUnit unit)
throws ConnectionException {
close.await(timeout, unit);
closeEvent.await(timeout, unit);
}
protected synchronized void sendClose()
throws TransportException {
try {
if (!closeRequested) {
log.info("Sending close");
log.debug("Sending close");
trans.write(newBuffer(Message.CHANNEL_CLOSE));
}
} finally {
@@ -294,11 +296,11 @@ public abstract class AbstractChannel
@Override
public synchronized boolean isOpen() {
lock.lock();
openCloseLock.lock();
try {
return open.isSet() && !close.isSet() && !closeRequested;
return openEvent.isSet() && !closeEvent.isSet() && !closeRequested;
} finally {
lock.unlock();
openCloseLock.unlock();
}
}
@@ -311,7 +313,7 @@ public abstract class AbstractChannel
} catch (Buffer.BufferException be) {
throw new ConnectionException(be);
}
log.info("Got chan request for `{}`", reqType);
log.debug("Got chan request for `{}`", reqType);
handleRequest(reqType, buf);
}
@@ -323,13 +325,13 @@ public abstract class AbstractChannel
} catch (Buffer.BufferException be) {
throw new ConnectionException(be);
}
log.info("Received window adjustment for {} bytes", howMuch);
log.debug("Received window adjustment for {} bytes", howMuch);
rwin.expand(howMuch);
}
protected void finishOff() {
conn.forget(this);
close.set();
closeEvent.set();
}
protected void gotExtendedData(SSHPacket buf)
@@ -369,7 +371,7 @@ public abstract class AbstractChannel
protected Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
Buffer.PlainBuffer reqSpecific)
throws TransportException {
log.info("Sending channel request for `{}`", reqType);
log.debug("Sending channel request for `{}`", reqType);
synchronized (chanReqResponseEvents) {
trans.write(
newBuffer(Message.CHANNEL_REQUEST)
@@ -381,7 +383,7 @@ public abstract class AbstractChannel
Event<ConnectionException> responseEvent = null;
if (wantReply) {
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType,
ConnectionException.chainer);
ConnectionException.chainer);
chanReqResponseEvents.add(responseEvent);
}
return responseEvent;
@@ -399,13 +401,13 @@ public abstract class AbstractChannel
responseEvent.deliverError(new ConnectionException("Request failed"));
} else
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR,
"Received response to channel request when none was requested");
"Received response to channel request when none was requested");
}
}
private synchronized void gotEOF()
throws TransportException {
log.info("Got EOF");
log.debug("Got EOF");
eofGot = true;
eofInputStreams();
if (eofSent)
@@ -422,7 +424,7 @@ public abstract class AbstractChannel
throws TransportException {
try {
if (!closeRequested && !eofSent) {
log.info("Sending EOF");
log.debug("Sending EOF");
trans.write(newBuffer(Message.CHANNEL_EOF));
if (eofGot)
sendClose();

View File

@@ -161,7 +161,7 @@ public final class ChannelInputStream
synchronized (win) {
final long adjustment = win.neededAdjustment();
if (adjustment > 0) {
log.info("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST to #{} for {} bytes", chan.getRecipient(), adjustment);
log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST to #{} for {} bytes", chan.getRecipient(), adjustment);
trans.write(new SSHPacket(Message.CHANNEL_WINDOW_ADJUST)
.putUInt32(chan.getRecipient()).putUInt32(adjustment));
win.expand(adjustment);

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());
open.await(conn.getTimeout(), TimeUnit.SECONDS);
openEvent.await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS);
}
private void gotOpenConfirmation(SSHPacket buf)
@@ -75,13 +75,13 @@ public abstract class AbstractDirectChannel
} catch (Buffer.BufferException be) {
throw new ConnectionException(be);
}
open.set();
openEvent.set();
}
private void gotOpenFailure(SSHPacket buf)
throws ConnectionException {
try {
open.deliverError(new OpenFailException(getType(), buf.readUInt32AsInt(), buf.readString()));
openEvent.deliverError(new OpenFailException(getType(), buf.readUInt32AsInt(), buf.readString()));
} catch (Buffer.BufferException be) {
throw new ConnectionException(be);
}

View File

@@ -129,10 +129,10 @@ public class LocalPortForwarder {
log.info("Listening on {}", serverSocket.getLocalSocketAddress());
while (!Thread.currentThread().isInterrupted()) {
final Socket socket = serverSocket.accept();
log.info("Got connection from {}", socket.getRemoteSocketAddress());
log.debug("Got connection from {}", socket.getRemoteSocketAddress());
openChannel(socket).start();
}
log.info("Interrupted!");
log.debug("Interrupted!");
}
}

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

@@ -66,20 +66,20 @@ public abstract class AbstractForwardedChannel
@Override
public void confirm()
throws TransportException {
log.info("Confirming `{}` channel #{}", getType(), getID());
log.debug("Confirming `{}` channel #{}", getType(), getID());
// Must ensure channel is attached before confirming, data could start coming in immediately!
conn.attach(this);
trans.write(newBuffer(Message.CHANNEL_OPEN_CONFIRMATION)
.putUInt32(getID())
.putUInt32(getLocalWinSize())
.putUInt32(getLocalMaxPacketSize()));
open.set();
openEvent.set();
}
@Override
public void reject(Reason reason, String message)
throws TransportException {
log.info("Rejecting `{}` channel: {}", getType(), message);
log.debug("Rejecting `{}` channel: {}", getType(), message);
conn.sendOpenFailure(getRecipient(), reason, message);
}

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

@@ -44,7 +44,7 @@ public class SocketForwardingConnectListener
@Override
public void gotConnect(Channel.Forwarded chan)
throws IOException {
log.info("New connection from {}:{}", chan.getOriginatorIP(), chan.getOriginatorPort());
log.debug("New connection from {}:{}", chan.getOriginatorIP(), chan.getOriginatorPort());
final Socket sock = new Socket();
sock.setSendBufferSize(chan.getLocalMaxPacketSize());

View File

@@ -15,16 +15,15 @@
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.schmizz.concurrent.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketReader
extends Thread {
@@ -57,27 +56,25 @@ public class PacketReader
throws IOException {
readIntoBuffer(lenBuf, 0, lenBuf.length);
return (int) (lenBuf[0] << 24 & 0xff000000L
final long len = (lenBuf[0] << 24 & 0xff000000L
| lenBuf[1] << 16 & 0x00ff0000L
| lenBuf[2] << 8 & 0x0000ff00L
| lenBuf[3] & 0x000000ffL);
if (len > SFTPPacket.MAX_SIZE) {
throw new IllegalStateException("Invalid packet: indicated length "+len+" too large");
}
return (int) len;
}
public SFTPPacket<Response> readPacket()
throws IOException {
int len = getPacketLength();
if (len > SFTPPacket.MAX_SIZE) {
throw new IllegalStateException("Invalid packet: indicated length "+len+" too large");
}
packet.rpos(0);
packet.wpos(0);
final int len = getPacketLength();
packet.clear();
packet.ensureCapacity(len);
readIntoBuffer(packet.array(), 0, len);
packet.wpos(len);
return packet;
}
@@ -106,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 {
@@ -48,8 +49,10 @@ public abstract class RemoteResource
@Override
public void close()
throws IOException {
log.info("Closing `{}`", this);
requester.doRequest(newRequest(PacketType.CLOSE)).ensureStatusPacketIsOK();
log.debug("Closing `{}`", this);
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;
@@ -81,7 +82,7 @@ public class SFTPEngine
throw new SFTPException("Expected INIT packet, received: " + type);
operativeVersion = response.readUInt32AsInt();
log.info("Server version {}", operativeVersion);
log.debug("Server version {}", operativeVersion);
if (MAX_SUPPORTED_VERSION < operativeVersion)
throw new SFTPException("Server reported incompatible protocol version: " + operativeVersion);
@@ -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

@@ -31,7 +31,7 @@ public class StatefulSFTPClient
throws IOException {
super(engine);
this.cwd = getSFTPEngine().canonicalize(".");
log.info("Start dir = {}", cwd);
log.debug("Start dir = {}", cwd);
}
private synchronized String cwdify(String path) {
@@ -40,11 +40,12 @@ 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");
}
log.info("CWD = {}", cwd);
cwd = targetCwd;
log.debug("CWD = {}", cwd);
}
public synchronized List<RemoteResourceInfo> ls()

View File

@@ -166,7 +166,7 @@ final class Decoder
}
if (isInvalidPacketLength(len)) { // Check packet length validity
log.info("Error decoding packet (invalid length) {}", inputBuffer.printHex());
log.error("Error decoding packet (invalid length) {}", inputBuffer.printHex());
throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len);
}

View File

@@ -80,7 +80,7 @@ final class Heartbeater
while (!isInterrupted()) {
final int hi = getPositiveInterval();
if (trans.isRunning()) {
log.info("Sending heartbeat since {} seconds elapsed", hi);
log.debug("Sending heartbeat since {} seconds elapsed", hi);
trans.write(new SSHPacket(Message.IGNORE));
}
Thread.sleep(hi * 1000);

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()
@@ -192,7 +192,7 @@ final class KeyExchanger
*/
private void sendKexInit()
throws TransportException {
log.info("Sending SSH_MSG_KEXINIT");
log.debug("Sending SSH_MSG_KEXINIT");
clientProposal = new Proposal(transport.getConfig());
transport.write(clientProposal.getPacket());
kexInitSent.set();
@@ -200,7 +200,7 @@ final class KeyExchanger
private void sendNewKeys()
throws TransportException {
log.info("Sending SSH_MSG_NEWKEYS");
log.debug("Sending SSH_MSG_NEWKEYS");
transport.write(new SSHPacket(Message.NEWKEYS));
}
@@ -354,20 +354,20 @@ final class KeyExchanger
case KEXINIT:
ensureReceivedMatchesExpected(msg, Message.KEXINIT);
log.info("Received SSH_MSG_KEXINIT");
log.debug("Received SSH_MSG_KEXINIT");
startKex(false); // Will start key exchange if not already on
/*
* 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;
case FOLLOWUP:
ensureKexOngoing();
log.info("Received kex followup data");
log.debug("Received kex followup data");
try {
if (kex.next(msg, buf)) {
verifyHost(kex.getHostKey());
@@ -382,7 +382,7 @@ final class KeyExchanger
case NEWKEYS:
ensureReceivedMatchesExpected(msg, Message.NEWKEYS);
ensureKexOngoing();
log.info("Received SSH_MSG_NEWKEYS");
log.debug("Received SSH_MSG_NEWKEYS");
gotNewKeys();
setKexDone();
expected = Expected.KEXINIT;

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

@@ -90,7 +90,7 @@ public final class TransportImpl
private final DisconnectListener nullDisconnectListener = new DisconnectListener() {
@Override
public void notifyDisconnect(DisconnectReason reason) {
log.debug("Default disconnect listener - {}", reason);
log.info("Disconnected - {}", reason);
}
};
@@ -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
@@ -299,7 +300,7 @@ public final class TransportImpl
if (service == null)
service = nullService;
log.info("Setting active service to {}", service.getName());
log.debug("Setting active service to {}", service.getName());
this.service = service;
}
@@ -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();
@@ -346,7 +347,7 @@ public final class TransportImpl
public long sendUnimplemented()
throws TransportException {
final long seq = decoder.getSequenceNumber();
log.info("Sending SSH_MSG_UNIMPLEMENTED for packet #{}", seq);
log.debug("Sending SSH_MSG_UNIMPLEMENTED for packet #{}", seq);
return write(new SSHPacket(Message.UNIMPLEMENTED).putUInt32(seq));
}
@@ -483,7 +484,7 @@ public final class TransportImpl
break;
}
case IGNORE: {
log.info("Received SSH_MSG_IGNORE");
log.debug("Received SSH_MSG_IGNORE");
break;
}
case UNIMPLEMENTED: {
@@ -508,7 +509,7 @@ public final class TransportImpl
try {
final boolean display = buf.readBoolean();
final String message = buf.readString();
log.info("Received SSH_MSG_DEBUG (display={}) '{}'", display, message);
log.debug("Received SSH_MSG_DEBUG (display={}) '{}'", display, message);
} catch (Buffer.BufferException be) {
throw new TransportException(be);
}
@@ -549,7 +550,7 @@ public final class TransportImpl
private void gotUnimplemented(SSHPacket buf)
throws SSHException {
long seqNum = buf.readUInt32();
log.info("Received SSH_MSG_UNIMPLEMENTED #{}", seqNum);
log.debug("Received SSH_MSG_UNIMPLEMENTED #{}", seqNum);
if (kexer.isKexOngoing())
throw new TransportException("Received SSH_MSG_UNIMPLEMENTED while exchanging keys");
getService().notifyUnimplemented(seqNum);
@@ -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

@@ -107,7 +107,7 @@ public abstract class AbstractDHG
sha1.init();
initDH(dh);
log.info("Sending SSH_MSG_KEXDH_INIT");
log.debug("Sending SSH_MSG_KEXDH_INIT");
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(dh.getE()));
}
@@ -117,7 +117,7 @@ public abstract class AbstractDHG
if (msg != Message.KEXDH_31)
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "Unexpected packet: " + msg);
log.info("Received SSH_MSG_KEXDH_REPLY");
log.debug("Received SSH_MSG_KEXDH_REPLY");
final byte[] K_S;
final BigInteger f;
final byte[] sig; // signature sent by server

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.info("Trying `{}` auth...", meth.getName());
authenticated.clear();
currentMethod = meth;
try {
currentMethod.init(authParams);
currentMethod.request();
authenticated.await(timeout, TimeUnit.SECONDS);
} catch (UserAuthException e) {
log.info("`{}` auth failed", meth.getName());
// Give other methods a shot
saveException(e);
continue;
}
log.info("`{}` 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

@@ -74,7 +74,7 @@ public class AuthKeyboardInteractive
for (int i = 0; i < numPrompts; i++) {
final String prompt = buf.readString();
final boolean echo = buf.readBoolean();
log.info("Requesting response for challenge `{}`; echo={}", prompt, echo);
log.debug("Requesting response for challenge `{}`; echo={}", prompt, echo);
userReplies[i] = new CharArrWrap(provider.getResponse(prompt, echo));
}
} catch (Buffer.BufferException be) {

View File

@@ -37,7 +37,7 @@ public class AuthPassword
public SSHPacket buildReq()
throws UserAuthException {
final AccountResource accountResource = makeAccountResource();
log.info("Requesting password for {}", accountResource);
log.debug("Requesting password for {}", accountResource);
return super.buildReq() // the generic stuff
.putBoolean(false) // no, we are not responding to a CHANGEREQ
.putSensitiveString(pwdf.reqPassword(accountResource));

View File

@@ -20,33 +20,31 @@ import net.schmizz.sshj.userauth.password.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
public class PasswordResponseProvider
implements ChallengeResponseProvider {
public static final Pattern DEFAULT_PROMPT_PATTERN = Pattern.compile(".*[pP]assword:\\s?\\z");
private final Logger log = LoggerFactory.getLogger(getClass());
private static final char[] EMPTY_RESPONSE = new char[0];
private static final Collection<String> DEFAULT_ACCEPTABLE_PROMPTS =
Collections.unmodifiableCollection(Arrays.asList("Password:", "Password: "));
private final Collection<String> acceptablePrompts;
private final Pattern promptPattern;
private final PasswordFinder pwdf;
private Resource resource;
private boolean gaveAlready;
public PasswordResponseProvider(PasswordFinder pwdf) {
this(pwdf, DEFAULT_ACCEPTABLE_PROMPTS);
this(pwdf, DEFAULT_PROMPT_PATTERN);
}
public PasswordResponseProvider(PasswordFinder pwdf, Collection<String> acceptablePrompts) {
public PasswordResponseProvider(PasswordFinder pwdf, Pattern promptPattern) {
this.pwdf = pwdf;
this.acceptablePrompts = acceptablePrompts;
this.promptPattern = promptPattern;
}
@Override
@@ -62,8 +60,7 @@ public class PasswordResponseProvider
@Override
public char[] getResponse(String prompt, boolean echo) {
if (!gaveAlready && !echo && acceptablePrompts.contains(prompt)) {
gaveAlready = true;
if (!echo && promptPattern.matcher(prompt).matches()) {
return pwdf.reqPassword(resource);
}
return EMPTY_RESPONSE;
@@ -74,4 +71,4 @@ public class PasswordResponseProvider
return pwdf.shouldRetry(resource);
}
}
}

View File

@@ -45,8 +45,6 @@ public interface FileTransfer {
/**
* Upload {@code localFile} to {@code remotePath}.
* <p/>
* Attributes will be set on {@code remotePath} based on the {@code localFile}.
*
* @param localFile
* @param remotePath
@@ -58,8 +56,6 @@ public interface FileTransfer {
/**
* Download {@code remotePath} to {@code localFile}.
* <p/>
* Attributes will be set on {@code localFile} based on the {@code remotePath}'s attributes.
*
* @param localFile
* @param remotePath

View File

@@ -23,20 +23,20 @@ public class LoggingTransferListener
@Override
public TransferListener directory(String name) {
log.info("started transferring directory `{}`", name);
log.debug("started transferring directory `{}`", name);
return new LoggingTransferListener(relPath + name + "/");
}
@Override
public StreamCopier.Listener file(final String name, final long size) {
final String path = relPath + name;
log.info("started transferring file `{}` ({} bytes)", path, size);
log.debug("started transferring file `{}` ({} bytes)", path, size);
return new StreamCopier.Listener() {
@Override
public void reportProgress(long transferred)
throws IOException {
if (log.isDebugEnabled()) {
log.debug("transferred {}% of `{}`", ((transferred * 100) / size), path);
if (log.isTraceEnabled()) {
log.trace("transferred {}% of `{}`", ((transferred * 100) / size), path);
}
}
};

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",