mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-07 07:40:55 +03:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be11cbb848 | ||
|
|
43b0599e1f | ||
|
|
b218186cae | ||
|
|
184236c3d5 | ||
|
|
cb1d773659 | ||
|
|
378665cb46 | ||
|
|
a5272dc413 | ||
|
|
60552fd001 | ||
|
|
ef082c668a | ||
|
|
e66386eb1c | ||
|
|
0937ec9800 | ||
|
|
4b2f42804e | ||
|
|
01765d24d2 | ||
|
|
1a2351c5ee | ||
|
|
1cec011401 | ||
|
|
52338c13cb | ||
|
|
09cf21f61a | ||
|
|
04c2e7b6b8 | ||
|
|
822f196dd8 | ||
|
|
a88a574b10 | ||
|
|
5cd6986355 | ||
|
|
b5d206bbcb | ||
|
|
4eae26c551 | ||
|
|
b950f88f52 | ||
|
|
3267860db4 | ||
|
|
d6eb5a040e | ||
|
|
21da5b9f65 | ||
|
|
6b66a952d4 | ||
|
|
aa4faf3f25 | ||
|
|
4be02450dd | ||
|
|
0cec27c28e | ||
|
|
4384367a1b | ||
|
|
4549648a76 | ||
|
|
20e2161022 | ||
|
|
fb0f3afa17 | ||
|
|
114c2bb424 | ||
|
|
079bde5dbf | ||
|
|
eaee42b017 | ||
|
|
8b61d96808 | ||
|
|
73fcc81e83 | ||
|
|
0f7926d4fa | ||
|
|
ca6f15650a | ||
|
|
eb78dc499d | ||
|
|
a852f33a15 | ||
|
|
ccabc1a20c | ||
|
|
cb2986d32e | ||
|
|
dc70f08e45 | ||
|
|
bf68ec18b2 | ||
|
|
7e78260ca9 | ||
|
|
27c60cee60 | ||
|
|
551b8b4fcf | ||
|
|
fd591e70be |
@@ -1,3 +1,8 @@
|
||||
Shikhar Bhushan <shikhar@schmizz.net>
|
||||
Cyril Ledru <cledru@keynectis.net>
|
||||
Incendium <incendium@gmail.com>
|
||||
Incendium <incendium@gmail.com>
|
||||
Philip Langdale <philipl@cloudera.com>
|
||||
Adar Dembo <adar@cloudera.com>
|
||||
Ioannis Canellos <iocanel@gmail.com>
|
||||
Neil Prosser <neil.prosser@gmail.com>
|
||||
hierynomus <jeroen@hierynomus.com>
|
||||
|
||||
@@ -39,6 +39,11 @@ Dependencies
|
||||
|
||||
Java 6+. slf4j_ is required. bouncycastle_ is highly recommended and required for using some of the crypto algorithms. jzlib_ is required for using zlib compression.
|
||||
|
||||
Bugs, questions
|
||||
----------------
|
||||
|
||||
`Issue tracker <https://github.com/shikhar/sshj/issues>`_
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
51
pom.xml
51
pom.xml
@@ -5,8 +5,8 @@
|
||||
|
||||
<groupId>net.schmizz</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.4.0</version>
|
||||
<packaging>bundle</packaging>
|
||||
<version>0.6.0</version>
|
||||
|
||||
<name>sshj</name>
|
||||
<description>SSHv2 library for Java</description>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<version>1.45</version>
|
||||
<version>1.46</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.1</version>
|
||||
<version>4.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -60,21 +60,27 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-core</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>0.9.24</version>
|
||||
<version>0.9.29</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>0.9.24</version>
|
||||
<version>0.9.29</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>
|
||||
|
||||
|
||||
@@ -96,6 +102,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>examples/*.java</exclude>
|
||||
@@ -107,11 +114,14 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<version>2.1</version>
|
||||
<configuration>
|
||||
<mavenExecutorId>forked-path</mavenExecutorId>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.2-beta-5</version>
|
||||
<version>2.2.1</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/assemble/examples.xml</descriptor>
|
||||
@@ -130,6 +140,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
@@ -142,6 +153,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.8</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
@@ -154,6 +166,25 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<version>2.3.5</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)",
|
||||
org.bouncycastle*;version="[1.4,2)",
|
||||
*
|
||||
</Import-Package>
|
||||
<Export-Package>net.schmizz.*</Export-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -209,7 +240,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.0</version>
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<passphrase>${gpg.passphrase}</passphrase>
|
||||
</configuration>
|
||||
|
||||
@@ -42,7 +42,7 @@ public class LocalPF {
|
||||
* google.com:80
|
||||
*/
|
||||
ssh.newLocalPortForwarder(new InetSocketAddress("localhost", 8080), "google.com", 80)
|
||||
.listen();
|
||||
.listen();
|
||||
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
|
||||
@@ -30,12 +30,12 @@ public class ErrorDeliveryUtil {
|
||||
}
|
||||
|
||||
public static void alertEvents(Throwable x, Event... events) {
|
||||
for (Event e: events)
|
||||
for (Event e : events)
|
||||
e.deliverError(x);
|
||||
}
|
||||
|
||||
public static void alertEvents(Throwable x, Collection<? extends Event> events) {
|
||||
for (Event e: events)
|
||||
for (Event e : events)
|
||||
e.deliverError(x);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ public class Event<T extends Throwable> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this event is in a 'set' state. An event is set by a call to {@link set()} or {@link
|
||||
* deliverError}
|
||||
* @return whether this event is in a 'set' state. An event is set by a call to {@link #set} or {@link
|
||||
* #deliverError}
|
||||
*/
|
||||
public boolean isSet() {
|
||||
return promise.isDelivered();
|
||||
|
||||
@@ -18,7 +18,8 @@ package net.schmizz.sshj;
|
||||
import net.schmizz.sshj.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
|
||||
public class AndroidConfig extends DefaultConfig {
|
||||
public class AndroidConfig
|
||||
extends DefaultConfig {
|
||||
|
||||
@Override
|
||||
protected void initRandomFactory(boolean ignored) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.List;
|
||||
* {@link Compression}, {@link MAC}, {@link Signature}, {@link Random}, and {@link FileKeyProvider}.
|
||||
*/
|
||||
public interface Config {
|
||||
|
||||
/**
|
||||
* Retrieve the list of named factories for {@code Cipher}.
|
||||
*
|
||||
|
||||
@@ -91,7 +91,7 @@ public class DefaultConfig
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final String VERSION = "SSHJ_0_3";
|
||||
private static final String VERSION = "SSHJ_0_6_0";
|
||||
|
||||
public DefaultConfig() {
|
||||
setVersion(VERSION);
|
||||
@@ -137,7 +137,7 @@ public class DefaultConfig
|
||||
boolean warn = false;
|
||||
// Ref. https://issues.apache.org/jira/browse/SSHD-24
|
||||
// "AES256 and AES192 requires unlimited cryptography extension"
|
||||
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext();) {
|
||||
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext(); ) {
|
||||
final Factory.Named<Cipher> f = i.next();
|
||||
try {
|
||||
final Cipher c = f.create();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
@@ -62,6 +61,7 @@ import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
@@ -113,7 +113,7 @@ import java.util.List;
|
||||
*/
|
||||
public class SSHClient
|
||||
extends SocketClient
|
||||
implements SessionFactory {
|
||||
implements Closeable, SessionFactory {
|
||||
|
||||
/** Default port for SSH */
|
||||
public static final int DEFAULT_PORT = 22;
|
||||
@@ -441,7 +441,7 @@ public class SSHClient
|
||||
/**
|
||||
* Utility function for createing a {@link KeyProvider} instance from given location on the file system. Creates a
|
||||
* one-off {@link PasswordFinder} using {@link PasswordUtils#createOneOff(char[])}, and calls {@link
|
||||
* #loadKeys(String,PasswordFinder)}.
|
||||
* #loadKeys(String, PasswordFinder)}.
|
||||
*
|
||||
* @param location location of the key file
|
||||
* @param passphrase passphrase as a char-array
|
||||
@@ -501,6 +501,33 @@ public class SSHClient
|
||||
return loadKeys(location, passphrase.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyProvider} instance from passed strings. Currently only PKCS8 format private key files are
|
||||
* supported (OpenSSH uses this format).
|
||||
* <p/>
|
||||
*
|
||||
* @param privateKey the private key as a string
|
||||
* @param publicKey the public key as a string if it's not included with the private key
|
||||
* @param passwordFinder the {@link PasswordFinder} that can supply the passphrase for decryption (may be {@code
|
||||
* null} in case keyfile is not encrypted)
|
||||
*
|
||||
* @return the key provider ready for use in authentication
|
||||
*
|
||||
* @throws SSHException if there was no suitable key provider available for the file format; typically because
|
||||
* BouncyCastle is not in the classpath
|
||||
* @throws IOException if the key file format is not known, etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder)
|
||||
throws IOException {
|
||||
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null);
|
||||
final FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format
|
||||
.toString());
|
||||
if (fkp == null)
|
||||
throw new SSHException("No provider available for " + format + " key file");
|
||||
fkp.init(privateKey, publicKey, passwordFinder);
|
||||
return fkp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts loading the user's {@code known_hosts} file from the default locations, i.e. {@code ~/.ssh/known_hosts}
|
||||
* and {@code ~/.ssh/known_hosts2} on most platforms. Adds the resulting {@link OpenSSHKnownHosts} object as a host
|
||||
@@ -661,4 +688,15 @@ public class SSHClient
|
||||
log.info("Key exchange took {} seconds", (System.currentTimeMillis() - start) / 1000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #disconnect()}.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +32,8 @@ public class Base64 {
|
||||
* @see Base64
|
||||
* @since 1.3
|
||||
*/
|
||||
public static class InputStream extends java.io.FilterInputStream {
|
||||
public static class InputStream
|
||||
extends java.io.FilterInputStream {
|
||||
|
||||
private final boolean encode; // Encoding or decoding
|
||||
private int position; // Current position in the buffer
|
||||
@@ -99,7 +100,8 @@ public class Base64 {
|
||||
* @since 1.3
|
||||
*/
|
||||
@Override
|
||||
public int read() throws java.io.IOException {
|
||||
public int read()
|
||||
throws java.io.IOException {
|
||||
|
||||
// Do we need to get data?
|
||||
if (position < 0)
|
||||
@@ -197,7 +199,8 @@ public class Base64 {
|
||||
* @since 1.3
|
||||
*/
|
||||
@Override
|
||||
public int read(byte[] dest, int off, int len) throws java.io.IOException {
|
||||
public int read(byte[] dest, int off, int len)
|
||||
throws java.io.IOException {
|
||||
int i;
|
||||
int b;
|
||||
for (i = 0; i < len; i++) {
|
||||
@@ -222,7 +225,8 @@ public class Base64 {
|
||||
* @see Base64
|
||||
* @since 1.3
|
||||
*/
|
||||
public static class OutputStream extends java.io.FilterOutputStream {
|
||||
public static class OutputStream
|
||||
extends java.io.FilterOutputStream {
|
||||
|
||||
private final boolean encode;
|
||||
private int position;
|
||||
@@ -289,7 +293,8 @@ public class Base64 {
|
||||
* @since 1.3
|
||||
*/
|
||||
@Override
|
||||
public void close() throws java.io.IOException {
|
||||
public void close()
|
||||
throws java.io.IOException {
|
||||
// 1. Ensure that pending characters are written
|
||||
flush();
|
||||
|
||||
@@ -308,7 +313,8 @@ public class Base64 {
|
||||
* @since 2.3
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws java.io.IOException {
|
||||
public void flush()
|
||||
throws java.io.IOException {
|
||||
flushBase64();
|
||||
super.flush();
|
||||
}
|
||||
@@ -318,7 +324,8 @@ public class Base64 {
|
||||
*
|
||||
* @throws java.io.IOException if there's an error.
|
||||
*/
|
||||
public void flushBase64() throws java.io.IOException {
|
||||
public void flushBase64()
|
||||
throws java.io.IOException {
|
||||
if (position > 0)
|
||||
if (encode) {
|
||||
out.write(encode3to4(b4, buffer, position, options));
|
||||
@@ -346,7 +353,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there's an error flushing
|
||||
* @since 1.5.1
|
||||
*/
|
||||
public void suspendEncoding() throws java.io.IOException {
|
||||
public void suspendEncoding()
|
||||
throws java.io.IOException {
|
||||
flushBase64();
|
||||
suspendEncoding = true;
|
||||
} // end suspendEncoding
|
||||
@@ -361,7 +369,8 @@ public class Base64 {
|
||||
* @since 1.3
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
|
||||
public void write(byte[] theBytes, int off, int len)
|
||||
throws java.io.IOException {
|
||||
// Encoding suspended?
|
||||
if (suspendEncoding) {
|
||||
super.out.write(theBytes, off, len);
|
||||
@@ -383,7 +392,8 @@ public class Base64 {
|
||||
* @since 1.3
|
||||
*/
|
||||
@Override
|
||||
public void write(int theByte) throws java.io.IOException {
|
||||
public void write(int theByte)
|
||||
throws java.io.IOException {
|
||||
// Encoding suspended?
|
||||
if (suspendEncoding) {
|
||||
super.out.write(theByte);
|
||||
@@ -673,7 +683,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException If bogus characters exist in source data
|
||||
* @since 1.3
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException {
|
||||
public static byte[] decode(byte[] source, int off, int len, int options)
|
||||
throws java.io.IOException {
|
||||
|
||||
// Lots of error checking and exception throwing
|
||||
if (source == null)
|
||||
@@ -725,7 +736,7 @@ public class Base64 {
|
||||
else
|
||||
// There's a bad input character in the Base64 stream.
|
||||
throw new java.io.IOException(String.format("Bad Base64 input character '%c' in array position %d",
|
||||
source[i], i));
|
||||
source[i], i));
|
||||
} // each input character
|
||||
|
||||
byte[] out = new byte[outBuffPosn];
|
||||
@@ -743,7 +754,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException If there is a problem
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s) throws java.io.IOException {
|
||||
public static byte[] decode(String s)
|
||||
throws java.io.IOException {
|
||||
return decode(s, NO_OPTIONS);
|
||||
}
|
||||
|
||||
@@ -759,7 +771,8 @@ public class Base64 {
|
||||
* @throws NullPointerException if <tt>s</tt> is null
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s, int options) throws java.io.IOException {
|
||||
public static byte[] decode(String s, int options)
|
||||
throws java.io.IOException {
|
||||
|
||||
if (s == null)
|
||||
throw new NullPointerException("Input string was null.");
|
||||
@@ -833,7 +846,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there is an error
|
||||
* @since 2.2
|
||||
*/
|
||||
public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException {
|
||||
public static void decodeFileToFile(String infile, String outfile)
|
||||
throws java.io.IOException {
|
||||
|
||||
byte[] decoded = Base64.decodeFromFile(infile);
|
||||
java.io.OutputStream out = null;
|
||||
@@ -864,7 +878,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there is an error
|
||||
* @since 2.1
|
||||
*/
|
||||
public static byte[] decodeFromFile(String filename) throws java.io.IOException {
|
||||
public static byte[] decodeFromFile(String filename)
|
||||
throws java.io.IOException {
|
||||
|
||||
byte[] decodedData = null;
|
||||
Base64.InputStream bis = null;
|
||||
@@ -878,12 +893,12 @@ public class Base64 {
|
||||
// Check for size of file
|
||||
if (file.length() > Integer.MAX_VALUE)
|
||||
throw new java.io.IOException("File is too big for this convenience method (" + file.length()
|
||||
+ " bytes).");
|
||||
+ " bytes).");
|
||||
buffer = new byte[(int) file.length()];
|
||||
|
||||
// Open a stream
|
||||
bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
|
||||
Base64.DECODE);
|
||||
Base64.DECODE);
|
||||
|
||||
// Read until done
|
||||
while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
|
||||
@@ -918,7 +933,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there is an error
|
||||
* @since 2.1
|
||||
*/
|
||||
public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException {
|
||||
public static void decodeToFile(String dataToDecode, String filename)
|
||||
throws java.io.IOException {
|
||||
|
||||
Base64.OutputStream bos = null;
|
||||
try {
|
||||
@@ -950,8 +966,9 @@ public class Base64 {
|
||||
* @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
|
||||
* @since 1.5
|
||||
*/
|
||||
public static Object decodeToObject(String encodedObject) throws java.io.IOException,
|
||||
java.lang.ClassNotFoundException {
|
||||
public static Object decodeToObject(String encodedObject)
|
||||
throws java.io.IOException,
|
||||
java.lang.ClassNotFoundException {
|
||||
|
||||
// Decode and gunzip if necessary
|
||||
byte[] objBytes = decode(encodedObject);
|
||||
@@ -1078,7 +1095,8 @@ public class Base64 {
|
||||
* @see Base64#DO_BREAK_LINES
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String encodeBytes(byte[] source, int options) throws java.io.IOException {
|
||||
public static String encodeBytes(byte[] source, int options)
|
||||
throws java.io.IOException {
|
||||
return encodeBytes(source, 0, source.length, options);
|
||||
} // end encodeBytes
|
||||
|
||||
@@ -1137,7 +1155,8 @@ public class Base64 {
|
||||
* @see Base64#DO_BREAK_LINES
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
|
||||
public static String encodeBytes(byte[] source, int off, int len, int options)
|
||||
throws java.io.IOException {
|
||||
byte[] encoded = encodeBytesToBytes(source, off, len, options);
|
||||
|
||||
// Return value according to relevant encoding.
|
||||
@@ -1189,7 +1208,8 @@ public class Base64 {
|
||||
* @see Base64#DO_BREAK_LINES
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
|
||||
public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options)
|
||||
throws java.io.IOException {
|
||||
|
||||
if (source == null)
|
||||
throw new NullPointerException("Cannot serialize a null array.");
|
||||
@@ -1302,7 +1322,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there is an error
|
||||
* @since 2.2
|
||||
*/
|
||||
public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException {
|
||||
public static void encodeFileToFile(String infile, String outfile)
|
||||
throws java.io.IOException {
|
||||
|
||||
String encoded = Base64.encodeFromFile(infile);
|
||||
java.io.OutputStream out = null;
|
||||
@@ -1333,7 +1354,8 @@ public class Base64 {
|
||||
* @throws java.io.IOException if there is an error
|
||||
* @since 2.1
|
||||
*/
|
||||
public static String encodeFromFile(String filename) throws java.io.IOException {
|
||||
public static String encodeFromFile(String filename)
|
||||
throws java.io.IOException {
|
||||
|
||||
String encodedData = null;
|
||||
Base64.InputStream bis = null;
|
||||
@@ -1348,7 +1370,7 @@ public class Base64 {
|
||||
|
||||
// Open a stream
|
||||
bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
|
||||
Base64.ENCODE);
|
||||
Base64.ENCODE);
|
||||
|
||||
// Read until done
|
||||
while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
|
||||
@@ -1387,7 +1409,8 @@ public class Base64 {
|
||||
* @throws NullPointerException if serializedObject is null
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException {
|
||||
public static String encodeObject(java.io.Serializable serializableObject)
|
||||
throws java.io.IOException {
|
||||
return encodeObject(serializableObject, NO_OPTIONS);
|
||||
} // end encodeObject
|
||||
|
||||
@@ -1420,7 +1443,8 @@ public class Base64 {
|
||||
* @see Base64#DO_BREAK_LINES
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException {
|
||||
public static String encodeObject(java.io.Serializable serializableObject, int options)
|
||||
throws java.io.IOException {
|
||||
|
||||
if (serializableObject == null)
|
||||
throw new NullPointerException("Cannot serialize a null object.");
|
||||
@@ -1481,7 +1505,8 @@ public class Base64 {
|
||||
* @throws NullPointerException if dataToEncode is null
|
||||
* @since 2.1
|
||||
*/
|
||||
public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException {
|
||||
public static void encodeToFile(byte[] dataToEncode, String filename)
|
||||
throws java.io.IOException {
|
||||
|
||||
if (dataToEncode == null)
|
||||
throw new NullPointerException("Data to encode was null.");
|
||||
|
||||
@@ -341,41 +341,13 @@ public class Buffer<T extends Buffer<T>> {
|
||||
*/
|
||||
public BigInteger readMPInt()
|
||||
throws BufferException {
|
||||
return new BigInteger(readMPIntAsBytes());
|
||||
return new BigInteger(readBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an SSH multiple-precision integer from a {@code BigInteger}
|
||||
*
|
||||
* @param bi {@code BigInteger} to write
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public T putMPInt(BigInteger bi) {
|
||||
return putMPInt(bi.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an SSH multiple-precision integer from a Java byte-array
|
||||
*
|
||||
* @param foo byte-array
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public T putMPInt(byte[] foo) {
|
||||
int i = foo.length;
|
||||
if ((foo[0] & 0x80) != 0) {
|
||||
i++;
|
||||
putUInt32(i);
|
||||
putByte((byte) 0);
|
||||
} else
|
||||
putUInt32(i);
|
||||
return putRawBytes(foo);
|
||||
}
|
||||
|
||||
public byte[] readMPIntAsBytes()
|
||||
throws BufferException {
|
||||
return readBytes();
|
||||
final byte[] asBytes = bi.toByteArray();
|
||||
putUInt32(asBytes.length);
|
||||
return putRawBytes(asBytes);
|
||||
}
|
||||
|
||||
public long readUInt64()
|
||||
|
||||
@@ -35,25 +35,11 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Utility functions for byte arrays. */
|
||||
public class ByteArrayUtils {
|
||||
|
||||
final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
/**
|
||||
* Check whether two byte arrays are the equal.
|
||||
*
|
||||
* @param a1
|
||||
* @param a2
|
||||
*
|
||||
* @return <code>true</code> or <code>false</code>
|
||||
*/
|
||||
public static boolean equals(byte[] a1, byte[] a2) {
|
||||
return (a1.length != a2.length && equals(a1, 0, a2, 0, a1.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether some part or whole of two byte arrays is equal, for <code>length</code> bytes starting at some
|
||||
* offset.
|
||||
@@ -75,17 +61,6 @@ public class ByteArrayUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hexadecimal representation of <code>array</code>, with each octet separated by a space.
|
||||
*
|
||||
* @param array
|
||||
*
|
||||
* @return hex string, each octet delimited by a space
|
||||
*/
|
||||
public static String printHex(byte[] array) {
|
||||
return printHex(array, 0, array.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hexadecimal representation of a byte array starting at <code>offset</code> index for <code>len</code>
|
||||
* bytes, with each octet separated by a space.
|
||||
@@ -139,8 +114,4 @@ public class ByteArrayUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] copyOf(byte[] array) {
|
||||
return Arrays.copyOf(array, array.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public interface ErrorNotifiable {
|
||||
|
||||
/** Utility functions. */
|
||||
class Util {
|
||||
|
||||
/** Notify all {@code notifiables} of given {@code error}. */
|
||||
public static void alertAll(SSHException error, ErrorNotifiable... notifiables) {
|
||||
for (ErrorNotifiable notifiable : notifiables)
|
||||
|
||||
@@ -32,7 +32,6 @@ public enum KeyType {
|
||||
|
||||
/** SSH identifier for RSA keys */
|
||||
RSA("ssh-rsa") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
@@ -64,7 +63,6 @@ public enum KeyType {
|
||||
|
||||
/** SSH identifier for DSA keys */
|
||||
DSA("ssh-dss") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
@@ -100,7 +98,6 @@ public enum KeyType {
|
||||
|
||||
/** Unrecognized */
|
||||
UNKNOWN("unknown") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
|
||||
@@ -58,6 +58,7 @@ import java.security.Signature;
|
||||
public class SecurityUtils {
|
||||
|
||||
private static class BouncyCastleRegistration {
|
||||
|
||||
public void run()
|
||||
throws Exception {
|
||||
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
|
||||
|
||||
@@ -89,13 +89,13 @@ public interface Connection {
|
||||
* @param wantReply whether a reply is requested
|
||||
* @param specifics {@link SSHPacket} containing fields specific to the request
|
||||
*
|
||||
* @return a {@link net.schmizz.concurrent.Promise} for the reply data (in case {@code wantReply} is true) which allows waiting on the
|
||||
* reply, or {@code null} if a reply is not requested.
|
||||
* @return a {@link net.schmizz.concurrent.Promise} for the reply data (in case {@code wantReply} is true) which
|
||||
* allows waiting on the reply, or {@code null} if a reply is not requested.
|
||||
*
|
||||
* @throws TransportException if there is an error sending the request
|
||||
*/
|
||||
public Promise<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
|
||||
byte[] specifics)
|
||||
byte[] specifics)
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.concurrent.ErrorDeliveryUtil;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
@@ -53,8 +53,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -76,7 +76,7 @@ public abstract class AbstractChannel
|
||||
/** Remote recipient ID */
|
||||
private int recipient;
|
||||
|
||||
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new LinkedList<Event<ConnectionException>>();
|
||||
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new ConcurrentLinkedQueue<Event<ConnectionException>>();
|
||||
|
||||
/* The lock used by to create the open & close events */
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
@@ -240,7 +240,8 @@ public abstract class AbstractChannel
|
||||
ErrorDeliveryUtil.alertEvents(error, chanReqResponseEvents);
|
||||
|
||||
in.notifyError(error);
|
||||
out.notifyError(error);
|
||||
if (out != null)
|
||||
out.notifyError(error);
|
||||
|
||||
finishOff();
|
||||
}
|
||||
@@ -312,7 +313,8 @@ public abstract class AbstractChannel
|
||||
handleRequest(reqType, buf);
|
||||
}
|
||||
|
||||
private void gotWindowAdjustment(SSHPacket buf) throws ConnectionException {
|
||||
private void gotWindowAdjustment(SSHPacket buf)
|
||||
throws ConnectionException {
|
||||
final int howMuch;
|
||||
try {
|
||||
howMuch = buf.readUInt32AsInt();
|
||||
@@ -362,8 +364,8 @@ public abstract class AbstractChannel
|
||||
stream.receive(buf.array(), buf.rpos(), len);
|
||||
}
|
||||
|
||||
protected synchronized Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
|
||||
Buffer.PlainBuffer reqSpecific)
|
||||
protected Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
|
||||
Buffer.PlainBuffer reqSpecific)
|
||||
throws TransportException {
|
||||
log.info("Sending channel request for `{}`", reqType);
|
||||
trans.write(
|
||||
@@ -375,13 +377,14 @@ public abstract class AbstractChannel
|
||||
|
||||
Event<ConnectionException> responseEvent = null;
|
||||
if (wantReply) {
|
||||
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType, ConnectionException.chainer, lock);
|
||||
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType,
|
||||
ConnectionException.chainer);
|
||||
chanReqResponseEvents.add(responseEvent);
|
||||
}
|
||||
return responseEvent;
|
||||
}
|
||||
|
||||
private synchronized void gotResponse(boolean success)
|
||||
private void gotResponse(boolean success)
|
||||
throws ConnectionException {
|
||||
final Event<ConnectionException> responseEvent = chanReqResponseEvents.poll();
|
||||
if (responseEvent != null) {
|
||||
@@ -390,9 +393,8 @@ public abstract class AbstractChannel
|
||||
else
|
||||
responseEvent.deliverError(new ConnectionException("Request failed"));
|
||||
} else
|
||||
throw new ConnectionException(
|
||||
DisconnectReason.PROTOCOL_ERROR,
|
||||
"Received response to channel request when none was requested");
|
||||
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR,
|
||||
"Received response to channel request when none was requested");
|
||||
}
|
||||
|
||||
private synchronized void gotEOF()
|
||||
@@ -428,7 +430,7 @@ public abstract class AbstractChannel
|
||||
@Override
|
||||
public String toString() {
|
||||
return "< " + type + " channel: id=" + id + ", recipient=" + recipient + ", localWin=" + lwin + ", remoteWin="
|
||||
+ rwin + " >";
|
||||
+ rwin + " >";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface Channel
|
||||
/** Direct channels are those that are initiated by us. */
|
||||
interface Direct
|
||||
extends Channel {
|
||||
|
||||
/**
|
||||
* Request opening this channel from remote end.
|
||||
*
|
||||
|
||||
@@ -116,7 +116,7 @@ public final class ChannelInputStream
|
||||
public int read(byte[] b, int off, int len)
|
||||
throws IOException {
|
||||
synchronized (buf) {
|
||||
for (; ;) {
|
||||
for (; ; ) {
|
||||
if (buf.available() > 0)
|
||||
break;
|
||||
if (eof)
|
||||
@@ -163,7 +163,7 @@ public final class ChannelInputStream
|
||||
if (adjustment > 0) {
|
||||
log.info("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));
|
||||
.putUInt32(chan.getRecipient()).putUInt32(adjustment));
|
||||
win.expand(adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ public class SocketStreamCopyMonitor
|
||||
}
|
||||
|
||||
public static void monitor(final int frequency, final TimeUnit unit,
|
||||
final Event<IOException> x, final Event<IOException> y,
|
||||
final Channel channel, final Socket socket) {
|
||||
final Event<IOException> x, final Event<IOException> y,
|
||||
final Channel channel, final Socket socket) {
|
||||
new SocketStreamCopyMonitor(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
@@ -51,12 +51,13 @@ public abstract class Window {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void consume(int dec) {
|
||||
public void consume(int dec)
|
||||
throws ConnectionException {
|
||||
synchronized (lock) {
|
||||
log.debug("Consuming by " + dec + " down to " + size);
|
||||
size -= dec;
|
||||
if (size < 0)
|
||||
throw new SSHRuntimeException("Window consumed to below 0");
|
||||
throw new ConnectionException("Window consumed to below 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +89,14 @@ public abstract class Window {
|
||||
}
|
||||
}
|
||||
|
||||
public void consume(int howMuch) {
|
||||
try {
|
||||
super.consume(howMuch);
|
||||
} catch (ConnectionException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Controls how much data remote end can send before an adjustment notification from us is required. */
|
||||
|
||||
@@ -53,7 +53,7 @@ public class LocalPortForwarder {
|
||||
.bufSize(getLocalMaxPacketSize())
|
||||
.spawnDaemon("chan2soc");
|
||||
SocketStreamCopyMonitor.monitor(5, TimeUnit.SECONDS, soc2chan, chan2soc, this, sock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSHPacket buildOpenReq() {
|
||||
|
||||
@@ -82,10 +82,12 @@ public interface Session
|
||||
throws TransportException;
|
||||
|
||||
@Deprecated
|
||||
String getOutputAsString() throws IOException;
|
||||
String getOutputAsString()
|
||||
throws IOException;
|
||||
|
||||
@Deprecated
|
||||
String getErrorAsString() throws IOException;
|
||||
String getErrorAsString()
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -132,6 +134,7 @@ public interface Session
|
||||
/** Subsystem API. */
|
||||
interface Subsystem
|
||||
extends Channel {
|
||||
|
||||
Integer getExitStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ public class SessionChannel
|
||||
public void changeWindowDimensions(int cols, int rows, int width, int height)
|
||||
throws TransportException {
|
||||
sendChannelRequest(
|
||||
"pty-req",
|
||||
"window-change",
|
||||
false,
|
||||
new Buffer.PlainBuffer()
|
||||
.putUInt32(cols)
|
||||
|
||||
@@ -69,9 +69,9 @@ public abstract class AbstractForwardedChannel
|
||||
// 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()));
|
||||
.putUInt32(getID())
|
||||
.putUInt32(getLocalWinSize())
|
||||
.putUInt32(getLocalMaxPacketSize()));
|
||||
open.set();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,18 +39,18 @@ public class RemotePortForwarder
|
||||
* address (or domain name) and port on which connections for forwarding
|
||||
* are to be accepted. Some strings used for 'address to bind' have
|
||||
* special-case semantics.
|
||||
* <p/>
|
||||
*
|
||||
* o "" means that connections are to be accepted on all protocol
|
||||
* families supported by the SSH implementation.
|
||||
* <p/>
|
||||
*
|
||||
* o "0.0.0.0" means to listen on all IPv4 addresses.
|
||||
* <p/>
|
||||
*
|
||||
* o "::" means to listen on all IPv6 addresses.
|
||||
* <p/>
|
||||
*
|
||||
* o "localhost" means to listen on all protocol families supported by
|
||||
* the SSH implementation on loopback addresses only ([RFC3330] and
|
||||
* [RFC3513]).
|
||||
* <p/>
|
||||
*
|
||||
* o "127.0.0.1" and "::1" indicate listening on the loopback
|
||||
* interfaces for IPv4 and IPv6, respectively.
|
||||
* </pre>
|
||||
@@ -198,9 +198,9 @@ public class RemotePortForwarder
|
||||
protected SSHPacket req(String reqName, Forward forward)
|
||||
throws ConnectionException, TransportException {
|
||||
final byte[] specifics = new Buffer.PlainBuffer().putString(forward.address).putUInt32(forward.port)
|
||||
.getCompactData();
|
||||
.getCompactData();
|
||||
return conn.sendGlobalRequest(reqName, true, specifics)
|
||||
.retrieve(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
.retrieve(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/** @return the active forwards. */
|
||||
@@ -227,7 +227,7 @@ public class RemotePortForwarder
|
||||
callListener(listeners.get(chan.getParentForward()), chan);
|
||||
else
|
||||
chan.reject(OpenFailException.Reason.ADMINISTRATIVELY_PROHIBITED, "Forwarding was not requested on `"
|
||||
+ chan.getParentForward() + "`");
|
||||
+ chan.getParentForward() + "`");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class FileMode {
|
||||
}
|
||||
|
||||
public int getTypeMask() {
|
||||
return mask & 0770000;
|
||||
return mask & 0170000;
|
||||
}
|
||||
|
||||
public int getPermissionsMask() {
|
||||
|
||||
@@ -57,9 +57,9 @@ public class PacketReader
|
||||
readIntoBuffer(lenBuf, 0, lenBuf.length);
|
||||
|
||||
return (int) (lenBuf[0] << 24 & 0xff000000L
|
||||
| lenBuf[1] << 16 & 0x00ff0000L
|
||||
| lenBuf[2] << 8 & 0x0000ff00L
|
||||
| lenBuf[3] & 0x000000ffL);
|
||||
| lenBuf[1] << 16 & 0x00ff0000L
|
||||
| lenBuf[2] << 8 & 0x0000ff00L
|
||||
| lenBuf[3] & 0x000000ffL);
|
||||
}
|
||||
|
||||
public SFTPPacket<Response> readPacket()
|
||||
@@ -97,7 +97,7 @@ public class PacketReader
|
||||
log.debug("Received {} packet", resp.getType());
|
||||
if (promise == null)
|
||||
throw new SFTPException("Received [" + resp.readType() + "] response for request-id " + resp.getRequestID()
|
||||
+ ", no such request was made");
|
||||
+ ", no such request was made");
|
||||
else
|
||||
promise.deliver(resp);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class RemoteDirectory
|
||||
throws IOException {
|
||||
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
|
||||
loop:
|
||||
for (; ;) {
|
||||
for (; ; ) {
|
||||
Response res = requester.doRequest(newRequest(PacketType.READDIR));
|
||||
switch (res.getType()) {
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ public class RemoteFile
|
||||
public FileAttributes fetchAttributes()
|
||||
throws IOException {
|
||||
return requester.doRequest(newRequest(PacketType.FSTAT))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
public long length()
|
||||
@@ -74,9 +74,9 @@ 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)
|
||||
.putUInt64(fileOffset)
|
||||
.putUInt32(len - off)
|
||||
.putRawBytes(data, off, len)
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ public class RemoteFile
|
||||
|
||||
public int getOutgoingPacketOverhead() {
|
||||
return 1 + // packet type
|
||||
4 + // request id
|
||||
4 + // next length
|
||||
handle.length() + // next
|
||||
8 + // file offset
|
||||
4 + // data length
|
||||
4; // packet length
|
||||
4 + // request id
|
||||
4 + // next length
|
||||
handle.length() + // next
|
||||
8 + // file offset
|
||||
4 + // data length
|
||||
4; // packet length
|
||||
}
|
||||
|
||||
public class RemoteFileOutputStream
|
||||
|
||||
@@ -51,7 +51,8 @@ public class Response
|
||||
private final PacketType type;
|
||||
private final long reqID;
|
||||
|
||||
public Response(Buffer<Response> pk, int protocolVersion) throws SFTPException {
|
||||
public Response(Buffer<Response> pk, int protocolVersion)
|
||||
throws SFTPException {
|
||||
super(pk);
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.type = readType();
|
||||
@@ -74,7 +75,8 @@ public class Response
|
||||
return type;
|
||||
}
|
||||
|
||||
public StatusCode readStatusCode() throws SFTPException {
|
||||
public StatusCode readStatusCode()
|
||||
throws SFTPException {
|
||||
try {
|
||||
return StatusCode.fromInt(readUInt32AsInt());
|
||||
} catch (BufferException be) {
|
||||
|
||||
@@ -56,7 +56,8 @@ public class SFTPEngine
|
||||
this(ssh, PathHelper.DEFAULT_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
public SFTPEngine(SessionFactory ssh, String pathSep) throws SSHException {
|
||||
public SFTPEngine(SessionFactory ssh, String pathSep)
|
||||
throws SSHException {
|
||||
sub = ssh.startSession().startSubsystem("sftp");
|
||||
out = sub.getOutputStream();
|
||||
reader = new PacketReader(this);
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.sshj.xfer.LocalDestFile;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -178,10 +181,22 @@ public class StatefulSFTPClient
|
||||
super.get(cwdify(source), dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void get(String source, LocalDestFile dest)
|
||||
throws IOException {
|
||||
super.get(cwdify(source), dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String source, String dest)
|
||||
throws IOException {
|
||||
super.put(source, cwdify(dest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(LocalSourceFile source, String dest)
|
||||
throws IOException {
|
||||
super.put(source, cwdify(dest));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ public abstract class AbstractSignature
|
||||
| sig[i++] & 0x000000ff;
|
||||
i += j;
|
||||
j = sig[i++] << 24 & 0xff000000
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
byte[] newSig = new byte[j];
|
||||
System.arraycopy(sig, i, newSig, 0, j);
|
||||
sig = newSig;
|
||||
|
||||
@@ -47,7 +47,7 @@ import net.schmizz.sshj.transport.mac.MAC;
|
||||
* <p/>
|
||||
* <pre>
|
||||
* Each packet is in the following format:
|
||||
* <p/>
|
||||
*
|
||||
* uint32 packet_length
|
||||
* byte padding_length
|
||||
* byte[n1] payload; n1 = packet_length - padding_length - 1
|
||||
|
||||
@@ -88,7 +88,7 @@ final class Decoder
|
||||
int need;
|
||||
|
||||
/* Decoding loop */
|
||||
for (; ;)
|
||||
for (; ; )
|
||||
|
||||
if (packetLength == -1) // Waiting for beginning of packet
|
||||
{
|
||||
@@ -124,7 +124,7 @@ final class Decoder
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("Received packet #{}: {}", seq, plain.printHex());
|
||||
|
||||
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet //
|
||||
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet
|
||||
|
||||
inputBuffer.clear();
|
||||
packetLength = -1;
|
||||
|
||||
@@ -63,7 +63,7 @@ final class Encoder
|
||||
private SSHPacket checkHeaderSpace(SSHPacket buffer) {
|
||||
if (buffer.rpos() < 5) {
|
||||
log.warn("Performance cost: when sending a packet, ensure that "
|
||||
+ "5 bytes are available in front of the buffer");
|
||||
+ "5 bytes are available in front of the buffer");
|
||||
SSHPacket nb = new SSHPacket(buffer.available() + 5);
|
||||
nb.rpos(5);
|
||||
nb.wpos(5);
|
||||
@@ -96,8 +96,6 @@ final class Encoder
|
||||
long encode(SSHPacket buffer) {
|
||||
encodeLock.lock();
|
||||
try {
|
||||
buffer = checkHeaderSpace(buffer);
|
||||
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("Encoding packet #{}: {}", seq, buffer.printHex());
|
||||
|
||||
|
||||
@@ -35,13 +35,12 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport;
|
||||
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.concurrent.ErrorDeliveryUtil;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.ErrorNotifiable;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.common.Message;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
@@ -57,6 +56,7 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
@@ -234,17 +234,16 @@ final class KeyExchanger
|
||||
|
||||
private void gotKexInit(SSHPacket buf)
|
||||
throws TransportException {
|
||||
Proposal serverProposal = new Proposal(buf);
|
||||
buf.rpos(buf.rpos() - 1);
|
||||
final Proposal serverProposal = new Proposal(buf);
|
||||
negotiatedAlgs = clientProposal.negotiate(serverProposal);
|
||||
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
|
||||
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(), negotiatedAlgs
|
||||
.getKeyExchangeAlgorithm());
|
||||
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(),
|
||||
negotiatedAlgs.getKeyExchangeAlgorithm());
|
||||
try {
|
||||
kex.init(transport,
|
||||
transport.getServerID().getBytes(IOUtils.UTF8),
|
||||
transport.getClientID().getBytes(IOUtils.UTF8),
|
||||
buf.getCompactData(),
|
||||
clientProposal.getPacket().getCompactData());
|
||||
transport.getServerID(), transport.getClientID(),
|
||||
serverProposal.getPacket().getCompactData(), clientProposal.getPacket().getCompactData());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, e);
|
||||
}
|
||||
@@ -262,7 +261,7 @@ final class KeyExchanger
|
||||
*
|
||||
* @return the resized key
|
||||
*/
|
||||
private static byte[] resizedKey(byte[] E, int blockSize, Digest hash, byte[] K, byte[] H) {
|
||||
private static byte[] resizedKey(byte[] E, int blockSize, Digest hash, BigInteger K, byte[] H) {
|
||||
while (blockSize > E.length) {
|
||||
Buffer.PlainBuffer buffer = new Buffer.PlainBuffer().putMPInt(K).putRawBytes(H).putRawBytes(E);
|
||||
hash.update(buffer.array(), 0, buffer.available());
|
||||
@@ -280,13 +279,15 @@ final class KeyExchanger
|
||||
private void gotNewKeys() {
|
||||
final Digest hash = kex.getHash();
|
||||
|
||||
final byte[] H = kex.getH();
|
||||
|
||||
if (sessionID == null)
|
||||
// session id is 'H' from the first key exchange and does not change thereafter
|
||||
sessionID = Arrays.copyOf(kex.getH(), kex.getH().length);
|
||||
sessionID = H;
|
||||
|
||||
final Buffer.PlainBuffer hashInput = new Buffer.PlainBuffer()
|
||||
.putMPInt(kex.getK())
|
||||
.putRawBytes(kex.getH())
|
||||
.putRawBytes(H)
|
||||
.putByte((byte) 0) // <placeholder>
|
||||
.putRawBytes(sessionID);
|
||||
final int pos = hashInput.available() - sessionID.length - 1; // Position of <placeholder>
|
||||
@@ -360,7 +361,6 @@ final class KeyExchanger
|
||||
* having sent the packet ourselves (would cause gotKexInit() to fail)
|
||||
*/
|
||||
kexInitSent.await(transport.getTimeout(), TimeUnit.SECONDS);
|
||||
buf.rpos(buf.rpos() - 1);
|
||||
gotKexInit(buf);
|
||||
expected = Expected.FOLLOWUP;
|
||||
break;
|
||||
|
||||
@@ -92,15 +92,15 @@ public final class NegotiatedAlgorithms {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ("[ " + //
|
||||
"kex=" + kex + "; " + //
|
||||
"sig=" + sig + "; " + //
|
||||
"c2sCipher=" + c2sCipher + "; " + //
|
||||
"s2cCipher=" + s2cCipher + "; " + //
|
||||
"c2sMAC=" + c2sMAC + "; " + //
|
||||
"s2cMAC=" + s2cMAC + "; " + //
|
||||
"c2sComp=" + c2sComp + "; " + //
|
||||
"s2cComp=" + s2cComp + //
|
||||
return ("[ " +
|
||||
"kex=" + kex + "; " +
|
||||
"sig=" + sig + "; " +
|
||||
"c2sCipher=" + c2sCipher + "; " +
|
||||
"s2cCipher=" + s2cCipher + "; " +
|
||||
"c2sMAC=" + c2sMAC + "; " +
|
||||
"s2cMAC=" + s2cMAC + "; " +
|
||||
"c2sComp=" + c2sComp + "; " +
|
||||
"s2cComp=" + s2cComp +
|
||||
" ]");
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,8 @@ class Proposal {
|
||||
packet.putUInt32(0); // "Reserved" for future by spec
|
||||
}
|
||||
|
||||
public Proposal(SSHPacket packet) throws TransportException {
|
||||
public Proposal(SSHPacket packet)
|
||||
throws TransportException {
|
||||
this.packet = packet;
|
||||
final int savedPos = packet.rpos();
|
||||
packet.rpos(packet.rpos() + 17); // Skip message ID & cookie
|
||||
@@ -144,14 +145,14 @@ class Proposal {
|
||||
public NegotiatedAlgorithms negotiate(Proposal other)
|
||||
throws TransportException {
|
||||
return new NegotiatedAlgorithms(
|
||||
firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()), //
|
||||
firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()), //
|
||||
firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()), //
|
||||
firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()), //
|
||||
firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()), //
|
||||
firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()), //
|
||||
firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()), //
|
||||
firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms()) //
|
||||
firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
|
||||
firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()),
|
||||
firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()),
|
||||
firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()),
|
||||
firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()),
|
||||
firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()),
|
||||
firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()),
|
||||
firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,12 +178,14 @@ public interface Transport
|
||||
*/
|
||||
void join()
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
* Joins the thread calling this method to the transport's death.
|
||||
*
|
||||
* @throws TransportException if the transport dies of an exception
|
||||
*/
|
||||
void join(int timeout, TimeUnit unit) throws TransportException;
|
||||
void join(int timeout, TimeUnit unit)
|
||||
throws TransportException;
|
||||
|
||||
/** Send a disconnection packet with reason as {@link DisconnectReason#BY_APPLICATION}, and closes this transport. */
|
||||
void disconnect();
|
||||
@@ -226,9 +228,7 @@ public interface Transport
|
||||
*/
|
||||
void setDisconnectListener(DisconnectListener listener);
|
||||
|
||||
/**
|
||||
* @return the current disconnect listener.
|
||||
*/
|
||||
/** @return the current disconnect listener. */
|
||||
DisconnectListener getDisconnectListener();
|
||||
|
||||
}
|
||||
@@ -35,8 +35,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport;
|
||||
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.concurrent.ErrorDeliveryUtil;
|
||||
import net.schmizz.concurrent.Event;
|
||||
import net.schmizz.sshj.AbstractService;
|
||||
import net.schmizz.sshj.Config;
|
||||
import net.schmizz.sshj.Service;
|
||||
@@ -62,12 +62,14 @@ public final class TransportImpl
|
||||
|
||||
private static final class NullService
|
||||
extends AbstractService {
|
||||
|
||||
NullService(Transport trans) {
|
||||
super("null-service", trans);
|
||||
}
|
||||
}
|
||||
|
||||
static final class ConnInfo {
|
||||
|
||||
final String host;
|
||||
final int port;
|
||||
final InputStream in;
|
||||
@@ -185,11 +187,11 @@ public final class TransportImpl
|
||||
String ident;
|
||||
|
||||
byte[] data = new byte[256];
|
||||
for (; ;) {
|
||||
for (; ; ) {
|
||||
int savedBufPos = buffer.rpos();
|
||||
int pos = 0;
|
||||
boolean needLF = false;
|
||||
for (; ;) {
|
||||
for (; ; ) {
|
||||
if (buffer.available() == 0) {
|
||||
// Need more data, so undo reading and return null
|
||||
buffer.rpos(savedBufPos);
|
||||
@@ -217,7 +219,7 @@ public final class TransportImpl
|
||||
|
||||
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
|
||||
"Server does not support SSHv2, identified as: " + ident);
|
||||
"Server does not support SSHv2, identified as: " + ident);
|
||||
|
||||
return ident;
|
||||
}
|
||||
@@ -438,9 +440,9 @@ public final class TransportImpl
|
||||
log.debug("Sending SSH_MSG_DISCONNECT: reason=[{}], msg=[{}]", reason, message);
|
||||
try {
|
||||
write(new SSHPacket(Message.DISCONNECT)
|
||||
.putUInt32(reason.toInt())
|
||||
.putString(message)
|
||||
.putString(""));
|
||||
.putUInt32(reason.toInt())
|
||||
.putString(message)
|
||||
.putString(""));
|
||||
} catch (IOException worthless) {
|
||||
log.debug("Error writing packet: {}", worthless.toString());
|
||||
}
|
||||
@@ -501,7 +503,8 @@ public final class TransportImpl
|
||||
}
|
||||
}
|
||||
|
||||
private void gotDebug(SSHPacket buf) throws TransportException {
|
||||
private void gotDebug(SSHPacket buf)
|
||||
throws TransportException {
|
||||
try {
|
||||
final boolean display = buf.readBoolean();
|
||||
final String message = buf.readString();
|
||||
@@ -529,7 +532,7 @@ public final class TransportImpl
|
||||
try {
|
||||
if (!serviceAccept.hasWaiters())
|
||||
throw new TransportException(DisconnectReason.PROTOCOL_ERROR,
|
||||
"Got a service accept notification when none was awaited");
|
||||
"Got a service accept notification when none was awaited");
|
||||
serviceAccept.set();
|
||||
} finally {
|
||||
serviceAccept.unlock();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES128CBC
|
||||
/** Named factory for AES128CBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES128CBC();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES128CTR
|
||||
/** Named factory for AES128CBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES128CTR();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES192CBC
|
||||
/** Named factory for AES192CBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES192CBC();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES192CTR
|
||||
/** Named factory for AES192CTR Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES192CTR();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES256CBC
|
||||
/** Named factory for AES256CBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES256CBC();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class AES256CTR
|
||||
/** Named factory for AES256CBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new AES256CTR();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class BlowfishCBC
|
||||
/** Named factory for BlowfishCBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new BlowfishCBC();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class NoneCipher
|
||||
/** Named factory for the no-op Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new NoneCipher();
|
||||
|
||||
@@ -42,6 +42,7 @@ public class TripleDESCBC
|
||||
/** Named factory for TripleDESCBC Cipher */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Cipher> {
|
||||
|
||||
@Override
|
||||
public Cipher create() {
|
||||
return new TripleDESCBC();
|
||||
|
||||
@@ -46,6 +46,7 @@ public class DelayedZlibCompression
|
||||
/** Named factory for the ZLib Delayed Compression. */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Compression> {
|
||||
|
||||
@Override
|
||||
public Compression create() {
|
||||
return new DelayedZlibCompression();
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class NoneCompression
|
||||
/** Named factory for the no-op <code>Compression</code> */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Compression> {
|
||||
|
||||
@Override
|
||||
public Compression create() {
|
||||
return null;
|
||||
|
||||
@@ -49,6 +49,7 @@ public class ZlibCompression
|
||||
/** Named factory for the ZLib Compression. */
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Compression> {
|
||||
|
||||
@Override
|
||||
public Compression create() {
|
||||
return new ZlibCompression();
|
||||
@@ -125,7 +126,7 @@ public class ZlibCompression
|
||||
return;
|
||||
default:
|
||||
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
|
||||
+ status);
|
||||
+ status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.Factory;
|
||||
import net.schmizz.sshj.common.KeyType;
|
||||
@@ -53,6 +52,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base class for DHG key exchange algorithms. Implementations will only have to configure the required data on the
|
||||
@@ -65,32 +65,30 @@ public abstract class AbstractDHG
|
||||
|
||||
private Transport trans;
|
||||
|
||||
private final Digest sha = new SHA1();
|
||||
private final Digest sha1 = new SHA1();
|
||||
private final DH dh = new DH();
|
||||
|
||||
private byte[] V_S;
|
||||
private byte[] V_C;
|
||||
private String V_S;
|
||||
private String V_C;
|
||||
private byte[] I_S;
|
||||
private byte[] I_C;
|
||||
|
||||
private byte[] e;
|
||||
private byte[] K;
|
||||
private byte[] H;
|
||||
private PublicKey hostKey;
|
||||
|
||||
@Override
|
||||
public byte[] getH() {
|
||||
return ByteArrayUtils.copyOf(H);
|
||||
return Arrays.copyOf(H, H.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getK() {
|
||||
return ByteArrayUtils.copyOf(K);
|
||||
public BigInteger getK() {
|
||||
return dh.getK();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Digest getHash() {
|
||||
return sha;
|
||||
return sha1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,19 +97,18 @@ public abstract class AbstractDHG
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
|
||||
public void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C)
|
||||
throws GeneralSecurityException, TransportException {
|
||||
this.trans = trans;
|
||||
this.V_S = ByteArrayUtils.copyOf(V_S);
|
||||
this.V_C = ByteArrayUtils.copyOf(V_C);
|
||||
this.I_S = ByteArrayUtils.copyOf(I_S);
|
||||
this.I_C = ByteArrayUtils.copyOf(I_C);
|
||||
sha.init();
|
||||
this.V_S = V_S;
|
||||
this.V_C = V_C;
|
||||
this.I_S = Arrays.copyOf(I_S, I_S.length);
|
||||
this.I_C = Arrays.copyOf(I_C, I_C.length);
|
||||
sha1.init();
|
||||
initDH(dh);
|
||||
e = dh.getE();
|
||||
|
||||
log.info("Sending SSH_MSG_KEXDH_INIT");
|
||||
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(e));
|
||||
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(dh.getE()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,19 +119,18 @@ public abstract class AbstractDHG
|
||||
|
||||
log.info("Received SSH_MSG_KEXDH_REPLY");
|
||||
final byte[] K_S;
|
||||
final byte[] f;
|
||||
final BigInteger f;
|
||||
final byte[] sig; // signature sent by server
|
||||
try {
|
||||
K_S = packet.readBytes();
|
||||
f = packet.readMPIntAsBytes();
|
||||
f = packet.readMPInt();
|
||||
sig = packet.readBytes();
|
||||
hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
} catch (Buffer.BufferException be) {
|
||||
throw new TransportException(be);
|
||||
}
|
||||
|
||||
dh.setF(new BigInteger(f));
|
||||
K = dh.getK();
|
||||
dh.computeK(f);
|
||||
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer()
|
||||
.putString(V_C)
|
||||
@@ -142,11 +138,11 @@ public abstract class AbstractDHG
|
||||
.putString(I_C)
|
||||
.putString(I_S)
|
||||
.putString(K_S)
|
||||
.putMPInt(e)
|
||||
.putMPInt(dh.getE())
|
||||
.putMPInt(f)
|
||||
.putMPInt(K);
|
||||
sha.update(buf.array(), 0, buf.available());
|
||||
H = sha.digest();
|
||||
.putMPInt(dh.getK());
|
||||
sha1.update(buf.array(), buf.rpos(), buf.available());
|
||||
H = sha1.digest();
|
||||
|
||||
Signature signature = Factory.Named.Util.create(trans.getConfig().getSignatureFactories(),
|
||||
KeyType.fromKey(hostKey).toString());
|
||||
@@ -158,6 +154,7 @@ public abstract class AbstractDHG
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void initDH(DH dh);
|
||||
protected abstract void initDH(DH dh)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ public class DH {
|
||||
private BigInteger p;
|
||||
private BigInteger g;
|
||||
private BigInteger e; // my public key
|
||||
private BigInteger f; // your public key
|
||||
private BigInteger K; // shared secret key
|
||||
private final KeyPairGenerator generator;
|
||||
private final KeyAgreement agreement;
|
||||
@@ -68,39 +67,30 @@ public class DH {
|
||||
}
|
||||
}
|
||||
|
||||
public void setF(BigInteger f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
public void setG(BigInteger g) {
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
public void setP(BigInteger p) {
|
||||
public void init(BigInteger p, BigInteger g)
|
||||
throws GeneralSecurityException {
|
||||
this.p = p;
|
||||
this.g = g;
|
||||
generator.initialize(new DHParameterSpec(p, g));
|
||||
final KeyPair kp = generator.generateKeyPair();
|
||||
agreement.init(kp.getPrivate());
|
||||
e = ((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY();
|
||||
}
|
||||
|
||||
public byte[] getE()
|
||||
public void computeK(BigInteger f)
|
||||
throws GeneralSecurityException {
|
||||
if (e == null) {
|
||||
generator.initialize(new DHParameterSpec(p, g));
|
||||
final KeyPair kp = generator.generateKeyPair();
|
||||
agreement.init(kp.getPrivate());
|
||||
e = ((javax.crypto.interfaces.DHPublicKey) kp.getPublic()).getY();
|
||||
}
|
||||
return e.toByteArray();
|
||||
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DH");
|
||||
final PublicKey yourPubKey = keyFactory.generatePublic(new DHPublicKeySpec(f, p, g));
|
||||
agreement.doPhase(yourPubKey, true);
|
||||
K = new BigInteger(1, agreement.generateSecret());
|
||||
}
|
||||
|
||||
public byte[] getK()
|
||||
throws GeneralSecurityException {
|
||||
if (K == null) {
|
||||
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DH");
|
||||
final DHPublicKeySpec keySpec = new DHPublicKeySpec(f, p, g);
|
||||
final PublicKey yourPubKey = keyFactory.generatePublic(keySpec);
|
||||
agreement.doPhase(yourPubKey, true);
|
||||
K = new BigInteger(agreement.generateSecret());
|
||||
}
|
||||
return K.toByteArray();
|
||||
public BigInteger getE() {
|
||||
return e;
|
||||
}
|
||||
|
||||
public BigInteger getK() {
|
||||
return K;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Diffie-Hellman key exchange with SHA-1 and Oakley Group 2 [RFC2409] (1024-bit MODP Group).
|
||||
*
|
||||
@@ -60,9 +62,9 @@ public class DHG1
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh) {
|
||||
dh.setG(DHGroupData.G);
|
||||
dh.setP(DHGroupData.P1);
|
||||
protected void initDH(DH dh)
|
||||
throws GeneralSecurityException {
|
||||
dh.init(DHGroupData.P1, DHGroupData.G);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Diffie-Hellman key exchange with SHA-1 and Oakley Group 14 [RFC3526] (2048-bit MODP Group).
|
||||
* <p/>
|
||||
@@ -61,9 +63,9 @@ public class DHG14
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh) {
|
||||
dh.setG(DHGroupData.G);
|
||||
dh.setP(DHGroupData.P14);
|
||||
protected void initDH(DH dh)
|
||||
throws GeneralSecurityException {
|
||||
dh.init(DHGroupData.P14, DHGroupData.G);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,18 +45,18 @@ public final class DHGroupData {
|
||||
|
||||
public static final BigInteger P1 =
|
||||
new BigInteger("1797693134862315907708391567937874531978602960487560117064444236841971802161585193" +
|
||||
"6894783379586492554150218056548598050364644054819923910005079287700335581663922955" +
|
||||
"3136239076508735759914822574862575007425302077447712589550957937778424442426617334" +
|
||||
"727629299387668709205606050270810842907692932019128194467627007");
|
||||
"6894783379586492554150218056548598050364644054819923910005079287700335581663922955" +
|
||||
"3136239076508735759914822574862575007425302077447712589550957937778424442426617334" +
|
||||
"727629299387668709205606050270810842907692932019128194467627007");
|
||||
|
||||
public static final BigInteger P14 =
|
||||
new BigInteger("3231700607131100730033891392642382824881794124114023911284200975140074170663435422" +
|
||||
"2619689417363569347117901737909704191754605873209195028853758986185622153212175412" +
|
||||
"5149017745202702357960782362488842461894775876411059286460994117232454266225221932" +
|
||||
"3054091903768052423551912567971587011700105805587765103886184728025797605490356973" +
|
||||
"2561526167081339361799541336476559160368317896729073178384589680639671900977202194" +
|
||||
"1686472258710314113364293195361934716365332097170774482279885885653692086452966360" +
|
||||
"7725026895550592836275112117409697299806841055435958486658329164213621823107899099" +
|
||||
"9448652468262416972035911852507045361090559");
|
||||
"2619689417363569347117901737909704191754605873209195028853758986185622153212175412" +
|
||||
"5149017745202702357960782362488842461894775876411059286460994117232454266225221932" +
|
||||
"3054091903768052423551912567971587011700105805587765103886184728025797605490356973" +
|
||||
"2561526167081339361799541336476559160368317896729073178384589680639671900977202194" +
|
||||
"1686472258710314113364293195361934716365332097170774482279885885653692086452966360" +
|
||||
"7725026895550592836275112117409697299806841055435958486658329164213621823107899099" +
|
||||
"9448652468262416972035911852507045361090559");
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
@@ -59,14 +60,14 @@ public interface KeyExchange {
|
||||
* @throws GeneralSecurityException
|
||||
* @throws TransportException if there is an error sending a packet
|
||||
*/
|
||||
void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
|
||||
void init(Transport trans, String V_S, String V_C, byte[] I_S, byte[] I_C)
|
||||
throws GeneralSecurityException, TransportException;
|
||||
|
||||
/** @return the computed H parameter */
|
||||
byte[] getH();
|
||||
|
||||
/** @return the computed K parameter */
|
||||
byte[] getK();
|
||||
BigInteger getK();
|
||||
|
||||
/**
|
||||
* The message digest used by this key exchange algorithm.
|
||||
|
||||
@@ -58,10 +58,9 @@ public class BouncyCastleRandom
|
||||
|
||||
}
|
||||
|
||||
private final RandomGenerator random;
|
||||
private final RandomGenerator random = new VMPCRandomGenerator();
|
||||
|
||||
public BouncyCastleRandom() {
|
||||
random = new VMPCRandomGenerator();
|
||||
byte[] seed = new SecureRandom().generateSeed(8);
|
||||
random.addSeedMaterial(seed);
|
||||
}
|
||||
|
||||
@@ -58,11 +58,7 @@ public class JCERandom
|
||||
}
|
||||
|
||||
private byte[] tmp = new byte[16];
|
||||
private SecureRandom random = null;
|
||||
|
||||
public JCERandom() {
|
||||
random = new SecureRandom();
|
||||
}
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Fill the given byte-array with random bytes from this PRNG.
|
||||
|
||||
@@ -40,6 +40,7 @@ import net.schmizz.sshj.common.Factory;
|
||||
/** A random factory wrapper that uses a single random instance. The underlying random instance has to be thread safe. */
|
||||
public class SingletonRandomFactory
|
||||
implements Random, Factory<Random> {
|
||||
|
||||
private final Random random;
|
||||
|
||||
public SingletonRandomFactory(Factory<Random> factory) {
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ConsoleKnownHostsVerifier
|
||||
protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
|
||||
final KeyType type = KeyType.fromKey(key);
|
||||
console.printf("The authenticity of host '%s' can't be established.\n" +
|
||||
"%s key fingerprint is %s.\n", hostname, type, SecurityUtils.getFingerprint(key));
|
||||
"%s key fingerprint is %s.\n", hostname, type, SecurityUtils.getFingerprint(key));
|
||||
String response = console.readLine("Are you sure you want to continue connecting (yes/no)? ");
|
||||
while (!(response.equalsIgnoreCase(YES) || response.equalsIgnoreCase(NO))) {
|
||||
response = console.readLine("Please explicitly enter yes/no: ");
|
||||
@@ -66,15 +66,15 @@ public class ConsoleKnownHostsVerifier
|
||||
final String path = getFile().getAbsolutePath();
|
||||
console.printf(
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
|
||||
"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n" +
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
|
||||
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
|
||||
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
|
||||
"It is also possible that the host key has just been changed.\n" +
|
||||
"The fingerprint for the %s key sent by the remote host is\n" +
|
||||
"%s.\n" +
|
||||
"Please contact your system administrator or" +
|
||||
"add correct host key in %s to get rid of this message.\n",
|
||||
"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n" +
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
|
||||
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
|
||||
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
|
||||
"It is also possible that the host key has just been changed.\n" +
|
||||
"The fingerprint for the %s key sent by the remote host is\n" +
|
||||
"%s.\n" +
|
||||
"Please contact your system administrator or" +
|
||||
"add correct host key in %s to get rid of this message.\n",
|
||||
type, fp, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -139,11 +139,13 @@ public class UserAuthImpl
|
||||
|
||||
case USERAUTH_BANNER: {
|
||||
banner = buf.readString();
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case USERAUTH_SUCCESS: {
|
||||
authenticated.set();
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case USERAUTH_FAILURE: {
|
||||
allowedMethods.clear();
|
||||
@@ -154,7 +156,8 @@ public class UserAuthImpl
|
||||
} else {
|
||||
authenticated.deliverError(new UserAuthException(currentMethod.getName() + " auth failed"));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
log.debug("Asking `{}` method to handle {} packet", currentMethod.getName(), msg);
|
||||
|
||||
@@ -33,4 +33,7 @@ public interface FileKeyProvider
|
||||
|
||||
void init(File location, PasswordFinder pwdf);
|
||||
|
||||
void init(String privateKey, String publicKey);
|
||||
|
||||
void init(String privateKey, String publicKey, PasswordFinder pwdf);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class KeyProviderUtil {
|
||||
|
||||
@@ -37,13 +39,50 @@ public class KeyProviderUtil {
|
||||
*/
|
||||
public static FileKeyProvider.Format detectKeyFileFormat(File location)
|
||||
throws IOException {
|
||||
BufferedReader br = new BufferedReader(new FileReader(location));
|
||||
return detectKeyFileFormat(new FileReader(location),
|
||||
new File(location + ".pub").exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect how a key file is encoded.
|
||||
* <p/>
|
||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||
*
|
||||
* @param privateKey Private key stored in a string
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
*
|
||||
* @return name of the key file format
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static FileKeyProvider.Format detectKeyFileFormat(String privateKey,
|
||||
boolean separatePubKey)
|
||||
throws IOException {
|
||||
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect how a key file is encoded.
|
||||
* <p/>
|
||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||
*
|
||||
* @param privateKey Private key accessible through a {@code Reader}
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
*
|
||||
* @return name of the key file format
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
private static FileKeyProvider.Format detectKeyFileFormat(Reader privateKey,
|
||||
boolean separatePubKey)
|
||||
throws IOException {
|
||||
BufferedReader br = new BufferedReader(privateKey);
|
||||
String firstLine = br.readLine();
|
||||
IOUtils.closeQuietly(br);
|
||||
if (firstLine == null)
|
||||
throw new IOException("Empty file");
|
||||
if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----"))
|
||||
if (new File(location + ".pub").exists())
|
||||
if (separatePubKey)
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
return FileKeyProvider.Format.OpenSSH;
|
||||
else
|
||||
@@ -54,5 +93,4 @@ public class KeyProviderUtil {
|
||||
*/
|
||||
return FileKeyProvider.Format.Unknown;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.security.PublicKey;
|
||||
|
||||
|
||||
/**
|
||||
* Represents an OpenSSH identity that consists of a PKCS8-encoded private key file and an unencrypted public key file
|
||||
* of the same name with the {@code ".pub"} extension. This allows to delay requesting of the passphrase until the
|
||||
@@ -62,18 +65,7 @@ public class OpenSSHKeyFile
|
||||
final File f = new File(location + ".pub");
|
||||
if (f.exists())
|
||||
try {
|
||||
final BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
try {
|
||||
final String keydata = br.readLine();
|
||||
if (keydata != null) {
|
||||
String[] parts = keydata.split(" ");
|
||||
assert parts.length >= 2;
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
}
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
initPubKey(new FileReader(f));
|
||||
} catch (IOException e) {
|
||||
// let super provide both public & private key
|
||||
log.warn("Error reading public key file: {}", e.toString());
|
||||
@@ -81,4 +73,36 @@ public class OpenSSHKeyFile
|
||||
super.init(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey) {
|
||||
if (publicKey != null) {
|
||||
initPubKey(new StringReader(publicKey));
|
||||
}
|
||||
super.init(privateKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and store the separate public key provided alongside the private key
|
||||
*
|
||||
* @param publicKey Public key accessible through a {@code Reader}
|
||||
*/
|
||||
private void initPubKey(Reader publicKey) {
|
||||
try {
|
||||
final BufferedReader br = new BufferedReader(publicKey);
|
||||
try {
|
||||
final String keydata = br.readLine();
|
||||
if (keydata != null) {
|
||||
String[] parts = keydata.split(" ");
|
||||
assert parts.length >= 2;
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
}
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// let super provide both public & private key
|
||||
log.warn("Error reading public key: {}", e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
|
||||
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import org.bouncycastle.openssl.EncryptionException;
|
||||
import org.bouncycastle.openssl.PEMReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@@ -39,6 +39,7 @@ public class PKCS8KeyFile
|
||||
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
|
||||
|
||||
@Override
|
||||
public FileKeyProvider create() {
|
||||
return new PKCS8KeyFile();
|
||||
@@ -52,7 +53,8 @@ public class PKCS8KeyFile
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected PasswordFinder pwdf;
|
||||
protected PrivateKeyFileResource resource;
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Resource resource;
|
||||
protected KeyPair kp;
|
||||
|
||||
protected KeyType type;
|
||||
@@ -89,6 +91,19 @@ public class PKCS8KeyFile
|
||||
this.pwdf = pwdf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey) {
|
||||
assert privateKey != null;
|
||||
assert publicKey == null;
|
||||
resource = new PrivateKeyStringResource(privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey, PasswordFinder pwdf) {
|
||||
init(privateKey, publicKey);
|
||||
this.pwdf = pwdf;
|
||||
}
|
||||
|
||||
protected org.bouncycastle.openssl.PasswordFinder makeBouncyPasswordFinder() {
|
||||
if (pwdf == null)
|
||||
return null;
|
||||
@@ -108,10 +123,10 @@ public class PKCS8KeyFile
|
||||
PEMReader r = null;
|
||||
Object o = null;
|
||||
try {
|
||||
for (; ;) {
|
||||
for (; ; ) {
|
||||
// while the PasswordFinder tells us we should retry
|
||||
try {
|
||||
r = new PEMReader(new InputStreamReader(new FileInputStream(resource.getDetail())), pFinder);
|
||||
r = new PEMReader(resource.getReader(), pFinder);
|
||||
o = r.readObject();
|
||||
} catch (EncryptionException e) {
|
||||
if (pwdf.shouldRetry(resource))
|
||||
|
||||
@@ -51,6 +51,7 @@ public class AuthKeyboardInteractive
|
||||
}
|
||||
|
||||
private static class CharArrWrap {
|
||||
|
||||
private final char[] arr;
|
||||
|
||||
private CharArrWrap(char[] arr) {
|
||||
|
||||
@@ -48,7 +48,7 @@ public abstract class KeyedAuthMethod
|
||||
|
||||
// public key as 2 strings: [ key type | key blob ]
|
||||
reqBuf.putString(KeyType.fromKey(key).toString())
|
||||
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
|
||||
.putString(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
|
||||
return reqBuf;
|
||||
}
|
||||
|
||||
@@ -67,9 +67,10 @@ public abstract class KeyedAuthMethod
|
||||
throw new UserAuthException("Could not create signature instance for " + kt + " key");
|
||||
|
||||
sigger.init(null, key);
|
||||
sigger.update(new Buffer.PlainBuffer().putString(params.getTransport().getSessionID()) //
|
||||
.putBuffer(reqBuf) // & rest of the data for sig
|
||||
.getCompactData());
|
||||
sigger.update(new Buffer.PlainBuffer()
|
||||
.putString(params.getTransport().getSessionID())
|
||||
.putBuffer(reqBuf) // & rest of the data for sig
|
||||
.getCompactData());
|
||||
reqBuf.putSignature(kt, sigger.sign());
|
||||
return reqBuf;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class AccountResource
|
||||
extends Resource<String> {
|
||||
|
||||
@@ -22,4 +26,9 @@ public class AccountResource
|
||||
super(user + "@" + host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader()
|
||||
throws IOException {
|
||||
return new StringReader(getDetail());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
public class PrivateKeyFileResource
|
||||
extends Resource<File> {
|
||||
@@ -24,4 +28,9 @@ public class PrivateKeyFileResource
|
||||
super(privateKeyFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader()
|
||||
throws IOException {
|
||||
return new InputStreamReader(new FileInputStream(getDetail()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class PrivateKeyStringResource
|
||||
extends Resource<String> {
|
||||
|
||||
public PrivateKeyStringResource(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader()
|
||||
throws IOException {
|
||||
return new StringReader(getDetail());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// If not overridden, the superclass's will return the private key as
|
||||
// part of the string.
|
||||
return "[" + getClass().getSimpleName() + "]";
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/** A password-protected resource */
|
||||
public abstract class Resource<H> {
|
||||
|
||||
@@ -28,6 +31,9 @@ public abstract class Resource<H> {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public abstract Reader getReader()
|
||||
throws IOException;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
|
||||
@@ -26,9 +26,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FileSystemFile
|
||||
implements LocalSourceFile, LocalDestFile {
|
||||
|
||||
@@ -26,7 +26,7 @@ public interface FileTransfer {
|
||||
throws IOException;
|
||||
|
||||
void upload(LocalSourceFile localFile, String remotePath)
|
||||
throws IOException;
|
||||
throws IOException;
|
||||
|
||||
void download(String remotePath, LocalDestFile localFile)
|
||||
throws IOException;
|
||||
|
||||
@@ -45,7 +45,6 @@ public interface LocalDestFile {
|
||||
/**
|
||||
* Set the permissions for the underlying file.
|
||||
*
|
||||
* @param f the file
|
||||
* @param perms permissions e.g. 0644
|
||||
*
|
||||
* @throws IOException
|
||||
@@ -66,7 +65,6 @@ public interface LocalDestFile {
|
||||
/**
|
||||
* Set the last modified time for the underlying file.
|
||||
*
|
||||
* @param f the file
|
||||
* @param t time in seconds since Unix epoch
|
||||
*
|
||||
* @throws IOException
|
||||
|
||||
@@ -34,7 +34,8 @@ public interface LocalSourceFile {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
int getPermissions() throws IOException;
|
||||
int getPermissions()
|
||||
throws IOException;
|
||||
|
||||
boolean isFile();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import net.schmizz.sshj.common.SSHException;
|
||||
|
||||
public class SCPException
|
||||
extends SSHException {
|
||||
|
||||
public SCPException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
import net.schmizz.sshj.xfer.AbstractFileTransfer;
|
||||
import net.schmizz.sshj.xfer.FileSystemFile;
|
||||
@@ -24,6 +22,8 @@ import net.schmizz.sshj.xfer.FileTransfer;
|
||||
import net.schmizz.sshj.xfer.LocalDestFile;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SCPFileTransfer
|
||||
extends AbstractFileTransfer
|
||||
implements FileTransfer {
|
||||
@@ -43,7 +43,7 @@ public class SCPFileTransfer
|
||||
}
|
||||
|
||||
private SCPEngine newSCPEngine() {
|
||||
return new SCPEngine(sessionFactory, getTransferListener());
|
||||
return new SCPEngine(sessionFactory, getTransferListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,9 +65,9 @@ public class SCPFileTransfer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(LocalSourceFile localFile, String remotePath)
|
||||
throws IOException {
|
||||
newSCPUploadClient().copy(localFile, remotePath);
|
||||
}
|
||||
public void upload(LocalSourceFile localFile, String remotePath)
|
||||
throws IOException {
|
||||
newSCPUploadClient().copy(localFile, remotePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.xfer.LocalFileFilter;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
|
||||
|
||||
@@ -28,8 +29,9 @@ import java.util.List;
|
||||
public final class SCPUploadClient {
|
||||
|
||||
private final SCPEngine engine;
|
||||
private LocalFileFilter uploadFilter;
|
||||
|
||||
SCPUploadClient(SCPEngine engine) {
|
||||
SCPUploadClient(SCPEngine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
@@ -45,7 +47,11 @@ public final class SCPUploadClient {
|
||||
return engine.getExitStatus();
|
||||
}
|
||||
|
||||
private synchronized void startCopy(LocalSourceFile sourceFile, String targetPath)
|
||||
public void setUploadFilter(LocalFileFilter uploadFilter) {
|
||||
this.uploadFilter = uploadFilter;
|
||||
}
|
||||
|
||||
private synchronized void startCopy(LocalSourceFile sourceFile, String targetPath)
|
||||
throws IOException {
|
||||
List<Arg> args = new LinkedList<Arg>();
|
||||
args.add(Arg.SINK);
|
||||
@@ -75,7 +81,7 @@ public final class SCPUploadClient {
|
||||
throws IOException {
|
||||
preserveTimeIfPossible(f);
|
||||
engine.sendMessage("D0" + getPermString(f) + " 0 " + f.getName());
|
||||
for (LocalSourceFile child : f.getChildren(null))
|
||||
for (LocalSourceFile child : f.getChildren(uploadFilter))
|
||||
process(child);
|
||||
engine.sendMessage("E");
|
||||
}
|
||||
|
||||
41
src/test/java/net/schmizz/sshj/LoadsOfConnects.java
Normal file
41
src/test/java/net/schmizz/sshj/LoadsOfConnects.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj;
|
||||
|
||||
import net.schmizz.sshj.util.BasicFixture;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class LoadsOfConnects {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final BasicFixture fixture = new BasicFixture();
|
||||
|
||||
@Test
|
||||
public void loadsOfConnects()
|
||||
throws IOException, InterruptedException {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
System.out.println("Try " + i);
|
||||
fixture.init(false);
|
||||
fixture.done();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,7 +50,8 @@ public class SmokeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticated() throws UserAuthException, TransportException {
|
||||
public void authenticated()
|
||||
throws UserAuthException, TransportException {
|
||||
fixture.dummyAuth();
|
||||
assertTrue(fixture.getClient().isAuthenticated());
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -113,6 +114,19 @@ public class OpenSSHKeyFileTest {
|
||||
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromString()
|
||||
throws IOException, GeneralSecurityException {
|
||||
FileKeyProvider dsa = new OpenSSHKeyFile();
|
||||
String privateKey = readFile("src/test/resources/id_dsa");
|
||||
String publicKey = readFile("src/test/resources/id_dsa.pub");
|
||||
dsa.init(privateKey, publicKey,
|
||||
PasswordUtils.createOneOff(correctPassphrase));
|
||||
assertEquals(dsa.getType(), KeyType.DSA);
|
||||
assertEquals(KeyUtil.newDSAPublicKey(y, p, q, g), dsa.getPublic());
|
||||
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
@@ -120,4 +134,18 @@ public class OpenSSHKeyFileTest {
|
||||
throw new AssertionError("bouncy castle needed");
|
||||
}
|
||||
|
||||
private String readFile(String pathname)
|
||||
throws IOException {
|
||||
StringBuilder fileContents = new StringBuilder();
|
||||
Scanner scanner = new Scanner(new File(pathname));
|
||||
String lineSeparator = System.getProperty("line.separator");
|
||||
try {
|
||||
while (scanner.hasNextLine()) {
|
||||
fileContents.append(scanner.nextLine() + lineSeparator);
|
||||
}
|
||||
return fileContents.toString();
|
||||
} finally {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/test/java/net/schmizz/sshj/sftp/FileModeTest.java
Normal file
23
src/test/java/net/schmizz/sshj/sftp/FileModeTest.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class FileModeTest {
|
||||
|
||||
@Test
|
||||
public void shouldDetectDirectoryWithLinuxMask() {
|
||||
FileMode fileMode = new FileMode(040755);
|
||||
assertThat(fileMode.toString(), equalTo("[mask=40755]"));
|
||||
assertThat(fileMode.getType(), equalTo(FileMode.Type.DIRECTORY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDetectDirectoryWithAixUnixMask() {
|
||||
FileMode fileMode = new FileMode(0240755);
|
||||
assertThat(fileMode.toString(), equalTo("[mask=240755]"));
|
||||
assertThat(fileMode.getType(), equalTo(FileMode.Type.DIRECTORY));
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
/** Tests {@link Buffer} functionality */
|
||||
public class BufferTest {
|
||||
|
||||
private Buffer.PlainBuffer posBuf;
|
||||
private Buffer.PlainBuffer handyBuf;
|
||||
|
||||
|
||||
@@ -29,34 +29,38 @@ import java.security.spec.RSAPublicKeySpec;
|
||||
public class KeyUtil {
|
||||
|
||||
/** Creates a DSA private key. */
|
||||
public static PrivateKey newDSAPrivateKey(String x, String p, String q, String g) throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("DSA").generatePrivate(new DSAPrivateKeySpec //
|
||||
(new BigInteger(x, 16), //
|
||||
new BigInteger(p, 16), //
|
||||
new BigInteger(q, 16), //
|
||||
new BigInteger(g, 16)));
|
||||
public static PrivateKey newDSAPrivateKey(String x, String p, String q, String g)
|
||||
throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("DSA").generatePrivate(new DSAPrivateKeySpec(new BigInteger(x, 16),
|
||||
new BigInteger(p, 16),
|
||||
new BigInteger(q, 16),
|
||||
new BigInteger(g, 16))
|
||||
);
|
||||
}
|
||||
|
||||
/** Creates a DSA public key. */
|
||||
public static PublicKey newDSAPublicKey(String y, String p, String q, String g) throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("DSA").generatePublic(new DSAPublicKeySpec //
|
||||
(new BigInteger(y, 16), //
|
||||
new BigInteger(p, 16), //
|
||||
new BigInteger(q, 16), //
|
||||
new BigInteger(g, 16)));
|
||||
public static PublicKey newDSAPublicKey(String y, String p, String q, String g)
|
||||
throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("DSA").generatePublic(new DSAPublicKeySpec(new BigInteger(y, 16),
|
||||
new BigInteger(p, 16),
|
||||
new BigInteger(q, 16),
|
||||
new BigInteger(g, 16))
|
||||
);
|
||||
}
|
||||
|
||||
/** Creates an RSA private key. */
|
||||
public static PrivateKey newRSAPrivateKey(String modulus, String exponent) throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec //
|
||||
(new BigInteger(modulus, 16), //
|
||||
new BigInteger(exponent, 16)));
|
||||
public static PrivateKey newRSAPrivateKey(String modulus, String exponent)
|
||||
throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("RSA").generatePrivate(new RSAPrivateKeySpec(new BigInteger(modulus, 16),
|
||||
new BigInteger(exponent, 16))
|
||||
);
|
||||
}
|
||||
|
||||
/** Creates an RSA public key. */
|
||||
public static PublicKey newRSAPublicKey(String modulus, String exponent) throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("RSA").generatePublic(new RSAPublicKeySpec //
|
||||
(new BigInteger(modulus, 16), new BigInteger(exponent, 16)));
|
||||
public static PublicKey newRSAPublicKey(String modulus, String exponent)
|
||||
throws GeneralSecurityException {
|
||||
return SecurityUtils.getKeyFactory("RSA").generatePublic(new RSAPublicKeySpec(new BigInteger(modulus, 16),
|
||||
new BigInteger(exponent, 16)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.schmizz.sshj.xfer.scp;
|
||||
|
||||
import net.schmizz.sshj.xfer.FileSystemFile;
|
||||
import net.schmizz.sshj.xfer.LocalFileFilter;
|
||||
import net.schmizz.sshj.xfer.LocalSourceFile;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.verification.VerificationMode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mockito.Matchers.endsWith;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class SCPUploadClientTest {
|
||||
|
||||
private SCPEngine engine;
|
||||
private SCPUploadClient scpUploadClient;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
engine = mock(SCPEngine.class);
|
||||
scpUploadClient = new SCPUploadClient(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldOnlySendFilterAcceptedFilesFromDirectory() throws IOException {
|
||||
scpUploadClient.setUploadFilter(new LocalFileFilter() {
|
||||
@Override
|
||||
public boolean accept(LocalSourceFile file) {
|
||||
return !file.getName().contains("not-");
|
||||
}
|
||||
});
|
||||
|
||||
File dir = temp.newFolder("filtered-scp-upload");
|
||||
new File(dir, "not-sent.txt").createNewFile();
|
||||
new File(dir, "sent.txt").createNewFile();
|
||||
|
||||
int copy = scpUploadClient.copy(new FileSystemFile(dir), "/tmp");
|
||||
verify(engine).startedDir("filtered-scp-upload");
|
||||
verify(engine).startedFile(eq("sent.txt"), isA(Long.class));
|
||||
verify(engine, times(1)).startedFile(isA(String.class), isA(Long.class));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user