mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-08 16:18:05 +03:00
Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20c5ab8dfc | ||
|
|
d9c438ed16 | ||
|
|
653e8ad4f2 | ||
|
|
c46dc913e8 | ||
|
|
069ebbd47d | ||
|
|
da2cec8fa2 | ||
|
|
75caa8bcf3 | ||
|
|
f664b7b24f | ||
|
|
70f3aeee68 | ||
|
|
882d40a1b6 | ||
|
|
9649b2f72e | ||
|
|
79a8d0b3ad | ||
|
|
2e7fcfd308 | ||
|
|
946422112d | ||
|
|
b11f0be894 | ||
|
|
ba6e5292c8 | ||
|
|
c8de9ed915 | ||
|
|
7ccd078e52 | ||
|
|
0aa8d5e141 | ||
|
|
2e32bb9aca | ||
|
|
2f4fa62b14 | ||
|
|
8a4367cc7a | ||
|
|
168272ad3b | ||
|
|
17eb5cff0f | ||
|
|
ebd5036d64 | ||
|
|
7797d774ac | ||
|
|
888a8f60d7 | ||
|
|
974e88efb4 | ||
|
|
9a4a24737f | ||
|
|
a1d17982ae | ||
|
|
3beee8350d | ||
|
|
3cd446b462 | ||
|
|
486dbf2b05 | ||
|
|
3cb235bbfd | ||
|
|
2882129211 | ||
|
|
fb97ccb67c | ||
|
|
8b21eff1d2 | ||
|
|
7874e7dbfd | ||
|
|
efc7702195 | ||
|
|
34a7b8e065 | ||
|
|
50c42b97a3 | ||
|
|
826660ab3f | ||
|
|
a3b6fde44a | ||
|
|
69555e9c74 | ||
|
|
241f61bdd1 | ||
|
|
0051dd420c | ||
|
|
f2abc4b397 | ||
|
|
fe0d42fc97 | ||
|
|
19e4670c24 | ||
|
|
fbd6e00720 | ||
|
|
f69cdb1505 | ||
|
|
135b1c819b | ||
|
|
9c51b862cd | ||
|
|
a6353cbb2d | ||
|
|
f11055a726 | ||
|
|
da98153ab6 | ||
|
|
70d4dbd88a | ||
|
|
b2a74935d4 | ||
|
|
f3c072fb06 | ||
|
|
738b317dcf | ||
|
|
d87e0c2da2 | ||
|
|
72ff74c99d | ||
|
|
5cb4f1be43 | ||
|
|
693379feb4 | ||
|
|
98529f733c | ||
|
|
582f789db4 | ||
|
|
c16ccc2644 | ||
|
|
5c3ba2282c | ||
|
|
e765536fa1 | ||
|
|
068fbdfc55 | ||
|
|
f4f9249b35 | ||
|
|
252fcebeac | ||
|
|
a7ccccaacf | ||
|
|
2420027a58 | ||
|
|
b54a4abfce | ||
|
|
0c28deab88 | ||
|
|
12c79dc2b2 | ||
|
|
6ef3dacc81 | ||
|
|
700998d064 | ||
|
|
dea6e3db91 | ||
|
|
dc5ff451f0 | ||
|
|
ca521500ed | ||
|
|
ca07e0d25a | ||
|
|
f372ae782a | ||
|
|
460a730879 | ||
|
|
25a18c6fd7 | ||
|
|
53389e6989 | ||
|
|
2f1b2c4e77 | ||
|
|
09b93ec11a | ||
|
|
63b321e8d1 | ||
|
|
49e3d4ec27 | ||
|
|
8e9a773e92 | ||
|
|
f30afe50f4 | ||
|
|
4af8fab434 | ||
|
|
436e5774a8 | ||
|
|
7a2850d760 | ||
|
|
53d8d548cf | ||
|
|
89d03361ba | ||
|
|
db4185608d | ||
|
|
71b6cee424 | ||
|
|
bdb1fe73e3 | ||
|
|
e327e2e8ad | ||
|
|
66fd4a4823 | ||
|
|
8be0e1c8b0 | ||
|
|
37de8faf9b | ||
|
|
9e92463ff2 | ||
|
|
9cbb75084a | ||
|
|
54ee1a6917 | ||
|
|
dd1be04a61 | ||
|
|
839021cd2b | ||
|
|
e6f2ed3035 | ||
|
|
6581772c18 |
57
NOTICE
57
NOTICE
@@ -6,3 +6,60 @@ The Apache Software Foundation (http://www.apache.org/):
|
||||
|
||||
- Apache MINA SSHD
|
||||
- Apache Commons-Net
|
||||
|
||||
|
||||
// Apache Mina SSHD notice
|
||||
|
||||
=========================================================================
|
||||
== NOTICE file for use with the Apache License, Version 2.0, ==
|
||||
== in this case for the SSHD distribution. ==
|
||||
=========================================================================
|
||||
|
||||
This product contains software developped by JCraft,Inc. and subject to
|
||||
the following license:
|
||||
|
||||
Copyright (c) 2002,2003,2004,2005,2006,2007,2008 Atsuhiko Yamanaka, JCraft,Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2000 - 2006 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
14
README.rst
14
README.rst
@@ -1,3 +1,8 @@
|
||||
.. image:: http://api.flattr.com/button/button-compact-static-100x17.png
|
||||
:align: right
|
||||
:alt: Flattr this
|
||||
:target: http://flattr.com/thing/49085/sshj-ssh-scp-and-sftp-library-for-java
|
||||
|
||||
sshj - SSHv2 library for Java
|
||||
==============================
|
||||
|
||||
@@ -6,10 +11,10 @@ To get started, have a look at one of the examples. Hopefully you will find the
|
||||
Features of the library include:
|
||||
|
||||
* reading known_hosts files for host key verification
|
||||
* password and publickey authentication
|
||||
* publickey, password and keyboard-interactive authentication
|
||||
* command, subsystem and shell channels
|
||||
* local and remote port forwarding
|
||||
* scp + complete sftp version 3 implementation
|
||||
* scp + complete sftp version 0-3 implementation
|
||||
|
||||
Implementations of the following algorithms are included:
|
||||
|
||||
@@ -37,8 +42,7 @@ If you need something that is not implemented, it shouldn't be too hard to add (
|
||||
Dependencies
|
||||
-------------
|
||||
|
||||
slf4j_ is required. bouncycastle_ is highly recommended and required for using some of the crypto algorithms.
|
||||
jzlib_ is required for using zlib compression.
|
||||
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.
|
||||
|
||||
|
||||
Help and discussion
|
||||
@@ -53,8 +57,6 @@ Contributing
|
||||
Fork away!
|
||||
|
||||
|
||||
.. _buildr: http://buildr.apache.org/installing.html
|
||||
|
||||
.. _slf4j: http://www.slf4j.org/download.html
|
||||
|
||||
.. _bouncycastle: http://www.bouncycastle.org/java.html
|
||||
|
||||
118
pom.xml
118
pom.xml
@@ -6,7 +6,7 @@
|
||||
<groupId>net.schmizz</groupId>
|
||||
<artifactId>sshj</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.1.1</version>
|
||||
<version>0.3.1</version>
|
||||
|
||||
<name>sshj</name>
|
||||
<description>SSHv2 library for Java</description>
|
||||
@@ -37,65 +37,43 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.5.11</version>
|
||||
<version>1.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15</artifactId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<version>1.45</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jzlib</artifactId>
|
||||
<version>1.0.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-core</artifactId>
|
||||
<version>0.3.0</version>
|
||||
<version>0.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>1.5.11</version>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>0.9.24</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.15</version>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>0.9.24</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jdmk</groupId>
|
||||
<artifactId>jmxtools</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jmx</groupId>
|
||||
<artifactId>jmxri</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>jms</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>oro</groupId>
|
||||
<artifactId>oro</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -109,6 +87,10 @@
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -118,8 +100,8 @@
|
||||
<excludes>
|
||||
<exclude>examples/*.java</exclude>
|
||||
</excludes>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -127,6 +109,24 @@
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.2-beta-5</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/assemble/examples.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
@@ -172,11 +172,11 @@
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<id>full-deps</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15</artifactId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<version>1.45</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -185,36 +185,14 @@
|
||||
<version>1.0.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>1.5.11</version>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>0.9.24</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.15</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jdmk</groupId>
|
||||
<artifactId>jmxtools</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jmx</groupId>
|
||||
<artifactId>jmxri</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>jms</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>oro</groupId>
|
||||
<artifactId>oro</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>0.9.24</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
15
src/assemble/examples.xml
Normal file
15
src/assemble/examples.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
<id>examples</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>src/main/java/examples</directory>
|
||||
<includes/>
|
||||
<outputDirectory>examples</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -16,32 +16,32 @@
|
||||
package examples;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session;
|
||||
import net.schmizz.sshj.connection.channel.direct.Session.Command;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** This examples demonstrates how a remote command can be executed. */
|
||||
public class Exec {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String... args)
|
||||
throws IOException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
final SSHClient ssh = new SSHClient();
|
||||
ssh.loadKnownHosts();
|
||||
|
||||
ssh.connect("localhost");
|
||||
try {
|
||||
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
|
||||
Command cmd = ssh.startSession().exec("ping google.com -n 1");
|
||||
|
||||
// Pipe.pipe(cmd.getInputStream(), System.out, cmd.getLocalMaxPacketSize(), false);
|
||||
System.out.print(cmd.getOutputAsString());
|
||||
System.out.println("\n** exit status: " + cmd.getExitStatus());
|
||||
final Session session = ssh.startSession();
|
||||
try {
|
||||
final Command cmd = session.exec("ping -c 1 google.com");
|
||||
System.out.print(cmd.getOutputAsString());
|
||||
cmd.join(5, TimeUnit.SECONDS);
|
||||
System.out.println("\n** exit status: " + cmd.getExitStatus());
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
|
||||
@@ -26,11 +26,6 @@ import java.net.InetSocketAddress;
|
||||
*/
|
||||
public class LocalPF {
|
||||
|
||||
// static
|
||||
// {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String... args)
|
||||
throws IOException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
@@ -46,7 +41,6 @@ public class LocalPF {
|
||||
* _We_ listen on localhost:8080 and forward all connections on to server, which then forwards it to
|
||||
* google.com:80
|
||||
*/
|
||||
|
||||
ssh.newLocalPortForwarder(new InetSocketAddress("localhost", 8080), "google.com", 80)
|
||||
.listen();
|
||||
|
||||
|
||||
@@ -28,10 +28,6 @@ import java.net.InetSocketAddress;
|
||||
*/
|
||||
public class RemotePF {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String... args)
|
||||
throws IOException {
|
||||
SSHClient client = new SSHClient();
|
||||
|
||||
@@ -28,10 +28,6 @@ import java.io.IOException;
|
||||
/** A very rudimentary psuedo-terminal based on console I/O. */
|
||||
class RudimentaryPTY {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String... args)
|
||||
throws IOException {
|
||||
|
||||
@@ -41,36 +37,35 @@ class RudimentaryPTY {
|
||||
ssh.addHostKeyVerifier(new ConsoleKnownHostsVerifier(khFile, System.console()));
|
||||
|
||||
ssh.connect("localhost");
|
||||
|
||||
Shell shell = null;
|
||||
|
||||
try {
|
||||
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
|
||||
final Session session = ssh.startSession();
|
||||
session.allocateDefaultPTY();
|
||||
try {
|
||||
|
||||
shell = session.startShell();
|
||||
session.allocateDefaultPTY();
|
||||
|
||||
new StreamCopier("stdout", shell.getInputStream(), System.out)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.start();
|
||||
final Shell shell = session.startShell();
|
||||
|
||||
new StreamCopier("stderr", shell.getErrorStream(), System.err)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.start();
|
||||
new StreamCopier("stdout", shell.getInputStream(), System.out)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.start();
|
||||
|
||||
// Now make System.in act as stdin. To exit, hit Ctrl+D (since that results in an EOF on System.in)
|
||||
// This is kinda messy because java only allows console input after you hit return
|
||||
// But this is just an example... a GUI app could implement a proper PTY
|
||||
StreamCopier.copy(System.in, shell.getOutputStream(), shell.getRemoteMaxPacketSize(), true);
|
||||
new StreamCopier("stderr", shell.getErrorStream(), System.err)
|
||||
.bufSize(shell.getLocalMaxPacketSize())
|
||||
.start();
|
||||
|
||||
// Now make System.in act as stdin. To exit, hit Ctrl+D (since that results in an EOF on System.in)
|
||||
// This is kinda messy because java only allows console input after you hit return
|
||||
// But this is just an example... a GUI app could implement a proper PTY
|
||||
StreamCopier.copy(System.in, shell.getOutputStream(), shell.getRemoteMaxPacketSize(), true);
|
||||
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
||||
if (shell != null)
|
||||
shell.close();
|
||||
|
||||
ssh.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,20 +22,17 @@ import java.io.IOException;
|
||||
/** This example demonstrates downloading of a file over SCP from the SSH server. */
|
||||
public class SCPDownload {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String[] args)
|
||||
throws IOException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
// ssh.useCompression(); // Can lead to significant speedup
|
||||
// ssh.useCompression(); // Can lead to significant speedup (needs JZlib in classpath)
|
||||
ssh.loadKnownHosts();
|
||||
ssh.connect("localhost");
|
||||
try {
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
ssh.newSCPFileTransfer()
|
||||
.download("well", "/tmp/");
|
||||
final String src = "test_file";
|
||||
final String target = "/tmp/";
|
||||
ssh.newSCPFileTransfer().download(src, target);
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
}
|
||||
|
||||
@@ -17,15 +17,12 @@ package examples;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/** This example demonstrates uploading of a file over SCP to the SSH server. */
|
||||
public class SCPUpload {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String[] args)
|
||||
throws IOException, ClassNotFoundException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
@@ -35,10 +32,12 @@ public class SCPUpload {
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
|
||||
// Present here to demo algorithm renegotiation - could have just put this before connect()
|
||||
// Make sure JZlib is in classpath for this to work
|
||||
ssh.useCompression();
|
||||
|
||||
ssh.newSCPFileTransfer()
|
||||
.upload("/Users/shikhar/well", "/tmp/");
|
||||
final String src = System.getProperty("user.home") + File.separator + "test_file";
|
||||
final String target = "/tmp/";
|
||||
ssh.newSCPFileTransfer().upload(src, target);
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
}
|
||||
|
||||
@@ -16,24 +16,28 @@
|
||||
package examples;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.sftp.SFTPClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** This example demonstrates downloading of a file over SFTP from the SSH server. */
|
||||
public class SFTPDownload {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String[] args)
|
||||
throws IOException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
final SSHClient ssh = new SSHClient();
|
||||
ssh.loadKnownHosts();
|
||||
ssh.connect("localhost");
|
||||
try {
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
ssh.newSFTPClient().get("well", "/tmp/");
|
||||
final String src = "test_file";
|
||||
final String target = "/tmp/";
|
||||
final SFTPClient sftp = ssh.newSFTPClient();
|
||||
try {
|
||||
sftp.get(src, target);
|
||||
} finally {
|
||||
sftp.close();
|
||||
}
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
}
|
||||
|
||||
@@ -16,25 +16,29 @@
|
||||
package examples;
|
||||
|
||||
import net.schmizz.sshj.SSHClient;
|
||||
import net.schmizz.sshj.sftp.SFTPClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/** This example demonstrates uploading of a file over SFTP to the SSH server. */
|
||||
public class SFTPUpload {
|
||||
|
||||
// static
|
||||
// {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String[] args)
|
||||
throws IOException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
final SSHClient ssh = new SSHClient();
|
||||
ssh.loadKnownHosts();
|
||||
ssh.connect("localhost");
|
||||
try {
|
||||
ssh.authPublickey(System.getProperty("user.name"));
|
||||
ssh.newSFTPClient().put("/Users/shikhar/well", "/tmp/");
|
||||
final String src = System.getProperty("user.home") + File.separator + "test_file";
|
||||
final String target = "/tmp/";
|
||||
final SFTPClient sftp = ssh.newSFTPClient();
|
||||
try {
|
||||
sftp.put(src, target);
|
||||
} finally {
|
||||
sftp.close();
|
||||
}
|
||||
} finally {
|
||||
ssh.disconnect();
|
||||
}
|
||||
|
||||
@@ -27,13 +27,9 @@ import java.net.InetSocketAddress;
|
||||
/** This example demonstrates how forwarding X11 connections from a remote host can be accomplished. */
|
||||
public class X11 {
|
||||
|
||||
// static {
|
||||
// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
|
||||
// }
|
||||
|
||||
public static void main(String... args)
|
||||
throws IOException, InterruptedException {
|
||||
SSHClient ssh = new SSHClient();
|
||||
final SSHClient ssh = new SSHClient();
|
||||
|
||||
// Compression makes X11 more feasible over slower connections
|
||||
// ssh.useCompression();
|
||||
@@ -59,7 +55,7 @@ public class X11 {
|
||||
*/
|
||||
sess.reqX11Forwarding("MIT-MAGIC-COOKIE-1", "b0956167c9ad8f34c8a2788878307dc9", 0);
|
||||
|
||||
Command cmd = sess.exec("mate");
|
||||
final Command cmd = sess.exec("/usr/X11/bin/xcalc");
|
||||
|
||||
new StreamCopier("stdout", cmd.getInputStream(), System.out).start();
|
||||
new StreamCopier("stderr", cmd.getErrorStream(), System.err).start();
|
||||
|
||||
@@ -32,8 +32,9 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
*/
|
||||
public class Future<V, T extends Throwable> {
|
||||
|
||||
private final Logger log;
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final String name;
|
||||
private final ExceptionChainer<T> chainer;
|
||||
private final ReentrantLock lock;
|
||||
private final Condition cond;
|
||||
@@ -60,7 +61,7 @@ public class Future<V, T extends Throwable> {
|
||||
* @param lock lock to use
|
||||
*/
|
||||
public Future(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
|
||||
this.log = LoggerFactory.getLogger("<< " + name + " >>");
|
||||
this.name = name;
|
||||
this.chainer = chainer;
|
||||
this.lock = lock == null ? new ReentrantLock() : lock;
|
||||
this.cond = this.lock.newCondition();
|
||||
@@ -74,7 +75,7 @@ public class Future<V, T extends Throwable> {
|
||||
public void set(V val) {
|
||||
lock();
|
||||
try {
|
||||
log.debug("Setting to `{}`", val);
|
||||
log.debug("Setting <<{}>> to `{}`", name, val);
|
||||
this.val = val;
|
||||
cond.signalAll();
|
||||
} finally {
|
||||
@@ -139,14 +140,14 @@ public class Future<V, T extends Throwable> {
|
||||
throw pendingEx;
|
||||
if (val != null)
|
||||
return val;
|
||||
log.debug("Awaiting");
|
||||
log.debug("Awaiting <<{}>>", name);
|
||||
while (val == null && pendingEx == null)
|
||||
if (timeout == 0)
|
||||
cond.await();
|
||||
else if (!cond.await(timeout, unit))
|
||||
throw chainer.chain(new TimeoutException("Timeout expired"));
|
||||
if (pendingEx != null) {
|
||||
log.error("Woke to: {}", pendingEx.toString());
|
||||
log.error("<<{}>> woke to: {}", name, pendingEx.toString());
|
||||
throw pendingEx;
|
||||
}
|
||||
return val;
|
||||
|
||||
@@ -44,29 +44,35 @@ public abstract class AbstractService
|
||||
timeout = trans.getTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws SSHException {
|
||||
trans.sendUnimplemented();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyError(SSHException error) {
|
||||
log.debug("Was notified of {}", error.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUnimplemented(long seqNum)
|
||||
throws SSHException {
|
||||
throw new SSHException(DisconnectReason.PROTOCOL_ERROR, "Unexpected: SSH_MSG_UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDisconnect()
|
||||
throws SSHException {
|
||||
log.debug("Was notified of disconnect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void request()
|
||||
throws TransportException {
|
||||
final Service active = trans.getService();
|
||||
|
||||
28
src/main/java/net/schmizz/sshj/AndroidConfig.java
Normal file
28
src/main/java/net/schmizz/sshj/AndroidConfig.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010 Shikhar Bhushan
|
||||
*
|
||||
* 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.transport.random.JCERandom;
|
||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||
|
||||
public class AndroidConfig extends DefaultConfig {
|
||||
|
||||
@Override
|
||||
protected void initRandomFactory(boolean ignored) {
|
||||
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -142,5 +142,5 @@ public interface Config {
|
||||
* @param version software version info
|
||||
*/
|
||||
void setVersion(String version);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -62,34 +62,42 @@ public class ConfigImpl
|
||||
private List<Factory.Named<Signature>> signatureFactories;
|
||||
private List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories;
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<Cipher>> getCipherFactories() {
|
||||
return cipherFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<Compression>> getCompressionFactories() {
|
||||
return compressionFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<FileKeyProvider>> getFileKeyProviderFactories() {
|
||||
return fileKeyProviderFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<KeyExchange>> getKeyExchangeFactories() {
|
||||
return kexFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<MAC>> getMACFactories() {
|
||||
return macFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Factory<Random> getRandomFactory() {
|
||||
return randomFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Factory.Named<Signature>> getSignatureFactories() {
|
||||
return signatureFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
@@ -98,6 +106,7 @@ public class ConfigImpl
|
||||
setCipherFactories(Arrays.asList(cipherFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCipherFactories(List<Factory.Named<Cipher>> cipherFactories) {
|
||||
this.cipherFactories = cipherFactories;
|
||||
}
|
||||
@@ -106,6 +115,7 @@ public class ConfigImpl
|
||||
setCompressionFactories(Arrays.asList(compressionFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompressionFactories(List<Factory.Named<Compression>> compressionFactories) {
|
||||
this.compressionFactories = compressionFactories;
|
||||
}
|
||||
@@ -114,6 +124,7 @@ public class ConfigImpl
|
||||
setFileKeyProviderFactories(Arrays.asList(fileKeyProviderFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileKeyProviderFactories(List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories) {
|
||||
this.fileKeyProviderFactories = fileKeyProviderFactories;
|
||||
}
|
||||
@@ -122,6 +133,7 @@ public class ConfigImpl
|
||||
setKeyExchangeFactories(Arrays.asList(kexFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyExchangeFactories(List<Factory.Named<KeyExchange>> kexFactories) {
|
||||
this.kexFactories = kexFactories;
|
||||
}
|
||||
@@ -130,10 +142,12 @@ public class ConfigImpl
|
||||
setMACFactories(Arrays.asList(macFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMACFactories(List<Factory.Named<MAC>> macFactories) {
|
||||
this.macFactories = macFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRandomFactory(Factory<Random> randomFactory) {
|
||||
this.randomFactory = randomFactory;
|
||||
}
|
||||
@@ -142,10 +156,12 @@ public class ConfigImpl
|
||||
setSignatureFactories(Arrays.asList(signatureFactories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignatureFactories(List<Factory.Named<Signature>> signatureFactories) {
|
||||
this.signatureFactories = signatureFactories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class DefaultConfig
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private static final String VERSION = "SSHJ_0_1";
|
||||
private static final String VERSION = "SSHJ_0_3";
|
||||
|
||||
public DefaultConfig() {
|
||||
setVersion(VERSION);
|
||||
@@ -134,6 +134,7 @@ public class DefaultConfig
|
||||
new TripleDESCBC.Factory(),
|
||||
new BlowfishCBC.Factory()));
|
||||
|
||||
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();) {
|
||||
@@ -144,10 +145,12 @@ public class DefaultConfig
|
||||
final byte[] iv = new byte[c.getIVSize()];
|
||||
c.init(Cipher.Mode.Encrypt, key, iv);
|
||||
} catch (Exception e) {
|
||||
log.warn("Disabling cipher `{}`: cipher strengths apparently limited by JCE policy", f.getName());
|
||||
warn = true;
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
if (warn)
|
||||
log.warn("Disabling high-strength ciphers: cipher strengths apparently limited by JCE policy");
|
||||
|
||||
setCipherFactories(avail);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder.Forward
|
||||
import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder;
|
||||
import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder.X11Channel;
|
||||
import net.schmizz.sshj.sftp.SFTPClient;
|
||||
import net.schmizz.sshj.sftp.SFTPEngine;
|
||||
import net.schmizz.sshj.sftp.StatefulSFTPClient;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
@@ -49,11 +50,14 @@ import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||
import net.schmizz.sshj.userauth.method.AuthPassword;
|
||||
import net.schmizz.sshj.userauth.method.AuthPublickey;
|
||||
import net.schmizz.sshj.userauth.method.PasswordResponseProvider;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -87,18 +91,19 @@ import java.util.List;
|
||||
* <p/>
|
||||
* <em>A simple example:</em>
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <pre>
|
||||
* client = new SSHClient();
|
||||
* client.initUserKnownHosts();
|
||||
* client.connect("hostname");
|
||||
* try
|
||||
* {
|
||||
* try {
|
||||
* client.authPassword("username", "password");
|
||||
* client.startSession().exec("true");
|
||||
* client.getConnection().join();
|
||||
* } finally
|
||||
* {
|
||||
* final Session session = client.startSession();
|
||||
* try {
|
||||
* session.exec("true");
|
||||
* client.getConnection().join();
|
||||
* } finally {
|
||||
* session.close();
|
||||
* } finally {
|
||||
* client.disconnect();
|
||||
* }
|
||||
* </pre>
|
||||
@@ -116,7 +121,6 @@ public class SSHClient
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
|
||||
/** Transport layer */
|
||||
protected final Transport trans;
|
||||
|
||||
@@ -154,19 +158,18 @@ public class SSHClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link HostKeyVerifier} that will verify any host at given {@code hostname:port} and a host key that has
|
||||
* the given {@code fingerprint}, e.g. {@code "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"}
|
||||
* Add a {@link HostKeyVerifier} that will verify any host that's able to claim a host key with the given {@code
|
||||
* fingerprint}, e.g. {@code "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"}
|
||||
*
|
||||
* @param host the hostname / IP address
|
||||
* @param port the port for which the {@code fingerprint} applies
|
||||
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
|
||||
*
|
||||
* @see SecurityUtils#getFingerprint
|
||||
*/
|
||||
public void addHostKeyVerifier(final String host, final int port, final String fingerprint) {
|
||||
public void addHostKeyVerifier(final String fingerprint) {
|
||||
addHostKeyVerifier(new HostKeyVerifier() {
|
||||
@Override
|
||||
public boolean verify(String h, int p, PublicKey k) {
|
||||
return host.equals(h) && port == p && SecurityUtils.getFingerprint(k).equals(fingerprint);
|
||||
return SecurityUtils.getFingerprint(k).equals(fingerprint);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -202,36 +205,8 @@ public class SSHClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method. The {@code password} array is
|
||||
* blanked out after use.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param password the password to use for authentication
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, char[] password)
|
||||
throws UserAuthException, TransportException {
|
||||
authPassword(username, PasswordUtils.createOneOff(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param pfinder the {@link PasswordFinder} to use for authentication
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, PasswordFinder pfinder)
|
||||
throws UserAuthException, TransportException {
|
||||
auth(username, new AuthPassword(pfinder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method.
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method and as a fallback basic
|
||||
* challenge-response authentication.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param password the password to use for authentication
|
||||
@@ -244,6 +219,52 @@ public class SSHClient
|
||||
authPassword(username, password.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method and as a fallback basic
|
||||
* challenge-response authentication.. The {@code password} array is blanked out after use.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param password the password to use for authentication
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(final String username, final char[] password)
|
||||
throws UserAuthException, TransportException {
|
||||
try {
|
||||
authPassword(username, new PasswordFinder() {
|
||||
|
||||
@Override
|
||||
public char[] reqPassword(Resource<?> resource) {
|
||||
return password.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Resource<?> resource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
} finally {
|
||||
PasswordUtils.blankOut(password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method and as a fallback basic
|
||||
* challenge-response authentication.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param pfinder the {@link PasswordFinder} to use for authentication
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, PasswordFinder pfinder)
|
||||
throws UserAuthException, TransportException {
|
||||
auth(username, new AuthPassword(pfinder), new AuthKeyboardInteractive(new PasswordResponseProvider(pfinder)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate {@code username} using the {@code "publickey"} authentication method, with keys from some common
|
||||
* locations on the file system. This method relies on {@code ~/.ssh/id_rsa} and {@code ~/.ssh/id_dsa}.
|
||||
@@ -574,7 +595,7 @@ public class SSHClient
|
||||
public SFTPClient newSFTPClient()
|
||||
throws IOException {
|
||||
assert isConnected() && isAuthenticated();
|
||||
return new SFTPClient(this);
|
||||
return new SFTPClient(new SFTPEngine(this).init());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -587,6 +608,7 @@ public class SSHClient
|
||||
doKex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session startSession()
|
||||
throws ConnectionException, TransportException {
|
||||
assert isConnected() && isAuthenticated();
|
||||
@@ -607,7 +629,7 @@ public class SSHClient
|
||||
* @throws TransportException if an error occurs during renegotiation
|
||||
*/
|
||||
public void useCompression()
|
||||
throws ClassNotFoundException, TransportException {
|
||||
throws TransportException {
|
||||
trans.getConfig().setCompressionFactories(Arrays.asList(
|
||||
new DelayedZlibCompression.Factory(),
|
||||
new ZlibCompression.Factory(),
|
||||
|
||||
@@ -120,8 +120,6 @@ abstract class SocketClient {
|
||||
output.close();
|
||||
output = null;
|
||||
}
|
||||
input = null;
|
||||
output = null;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
|
||||
@@ -38,12 +38,7 @@ package net.schmizz.sshj.common;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Buffer<T extends Buffer<T>> {
|
||||
@@ -445,71 +440,34 @@ public class Buffer<T extends Buffer<T>> {
|
||||
* This is useful when a plaintext password needs to be sent. If {@code passwd} is {@code null}, an empty string is
|
||||
* written.
|
||||
*
|
||||
* @param passwd (null-ok) the password as a character array
|
||||
* @param str (null-ok) the password as a character array
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T putPassword(char[] passwd) {
|
||||
if (passwd == null)
|
||||
public T putSensitiveString(char[] str) {
|
||||
if (str == null)
|
||||
return putString("");
|
||||
putInt(passwd.length);
|
||||
ensureCapacity(passwd.length);
|
||||
for (char c : passwd)
|
||||
putInt(str.length);
|
||||
ensureCapacity(str.length);
|
||||
for (char c : str)
|
||||
data[wpos++] = (byte) c;
|
||||
Arrays.fill(passwd, ' ');
|
||||
Arrays.fill(str, ' ');
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public PublicKey readPublicKey() {
|
||||
try {
|
||||
switch (KeyType.fromString(readString())) {
|
||||
case RSA: {
|
||||
BigInteger e = readMPInt();
|
||||
BigInteger n = readMPInt();
|
||||
KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
|
||||
return keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
|
||||
}
|
||||
case DSA: {
|
||||
BigInteger p = readMPInt();
|
||||
BigInteger q = readMPInt();
|
||||
BigInteger g = readMPInt();
|
||||
BigInteger y = readMPInt();
|
||||
KeyFactory keyFactory = SecurityUtils.getKeyFactory("DSA");
|
||||
return keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
|
||||
}
|
||||
default:
|
||||
assert false;
|
||||
}
|
||||
final String type = readString();
|
||||
return KeyType.fromString(type).readPubKeyFromBuffer(type, this);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T putPublicKey(PublicKey key) {
|
||||
final KeyType type = KeyType.fromKey(key);
|
||||
switch (type) {
|
||||
case RSA: {
|
||||
final RSAPublicKey rsaKey = (RSAPublicKey) key;
|
||||
putString(type.toString()) // ssh-rsa
|
||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
break;
|
||||
}
|
||||
case DSA: {
|
||||
final DSAPublicKey dsaKey = (DSAPublicKey) key;
|
||||
putString(type.toString()) // ssh-dss
|
||||
.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new SSHRuntimeException("Don't know how to encode key: " + key);
|
||||
}
|
||||
KeyType.fromKey(key).putPubKeyIntoBuffer(key, this);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,39 +18,32 @@ package net.schmizz.sshj.common;
|
||||
/** Disconnect error codes */
|
||||
public enum DisconnectReason {
|
||||
|
||||
UNKNOWN(0),
|
||||
HOST_NOT_ALLOWED_TO_CONNECT(1),
|
||||
PROTOCOL_ERROR(2),
|
||||
KEY_EXCHANGE_FAILED(3),
|
||||
HOST_AUTHENTICATION_FAILED(4),
|
||||
RESERVED(4),
|
||||
MAC_ERROR(5),
|
||||
COMPRESSION_ERROR(6),
|
||||
SERVICE_NOT_AVAILABLE(7),
|
||||
PROTOCOL_VERSION_NOT_SUPPORTED(8),
|
||||
HOST_KEY_NOT_VERIFIABLE(9),
|
||||
CONNECTION_LOST(10),
|
||||
BY_APPLICATION(11),
|
||||
TOO_MANY_CONNECTIONS(12),
|
||||
AUTH_CANCELLED_BY_USER(13),
|
||||
NO_MORE_AUTH_METHODS_AVAILABLE(14),
|
||||
ILLEGAL_USER_NAME(15);
|
||||
UNKNOWN,
|
||||
HOST_NOT_ALLOWED_TO_CONNECT,
|
||||
PROTOCOL_ERROR,
|
||||
KEY_EXCHANGE_FAILED,
|
||||
RESERVED,
|
||||
MAC_ERROR,
|
||||
COMPRESSION_ERROR,
|
||||
SERVICE_NOT_AVAILABLE,
|
||||
PROTOCOL_VERSION_NOT_SUPPORTED,
|
||||
HOST_KEY_NOT_VERIFIABLE,
|
||||
CONNECTION_LOST,
|
||||
BY_APPLICATION,
|
||||
TOO_MANY_CONNECTIONS,
|
||||
AUTH_CANCELLED_BY_USER,
|
||||
NO_MORE_AUTH_METHODS_AVAILABLE,
|
||||
ILLEGAL_USER_NAME;
|
||||
|
||||
public static DisconnectReason fromInt(int code) {
|
||||
for (DisconnectReason dc : values())
|
||||
if (dc.code == code)
|
||||
return dc;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private final int code;
|
||||
|
||||
private DisconnectReason(int code) {
|
||||
this.code = code;
|
||||
final int len = values().length;
|
||||
if (code < 0 || code > len)
|
||||
return UNKNOWN;
|
||||
return values()[code];
|
||||
}
|
||||
|
||||
public int toInt() {
|
||||
return code;
|
||||
return ordinal();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,47 +15,117 @@
|
||||
*/
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.DSAPrivateKey;
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
|
||||
/** Type of key e.g. rsa, dsa */
|
||||
public enum KeyType {
|
||||
|
||||
/** SSH identifier for RSA keys */
|
||||
RSA("ssh-rsa", new KeyChecker() {
|
||||
public boolean isMyType(Key key) {
|
||||
RSA("ssh-rsa") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
final BigInteger e = buf.readMPInt();
|
||||
final BigInteger n = buf.readMPInt();
|
||||
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
|
||||
return keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
|
||||
}
|
||||
}),
|
||||
|
||||
},
|
||||
|
||||
/** SSH identifier for DSA keys */
|
||||
DSA("ssh-dss", new KeyChecker() {
|
||||
public boolean isMyType(Key key) {
|
||||
DSA("ssh-dss") {
|
||||
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
final BigInteger p = buf.readMPInt();
|
||||
final BigInteger q = buf.readMPInt();
|
||||
final BigInteger g = buf.readMPInt();
|
||||
final BigInteger y = buf.readMPInt();
|
||||
final KeyFactory keyFactory = SecurityUtils.getKeyFactory("DSA");
|
||||
return keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
|
||||
buf.putString(sType)
|
||||
.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return (key instanceof DSAPublicKey || key instanceof DSAPrivateKey);
|
||||
}
|
||||
}),
|
||||
|
||||
},
|
||||
|
||||
/** Unrecognized */
|
||||
UNKNOWN("unknown", null);
|
||||
UNKNOWN("unknown") {
|
||||
|
||||
private static interface KeyChecker {
|
||||
boolean isMyType(Key key);
|
||||
}
|
||||
@Override
|
||||
public PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException {
|
||||
throw new UnsupportedOperationException("Don't know how to decode key:" + type);
|
||||
}
|
||||
|
||||
private final String sType;
|
||||
private final KeyChecker checker;
|
||||
@Override
|
||||
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
|
||||
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
|
||||
}
|
||||
|
||||
private KeyType(String type, KeyChecker checker) {
|
||||
@Override
|
||||
protected boolean isMyType(Key key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
protected final String sType;
|
||||
|
||||
private KeyType(String type) {
|
||||
this.sType = type;
|
||||
this.checker = checker;
|
||||
}
|
||||
|
||||
public abstract PublicKey readPubKeyFromBuffer(String type, Buffer<?> buf)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
public abstract void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf);
|
||||
|
||||
protected abstract boolean isMyType(Key key);
|
||||
|
||||
public static KeyType fromKey(Key key) {
|
||||
for (KeyType kt : values())
|
||||
if (kt.checker != null && kt.checker.isMyType((key)))
|
||||
if (kt.isMyType((key)))
|
||||
return kt;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package net.schmizz.sshj.common;
|
||||
/** SSH message identifiers */
|
||||
public enum Message {
|
||||
|
||||
UNKNOWN(0),
|
||||
DISCONNECT(1),
|
||||
IGNORE(2),
|
||||
UNIMPLEMENTED(3),
|
||||
@@ -67,8 +68,11 @@ public enum Message {
|
||||
|
||||
static {
|
||||
for (Message c : Message.values())
|
||||
if (cache[c.toByte()] == null)
|
||||
cache[c.toByte()] = c;
|
||||
cache[c.toByte()] = c;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (cache[i] == null)
|
||||
cache[i] = UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static Message fromByte(byte b) {
|
||||
|
||||
@@ -48,6 +48,7 @@ public class SSHException
|
||||
|
||||
public static final ExceptionChainer<SSHException> chainer = new ExceptionChainer<SSHException>() {
|
||||
|
||||
@Override
|
||||
public SSHException chain(Throwable t) {
|
||||
if (t instanceof SSHException)
|
||||
return (SSHException) t;
|
||||
|
||||
@@ -76,11 +76,7 @@ public class SSHPacket
|
||||
* @return the message identifier
|
||||
*/
|
||||
public Message readMessageID() {
|
||||
byte b = readByte();
|
||||
Message cmd = Message.fromByte(b);
|
||||
if (cmd == null)
|
||||
throw new BufferException("Unknown message ID: " + b);
|
||||
return cmd;
|
||||
return Message.fromByte(readByte());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class StreamCopier
|
||||
extends Thread {
|
||||
@@ -34,18 +33,23 @@ public class StreamCopier
|
||||
}
|
||||
|
||||
public static ErrorCallback closeOnErrorCallback(final Closeable... toClose) {
|
||||
final Closeable[] closeables = Arrays.copyOf(toClose, toClose.length);
|
||||
return new ErrorCallback() {
|
||||
@Override
|
||||
public void onError(IOException ioe) {
|
||||
IOUtils.closeQuietly(closeables);
|
||||
IOUtils.closeQuietly(toClose);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing)
|
||||
public interface Listener {
|
||||
void reportProgress(long transferred);
|
||||
}
|
||||
|
||||
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing, Listener listener)
|
||||
throws IOException {
|
||||
long count = 0;
|
||||
|
||||
final boolean reportProgress = listener != null;
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
final byte[] buf = new byte[bufSize];
|
||||
@@ -55,6 +59,8 @@ public class StreamCopier
|
||||
count += read;
|
||||
if (keepFlushing)
|
||||
out.flush();
|
||||
if (reportProgress)
|
||||
listener.reportProgress(count);
|
||||
}
|
||||
if (!keepFlushing)
|
||||
out.flush();
|
||||
@@ -66,6 +72,11 @@ public class StreamCopier
|
||||
return count;
|
||||
}
|
||||
|
||||
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing)
|
||||
throws IOException {
|
||||
return copy(in, out, bufSize, keepFlushing, null);
|
||||
}
|
||||
|
||||
public static String copyStreamToString(InputStream stream)
|
||||
throws IOException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
@@ -75,7 +86,7 @@ public class StreamCopier
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final Logger log;
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
@@ -84,6 +95,7 @@ public class StreamCopier
|
||||
private boolean keepFlushing = true;
|
||||
|
||||
private ErrorCallback errCB = new ErrorCallback() {
|
||||
@Override
|
||||
public void onError(IOException ioe) {
|
||||
}
|
||||
}; // Default null cb
|
||||
@@ -91,9 +103,7 @@ public class StreamCopier
|
||||
public StreamCopier(String name, InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
|
||||
setName("streamCopier");
|
||||
log = LoggerFactory.getLogger(name);
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public StreamCopier bufSize(int size) {
|
||||
|
||||
@@ -24,6 +24,7 @@ public class ConnectionException
|
||||
extends SSHException {
|
||||
|
||||
public static final ExceptionChainer<ConnectionException> chainer = new ExceptionChainer<ConnectionException>() {
|
||||
@Override
|
||||
public ConnectionException chain(Throwable t) {
|
||||
if (t instanceof ConnectionException)
|
||||
return (ConnectionException) t;
|
||||
|
||||
@@ -30,6 +30,7 @@ import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
@@ -63,19 +64,23 @@ public class ConnectionImpl
|
||||
super("ssh-connection", trans);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(Channel chan) {
|
||||
log.info("Attaching `{}` channel (#{})", chan.getType(), chan.getID());
|
||||
channels.put(chan.getID(), chan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel get(int id) {
|
||||
return channels.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForwardedChannelOpener get(String chanType) {
|
||||
return openers.get(chanType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forget(Channel chan) {
|
||||
log.info("Forgetting `{}` channel (#{})", chan.getType(), chan.getID());
|
||||
channels.remove(chan.getID());
|
||||
@@ -85,11 +90,13 @@ public class ConnectionImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forget(ForwardedChannelOpener opener) {
|
||||
log.info("Forgetting opener for `{}` channels: {}", opener.getChannelType(), opener);
|
||||
openers.remove(opener.getChannelType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(ForwardedChannelOpener opener) {
|
||||
log.info("Attaching opener for `{}` channels: {}", opener.getChannelType(), opener);
|
||||
openers.put(opener.getChannelType(), opener);
|
||||
@@ -146,26 +153,32 @@ public class ConnectionImpl
|
||||
channels.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPacketSize() {
|
||||
return maxPacketSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transport getTransport() {
|
||||
return trans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxPacketSize(int maxPacketSize) {
|
||||
this.maxPacketSize = maxPacketSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowSize() {
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWindowSize(int windowSize) {
|
||||
this.windowSize = windowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void join()
|
||||
throws InterruptedException {
|
||||
synchronized (internalSynchronizer) {
|
||||
@@ -174,10 +187,12 @@ public class ConnectionImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextID() {
|
||||
return nextID.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
|
||||
byte[] specifics)
|
||||
throws TransportException {
|
||||
@@ -221,6 +236,7 @@ public class ConnectionImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOpenFailure(int recipient, Reason reason, String message)
|
||||
throws TransportException {
|
||||
trans.write(new SSHPacket(Message.CHANNEL_OPEN_FAILURE)
|
||||
@@ -233,9 +249,9 @@ public class ConnectionImpl
|
||||
public void notifyDisconnect()
|
||||
throws SSHException {
|
||||
super.notifyDisconnect();
|
||||
FutureUtils.alertAll(new ConnectionException("Disconnected."), globalReqFutures);
|
||||
for (Channel chan : channels.values())
|
||||
chan.close();
|
||||
final ConnectionException ex = new ConnectionException("Disconnected.");
|
||||
FutureUtils.alertAll(ex, globalReqFutures);
|
||||
ErrorNotifiable.Util.alertAll(ex, new HashSet<Channel>(channels.values()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public abstract class AbstractChannel
|
||||
implements Channel {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log;
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/** Transport layer */
|
||||
protected final Transport trans;
|
||||
@@ -78,12 +78,12 @@ public abstract class AbstractChannel
|
||||
|
||||
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new LinkedList<Event<ConnectionException>>();
|
||||
|
||||
/* The lock used by #newEvent to create open & close events */
|
||||
/* The lock used by to create the open & close events */
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
/** Channel open event */
|
||||
protected final Event<ConnectionException> open;
|
||||
/** Channel close event */
|
||||
private final Event<ConnectionException> close;
|
||||
protected final Event<ConnectionException> close;
|
||||
|
||||
/* Access to these fields should be synchronized using this object */
|
||||
private boolean eofSent;
|
||||
@@ -109,9 +109,7 @@ public abstract class AbstractChannel
|
||||
|
||||
id = conn.nextID();
|
||||
|
||||
log = LoggerFactory.getLogger("chan#" + id);
|
||||
|
||||
lwin = new Window.Local(id, conn.getWindowSize(), conn.getMaxPacketSize());
|
||||
lwin = new Window.Local(conn.getWindowSize(), conn.getMaxPacketSize());
|
||||
in = new ChannelInputStream(this, trans, lwin);
|
||||
|
||||
open = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, lock);
|
||||
@@ -120,51 +118,62 @@ public abstract class AbstractChannel
|
||||
|
||||
protected void init(int recipient, int remoteWinSize, int remoteMaxPacketSize) {
|
||||
this.recipient = recipient;
|
||||
rwin = new Window.Remote(id, remoteWinSize, remoteMaxPacketSize);
|
||||
rwin = new Window.Remote(remoteWinSize, remoteMaxPacketSize);
|
||||
out = new ChannelOutputStream(this, trans, rwin);
|
||||
log.info("Initialized - {}", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAutoExpand() {
|
||||
return autoExpand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalMaxPacketSize() {
|
||||
return lwin.getMaxPacketSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalWinSize() {
|
||||
return lwin.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteMaxPacketSize() {
|
||||
return rwin.getMaxPacketSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteWinSize() {
|
||||
return rwin.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
switch (msg) {
|
||||
@@ -223,6 +232,7 @@ public abstract class AbstractChannel
|
||||
IOUtils.closeQuietly(in, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyError(SSHException error) {
|
||||
log.debug("Channel #{} got notified of {}", getID(), error.toString());
|
||||
|
||||
@@ -235,10 +245,12 @@ public abstract class AbstractChannel
|
||||
finishOff();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoExpand(boolean autoExpand) {
|
||||
this.autoExpand = autoExpand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws ConnectionException, TransportException {
|
||||
lock.lock();
|
||||
@@ -255,6 +267,16 @@ public abstract class AbstractChannel
|
||||
}
|
||||
}
|
||||
|
||||
public void join()
|
||||
throws ConnectionException {
|
||||
close.await();
|
||||
}
|
||||
|
||||
public void join(int timeout, TimeUnit unit)
|
||||
throws ConnectionException {
|
||||
close.await(timeout, unit);
|
||||
}
|
||||
|
||||
protected synchronized void sendClose()
|
||||
throws TransportException {
|
||||
try {
|
||||
@@ -267,6 +289,7 @@ public abstract class AbstractChannel
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isOpen() {
|
||||
lock.lock();
|
||||
try {
|
||||
@@ -289,7 +312,6 @@ public abstract class AbstractChannel
|
||||
rwin.expand(howMuch);
|
||||
}
|
||||
|
||||
/** Called when this channel's end-of-life has been reached. Subclasses may override but must call super. */
|
||||
protected void finishOff() {
|
||||
conn.forget(this);
|
||||
close.set();
|
||||
@@ -317,7 +339,7 @@ public abstract class AbstractChannel
|
||||
protected void receiveInto(ChannelInputStream stream, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final int len = buf.readInt();
|
||||
if (len < 0 || len > getLocalMaxPacketSize() || len != buf.available())
|
||||
if (len < 0 || len > getLocalMaxPacketSize() || len > buf.available())
|
||||
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Bad item length: " + len);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("IN #{}: {}", id, ByteArrayUtils.printHex(buf.array(), buf.rpos(), len));
|
||||
@@ -371,6 +393,7 @@ public abstract class AbstractChannel
|
||||
in.eof();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void sendEOF()
|
||||
throws TransportException {
|
||||
try {
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** A channel is the basic medium for application-layer data on top of an SSH transport. */
|
||||
public interface Channel
|
||||
@@ -34,13 +35,12 @@ public interface Channel
|
||||
/**
|
||||
* Request opening this channel from remote end.
|
||||
*
|
||||
* @throws OpenFailException in case the channel open request was rejected
|
||||
* @throws net.schmizz.sshj.connection.ConnectionException
|
||||
* other connection-layer error
|
||||
* @throws TransportException error writing packets etc.
|
||||
* @throws OpenFailException in case the channel open request was rejected
|
||||
* @throws ConnectionException other connection-layer error
|
||||
* @throws TransportException error writing packets etc.
|
||||
*/
|
||||
void open()
|
||||
throws OpenFailException, ConnectionException, TransportException;
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ public interface Channel
|
||||
|
||||
|
||||
/** Close this channel. */
|
||||
@Override
|
||||
void close()
|
||||
throws TransportException, ConnectionException;
|
||||
|
||||
@@ -135,4 +136,10 @@ public interface Channel
|
||||
*/
|
||||
void setAutoExpand(boolean autoExpand);
|
||||
|
||||
void join()
|
||||
throws ConnectionException;
|
||||
|
||||
void join(int timeout, TimeUnit unit)
|
||||
throws ConnectionException;
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public final class ChannelInputStream
|
||||
extends InputStream
|
||||
implements ErrorNotifiable {
|
||||
|
||||
private final Logger log;
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Channel chan;
|
||||
private final Transport trans;
|
||||
@@ -71,8 +71,6 @@ public final class ChannelInputStream
|
||||
private SSHException error;
|
||||
|
||||
public ChannelInputStream(Channel chan, Transport trans, Window.Local win) {
|
||||
log = LoggerFactory.getLogger("<< chan#" + chan.getID() + " / input stream >>");
|
||||
|
||||
this.chan = chan;
|
||||
this.trans = trans;
|
||||
this.win = win;
|
||||
@@ -100,6 +98,7 @@ public final class ChannelInputStream
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void notifyError(SSHException error) {
|
||||
this.error = error;
|
||||
eof();
|
||||
@@ -109,7 +108,7 @@ public final class ChannelInputStream
|
||||
public int read()
|
||||
throws IOException {
|
||||
synchronized (b) {
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0];
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0] & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public final class ChannelOutputStream
|
||||
implements ErrorNotifiable {
|
||||
|
||||
private final Channel chan;
|
||||
private Transport trans;
|
||||
private final Transport trans;
|
||||
private final Window.Remote win;
|
||||
private final SSHPacket buffer = new SSHPacket();
|
||||
private final byte[] b = new byte[1];
|
||||
@@ -102,6 +102,7 @@ public final class ChannelOutputStream
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void notifyError(SSHException error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,12 @@ package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class Window {
|
||||
|
||||
protected final Logger log;
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected final Object lock = new Object();
|
||||
|
||||
@@ -31,8 +30,7 @@ public abstract class Window {
|
||||
|
||||
protected int size;
|
||||
|
||||
public Window(int chanID, String kindOfWindow, int initialWinSize, int maxPacketSize) {
|
||||
log = LoggerFactory.getLogger("<< chan#" + chanID + " / " + kindOfWindow + " >>");
|
||||
public Window(int initialWinSize, int maxPacketSize) {
|
||||
size = initialWinSize;
|
||||
this.maxPacketSize = maxPacketSize;
|
||||
}
|
||||
@@ -71,8 +69,8 @@ public abstract class Window {
|
||||
public static final class Remote
|
||||
extends Window {
|
||||
|
||||
public Remote(int chanID, int initialWinSize, int maxPacketSize) {
|
||||
super(chanID, "remote win", initialWinSize, maxPacketSize);
|
||||
public Remote(int initialWinSize, int maxPacketSize) {
|
||||
super(initialWinSize, maxPacketSize);
|
||||
}
|
||||
|
||||
public void waitAndConsume(int howMuch)
|
||||
@@ -99,14 +97,13 @@ public abstract class Window {
|
||||
private final int initialSize;
|
||||
private final int threshold;
|
||||
|
||||
public Local(int chanID, int initialWinSize, int maxPacketSize) {
|
||||
super(chanID, "local win", initialWinSize, maxPacketSize);
|
||||
public Local(int initialWinSize, int maxPacketSize) {
|
||||
super(initialWinSize, maxPacketSize);
|
||||
this.initialSize = initialWinSize;
|
||||
threshold = Math.min(maxPacketSize * 20, initialSize / 4);
|
||||
}
|
||||
|
||||
public int neededAdjustment()
|
||||
throws TransportException {
|
||||
public int neededAdjustment() {
|
||||
synchronized (lock) {
|
||||
return (size - threshold <= 0) ? (initialSize - size) : 0;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public abstract class AbstractDirectChannel
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open()
|
||||
throws ConnectionException, TransportException {
|
||||
trans.write(buildOpenReq());
|
||||
|
||||
@@ -48,31 +48,32 @@ public class LocalPortForwarder {
|
||||
|
||||
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(this,
|
||||
new Closeable() {
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
sock.close();
|
||||
}
|
||||
});
|
||||
|
||||
new StreamCopier("chan2soc", getInputStream(), sock.getOutputStream()) //
|
||||
.bufSize(getLocalMaxPacketSize()) //
|
||||
.errorCallback(closer) //
|
||||
.daemon(true) //
|
||||
new StreamCopier("chan2soc", getInputStream(), sock.getOutputStream())
|
||||
.bufSize(getLocalMaxPacketSize())
|
||||
.errorCallback(closer)
|
||||
.daemon(true)
|
||||
.start();
|
||||
|
||||
new StreamCopier("soc2chan", sock.getInputStream(), getOutputStream()) //
|
||||
.bufSize(getRemoteMaxPacketSize()) //
|
||||
.errorCallback(closer) //
|
||||
.daemon(true) //
|
||||
new StreamCopier("soc2chan", sock.getInputStream(), getOutputStream())
|
||||
.bufSize(getRemoteMaxPacketSize())
|
||||
.errorCallback(closer)
|
||||
.daemon(true)
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SSHPacket buildOpenReq() {
|
||||
return super.buildOpenReq() //
|
||||
.putString(host) //
|
||||
.putInt(port) //
|
||||
.putString(ss.getInetAddress().getHostAddress()) //
|
||||
return super.buildOpenReq()
|
||||
.putString(host)
|
||||
.putInt(port)
|
||||
.putString(ss.getInetAddress().getHostAddress())
|
||||
.putInt(ss.getLocalPort());
|
||||
}
|
||||
|
||||
@@ -85,6 +86,19 @@ public class LocalPortForwarder {
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
/**
|
||||
* Create a local port forwarder with specified binding ({@code listeningAddr}. It does not, however, start
|
||||
* listening unless {@link #listen() explicitly told to}. The {@link javax.net.ServerSocketFactory#getDefault()
|
||||
* default} server socket factory is used.
|
||||
*
|
||||
* @param conn {@link Connection} implementation
|
||||
* @param listeningAddr {@link SocketAddress} this forwarder will listen on, if {@code null} then an ephemeral port
|
||||
* and valid local address will be picked to bind the server socket
|
||||
* @param host what host the SSH server will further forward to
|
||||
* @param port port on {@code toHost}
|
||||
*
|
||||
* @throws IOException if there is an error binding on specified {@code listeningAddr}
|
||||
*/
|
||||
public LocalPortForwarder(Connection conn, SocketAddress listeningAddr, String host, int port)
|
||||
throws IOException {
|
||||
this(ServerSocketFactory.getDefault(), conn, listeningAddr, host, port);
|
||||
@@ -94,6 +108,7 @@ public class LocalPortForwarder {
|
||||
* Create a local port forwarder with specified binding ({@code listeningAddr}. It does not, however, start
|
||||
* listening unless {@link #listen() explicitly told to}.
|
||||
*
|
||||
* @param ssf factory to use for creating the server socket
|
||||
* @param conn {@link Connection} implementation
|
||||
* @param listeningAddr {@link SocketAddress} this forwarder will listen on, if {@code null} then an ephemeral port
|
||||
* and valid local address will be picked to bind the server socket
|
||||
@@ -112,16 +127,21 @@ public class LocalPortForwarder {
|
||||
ss.bind(listeningAddr);
|
||||
}
|
||||
|
||||
/** @return the address to which this forwarder is bound for listening */
|
||||
public SocketAddress getListeningAddress() {
|
||||
return ss.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
/** Start listening for incoming connections and forward to remote host as a channel. */
|
||||
/**
|
||||
* Start listening for incoming connections and forward to remote host as a channel.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void listen()
|
||||
throws IOException {
|
||||
log.info("Listening on {}", ss.getLocalSocketAddress());
|
||||
Socket sock;
|
||||
while (true) {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
sock = ss.accept();
|
||||
log.info("Got connection from {}", sock.getRemoteSocketAddress());
|
||||
DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, sock);
|
||||
|
||||
@@ -35,10 +35,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.IOUtils;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.StreamCopier;
|
||||
import net.schmizz.sshj.common.*;
|
||||
import net.schmizz.sshj.connection.Connection;
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
import net.schmizz.sshj.connection.channel.ChannelInputStream;
|
||||
@@ -46,16 +43,19 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** {@link Session} implementation. */
|
||||
public class
|
||||
SessionChannel
|
||||
/**
|
||||
* {@link Session} implementation.
|
||||
*/
|
||||
public class SessionChannel
|
||||
extends AbstractDirectChannel
|
||||
implements Session, Session.Command, Session.Shell, Session.Subsystem {
|
||||
|
||||
private final ChannelInputStream err = new ChannelInputStream(this, trans, lwin);
|
||||
|
||||
private Integer exitStatus;
|
||||
|
||||
private Signal exitSignal;
|
||||
@@ -64,28 +64,19 @@ public class
|
||||
|
||||
private Boolean canDoFlowControl;
|
||||
|
||||
private ChannelInputStream err = new ChannelInputStream(this, trans, lwin);
|
||||
private boolean usedUp;
|
||||
|
||||
public SessionChannel(Connection conn) {
|
||||
super(conn, "session");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allocateDefaultPTY()
|
||||
throws ConnectionException, TransportException {
|
||||
// TODO FIXME (maybe?): These modes were originally copied from what SSHD was doing;
|
||||
// and then the echo modes were set to 0 to better serve the PTY example.
|
||||
// Not sure what default PTY modes should be.
|
||||
final Map<PTYMode, Integer> modes = new HashMap<PTYMode, Integer>();
|
||||
modes.put(PTYMode.ISIG, 1);
|
||||
modes.put(PTYMode.ICANON, 1);
|
||||
modes.put(PTYMode.ECHO, 0);
|
||||
modes.put(PTYMode.ECHOE, 0);
|
||||
modes.put(PTYMode.ECHOK, 0);
|
||||
modes.put(PTYMode.ECHONL, 0);
|
||||
modes.put(PTYMode.NOFLSH, 0);
|
||||
allocatePTY("vt100", 0, 0, 0, 0, modes);
|
||||
allocatePTY("dummy", 80, 24, 0, 0, Collections.<PTYMode, Integer>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allocatePTY(String term, int cols, int rows, int width, int height, Map<PTYMode, Integer> modes)
|
||||
throws ConnectionException, TransportException {
|
||||
sendChannelRequest(
|
||||
@@ -101,10 +92,12 @@ public class
|
||||
).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean canDoFlowControl() {
|
||||
return canDoFlowControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeWindowDimensions(int cols, int rows, int width, int height)
|
||||
throws TransportException {
|
||||
sendChannelRequest(
|
||||
@@ -118,35 +111,44 @@ public class
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command exec(String command)
|
||||
throws ConnectionException, TransportException {
|
||||
checkReuse();
|
||||
log.info("Will request to exec `{}`", command);
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
usedUp = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorAsString()
|
||||
throws IOException {
|
||||
return StreamCopier.copyStreamToString(err);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getErrorStream() {
|
||||
return err;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExitErrorMessage() {
|
||||
return exitErrMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Signal getExitSignal() {
|
||||
return exitSignal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExitStatus() {
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputAsString()
|
||||
throws IOException {
|
||||
return StreamCopier.copyStreamToString(getInputStream());
|
||||
@@ -168,9 +170,9 @@ public class
|
||||
super.handleRequest(req, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reqX11Forwarding(String authProto, String authCookie, int screen)
|
||||
throws ConnectionException,
|
||||
TransportException {
|
||||
throws ConnectionException, TransportException {
|
||||
sendChannelRequest(
|
||||
"x11-req",
|
||||
true,
|
||||
@@ -182,31 +184,40 @@ public class
|
||||
).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvVar(String name, String value)
|
||||
throws ConnectionException, TransportException {
|
||||
sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signal(Signal sig)
|
||||
throws TransportException {
|
||||
sendChannelRequest("signal", false, new Buffer.PlainBuffer().putString(sig.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shell startShell()
|
||||
throws ConnectionException, TransportException {
|
||||
checkReuse();
|
||||
sendChannelRequest("shell", true, null).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
usedUp = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subsystem startSubsystem(String name)
|
||||
throws ConnectionException, TransportException {
|
||||
checkReuse();
|
||||
log.info("Will request `{}` subsystem", name);
|
||||
sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
usedUp = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getExitWasCoreDumped() {
|
||||
return wasCoreDumped;
|
||||
}
|
||||
@@ -232,4 +243,15 @@ public class
|
||||
super.gotExtendedData(dataTypeCode, buf);
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void notifyError(SSHException error) {
|
||||
err.notifyError(error);
|
||||
super.notifyError(error);
|
||||
}
|
||||
|
||||
private void checkReuse() {
|
||||
if (usedUp)
|
||||
throw new SSHRuntimeException("This session channel is all used up");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,21 +19,20 @@ package net.schmizz.sshj.connection.channel.direct;
|
||||
/** Various signals that may be sent or received. The signals are from POSIX and simply miss the {@code "SIG_"} prefix. */
|
||||
public enum Signal {
|
||||
|
||||
ABRT("ABRT"),
|
||||
ALRM("ALRM"),
|
||||
FPE("FPE"),
|
||||
HUP("HUP"),
|
||||
ILL("ILL"),
|
||||
INT("INT"),
|
||||
KILL("KILL"),
|
||||
PIPE("PIPE"),
|
||||
QUIT(
|
||||
"QUIT"),
|
||||
SEGV("SEGV"),
|
||||
TERM("TERM"),
|
||||
USR1("USR1"),
|
||||
USR2("USR2"),
|
||||
UNKNOWN("UNKNOWN");
|
||||
ABRT,
|
||||
ALRM,
|
||||
FPE,
|
||||
HUP,
|
||||
ILL,
|
||||
INT,
|
||||
KILL,
|
||||
PIPE,
|
||||
QUIT,
|
||||
SEGV,
|
||||
TERM,
|
||||
USR1,
|
||||
USR2,
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* Create from the string representation used when the signal is received as part of an SSH packet.
|
||||
@@ -44,20 +43,9 @@ public enum Signal {
|
||||
*/
|
||||
public static Signal fromString(String name) {
|
||||
for (Signal sig : Signal.values())
|
||||
if (sig.name.equals(name))
|
||||
if (sig.toString().equals(name))
|
||||
return sig;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
private Signal(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public abstract class AbstractForwardedChannel
|
||||
init(recipient, remoteWinSize, remoteMaxPacketSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm()
|
||||
throws TransportException {
|
||||
log.info("Confirming `{}` channel #{}", getType(), getID());
|
||||
@@ -74,16 +75,19 @@ public abstract class AbstractForwardedChannel
|
||||
open.set();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Reason reason, String message)
|
||||
throws TransportException {
|
||||
log.info("Rejecting `{}` channel: {}", getType(), message);
|
||||
conn.sendOpenFailure(getRecipient(), reason, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginatorIP() {
|
||||
return origIP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOriginatorPort() {
|
||||
return origPort;
|
||||
}
|
||||
|
||||
@@ -40,14 +40,12 @@ public abstract class AbstractForwardedChannelOpener
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType() {
|
||||
return chanType;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the listener with the new channel in a separate thread.
|
||||
*/
|
||||
|
||||
/** Calls the listener with the new channel in a separate thread. */
|
||||
protected void callListener(final ConnectListener listener, final Channel.Forwarded chan) {
|
||||
new Thread() {
|
||||
|
||||
|
||||
@@ -128,13 +128,12 @@ public class RemotePortForwarder
|
||||
private final Forward fwd;
|
||||
|
||||
public ForwardedTCPIPChannel(Connection conn, int recipient, int remoteWinSize, int remoteMaxPacketSize,
|
||||
Forward fwd, String origIP, int origPort)
|
||||
throws TransportException {
|
||||
Forward fwd, String origIP, int origPort) {
|
||||
super(conn, TYPE, recipient, remoteWinSize, remoteMaxPacketSize, origIP, origPort);
|
||||
this.fwd = fwd;
|
||||
}
|
||||
|
||||
/** Returns the forwarding from which this channel originates. */
|
||||
/** @return the forwarding from which this channel originates. */
|
||||
public Forward getParentForward() {
|
||||
return fwd;
|
||||
}
|
||||
@@ -209,6 +208,7 @@ public class RemotePortForwarder
|
||||
* Internal API. Creates a {@link ForwardedTCPIPChannel} from the {@code CHANNEL_OPEN} request and calls associated
|
||||
* {@code ConnectListener} for that forward in a separate thread.
|
||||
*/
|
||||
@Override
|
||||
public void handleOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final ForwardedTCPIPChannel chan = new ForwardedTCPIPChannel(conn, buf.readInt(), buf.readInt(), buf.readInt(),
|
||||
|
||||
@@ -40,6 +40,7 @@ public class SocketForwardingConnectListener
|
||||
}
|
||||
|
||||
/** On connect, confirm the channel and start forwarding. */
|
||||
@Override
|
||||
public void gotConnect(Channel.Forwarded chan)
|
||||
throws IOException {
|
||||
log.info("New connection from " + chan.getOriginatorIP() + ":" + chan.getOriginatorPort());
|
||||
@@ -54,6 +55,7 @@ public class SocketForwardingConnectListener
|
||||
chan.confirm();
|
||||
|
||||
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(chan, new Closeable() {
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
sock.close();
|
||||
|
||||
@@ -52,10 +52,13 @@ public class X11Forwarder
|
||||
}
|
||||
|
||||
/** Internal API */
|
||||
@Override
|
||||
public void handleOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
callListener(listener, new X11Channel(conn, buf.readInt(), buf.readInt(), buf.readInt(), buf.readString(), buf
|
||||
.readInt()));
|
||||
callListener(listener, new X11Channel(conn,
|
||||
buf.readInt(),
|
||||
buf.readInt(), buf.readInt(),
|
||||
buf.readString(), buf.readInt()));
|
||||
}
|
||||
|
||||
/** Stop handling {@code x11} channel open requests. De-registers itself with connection layer. */
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.Set;
|
||||
|
||||
public final class FileAttributes {
|
||||
|
||||
public static final FileAttributes EMPTY = new FileAttributes();
|
||||
|
||||
public static enum Flag {
|
||||
|
||||
SIZE(0x00000001),
|
||||
@@ -58,7 +60,7 @@ public final class FileAttributes {
|
||||
private final long mtime;
|
||||
private final Map<String, String> ext = new HashMap<String, String>();
|
||||
|
||||
public FileAttributes() {
|
||||
private FileAttributes() {
|
||||
size = atime = mtime = uid = gid = mask = 0;
|
||||
mode = new FileMode(0);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,11 @@ public class PacketReader
|
||||
private final Map<Long, Future<Response, SFTPException>> futures = new ConcurrentHashMap<Long, Future<Response, SFTPException>>();
|
||||
private final SFTPPacket<Response> packet = new SFTPPacket<Response>();
|
||||
private final byte[] lenBuf = new byte[4];
|
||||
private final SFTPEngine engine;
|
||||
|
||||
public PacketReader(InputStream in) {
|
||||
this.in = in;
|
||||
public PacketReader(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
this.in = engine.getSubsystem().getInputStream();
|
||||
setName("sftp reader");
|
||||
}
|
||||
|
||||
@@ -78,7 +80,7 @@ public class PacketReader
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
while (!isInterrupted()) {
|
||||
readPacket();
|
||||
handle();
|
||||
}
|
||||
@@ -90,7 +92,7 @@ public class PacketReader
|
||||
|
||||
public void handle()
|
||||
throws SFTPException {
|
||||
Response resp = new Response(packet);
|
||||
Response resp = new Response(packet, engine.getOperativeProtocolVersion());
|
||||
Future<Response, SFTPException> future = futures.remove(resp.getRequestID());
|
||||
log.debug("Received {} packet", resp.getType());
|
||||
if (future == null)
|
||||
|
||||
@@ -52,8 +52,7 @@ public enum PacketType {
|
||||
|
||||
static {
|
||||
for (PacketType t : PacketType.values())
|
||||
if (cache[t.toByte() & 0xff] == null)
|
||||
cache[t.toByte() & 0xff] = t;
|
||||
cache[t.toByte() & 0xff] = t;
|
||||
}
|
||||
|
||||
private PacketType(int b) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
class PathComponents {
|
||||
public class PathComponents {
|
||||
|
||||
public static String adjustForParent(String parent, String path) {
|
||||
return (path.startsWith("/")) ? path // Absolute path, nothing to adjust
|
||||
|
||||
@@ -17,13 +17,13 @@ package net.schmizz.sshj.sftp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class PathHelper {
|
||||
public class PathHelper {
|
||||
|
||||
private final SFTPEngine sftp;
|
||||
private final SFTPEngine engine;
|
||||
private String dotDir;
|
||||
|
||||
public PathHelper(SFTPEngine sftp) {
|
||||
this.sftp = sftp;
|
||||
public PathHelper(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
public PathComponents getComponents(String path)
|
||||
@@ -49,14 +49,14 @@ class PathHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private String getDotDir()
|
||||
private synchronized String getDotDir()
|
||||
throws IOException {
|
||||
return (dotDir != null) ? dotDir : (dotDir = canon("."));
|
||||
}
|
||||
|
||||
private String canon(String path)
|
||||
throws IOException {
|
||||
return sftp.canonicalize(path);
|
||||
return engine.canonicalize(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class RandomAccessRemoteFile
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean()
|
||||
throws IOException {
|
||||
final int ch = read();
|
||||
@@ -69,6 +70,7 @@ public class RandomAccessRemoteFile
|
||||
return (ch != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte()
|
||||
throws IOException {
|
||||
final int ch = this.read();
|
||||
@@ -77,6 +79,7 @@ public class RandomAccessRemoteFile
|
||||
return (byte) (ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar()
|
||||
throws IOException {
|
||||
final int ch1 = this.read();
|
||||
@@ -86,21 +89,25 @@ public class RandomAccessRemoteFile
|
||||
return (char) ((ch1 << 8) + ch2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble()
|
||||
throws IOException {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat()
|
||||
throws IOException {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b)
|
||||
throws IOException {
|
||||
readFully(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b, int off, int len)
|
||||
throws IOException {
|
||||
int n = 0;
|
||||
@@ -112,6 +119,7 @@ public class RandomAccessRemoteFile
|
||||
} while (n < len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt()
|
||||
throws IOException {
|
||||
final int ch1 = read();
|
||||
@@ -123,6 +131,7 @@ public class RandomAccessRemoteFile
|
||||
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine()
|
||||
throws IOException {
|
||||
StringBuffer input = new StringBuffer();
|
||||
@@ -151,11 +160,13 @@ public class RandomAccessRemoteFile
|
||||
return input.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong()
|
||||
throws IOException {
|
||||
return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort()
|
||||
throws IOException {
|
||||
final int ch1 = this.read();
|
||||
@@ -165,11 +176,13 @@ public class RandomAccessRemoteFile
|
||||
return (short) ((ch1 << 8) + ch2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF()
|
||||
throws IOException {
|
||||
return DataInputStream.readUTF(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte()
|
||||
throws IOException {
|
||||
final int ch = this.read();
|
||||
@@ -178,6 +191,7 @@ public class RandomAccessRemoteFile
|
||||
return ch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort()
|
||||
throws IOException {
|
||||
final int ch1 = this.read();
|
||||
@@ -187,6 +201,7 @@ public class RandomAccessRemoteFile
|
||||
return (ch1 << 8) + ch2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n)
|
||||
throws IOException {
|
||||
if (n <= 0)
|
||||
@@ -202,45 +217,53 @@ public class RandomAccessRemoteFile
|
||||
return (int) (newpos - pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int i)
|
||||
throws IOException {
|
||||
singleByte[0] = (byte) i;
|
||||
write(singleByte);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b)
|
||||
throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len)
|
||||
throws IOException {
|
||||
rf.write(fp, b, off, len);
|
||||
fp += (len - off);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean v)
|
||||
throws IOException {
|
||||
write(v ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int v)
|
||||
throws IOException {
|
||||
write(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(String s)
|
||||
throws IOException {
|
||||
final byte[] b = s.getBytes();
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChar(int v)
|
||||
throws IOException {
|
||||
write((v >>> 8) & 0xFF);
|
||||
write(v & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChars(String s)
|
||||
throws IOException {
|
||||
final int clen = s.length();
|
||||
@@ -255,16 +278,19 @@ public class RandomAccessRemoteFile
|
||||
write(b, 0, blen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double v)
|
||||
throws IOException {
|
||||
writeLong(Double.doubleToLongBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float v)
|
||||
throws IOException {
|
||||
writeInt(Float.floatToIntBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int v)
|
||||
throws IOException {
|
||||
write((v >>> 24) & 0xFF);
|
||||
@@ -273,6 +299,7 @@ public class RandomAccessRemoteFile
|
||||
write(v & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long v)
|
||||
throws IOException {
|
||||
write((int) (v >>> 56) & 0xFF);
|
||||
@@ -285,12 +312,14 @@ public class RandomAccessRemoteFile
|
||||
write((int) v & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int v)
|
||||
throws IOException {
|
||||
write((v >>> 8) & 0xFF);
|
||||
write(v & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(String str)
|
||||
throws IOException {
|
||||
final DataOutputStream dos = new DataOutputStream(rf.new RemoteFileOutputStream(fp));
|
||||
|
||||
@@ -46,7 +46,7 @@ public class RemoteDirectory
|
||||
if (!(name.equals(".") || name.equals("..")) && (filter == null || filter.accept(inf)))
|
||||
rri.add(inf);
|
||||
}
|
||||
break loop;
|
||||
break;
|
||||
|
||||
case STATUS:
|
||||
res.ensureStatusIs(StatusCode.EOF);
|
||||
|
||||
@@ -38,8 +38,8 @@ public class RemoteFile
|
||||
|
||||
public FileAttributes fetchAttributes()
|
||||
throws IOException {
|
||||
return requester.doRequest(newRequest(PacketType.FSTAT)) //
|
||||
.ensurePacketTypeIs(PacketType.ATTRS) //
|
||||
return requester.doRequest(newRequest(PacketType.FSTAT))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
@@ -73,11 +73,10 @@ public class RemoteFile
|
||||
|
||||
public void write(long fileOffset, byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
requester.doRequest( //
|
||||
newRequest(PacketType.WRITE) //
|
||||
.putUINT64(fileOffset) //
|
||||
.putInt(len - off) //
|
||||
.putRawBytes(data, off, len) //
|
||||
requester.doRequest(newRequest(PacketType.WRITE)
|
||||
.putUINT64(fileOffset)
|
||||
.putInt(len - off)
|
||||
.putRawBytes(data, off, len)
|
||||
).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
@@ -171,7 +170,7 @@ public class RemoteFile
|
||||
@Override
|
||||
public int read()
|
||||
throws IOException {
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0];
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
abstract class RemoteResource
|
||||
public abstract class RemoteResource
|
||||
implements Closeable {
|
||||
|
||||
/** Logger */
|
||||
@@ -45,6 +45,7 @@ abstract class RemoteResource
|
||||
return requester.newRequest(type).putString(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
log.info("Closing `{}`", this);
|
||||
|
||||
@@ -55,12 +55,7 @@ public class RemoteResourceInfo {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof RemoteResourceInfo) {
|
||||
final RemoteResourceInfo that = (RemoteResourceInfo) o;
|
||||
if (comps.equals(that.comps))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return o instanceof RemoteResourceInfo && (comps.equals(((RemoteResourceInfo) o).comps));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,11 +25,10 @@ public class Request
|
||||
private final Future<Response, SFTPException> responseFuture;
|
||||
|
||||
public Request(PacketType type, long reqID) {
|
||||
super();
|
||||
this.reqID = reqID;
|
||||
super(type);
|
||||
this.type = type;
|
||||
this.reqID = reqID;
|
||||
responseFuture = new Future<Response, SFTPException>("sftp / " + reqID, SFTPException.chainer);
|
||||
putByte(type.toByte());
|
||||
putInt(reqID);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,15 +47,21 @@ public class Response
|
||||
|
||||
}
|
||||
|
||||
private final int protocolVersion;
|
||||
private final PacketType type;
|
||||
private final long reqID;
|
||||
|
||||
public Response(Buffer<Response> pk) {
|
||||
public Response(Buffer<Response> pk, int protocolVersion) {
|
||||
super(pk);
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.type = readType();
|
||||
this.reqID = readLong();
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public long getRequestID() {
|
||||
return reqID;
|
||||
}
|
||||
@@ -72,7 +78,7 @@ public class Response
|
||||
throws SFTPException {
|
||||
if (getType() != pt)
|
||||
if (getType() == PacketType.STATUS)
|
||||
throw new SFTPException(readStatusCode(), readString());
|
||||
error(readStatusCode());
|
||||
else
|
||||
throw new SFTPException("Unexpected packet " + getType());
|
||||
return this;
|
||||
@@ -85,10 +91,15 @@ public class Response
|
||||
|
||||
public Response ensureStatusIs(StatusCode acceptable)
|
||||
throws SFTPException {
|
||||
StatusCode sc = readStatusCode();
|
||||
final StatusCode sc = readStatusCode();
|
||||
if (sc != acceptable)
|
||||
throw new SFTPException(sc, readString());
|
||||
error(sc);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected String error(StatusCode sc)
|
||||
throws SFTPException {
|
||||
throw new SFTPException(sc, protocolVersion < 3 ? sc.toString() : readString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,32 +15,36 @@
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
import net.schmizz.sshj.xfer.FilePermission;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class SFTPClient {
|
||||
public class SFTPClient
|
||||
implements Closeable {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final SFTPEngine sftp;
|
||||
private final SFTPEngine engine;
|
||||
private final SFTPFileTransfer xfer;
|
||||
private final PathHelper pathHelper;
|
||||
|
||||
public SFTPClient(SessionFactory ssh)
|
||||
throws IOException {
|
||||
this.sftp = new SFTPEngine(ssh).init();
|
||||
this.xfer = new SFTPFileTransfer(sftp);
|
||||
public SFTPClient(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
this.pathHelper = new PathHelper(engine);
|
||||
this.xfer = new SFTPFileTransfer(engine);
|
||||
}
|
||||
|
||||
public SFTPEngine getSFTPEngine() {
|
||||
return sftp;
|
||||
return engine;
|
||||
}
|
||||
|
||||
public SFTPFileTransfer getFileTansfer() {
|
||||
@@ -54,7 +58,7 @@ public class SFTPClient {
|
||||
|
||||
public List<RemoteResourceInfo> ls(String path, RemoteResourceFilter filter)
|
||||
throws IOException {
|
||||
final RemoteDirectory dir = sftp.openDir(path);
|
||||
final RemoteDirectory dir = engine.openDir(path);
|
||||
try {
|
||||
return dir.scan(filter);
|
||||
} finally {
|
||||
@@ -65,12 +69,12 @@ public class SFTPClient {
|
||||
public RemoteFile open(String filename, Set<OpenMode> mode, FileAttributes attrs)
|
||||
throws IOException {
|
||||
log.debug("Opening `{}`", filename);
|
||||
return sftp.open(filename, mode, attrs);
|
||||
return engine.open(filename, mode, attrs);
|
||||
}
|
||||
|
||||
public RemoteFile open(String filename, Set<OpenMode> mode)
|
||||
throws IOException {
|
||||
return open(filename, mode, new FileAttributes());
|
||||
return open(filename, mode, FileAttributes.EMPTY);
|
||||
}
|
||||
|
||||
public RemoteFile open(String filename)
|
||||
@@ -80,36 +84,68 @@ public class SFTPClient {
|
||||
|
||||
public void mkdir(String dirname)
|
||||
throws IOException {
|
||||
sftp.makeDir(dirname);
|
||||
engine.makeDir(dirname);
|
||||
}
|
||||
|
||||
public void mkdirs(String path)
|
||||
throws IOException {
|
||||
final Deque<String> dirsToMake = new LinkedList<String>();
|
||||
for (PathComponents current = pathHelper.getComponents(path); ; current = pathHelper
|
||||
.getComponents(current.getParent())) {
|
||||
final FileAttributes attrs = statExistence(current.getPath());
|
||||
if (attrs == null) {
|
||||
dirsToMake.push(current.getPath());
|
||||
} else if (attrs.getType() != FileMode.Type.DIRECTORY) {
|
||||
throw new SFTPException(current.getPath() + " exists but is not a directory");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!dirsToMake.isEmpty()) {
|
||||
mkdir(dirsToMake.pop());
|
||||
}
|
||||
}
|
||||
|
||||
public FileAttributes statExistence(String path)
|
||||
throws IOException {
|
||||
try {
|
||||
return engine.stat(path);
|
||||
} catch (SFTPException sftpe) {
|
||||
if (sftpe.getStatusCode() == Response.StatusCode.NO_SUCH_FILE) {
|
||||
return null;
|
||||
} else {
|
||||
throw sftpe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void rename(String oldpath, String newpath)
|
||||
throws IOException {
|
||||
sftp.rename(oldpath, newpath);
|
||||
engine.rename(oldpath, newpath);
|
||||
}
|
||||
|
||||
public void rm(String filename)
|
||||
throws IOException {
|
||||
sftp.remove(filename);
|
||||
engine.remove(filename);
|
||||
}
|
||||
|
||||
public void rmdir(String dirname)
|
||||
throws IOException {
|
||||
sftp.removeDir(dirname);
|
||||
engine.removeDir(dirname);
|
||||
}
|
||||
|
||||
public void symlink(String linkpath, String targetpath)
|
||||
throws IOException {
|
||||
sftp.symlink(linkpath, targetpath);
|
||||
engine.symlink(linkpath, targetpath);
|
||||
}
|
||||
|
||||
public int version() {
|
||||
return sftp.getOperativeProtocolVersion();
|
||||
return engine.getOperativeProtocolVersion();
|
||||
}
|
||||
|
||||
public void setattr(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
sftp.setAttributes(path, attrs);
|
||||
engine.setAttributes(path, attrs);
|
||||
}
|
||||
|
||||
public int uid(String path)
|
||||
@@ -149,17 +185,17 @@ public class SFTPClient {
|
||||
|
||||
public String readlink(String path)
|
||||
throws IOException {
|
||||
return sftp.readLink(path);
|
||||
return engine.readLink(path);
|
||||
}
|
||||
|
||||
public FileAttributes stat(String path)
|
||||
throws IOException {
|
||||
return sftp.stat(path);
|
||||
return engine.stat(path);
|
||||
}
|
||||
|
||||
public FileAttributes lstat(String path)
|
||||
throws IOException {
|
||||
return sftp.lstat(path);
|
||||
return engine.lstat(path);
|
||||
}
|
||||
|
||||
public void chown(String path, int uid)
|
||||
@@ -184,7 +220,7 @@ public class SFTPClient {
|
||||
|
||||
public String canonicalize(String path)
|
||||
throws IOException {
|
||||
return sftp.canonicalize(path);
|
||||
return engine.canonicalize(path);
|
||||
}
|
||||
|
||||
public long size(String path)
|
||||
@@ -202,4 +238,10 @@ public class SFTPClient {
|
||||
xfer.upload(source, dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
engine.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.EnumSet;
|
||||
@@ -30,39 +31,34 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SFTPEngine
|
||||
implements Requester {
|
||||
|
||||
/** Logger */
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public static final int PROTOCOL_VERSION = 3;
|
||||
implements Requester, Closeable {
|
||||
|
||||
public static final int MAX_SUPPORTED_VERSION = 3;
|
||||
public static final int DEFAULT_TIMEOUT = 30;
|
||||
|
||||
private volatile int timeout = DEFAULT_TIMEOUT;
|
||||
/** Logger */
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Subsystem sub;
|
||||
private final PacketReader reader;
|
||||
private final OutputStream out;
|
||||
protected volatile int timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
private long reqID;
|
||||
private int negotiatedVersion;
|
||||
private final Map<String, String> serverExtensions = new HashMap<String, String>();
|
||||
protected final Subsystem sub;
|
||||
protected final PacketReader reader;
|
||||
protected final OutputStream out;
|
||||
|
||||
protected long reqID;
|
||||
protected int operativeVersion;
|
||||
protected final Map<String, String> serverExtensions = new HashMap<String, String>();
|
||||
|
||||
public SFTPEngine(SessionFactory ssh)
|
||||
throws SSHException {
|
||||
sub = ssh.startSession().startSubsystem("sftp");
|
||||
out = sub.getOutputStream();
|
||||
reader = new PacketReader(sub.getInputStream());
|
||||
}
|
||||
|
||||
public Subsystem getSubsystem() {
|
||||
return sub;
|
||||
reader = new PacketReader(this);
|
||||
}
|
||||
|
||||
public SFTPEngine init()
|
||||
throws IOException {
|
||||
transmit(new SFTPPacket<Request>(PacketType.INIT).putInt(PROTOCOL_VERSION));
|
||||
transmit(new SFTPPacket<Request>(PacketType.INIT).putInt(MAX_SUPPORTED_VERSION));
|
||||
|
||||
final SFTPPacket<Response> response = reader.readPacket();
|
||||
|
||||
@@ -70,10 +66,10 @@ public class SFTPEngine
|
||||
if (type != PacketType.VERSION)
|
||||
throw new SFTPException("Expected INIT packet, received: " + type);
|
||||
|
||||
negotiatedVersion = response.readInt();
|
||||
log.info("Client version {}, server version {}", PROTOCOL_VERSION, negotiatedVersion);
|
||||
if (negotiatedVersion < PROTOCOL_VERSION)
|
||||
throw new SFTPException("Server reported protocol version: " + negotiatedVersion);
|
||||
operativeVersion = response.readInt();
|
||||
log.info("Server version {}", operativeVersion);
|
||||
if (MAX_SUPPORTED_VERSION < operativeVersion)
|
||||
throw new SFTPException("Server reported incompatible protocol version: " + operativeVersion);
|
||||
|
||||
while (response.available() > 0)
|
||||
serverExtensions.put(response.readString(), response.readString());
|
||||
@@ -83,14 +79,24 @@ public class SFTPEngine
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOperativeProtocolVersion() {
|
||||
return negotiatedVersion;
|
||||
public Subsystem getSubsystem() {
|
||||
return sub;
|
||||
}
|
||||
|
||||
public int getOperativeProtocolVersion() {
|
||||
return operativeVersion;
|
||||
}
|
||||
|
||||
public Request newExtendedRequest(String reqName) {
|
||||
return newRequest(PacketType.EXTENDED).putString(reqName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Request newRequest(PacketType type) {
|
||||
return new Request(type, reqID = reqID + 1 & 0xffffffffL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response doRequest(Request req)
|
||||
throws IOException {
|
||||
reader.expectResponseTo(req);
|
||||
@@ -99,20 +105,8 @@ public class SFTPEngine
|
||||
return req.getResponseFuture().get(timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private synchronized void transmit(SFTPPacket<Request> payload)
|
||||
throws IOException {
|
||||
final int len = payload.available();
|
||||
out.write((len >>> 24) & 0xff);
|
||||
out.write((len >>> 16) & 0xff);
|
||||
out.write((len >>> 8) & 0xff);
|
||||
out.write(len & 0xff);
|
||||
out.write(payload.array(), payload.rpos(), len);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa)
|
||||
throws IOException {
|
||||
|
||||
final String handle = doRequest(
|
||||
newRequest(PacketType.OPEN).putString(path).putInt(OpenMode.toMask(modes)).putFileAttributes(fa)
|
||||
).ensurePacketTypeIs(PacketType.HANDLE).readString();
|
||||
@@ -121,7 +115,7 @@ public class SFTPEngine
|
||||
|
||||
public RemoteFile open(String filename, Set<OpenMode> modes)
|
||||
throws IOException {
|
||||
return open(filename, modes, new FileAttributes());
|
||||
return open(filename, modes, FileAttributes.EMPTY);
|
||||
}
|
||||
|
||||
public RemoteFile open(String filename)
|
||||
@@ -146,6 +140,8 @@ public class SFTPEngine
|
||||
|
||||
public String readLink(String path)
|
||||
throws IOException {
|
||||
if (operativeVersion < 3)
|
||||
throw new SFTPException("READLINK is not supported in SFTPv" + operativeVersion);
|
||||
return readSingleName(
|
||||
doRequest(
|
||||
newRequest(PacketType.READLINK).putString(path)
|
||||
@@ -154,18 +150,18 @@ public class SFTPEngine
|
||||
|
||||
public void makeDir(String path, FileAttributes attrs)
|
||||
throws IOException {
|
||||
doRequest(
|
||||
newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)
|
||||
).ensureStatusPacketIsOK();
|
||||
doRequest(newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)).ensureStatusPacketIsOK();
|
||||
}
|
||||
|
||||
public void makeDir(String path)
|
||||
throws IOException {
|
||||
makeDir(path, new FileAttributes());
|
||||
makeDir(path, FileAttributes.EMPTY);
|
||||
}
|
||||
|
||||
public void symlink(String linkpath, String targetpath)
|
||||
throws IOException {
|
||||
if (operativeVersion < 3)
|
||||
throw new SFTPException("SYMLINK is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath)
|
||||
).ensureStatusPacketIsOK();
|
||||
@@ -185,13 +181,6 @@ public class SFTPEngine
|
||||
).ensureStatusIs(Response.StatusCode.OK);
|
||||
}
|
||||
|
||||
private FileAttributes stat(PacketType pt, String path)
|
||||
throws IOException {
|
||||
return doRequest(newRequest(pt).putString(path))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
public FileAttributes stat(String path)
|
||||
throws IOException {
|
||||
return stat(PacketType.STAT, path);
|
||||
@@ -204,6 +193,8 @@ public class SFTPEngine
|
||||
|
||||
public void rename(String oldPath, String newPath)
|
||||
throws IOException {
|
||||
if (operativeVersion < 1)
|
||||
throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion);
|
||||
doRequest(
|
||||
newRequest(PacketType.RENAME).putString(oldPath).putString(newPath)
|
||||
).ensureStatusPacketIsOK();
|
||||
@@ -217,15 +208,6 @@ public class SFTPEngine
|
||||
));
|
||||
}
|
||||
|
||||
private static String readSingleName(Response res)
|
||||
throws IOException {
|
||||
res.ensurePacketTypeIs(PacketType.NAME);
|
||||
if (res.readInt() == 1)
|
||||
return res.readString();
|
||||
else
|
||||
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
@@ -234,4 +216,37 @@ public class SFTPEngine
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws IOException {
|
||||
sub.close();
|
||||
}
|
||||
|
||||
protected FileAttributes stat(PacketType pt, String path)
|
||||
throws IOException {
|
||||
return doRequest(newRequest(pt).putString(path))
|
||||
.ensurePacketTypeIs(PacketType.ATTRS)
|
||||
.readFileAttributes();
|
||||
}
|
||||
|
||||
protected static String readSingleName(Response res)
|
||||
throws IOException {
|
||||
res.ensurePacketTypeIs(PacketType.NAME);
|
||||
if (res.readInt() == 1)
|
||||
return res.readString();
|
||||
else
|
||||
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
|
||||
}
|
||||
|
||||
protected synchronized void transmit(SFTPPacket<Request> payload)
|
||||
throws IOException {
|
||||
final int len = payload.available();
|
||||
out.write((len >>> 24) & 0xff);
|
||||
out.write((len >>> 16) & 0xff);
|
||||
out.write((len >>> 8) & 0xff);
|
||||
out.write(len & 0xff);
|
||||
out.write(payload.array(), payload.rpos(), len);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class SFTPException
|
||||
|
||||
public static final ExceptionChainer<SFTPException> chainer = new ExceptionChainer<SFTPException>() {
|
||||
|
||||
@Override
|
||||
public SFTPException chain(Throwable t) {
|
||||
if (t instanceof SFTPException)
|
||||
return (SFTPException) t;
|
||||
@@ -66,7 +67,6 @@ public class SFTPException
|
||||
|
||||
public StatusCode getStatusCode() {
|
||||
return (sc == null) ? StatusCode.UNKNOWN : sc;
|
||||
|
||||
}
|
||||
|
||||
public SFTPException(StatusCode sc, String msg) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import net.schmizz.sshj.sftp.Response.StatusCode;
|
||||
import net.schmizz.sshj.xfer.AbstractFileTransfer;
|
||||
import net.schmizz.sshj.xfer.FileTransfer;
|
||||
import net.schmizz.sshj.xfer.FileTransferUtil;
|
||||
import net.schmizz.sshj.xfer.TransferListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
@@ -32,38 +33,42 @@ public class SFTPFileTransfer
|
||||
extends AbstractFileTransfer
|
||||
implements FileTransfer {
|
||||
|
||||
private final SFTPEngine sftp;
|
||||
private final SFTPEngine engine;
|
||||
private final PathHelper pathHelper;
|
||||
|
||||
private volatile FileFilter uploadFilter = defaultLocalFilter;
|
||||
private volatile RemoteResourceFilter downloadFilter = defaultRemoteFilter;
|
||||
|
||||
private static final FileFilter defaultLocalFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathName) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private static final RemoteResourceFilter defaultRemoteFilter = new RemoteResourceFilter() {
|
||||
@Override
|
||||
public boolean accept(RemoteResourceInfo resource) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public SFTPFileTransfer(SFTPEngine sftp) {
|
||||
this.sftp = sftp;
|
||||
this.pathHelper = new PathHelper(sftp);
|
||||
public SFTPFileTransfer(SFTPEngine engine) {
|
||||
this.engine = engine;
|
||||
this.pathHelper = new PathHelper(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(String source, String dest)
|
||||
throws IOException {
|
||||
new Uploader().upload(new File(source), dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(String source, String dest)
|
||||
throws IOException {
|
||||
final PathComponents pathComponents = pathHelper.getComponents(source);
|
||||
final FileAttributes attributes = sftp.stat(source);
|
||||
final FileAttributes attributes = engine.stat(source);
|
||||
new Downloader().download(new RemoteResourceInfo(pathComponents, attributes), new File(dest));
|
||||
}
|
||||
|
||||
@@ -85,7 +90,64 @@ public class SFTPFileTransfer
|
||||
|
||||
private class Downloader {
|
||||
|
||||
private void setAttributes(final RemoteResourceInfo remote, final File local)
|
||||
private final TransferListener listener = getTransferListener();
|
||||
|
||||
private void download(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final File adjustedFile;
|
||||
switch (remote.getAttributes().getType()) {
|
||||
case DIRECTORY:
|
||||
listener.startedDir(remote.getName());
|
||||
adjustedFile = downloadDir(remote, local);
|
||||
listener.finishedDir();
|
||||
break;
|
||||
case UNKNOWN:
|
||||
log.warn("Server did not supply information about the type of file at `{}` " +
|
||||
"-- assuming it is a regular file!", remote.getPath());
|
||||
case REGULAR:
|
||||
listener.startedFile(remote.getName(), remote.getAttributes().getSize());
|
||||
adjustedFile = downloadFile(remote, local);
|
||||
listener.finishedFile();
|
||||
break;
|
||||
default:
|
||||
throw new IOException(remote + " is not a regular file or directory");
|
||||
}
|
||||
copyAttributes(remote, adjustedFile);
|
||||
|
||||
}
|
||||
|
||||
private File downloadDir(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final File adjusted = FileTransferUtil.getTargetDirectory(local, remote.getName());
|
||||
final RemoteDirectory rd = engine.openDir(remote.getPath());
|
||||
try {
|
||||
for (RemoteResourceInfo rri : rd.scan(getDownloadFilter()))
|
||||
download(rri, new File(adjusted.getPath(), rri.getName()));
|
||||
} finally {
|
||||
rd.close();
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
private File downloadFile(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final File adjusted = FileTransferUtil.getTargetFile(local, remote.getName());
|
||||
final RemoteFile rf = engine.open(remote.getPath());
|
||||
try {
|
||||
final FileOutputStream fos = new FileOutputStream(adjusted);
|
||||
try {
|
||||
StreamCopier.copy(rf.getInputStream(), fos, engine.getSubsystem()
|
||||
.getLocalMaxPacketSize(), false, listener);
|
||||
} finally {
|
||||
fos.close();
|
||||
}
|
||||
} finally {
|
||||
rf.close();
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
private void copyAttributes(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final FileAttributes attrs = remote.getAttributes();
|
||||
getModeSetter().setPermissions(local, attrs.getMode().getPermissionsMask());
|
||||
@@ -95,102 +157,65 @@ public class SFTPFileTransfer
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFile(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final File adjusted = FileTransferUtil.getTargetFile(local, remote.getName());
|
||||
setAttributes(remote, adjusted);
|
||||
final RemoteFile rf = sftp.open(remote.getPath());
|
||||
try {
|
||||
final FileOutputStream fos = new FileOutputStream(adjusted);
|
||||
try {
|
||||
StreamCopier.copy(rf.getInputStream(), fos, sftp.getSubsystem()
|
||||
.getLocalMaxPacketSize(), false);
|
||||
} finally {
|
||||
fos.close();
|
||||
}
|
||||
} finally {
|
||||
rf.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadDir(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
final File adjusted = FileTransferUtil.getTargetDirectory(local, remote.getName());
|
||||
setAttributes(remote, adjusted);
|
||||
final RemoteDirectory rd = sftp.openDir(remote.getPath());
|
||||
try {
|
||||
for (RemoteResourceInfo rri : rd.scan(getDownloadFilter()))
|
||||
download(rri, new File(adjusted.getPath(), rri.getName()));
|
||||
} finally {
|
||||
rd.close();
|
||||
}
|
||||
}
|
||||
|
||||
void download(final RemoteResourceInfo remote, final File local)
|
||||
throws IOException {
|
||||
log.info("Downloading [{}] to [{}]", remote, local);
|
||||
switch (remote.getAttributes().getType()) {
|
||||
case DIRECTORY:
|
||||
downloadDir(remote, local);
|
||||
break;
|
||||
case UNKNOWN:
|
||||
log.warn("Server did not supply information about the type of file at `{}` -- assuming it is a regular file!");
|
||||
case REGULAR:
|
||||
downloadFile(remote, local);
|
||||
break;
|
||||
default:
|
||||
throw new IOException(remote + " is not a regular file or directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Uploader {
|
||||
|
||||
void upload(File local, String remote)
|
||||
private final TransferListener listener = getTransferListener();
|
||||
|
||||
private void upload(File local, String remote)
|
||||
throws IOException {
|
||||
log.info("Uploading [{}] to [{}]", local, remote);
|
||||
if (local.isDirectory())
|
||||
uploadDir(local, remote);
|
||||
else if (local.isFile())
|
||||
uploadFile(local, remote);
|
||||
else
|
||||
final String adjustedPath;
|
||||
if (local.isDirectory()) {
|
||||
listener.startedDir(local.getName());
|
||||
adjustedPath = uploadDir(local, remote);
|
||||
listener.finishedDir();
|
||||
} else if (local.isFile()) {
|
||||
listener.startedFile(local.getName(), local.length());
|
||||
adjustedPath = uploadFile(local, remote);
|
||||
listener.finishedFile();
|
||||
} else
|
||||
throw new IOException(local + " is not a file or directory");
|
||||
engine.setAttributes(adjustedPath, getAttributes(local));
|
||||
}
|
||||
|
||||
private void uploadDir(File local, String remote)
|
||||
private String uploadDir(File local, String remote)
|
||||
throws IOException {
|
||||
final String adjusted = prepareDir(local, remote);
|
||||
for (File f : local.listFiles(getUploadFilter()))
|
||||
upload(f, adjusted);
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
private void uploadFile(File local, String remote)
|
||||
private String uploadFile(File local, String remote)
|
||||
throws IOException {
|
||||
final String adjusted = prepareFile(local, remote);
|
||||
final RemoteFile rf = sftp.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC),
|
||||
getAttributes(local));
|
||||
final RemoteFile rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE,
|
||||
OpenMode.CREAT,
|
||||
OpenMode.TRUNC));
|
||||
try {
|
||||
final FileInputStream fis = new FileInputStream(local);
|
||||
try {
|
||||
StreamCopier.copy(fis, rf.getOutputStream(), sftp.getSubsystem().getRemoteMaxPacketSize()
|
||||
- rf.getOutgoingPacketOverhead(), false);
|
||||
final int bufSize = engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead();
|
||||
StreamCopier.copy(fis, rf.getOutputStream(), bufSize, false, listener);
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
} finally {
|
||||
rf.close();
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
private String prepareDir(File local, String remote)
|
||||
throws IOException {
|
||||
final FileAttributes attrs;
|
||||
try {
|
||||
attrs = sftp.stat(remote);
|
||||
attrs = engine.stat(remote);
|
||||
} catch (SFTPException e) {
|
||||
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
|
||||
log.debug("probeDir: {} does not exist, creating", remote);
|
||||
sftp.makeDir(remote, getAttributes(local));
|
||||
engine.makeDir(remote);
|
||||
return remote;
|
||||
} else
|
||||
throw e;
|
||||
@@ -199,11 +224,6 @@ public class SFTPFileTransfer
|
||||
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY)
|
||||
if (pathHelper.getComponents(remote).getName().equals(local.getName())) {
|
||||
log.debug("probeDir: {} already exists", remote);
|
||||
final FileAttributes localAttrs = getAttributes(local);
|
||||
if (attrs.getMode().getMask() != localAttrs.getMode().getMask()
|
||||
|| (getModeGetter().preservesTimes()
|
||||
&& (attrs.getAtime() != attrs.getAtime() || attrs.getMtime() != localAttrs.getMtime())))
|
||||
sftp.setAttributes(remote, localAttrs);
|
||||
return remote;
|
||||
} else {
|
||||
log.debug("probeDir: {} already exists, path adjusted for {}", remote, local.getName());
|
||||
@@ -217,7 +237,7 @@ public class SFTPFileTransfer
|
||||
throws IOException {
|
||||
final FileAttributes attrs;
|
||||
try {
|
||||
attrs = sftp.stat(remote);
|
||||
attrs = engine.stat(remote);
|
||||
} catch (SFTPException e) {
|
||||
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
|
||||
log.debug("probeFile: {} does not exist", remote);
|
||||
@@ -238,7 +258,6 @@ public class SFTPFileTransfer
|
||||
private FileAttributes getAttributes(File local)
|
||||
throws IOException {
|
||||
final FileAttributes.Builder builder = new FileAttributes.Builder()
|
||||
.withType(local.isDirectory() ? FileMode.Type.DIRECTORY : FileMode.Type.REGULAR)
|
||||
.withPermissions(getModeGetter().getPermissions(local));
|
||||
if (getModeGetter().preservesTimes())
|
||||
builder.withAtimeMtime(getModeGetter().getLastAccessTime(local), getModeGetter().getLastModifiedTime(local));
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package net.schmizz.sshj.sftp;
|
||||
|
||||
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -26,9 +24,9 @@ public class StatefulSFTPClient
|
||||
|
||||
private String cwd;
|
||||
|
||||
public StatefulSFTPClient(SessionFactory ssh)
|
||||
public StatefulSFTPClient(SFTPEngine engine)
|
||||
throws IOException {
|
||||
super(ssh);
|
||||
super(engine);
|
||||
this.cwd = getSFTPEngine().canonicalize(".");
|
||||
log.info("Start dir = " + cwd);
|
||||
}
|
||||
@@ -37,8 +35,12 @@ public class StatefulSFTPClient
|
||||
return PathComponents.adjustForParent(cwd, path);
|
||||
}
|
||||
|
||||
public synchronized void cd(String dirname) {
|
||||
public synchronized void cd(String dirname)
|
||||
throws IOException {
|
||||
cwd = cwdify(dirname);
|
||||
if (statExistence(cwd) == null) {
|
||||
throw new SFTPException(cwd + ": does not exist");
|
||||
}
|
||||
log.info("CWD = " + cwd);
|
||||
}
|
||||
|
||||
@@ -98,6 +100,18 @@ public class StatefulSFTPClient
|
||||
super.mkdir(cwdify(dirname));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdirs(String path)
|
||||
throws IOException {
|
||||
super.mkdirs(cwdify(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileAttributes statExistence(String path)
|
||||
throws IOException {
|
||||
return super.statExistence(cwdify(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String oldpath, String newpath)
|
||||
throws IOException {
|
||||
@@ -167,7 +181,7 @@ public class StatefulSFTPClient
|
||||
@Override
|
||||
public void put(String source, String dest)
|
||||
throws IOException {
|
||||
super.get(source, cwdify(dest));
|
||||
super.put(source, cwdify(dest));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,29 +47,32 @@ import java.security.SignatureException;
|
||||
public abstract class AbstractSignature
|
||||
implements Signature {
|
||||
|
||||
protected final String algorithm;
|
||||
protected java.security.Signature signature;
|
||||
protected String algorithm;
|
||||
|
||||
protected AbstractSignature(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public void init(PublicKey pubkey, PrivateKey prvkey) {
|
||||
@Override
|
||||
public void init(PublicKey publicKey, PrivateKey privateKey) {
|
||||
try {
|
||||
signature = SecurityUtils.getSignature(algorithm);
|
||||
if (pubkey != null)
|
||||
signature.initVerify(pubkey);
|
||||
if (prvkey != null)
|
||||
signature.initSign(prvkey);
|
||||
if (publicKey != null)
|
||||
signature.initVerify(publicKey);
|
||||
if (privateKey != null)
|
||||
signature.initSign(privateKey);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] foo) {
|
||||
update(foo, 0, foo.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] foo, int off, int len) {
|
||||
try {
|
||||
signature.update(foo, off, len);
|
||||
@@ -81,19 +84,18 @@ public abstract class AbstractSignature
|
||||
protected byte[] extractSig(byte[] sig) {
|
||||
if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) {
|
||||
int i = 0;
|
||||
int j;
|
||||
j = sig[i++] << 24 & 0xff000000 //
|
||||
| sig[i++] << 16 & 0x00ff0000 //
|
||||
| sig[i++] << 8 & 0x0000ff00 //
|
||||
| sig[i++] & 0x000000ff;
|
||||
int j = sig[i++] << 24 & 0xff000000
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
i += j;
|
||||
j = sig[i++] << 24 & 0xff000000 //
|
||||
| sig[i++] << 16 & 0x00ff0000 //
|
||||
| sig[i++] << 8 & 0x0000ff00 //
|
||||
j = sig[i++] << 24 & 0xff000000
|
||||
| sig[i++] << 16 & 0x00ff0000
|
||||
| sig[i++] << 8 & 0x0000ff00
|
||||
| sig[i++] & 0x000000ff;
|
||||
byte[] tmp = new byte[j];
|
||||
System.arraycopy(sig, i, tmp, 0, j);
|
||||
sig = tmp;
|
||||
byte[] newSig = new byte[j];
|
||||
System.arraycopy(sig, i, newSig, 0, j);
|
||||
sig = newSig;
|
||||
}
|
||||
return sig;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ public class SignatureDSA
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureDSA();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.DSA.toString();
|
||||
}
|
||||
@@ -62,6 +64,7 @@ public class SignatureDSA
|
||||
super("SHA1withDSA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign() {
|
||||
byte[] sig;
|
||||
try {
|
||||
@@ -72,15 +75,16 @@ public class SignatureDSA
|
||||
|
||||
// sig is in ASN.1
|
||||
// SEQUENCE::={ r INTEGER, s INTEGER }
|
||||
int len = 0;
|
||||
int index = 3;
|
||||
len = sig[index++] & 0xff;
|
||||
byte[] r = new byte[len];
|
||||
System.arraycopy(sig, index, r, 0, r.length);
|
||||
index = index + len + 1;
|
||||
len = sig[index++] & 0xff;
|
||||
byte[] s = new byte[len];
|
||||
System.arraycopy(sig, index, s, 0, s.length);
|
||||
|
||||
int rIndex = 3;
|
||||
int rLen = sig[rIndex++] & 0xff;
|
||||
byte[] r = new byte[rLen];
|
||||
System.arraycopy(sig, rIndex, r, 0, r.length);
|
||||
|
||||
int sIndex = rIndex + rLen + 1;
|
||||
int sLen = sig[sIndex++] & 0xff;
|
||||
byte[] s = new byte[sLen];
|
||||
System.arraycopy(sig, sIndex, s, 0, s.length);
|
||||
|
||||
byte[] result = new byte[40];
|
||||
|
||||
@@ -91,6 +95,7 @@ public class SignatureDSA
|
||||
result,
|
||||
r.length > 20 ? 0 : 20 - r.length,
|
||||
r.length > 20 ? 20 : r.length);
|
||||
|
||||
System.arraycopy(s,
|
||||
s.length > 20 ? 1 : 0,
|
||||
result,
|
||||
@@ -100,6 +105,7 @@ public class SignatureDSA
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
sig = extractSig(sig);
|
||||
|
||||
|
||||
@@ -48,10 +48,12 @@ public class SignatureRSA
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Signature> {
|
||||
|
||||
@Override
|
||||
public Signature create() {
|
||||
return new SignatureRSA();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KeyType.RSA.toString();
|
||||
}
|
||||
@@ -62,6 +64,7 @@ public class SignatureRSA
|
||||
super("SHA1withRSA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign() {
|
||||
try {
|
||||
return signature.sign();
|
||||
@@ -70,6 +73,7 @@ public class SignatureRSA
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) {
|
||||
sig = extractSig(sig);
|
||||
try {
|
||||
|
||||
@@ -74,7 +74,7 @@ abstract class Converter {
|
||||
this.mac = mac;
|
||||
this.compression = compression;
|
||||
if (compression != null)
|
||||
compression.init(getCompressionType(), -1);
|
||||
compression.init(getCompressionType());
|
||||
this.cipherSize = cipher.getIVSize();
|
||||
}
|
||||
|
||||
@@ -86,6 +86,6 @@ abstract class Converter {
|
||||
return compression != null && (authed || !compression.isDelayed());
|
||||
}
|
||||
|
||||
abstract Compression.Type getCompressionType();
|
||||
abstract Compression.Mode getCompressionType();
|
||||
|
||||
}
|
||||
@@ -199,8 +199,8 @@ final class Decoder
|
||||
}
|
||||
|
||||
@Override
|
||||
Compression.Type getCompressionType() {
|
||||
return Compression.Type.Inflater;
|
||||
Compression.Mode getCompressionType() {
|
||||
return Compression.Mode.INFLATE;
|
||||
}
|
||||
|
||||
int getMaxPacketLength() {
|
||||
|
||||
@@ -160,9 +160,8 @@ final class Encoder
|
||||
}
|
||||
|
||||
@Override
|
||||
Compression.Type getCompressionType() {
|
||||
return Compression.Type.Deflater;
|
||||
Compression.Mode getCompressionType() {
|
||||
return Compression.Mode.DEFLATE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -50,8 +50,6 @@ final class Heartbeater
|
||||
|
||||
private int interval;
|
||||
|
||||
private boolean started;
|
||||
|
||||
Heartbeater(TransportImpl trans) {
|
||||
this.trans = trans;
|
||||
setName("heartbeater");
|
||||
@@ -59,36 +57,36 @@ final class Heartbeater
|
||||
|
||||
synchronized void setInterval(int interval) {
|
||||
this.interval = interval;
|
||||
if (interval != 0) {
|
||||
if (!started)
|
||||
start();
|
||||
notify();
|
||||
}
|
||||
if (interval > 0 && getState() == Thread.State.NEW)
|
||||
start();
|
||||
notify();
|
||||
}
|
||||
|
||||
synchronized int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
synchronized private int getPositiveInterval()
|
||||
throws InterruptedException {
|
||||
while (interval <= 0)
|
||||
wait();
|
||||
return interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
log.debug("Starting");
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int hi;
|
||||
synchronized (this) {
|
||||
while ((hi = interval) == 0)
|
||||
wait();
|
||||
}
|
||||
if (!started)
|
||||
started = true;
|
||||
else if (trans.isRunning()) {
|
||||
while (!isInterrupted()) {
|
||||
final int hi = getPositiveInterval();
|
||||
if (trans.isRunning()) {
|
||||
log.info("Sending heartbeat since {} seconds elapsed", hi);
|
||||
trans.write(new SSHPacket(Message.IGNORE));
|
||||
}
|
||||
Thread.sleep(hi * 1000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
if (isInterrupted()) {
|
||||
// We are meant to shut up and draw to a close if interrupted
|
||||
} else
|
||||
trans.die(e);
|
||||
@@ -96,4 +94,5 @@ final class Heartbeater
|
||||
|
||||
log.debug("Stopping");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
@@ -91,8 +92,7 @@ final class KeyExchanger
|
||||
private Proposal clientProposal;
|
||||
private NegotiatedAlgorithms negotiatedAlgs;
|
||||
|
||||
private final Event<TransportException> kexInitSent = new Event<TransportException>("kexinit sent",
|
||||
TransportException.chainer);
|
||||
private final Event<TransportException> kexInitSent = new Event<TransportException>("kexinit sent", TransportException.chainer);
|
||||
|
||||
private final Event<TransportException> done;
|
||||
|
||||
@@ -228,8 +228,12 @@ final class KeyExchanger
|
||||
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
|
||||
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(), negotiatedAlgs
|
||||
.getKeyExchangeAlgorithm());
|
||||
kex.init(transport, transport.getServerID().getBytes(), transport.getClientID().getBytes(), buf
|
||||
.getCompactData(), clientProposal.getPacket().getCompactData());
|
||||
try {
|
||||
kex.init(transport, transport.getServerID().getBytes(), transport.getClientID().getBytes(), buf
|
||||
.getCompactData(), clientProposal.getPacket().getCompactData());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +330,7 @@ final class KeyExchanger
|
||||
transport.getDecoder().setAlgorithms(cipher_S2C, mac_S2C, compression_S2C);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws TransportException {
|
||||
switch (expected) {
|
||||
@@ -347,10 +352,14 @@ final class KeyExchanger
|
||||
case FOLLOWUP:
|
||||
ensureKexOngoing();
|
||||
log.info("Received kex followup data");
|
||||
if (kex.next(msg, buf)) {
|
||||
verifyHost(kex.getHostKey());
|
||||
sendNewKeys();
|
||||
expected = Expected.NEWKEYS;
|
||||
try {
|
||||
if (kex.next(msg, buf)) {
|
||||
verifyHost(kex.getHostKey());
|
||||
sendNewKeys();
|
||||
expected = Expected.NEWKEYS;
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, e);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -369,6 +378,7 @@ final class KeyExchanger
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyError(SSHException error) {
|
||||
log.debug("Got notified of {}", error.toString());
|
||||
FutureUtils.alertAll(error, kexInitSent, done);
|
||||
|
||||
@@ -41,22 +41,20 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
final class Reader
|
||||
public final class Reader
|
||||
extends Thread {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final TransportImpl trans;
|
||||
|
||||
Reader(TransportImpl trans) {
|
||||
public Reader(TransportImpl trans) {
|
||||
this.trans = trans;
|
||||
setName("reader");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final Thread curThread = Thread.currentThread();
|
||||
|
||||
try {
|
||||
|
||||
final Decoder decoder = trans.getDecoder();
|
||||
@@ -66,7 +64,7 @@ final class Reader
|
||||
|
||||
int needed = 1;
|
||||
|
||||
while (!curThread.isInterrupted()) {
|
||||
while (!isInterrupted()) {
|
||||
int read = inp.read(recvbuf, 0, needed);
|
||||
if (read == -1)
|
||||
throw new TransportException("Broken transport; encountered EOF");
|
||||
@@ -75,7 +73,7 @@ final class Reader
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (curThread.isInterrupted()) {
|
||||
if (isInterrupted()) {
|
||||
// We are meant to shut up and draw to a close if interrupted
|
||||
} else
|
||||
trans.die(e);
|
||||
|
||||
@@ -25,6 +25,7 @@ public class TransportException
|
||||
|
||||
/** @see ExceptionChainer */
|
||||
public static final ExceptionChainer<TransportException> chainer = new ExceptionChainer<TransportException>() {
|
||||
@Override
|
||||
public TransportException chain(Throwable t) {
|
||||
if (t instanceof TransportException)
|
||||
return (TransportException) t;
|
||||
|
||||
@@ -133,6 +133,7 @@ public final class TransportImpl
|
||||
clientID = "SSH-2.0-" + config.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String remoteHost, int remotePort, InputStream in, OutputStream out)
|
||||
throws TransportException {
|
||||
connInfo = new ConnInfo(remoteHost, remotePort, in, out);
|
||||
@@ -214,10 +215,12 @@ public final class TransportImpl
|
||||
return ident;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHostKeyVerifier(HostKeyVerifier hkv) {
|
||||
kexer.addHostKeyVerifier(hkv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doKex()
|
||||
throws TransportException {
|
||||
kexer.startKex(true);
|
||||
@@ -227,50 +230,62 @@ public final class TransportImpl
|
||||
return kexer.isKexDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeartbeatInterval() {
|
||||
return heartbeater.getInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeartbeatInterval(int interval) {
|
||||
heartbeater.setInterval(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return connInfo.host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemotePort() {
|
||||
return connInfo.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientVersion() {
|
||||
return clientID.substring(8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersion() {
|
||||
return serverID == null ? serverID : serverID.substring(8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSessionID() {
|
||||
return kexer.getSessionID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Service getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setService(Service service) {
|
||||
if (service == null)
|
||||
service = nullService;
|
||||
@@ -279,6 +294,7 @@ public final class TransportImpl
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reqService(Service service)
|
||||
throws TransportException {
|
||||
serviceAccept.lock();
|
||||
@@ -305,16 +321,19 @@ public final class TransportImpl
|
||||
write(new SSHPacket(Message.SERVICE_REQUEST).putString(serviceName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated() {
|
||||
this.authed = true;
|
||||
encoder.setAuthenticated();
|
||||
decoder.setAuthenticated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return authed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendUnimplemented()
|
||||
throws TransportException {
|
||||
final long seq = decoder.getSequenceNumber();
|
||||
@@ -322,23 +341,28 @@ public final class TransportImpl
|
||||
return write(new SSHPacket(Message.UNIMPLEMENTED).putInt(seq));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void join()
|
||||
throws TransportException {
|
||||
close.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return reader.isAlive() && !close.isSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
disconnect(DisconnectReason.BY_APPLICATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(DisconnectReason reason) {
|
||||
disconnect(reason, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(DisconnectReason reason, String message) {
|
||||
close.lock(); // CAS type operation on close
|
||||
try {
|
||||
@@ -357,6 +381,7 @@ public final class TransportImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long write(SSHPacket payload)
|
||||
throws TransportException {
|
||||
writeLock.lock();
|
||||
@@ -396,8 +421,8 @@ public final class TransportImpl
|
||||
.putInt(reason.toInt())
|
||||
.putString(message)
|
||||
.putString(""));
|
||||
} catch (IOException logged) {
|
||||
log.warn("Error writing packet: {}", logged);
|
||||
} catch (IOException worthless) {
|
||||
log.debug("Error writing packet: {}", worthless.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,6 +441,7 @@ public final class TransportImpl
|
||||
*
|
||||
* @throws SSHException if an error occurs during handling (unrecoverable)
|
||||
*/
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws SSHException {
|
||||
this.msg = msg;
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes128-cbc";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes128-ctr";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes192-cbc";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes192-ctr";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes256-cbc";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "aes256-ctr";
|
||||
}
|
||||
|
||||
@@ -70,14 +70,17 @@ public class BaseCipher
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bsize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIVSize() {
|
||||
return ivsize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Mode mode, byte[] key, byte[] iv) {
|
||||
key = BaseCipher.resize(key, bsize);
|
||||
iv = BaseCipher.resize(iv, ivsize);
|
||||
@@ -91,6 +94,7 @@ public class BaseCipher
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] input, int inputOffset, int inputLen) {
|
||||
try {
|
||||
cipher.update(input, inputOffset, inputLen, input, inputOffset);
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "blowfish-cbc";
|
||||
}
|
||||
|
||||
@@ -42,26 +42,32 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIVSize() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Mode mode, byte[] bytes, byte[] bytes1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] input, int inputOffset, int inputLen) {
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "3des-cbc";
|
||||
}
|
||||
|
||||
@@ -35,34 +35,26 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.compression;
|
||||
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
/** Interface used to compress the stream of data between the SSH server and clients. */
|
||||
public interface Compression {
|
||||
|
||||
/** Enum identifying if this object will be used to compress or uncompress data. */
|
||||
enum Type {
|
||||
Inflater,
|
||||
Deflater
|
||||
enum Mode {
|
||||
INFLATE,
|
||||
DEFLATE
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress the given buffer in place.
|
||||
*
|
||||
* @param buffer the buffer containing the data to compress s
|
||||
*/
|
||||
void compress(SSHPacket buffer);
|
||||
|
||||
/**
|
||||
* Initialize this object to either compress or uncompress data. This method must be called prior to any calls to
|
||||
* either <code>compress</code> or <code>uncompress</code>. Once the object has been initialized, only one of
|
||||
* <code>compress</code> or <code>uncompress</code> method can be called.
|
||||
*
|
||||
* @param type
|
||||
* @param level
|
||||
* @param mode
|
||||
*/
|
||||
void init(Type type, int level);
|
||||
void init(Mode mode);
|
||||
|
||||
/**
|
||||
* Delayed compression is an Open-SSH specific feature which informs both the client and server to not compress data
|
||||
@@ -72,13 +64,22 @@ public interface Compression {
|
||||
*/
|
||||
boolean isDelayed();
|
||||
|
||||
/**
|
||||
* Compress the given buffer in place.
|
||||
*
|
||||
* @param buffer the buffer containing the data to compress s
|
||||
*/
|
||||
void compress(Buffer buffer);
|
||||
|
||||
/**
|
||||
* Uncompress the data in a buffer into another buffer.
|
||||
*
|
||||
* @param from the buffer containing the data to uncompress
|
||||
* @param to the buffer receiving the uncompressed data
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
void uncompress(SSHPacket from, SSHPacket to)
|
||||
void uncompress(Buffer from, Buffer to)
|
||||
throws TransportException;
|
||||
|
||||
}
|
||||
|
||||
@@ -46,10 +46,12 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "zlib@openssh.com";
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ package net.schmizz.sshj.transport.compression;
|
||||
|
||||
import com.jcraft.jzlib.JZlib;
|
||||
import com.jcraft.jzlib.ZStream;
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.SSHPacket;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
@@ -49,73 +49,80 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "zlib";
|
||||
}
|
||||
}
|
||||
|
||||
static private final int BUF_SIZE = 4096;
|
||||
private static final int BUF_SIZE = 4096;
|
||||
|
||||
private final byte[] tempBuf = new byte[BUF_SIZE];
|
||||
|
||||
private ZStream stream;
|
||||
private final byte[] tmpbuf = new byte[BUF_SIZE];
|
||||
|
||||
/** Create a new instance of a ZLib base compression */
|
||||
public ZlibCompression() {
|
||||
@Override
|
||||
public void init(Mode mode) {
|
||||
stream = new ZStream();
|
||||
switch (mode) {
|
||||
case DEFLATE:
|
||||
stream.deflateInit(JZlib.Z_DEFAULT_COMPRESSION);
|
||||
break;
|
||||
case INFLATE:
|
||||
stream.inflateInit();
|
||||
break;
|
||||
default:
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
public void compress(SSHPacket buffer) {
|
||||
@Override
|
||||
public boolean isDelayed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compress(Buffer buffer) {
|
||||
stream.next_in = buffer.array();
|
||||
stream.next_in_index = buffer.rpos();
|
||||
stream.avail_in = buffer.available();
|
||||
buffer.wpos(buffer.rpos());
|
||||
do {
|
||||
stream.next_out = tmpbuf;
|
||||
stream.next_out = tempBuf;
|
||||
stream.next_out_index = 0;
|
||||
stream.avail_out = BUF_SIZE;
|
||||
int status = stream.deflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
switch (status) {
|
||||
case JZlib.Z_OK:
|
||||
buffer.putRawBytes(tmpbuf, 0, BUF_SIZE - stream.avail_out);
|
||||
break;
|
||||
default:
|
||||
throw new SSHRuntimeException("compress: deflate returned " + status);
|
||||
final int status = stream.deflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
if (status == JZlib.Z_OK) {
|
||||
buffer.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
|
||||
} else {
|
||||
throw new SSHRuntimeException("compress: deflate returned " + status);
|
||||
}
|
||||
} while (stream.avail_out == 0);
|
||||
}
|
||||
|
||||
public void init(Type type, int level) {
|
||||
stream = new ZStream();
|
||||
if (type == Type.Deflater)
|
||||
stream.deflateInit(level);
|
||||
else
|
||||
stream.inflateInit();
|
||||
}
|
||||
|
||||
public boolean isDelayed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void uncompress(SSHPacket from, SSHPacket to)
|
||||
@Override
|
||||
public void uncompress(Buffer from, Buffer to)
|
||||
throws TransportException {
|
||||
stream.next_in = from.array();
|
||||
stream.next_in_index = from.rpos();
|
||||
stream.avail_in = from.available();
|
||||
|
||||
while (true) {
|
||||
stream.next_out = tmpbuf;
|
||||
stream.next_out = tempBuf;
|
||||
stream.next_out_index = 0;
|
||||
stream.avail_out = BUF_SIZE;
|
||||
int status = stream.inflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
final int status = stream.inflate(JZlib.Z_PARTIAL_FLUSH);
|
||||
switch (status) {
|
||||
case JZlib.Z_OK:
|
||||
to.putRawBytes(tmpbuf, 0, BUF_SIZE - stream.avail_out);
|
||||
to.putRawBytes(tempBuf, 0, BUF_SIZE - stream.avail_out);
|
||||
break;
|
||||
case JZlib.Z_BUF_ERROR:
|
||||
return; // wtf.. but this works *head spins*
|
||||
return;
|
||||
default:
|
||||
throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
|
||||
+ status);
|
||||
|
||||
@@ -61,14 +61,17 @@ public class BaseDigest
|
||||
this.bsize = bsize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] digest() {
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bsize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
md = SecurityUtils.getMessageDigest(algorithm);
|
||||
@@ -77,10 +80,12 @@ public class BaseDigest
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] foo) {
|
||||
update(foo, 0, foo.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] foo, int start, int len) {
|
||||
md.update(foo, start, len);
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ public class MD5
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Digest> {
|
||||
|
||||
@Override
|
||||
public Digest create() {
|
||||
return new MD5();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "md5";
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ public class SHA1
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<Digest> {
|
||||
|
||||
@Override
|
||||
public Digest create() {
|
||||
return new SHA1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "sha1";
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import net.schmizz.sshj.transport.digest.SHA1;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
@@ -72,29 +74,33 @@ public abstract class AbstractDHG
|
||||
private byte[] I_C;
|
||||
|
||||
private byte[] e;
|
||||
private byte[] f;
|
||||
private byte[] K;
|
||||
private byte[] H;
|
||||
private PublicKey hostKey;
|
||||
|
||||
@Override
|
||||
public byte[] getH() {
|
||||
return ByteArrayUtils.copyOf(H);
|
||||
}
|
||||
|
||||
public Digest getHash() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
public PublicKey getHostKey() {
|
||||
return hostKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getK() {
|
||||
return ByteArrayUtils.copyOf(K);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Digest getHash() {
|
||||
return sha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getHostKey() {
|
||||
return hostKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
|
||||
throws TransportException {
|
||||
throws GeneralSecurityException, TransportException {
|
||||
this.trans = trans;
|
||||
this.V_S = ByteArrayUtils.copyOf(V_S);
|
||||
this.V_C = ByteArrayUtils.copyOf(V_C);
|
||||
@@ -108,29 +114,30 @@ public abstract class AbstractDHG
|
||||
trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean next(Message msg, SSHPacket packet)
|
||||
throws TransportException {
|
||||
throws GeneralSecurityException, TransportException {
|
||||
if (msg != Message.KEXDH_31)
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "Unxpected packet: " + msg);
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "Unexpected packet: " + msg);
|
||||
|
||||
log.info("Received SSH_MSG_KEXDH_REPLY");
|
||||
final byte[] K_S = packet.readBytes();
|
||||
f = packet.readMPIntAsBytes();
|
||||
final byte[] f = packet.readMPIntAsBytes();
|
||||
final byte[] sig = packet.readBytes(); // signature sent by server
|
||||
dh.setF(f);
|
||||
dh.setF(new BigInteger(f));
|
||||
K = dh.getK();
|
||||
|
||||
hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
|
||||
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer() // our hash
|
||||
.putString(V_C) //
|
||||
.putString(V_S) //
|
||||
.putString(I_C) //
|
||||
.putString(I_S) //
|
||||
.putString(K_S) //
|
||||
.putMPInt(e) //
|
||||
.putMPInt(f) //
|
||||
.putMPInt(K); //
|
||||
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer()
|
||||
.putString(V_C)
|
||||
.putString(V_S)
|
||||
.putString(I_C)
|
||||
.putString(I_S)
|
||||
.putString(K_S)
|
||||
.putMPInt(e)
|
||||
.putMPInt(f)
|
||||
.putMPInt(K);
|
||||
sha.update(buf.array(), 0, buf.available());
|
||||
H = sha.digest();
|
||||
|
||||
@@ -139,7 +146,8 @@ public abstract class AbstractDHG
|
||||
signature.init(hostKey, null);
|
||||
signature.update(H, 0, H.length);
|
||||
if (!signature.verify(sig))
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "KeyExchange signature verification failed");
|
||||
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED,
|
||||
"KeyExchange signature verification failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
||||
import net.schmizz.sshj.common.SSHRuntimeException;
|
||||
import net.schmizz.sshj.common.SecurityUtils;
|
||||
|
||||
@@ -55,79 +54,53 @@ public class DH {
|
||||
private BigInteger p;
|
||||
private BigInteger g;
|
||||
private BigInteger e; // my public key
|
||||
private byte[] e_array;
|
||||
private BigInteger f; // your public key
|
||||
private BigInteger K; // shared secret key
|
||||
private byte[] K_array;
|
||||
private final KeyPairGenerator myKpairGen;
|
||||
private final KeyAgreement myKeyAgree;
|
||||
private final KeyPairGenerator generator;
|
||||
private final KeyAgreement agreement;
|
||||
|
||||
public DH() {
|
||||
try {
|
||||
myKpairGen = SecurityUtils.getKeyPairGenerator("DH");
|
||||
myKeyAgree = SecurityUtils.getKeyAgreement("DH");
|
||||
generator = SecurityUtils.getKeyPairGenerator("DH");
|
||||
agreement = SecurityUtils.getKeyAgreement("DH");
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public byte[] getE() {
|
||||
if (e == null) {
|
||||
DHParameterSpec dhSkipParamSpec = new DHParameterSpec(p, g);
|
||||
KeyPair myKpair;
|
||||
try {
|
||||
myKpairGen.initialize(dhSkipParamSpec);
|
||||
myKpair = myKpairGen.generateKeyPair();
|
||||
myKeyAgree.init(myKpair.getPrivate());
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
e = ((javax.crypto.interfaces.DHPublicKey) myKpair.getPublic()).getY();
|
||||
e_array = e.toByteArray();
|
||||
}
|
||||
return ByteArrayUtils.copyOf(e_array);
|
||||
}
|
||||
|
||||
public byte[] getK() {
|
||||
if (K == null) {
|
||||
try {
|
||||
KeyFactory myKeyFac = SecurityUtils.getKeyFactory("DH");
|
||||
DHPublicKeySpec keySpec = new DHPublicKeySpec(f, p, g);
|
||||
PublicKey yourPubKey = myKeyFac.generatePublic(keySpec);
|
||||
myKeyAgree.doPhase(yourPubKey, true);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new SSHRuntimeException(e);
|
||||
}
|
||||
byte[] mySharedSecret = myKeyAgree.generateSecret();
|
||||
K = new BigInteger(mySharedSecret);
|
||||
K_array = mySharedSecret;
|
||||
}
|
||||
return ByteArrayUtils.copyOf(K_array);
|
||||
}
|
||||
|
||||
public void setF(byte[] f) {
|
||||
setF(new BigInteger(f));
|
||||
}
|
||||
|
||||
public void setG(byte[] g) {
|
||||
setG(new BigInteger(g));
|
||||
}
|
||||
|
||||
public void setP(byte[] p) {
|
||||
setP(new BigInteger(p));
|
||||
}
|
||||
|
||||
void setF(BigInteger f) {
|
||||
public void setF(BigInteger f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
void setG(BigInteger g) {
|
||||
public void setG(BigInteger g) {
|
||||
this.g = g;
|
||||
}
|
||||
|
||||
void setP(BigInteger p) {
|
||||
public void setP(BigInteger p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
public byte[] getE()
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,10 +47,12 @@ public class DHG1
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new DHG1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "diffie-hellman-group1-sha1";
|
||||
}
|
||||
@@ -59,8 +61,8 @@ public class DHG1
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh) {
|
||||
dh.setG(DHGroupData.getG());
|
||||
dh.setP(DHGroupData.getP1());
|
||||
dh.setG(DHGroupData.G);
|
||||
dh.setP(DHGroupData.P1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ public class DHG14
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<KeyExchange> {
|
||||
|
||||
@Override
|
||||
public KeyExchange create() {
|
||||
return new DHG14();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "diffie-hellman-group14-sha1";
|
||||
}
|
||||
@@ -60,8 +62,8 @@ public class DHG14
|
||||
|
||||
@Override
|
||||
protected void initDH(DH dh) {
|
||||
dh.setG(DHGroupData.getG());
|
||||
dh.setP(DHGroupData.getP14());
|
||||
dh.setG(DHGroupData.G);
|
||||
dh.setP(DHGroupData.P14);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,70 +35,28 @@
|
||||
*/
|
||||
package net.schmizz.sshj.transport.kex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/** Simple class holding the data for DH group key exchanges. */
|
||||
public final class DHGroupData {
|
||||
|
||||
public static byte[] getG() {
|
||||
final byte[] G = {2};
|
||||
return G;
|
||||
}
|
||||
public static final BigInteger G =
|
||||
new BigInteger("2");
|
||||
|
||||
public static byte[] getP1() {
|
||||
final byte[] P_1 = {(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xC9, (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68,
|
||||
(byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, (byte) 0x8B, (byte) 0x80, (byte) 0xDC,
|
||||
(byte) 0x1C, (byte) 0xD1, (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A, (byte) 0x67,
|
||||
(byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13,
|
||||
(byte) 0x9B, (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, (byte) 0x8E, (byte) 0x34,
|
||||
(byte) 0x04, (byte) 0xDD, (byte) 0xEF, (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A,
|
||||
(byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, (byte) 0x6D, (byte) 0xF2, (byte) 0x5F,
|
||||
(byte) 0x14, (byte) 0x37, (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D, (byte) 0x51,
|
||||
(byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E,
|
||||
(byte) 0x7E, (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, (byte) 0xA6, (byte) 0x37,
|
||||
(byte) 0xED, (byte) 0x6B, (byte) 0x0B, (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4, (byte) 0x06,
|
||||
(byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B, (byte) 0xFB, (byte) 0x5A, (byte) 0x89,
|
||||
(byte) 0x9F, (byte) 0xA5, (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C, (byte) 0x4B,
|
||||
(byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28, (byte) 0x66, (byte) 0x51, (byte) 0xEC, (byte) 0xE6,
|
||||
(byte) 0x53, (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF};
|
||||
return P_1;
|
||||
}
|
||||
public static final BigInteger P1 =
|
||||
new BigInteger("1797693134862315907708391567937874531978602960487560117064444236841971802161585193" +
|
||||
"6894783379586492554150218056548598050364644054819923910005079287700335581663922955" +
|
||||
"3136239076508735759914822574862575007425302077447712589550957937778424442426617334" +
|
||||
"727629299387668709205606050270810842907692932019128194467627007");
|
||||
|
||||
public static byte[] getP14() {
|
||||
final byte[] P_14 = {(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9, (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21,
|
||||
(byte) 0x68, (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, (byte) 0x8B, (byte) 0x80,
|
||||
(byte) 0xDC, (byte) 0x1C, (byte) 0xD1, (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A,
|
||||
(byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, (byte) 0xBE, (byte) 0xA6, (byte) 0x3B,
|
||||
(byte) 0x13, (byte) 0x9B, (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, (byte) 0x8E,
|
||||
(byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF, (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD,
|
||||
(byte) 0x3A, (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, (byte) 0x6D, (byte) 0xF2,
|
||||
(byte) 0x5F, (byte) 0x14, (byte) 0x37, (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D,
|
||||
(byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, (byte) 0xB5, (byte) 0x76, (byte) 0x62,
|
||||
(byte) 0x5E, (byte) 0x7E, (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, (byte) 0xA6,
|
||||
(byte) 0x37, (byte) 0xED, (byte) 0x6B, (byte) 0x0B, (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4,
|
||||
(byte) 0x06, (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B, (byte) 0xFB, (byte) 0x5A,
|
||||
(byte) 0x89, (byte) 0x9F, (byte) 0xA5, (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C,
|
||||
(byte) 0x4B, (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28, (byte) 0x66, (byte) 0x51, (byte) 0xEC,
|
||||
(byte) 0xE4, (byte) 0x5B, (byte) 0x3D, (byte) 0xC2, (byte) 0x00, (byte) 0x7C, (byte) 0xB8, (byte) 0xA1,
|
||||
(byte) 0x63, (byte) 0xBF, (byte) 0x05, (byte) 0x98, (byte) 0xDA, (byte) 0x48, (byte) 0x36, (byte) 0x1C,
|
||||
(byte) 0x55, (byte) 0xD3, (byte) 0x9A, (byte) 0x69, (byte) 0x16, (byte) 0x3F, (byte) 0xA8, (byte) 0xFD,
|
||||
(byte) 0x24, (byte) 0xCF, (byte) 0x5F, (byte) 0x83, (byte) 0x65, (byte) 0x5D, (byte) 0x23, (byte) 0xDC,
|
||||
(byte) 0xA3, (byte) 0xAD, (byte) 0x96, (byte) 0x1C, (byte) 0x62, (byte) 0xF3, (byte) 0x56, (byte) 0x20,
|
||||
(byte) 0x85, (byte) 0x52, (byte) 0xBB, (byte) 0x9E, (byte) 0xD5, (byte) 0x29, (byte) 0x07, (byte) 0x70,
|
||||
(byte) 0x96, (byte) 0x96, (byte) 0x6D, (byte) 0x67, (byte) 0x0C, (byte) 0x35, (byte) 0x4E, (byte) 0x4A,
|
||||
(byte) 0xBC, (byte) 0x98, (byte) 0x04, (byte) 0xF1, (byte) 0x74, (byte) 0x6C, (byte) 0x08, (byte) 0xCA,
|
||||
(byte) 0x18, (byte) 0x21, (byte) 0x7C, (byte) 0x32, (byte) 0x90, (byte) 0x5E, (byte) 0x46, (byte) 0x2E,
|
||||
(byte) 0x36, (byte) 0xCE, (byte) 0x3B, (byte) 0xE3, (byte) 0x9E, (byte) 0x77, (byte) 0x2C, (byte) 0x18,
|
||||
(byte) 0x0E, (byte) 0x86, (byte) 0x03, (byte) 0x9B, (byte) 0x27, (byte) 0x83, (byte) 0xA2, (byte) 0xEC,
|
||||
(byte) 0x07, (byte) 0xA2, (byte) 0x8F, (byte) 0xB5, (byte) 0xC5, (byte) 0x5D, (byte) 0xF0, (byte) 0x6F,
|
||||
(byte) 0x4C, (byte) 0x52, (byte) 0xC9, (byte) 0xDE, (byte) 0x2B, (byte) 0xCB, (byte) 0xF6, (byte) 0x95,
|
||||
(byte) 0x58, (byte) 0x17, (byte) 0x18, (byte) 0x39, (byte) 0x95, (byte) 0x49, (byte) 0x7C, (byte) 0xEA,
|
||||
(byte) 0x95, (byte) 0x6A, (byte) 0xE5, (byte) 0x15, (byte) 0xD2, (byte) 0x26, (byte) 0x18, (byte) 0x98,
|
||||
(byte) 0xFA, (byte) 0x05, (byte) 0x10, (byte) 0x15, (byte) 0x72, (byte) 0x8E, (byte) 0x5A, (byte) 0x8A,
|
||||
(byte) 0xAC, (byte) 0xAA, (byte) 0x68, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
|
||||
return P_14;
|
||||
}
|
||||
public static final BigInteger P14 =
|
||||
new BigInteger("3231700607131100730033891392642382824881794124114023911284200975140074170663435422" +
|
||||
"2619689417363569347117901737909704191754605873209195028853758986185622153212175412" +
|
||||
"5149017745202702357960782362488842461894775876411059286460994117232454266225221932" +
|
||||
"3054091903768052423551912567971587011700105805587765103886184728025797605490356973" +
|
||||
"2561526167081339361799541336476559160368317896729073178384589680639671900977202194" +
|
||||
"1686472258710314113364293195361934716365332097170774482279885885653692086452966360" +
|
||||
"7725026895550592836275112117409697299806841055435958486658329164213621823107899099" +
|
||||
"9448652468262416972035911852507045361090559");
|
||||
|
||||
}
|
||||
|
||||
@@ -41,14 +41,33 @@ import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
import net.schmizz.sshj.transport.digest.Digest;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/** Key exchange algorithm. */
|
||||
public interface KeyExchange {
|
||||
|
||||
/**
|
||||
* Initialize the key exchange algorithm.
|
||||
*
|
||||
* @param trans the transport
|
||||
* @param V_S the server identification string
|
||||
* @param V_C the client identification string
|
||||
* @param I_S the server key init packet
|
||||
* @param I_C the client key init packet
|
||||
*
|
||||
* @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)
|
||||
throws GeneralSecurityException, TransportException;
|
||||
|
||||
/** @return the computed H parameter */
|
||||
byte[] getH();
|
||||
|
||||
/** @return the computed K parameter */
|
||||
byte[] getK();
|
||||
|
||||
/**
|
||||
* The message digest used by this key exchange algorithm.
|
||||
*
|
||||
@@ -59,23 +78,6 @@ public interface KeyExchange {
|
||||
/** @return the host key determined from server's response packets */
|
||||
PublicKey getHostKey();
|
||||
|
||||
/** @return the computed K parameter */
|
||||
byte[] getK();
|
||||
|
||||
/**
|
||||
* Initialize the key exchange algorithm.
|
||||
*
|
||||
* @param trans the transport
|
||||
* @param V_S the server identification string
|
||||
* @param V_C the client identification string
|
||||
* @param I_S the server key init packet
|
||||
* @param I_C the client key init packet
|
||||
*
|
||||
* @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)
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
* Process the next packet
|
||||
*
|
||||
@@ -84,9 +86,10 @@ public interface KeyExchange {
|
||||
*
|
||||
* @return a boolean indicating if the processing is complete or if more packets are to be received
|
||||
*
|
||||
* @throws TransportException if there is an error sending a packet
|
||||
* @throws GeneralSecurityException
|
||||
* @throws TransportException if there is an error sending a packet
|
||||
*/
|
||||
boolean next(Message msg, SSHPacket buffer)
|
||||
throws TransportException;
|
||||
throws GeneralSecurityException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
@@ -59,14 +59,17 @@ public class BaseMAC
|
||||
tmp = new byte[defbsize];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] doFinal() {
|
||||
return mac.doFinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] doFinal(byte[] input) {
|
||||
return mac.doFinal(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFinal(byte[] buf, int offset) {
|
||||
try {
|
||||
if (bsize != defbsize) {
|
||||
@@ -79,10 +82,12 @@ public class BaseMAC
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return bsize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(byte[] key) {
|
||||
if (key.length > defbsize) {
|
||||
byte[] tmp = new byte[defbsize];
|
||||
@@ -99,14 +104,17 @@ public class BaseMAC
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte foo[], int s, int l) {
|
||||
mac.update(foo, s, l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] foo) {
|
||||
mac.update(foo, 0, foo.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long i) {
|
||||
tmp[0] = (byte) (i >>> 24);
|
||||
tmp[1] = (byte) (i >>> 16);
|
||||
|
||||
@@ -43,10 +43,12 @@ public class HMACMD5
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||
|
||||
@Override
|
||||
public MAC create() {
|
||||
return new HMACMD5();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hmac-md5";
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ public class HMACMD596
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||
|
||||
@Override
|
||||
public MAC create() {
|
||||
return new HMACMD596();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hmac-md5-96";
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ public class HMACSHA1
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||
|
||||
@Override
|
||||
public MAC create() {
|
||||
return new HMACSHA1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hmac-sha1";
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ public class HMACSHA196
|
||||
public static class Factory
|
||||
implements net.schmizz.sshj.common.Factory.Named<MAC> {
|
||||
|
||||
@Override
|
||||
public MAC create() {
|
||||
return new HMACSHA196();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hmac-sha1-96";
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user