mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8cfc796af | ||
|
|
d9c0c6725c | ||
|
|
b2297c6b44 | ||
|
|
e10ad28f2f | ||
|
|
61fc00a90a | ||
|
|
c8ef7ff0ca | ||
|
|
e6c4f6ae69 | ||
|
|
3418df7a56 | ||
|
|
0ddd1f38c5 | ||
|
|
0ec6918d7a | ||
|
|
88a88c5dba | ||
|
|
6656214803 | ||
|
|
c781724028 | ||
|
|
eefaa26882 | ||
|
|
0d52441f01 | ||
|
|
9539ff6b7a | ||
|
|
1ced1d4fdc | ||
|
|
77924fd0be | ||
|
|
3f195649fa | ||
|
|
42a4358f5c | ||
|
|
61ce0f4868 | ||
|
|
777995af3b | ||
|
|
635cf88acd | ||
|
|
ce515fddcd | ||
|
|
9acff6202c | ||
|
|
cbd118e0b1 | ||
|
|
a8cf749d95 | ||
|
|
f3d4707ef0 | ||
|
|
4c5da634ad | ||
|
|
2fdafb76fd |
93
pom.xml
93
pom.xml
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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() + " >";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 + "}";
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user