mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-07 07:40:55 +03:00
Compare commits
40 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 | ||
|
|
80b164a299 | ||
|
|
75418f33b7 | ||
|
|
732de2b605 | ||
|
|
4fb56b868f | ||
|
|
a877ec1448 | ||
|
|
b44631ea97 | ||
|
|
a50962ba2f | ||
|
|
e8215e4af2 | ||
|
|
3c2bda3196 | ||
|
|
b13e22084b |
2
NOTICE
2
NOTICE
@@ -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
93
pom.xml
@@ -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>
|
||||
|
||||
|
||||
@@ -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";
|
||||
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,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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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