You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/04/17 12:33:05 UTC
[1/6] mina-sshd git commit: [SSHD-816] Added mechanism for specifying
non-default SftpClientFactory for SFTP CLI code
Repository: mina-sshd
Updated Branches:
refs/heads/master 76fe82188 -> 04c579408
[SSHD-816] Added mechanism for specifying non-default SftpClientFactory for SFTP CLI code
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/04c57940
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/04c57940
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/04c57940
Branch: refs/heads/master
Commit: 04c57940851f2a13347ea3c96eb4217ca8c13ca1
Parents: 2b7e22a
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Tue Apr 17 13:10:08 2018 +0300
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Tue Apr 17 15:32:31 2018 +0300
----------------------------------------------------------------------
README.md | 16 ++++++-
.../apache/sshd/cli/client/SftpCommandMain.java | 49 +++++++++++++++++++-
.../sshd/cli/server/SshServerCliSupport.java | 2 +-
3 files changed, 63 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/04c57940/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index bb4f586..4878117 100644
--- a/README.md
+++ b/README.md
@@ -1293,6 +1293,18 @@ In order to use this CLI code as part of another project, one needs to include t
</dependency>
```
+### Command line clients
+
+* **SftpCommandMain** - by default uses an internal `SftpClientFactory`. This can be overridden as follows:
+
+1. Provide a `-o SftpClientFactory=XXX` command line argument where the option specifies the fully-qualified name of
+the class that implements this interface.
+
+2. Add a `META-INF\services\org.apache.sshd.client.subsystem.sftp.SftpClientFactory` file containing the fully-qualified name of
+the class that implements this interface. **Note:** if more than one such instance is detected an exception is thrown.
+
+**Note:** The specified class(es) must be public and contain a public no-args constructor.
+
### Command line SSH daemon
* **Port** - by default the SSH server sets up to list on port 8000 in order to avoid conflicts with any running SSH O/S daemon. This can be modified by providing a `-p NNNN`
@@ -1302,8 +1314,8 @@ or `-o Port=NNNN` command line option.
This can be overwritten as follows (in this order):
1. Provide a `org.apache.sshd.server.subsystem.SubsystemFactory` system property containing comma-separated fully-qualified names of classes implementing
-this interface. The implementations must have a public no-args constructor for instantiating them. The order of the provided subsystems will be according
-to their order in the specified list
+this interface. The implementations must be public and have a public no-args constructor for instantiating them. The order of the provided subsystems will
+be according to their order in the specified list.
2. Provide a `-o Subsystem=xxx,yyy` command line argument where value is a comma-separated list of the **name**(s) of the auto-detected factories via
the `ServiceLoader` mechanism. The special value `none` may be used to indicate that no subsystem is to be configured. **Note:** no specific order is
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/04c57940/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index 1d982f2..09b440d 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -37,6 +37,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
+import java.util.ServiceLoader;
import java.util.TreeMap;
import java.util.logging.Level;
@@ -61,6 +62,7 @@ import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.threads.ThreadUtils;
/**
* TODO Add javadoc
@@ -240,6 +242,51 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
return sb;
}
+ public static SftpClientFactory resolveSftpClientFactory(String... args) {
+ int numArgs = GenericUtils.length(args);
+ ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(SftpClientFactory.class);
+ for (int index = 0; index < numArgs; index++) {
+ String argVal = args[index];
+ if ("-o".equals(argVal)) {
+ index++;
+
+ String opt = args[index];
+ int idx = opt.indexOf('=');
+ ValidateUtils.checkTrue(idx > 0, "bad syntax for option: %s %s", argVal, opt);
+
+ String optName = opt.substring(0, idx);
+ String optValue = opt.substring(idx + 1);
+ if (!Objects.equals(SftpClientFactory.class.getSimpleName(), optName)) {
+ continue;
+ }
+
+ try {
+ Class<?> clazz = cl.loadClass(optValue);
+ return SftpClientFactory.class.cast(clazz.newInstance());
+ } catch (Throwable t) {
+ System.err.append("Failed (").append(t.getClass().getSimpleName()).append(')')
+ .append(" to instantiate ").append(optValue)
+ .append(": ").println(t.getMessage());
+ System.err.flush();
+ throw GenericUtils.toRuntimeException(t, true);
+ }
+ }
+ }
+
+ ServiceLoader<SftpClientFactory> loader = ServiceLoader.load(SftpClientFactory.class, cl);
+ SftpClientFactory factory = null;
+ for (SftpClientFactory f : loader) {
+ ValidateUtils.checkState(factory == null, "Multiple factories detected - select one");
+ factory = f;
+ }
+
+ if (factory != null) {
+ return factory;
+ }
+
+ return SftpClientFactory.instance();
+ }
+
public static void main(String[] args) throws Exception {
PrintStream stdout = System.out;
PrintStream stderr = System.err;
@@ -262,7 +309,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
try {
// TODO allow command-line specification of SftpClientFactory
- SftpClientFactory clientFactory = SftpClientFactory.instance();
+ SftpClientFactory clientFactory = resolveSftpClientFactory(args);
try (SftpClient sftpClient = clientFactory.createSftpClient(session);
SftpCommandMain sftp = new SftpCommandMain(sftpClient)) {
// TODO allow injection of extra CommandExecutor(s) via command line and/or service loading
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/04c57940/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
index 903128a..585f4a9 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
@@ -144,7 +144,7 @@ public abstract class SshServerCliSupport extends CliSupport {
for (String fqcn : classes) {
try {
Class<?> clazz = cl.loadClass(fqcn);
- SubsystemFactory factory = (SubsystemFactory) clazz.newInstance();
+ SubsystemFactory factory = SubsystemFactory.class.cast(clazz.newInstance());
subsystems.add(factory);
} catch (Throwable t) {
System.err.append("Failed (").append(t.getClass().getSimpleName()).append(')')
[6/6] mina-sshd git commit: [SSHD-816] Moved all 'main' code for
client commands to sshd-cli module
Posted by lg...@apache.org.
[SSHD-816] Moved all 'main' code for client commands to sshd-cli module
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/536effdc
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/536effdc
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/536effdc
Branch: refs/heads/master
Commit: 536effdc5c1a9a0b8dcc0fc290b59fddd7efeb51
Parents: 76fe821
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Tue Apr 17 10:13:01 2018 +0300
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Tue Apr 17 15:32:31 2018 +0300
----------------------------------------------------------------------
README.md | 14 +-
assembly/pom.xml | 5 +
assembly/src/main/distribution/bin/scp.bat | 2 +-
assembly/src/main/distribution/bin/scp.sh | 2 +-
assembly/src/main/distribution/bin/sftp.bat | 2 +-
assembly/src/main/distribution/bin/sftp.sh | 2 +-
.../src/main/distribution/bin/ssh-keyscan.bat | 2 +-
.../src/main/distribution/bin/ssh-keyscan.sh | 2 +-
assembly/src/main/distribution/bin/ssh.bat | 2 +-
assembly/src/main/distribution/bin/ssh.sh | 2 +-
pom.xml | 1 +
sshd-cli/pom.xml | 115 +++
.../java/org/apache/sshd/cli/CliSupport.java | 38 +
.../apache/sshd/cli/client/ScpCommandMain.java | 220 +++++
.../sshd/cli/client/SftpCommandExecutor.java | 34 +
.../apache/sshd/cli/client/SftpCommandMain.java | 919 ++++++++++++++++++
.../sshd/cli/client/SshClientCliSupport.java | 632 +++++++++++++
.../apache/sshd/cli/client/SshClientMain.java | 193 ++++
.../apache/sshd/cli/client/SshKeyScanMain.java | 741 +++++++++++++++
.../apache/sshd/cli/client/ChannelExecMain.java | 93 ++
.../cli/client/ScpCommandMainDevelopment.java | 36 +
.../cli/client/SftpCommandMainDevelopment.java | 36 +
.../cli/client/SshClientMainDevelopment.java | 36 +
.../cli/client/SshKeyScanMainDevelopment.java | 36 +
.../java/org/apache/sshd/client/SshClient.java | 740 ---------------
.../java/org/apache/sshd/client/SshKeyScan.java | 740 ---------------
.../sshd/client/scp/DefaultScpClient.java | 184 ----
.../org/apache/sshd/client/SshClientMain.java | 36 -
.../org/apache/sshd/client/SshKeyScanMain.java | 36 -
.../sshd/client/channel/ChannelExecMain.java | 93 --
.../apache/sshd/client/scp/ScpCommandMain.java | 36 -
.../sshd/client/subsystem/sftp/SftpCommand.java | 920 -------------------
.../client/subsystem/sftp/SftpCommandMain.java | 36 -
33 files changed, 3155 insertions(+), 2831 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index ecf2317..bcef46f 100644
--- a/README.md
+++ b/README.md
@@ -1279,9 +1279,19 @@ in case the classes it used it are modified or deleted.
## Command line clients
-The _apache-sshd.zip_ distribution provides `Windows/Linux` scripts that use the MINA SSHD code base to implement the common _ssh, scp, sftp_ commands. The clients accept most useful switches from the original commands they mimic, where the `-o Option=Value` arguments can be used to configure the client/server in addition to the system properties mechanism. For more details, consult the _main_ methods code in the respective `SshClient`, `SftpCommand` and `DefaultScpClient` classes. The code also includes `SshKeyScan#main` that is a simple implementation for [ssh-keyscan(1)](https://www.freebsd.org/cgi/man.cgi?query=ssh-keyscan&sektion=1).
+The _apache-sshd.zip_ distribution provides `Windows/Linux` scripts that use the MINA SSHD code base to implement the common _ssh, scp, sftp_ commands. The clients accept most useful switches from the original commands they mimic, where the `-o Option=Value` arguments can be used to configure the client/server in addition to the system properties mechanism. For more details, consult the _main_ methods code in the respective `SshClientMain`, `SftpCommandMain` and `ScpClientMain` classes. The code also includes `SshKeyScanMain` that is a simple implementation for [ssh-keyscan(1)](https://www.freebsd.org/cgi/man.cgi?query=ssh-keyscan&sektion=1).
-The distribution also includes also an _sshd_ script that can be used to launch a server instance - see `SshServer#main` for activation command line arguments and options.
+The distribution also includes also an _sshd_ script that can be used to launch a server instance - see `SshServerMain#main` for activation command line arguments and options.
+
+In order to use this CLI code as part of another project, one needs to include the _sshd-cli_ module:
+
+```xml
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-cli</artifactId>
+ <version>...same version as the core...</version>
+ </dependency>
+```
## GIT support
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/pom.xml
----------------------------------------------------------------------
diff --git a/assembly/pom.xml b/assembly/pom.xml
index 85eab19..f3c4716 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -47,6 +47,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/scp.bat
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/scp.bat b/assembly/src/main/distribution/bin/scp.bat
index ce86067..68eea30 100644
--- a/assembly/src/main/distribution/bin/scp.bat
+++ b/assembly/src/main/distribution/bin/scp.bat
@@ -91,7 +91,7 @@ goto :EOF
SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
rem Execute the Java Virtual Machine
cd %SSHD_HOME%
-"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.client.scp.DefaultScpClient %ARGS%
+"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.cli.client.ScpCommandMain %ARGS%
rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/scp.sh
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/scp.sh b/assembly/src/main/distribution/bin/scp.sh
index 55d26b8..0d28c89 100644
--- a/assembly/src/main/distribution/bin/scp.sh
+++ b/assembly/src/main/distribution/bin/scp.sh
@@ -255,7 +255,7 @@ run() {
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
cd $SSHD_BASE
- exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.client.scp.DefaultScpClient "$@"
+ exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.cli.client.ScpCommandMain "$@"
}
main() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/sftp.bat
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/sftp.bat b/assembly/src/main/distribution/bin/sftp.bat
index f4c5aee..3a1b908 100644
--- a/assembly/src/main/distribution/bin/sftp.bat
+++ b/assembly/src/main/distribution/bin/sftp.bat
@@ -91,7 +91,7 @@ goto :EOF
SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
rem Execute the Java Virtual Machine
cd %SSHD_HOME%
-"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.client.subsystem.sftp.SftpCommand %ARGS%
+"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.cli.client.SftpCommandMain %ARGS%
rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/sftp.sh
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/sftp.sh b/assembly/src/main/distribution/bin/sftp.sh
index 8884ce8..477e7fe 100644
--- a/assembly/src/main/distribution/bin/sftp.sh
+++ b/assembly/src/main/distribution/bin/sftp.sh
@@ -255,7 +255,7 @@ run() {
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
cd $SSHD_BASE
- exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.client.subsystem.sftp.SftpCommand "$@"
+ exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.cli.client.SftpCommandMain "$@"
}
main() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/ssh-keyscan.bat
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/ssh-keyscan.bat b/assembly/src/main/distribution/bin/ssh-keyscan.bat
index bbbd1e0..1c2b78e 100644
--- a/assembly/src/main/distribution/bin/ssh-keyscan.bat
+++ b/assembly/src/main/distribution/bin/ssh-keyscan.bat
@@ -91,7 +91,7 @@ goto :EOF
SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
rem Execute the Java Virtual Machine
cd %SSHD_HOME%
-"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.client.SshKeyScan %ARGS%
+"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.cli.client.SshKeyScanMain %ARGS%
rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/ssh-keyscan.sh
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/ssh-keyscan.sh b/assembly/src/main/distribution/bin/ssh-keyscan.sh
index e6245e0..083f7b8 100644
--- a/assembly/src/main/distribution/bin/ssh-keyscan.sh
+++ b/assembly/src/main/distribution/bin/ssh-keyscan.sh
@@ -255,7 +255,7 @@ run() {
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
cd $SSHD_BASE
- exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.client.SshKeyScan "$@"
+ exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.cli.client.SshKeyScanMain "$@"
}
main() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/ssh.bat
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/ssh.bat b/assembly/src/main/distribution/bin/ssh.bat
index 14b5b41..85e93c2 100644
--- a/assembly/src/main/distribution/bin/ssh.bat
+++ b/assembly/src/main/distribution/bin/ssh.bat
@@ -91,7 +91,7 @@ goto :EOF
SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
rem Execute the Java Virtual Machine
cd %SSHD_HOME%
-"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.client.SshClient %ARGS%
+"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.cli.client.SshClientMain %ARGS%
rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/assembly/src/main/distribution/bin/ssh.sh
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/ssh.sh b/assembly/src/main/distribution/bin/ssh.sh
index 3e29813..fe07901 100644
--- a/assembly/src/main/distribution/bin/ssh.sh
+++ b/assembly/src/main/distribution/bin/ssh.sh
@@ -255,7 +255,7 @@ run() {
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
cd $SSHD_BASE
- exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.client.SshClient "$@"
+ exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.cli.client.SshClientMain "$@"
}
main() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ea73bb4..9cbf957 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1010,6 +1010,7 @@
<module>sshd-git</module>
<module>sshd-contrib</module>
<module>sshd-spring-sftp</module>
+ <module>sshd-cli</module>
<module>assembly</module>
</modules>
</project>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-cli/pom.xml b/sshd-cli/pom.xml
new file mode 100644
index 0000000..bd19a7e
--- /dev/null
+++ b/sshd-cli/pom.xml
@@ -0,0 +1,115 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+
+ <!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd</artifactId>
+ <version>1.7.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sshd-cli</artifactId>
+ <name>Apache Mina SSHD :: CLI</name>
+ <packaging>jar</packaging>
+ <inceptionYear>2018</inceptionYear>
+
+ <properties>
+ <projectRoot>${project.basedir}/..</projectRoot>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-sftp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jsch</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jzlib</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/filtered-resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <additionalparam>-Xdoclint:none</additionalparam>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
new file mode 100644
index 0000000..14737bb
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli;
+
+import java.io.PrintStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class CliSupport {
+ protected CliSupport() {
+ super();
+ }
+
+ public static boolean showError(PrintStream stderr, String message) {
+ stderr.println(message);
+ return true;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
new file mode 100644
index 0000000..770ab32
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+
+import org.apache.sshd.client.scp.ScpClient;
+import org.apache.sshd.client.scp.ScpClient.Option;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.scp.ScpLocation;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ScpCommandMain extends SshClientCliSupport {
+ /**
+ * Command line option used to indicate a non-default port
+ */
+ public static final String SCP_PORT_OPTION = "-P";
+
+ public ScpCommandMain() {
+ super(); // in case someone wants to extend it
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static String[] normalizeCommandArguments(PrintStream stdout, PrintStream stderr, String... args) {
+ int numArgs = GenericUtils.length(args);
+ if (numArgs <= 0) {
+ return args;
+ }
+
+ List<String> effective = new ArrayList<>(numArgs);
+ boolean error = false;
+ for (int index = 0; (index < numArgs) && (!error); index++) {
+ String argName = args[index];
+ // handled by 'setupClientSession'
+ if (isArgumentedOption(SCP_PORT_OPTION, argName)) {
+ if ((index + 1) >= numArgs) {
+ error = showError(stderr, "option requires an argument: " + argName);
+ break;
+ }
+
+ effective.add(argName);
+ effective.add(args[++index]);
+ } else if ("-r".equals(argName) || "-p".equals(argName)
+ || "-q".equals(argName) || "-C".equals(argName)
+ || "-v".equals(argName) || "-vv".equals(argName) || "-vvv".equals(argName)) {
+ effective.add(argName);
+ } else if (argName.charAt(0) == '-') {
+ error = showError(stderr, "Unknown option: " + argName);
+ break;
+ } else {
+ if ((index + 1) >= numArgs) {
+ error = showError(stderr, "Not enough arguments");
+ break;
+ }
+
+ ScpLocation source = new ScpLocation(argName);
+ ScpLocation target = new ScpLocation(args[++index]);
+ if (index < (numArgs - 1)) {
+ error = showError(stderr, "Unexpected extra arguments");
+ break;
+ }
+
+ if (source.isLocal() == target.isLocal()) {
+ error = showError(stderr, "Both targets are either remote or local");
+ break;
+ }
+
+ ScpLocation remote = source.isLocal() ? target : source;
+ effective.add(remote.resolveUsername() + "@" + remote.getHost());
+ effective.add(source.toString());
+ effective.add(target.toString());
+ break;
+ }
+ }
+
+ if (error) {
+ return null;
+ }
+
+ return effective.toArray(new String[effective.size()]);
+ }
+
+ public static void main(String[] args) throws Exception {
+ final PrintStream stdout = System.out;
+ final PrintStream stderr = System.err;
+ OutputStream logStream = stdout;
+ try (BufferedReader stdin = new BufferedReader(
+ new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
+ args = normalizeCommandArguments(stdout, stderr, args);
+ int numArgs = GenericUtils.length(args);
+ // see the way normalizeCommandArguments works...
+ if (numArgs >= 2) {
+ Level level = resolveLoggingVerbosity(args, numArgs - 2);
+ logStream = resolveLoggingTargetStream(stdout, stderr, args, numArgs - 2);
+ if (logStream != null) {
+ setupLogging(level, stdout, stderr, logStream);
+ }
+ }
+
+ ClientSession session = (logStream == null) || GenericUtils.isEmpty(args)
+ ? null : setupClientSession(SCP_PORT_OPTION, stdin, stdout, stderr, args);
+ if (session == null) {
+ stderr.println("usage: scp [" + SCP_PORT_OPTION + " port] [-i identity]"
+ + " [-v[v][v]] [-E logoutput] [-r] [-p] [-q] [-o option=value]"
+ + " [-c cipherlist] [-m maclist] [-w password] [-C] <source> <target>");
+ stderr.println();
+ stderr.println("Where <source> or <target> are either 'user@host:file' or a local file path");
+ stderr.println("NOTE: exactly ONE of the source or target must be remote and the other one local");
+ System.exit(-1);
+ return; // not that we really need it...
+ }
+
+ try {
+ // see the way normalizeCommandArguments works...
+ Collection<Option> options = EnumSet.noneOf(Option.class);
+ boolean quiet = false;
+ for (int index = 0; index < numArgs; index++) {
+ String argName = args[index];
+ if ("-r".equals(argName)) {
+ options.add(Option.Recursive);
+ } else if ("-p".equals(argName)) {
+ options.add(Option.PreserveAttributes);
+ } else if ("-q".equals(argName)) {
+ quiet = true;
+ }
+ }
+
+ if (!quiet) {
+ session.setScpTransferEventListener(new ScpTransferEventListener() {
+ @Override
+ public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
+ logEvent("startFolderEvent", op, file, -1L, perms, null);
+ }
+
+ @Override
+ public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
+ logEvent("endFolderEvent", op, file, -1L, perms, thrown);
+ }
+
+ @Override
+ public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
+ logEvent("startFileEvent", op, file, length, perms, null);
+ }
+
+ @Override
+ public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
+ logEvent("endFileEvent", op, file, length, perms, thrown);
+ }
+
+ private void logEvent(String name, FileOperation op, Path file, long length, Collection<PosixFilePermission> perms, Throwable thrown) {
+ PrintStream ps = (thrown == null) ? stdout : stderr;
+ ps.append('\t').append(name).append('[').append(op.name()).append(']').append(' ').append(file.toString());
+ if (length > 0L) {
+ ps.append(' ').append("length=").append(Long.toString(length));
+ }
+ ps.append(' ').append(String.valueOf(perms));
+
+ if (thrown != null) {
+ ps.append(" - ").append(thrown.getClass().getSimpleName()).append(": ").append(thrown.getMessage());
+ }
+ ps.println();
+ }
+ });
+ }
+
+ ScpClient client = session.createScpClient();
+ ScpLocation source = new ScpLocation(args[numArgs - 2]);
+ ScpLocation target = new ScpLocation(args[numArgs - 1]);
+ if (source.isLocal()) {
+ client.upload(source.getPath(), target.getPath(), options);
+ } else {
+ client.download(source.getPath(), target.getPath(), options);
+ }
+ } finally {
+ session.close();
+ }
+ } finally {
+ if ((logStream != stdout) && (logStream != stderr)) {
+ logStream.close();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandExecutor.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandExecutor.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandExecutor.java
new file mode 100644
index 0000000..fb1a762
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandExecutor.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.PrintStream;
+
+import org.apache.sshd.common.NamedResource;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpCommandExecutor extends NamedResource {
+ // return value is whether to stop running
+ boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
new file mode 100644
index 0000000..1d982f2
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -0,0 +1,919 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.channels.Channel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.logging.Level;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.kex.KexProposalOption;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.StatVfsExtensionParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpCommandMain extends SshClientCliSupport implements Channel {
+ /**
+ * Command line option used to indicate a non-default port number
+ */
+ public static final String SFTP_PORT_OPTION = "-P";
+
+ private final SftpClient client;
+ private final Map<String, SftpCommandExecutor> commandsMap;
+ private String cwdRemote;
+ private String cwdLocal;
+
+ public SftpCommandMain(SftpClient client) {
+ this.client = Objects.requireNonNull(client, "No client");
+
+ Map<String, SftpCommandExecutor> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (SftpCommandExecutor e : Arrays.asList(
+ new ExitCommandExecutor(),
+ new PwdCommandExecutor(),
+ new InfoCommandExecutor(),
+ new SessionCommandExecutor(),
+ new VersionCommandExecutor(),
+ new CdCommandExecutor(),
+ new LcdCommandExecutor(),
+ new MkdirCommandExecutor(),
+ new LsCommandExecutor(),
+ new LStatCommandExecutor(),
+ new ReadLinkCommandExecutor(),
+ new RmCommandExecutor(),
+ new RmdirCommandExecutor(),
+ new RenameCommandExecutor(),
+ new StatVfsCommandExecutor(),
+ new GetCommandExecutor(),
+ new PutCommandExecutor(),
+ new HelpCommandExecutor()
+ )) {
+ String name = e.getName();
+ ValidateUtils.checkTrue(map.put(name, e) == null, "Multiple commands named '%s'", name);
+ }
+ commandsMap = Collections.unmodifiableMap(map);
+ cwdLocal = System.getProperty("user.dir");
+ }
+
+ public final SftpClient getClient() {
+ return client;
+ }
+
+ public void doInteractive(BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ SftpClient sftp = getClient();
+ setCurrentRemoteDirectory(sftp.canonicalPath("."));
+ while (true) {
+ stdout.append(getCurrentRemoteDirectory()).append(" > ").flush();
+ String line = stdin.readLine();
+ if (line == null) { // EOF
+ break;
+ }
+
+ line = GenericUtils.replaceWhitespaceAndTrim(line);
+ if (GenericUtils.isEmpty(line)) {
+ continue;
+ }
+
+ String cmd;
+ String args;
+ int pos = line.indexOf(' ');
+ if (pos > 0) {
+ cmd = line.substring(0, pos);
+ args = line.substring(pos + 1).trim();
+ } else {
+ cmd = line;
+ args = "";
+ }
+
+ SftpCommandExecutor exec = commandsMap.get(cmd);
+ try {
+ if (exec == null) {
+ stderr.append("Unknown command: ").println(line);
+ } else {
+ try {
+ if (exec.executeCommand(args, stdin, stdout, stderr)) {
+ break;
+ }
+ } catch (Exception e) {
+ stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
+ } finally {
+ stdout.flush();
+ }
+ }
+ } finally {
+ stderr.flush(); // just makings sure
+ }
+ }
+ }
+
+ protected String resolveLocalPath(String pathArg) {
+ String cwd = getCurrentLocalDirectory();
+ if (GenericUtils.isEmpty(pathArg)) {
+ return cwd;
+ }
+
+ if (OsUtils.isWin32()) {
+ if ((pathArg.length() >= 2) && (pathArg.charAt(1) == ':')) {
+ return pathArg;
+ }
+ } else {
+ if (pathArg.charAt(0) == '/') {
+ return pathArg;
+ }
+ }
+
+ return cwd + File.separator + pathArg.replace('/', File.separatorChar);
+ }
+
+ protected String resolveRemotePath(String pathArg) {
+ String cwd = getCurrentRemoteDirectory();
+ if (GenericUtils.isEmpty(pathArg)) {
+ return cwd;
+ }
+
+ if (pathArg.charAt(0) == '/') {
+ return pathArg;
+ } else {
+ return cwd + "/" + pathArg;
+ }
+ }
+
+ protected <A extends Appendable> A appendFileAttributes(A stdout, SftpClient sftp, String path, Attributes attrs) throws IOException {
+ stdout.append('\t').append(Long.toString(attrs.getSize()))
+ .append('\t').append(SftpFileSystemProvider.getRWXPermissions(attrs.getPermissions()));
+ if (attrs.isSymbolicLink()) {
+ String linkValue = sftp.readLink(path);
+ stdout.append(" => ")
+ .append('(').append(attrs.isDirectory() ? "dir" : "file").append(')')
+ .append(' ').append(linkValue);
+ }
+
+ return stdout;
+ }
+
+ public String getCurrentRemoteDirectory() {
+ return cwdRemote;
+ }
+
+ public void setCurrentRemoteDirectory(String path) {
+ cwdRemote = path;
+ }
+
+ public String getCurrentLocalDirectory() {
+ return cwdLocal;
+ }
+
+ public void setCurrentLocalDirectory(String path) {
+ cwdLocal = path;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return client.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ client.close();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static <A extends Appendable> A appendInfoValue(A sb, CharSequence name, Object value) throws IOException {
+ sb.append('\t').append(name).append(": ").append(Objects.toString(value));
+ return sb;
+ }
+
+ public static void main(String[] args) throws Exception {
+ PrintStream stdout = System.out;
+ PrintStream stderr = System.err;
+ OutputStream logStream = stderr;
+ try (BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
+ Level level = resolveLoggingVerbosity(args);
+ logStream = resolveLoggingTargetStream(stdout, stderr, args);
+ if (logStream != null) {
+ setupLogging(level, stdout, stderr, logStream);
+ }
+
+ ClientSession session = (logStream == null) ? null : setupClientSession(SFTP_PORT_OPTION, stdin, stdout, stderr, args);
+ if (session == null) {
+ System.err.println("usage: sftp [-v[v][v]] [-E logoutput] [-i identity]"
+ + " [-l login] [" + SFTP_PORT_OPTION + " port] [-o option=value]"
+ + " [-w password] [-c cipherlist] [-m maclist] [-C] hostname/user@host");
+ System.exit(-1);
+ return;
+ }
+
+ try {
+ // TODO allow command-line specification of SftpClientFactory
+ SftpClientFactory clientFactory = SftpClientFactory.instance();
+ try (SftpClient sftpClient = clientFactory.createSftpClient(session);
+ SftpCommandMain sftp = new SftpCommandMain(sftpClient)) {
+ // TODO allow injection of extra CommandExecutor(s) via command line and/or service loading
+ sftp.doInteractive(stdin, stdout, stderr);
+ }
+ } finally {
+ session.close();
+ }
+ } finally {
+ if ((logStream != stdout) && (logStream != stderr)) {
+ logStream.close();
+ }
+ }
+ }
+
+ private static class ExitCommandExecutor implements SftpCommandExecutor {
+ ExitCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "exit";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ stdout.println("Exiting");
+ return true;
+ }
+ }
+
+ private class PwdCommandExecutor implements SftpCommandExecutor {
+ protected PwdCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "pwd";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ stdout.append('\t').append("Remote: ").println(getCurrentRemoteDirectory());
+ stdout.append('\t').append("Local: ").println(getCurrentLocalDirectory());
+ return false;
+ }
+ }
+
+ private class SessionCommandExecutor implements SftpCommandExecutor {
+ SessionCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "session";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ SftpClient sftp = getClient();
+ ClientSession session = sftp.getSession();
+ appendInfoValue(stdout, "Session ID", BufferUtils.toHex(session.getSessionId())).println();
+ appendInfoValue(stdout, "Connect address", session.getConnectAddress()).println();
+
+ IoSession ioSession = session.getIoSession();
+ appendInfoValue(stdout, "Local address", ioSession.getLocalAddress()).println();
+ appendInfoValue(stdout, "Remote address", ioSession.getRemoteAddress()).println();
+
+ for (KexProposalOption option : KexProposalOption.VALUES) {
+ appendInfoValue(stdout, option.getDescription(), session.getNegotiatedKexParameter(option)).println();
+ }
+
+ return false;
+ }
+ }
+
+ private class InfoCommandExecutor implements SftpCommandExecutor {
+ InfoCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "info";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ SftpClient sftp = getClient();
+ Session session = sftp.getSession();
+ stdout.append('\t').println(session.getServerVersion());
+
+ Map<String, byte[]> extensions = sftp.getServerExtensions();
+ Map<String, ?> parsed = ParserUtils.parse(extensions);
+ if (GenericUtils.size(extensions) > 0) {
+ stdout.println();
+ }
+
+ extensions.forEach((name, value) -> {
+ Object info = parsed.get(name);
+
+ stdout.append('\t').append(name).append(": ");
+ if (info == null) {
+ stdout.println(BufferUtils.toHex(value));
+ } else {
+ stdout.println(info);
+ }
+ });
+
+ return false;
+ }
+ }
+
+ private class VersionCommandExecutor implements SftpCommandExecutor {
+ VersionCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "version";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ SftpClient sftp = getClient();
+ stdout.append('\t').println(sftp.getVersion());
+ return false;
+ }
+ }
+
+ private class CdCommandExecutor extends PwdCommandExecutor {
+ CdCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "cd";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
+
+ String newPath = resolveRemotePath(args);
+ SftpClient sftp = getClient();
+ setCurrentRemoteDirectory(sftp.canonicalPath(newPath));
+ return super.executeCommand("", stdin, stdout, stderr);
+ }
+ }
+
+ private class LcdCommandExecutor extends PwdCommandExecutor {
+ LcdCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "lcd";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ if (GenericUtils.isEmpty(args)) {
+ setCurrentLocalDirectory(System.getProperty("user.home"));
+ } else {
+ Path path = Paths.get(resolveLocalPath(args)).normalize().toAbsolutePath();
+ ValidateUtils.checkTrue(Files.exists(path), "No such local directory: %s", path);
+ ValidateUtils.checkTrue(Files.isDirectory(path), "Path is not a directory: %s", path);
+ setCurrentLocalDirectory(path.toString());
+ }
+
+ return super.executeCommand("", stdin, stdout, stderr);
+ }
+ }
+
+ private class MkdirCommandExecutor implements SftpCommandExecutor {
+ MkdirCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "mkdir";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
+
+ String path = resolveRemotePath(args);
+ SftpClient sftp = getClient();
+ sftp.mkdir(path);
+ return false;
+ }
+ }
+
+ private class LsCommandExecutor implements SftpCommandExecutor {
+ LsCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "ls";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ int numComps = GenericUtils.length(comps);
+ String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
+ String flags = (numComps >= 2) ? GenericUtils.trimToEmpty(comps[0]) : null;
+ // ignore all flags
+ if ((GenericUtils.length(pathArg) > 0) && (pathArg.charAt(0) == '-')) {
+ flags = pathArg;
+ pathArg = null;
+ }
+
+ String path = resolveRemotePath(pathArg);
+ SftpClient sftp = getClient();
+ int version = sftp.getVersion();
+ boolean showLongName = (version == SftpConstants.SFTP_V3) && (GenericUtils.length(flags) > 1) && (flags.indexOf('l') > 0);
+ for (SftpClient.DirEntry entry : sftp.readDir(path)) {
+ String fileName = entry.getFilename();
+ SftpClient.Attributes attrs = entry.getAttributes();
+ appendFileAttributes(stdout.append('\t').append(fileName), sftp, path + "/" + fileName, attrs).println();
+ if (showLongName) {
+ stdout.append("\t\tlong-name: ").println(entry.getLongFilename());
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private class RmCommandExecutor implements SftpCommandExecutor {
+ RmCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "rm";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ int numArgs = GenericUtils.length(comps);
+ ValidateUtils.checkTrue(numArgs >= 1, "No arguments");
+ ValidateUtils.checkTrue(numArgs <= 2, "Too many arguments: %s", args);
+
+ String remotePath = comps[0];
+ boolean recursive = false;
+ boolean verbose = false;
+ if (remotePath.charAt(0) == '-') {
+ ValidateUtils.checkTrue(remotePath.length() > 1, "Missing flags specification: %s", args);
+ ValidateUtils.checkTrue(numArgs == 2, "Missing remote directory: %s", args);
+
+ for (int index = 1; index < remotePath.length(); index++) {
+ char ch = remotePath.charAt(index);
+ switch(ch) {
+ case 'r' :
+ recursive = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown flag (" + String.valueOf(ch) + ")");
+ }
+ }
+ remotePath = comps[1];
+ }
+
+ String path = resolveRemotePath(remotePath);
+ SftpClient sftp = getClient();
+ if (recursive) {
+ Attributes attrs = sftp.stat(path);
+ ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path not a directory: %s", args);
+ removeRecursive(sftp, path, attrs, stdout, verbose);
+ } else {
+ sftp.remove(path);
+ if (verbose) {
+ stdout.append('\t').append("Removed ").println(path);
+ }
+ }
+
+ return false;
+ }
+
+ private void removeRecursive(SftpClient sftp, String path, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
+ if (attrs.isDirectory()) {
+ for (DirEntry entry : sftp.readDir(path)) {
+ String name = entry.getFilename();
+ if (".".equals(name) || "..".equals(name)) {
+ continue;
+ }
+
+ removeRecursive(sftp, path + "/" + name, entry.getAttributes(), stdout, verbose);
+ }
+
+ sftp.rmdir(path);
+ } else if (attrs.isRegularFile()) {
+ sftp.remove(path);
+ } else {
+ if (verbose) {
+ stdout.append('\t').append("Skip special file ").println(path);
+ return;
+ }
+ }
+
+ if (verbose) {
+ stdout.append('\t').append("Removed ").println(path);
+ }
+ }
+ }
+
+ private class RmdirCommandExecutor implements SftpCommandExecutor {
+ RmdirCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "rmdir";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
+
+ String path = resolveRemotePath(args);
+ SftpClient sftp = getClient();
+ sftp.rmdir(path);
+ return false;
+ }
+ }
+
+ private class RenameCommandExecutor implements SftpCommandExecutor {
+ RenameCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "rename";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ ValidateUtils.checkTrue(GenericUtils.length(comps) == 2, "Invalid number of arguments: %s", args);
+
+ String oldPath = resolveRemotePath(GenericUtils.trimToEmpty(comps[0]));
+ String newPath = resolveRemotePath(GenericUtils.trimToEmpty(comps[1]));
+ SftpClient sftp = getClient();
+ sftp.rename(oldPath, newPath);
+ return false;
+ }
+ }
+
+ private class StatVfsCommandExecutor implements SftpCommandExecutor {
+ StatVfsCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return StatVfsExtensionParser.NAME;
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ int numArgs = GenericUtils.length(comps);
+ ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
+
+ SftpClient sftp = getClient();
+ OpenSSHStatPathExtension ext = sftp.getExtension(OpenSSHStatPathExtension.class);
+ ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName());
+
+ String remPath = resolveRemotePath((numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
+ OpenSSHStatExtensionInfo info = ext.stat(remPath);
+ Field[] fields = info.getClass().getFields();
+ for (Field f : fields) {
+ String name = f.getName();
+ int mod = f.getModifiers();
+ if (Modifier.isStatic(mod)) {
+ continue;
+ }
+
+ Object value = f.get(info);
+ stdout.append('\t').append(name).append(": ").println(value);
+ }
+
+ return false;
+ }
+ }
+
+ private class LStatCommandExecutor implements SftpCommandExecutor {
+ LStatCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "lstat";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
+
+ String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
+ SftpClient client = getClient();
+ Attributes attrs = client.lstat(path);
+ appendFileAttributes(stdout, client, path, attrs).println();
+ return false;
+ }
+ }
+
+ private class ReadLinkCommandExecutor implements SftpCommandExecutor {
+ ReadLinkCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "readlink";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
+
+ String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
+ SftpClient client = getClient();
+ String linkData = client.readLink(path);
+ stdout.append('\t').println(linkData);
+ return false;
+ }
+ }
+
+ private class HelpCommandExecutor implements SftpCommandExecutor {
+ HelpCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "help";
+ }
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+ for (String cmd : commandsMap.keySet()) {
+ stdout.append('\t').println(cmd);
+ }
+ return false;
+ }
+ }
+
+ private abstract class TransferCommandExecutor implements SftpCommandExecutor {
+ protected TransferCommandExecutor() {
+ super();
+ }
+
+ protected void createDirectories(SftpClient sftp, String remotePath) throws IOException {
+ try {
+ Attributes attrs = sftp.stat(remotePath);
+ ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path already exists but is not a directory: %s", remotePath);
+ return;
+ } catch (SftpException e) {
+ int status = e.getStatus();
+ ValidateUtils.checkTrue(status == SftpConstants.SSH_FX_NO_SUCH_FILE, "Failed to get status of %s: %s", remotePath, e.getMessage());
+ }
+
+ int pos = remotePath.lastIndexOf('/');
+ ValidateUtils.checkTrue(pos > 0, "No more parents for %s", remotePath);
+ createDirectories(sftp, remotePath.substring(0, pos));
+ }
+
+ protected void transferFile(SftpClient sftp, Path localPath, String remotePath, boolean upload, PrintStream stdout, boolean verbose) throws IOException {
+ // Create the file's hierarchy
+ if (upload) {
+ int pos = remotePath.lastIndexOf('/');
+ ValidateUtils.checkTrue(pos > 0, "Missing full remote file path: %s", remotePath);
+ createDirectories(sftp, remotePath.substring(0, pos));
+ } else {
+ Files.createDirectories(localPath.getParent());
+ }
+
+ try (InputStream input = upload ? Files.newInputStream(localPath) : sftp.read(remotePath);
+ OutputStream output = upload ? sftp.write(remotePath) : Files.newOutputStream(localPath)) {
+ IoUtils.copy(input, output, SftpClient.IO_BUFFER_SIZE);
+ }
+
+ if (verbose) {
+ stdout.append('\t')
+ .append("Copied ").append(upload ? localPath.toString() : remotePath)
+ .append(" to ").println(upload ? remotePath : localPath.toString());
+ }
+ }
+
+ protected void transferRemoteDir(SftpClient sftp, Path localPath, String remotePath, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
+ if (attrs.isDirectory()) {
+ for (DirEntry entry : sftp.readDir(remotePath)) {
+ String name = entry.getFilename();
+ if (".".equals(name) || "..".equals(name)) {
+ continue;
+ }
+
+ transferRemoteDir(sftp, localPath.resolve(name), remotePath + "/" + name, entry.getAttributes(), stdout, verbose);
+ }
+ } else if (attrs.isRegularFile()) {
+ transferFile(sftp, localPath, remotePath, false, stdout, verbose);
+ } else {
+ if (verbose) {
+ stdout.append('\t').append("Skip remote special file ").println(remotePath);
+ }
+ }
+ }
+
+ protected void transferLocalDir(SftpClient sftp, Path localPath, String remotePath, PrintStream stdout, boolean verbose) throws IOException {
+ if (Files.isDirectory(localPath)) {
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(localPath)) {
+ for (Path entry : ds) {
+ String name = entry.getFileName().toString();
+ transferLocalDir(sftp, localPath.resolve(name), remotePath + "/" + name, stdout, verbose);
+ }
+ }
+ } else if (Files.isRegularFile(localPath)) {
+ transferFile(sftp, localPath, remotePath, true, stdout, verbose);
+ } else {
+ if (verbose) {
+ stdout.append('\t').append("Skip local special file ").println(localPath);
+ }
+ }
+ }
+
+ protected void executeCommand(String args, boolean upload, PrintStream stdout) throws IOException {
+ String[] comps = GenericUtils.split(args, ' ');
+ int numArgs = GenericUtils.length(comps);
+ ValidateUtils.checkTrue((numArgs >= 1) && (numArgs <= 3), "Invalid number of arguments: %s", args);
+
+ String src = comps[0];
+ boolean recursive = false;
+ boolean verbose = false;
+ int tgtIndex = 1;
+ if (src.charAt(0) == '-') {
+ ValidateUtils.checkTrue(src.length() > 1, "Missing flags specification: %s", args);
+ ValidateUtils.checkTrue(numArgs >= 2, "Missing source specification: %s", args);
+
+ for (int index = 1; index < src.length(); index++) {
+ char ch = src.charAt(index);
+ switch(ch) {
+ case 'r' :
+ recursive = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown flag (" + String.valueOf(ch) + ")");
+ }
+ }
+ src = comps[1];
+ tgtIndex++;
+ }
+
+ String tgt = (tgtIndex < numArgs) ? comps[tgtIndex] : null;
+ String localPath;
+ String remotePath;
+ if (upload) {
+ localPath = src;
+ remotePath = ValidateUtils.checkNotNullAndNotEmpty(tgt, "No remote target specified: %s", args);
+ } else {
+ localPath = GenericUtils.isEmpty(tgt) ? getCurrentLocalDirectory() : tgt;
+ remotePath = src;
+ }
+
+ SftpClient sftp = getClient();
+ Path local = Paths.get(resolveLocalPath(localPath)).normalize().toAbsolutePath();
+ String remote = resolveRemotePath(remotePath);
+ if (recursive) {
+ if (upload) {
+ ValidateUtils.checkTrue(Files.isDirectory(local), "Local path not a directory or does not exist: %s", local);
+ transferLocalDir(sftp, local, remote, stdout, verbose);
+ } else {
+ Attributes attrs = sftp.stat(remote);
+ ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path not a directory: %s", remote);
+ transferRemoteDir(sftp, local, remote, attrs, stdout, verbose);
+ }
+ } else {
+ if (Files.exists(local) && Files.isDirectory(local)) {
+ int pos = remote.lastIndexOf('/');
+ String name = (pos >= 0) ? remote.substring(pos + 1) : remote;
+ local = local.resolve(name);
+ }
+
+ transferFile(sftp, local, remote, upload, stdout, verbose);
+ }
+ }
+ }
+
+ private class GetCommandExecutor extends TransferCommandExecutor {
+ GetCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "get";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ executeCommand(args, false, stdout);
+ return false;
+ }
+ }
+
+ private class PutCommandExecutor extends TransferCommandExecutor {
+ PutCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "put";
+ }
+
+ @Override
+ public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ executeCommand(args, true, stdout);
+ return false;
+ }
+ }
+}
[4/6] mina-sshd git commit: [SSHD-816] Moved all 'main' code for
client commands to sshd-cli module
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 0874b93..1d034c1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -18,44 +18,24 @@
*/
package org.apache.sshd.client;
-import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.PrintWriter;
import java.io.StreamCorruptedException;
-import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
-import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Formatter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.Logger;
import org.apache.sshd.agent.SshAgentFactory;
import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
@@ -65,18 +45,12 @@ import org.apache.sshd.client.auth.keyboard.UserInteraction;
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
-import org.apache.sshd.client.channel.ChannelShell;
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
-import org.apache.sshd.client.config.keys.ClientIdentity;
import org.apache.sshd.client.config.keys.ClientIdentityLoader;
import org.apache.sshd.client.config.keys.DefaultClientIdentitiesWatcher;
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.future.DefaultConnectFuture;
-import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
-import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.AbstractClientSession;
import org.apache.sshd.client.session.ClientConnectionServiceFactory;
@@ -89,40 +63,22 @@ import org.apache.sshd.client.simple.AbstractSimpleClientSessionCreator;
import org.apache.sshd.client.simple.SimpleClient;
import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.Factory;
-import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.ServiceFactory;
import org.apache.sshd.common.channel.Channel;
-import org.apache.sshd.common.cipher.BuiltinCiphers;
-import org.apache.sshd.common.cipher.Cipher;
-import org.apache.sshd.common.compression.BuiltinCompressions;
-import org.apache.sshd.common.compression.Compression;
-import org.apache.sshd.common.config.CompressionConfigValue;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.config.keys.BuiltinIdentities;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.helpers.AbstractFactoryManager;
import org.apache.sshd.common.io.IoConnectFuture;
import org.apache.sshd.common.io.IoConnector;
import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.mac.BuiltinMacs;
-import org.apache.sshd.common.mac.Mac;
import org.apache.sshd.common.scp.ScpFileOpener;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
import org.apache.sshd.common.util.net.SshdSocketAddress;
/**
@@ -180,11 +136,6 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
public static final Factory<SshClient> DEFAULT_SSH_CLIENT_FACTORY = SshClient::new;
/**
- * Command line option used to indicate non-default target port
- */
- public static final String SSH_CLIENT_PORT_OPTION = "-p";
-
- /**
* Default user authentication preferences if not set
* @see <A HREF="http://linux.die.net/man/5/ssh_config">ssh_config(5) - PreferredAuthentications</A>
*/
@@ -821,695 +772,4 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
public static SshClient setUpDefaultClient() {
return ClientBuilder.builder().build();
}
-
- /*=================================
- Main class implementation
- *=================================*/
-
- public static boolean showError(PrintStream stderr, String message) {
- stderr.println(message);
- return true;
- }
-
- public static boolean isArgumentedOption(String portOption, String argName) {
- return portOption.equals(argName)
- || "-i".equals(argName)
- || "-o".equals(argName)
- || "-l".equals(argName)
- || "-w".equals(argName)
- || "-c".equals(argName)
- || "-m".equals(argName)
- || "-E".equals(argName);
- }
-
- // NOTE: ClientSession#getFactoryManager is the SshClient
- public static ClientSession setupClientSession(
- String portOption, BufferedReader stdin, PrintStream stdout, PrintStream stderr, String... args)
- throws Exception {
-
- int port = -1;
- String host = null;
- String login = null;
- String password = null;
- boolean error = false;
- List<Path> identities = new ArrayList<>();
- Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- List<NamedFactory<Cipher>> ciphers = null;
- List<NamedFactory<Mac>> macs = null;
- List<NamedFactory<Compression>> compressions = null;
- int numArgs = GenericUtils.length(args);
- for (int i = 0; (!error) && (i < numArgs); i++) {
- String argName = args[i];
- String argVal = null;
- if (isArgumentedOption(portOption, argName)) {
- if ((i + 1) >= numArgs) {
- error = showError(stderr, "option requires an argument: " + argName);
- break;
- }
-
- argVal = args[++i];
- }
-
- if (portOption.equals(argName)) {
- if (port > 0) {
- error = showError(stderr, argName + " option value re-specified: " + port);
- break;
- }
-
- port = Integer.parseInt(argVal);
- if (port <= 0) {
- error = showError(stderr, "Bad option value for " + argName + ": " + port);
- break;
- }
- } else if ("-w".equals(argName)) {
- if (GenericUtils.length(password) > 0) {
- error = showError(stderr, argName + " option value re-specified: " + password);
- break;
- }
- password = argVal;
- } else if ("-c".equals(argName)) {
- ciphers = setupCiphers(argName, argVal, ciphers, stderr);
- if (GenericUtils.isEmpty(ciphers)) {
- error = true;
- break;
- }
- } else if ("-m".equals(argName)) {
- macs = setupMacs(argName, argVal, macs, stderr);
- if (GenericUtils.isEmpty(macs)) {
- error = true;
- break;
- }
- } else if ("-i".equals(argName)) {
- identities.add(resolveIdentityFile(argVal));
- } else if ("-C".equals(argName)) {
- compressions = setupCompressions(argName,
- GenericUtils.join(
- Arrays.asList(
- BuiltinCompressions.Constants.ZLIB, BuiltinCompressions.Constants.DELAYED_ZLIB), ','),
- compressions, stderr);
- if (GenericUtils.isEmpty(compressions)) {
- error = true;
- break;
- }
- } else if ("-o".equals(argName)) {
- String opt = argVal;
- int idx = opt.indexOf('=');
- if (idx <= 0) {
- error = showError(stderr, "bad syntax for option: " + opt);
- break;
- }
-
- String optName = opt.substring(0, idx);
- String optValue = opt.substring(idx + 1);
- if (HostConfigEntry.IDENTITY_FILE_CONFIG_PROP.equals(optName)) {
- identities.add(resolveIdentityFile(optValue));
- } else {
- options.put(optName, optValue);
- }
- } else if ("-l".equals(argName)) {
- if (login != null) {
- error = showError(stderr, argName + " option value re-specified: " + port);
- break;
- }
-
- login = argVal;
- } else if (argName.charAt(0) != '-') {
- if (host != null) { // assume part of a command following it
- break;
- }
-
- host = argName;
- int pos = host.indexOf('@'); // check if user@host
- if (pos > 0) {
- if (login == null) {
- login = host.substring(0, pos);
- host = host.substring(pos + 1);
- } else {
- error = showError(stderr, "Login already specified using -l option (" + login + "): " + host);
- break;
- }
- }
- }
- }
-
- if ((!error) && GenericUtils.isEmpty(host)) {
- error = showError(stderr, "Hostname not specified");
- }
-
- if (error) {
- return null;
- }
-
- SshClient client = setupClient(options, ciphers, macs, compressions, identities, stdin, stdout, stderr);
- if (client == null) {
- return null;
- }
-
- try {
- client.start();
-
- if (login == null) {
- login = OsUtils.getCurrentUser();
- }
-
- if (port <= 0) {
- port = SshConfigFileReader.DEFAULT_PORT;
- }
-
- // TODO use a configurable wait time
- ClientSession session = client.connect(login, host, port).verify().getSession();
- try {
- if (GenericUtils.length(password) > 0) {
- session.addPasswordIdentity(password);
- }
- session.auth().verify(FactoryManager.DEFAULT_AUTH_TIMEOUT); // TODO use a configurable wait time
- return session;
- } catch (Exception e) {
- session.close(true);
- throw e;
- }
- } catch (Exception e) {
- client.close();
- throw e;
- }
- }
-
- public static Path resolveIdentityFile(String id) throws IOException {
- BuiltinIdentities identity = BuiltinIdentities.fromName(id);
- if (identity != null) {
- String fileName = ClientIdentity.getIdentityFileName(identity.getName());
- Path keysFolder = PublicKeyEntry.getDefaultKeysFolderPath();
- return keysFolder.resolve(fileName);
- } else {
- return Paths.get(id);
- }
- }
-
- // returns null if error encountered
- public static SshClient setupClient(
- Map<String, Object> options,
- List<NamedFactory<Cipher>> ciphers,
- List<NamedFactory<Mac>> macs,
- List<NamedFactory<Compression>> compressions,
- Collection<? extends Path> identities,
- BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
- if (GenericUtils.isEmpty(ciphers)) {
- ciphers = setupCiphers(resolver, stderr);
- if (ciphers == null) {
- return null;
- }
- }
-
- if (GenericUtils.isEmpty(macs)) {
- macs = setupMacs(resolver, stderr);
- if (macs == null) {
- return null;
- }
- }
-
- if (GenericUtils.isEmpty(compressions)) {
- compressions = setupCompressions(resolver, stderr);
- if (compressions == null) {
- return null;
- }
- }
-
- SshClient client = SshClient.setUpDefaultClient();
- try {
- if (GenericUtils.size(ciphers) > 0) {
- client.setCipherFactories(ciphers);
- }
-
- if (GenericUtils.size(macs) > 0) {
- client.setMacFactories(macs);
- }
-
- if (GenericUtils.size(compressions) > 0) {
- client.setCompressionFactories(compressions);
- }
-
- try {
- setupSessionIdentities(client, identities, stdin, stdout, stderr);
- } catch (Throwable t) { // show but do not fail the setup - maybe a password can be used
- showError(stderr, t.getClass().getSimpleName() + " while loading user keys: " + t.getMessage());
- }
-
- setupServerKeyVerifier(client, options, stdin, stdout, stderr);
- setupSessionUserInteraction(client, stdin, stdout, stderr);
-
- Map<String, Object> props = client.getProperties();
- props.putAll(options);
- return client;
- } catch (Throwable t) {
- showError(stderr, "Failed (" + t.getClass().getSimpleName() + ") to setup client: " + t.getMessage());
- client.close();
- return null;
- }
- }
-
- public static FileKeyPairProvider setupSessionIdentities(ClientFactoryManager client, Collection<? extends Path> identities,
- BufferedReader stdin, PrintStream stdout, PrintStream stderr)
- throws Throwable {
- client.setFilePasswordProvider(file -> {
- stdout.print("Enter password for private key file=" + file + ": ");
- return stdin.readLine();
- });
-
- if (GenericUtils.isEmpty(identities)) {
- return null;
- }
-
- FileKeyPairProvider provider = new FileKeyPairProvider() {
- @Override
- public String toString() {
- return FileKeyPairProvider.class.getSimpleName() + "[clientIdentitiesProvider]";
- }
- };
- provider.setPaths(identities);
- client.setKeyPairProvider(provider);
- return provider;
- }
-
- public static UserInteraction setupSessionUserInteraction(
- ClientAuthenticationManager client, BufferedReader stdin, PrintStream stdout, PrintStream stderr) {
- UserInteraction ui = new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public void serverVersionInfo(ClientSession session, List<String> lines) {
- for (String l : lines) {
- stdout.append('\t').println(l);
- }
- }
-
- @Override
- public void welcome(ClientSession clientSession, String banner, String lang) {
- stdout.println(banner);
- }
-
- @Override
- public String[] interactive(ClientSession clientSession, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
- int numPropmts = GenericUtils.length(prompt);
- String[] answers = new String[numPropmts];
- try {
- for (int i = 0; i < numPropmts; i++) {
- stdout.append(prompt[i]).print(" ");
- answers[i] = stdin.readLine();
- }
- } catch (IOException e) {
- stderr.append(e.getClass().getSimpleName()).append(" while read prompts: ").println(e.getMessage());
- }
- return answers;
- }
-
- @Override
- public String getUpdatedPassword(ClientSession clientSession, String prompt, String lang) {
- stdout.append(prompt).print(" ");
- try {
- return stdin.readLine();
- } catch (IOException e) {
- stderr.append(e.getClass().getSimpleName()).append(" while read password: ").println(e.getMessage());
- return null;
- }
- }
- };
- client.setUserInteraction(ui);
- return ui;
- }
-
- public static ServerKeyVerifier setupServerKeyVerifier(
- ClientAuthenticationManager manager, Map<String, ?> options, BufferedReader stdin, PrintStream stdout, PrintStream stderr) {
- ServerKeyVerifier current = manager.getServerKeyVerifier();
- if (current == null) {
- current = ClientBuilder.DEFAULT_SERVER_KEY_VERIFIER;
- manager.setServerKeyVerifier(current);
- }
-
- String strictValue = Objects.toString(options.remove(KnownHostsServerKeyVerifier.STRICT_CHECKING_OPTION), "true");
- if (!SshConfigFileReader.parseBooleanValue(strictValue)) {
- return current;
- }
-
- String filePath = Objects.toString(options.remove(KnownHostsServerKeyVerifier.KNOWN_HOSTS_FILE_OPTION), null);
- if (GenericUtils.isEmpty(filePath)) {
- current = new DefaultKnownHostsServerKeyVerifier(current);
- } else { // if user specifies a different location than default be lenient
- current = new DefaultKnownHostsServerKeyVerifier(current, false, Paths.get(filePath));
- }
-
- ((KnownHostsServerKeyVerifier) current).setModifiedServerKeyAcceptor((clientSession, remoteAddress, entry, expected, actual) -> {
- stderr.append("Mismatched keys presented by ").append(Objects.toString(remoteAddress))
- .append(" for entry=").println(entry);
- stderr.append('\t').append("Expected=").append(KeyUtils.getKeyType(expected))
- .append('-').println(KeyUtils.getFingerPrint(expected));
- stderr.append('\t').append("Actual=").append(KeyUtils.getKeyType(actual))
- .append('-').println(KeyUtils.getFingerPrint(actual));
- stderr.flush(); // just making sure
-
- stdout.append("Accept key and update known hosts: y/[N]");
- stdout.flush(); // just making sure
-
- String ans = GenericUtils.trimToEmpty(stdin.readLine());
- return (GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) == 'y');
- });
-
- manager.setServerKeyVerifier(current);
- return current;
- }
-
- public static Level resolveLoggingVerbosity(String... args) {
- return resolveLoggingVerbosity(args, GenericUtils.length(args));
- }
-
- public static Level resolveLoggingVerbosity(String[] args, int maxIndex) {
- for (int index = 0; index < maxIndex; index++) {
- String argName = args[index];
- if ("-v".equals(argName)) {
- return Level.INFO;
- } else if ("-vv".equals(argName)) {
- return Level.FINE;
- } else if ("-vvv".equals(argName)) {
- return Level.FINEST;
- }
- }
-
- return Level.WARNING;
- }
-
- public static OutputStream resolveLoggingTargetStream(PrintStream stdout, PrintStream stderr, String... args) {
- return resolveLoggingTargetStream(stdout, stderr, args, GenericUtils.length(args));
- }
-
- public static OutputStream resolveLoggingTargetStream(PrintStream stdout, PrintStream stderr, String[] args, int maxIndex) {
- for (int index = 0; index < maxIndex; index++) {
- String argName = args[index];
- if ("-E".equals(argName)) {
- if ((index + 1) >= maxIndex) {
- showError(stderr, "Missing " + argName + " option argument");
- return null;
- }
-
- String argVal = args[index + 1];
- if ("--".equals(argVal)) {
- return stdout;
- }
-
- try {
- Path path = Paths.get(argVal).normalize().toAbsolutePath();
- return Files.newOutputStream(path);
- } catch (IOException e) {
- showError(stderr, "Failed (" + e.getClass().getSimpleName() + ") to open " + argVal + ": " + e.getMessage());
- return null;
- }
- }
- }
-
- return stderr;
- }
-
- public static List<NamedFactory<Compression>> setupCompressions(PropertyResolver options, PrintStream stderr) {
- String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.COMPRESSION_PROP);
- if (GenericUtils.isEmpty(argVal)) {
- return Collections.emptyList();
- }
-
- NamedFactory<Compression> value = CompressionConfigValue.fromName(argVal);
- if (value == null) {
- showError(stderr, "Unknown compression configuration value: " + argVal);
- return null;
- }
-
- return Collections.singletonList(value);
- }
-
- public static List<NamedFactory<Compression>> setupCompressions(
- String argName, String argVal, List<NamedFactory<Compression>> current, PrintStream stderr) {
- if (GenericUtils.size(current) > 0) {
- showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
- return null;
- }
-
- BuiltinCompressions.ParseResult result = BuiltinCompressions.parseCompressionsList(argVal);
- Collection<? extends NamedFactory<Compression>> available = result.getParsedFactories();
- if (GenericUtils.isEmpty(available)) {
- showError(stderr, "No known compressions in " + argVal);
- return null;
- }
-
- Collection<String> unsupported = result.getUnsupportedFactories();
- if (GenericUtils.size(unsupported) > 0) {
- stderr.append("Ignored unsupported compressions: ").println(GenericUtils.join(unsupported, ','));
- }
-
- return new ArrayList<>(available);
- }
-
- public static List<NamedFactory<Mac>> setupMacs(PropertyResolver options, PrintStream stderr) {
- String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.MACS_CONFIG_PROP);
- return GenericUtils.isEmpty(argVal)
- ? Collections.emptyList()
- : setupMacs(SshConfigFileReader.MACS_CONFIG_PROP, argVal, null, stderr);
- }
-
- public static List<NamedFactory<Mac>> setupMacs(String argName, String argVal, List<NamedFactory<Mac>> current, PrintStream stderr) {
- if (GenericUtils.size(current) > 0) {
- showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
- return null;
- }
-
- BuiltinMacs.ParseResult result = BuiltinMacs.parseMacsList(argVal);
- Collection<? extends NamedFactory<Mac>> available = result.getParsedFactories();
- if (GenericUtils.isEmpty(available)) {
- showError(stderr, "No known MACs in " + argVal);
- return null;
- }
-
- Collection<String> unsupported = result.getUnsupportedFactories();
- if (GenericUtils.size(unsupported) > 0) {
- stderr.append("Ignored unsupported MACs: ").println(GenericUtils.join(unsupported, ','));
- }
-
- return new ArrayList<>(available);
- }
-
- public static List<NamedFactory<Cipher>> setupCiphers(PropertyResolver options, PrintStream stderr) {
- String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.CIPHERS_CONFIG_PROP);
- return GenericUtils.isEmpty(argVal)
- ? Collections.emptyList()
- : setupCiphers(SshConfigFileReader.CIPHERS_CONFIG_PROP, argVal, null, stderr);
- }
-
- // returns null - e.g., re-specified or no supported cipher found
- public static List<NamedFactory<Cipher>> setupCiphers(String argName, String argVal, List<NamedFactory<Cipher>> current, PrintStream stderr) {
- if (GenericUtils.size(current) > 0) {
- showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
- return null;
- }
-
- BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(argVal);
- Collection<? extends NamedFactory<Cipher>> available = result.getParsedFactories();
- if (GenericUtils.isEmpty(available)) {
- showError(stderr, "No known ciphers in " + argVal);
- return null;
- }
-
- Collection<String> unsupported = result.getUnsupportedFactories();
- if (GenericUtils.size(unsupported) > 0) {
- stderr.append("Ignored unsupported ciphers: ").println(GenericUtils.join(unsupported, ','));
- }
-
- return new ArrayList<>(available);
- }
-
- public static Handler setupLogging(Level level, final PrintStream stdout, final PrintStream stderr, final OutputStream outputStream) {
- Handler fh = new ConsoleHandler() {
- {
- setOutputStream(outputStream); // override the default (stderr)
- }
-
- @Override
- protected synchronized void setOutputStream(OutputStream out) throws SecurityException {
- if ((out == stdout) || (out == stderr)) {
- super.setOutputStream(new NoCloseOutputStream(out));
- } else {
- super.setOutputStream(out);
- }
- }
- };
- fh.setLevel(Level.FINEST);
- fh.setFormatter(new Formatter() {
- @Override
- public String format(LogRecord record) {
- String message = formatMessage(record);
- String throwable = "";
- Throwable t = record.getThrown();
- if (t != null) {
- StringWriter sw = new StringWriter();
- try (PrintWriter pw = new PrintWriter(sw)) {
- pw.println();
- t.printStackTrace(pw); // NOPMD
- }
- throwable = sw.toString();
- }
- return String.format("%1$tY-%1$tm-%1$td: %2$-7.7s: %3$-32.32s: %4$s%5$s%n",
- new Date(record.getMillis()), record.getLevel().getName(),
- record.getLoggerName(), message, throwable);
- }
- });
-
- Logger root = Logger.getLogger("");
- for (Handler handler : root.getHandlers()) {
- root.removeHandler(handler);
- }
- root.addHandler(fh);
- root.setLevel(level);
- return fh;
- }
-
- //////////////////////////////////////////////////////////////////////////
-
- public static void main(String[] args) throws Exception {
- PrintStream stdout = System.out;
- PrintStream stderr = System.err;
- boolean agentForward = false;
- List<String> command = null;
- int socksPort = -1;
- int numArgs = GenericUtils.length(args);
- boolean error = false;
- String target = null;
- Level level = Level.WARNING;
- OutputStream logStream = stderr;
- for (int i = 0; i < numArgs; i++) {
- String argName = args[i];
- // handled by 'setupClientSession'
- if (GenericUtils.isEmpty(command) && isArgumentedOption("-p", argName)) {
- if ((i + 1) >= numArgs) {
- error = showError(stderr, "option requires an argument: " + argName);
- break;
- }
-
- i++;
- continue;
- }
-
- // verbosity handled separately
- if (GenericUtils.isEmpty(command) && ("-v".equals(argName) || "-vv".equals(argName) || "-vvv".equals(argName))) {
- continue;
- }
-
- if (GenericUtils.isEmpty(command) && "-D".equals(argName)) {
- if ((i + 1) >= numArgs) {
- error = showError(stderr, "option requires an argument: " + argName);
- break;
- }
- if (socksPort > 0) {
- error = showError(stderr, argName + " option value re-specified: " + socksPort);
- break;
- }
-
- socksPort = Integer.parseInt(args[++i]);
- if (socksPort <= 0) {
- error = showError(stderr, "Bad option value for " + argName + ": " + socksPort);
- break;
- }
- } else if (GenericUtils.isEmpty(command) && "-A".equals(argName)) {
- agentForward = true;
- } else if (GenericUtils.isEmpty(command) && "-a".equals(argName)) {
- agentForward = false;
- } else {
- level = resolveLoggingVerbosity(args, i);
- logStream = resolveLoggingTargetStream(stdout, stderr, args, i);
- if (logStream == null) {
- error = true;
- break;
- }
- if (GenericUtils.isEmpty(command) && target == null) {
- target = argName;
- } else {
- if (command == null) {
- command = new ArrayList<>();
- }
- command.add(argName);
- }
- }
- }
-
- ClientSession session = null;
- try (BufferedReader stdin = new BufferedReader(
- new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
- if (!error) {
- setupLogging(level, stdout, stderr, logStream);
-
- session = setupClientSession(SSH_CLIENT_PORT_OPTION, stdin, stdout, stderr, args);
- if (session == null) {
- error = true;
- }
- }
-
- if (error) {
- System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-E logoutputfile] [-D socksPort]"
- + " [-l login] [" + SSH_CLIENT_PORT_OPTION + " port] [-o option=value]"
- + " [-w password] [-c cipherslist] [-m maclist] [-C]"
- + " hostname/user@host [command]");
- System.exit(-1);
- return;
- }
-
- try (SshClient client = (SshClient) session.getFactoryManager()) {
- /*
- String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
- if (authSock == null && provider != null) {
- Iterable<KeyPair> keys = provider.loadKeys();
- AgentServer server = new AgentServer();
- authSock = server.start();
- SshAgent agent = new AgentClient(authSock);
- for (KeyPair key : keys) {
- agent.addIdentity(key, "");
- }
- agent.close();
- props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
- }
- */
-
- try {
- if (socksPort >= 0) {
- session.startDynamicPortForwarding(new SshdSocketAddress(SshdSocketAddress.LOCALHOST_NAME, socksPort));
- Thread.sleep(Long.MAX_VALUE);
- } else {
- ClientChannel channel;
- if (GenericUtils.isEmpty(command)) {
- channel = session.createShellChannel();
- ((ChannelShell) channel).setAgentForwarding(agentForward);
- channel.setIn(new NoCloseInputStream(System.in));
- } else {
- channel = session.createExecChannel(String.join(" ", command).trim());
- }
-
- try (OutputStream channelOut = new NoCloseOutputStream(System.out);
- OutputStream channelErr = new NoCloseOutputStream(System.err)) {
- channel.setOut(channelOut);
- channel.setErr(channelErr);
- channel.open().await(); // TODO use verify and a configurable timeout
- channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
- } finally {
- channel.close();
- }
- session.close(false);
- }
- } finally {
- client.stop();
- }
- } finally {
- session.close();
- }
- } finally {
- if (logStream != null && logStream != stdout && logStream != stderr) {
- logStream.close();
- }
- }
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java b/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
deleted file mode 100644
index 6aa7dbb..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshKeyScan.java
+++ /dev/null
@@ -1,740 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.InterruptedIOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintStream;
-import java.io.Writer;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.channels.Channel;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.Level;
-
-import org.apache.sshd.client.auth.keyboard.UserInteraction;
-import org.apache.sshd.client.future.ConnectFuture;
-import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.cipher.ECCurves;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.config.keys.BuiltinIdentities;
-import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.config.keys.PublicKeyEntry;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.kex.KexProposalOption;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionListener;
-import org.apache.sshd.common.signature.BuiltinSignatures;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.logging.LoggingUtils;
-import org.apache.sshd.common.util.logging.SimplifiedLog;
-import org.apache.sshd.common.util.net.SshdSocketAddress;
-import org.apache.sshd.common.util.security.SecurityUtils;
-
-/**
- * A naive implementation of <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh-keyscan&sektion=1">ssh-keyscan(1)</A>
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SshKeyScan implements Channel, Callable<Void>, ServerKeyVerifier, SessionListener, SimplifiedLog {
- /**
- * Default key types if not overridden from the command line
- */
- public static final List<String> DEFAULT_KEY_TYPES =
- Collections.unmodifiableList(Arrays.asList(BuiltinIdentities.Constants.RSA, BuiltinIdentities.Constants.ECDSA));
- public static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
- public static final Level DEFAULT_LEVEL = Level.INFO;
-
- private final AtomicBoolean open = new AtomicBoolean(true);
- private SshClient client;
- private int port;
- private long timeout;
- private List<String> keyTypes;
- private InputStream input;
- private Level level;
- private final Map<String, String> currentHostFingerprints = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
- public SshKeyScan() {
- super();
- }
-
- public int getPort() {
- return port;
- }
-
- public void setPort(int port) {
- this.port = port;
- }
-
- public InputStream getInputStream() {
- return input;
- }
-
- public void setInputStream(InputStream input) {
- this.input = input;
- }
-
- public List<String> getKeyTypes() {
- return keyTypes;
- }
-
- public void setKeyTypes(List<String> keyTypes) {
- this.keyTypes = keyTypes;
- }
-
- public long getTimeout() {
- return timeout;
- }
-
- public void setTimeout(long timeout) {
- this.timeout = timeout;
- }
-
- public Level getLogLevel() {
- return level;
- }
-
- public void setLogLevel(Level level) {
- this.level = level;
- }
-
- @Override
- public void log(Level level, Object message, Throwable t) {
- if (isEnabled(level)) {
- PrintStream ps = System.out;
- if ((t != null) || Level.SEVERE.equals(level) || Level.WARNING.equals(level)) {
- ps = System.err;
- }
-
- ps.append('\t').println(message);
- if (t != null) {
- ps.append("\t\t").append(t.getClass().getSimpleName()).append(": ").println(t.getMessage());
- }
- }
- }
-
- @Override
- public boolean isEnabled(Level level) {
- return LoggingUtils.isLoggable(level, getLogLevel());
- }
-
- @Override
- public Void call() throws Exception {
- ValidateUtils.checkTrue(isOpen(), "Scanner is closed");
-
- Collection<String> typeNames = getKeyTypes();
- Map<String, List<KeyPair>> pairsMap = createKeyPairs(typeNames);
- /*
- * We will need to switch signature factories for each specific
- * key type in order to force the server to send ONLY that specific
- * key, so pre-create the factories map according to the selected
- * key types
- */
- SortedMap<String, List<NamedFactory<Signature>>> sigFactories = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- Collection<String> sigTypes = GenericUtils.asSortedSet(sigFactories.comparator(), pairsMap.keySet());
- for (String kt : sigTypes) {
- List<NamedFactory<Signature>> factories = resolveSignatureFactories(kt);
- if (GenericUtils.isEmpty(factories)) {
- if (isEnabled(Level.FINEST)) {
- log(Level.FINEST, "Skip empty signature factories for " + kt);
- }
- pairsMap.remove(kt);
- } else {
- sigFactories.put(kt, factories);
- }
- }
-
- ValidateUtils.checkTrue(!GenericUtils.isEmpty(pairsMap), "No client key pairs");
- ValidateUtils.checkTrue(!GenericUtils.isEmpty(sigFactories), "No signature factories");
-
- Exception err = null;
- try {
- ValidateUtils.checkTrue(client == null, "Client still active");
- client = SshClient.setUpDefaultClient();
- client.setServerKeyVerifier(this);
-
- try (BufferedReader rdr = new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8))) {
- client.setUserInteraction(new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
- return null;
- }
-
- @Override
- public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
- return null;
- }
-
- @Override
- public void serverVersionInfo(ClientSession session, List<String> lines) {
- if (isEnabled(Level.FINE) && GenericUtils.isNotEmpty(lines)) {
- for (String l : lines) {
- log(Level.FINE, "Server Info: " + l);
- }
- }
- }
-
- @Override
- public void welcome(ClientSession session, String banner, String lang) {
- if (isEnabled(Level.FINE) && GenericUtils.isNotEmpty(banner)) {
- String[] lines = GenericUtils.split(banner, '\n');
- for (String l : lines) {
- log(Level.FINE, "Welcome[" + lang + "]: " + l);
- }
- }
- }
- });
-
- client.start();
- for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
- line = GenericUtils.replaceWhitespaceAndTrim(line);
-
- String[] hosts = GenericUtils.split(line, ',');
- if (GenericUtils.isEmpty(hosts)) {
- continue;
- }
-
- for (String h : hosts) {
- if (!isOpen()) {
- throw new InterruptedIOException("Closed while preparing to contact host=" + h);
- }
-
- try {
- resolveServerKeys(client, h, pairsMap, sigFactories);
- } catch (Exception e) {
- // check if interrupted while scanning host keys
- if (e instanceof InterruptedIOException) {
- throw e;
- }
-
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Failed to retrieve keys from " + h, e);
- }
- err = GenericUtils.accumulateException(err, e);
- } finally {
- currentHostFingerprints.clear();
- }
- }
- }
- }
- } finally {
- try {
- close();
- } catch (IOException e) {
- err = GenericUtils.accumulateException(err, e);
- }
- }
-
- if (err != null) {
- throw err;
- }
-
- return null;
- }
-
- protected void resolveServerKeys(SshClient client, String host,
- Map<String, List<KeyPair>> pairsMap, Map<String, List<NamedFactory<Signature>>> sigFactories)
- throws IOException {
- // Cannot use forEach because of the potential for throwing IOException by the invoked code
- for (Map.Entry<String, List<KeyPair>> pe : pairsMap.entrySet()) {
- String kt = pe.getKey();
- if (!isOpen()) {
- throw new InterruptedIOException("Closed while attempting to retrieve key type=" + kt + " from " + host);
- }
-
- List<NamedFactory<Signature>> current = client.getSignatureFactories();
- try {
- /*
- * Replace whatever factories there are right now with the
- * specific one for the key in order to extract only the
- * specific host key type
- */
- List<NamedFactory<Signature>> forced = sigFactories.get(kt);
- client.setSignatureFactories(forced);
- resolveServerKeys(client, host, kt, pe.getValue());
- } catch (Exception e) {
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Failed to resolve key=" + kt + " for " + host);
- }
-
- if (e instanceof ConnectException) {
- return; // makes no sense to try again with another key type...
- }
- } finally {
- client.setSignatureFactories(current); // don't have to, but be nice...
- }
- }
- }
-
- protected void resolveServerKeys(SshClient client, String host, String kt, List<KeyPair> ids) throws Exception {
- int connectPort = getPort();
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Connecting to " + host + ":" + connectPort + " to retrieve key type=" + kt);
- }
-
- ConnectFuture future = client.connect(UUID.randomUUID().toString(), host, connectPort);
- long waitTime = getTimeout();
- if (!future.await(waitTime)) {
- throw new ConnectException("Failed to connect to " + host + ":" + connectPort
- + " within " + waitTime + " msec."
- + " to retrieve key type=" + kt);
- }
-
- try (ClientSession session = future.getSession()) {
- IoSession ioSession = session.getIoSession();
- SocketAddress remoteAddress = ioSession.getRemoteAddress();
- String remoteLocation = toString(remoteAddress);
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Connected to " + remoteLocation + " to retrieve key type=" + kt);
- }
-
- try {
- session.addSessionListener(this);
- if (isEnabled(Level.FINER)) {
- log(Level.FINER, "Authenticating with key type=" + kt + " to " + remoteLocation);
- }
-
- GenericUtils.forEach(ids, session::addPublicKeyIdentity);
-
- try {
- // shouldn't really succeed, but do it since key exchange occurs only on auth attempt
- session.auth().verify(waitTime);
- log(Level.WARNING, "Unexpected authentication success using key type=" + kt + " with " + remoteLocation);
- } catch (Exception e) {
- if (isEnabled(Level.FINER)) {
- log(Level.FINER, "Failed to authenticate using key type=" + kt + " with " + remoteLocation);
- }
- } finally {
- GenericUtils.forEach(ids, session::removePublicKeyIdentity);
- }
- } finally {
- session.removeSessionListener(this);
- }
- }
- }
-
- @Override
- public void sessionCreated(Session session) {
- logSessionEvent(session, "Created");
- }
-
- @Override
- public void sessionEvent(Session session, Event event) {
- logSessionEvent(session, event);
- if (isEnabled(Level.FINEST) && Event.KexCompleted.equals(event)) {
- IoSession ioSession = session.getIoSession();
- SocketAddress remoteAddress = ioSession.getRemoteAddress();
- String remoteLocation = toString(remoteAddress);
- for (KexProposalOption paramType : KexProposalOption.VALUES) {
- String paramValue = session.getNegotiatedKexParameter(paramType);
- log(Level.FINEST, remoteLocation + "[" + paramType.getDescription() + "]: " + paramValue);
- }
- }
- }
-
- @Override
- public void sessionException(Session session, Throwable t) {
- logSessionEvent(session, t);
- }
-
- @Override
- public void sessionClosed(Session session) {
- logSessionEvent(session, "Closed");
- }
-
- @Override
- public void sessionNegotiationStart(
- Session session, Map<KexProposalOption, String> clientProposal, Map<KexProposalOption, String> serverProposal) {
- logSessionEvent(session, "sessionNegotiationStart");
- logNegotiationProposal("c2s", clientProposal);
- logNegotiationProposal("s2c", serverProposal);
- }
-
- protected void logNegotiationProposal(String type, Map<KexProposalOption, String> proposal) {
- if (!isEnabled(Level.FINEST)) {
- return;
- }
-
- proposal.forEach((option, value) -> log(Level.FINEST, option.getDescription() + "[" + type + "]: " + value));
- }
-
- @Override
- public void sessionNegotiationEnd(Session session, Map<KexProposalOption, String> clientProposal,
- Map<KexProposalOption, String> serverProposal, Map<KexProposalOption, String> negotiatedOptions,
- Throwable reason) {
- if (reason == null) {
- logSessionEvent(session, "sessionNegotiationStart");
- } else {
- logSessionEvent(session, reason);
- }
- }
-
- protected void logSessionEvent(Session session, Object event) {
- if (isEnabled(Level.FINEST)) {
- IoSession ioSession = session.getIoSession();
- SocketAddress remoteAddress = ioSession.getRemoteAddress();
- log(Level.FINEST, "Session " + toString(remoteAddress) + " event: " + event);
- }
- }
-
- @Override
- public boolean verifyServerKey(ClientSession sshClientSession, SocketAddress remoteAddress, PublicKey serverKey) {
- String remoteLocation = toString(remoteAddress);
- String extra = KeyUtils.getFingerPrint(serverKey);
- try {
- String keyType = KeyUtils.getKeyType(serverKey);
- String current = GenericUtils.isEmpty(keyType) ? null : currentHostFingerprints.get(keyType);
- if (Objects.equals(current, extra)) {
- if (isEnabled(Level.FINER)) {
- log(Level.FINER, "verifyServerKey(" + remoteLocation + ")[" + keyType + "] skip existing key: " + extra);
- }
- } else {
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "verifyServerKey(" + remoteLocation + ")[" + keyType + "] found new key: " + extra);
- }
-
- writeServerKey(remoteLocation, keyType, serverKey);
-
- if (!GenericUtils.isEmpty(keyType)) {
- currentHostFingerprints.put(keyType, extra);
- }
- }
- } catch (Exception e) {
- log(Level.SEVERE, "Failed to output the public key " + extra + " from " + remoteLocation, e);
- }
-
- return true;
- }
-
- protected void writeServerKey(String remoteLocation, String keyType, PublicKey serverKey) throws Exception {
- StringBuilder sb = new StringBuilder(256).append(remoteLocation).append(' ');
- PublicKeyEntry.appendPublicKeyEntry(sb, serverKey);
- log(Level.INFO, sb);
- }
-
- private static String toString(SocketAddress addr) {
- if (addr == null) {
- return null;
- } else if (addr instanceof InetSocketAddress) {
- return ((InetSocketAddress) addr).getHostString();
- } else if (addr instanceof SshdSocketAddress) {
- return ((SshdSocketAddress) addr).getHostName();
- } else {
- return addr.toString();
- }
- }
-
- protected List<NamedFactory<Signature>> resolveSignatureFactories(String keyType) throws GeneralSecurityException {
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Resolve signature factories for " + keyType);
- }
-
- if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
- return Collections.singletonList(BuiltinSignatures.rsa);
- } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
- return Collections.singletonList(BuiltinSignatures.dsa);
- } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
- List<NamedFactory<Signature>> factories = new ArrayList<>(ECCurves.NAMES.size());
- for (String n : ECCurves.NAMES) {
- if (isEnabled(Level.FINER)) {
- log(Level.FINER, "Resolve signature factory for curve=" + n);
- }
-
- NamedFactory<Signature> f =
- ValidateUtils.checkNotNull(BuiltinSignatures.fromString(n), "Unknown curve signature: %s", n);
- factories.add(f);
- }
-
- return factories;
- } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyType)) {
- return Collections.singletonList(BuiltinSignatures.ed25519);
- } else {
- throw new NoSuchAlgorithmException("Unknown key type: " + keyType);
- }
- }
-
- protected Map<String, List<KeyPair>> createKeyPairs(Collection<String> typeNames) throws GeneralSecurityException {
- if (GenericUtils.isEmpty(typeNames)) {
- return Collections.emptyMap();
- }
-
- Map<String, List<KeyPair>> pairsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- for (String kt : typeNames) {
- if ("*".equalsIgnoreCase(kt) || "all".equalsIgnoreCase(kt)) {
- ValidateUtils.checkTrue(typeNames.size() == 1, "Wildcard key type must be the only one specified: %s", typeNames);
- return createKeyPairs(BuiltinIdentities.NAMES);
- }
-
- if (pairsMap.containsKey(kt)) {
- log(Level.WARNING, "Key type " + kt + " re-specified");
- continue;
- }
-
- List<KeyPair> kps = createKeyPairs(kt);
- if (GenericUtils.isEmpty(kps)) {
- log(Level.WARNING, "No key-pairs generated for key type " + kt);
- continue;
- }
-
- pairsMap.put(kt, kps);
- }
-
- return pairsMap;
- }
-
- protected List<KeyPair> createKeyPairs(String keyType) throws GeneralSecurityException {
- if (isEnabled(Level.FINE)) {
- log(Level.FINE, "Generate key pairs for " + keyType);
- }
-
- if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
- return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_RSA, 1024));
- } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
- return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_DSS, 512));
- } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
- if (!SecurityUtils.isECCSupported()) {
- throw new NoSuchAlgorithmException("ECC not supported: " + keyType);
- }
-
- List<KeyPair> kps = new ArrayList<>(ECCurves.NAMES.size());
- for (ECCurves curve : ECCurves.VALUES) {
- String curveName = curve.getName();
- if (isEnabled(Level.FINER)) {
- log(Level.FINER, "Generate key pair for curve=" + curveName);
- }
-
- kps.add(KeyUtils.generateKeyPair(curve.getKeyType(), curve.getKeySize()));
- }
-
- return kps;
- } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyType)) {
- if (!SecurityUtils.isEDDSACurveSupported()) {
- throw new NoSuchAlgorithmException("EDDSA curves not supported: " + keyType);
- }
-
- KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
- return Collections.singletonList(g.generateKeyPair());
- } else {
- throw new InvalidKeySpecException("Unknown key type: " + keyType);
- }
- }
-
- @Override
- public boolean isOpen() {
- return open.get();
- }
-
- @Override
- public void close() throws IOException {
- if (!open.getAndSet(false)) {
- return; // already closed
- }
-
- IOException err = null;
- if (input != null) {
- try {
- input.close();
- } catch (IOException e) {
- err = GenericUtils.accumulateException(err, e);
- } finally {
- input = null;
- }
- }
-
- if (client != null) {
- try {
- client.close();
- } catch (IOException e) {
- err = GenericUtils.accumulateException(err, e);
- } finally {
- try {
- client.stop();
- } finally {
- client = null;
- }
- }
- }
- if (err != null) {
- throw err;
- }
- }
-
- //////////////////////////////////////////////////////////////////////////
-
- // returns a List of the hosts to be contacted
- public static List<String> parseCommandLineArguments(SshKeyScan scanner, String... args) throws IOException {
- int numArgs = GenericUtils.length(args);
- for (int index = 0; index < numArgs; index++) {
- String optName = args[index];
- if ("-f".equals(optName)) {
- index++;
- ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
- ValidateUtils.checkTrue(scanner.getInputStream() == null, "%s option re-specified", optName);
-
- String filePath = args[index];
- if ("-".equals(filePath)) {
- scanner.setInputStream(new NoCloseInputStream(System.in));
- } else {
- scanner.setInputStream(new FileInputStream(filePath));
- }
- } else if ("-t".equals(optName)) {
- index++;
- ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
- ValidateUtils.checkTrue(GenericUtils.isEmpty(scanner.getKeyTypes()), "%s option re-specified", optName);
-
- String typeList = args[index];
- String[] types = GenericUtils.split(typeList, ',');
- ValidateUtils.checkTrue(GenericUtils.length(types) > 0, "No types specified for %s", optName);
- scanner.setKeyTypes(Arrays.asList(types));
- } else if ("-p".equals(optName)) {
- index++;
- ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
- ValidateUtils.checkTrue(scanner.getPort() <= 0, "%s option re-specified", optName);
-
- String portValue = args[index];
- int port = Integer.parseInt(portValue);
- ValidateUtils.checkTrue((port > 0) && (port <= 0xFFFF), "Bad port: %s", portValue);
- scanner.setPort(port);
- } else if ("-T".equals(optName)) {
- index++;
- ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
- ValidateUtils.checkTrue(scanner.getTimeout() <= 0, "%s option re-specified", optName);
-
- String timeoutValue = args[index];
- long timeout = Long.parseLong(timeoutValue);
- ValidateUtils.checkTrue(timeout > 0L, "Bad timeout: %s", timeoutValue);
- scanner.setTimeout(timeout);
- } else if ("-v".equals(optName)) {
- ValidateUtils.checkTrue(scanner.getLogLevel() == null, "%s option re-specified", optName);
- scanner.setLogLevel(Level.FINEST);
- } else { // stop at first non-option - assume the rest are host names/addresses
- ValidateUtils.checkTrue(optName.charAt(0) != '-', "Unknown option: %s", optName);
-
- int remaining = numArgs - index;
- if (remaining == 1) {
- return Collections.singletonList(optName);
- }
-
- List<String> hosts = new ArrayList<>(remaining);
- for (; index < numArgs; index++) {
- hosts.add(args[index]);
- }
-
- return hosts;
- }
- }
-
- return Collections.emptyList();
- }
-
- /* -------------------------------------------------------------------- */
-
- public static <S extends SshKeyScan> S setInputStream(S scanner, Collection<String> hosts) throws IOException {
- if (GenericUtils.isEmpty(hosts)) {
- Objects.requireNonNull(scanner.getInputStream(), "No hosts or file specified");
- } else {
- ValidateUtils.checkTrue(scanner.getInputStream() == null, "Both hosts and file specified");
-
- // convert the hosts from the arguments into a "file" - one host per line
- try (ByteArrayOutputStream baos = new ByteArrayOutputStream(hosts.size() * 32)) {
- try (Writer w = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
- for (String h : hosts) {
- w.append(h).append(IoUtils.EOL);
- }
- }
-
- byte[] data = baos.toByteArray();
- scanner.setInputStream(new ByteArrayInputStream(data));
- }
- }
-
- return scanner;
- }
-
- public static <S extends SshKeyScan> S initializeScanner(S scanner, Collection<String> hosts) throws IOException {
- setInputStream(scanner, hosts);
- if (scanner.getPort() <= 0) {
- scanner.setPort(SshConfigFileReader.DEFAULT_PORT);
- }
-
- if (scanner.getTimeout() <= 0L) {
- scanner.setTimeout(DEFAULT_TIMEOUT);
- }
-
- if (GenericUtils.isEmpty(scanner.getKeyTypes())) {
- scanner.setKeyTypes(DEFAULT_KEY_TYPES);
- }
-
- if (scanner.getLogLevel() == null) {
- scanner.setLogLevel(DEFAULT_LEVEL);
- }
-
- return scanner;
- }
-
- /* -------------------------------------------------------------------- */
-
- public static void main(String[] args) throws Exception {
- try (SshKeyScan scanner = new SshKeyScan()) {
- Collection<String> hosts = parseCommandLineArguments(scanner, args);
- initializeScanner(scanner, hosts);
- scanner.call();
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index dfc7f7d..16d0cb2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -18,26 +18,17 @@
*/
package org.apache.sshd.client.scp;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.List;
import java.util.Objects;
-import java.util.Set;
-import java.util.logging.Level;
-import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.FactoryManager;
@@ -46,23 +37,15 @@ import org.apache.sshd.common.file.util.MockFileSystem;
import org.apache.sshd.common.file.util.MockPath;
import org.apache.sshd.common.scp.ScpFileOpener;
import org.apache.sshd.common.scp.ScpHelper;
-import org.apache.sshd.common.scp.ScpLocation;
import org.apache.sshd.common.scp.ScpTimestamp;
import org.apache.sshd.common.scp.ScpTransferEventListener;
import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
-import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class DefaultScpClient extends AbstractScpClient {
- /**
- * Command line option used to indicate a non-default port
- */
- public static final String SCP_PORT_OPTION = "-P";
-
protected final ScpFileOpener opener;
protected final ScpTransferEventListener listener;
private final ClientSession clientSession;
@@ -172,172 +155,5 @@ public class DefaultScpClient extends AbstractScpClient {
channel.close(false);
}
}
-
- //////////////////////////////////////////////////////////////////////////
-
- private static boolean showError(PrintStream stderr, String message) {
- stderr.println(message);
- return true;
- }
-
- private static String[] normalizeCommandArguments(PrintStream stdout, PrintStream stderr, String... args) {
- int numArgs = GenericUtils.length(args);
- if (numArgs <= 0) {
- return args;
- }
-
- List<String> effective = new ArrayList<>(numArgs);
- boolean error = false;
- for (int index = 0; (index < numArgs) && (!error); index++) {
- String argName = args[index];
- // handled by 'setupClientSession'
- if (SshClient.isArgumentedOption(SCP_PORT_OPTION, argName)) {
- if ((index + 1) >= numArgs) {
- error = showError(stderr, "option requires an argument: " + argName);
- break;
- }
-
- effective.add(argName);
- effective.add(args[++index]);
- } else if ("-r".equals(argName) || "-p".equals(argName)
- || "-q".equals(argName) || "-C".equals(argName)
- || "-v".equals(argName) || "-vv".equals(argName) || "-vvv".equals(argName)) {
- effective.add(argName);
- } else if (argName.charAt(0) == '-') {
- error = showError(stderr, "Unknown option: " + argName);
- break;
- } else {
- if ((index + 1) >= numArgs) {
- error = showError(stderr, "Not enough arguments");
- break;
- }
-
- ScpLocation source = new ScpLocation(argName);
- ScpLocation target = new ScpLocation(args[++index]);
- if (index < (numArgs - 1)) {
- error = showError(stderr, "Unexpected extra arguments");
- break;
- }
-
- if (source.isLocal() == target.isLocal()) {
- error = showError(stderr, "Both targets are either remote or local");
- break;
- }
-
- ScpLocation remote = source.isLocal() ? target : source;
- effective.add(remote.resolveUsername() + "@" + remote.getHost());
- effective.add(source.toString());
- effective.add(target.toString());
- break;
- }
- }
-
- if (error) {
- return null;
- }
-
- return effective.toArray(new String[effective.size()]);
- }
-
- public static void main(String[] args) throws Exception {
- final PrintStream stdout = System.out;
- final PrintStream stderr = System.err;
- OutputStream logStream = stdout;
- try (BufferedReader stdin = new BufferedReader(
- new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
- args = normalizeCommandArguments(stdout, stderr, args);
- int numArgs = GenericUtils.length(args);
- // see the way normalizeCommandArguments works...
- if (numArgs >= 2) {
- Level level = SshClient.resolveLoggingVerbosity(args, numArgs - 2);
- logStream = SshClient.resolveLoggingTargetStream(stdout, stderr, args, numArgs - 2);
- if (logStream != null) {
- SshClient.setupLogging(level, stdout, stderr, logStream);
- }
- }
-
- ClientSession session = (logStream == null) || GenericUtils.isEmpty(args)
- ? null : SshClient.setupClientSession(SCP_PORT_OPTION, stdin, stdout, stderr, args);
- if (session == null) {
- stderr.println("usage: scp [" + SCP_PORT_OPTION + " port] [-i identity]"
- + " [-v[v][v]] [-E logoutput] [-r] [-p] [-q] [-o option=value]"
- + " [-c cipherlist] [-m maclist] [-w password] [-C] <source> <target>");
- stderr.println();
- stderr.println("Where <source> or <target> are either 'user@host:file' or a local file path");
- stderr.println("NOTE: exactly ONE of the source or target must be remote and the other one local");
- System.exit(-1);
- return; // not that we really need it...
- }
-
- try {
- // see the way normalizeCommandArguments works...
- Collection<Option> options = EnumSet.noneOf(Option.class);
- boolean quiet = false;
- for (int index = 0; index < numArgs; index++) {
- String argName = args[index];
- if ("-r".equals(argName)) {
- options.add(Option.Recursive);
- } else if ("-p".equals(argName)) {
- options.add(Option.PreserveAttributes);
- } else if ("-q".equals(argName)) {
- quiet = true;
- }
- }
-
- if (!quiet) {
- session.setScpTransferEventListener(new ScpTransferEventListener() {
- @Override
- public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
- logEvent("startFolderEvent", op, file, -1L, perms, null);
- }
-
- @Override
- public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
- logEvent("endFolderEvent", op, file, -1L, perms, thrown);
- }
-
- @Override
- public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
- logEvent("startFileEvent", op, file, length, perms, null);
- }
-
- @Override
- public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
- logEvent("endFileEvent", op, file, length, perms, thrown);
- }
-
- private void logEvent(String name, FileOperation op, Path file, long length, Collection<PosixFilePermission> perms, Throwable thrown) {
- PrintStream ps = (thrown == null) ? stdout : stderr;
- ps.append('\t').append(name).append('[').append(op.name()).append(']').append(' ').append(file.toString());
- if (length > 0L) {
- ps.append(' ').append("length=").append(Long.toString(length));
- }
- ps.append(' ').append(String.valueOf(perms));
-
- if (thrown != null) {
- ps.append(" - ").append(thrown.getClass().getSimpleName()).append(": ").append(thrown.getMessage());
- }
- ps.println();
- }
- });
- }
-
- ScpClient client = session.createScpClient();
- ScpLocation source = new ScpLocation(args[numArgs - 2]);
- ScpLocation target = new ScpLocation(args[numArgs - 1]);
- if (source.isLocal()) {
- client.upload(source.getPath(), target.getPath(), options);
- } else {
- client.download(source.getPath(), target.getPath(), options);
- }
- } finally {
- session.close();
- }
- } finally {
- if ((logStream != stdout) && (logStream != stderr)) {
- logStream.close();
- }
- }
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java b/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
deleted file mode 100644
index 66f66dd..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client;
-
-/**
- * Just a test class used to invoke {@link SshClient#main(String[])} in
- * order to have logging - which is in {@code test} scope
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class SshClientMain {
- private SshClientMain() {
- throw new UnsupportedOperationException("No instance");
- }
-
- public static void main(String[] args) throws Exception {
- SshClient.main(args);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/test/java/org/apache/sshd/client/SshKeyScanMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/SshKeyScanMain.java b/sshd-core/src/test/java/org/apache/sshd/client/SshKeyScanMain.java
deleted file mode 100644
index 856b324..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/SshKeyScanMain.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client;
-
-/**
- * Just a test class used to invoke {@link SshKeyScan#main(String[])} in
- * order to have logging - which is in {@code test} scope
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class SshKeyScanMain {
- private SshKeyScanMain() {
- throw new UnsupportedOperationException("No instance");
- }
-
- public static void main(String[] args) throws Exception {
- SshKeyScan.main(args);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
deleted file mode 100644
index 798fe70..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client.channel;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.util.test.BaseTestSupport;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class ChannelExecMain extends BaseTestSupport {
- public static void doExecuteCommands(
- BufferedReader stdin, PrintStream stdout, PrintStream stderr, ClientSession session) throws Exception {
- while (true) {
- stdout.print("> ");
-
- String command = stdin.readLine();
- if ("q".equalsIgnoreCase(command) || "quit".equalsIgnoreCase(command)) {
- break;
- }
- if (GenericUtils.isEmpty(command)) {
- continue;
- }
-
- while (true) {
- try {
- String response = session.executeRemoteCommand(command);
- String[] lines = GenericUtils.split(response, '\n');
- for (String l : lines) {
- stdout.append('\t').println(l);
- }
- } catch (Exception e) {
- stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
- }
-
- stdout.append("Execute ").append(command).print(" again [y]/n ");
- String ans = stdin.readLine();
- if ((GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) != 'y')) {
- break;
- }
- }
- }
- }
-
- public static void main(String[] args) throws Exception {
- PrintStream stdout = System.out;
- PrintStream stderr = System.err;
- try (BufferedReader stdin = new BufferedReader(
- new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
- ClientSession session = SshClient.setupClientSession("-P", stdin, stdout, stderr, args);
- if (session == null) {
- System.err.println("usage: channelExec [-i identity] [-l login] [-P port] [-o option=value]"
- + " [-w password] [-c cipherlist] [-m maclist] [-C] hostname/user@host");
- System.exit(-1);
- return;
- }
-
- try (SshClient client = (SshClient) session.getFactoryManager()) {
- try (ClientSession clientSession = session) {
- doExecuteCommands(stdin, stdout, stderr, session);
- } finally {
- client.stop();
- }
- }
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpCommandMain.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpCommandMain.java
deleted file mode 100644
index 75b0454..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpCommandMain.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client.scp;
-
-/**
- * Just a test class used to invoke {@link org.apache.sshd.client.scp.DefaultScpClient#main(String[])} in
- * order to have logging - which is in {@code test} scope
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class ScpCommandMain {
- private ScpCommandMain() {
- throw new UnsupportedOperationException("No instance");
- }
-
- public static void main(String[] args) throws Exception {
- DefaultScpClient.main(args);
- }
-}
[5/6] mina-sshd git commit: [SSHD-816] Moved all 'main' code for
client commands to sshd-cli module
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
new file mode 100644
index 0000000..47021e1
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -0,0 +1,632 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import org.apache.sshd.cli.CliSupport;
+import org.apache.sshd.client.ClientAuthenticationManager;
+import org.apache.sshd.client.ClientBuilder;
+import org.apache.sshd.client.ClientFactoryManager;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.keys.ClientIdentity;
+import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.cipher.BuiltinCiphers;
+import org.apache.sshd.common.cipher.Cipher;
+import org.apache.sshd.common.compression.BuiltinCompressions;
+import org.apache.sshd.common.compression.Compression;
+import org.apache.sshd.common.config.CompressionConfigValue;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.mac.BuiltinMacs;
+import org.apache.sshd.common.mac.Mac;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class SshClientCliSupport extends CliSupport {
+ /**
+ * Command line option used to indicate non-default target port
+ */
+ public static final String SSH_CLIENT_PORT_OPTION = "-p";
+
+ protected SshClientCliSupport() {
+ super();
+ }
+
+ public static boolean isArgumentedOption(String portOption, String argName) {
+ return portOption.equals(argName)
+ || "-i".equals(argName)
+ || "-o".equals(argName)
+ || "-l".equals(argName)
+ || "-w".equals(argName)
+ || "-c".equals(argName)
+ || "-m".equals(argName)
+ || "-E".equals(argName);
+ }
+
+ // NOTE: ClientSession#getFactoryManager is the SshClient
+ public static ClientSession setupClientSession(
+ String portOption, BufferedReader stdin, PrintStream stdout, PrintStream stderr, String... args)
+ throws Exception {
+ int port = -1;
+ String host = null;
+ String login = null;
+ String password = null;
+ boolean error = false;
+ List<Path> identities = new ArrayList<>();
+ Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ List<NamedFactory<Cipher>> ciphers = null;
+ List<NamedFactory<Mac>> macs = null;
+ List<NamedFactory<Compression>> compressions = null;
+ int numArgs = GenericUtils.length(args);
+ for (int i = 0; (!error) && (i < numArgs); i++) {
+ String argName = args[i];
+ String argVal = null;
+ if (isArgumentedOption(portOption, argName)) {
+ if ((i + 1) >= numArgs) {
+ error = showError(stderr, "option requires an argument: " + argName);
+ break;
+ }
+
+ argVal = args[++i];
+ }
+
+ if (portOption.equals(argName)) {
+ if (port > 0) {
+ error = showError(stderr, argName + " option value re-specified: " + port);
+ break;
+ }
+
+ port = Integer.parseInt(argVal);
+ if (port <= 0) {
+ error = showError(stderr, "Bad option value for " + argName + ": " + port);
+ break;
+ }
+ } else if ("-w".equals(argName)) {
+ if (GenericUtils.length(password) > 0) {
+ error = showError(stderr, argName + " option value re-specified: " + password);
+ break;
+ }
+ password = argVal;
+ } else if ("-c".equals(argName)) {
+ ciphers = setupCiphers(argName, argVal, ciphers, stderr);
+ if (GenericUtils.isEmpty(ciphers)) {
+ error = true;
+ break;
+ }
+ } else if ("-m".equals(argName)) {
+ macs = setupMacs(argName, argVal, macs, stderr);
+ if (GenericUtils.isEmpty(macs)) {
+ error = true;
+ break;
+ }
+ } else if ("-i".equals(argName)) {
+ identities.add(resolveIdentityFile(argVal));
+ } else if ("-C".equals(argName)) {
+ compressions = setupCompressions(argName,
+ GenericUtils.join(
+ Arrays.asList(
+ BuiltinCompressions.Constants.ZLIB, BuiltinCompressions.Constants.DELAYED_ZLIB), ','),
+ compressions, stderr);
+ if (GenericUtils.isEmpty(compressions)) {
+ error = true;
+ break;
+ }
+ } else if ("-o".equals(argName)) {
+ String opt = argVal;
+ int idx = opt.indexOf('=');
+ if (idx <= 0) {
+ error = showError(stderr, "bad syntax for option: " + opt);
+ break;
+ }
+
+ String optName = opt.substring(0, idx);
+ String optValue = opt.substring(idx + 1);
+ if (HostConfigEntry.IDENTITY_FILE_CONFIG_PROP.equals(optName)) {
+ Path idFile = resolveIdentityFile(optValue);
+ identities.add(idFile);
+ } else {
+ options.put(optName, optValue);
+ }
+ } else if ("-l".equals(argName)) {
+ if (login != null) {
+ error = showError(stderr, argName + " option value re-specified: " + port);
+ break;
+ }
+
+ login = argVal;
+ } else if (argName.charAt(0) != '-') {
+ if (host != null) { // assume part of a command following it
+ break;
+ }
+
+ host = argName;
+ int pos = host.indexOf('@'); // check if user@host
+ if (pos > 0) {
+ if (login == null) {
+ login = host.substring(0, pos);
+ host = host.substring(pos + 1);
+ } else {
+ error = showError(stderr, "Login already specified using -l option (" + login + "): " + host);
+ break;
+ }
+ }
+ }
+ }
+
+ if ((!error) && GenericUtils.isEmpty(host)) {
+ error = showError(stderr, "Hostname not specified");
+ }
+
+ if (error) {
+ return null;
+ }
+
+ SshClient client = setupClient(options, ciphers, macs, compressions, identities, stdin, stdout, stderr);
+ if (client == null) {
+ return null;
+ }
+
+ try {
+ client.start();
+
+ if (login == null) {
+ login = OsUtils.getCurrentUser();
+ }
+
+ if (port <= 0) {
+ port = SshConfigFileReader.DEFAULT_PORT;
+ }
+
+ // TODO use a configurable wait time
+ ClientSession session = client.connect(login, host, port).verify().getSession();
+ try {
+ if (GenericUtils.length(password) > 0) {
+ session.addPasswordIdentity(password);
+ }
+ session.auth().verify(FactoryManager.DEFAULT_AUTH_TIMEOUT); // TODO use a configurable wait time
+ return session;
+ } catch (Exception e) {
+ session.close(true);
+ throw e;
+ }
+ } catch (Exception e) {
+ client.close();
+ throw e;
+ }
+ }
+
+ public static Path resolveIdentityFile(String id) throws IOException {
+ BuiltinIdentities identity = BuiltinIdentities.fromName(id);
+ if (identity != null) {
+ String fileName = ClientIdentity.getIdentityFileName(identity.getName());
+ Path keysFolder = PublicKeyEntry.getDefaultKeysFolderPath();
+ return keysFolder.resolve(fileName);
+ } else {
+ return Paths.get(id);
+ }
+ }
+
+ // returns null if error encountered
+ public static SshClient setupClient(
+ Map<String, Object> options,
+ List<NamedFactory<Cipher>> ciphers,
+ List<NamedFactory<Mac>> macs,
+ List<NamedFactory<Compression>> compressions,
+ Collection<? extends Path> identities,
+ BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
+ if (GenericUtils.isEmpty(ciphers)) {
+ ciphers = setupCiphers(resolver, stderr);
+ if (ciphers == null) {
+ return null;
+ }
+ }
+
+ if (GenericUtils.isEmpty(macs)) {
+ macs = setupMacs(resolver, stderr);
+ if (macs == null) {
+ return null;
+ }
+ }
+
+ if (GenericUtils.isEmpty(compressions)) {
+ compressions = setupCompressions(resolver, stderr);
+ if (compressions == null) {
+ return null;
+ }
+ }
+
+ SshClient client = SshClient.setUpDefaultClient();
+ try {
+ if (GenericUtils.size(ciphers) > 0) {
+ client.setCipherFactories(ciphers);
+ }
+
+ if (GenericUtils.size(macs) > 0) {
+ client.setMacFactories(macs);
+ }
+
+ if (GenericUtils.size(compressions) > 0) {
+ client.setCompressionFactories(compressions);
+ }
+
+ try {
+ setupSessionIdentities(client, identities, stdin, stdout, stderr);
+ } catch (Throwable t) { // show but do not fail the setup - maybe a password can be used
+ showError(stderr, t.getClass().getSimpleName() + " while loading user keys: " + t.getMessage());
+ }
+
+ setupServerKeyVerifier(client, options, stdin, stdout, stderr);
+ setupSessionUserInteraction(client, stdin, stdout, stderr);
+
+ Map<String, Object> props = client.getProperties();
+ props.putAll(options);
+ return client;
+ } catch (Throwable t) {
+ showError(stderr, "Failed (" + t.getClass().getSimpleName() + ") to setup client: " + t.getMessage());
+ client.close();
+ return null;
+ }
+ }
+
+ public static FileKeyPairProvider setupSessionIdentities(ClientFactoryManager client, Collection<? extends Path> identities,
+ BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Throwable {
+ client.setFilePasswordProvider(file -> {
+ stdout.print("Enter password for private key file=" + file + ": ");
+ return stdin.readLine();
+ });
+
+ if (GenericUtils.isEmpty(identities)) {
+ return null;
+ }
+
+ FileKeyPairProvider provider = new FileKeyPairProvider() {
+ @Override
+ public String toString() {
+ return FileKeyPairProvider.class.getSimpleName() + "[clientIdentitiesProvider]";
+ }
+ };
+ provider.setPaths(identities);
+ client.setKeyPairProvider(provider);
+ return provider;
+ }
+
+ public static UserInteraction setupSessionUserInteraction(
+ ClientAuthenticationManager client, BufferedReader stdin, PrintStream stdout, PrintStream stderr) {
+ UserInteraction ui = new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public void serverVersionInfo(ClientSession session, List<String> lines) {
+ for (String l : lines) {
+ stdout.append('\t').println(l);
+ }
+ }
+
+ @Override
+ public void welcome(ClientSession clientSession, String banner, String lang) {
+ stdout.println(banner);
+ }
+
+ @Override
+ public String[] interactive(ClientSession clientSession, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
+ int numPropmts = GenericUtils.length(prompt);
+ String[] answers = new String[numPropmts];
+ try {
+ for (int i = 0; i < numPropmts; i++) {
+ stdout.append(prompt[i]).print(" ");
+ answers[i] = stdin.readLine();
+ }
+ } catch (IOException e) {
+ stderr.append(e.getClass().getSimpleName()).append(" while read prompts: ").println(e.getMessage());
+ }
+ return answers;
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession clientSession, String prompt, String lang) {
+ stdout.append(prompt).print(" ");
+ try {
+ return stdin.readLine();
+ } catch (IOException e) {
+ stderr.append(e.getClass().getSimpleName()).append(" while read password: ").println(e.getMessage());
+ return null;
+ }
+ }
+ };
+ client.setUserInteraction(ui);
+ return ui;
+ }
+
+ public static ServerKeyVerifier setupServerKeyVerifier(
+ ClientAuthenticationManager manager, Map<String, ?> options, BufferedReader stdin, PrintStream stdout, PrintStream stderr) {
+ ServerKeyVerifier current = manager.getServerKeyVerifier();
+ if (current == null) {
+ current = ClientBuilder.DEFAULT_SERVER_KEY_VERIFIER;
+ manager.setServerKeyVerifier(current);
+ }
+
+ String strictValue = Objects.toString(options.remove(KnownHostsServerKeyVerifier.STRICT_CHECKING_OPTION), "true");
+ if (!SshConfigFileReader.parseBooleanValue(strictValue)) {
+ return current;
+ }
+
+ String filePath = Objects.toString(options.remove(KnownHostsServerKeyVerifier.KNOWN_HOSTS_FILE_OPTION), null);
+ if (GenericUtils.isEmpty(filePath)) {
+ current = new DefaultKnownHostsServerKeyVerifier(current);
+ } else { // if user specifies a different location than default be lenient
+ current = new DefaultKnownHostsServerKeyVerifier(current, false, Paths.get(filePath));
+ }
+
+ ((KnownHostsServerKeyVerifier) current).setModifiedServerKeyAcceptor((clientSession, remoteAddress, entry, expected, actual) -> {
+ stderr.append("Mismatched keys presented by ").append(Objects.toString(remoteAddress))
+ .append(" for entry=").println(entry);
+ stderr.append('\t').append("Expected=").append(KeyUtils.getKeyType(expected))
+ .append('-').println(KeyUtils.getFingerPrint(expected));
+ stderr.append('\t').append("Actual=").append(KeyUtils.getKeyType(actual))
+ .append('-').println(KeyUtils.getFingerPrint(actual));
+ stderr.flush(); // just making sure
+
+ stdout.append("Accept key and update known hosts: y/[N]");
+ stdout.flush(); // just making sure
+
+ String ans = GenericUtils.trimToEmpty(stdin.readLine());
+ return (GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) == 'y');
+ });
+
+ manager.setServerKeyVerifier(current);
+ return current;
+ }
+
+ public static Level resolveLoggingVerbosity(String... args) {
+ return resolveLoggingVerbosity(args, GenericUtils.length(args));
+ }
+
+ public static Level resolveLoggingVerbosity(String[] args, int maxIndex) {
+ for (int index = 0; index < maxIndex; index++) {
+ String argName = args[index];
+ if ("-v".equals(argName)) {
+ return Level.INFO;
+ } else if ("-vv".equals(argName)) {
+ return Level.FINE;
+ } else if ("-vvv".equals(argName)) {
+ return Level.FINEST;
+ }
+ }
+
+ return Level.WARNING;
+ }
+
+ public static OutputStream resolveLoggingTargetStream(PrintStream stdout, PrintStream stderr, String... args) {
+ return resolveLoggingTargetStream(stdout, stderr, args, GenericUtils.length(args));
+ }
+
+ public static OutputStream resolveLoggingTargetStream(PrintStream stdout, PrintStream stderr, String[] args, int maxIndex) {
+ for (int index = 0; index < maxIndex; index++) {
+ String argName = args[index];
+ if ("-E".equals(argName)) {
+ if ((index + 1) >= maxIndex) {
+ showError(stderr, "Missing " + argName + " option argument");
+ return null;
+ }
+
+ String argVal = args[index + 1];
+ if ("--".equals(argVal)) {
+ return stdout;
+ }
+
+ try {
+ Path path = Paths.get(argVal).normalize().toAbsolutePath();
+ return Files.newOutputStream(path);
+ } catch (IOException e) {
+ showError(stderr, "Failed (" + e.getClass().getSimpleName() + ") to open " + argVal + ": " + e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ return stderr;
+ }
+
+ public static List<NamedFactory<Compression>> setupCompressions(PropertyResolver options, PrintStream stderr) {
+ String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.COMPRESSION_PROP);
+ if (GenericUtils.isEmpty(argVal)) {
+ return Collections.emptyList();
+ }
+
+ NamedFactory<Compression> value = CompressionConfigValue.fromName(argVal);
+ if (value == null) {
+ showError(stderr, "Unknown compression configuration value: " + argVal);
+ return null;
+ }
+
+ return Collections.singletonList(value);
+ }
+
+ public static List<NamedFactory<Compression>> setupCompressions(
+ String argName, String argVal, List<NamedFactory<Compression>> current, PrintStream stderr) {
+ if (GenericUtils.size(current) > 0) {
+ showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+ return null;
+ }
+
+ BuiltinCompressions.ParseResult result = BuiltinCompressions.parseCompressionsList(argVal);
+ Collection<? extends NamedFactory<Compression>> available = result.getParsedFactories();
+ if (GenericUtils.isEmpty(available)) {
+ showError(stderr, "No known compressions in " + argVal);
+ return null;
+ }
+
+ Collection<String> unsupported = result.getUnsupportedFactories();
+ if (GenericUtils.size(unsupported) > 0) {
+ stderr.append("Ignored unsupported compressions: ").println(GenericUtils.join(unsupported, ','));
+ }
+
+ return new ArrayList<>(available);
+ }
+
+ public static List<NamedFactory<Mac>> setupMacs(PropertyResolver options, PrintStream stderr) {
+ String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.MACS_CONFIG_PROP);
+ return GenericUtils.isEmpty(argVal)
+ ? Collections.emptyList()
+ : setupMacs(SshConfigFileReader.MACS_CONFIG_PROP, argVal, null, stderr);
+ }
+
+ public static List<NamedFactory<Mac>> setupMacs(String argName, String argVal, List<NamedFactory<Mac>> current, PrintStream stderr) {
+ if (GenericUtils.size(current) > 0) {
+ showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+ return null;
+ }
+
+ BuiltinMacs.ParseResult result = BuiltinMacs.parseMacsList(argVal);
+ Collection<? extends NamedFactory<Mac>> available = result.getParsedFactories();
+ if (GenericUtils.isEmpty(available)) {
+ showError(stderr, "No known MACs in " + argVal);
+ return null;
+ }
+
+ Collection<String> unsupported = result.getUnsupportedFactories();
+ if (GenericUtils.size(unsupported) > 0) {
+ stderr.append("Ignored unsupported MACs: ").println(GenericUtils.join(unsupported, ','));
+ }
+
+ return new ArrayList<>(available);
+ }
+
+ public static List<NamedFactory<Cipher>> setupCiphers(PropertyResolver options, PrintStream stderr) {
+ String argVal = PropertyResolverUtils.getString(options, SshConfigFileReader.CIPHERS_CONFIG_PROP);
+ return GenericUtils.isEmpty(argVal)
+ ? Collections.emptyList()
+ : setupCiphers(SshConfigFileReader.CIPHERS_CONFIG_PROP, argVal, null, stderr);
+ }
+
+ // returns null - e.g., re-specified or no supported cipher found
+ public static List<NamedFactory<Cipher>> setupCiphers(String argName, String argVal, List<NamedFactory<Cipher>> current, PrintStream stderr) {
+ if (GenericUtils.size(current) > 0) {
+ showError(stderr, argName + " option value re-specified: " + NamedResource.getNames(current));
+ return null;
+ }
+
+ BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(argVal);
+ Collection<? extends NamedFactory<Cipher>> available = result.getParsedFactories();
+ if (GenericUtils.isEmpty(available)) {
+ showError(stderr, "No known ciphers in " + argVal);
+ return null;
+ }
+
+ Collection<String> unsupported = result.getUnsupportedFactories();
+ if (GenericUtils.size(unsupported) > 0) {
+ stderr.append("Ignored unsupported ciphers: ").println(GenericUtils.join(unsupported, ','));
+ }
+
+ return new ArrayList<>(available);
+ }
+
+ public static Handler setupLogging(Level level, final PrintStream stdout, final PrintStream stderr, final OutputStream outputStream) {
+ Handler fh = new ConsoleHandler() {
+ {
+ setOutputStream(outputStream); // override the default (stderr)
+ }
+
+ @Override
+ protected synchronized void setOutputStream(OutputStream out) throws SecurityException {
+ if ((out == stdout) || (out == stderr)) {
+ super.setOutputStream(new NoCloseOutputStream(out));
+ } else {
+ super.setOutputStream(out);
+ }
+ }
+ };
+ fh.setLevel(Level.FINEST);
+ fh.setFormatter(new Formatter() {
+ @Override
+ public String format(LogRecord record) {
+ String message = formatMessage(record);
+ String throwable = "";
+ Throwable t = record.getThrown();
+ if (t != null) {
+ StringWriter sw = new StringWriter();
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ pw.println();
+ t.printStackTrace(pw); // NOPMD
+ }
+ throwable = sw.toString();
+ }
+ return String.format("%1$tY-%1$tm-%1$td: %2$-7.7s: %3$-32.32s: %4$s%5$s%n",
+ new Date(record.getMillis()), record.getLevel().getName(),
+ record.getLoggerName(), message, throwable);
+ }
+ });
+
+ Logger root = Logger.getLogger("");
+ for (Handler handler : root.getHandlers()) {
+ root.removeHandler(handler);
+ }
+ root.addHandler(fh);
+ root.setLevel(level);
+ return fh;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
new file mode 100644
index 0000000..9ebca2c
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.logging.Level;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ChannelShell;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshClientMain extends SshClientCliSupport {
+ protected SshClientMain() {
+ super(); // in case someone wants to extend it
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static void main(String[] args) throws Exception {
+ PrintStream stdout = System.out;
+ PrintStream stderr = System.err;
+ boolean agentForward = false;
+ List<String> command = null;
+ int socksPort = -1;
+ int numArgs = GenericUtils.length(args);
+ boolean error = false;
+ String target = null;
+ Level level = Level.WARNING;
+ OutputStream logStream = stderr;
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ // handled by 'setupClientSession'
+ if (GenericUtils.isEmpty(command) && isArgumentedOption("-p", argName)) {
+ if ((i + 1) >= numArgs) {
+ error = showError(stderr, "option requires an argument: " + argName);
+ break;
+ }
+
+ i++;
+ continue;
+ }
+
+ // verbosity handled separately
+ if (GenericUtils.isEmpty(command) && ("-v".equals(argName) || "-vv".equals(argName) || "-vvv".equals(argName))) {
+ continue;
+ }
+
+ if (GenericUtils.isEmpty(command) && "-D".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ error = showError(stderr, "option requires an argument: " + argName);
+ break;
+ }
+ if (socksPort > 0) {
+ error = showError(stderr, argName + " option value re-specified: " + socksPort);
+ break;
+ }
+
+ socksPort = Integer.parseInt(args[++i]);
+ if (socksPort <= 0) {
+ error = showError(stderr, "Bad option value for " + argName + ": " + socksPort);
+ break;
+ }
+ } else if (GenericUtils.isEmpty(command) && "-A".equals(argName)) {
+ agentForward = true;
+ } else if (GenericUtils.isEmpty(command) && "-a".equals(argName)) {
+ agentForward = false;
+ } else {
+ level = resolveLoggingVerbosity(args, i);
+ logStream = resolveLoggingTargetStream(stdout, stderr, args, i);
+ if (logStream == null) {
+ error = true;
+ break;
+ }
+ if (GenericUtils.isEmpty(command) && target == null) {
+ target = argName;
+ } else {
+ if (command == null) {
+ command = new ArrayList<>();
+ }
+ command.add(argName);
+ }
+ }
+ }
+
+ ClientSession session = null;
+ try (BufferedReader stdin = new BufferedReader(
+ new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
+ if (!error) {
+ setupLogging(level, stdout, stderr, logStream);
+
+ session = setupClientSession(SSH_CLIENT_PORT_OPTION, stdin, stdout, stderr, args);
+ if (session == null) {
+ error = true;
+ }
+ }
+
+ if (error) {
+ System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-E logoutputfile] [-D socksPort]"
+ + " [-l login] [" + SSH_CLIENT_PORT_OPTION + " port] [-o option=value]"
+ + " [-w password] [-c cipherslist] [-m maclist] [-C]"
+ + " hostname/user@host [command]");
+ System.exit(-1);
+ return;
+ }
+
+ try (SshClient client = (SshClient) session.getFactoryManager()) {
+ /*
+ String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+ if (authSock == null && provider != null) {
+ Iterable<KeyPair> keys = provider.loadKeys();
+ AgentServer server = new AgentServer();
+ authSock = server.start();
+ SshAgent agent = new AgentClient(authSock);
+ for (KeyPair key : keys) {
+ agent.addIdentity(key, "");
+ }
+ agent.close();
+ props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
+ }
+ */
+
+ try {
+ if (socksPort >= 0) {
+ session.startDynamicPortForwarding(new SshdSocketAddress(SshdSocketAddress.LOCALHOST_NAME, socksPort));
+ Thread.sleep(Long.MAX_VALUE);
+ } else {
+ ClientChannel channel;
+ if (GenericUtils.isEmpty(command)) {
+ channel = session.createShellChannel();
+ ((ChannelShell) channel).setAgentForwarding(agentForward);
+ channel.setIn(new NoCloseInputStream(System.in));
+ } else {
+ channel = session.createExecChannel(String.join(" ", command).trim());
+ }
+
+ try (OutputStream channelOut = new NoCloseOutputStream(System.out);
+ OutputStream channelErr = new NoCloseOutputStream(System.err)) {
+ channel.setOut(channelOut);
+ channel.setErr(channelErr);
+ channel.open().await(); // TODO use verify and a configurable timeout
+ channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
+ } finally {
+ channel.close();
+ }
+ session.close(false);
+ }
+ } finally {
+ client.stop();
+ }
+ } finally {
+ session.close();
+ }
+ } finally {
+ if (logStream != null && logStream != stdout && logStream != stderr) {
+ logStream.close();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
new file mode 100644
index 0000000..b5350f0
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
@@ -0,0 +1,741 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Writer;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.Channel;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.kex.KexProposalOption;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.logging.LoggingUtils;
+import org.apache.sshd.common.util.logging.SimplifiedLog;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * A naive implementation of <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh-keyscan&sektion=1">ssh-keyscan(1)</A>
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifier, SessionListener, SimplifiedLog {
+ /**
+ * Default key types if not overridden from the command line
+ */
+ public static final List<String> DEFAULT_KEY_TYPES =
+ Collections.unmodifiableList(Arrays.asList(BuiltinIdentities.Constants.RSA, BuiltinIdentities.Constants.ECDSA));
+ public static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
+ public static final Level DEFAULT_LEVEL = Level.INFO;
+
+ private final AtomicBoolean open = new AtomicBoolean(true);
+ private SshClient client;
+ private int port;
+ private long timeout;
+ private List<String> keyTypes;
+ private InputStream input;
+ private Level level;
+ private final Map<String, String> currentHostFingerprints = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+ public SshKeyScanMain() {
+ super();
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public InputStream getInputStream() {
+ return input;
+ }
+
+ public void setInputStream(InputStream input) {
+ this.input = input;
+ }
+
+ public List<String> getKeyTypes() {
+ return keyTypes;
+ }
+
+ public void setKeyTypes(List<String> keyTypes) {
+ this.keyTypes = keyTypes;
+ }
+
+ public long getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ public Level getLogLevel() {
+ return level;
+ }
+
+ public void setLogLevel(Level level) {
+ this.level = level;
+ }
+
+ @Override
+ public void log(Level level, Object message, Throwable t) {
+ if (isEnabled(level)) {
+ PrintStream ps = System.out;
+ if ((t != null) || Level.SEVERE.equals(level) || Level.WARNING.equals(level)) {
+ ps = System.err;
+ }
+
+ ps.append('\t').println(message);
+ if (t != null) {
+ ps.append("\t\t").append(t.getClass().getSimpleName()).append(": ").println(t.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public boolean isEnabled(Level level) {
+ return LoggingUtils.isLoggable(level, getLogLevel());
+ }
+
+ @Override
+ public Void call() throws Exception {
+ ValidateUtils.checkTrue(isOpen(), "Scanner is closed");
+
+ Collection<String> typeNames = getKeyTypes();
+ Map<String, List<KeyPair>> pairsMap = createKeyPairs(typeNames);
+ /*
+ * We will need to switch signature factories for each specific
+ * key type in order to force the server to send ONLY that specific
+ * key, so pre-create the factories map according to the selected
+ * key types
+ */
+ SortedMap<String, List<NamedFactory<Signature>>> sigFactories = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ Collection<String> sigTypes = GenericUtils.asSortedSet(sigFactories.comparator(), pairsMap.keySet());
+ for (String kt : sigTypes) {
+ List<NamedFactory<Signature>> factories = resolveSignatureFactories(kt);
+ if (GenericUtils.isEmpty(factories)) {
+ if (isEnabled(Level.FINEST)) {
+ log(Level.FINEST, "Skip empty signature factories for " + kt);
+ }
+ pairsMap.remove(kt);
+ } else {
+ sigFactories.put(kt, factories);
+ }
+ }
+
+ ValidateUtils.checkTrue(!GenericUtils.isEmpty(pairsMap), "No client key pairs");
+ ValidateUtils.checkTrue(!GenericUtils.isEmpty(sigFactories), "No signature factories");
+
+ Exception err = null;
+ try {
+ ValidateUtils.checkTrue(client == null, "Client still active");
+ client = SshClient.setUpDefaultClient();
+ client.setServerKeyVerifier(this);
+
+ try (BufferedReader rdr = new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8))) {
+ client.setUserInteraction(new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
+ return null;
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+ return null;
+ }
+
+ @Override
+ public void serverVersionInfo(ClientSession session, List<String> lines) {
+ if (isEnabled(Level.FINE) && GenericUtils.isNotEmpty(lines)) {
+ for (String l : lines) {
+ log(Level.FINE, "Server Info: " + l);
+ }
+ }
+ }
+
+ @Override
+ public void welcome(ClientSession session, String banner, String lang) {
+ if (isEnabled(Level.FINE) && GenericUtils.isNotEmpty(banner)) {
+ String[] lines = GenericUtils.split(banner, '\n');
+ for (String l : lines) {
+ log(Level.FINE, "Welcome[" + lang + "]: " + l);
+ }
+ }
+ }
+ });
+
+ client.start();
+ for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
+ line = GenericUtils.replaceWhitespaceAndTrim(line);
+
+ String[] hosts = GenericUtils.split(line, ',');
+ if (GenericUtils.isEmpty(hosts)) {
+ continue;
+ }
+
+ for (String h : hosts) {
+ if (!isOpen()) {
+ throw new InterruptedIOException("Closed while preparing to contact host=" + h);
+ }
+
+ try {
+ resolveServerKeys(client, h, pairsMap, sigFactories);
+ } catch (Exception e) {
+ // check if interrupted while scanning host keys
+ if (e instanceof InterruptedIOException) {
+ throw e;
+ }
+
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Failed to retrieve keys from " + h, e);
+ }
+ err = GenericUtils.accumulateException(err, e);
+ } finally {
+ currentHostFingerprints.clear();
+ }
+ }
+ }
+ }
+ } finally {
+ try {
+ close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+ }
+
+ if (err != null) {
+ throw err;
+ }
+
+ return null;
+ }
+
+ protected void resolveServerKeys(SshClient client, String host,
+ Map<String, List<KeyPair>> pairsMap, Map<String, List<NamedFactory<Signature>>> sigFactories)
+ throws IOException {
+ // Cannot use forEach because of the potential for throwing IOException by the invoked code
+ for (Map.Entry<String, List<KeyPair>> pe : pairsMap.entrySet()) {
+ String kt = pe.getKey();
+ if (!isOpen()) {
+ throw new InterruptedIOException("Closed while attempting to retrieve key type=" + kt + " from " + host);
+ }
+
+ List<NamedFactory<Signature>> current = client.getSignatureFactories();
+ try {
+ /*
+ * Replace whatever factories there are right now with the
+ * specific one for the key in order to extract only the
+ * specific host key type
+ */
+ List<NamedFactory<Signature>> forced = sigFactories.get(kt);
+ client.setSignatureFactories(forced);
+ resolveServerKeys(client, host, kt, pe.getValue());
+ } catch (Exception e) {
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Failed to resolve key=" + kt + " for " + host);
+ }
+
+ if (e instanceof ConnectException) {
+ return; // makes no sense to try again with another key type...
+ }
+ } finally {
+ client.setSignatureFactories(current); // don't have to, but be nice...
+ }
+ }
+ }
+
+ protected void resolveServerKeys(SshClient client, String host, String kt, List<KeyPair> ids) throws Exception {
+ int connectPort = getPort();
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Connecting to " + host + ":" + connectPort + " to retrieve key type=" + kt);
+ }
+
+ ConnectFuture future = client.connect(UUID.randomUUID().toString(), host, connectPort);
+ long waitTime = getTimeout();
+ if (!future.await(waitTime)) {
+ throw new ConnectException("Failed to connect to " + host + ":" + connectPort
+ + " within " + waitTime + " msec."
+ + " to retrieve key type=" + kt);
+ }
+
+ try (ClientSession session = future.getSession()) {
+ IoSession ioSession = session.getIoSession();
+ SocketAddress remoteAddress = ioSession.getRemoteAddress();
+ String remoteLocation = toString(remoteAddress);
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Connected to " + remoteLocation + " to retrieve key type=" + kt);
+ }
+
+ try {
+ session.addSessionListener(this);
+ if (isEnabled(Level.FINER)) {
+ log(Level.FINER, "Authenticating with key type=" + kt + " to " + remoteLocation);
+ }
+
+ GenericUtils.forEach(ids, session::addPublicKeyIdentity);
+
+ try {
+ // shouldn't really succeed, but do it since key exchange occurs only on auth attempt
+ session.auth().verify(waitTime);
+ log(Level.WARNING, "Unexpected authentication success using key type=" + kt + " with " + remoteLocation);
+ } catch (Exception e) {
+ if (isEnabled(Level.FINER)) {
+ log(Level.FINER, "Failed to authenticate using key type=" + kt + " with " + remoteLocation);
+ }
+ } finally {
+ GenericUtils.forEach(ids, session::removePublicKeyIdentity);
+ }
+ } finally {
+ session.removeSessionListener(this);
+ }
+ }
+ }
+
+ @Override
+ public void sessionCreated(Session session) {
+ logSessionEvent(session, "Created");
+ }
+
+ @Override
+ public void sessionEvent(Session session, Event event) {
+ logSessionEvent(session, event);
+ if (isEnabled(Level.FINEST) && Event.KexCompleted.equals(event)) {
+ IoSession ioSession = session.getIoSession();
+ SocketAddress remoteAddress = ioSession.getRemoteAddress();
+ String remoteLocation = toString(remoteAddress);
+ for (KexProposalOption paramType : KexProposalOption.VALUES) {
+ String paramValue = session.getNegotiatedKexParameter(paramType);
+ log(Level.FINEST, remoteLocation + "[" + paramType.getDescription() + "]: " + paramValue);
+ }
+ }
+ }
+
+ @Override
+ public void sessionException(Session session, Throwable t) {
+ logSessionEvent(session, t);
+ }
+
+ @Override
+ public void sessionClosed(Session session) {
+ logSessionEvent(session, "Closed");
+ }
+
+ @Override
+ public void sessionNegotiationStart(
+ Session session, Map<KexProposalOption, String> clientProposal, Map<KexProposalOption, String> serverProposal) {
+ logSessionEvent(session, "sessionNegotiationStart");
+ logNegotiationProposal("c2s", clientProposal);
+ logNegotiationProposal("s2c", serverProposal);
+ }
+
+ protected void logNegotiationProposal(String type, Map<KexProposalOption, String> proposal) {
+ if (!isEnabled(Level.FINEST)) {
+ return;
+ }
+
+ proposal.forEach((option, value) -> log(Level.FINEST, option.getDescription() + "[" + type + "]: " + value));
+ }
+
+ @Override
+ public void sessionNegotiationEnd(Session session, Map<KexProposalOption, String> clientProposal,
+ Map<KexProposalOption, String> serverProposal, Map<KexProposalOption, String> negotiatedOptions,
+ Throwable reason) {
+ if (reason == null) {
+ logSessionEvent(session, "sessionNegotiationStart");
+ } else {
+ logSessionEvent(session, reason);
+ }
+ }
+
+ protected void logSessionEvent(Session session, Object event) {
+ if (isEnabled(Level.FINEST)) {
+ IoSession ioSession = session.getIoSession();
+ SocketAddress remoteAddress = ioSession.getRemoteAddress();
+ log(Level.FINEST, "Session " + toString(remoteAddress) + " event: " + event);
+ }
+ }
+
+ @Override
+ public boolean verifyServerKey(ClientSession sshClientSession, SocketAddress remoteAddress, PublicKey serverKey) {
+ String remoteLocation = toString(remoteAddress);
+ String extra = KeyUtils.getFingerPrint(serverKey);
+ try {
+ String keyType = KeyUtils.getKeyType(serverKey);
+ String current = GenericUtils.isEmpty(keyType) ? null : currentHostFingerprints.get(keyType);
+ if (Objects.equals(current, extra)) {
+ if (isEnabled(Level.FINER)) {
+ log(Level.FINER, "verifyServerKey(" + remoteLocation + ")[" + keyType + "] skip existing key: " + extra);
+ }
+ } else {
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "verifyServerKey(" + remoteLocation + ")[" + keyType + "] found new key: " + extra);
+ }
+
+ writeServerKey(remoteLocation, keyType, serverKey);
+
+ if (!GenericUtils.isEmpty(keyType)) {
+ currentHostFingerprints.put(keyType, extra);
+ }
+ }
+ } catch (Exception e) {
+ log(Level.SEVERE, "Failed to output the public key " + extra + " from " + remoteLocation, e);
+ }
+
+ return true;
+ }
+
+ protected void writeServerKey(String remoteLocation, String keyType, PublicKey serverKey) throws Exception {
+ StringBuilder sb = new StringBuilder(256).append(remoteLocation).append(' ');
+ PublicKeyEntry.appendPublicKeyEntry(sb, serverKey);
+ log(Level.INFO, sb);
+ }
+
+ private static String toString(SocketAddress addr) {
+ if (addr == null) {
+ return null;
+ } else if (addr instanceof InetSocketAddress) {
+ return ((InetSocketAddress) addr).getHostString();
+ } else if (addr instanceof SshdSocketAddress) {
+ return ((SshdSocketAddress) addr).getHostName();
+ } else {
+ return addr.toString();
+ }
+ }
+
+ protected List<NamedFactory<Signature>> resolveSignatureFactories(String keyType) throws GeneralSecurityException {
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Resolve signature factories for " + keyType);
+ }
+
+ if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
+ return Collections.singletonList(BuiltinSignatures.rsa);
+ } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
+ return Collections.singletonList(BuiltinSignatures.dsa);
+ } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
+ List<NamedFactory<Signature>> factories = new ArrayList<>(ECCurves.NAMES.size());
+ for (String n : ECCurves.NAMES) {
+ if (isEnabled(Level.FINER)) {
+ log(Level.FINER, "Resolve signature factory for curve=" + n);
+ }
+
+ NamedFactory<Signature> f =
+ ValidateUtils.checkNotNull(BuiltinSignatures.fromString(n), "Unknown curve signature: %s", n);
+ factories.add(f);
+ }
+
+ return factories;
+ } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyType)) {
+ return Collections.singletonList(BuiltinSignatures.ed25519);
+ } else {
+ throw new NoSuchAlgorithmException("Unknown key type: " + keyType);
+ }
+ }
+
+ protected Map<String, List<KeyPair>> createKeyPairs(Collection<String> typeNames) throws GeneralSecurityException {
+ if (GenericUtils.isEmpty(typeNames)) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, List<KeyPair>> pairsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (String kt : typeNames) {
+ if ("*".equalsIgnoreCase(kt) || "all".equalsIgnoreCase(kt)) {
+ ValidateUtils.checkTrue(typeNames.size() == 1, "Wildcard key type must be the only one specified: %s", typeNames);
+ return createKeyPairs(BuiltinIdentities.NAMES);
+ }
+
+ if (pairsMap.containsKey(kt)) {
+ log(Level.WARNING, "Key type " + kt + " re-specified");
+ continue;
+ }
+
+ List<KeyPair> kps = createKeyPairs(kt);
+ if (GenericUtils.isEmpty(kps)) {
+ log(Level.WARNING, "No key-pairs generated for key type " + kt);
+ continue;
+ }
+
+ pairsMap.put(kt, kps);
+ }
+
+ return pairsMap;
+ }
+
+ protected List<KeyPair> createKeyPairs(String keyType) throws GeneralSecurityException {
+ if (isEnabled(Level.FINE)) {
+ log(Level.FINE, "Generate key pairs for " + keyType);
+ }
+
+ if (BuiltinIdentities.Constants.RSA.equalsIgnoreCase(keyType)) {
+ return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_RSA, 1024));
+ } else if (BuiltinIdentities.Constants.DSA.equalsIgnoreCase(keyType)) {
+ return Collections.singletonList(KeyUtils.generateKeyPair(KeyPairProvider.SSH_DSS, 512));
+ } else if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyType)) {
+ if (!SecurityUtils.isECCSupported()) {
+ throw new NoSuchAlgorithmException("ECC not supported: " + keyType);
+ }
+
+ List<KeyPair> kps = new ArrayList<>(ECCurves.NAMES.size());
+ for (ECCurves curve : ECCurves.VALUES) {
+ String curveName = curve.getName();
+ if (isEnabled(Level.FINER)) {
+ log(Level.FINER, "Generate key pair for curve=" + curveName);
+ }
+
+ kps.add(KeyUtils.generateKeyPair(curve.getKeyType(), curve.getKeySize()));
+ }
+
+ return kps;
+ } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyType)) {
+ if (!SecurityUtils.isEDDSACurveSupported()) {
+ throw new NoSuchAlgorithmException("EDDSA curves not supported: " + keyType);
+ }
+
+ KeyPairGenerator g = SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
+ return Collections.singletonList(g.generateKeyPair());
+ } else {
+ throw new InvalidKeySpecException("Unknown key type: " + keyType);
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!open.getAndSet(false)) {
+ return; // already closed
+ }
+
+ IOException err = null;
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ } finally {
+ input = null;
+ }
+ }
+
+ if (client != null) {
+ try {
+ client.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ } finally {
+ try {
+ client.stop();
+ } finally {
+ client = null;
+ }
+ }
+ }
+ if (err != null) {
+ throw err;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ // returns a List of the hosts to be contacted
+ public static List<String> parseCommandLineArguments(SshKeyScanMain scanner, String... args) throws IOException {
+ int numArgs = GenericUtils.length(args);
+ for (int index = 0; index < numArgs; index++) {
+ String optName = args[index];
+ if ("-f".equals(optName)) {
+ index++;
+ ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
+ ValidateUtils.checkTrue(scanner.getInputStream() == null, "%s option re-specified", optName);
+
+ String filePath = args[index];
+ if ("-".equals(filePath)) {
+ scanner.setInputStream(new NoCloseInputStream(System.in));
+ } else {
+ scanner.setInputStream(new FileInputStream(filePath));
+ }
+ } else if ("-t".equals(optName)) {
+ index++;
+ ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
+ ValidateUtils.checkTrue(GenericUtils.isEmpty(scanner.getKeyTypes()), "%s option re-specified", optName);
+
+ String typeList = args[index];
+ String[] types = GenericUtils.split(typeList, ',');
+ ValidateUtils.checkTrue(GenericUtils.length(types) > 0, "No types specified for %s", optName);
+ scanner.setKeyTypes(Arrays.asList(types));
+ } else if ("-p".equals(optName)) {
+ index++;
+ ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
+ ValidateUtils.checkTrue(scanner.getPort() <= 0, "%s option re-specified", optName);
+
+ String portValue = args[index];
+ int port = Integer.parseInt(portValue);
+ ValidateUtils.checkTrue((port > 0) && (port <= 0xFFFF), "Bad port: %s", portValue);
+ scanner.setPort(port);
+ } else if ("-T".equals(optName)) {
+ index++;
+ ValidateUtils.checkTrue(index < numArgs, "Missing %s option argument", optName);
+ ValidateUtils.checkTrue(scanner.getTimeout() <= 0, "%s option re-specified", optName);
+
+ String timeoutValue = args[index];
+ long timeout = Long.parseLong(timeoutValue);
+ ValidateUtils.checkTrue(timeout > 0L, "Bad timeout: %s", timeoutValue);
+ scanner.setTimeout(timeout);
+ } else if ("-v".equals(optName)) {
+ ValidateUtils.checkTrue(scanner.getLogLevel() == null, "%s option re-specified", optName);
+ scanner.setLogLevel(Level.FINEST);
+ } else { // stop at first non-option - assume the rest are host names/addresses
+ ValidateUtils.checkTrue(optName.charAt(0) != '-', "Unknown option: %s", optName);
+
+ int remaining = numArgs - index;
+ if (remaining == 1) {
+ return Collections.singletonList(optName);
+ }
+
+ List<String> hosts = new ArrayList<>(remaining);
+ for (; index < numArgs; index++) {
+ hosts.add(args[index]);
+ }
+
+ return hosts;
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ /* -------------------------------------------------------------------- */
+
+ public static <S extends SshKeyScanMain> S setInputStream(S scanner, Collection<String> hosts) throws IOException {
+ if (GenericUtils.isEmpty(hosts)) {
+ Objects.requireNonNull(scanner.getInputStream(), "No hosts or file specified");
+ } else {
+ ValidateUtils.checkTrue(scanner.getInputStream() == null, "Both hosts and file specified");
+
+ // convert the hosts from the arguments into a "file" - one host per line
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(hosts.size() * 32)) {
+ try (Writer w = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
+ for (String h : hosts) {
+ w.append(h).append(IoUtils.EOL);
+ }
+ }
+
+ byte[] data = baos.toByteArray();
+ scanner.setInputStream(new ByteArrayInputStream(data));
+ }
+ }
+
+ return scanner;
+ }
+
+ public static <S extends SshKeyScanMain> S initializeScanner(S scanner, Collection<String> hosts) throws IOException {
+ setInputStream(scanner, hosts);
+ if (scanner.getPort() <= 0) {
+ scanner.setPort(SshConfigFileReader.DEFAULT_PORT);
+ }
+
+ if (scanner.getTimeout() <= 0L) {
+ scanner.setTimeout(DEFAULT_TIMEOUT);
+ }
+
+ if (GenericUtils.isEmpty(scanner.getKeyTypes())) {
+ scanner.setKeyTypes(DEFAULT_KEY_TYPES);
+ }
+
+ if (scanner.getLogLevel() == null) {
+ scanner.setLogLevel(DEFAULT_LEVEL);
+ }
+
+ return scanner;
+ }
+
+ /* -------------------------------------------------------------------- */
+
+ public static void main(String[] args) throws Exception {
+ try (SshKeyScanMain scanner = new SshKeyScanMain()) {
+ Collection<String> hosts = parseCommandLineArguments(scanner, args);
+ initializeScanner(scanner, hosts);
+ scanner.call();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
new file mode 100644
index 0000000..c0ccc32
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.util.test.BaseTestSupport;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ChannelExecMain extends BaseTestSupport {
+ public static void doExecuteCommands(
+ BufferedReader stdin, PrintStream stdout, PrintStream stderr, ClientSession session) throws Exception {
+ while (true) {
+ stdout.print("> ");
+
+ String command = stdin.readLine();
+ if ("q".equalsIgnoreCase(command) || "quit".equalsIgnoreCase(command)) {
+ break;
+ }
+ if (GenericUtils.isEmpty(command)) {
+ continue;
+ }
+
+ while (true) {
+ try {
+ String response = session.executeRemoteCommand(command);
+ String[] lines = GenericUtils.split(response, '\n');
+ for (String l : lines) {
+ stdout.append('\t').println(l);
+ }
+ } catch (Exception e) {
+ stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
+ }
+
+ stdout.append("Execute ").append(command).print(" again [y]/n ");
+ String ans = stdin.readLine();
+ if ((GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) != 'y')) {
+ break;
+ }
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ PrintStream stdout = System.out;
+ PrintStream stderr = System.err;
+ try (BufferedReader stdin = new BufferedReader(
+ new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
+ ClientSession session = SshClientCliSupport.setupClientSession("-P", stdin, stdout, stderr, args);
+ if (session == null) {
+ System.err.println("usage: channelExec [-i identity] [-l login] [-P port] [-o option=value]"
+ + " [-w password] [-c cipherlist] [-m maclist] [-C] hostname/user@host");
+ System.exit(-1);
+ return;
+ }
+
+ try (SshClient client = (SshClient) session.getFactoryManager()) {
+ try (ClientSession clientSession = session) {
+ doExecuteCommands(stdin, stdout, stderr, session);
+ } finally {
+ client.stop();
+ }
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/test/java/org/apache/sshd/cli/client/ScpCommandMainDevelopment.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ScpCommandMainDevelopment.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ScpCommandMainDevelopment.java
new file mode 100644
index 0000000..833dca2
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ScpCommandMainDevelopment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+/**
+ * Just a test class used to invoke {@link ScpCommandMain#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class ScpCommandMainDevelopment {
+ private ScpCommandMainDevelopment() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ ScpCommandMain.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/test/java/org/apache/sshd/cli/client/SftpCommandMainDevelopment.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/SftpCommandMainDevelopment.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SftpCommandMainDevelopment.java
new file mode 100644
index 0000000..07fb170
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SftpCommandMainDevelopment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+/**
+ * Just a test class used to invoke {@link SftpCommandMain#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SftpCommandMainDevelopment {
+ private SftpCommandMainDevelopment() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ SftpCommandMain.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshClientMainDevelopment.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshClientMainDevelopment.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshClientMainDevelopment.java
new file mode 100644
index 0000000..e36776a
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshClientMainDevelopment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+/**
+ * Just a test class used to invoke {@link SshClientMain#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SshClientMainDevelopment {
+ private SshClientMainDevelopment() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ SshClientMain.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshKeyScanMainDevelopment.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshKeyScanMainDevelopment.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshKeyScanMainDevelopment.java
new file mode 100644
index 0000000..9d38925
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/SshKeyScanMainDevelopment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client;
+
+/**
+ * Just a test class used to invoke {@link SshKeyScanMain#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SshKeyScanMainDevelopment {
+ private SshKeyScanMainDevelopment() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ SshKeyScanMain.main(args);
+ }
+}
[3/6] mina-sshd git commit: [SSHD-816] Moved all 'main' code for
client commands to sshd-cli module
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
deleted file mode 100644
index 61a83ec..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
+++ /dev/null
@@ -1,920 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client.subsystem.sftp;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.nio.channels.Channel;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.logging.Level;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
-import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
-import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
-import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.kex.KexProposalOption;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
-import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
-import org.apache.sshd.common.subsystem.sftp.extensions.openssh.StatVfsExtensionParser;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-
-/**
- * Implements a simple command line SFTP client similar to the Linux one
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpCommand implements Channel {
- /**
- * Command line option used to indicate a non-default port number
- */
- public static final String SFTP_PORT_OPTION = "-P";
-
- private final SftpClient client;
- private final Map<String, CommandExecutor> commandsMap;
- private String cwdRemote;
- private String cwdLocal;
-
- @SuppressWarnings("synthetic-access")
- public SftpCommand(SftpClient client) {
- this.client = Objects.requireNonNull(client, "No client");
-
- Map<String, CommandExecutor> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- for (CommandExecutor e : Arrays.asList(
- new ExitCommandExecutor(),
- new PwdCommandExecutor(),
- new InfoCommandExecutor(),
- new SessionCommandExecutor(),
- new VersionCommandExecutor(),
- new CdCommandExecutor(),
- new LcdCommandExecutor(),
- new MkdirCommandExecutor(),
- new LsCommandExecutor(),
- new LStatCommandExecutor(),
- new ReadLinkCommandExecutor(),
- new RmCommandExecutor(),
- new RmdirCommandExecutor(),
- new RenameCommandExecutor(),
- new StatVfsCommandExecutor(),
- new GetCommandExecutor(),
- new PutCommandExecutor(),
- new HelpCommandExecutor()
- )) {
- String name = e.getName();
- ValidateUtils.checkTrue(map.put(name, e) == null, "Multiple commands named '%s'", name);
- }
- commandsMap = Collections.unmodifiableMap(map);
- cwdLocal = System.getProperty("user.dir");
- }
-
- public final SftpClient getClient() {
- return client;
- }
-
- public void doInteractive(BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- SftpClient sftp = getClient();
- setCurrentRemoteDirectory(sftp.canonicalPath("."));
- while (true) {
- stdout.append(getCurrentRemoteDirectory()).append(" > ").flush();
- String line = stdin.readLine();
- if (line == null) { // EOF
- break;
- }
-
- line = GenericUtils.replaceWhitespaceAndTrim(line);
- if (GenericUtils.isEmpty(line)) {
- continue;
- }
-
- String cmd;
- String args;
- int pos = line.indexOf(' ');
- if (pos > 0) {
- cmd = line.substring(0, pos);
- args = line.substring(pos + 1).trim();
- } else {
- cmd = line;
- args = "";
- }
-
- CommandExecutor exec = commandsMap.get(cmd);
- try {
- if (exec == null) {
- stderr.append("Unknown command: ").println(line);
- } else {
- try {
- if (exec.executeCommand(args, stdin, stdout, stderr)) {
- break;
- }
- } catch (Exception e) {
- stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
- } finally {
- stdout.flush();
- }
- }
- } finally {
- stderr.flush(); // just makings sure
- }
- }
- }
-
- protected String resolveLocalPath(String pathArg) {
- String cwd = getCurrentLocalDirectory();
- if (GenericUtils.isEmpty(pathArg)) {
- return cwd;
- }
-
- if (OsUtils.isWin32()) {
- if ((pathArg.length() >= 2) && (pathArg.charAt(1) == ':')) {
- return pathArg;
- }
- } else {
- if (pathArg.charAt(0) == '/') {
- return pathArg;
- }
- }
-
- return cwd + File.separator + pathArg.replace('/', File.separatorChar);
- }
-
- protected String resolveRemotePath(String pathArg) {
- String cwd = getCurrentRemoteDirectory();
- if (GenericUtils.isEmpty(pathArg)) {
- return cwd;
- }
-
- if (pathArg.charAt(0) == '/') {
- return pathArg;
- } else {
- return cwd + "/" + pathArg;
- }
- }
-
- protected <A extends Appendable> A appendFileAttributes(A stdout, SftpClient sftp, String path, Attributes attrs) throws IOException {
- stdout.append('\t').append(Long.toString(attrs.getSize()))
- .append('\t').append(SftpFileSystemProvider.getRWXPermissions(attrs.getPermissions()));
- if (attrs.isSymbolicLink()) {
- String linkValue = sftp.readLink(path);
- stdout.append(" => ")
- .append('(').append(attrs.isDirectory() ? "dir" : "file").append(')')
- .append(' ').append(linkValue);
- }
-
- return stdout;
- }
-
- public String getCurrentRemoteDirectory() {
- return cwdRemote;
- }
-
- public void setCurrentRemoteDirectory(String path) {
- cwdRemote = path;
- }
-
- public String getCurrentLocalDirectory() {
- return cwdLocal;
- }
-
- public void setCurrentLocalDirectory(String path) {
- cwdLocal = path;
- }
-
- @Override
- public boolean isOpen() {
- return client.isOpen();
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- client.close();
- }
- }
-
- public interface CommandExecutor extends NamedResource {
- // return value is whether to stop running
- boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception;
- }
-
- //////////////////////////////////////////////////////////////////////////
-
- public static <A extends Appendable> A appendInfoValue(A sb, CharSequence name, Object value) throws IOException {
- sb.append('\t').append(name).append(": ").append(Objects.toString(value));
- return sb;
- }
-
- public static void main(String[] args) throws Exception {
- PrintStream stdout = System.out;
- PrintStream stderr = System.err;
- OutputStream logStream = stderr;
- try (BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
- Level level = SshClient.resolveLoggingVerbosity(args);
- logStream = SshClient.resolveLoggingTargetStream(stdout, stderr, args);
- if (logStream != null) {
- SshClient.setupLogging(level, stdout, stderr, logStream);
- }
-
- ClientSession session = (logStream == null) ? null : SshClient.setupClientSession(SFTP_PORT_OPTION, stdin, stdout, stderr, args);
- if (session == null) {
- System.err.println("usage: sftp [-v[v][v]] [-E logoutput] [-i identity]"
- + " [-l login] [" + SFTP_PORT_OPTION + " port] [-o option=value]"
- + " [-w password] [-c cipherlist] [-m maclist] [-C] hostname/user@host");
- System.exit(-1);
- return;
- }
-
- try {
- try (SftpCommand sftp = new SftpCommand(SftpClientFactory.instance().createSftpClient(session))) {
- sftp.doInteractive(stdin, stdout, stderr);
- }
- } finally {
- session.close();
- }
- } finally {
- if ((logStream != stdout) && (logStream != stderr)) {
- logStream.close();
- }
- }
- }
-
- private static class ExitCommandExecutor implements CommandExecutor {
- ExitCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "exit";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- stdout.println("Exiting");
- return true;
- }
- }
-
- private class PwdCommandExecutor implements CommandExecutor {
- protected PwdCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "pwd";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- stdout.append('\t').append("Remote: ").println(getCurrentRemoteDirectory());
- stdout.append('\t').append("Local: ").println(getCurrentLocalDirectory());
- return false;
- }
- }
-
- private class SessionCommandExecutor implements CommandExecutor {
- SessionCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "session";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- SftpClient sftp = getClient();
- ClientSession session = sftp.getSession();
- appendInfoValue(stdout, "Session ID", BufferUtils.toHex(session.getSessionId())).println();
- appendInfoValue(stdout, "Connect address", session.getConnectAddress()).println();
-
- IoSession ioSession = session.getIoSession();
- appendInfoValue(stdout, "Local address", ioSession.getLocalAddress()).println();
- appendInfoValue(stdout, "Remote address", ioSession.getRemoteAddress()).println();
-
- for (KexProposalOption option : KexProposalOption.VALUES) {
- appendInfoValue(stdout, option.getDescription(), session.getNegotiatedKexParameter(option)).println();
- }
-
- return false;
- }
- }
-
- private class InfoCommandExecutor implements CommandExecutor {
- InfoCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "info";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- SftpClient sftp = getClient();
- Session session = sftp.getSession();
- stdout.append('\t').println(session.getServerVersion());
-
- Map<String, byte[]> extensions = sftp.getServerExtensions();
- Map<String, ?> parsed = ParserUtils.parse(extensions);
- if (GenericUtils.size(extensions) > 0) {
- stdout.println();
- }
-
- extensions.forEach((name, value) -> {
- Object info = parsed.get(name);
-
- stdout.append('\t').append(name).append(": ");
- if (info == null) {
- stdout.println(BufferUtils.toHex(value));
- } else {
- stdout.println(info);
- }
- });
-
- return false;
- }
- }
-
- private class VersionCommandExecutor implements CommandExecutor {
- VersionCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "version";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- SftpClient sftp = getClient();
- stdout.append('\t').println(sftp.getVersion());
- return false;
- }
- }
-
- private class CdCommandExecutor extends PwdCommandExecutor {
- CdCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "cd";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
-
- String newPath = resolveRemotePath(args);
- SftpClient sftp = getClient();
- setCurrentRemoteDirectory(sftp.canonicalPath(newPath));
- return super.executeCommand("", stdin, stdout, stderr);
- }
- }
-
- private class LcdCommandExecutor extends PwdCommandExecutor {
- LcdCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "lcd";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- if (GenericUtils.isEmpty(args)) {
- setCurrentLocalDirectory(System.getProperty("user.home"));
- } else {
- Path path = Paths.get(resolveLocalPath(args)).normalize().toAbsolutePath();
- ValidateUtils.checkTrue(Files.exists(path), "No such local directory: %s", path);
- ValidateUtils.checkTrue(Files.isDirectory(path), "Path is not a directory: %s", path);
- setCurrentLocalDirectory(path.toString());
- }
-
- return super.executeCommand("", stdin, stdout, stderr);
- }
- }
-
- private class MkdirCommandExecutor implements CommandExecutor {
- MkdirCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "mkdir";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
-
- String path = resolveRemotePath(args);
- SftpClient sftp = getClient();
- sftp.mkdir(path);
- return false;
- }
- }
-
- private class LsCommandExecutor implements CommandExecutor {
- LsCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "ls";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- int numComps = GenericUtils.length(comps);
- String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
- String flags = (numComps >= 2) ? GenericUtils.trimToEmpty(comps[0]) : null;
- // ignore all flags
- if ((GenericUtils.length(pathArg) > 0) && (pathArg.charAt(0) == '-')) {
- flags = pathArg;
- pathArg = null;
- }
-
- String path = resolveRemotePath(pathArg);
- SftpClient sftp = getClient();
- int version = sftp.getVersion();
- boolean showLongName = (version == SftpConstants.SFTP_V3) && (GenericUtils.length(flags) > 1) && (flags.indexOf('l') > 0);
- for (SftpClient.DirEntry entry : sftp.readDir(path)) {
- String fileName = entry.getFilename();
- SftpClient.Attributes attrs = entry.getAttributes();
- appendFileAttributes(stdout.append('\t').append(fileName), sftp, path + "/" + fileName, attrs).println();
- if (showLongName) {
- stdout.append("\t\tlong-name: ").println(entry.getLongFilename());
- }
- }
-
- return false;
- }
- }
-
- private class RmCommandExecutor implements CommandExecutor {
- RmCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "rm";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- int numArgs = GenericUtils.length(comps);
- ValidateUtils.checkTrue(numArgs >= 1, "No arguments");
- ValidateUtils.checkTrue(numArgs <= 2, "Too many arguments: %s", args);
-
- String remotePath = comps[0];
- boolean recursive = false;
- boolean verbose = false;
- if (remotePath.charAt(0) == '-') {
- ValidateUtils.checkTrue(remotePath.length() > 1, "Missing flags specification: %s", args);
- ValidateUtils.checkTrue(numArgs == 2, "Missing remote directory: %s", args);
-
- for (int index = 1; index < remotePath.length(); index++) {
- char ch = remotePath.charAt(index);
- switch(ch) {
- case 'r' :
- recursive = true;
- break;
- case 'v':
- verbose = true;
- break;
- default:
- throw new IllegalArgumentException("Unknown flag (" + String.valueOf(ch) + ")");
- }
- }
- remotePath = comps[1];
- }
-
- String path = resolveRemotePath(remotePath);
- SftpClient sftp = getClient();
- if (recursive) {
- Attributes attrs = sftp.stat(path);
- ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path not a directory: %s", args);
- removeRecursive(sftp, path, attrs, stdout, verbose);
- } else {
- sftp.remove(path);
- if (verbose) {
- stdout.append('\t').append("Removed ").println(path);
- }
- }
-
- return false;
- }
-
- private void removeRecursive(SftpClient sftp, String path, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
- if (attrs.isDirectory()) {
- for (DirEntry entry : sftp.readDir(path)) {
- String name = entry.getFilename();
- if (".".equals(name) || "..".equals(name)) {
- continue;
- }
-
- removeRecursive(sftp, path + "/" + name, entry.getAttributes(), stdout, verbose);
- }
-
- sftp.rmdir(path);
- } else if (attrs.isRegularFile()) {
- sftp.remove(path);
- } else {
- if (verbose) {
- stdout.append('\t').append("Skip special file ").println(path);
- return;
- }
- }
-
- if (verbose) {
- stdout.append('\t').append("Removed ").println(path);
- }
- }
- }
-
- private class RmdirCommandExecutor implements CommandExecutor {
- RmdirCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "rmdir";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
-
- String path = resolveRemotePath(args);
- SftpClient sftp = getClient();
- sftp.rmdir(path);
- return false;
- }
- }
-
- private class RenameCommandExecutor implements CommandExecutor {
- RenameCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "rename";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- ValidateUtils.checkTrue(GenericUtils.length(comps) == 2, "Invalid number of arguments: %s", args);
-
- String oldPath = resolveRemotePath(GenericUtils.trimToEmpty(comps[0]));
- String newPath = resolveRemotePath(GenericUtils.trimToEmpty(comps[1]));
- SftpClient sftp = getClient();
- sftp.rename(oldPath, newPath);
- return false;
- }
- }
-
- private class StatVfsCommandExecutor implements CommandExecutor {
- StatVfsCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return StatVfsExtensionParser.NAME;
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- int numArgs = GenericUtils.length(comps);
- ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
-
- SftpClient sftp = getClient();
- OpenSSHStatPathExtension ext = sftp.getExtension(OpenSSHStatPathExtension.class);
- ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName());
-
- String remPath = resolveRemotePath((numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
- OpenSSHStatExtensionInfo info = ext.stat(remPath);
- Field[] fields = info.getClass().getFields();
- for (Field f : fields) {
- String name = f.getName();
- int mod = f.getModifiers();
- if (Modifier.isStatic(mod)) {
- continue;
- }
-
- Object value = f.get(info);
- stdout.append('\t').append(name).append(": ").println(value);
- }
-
- return false;
- }
- }
-
- private class LStatCommandExecutor implements CommandExecutor {
- LStatCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "lstat";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
-
- String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
- SftpClient client = getClient();
- Attributes attrs = client.lstat(path);
- appendFileAttributes(stdout, client, path, attrs).println();
- return false;
- }
- }
-
- private class ReadLinkCommandExecutor implements CommandExecutor {
- ReadLinkCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "readlink";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- String[] comps = GenericUtils.split(args, ' ');
- ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
-
- String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
- SftpClient client = getClient();
- String linkData = client.readLink(path);
- stdout.append('\t').println(linkData);
- return false;
- }
- }
-
- private class HelpCommandExecutor implements CommandExecutor {
- HelpCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "help";
- }
-
- @Override
- @SuppressWarnings("synthetic-access")
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- for (String cmd : commandsMap.keySet()) {
- stdout.append('\t').println(cmd);
- }
- return false;
- }
- }
-
- private abstract class TransferCommandExecutor implements CommandExecutor {
- protected TransferCommandExecutor() {
- super();
- }
-
- protected void createDirectories(SftpClient sftp, String remotePath) throws IOException {
- try {
- Attributes attrs = sftp.stat(remotePath);
- ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path already exists but is not a directory: %s", remotePath);
- return;
- } catch (SftpException e) {
- int status = e.getStatus();
- ValidateUtils.checkTrue(status == SftpConstants.SSH_FX_NO_SUCH_FILE, "Failed to get status of %s: %s", remotePath, e.getMessage());
- }
-
- int pos = remotePath.lastIndexOf('/');
- ValidateUtils.checkTrue(pos > 0, "No more parents for %s", remotePath);
- createDirectories(sftp, remotePath.substring(0, pos));
- }
-
- protected void transferFile(SftpClient sftp, Path localPath, String remotePath, boolean upload, PrintStream stdout, boolean verbose) throws IOException {
- // Create the file's hierarchy
- if (upload) {
- int pos = remotePath.lastIndexOf('/');
- ValidateUtils.checkTrue(pos > 0, "Missing full remote file path: %s", remotePath);
- createDirectories(sftp, remotePath.substring(0, pos));
- } else {
- Files.createDirectories(localPath.getParent());
- }
-
- try (InputStream input = upload ? Files.newInputStream(localPath) : sftp.read(remotePath);
- OutputStream output = upload ? sftp.write(remotePath) : Files.newOutputStream(localPath)) {
- IoUtils.copy(input, output, SftpClient.IO_BUFFER_SIZE);
- }
-
- if (verbose) {
- stdout.append('\t')
- .append("Copied ").append(upload ? localPath.toString() : remotePath)
- .append(" to ").println(upload ? remotePath : localPath.toString());
- }
- }
-
- protected void transferRemoteDir(SftpClient sftp, Path localPath, String remotePath, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
- if (attrs.isDirectory()) {
- for (DirEntry entry : sftp.readDir(remotePath)) {
- String name = entry.getFilename();
- if (".".equals(name) || "..".equals(name)) {
- continue;
- }
-
- transferRemoteDir(sftp, localPath.resolve(name), remotePath + "/" + name, entry.getAttributes(), stdout, verbose);
- }
- } else if (attrs.isRegularFile()) {
- transferFile(sftp, localPath, remotePath, false, stdout, verbose);
- } else {
- if (verbose) {
- stdout.append('\t').append("Skip remote special file ").println(remotePath);
- }
- }
- }
-
- protected void transferLocalDir(SftpClient sftp, Path localPath, String remotePath, PrintStream stdout, boolean verbose) throws IOException {
- if (Files.isDirectory(localPath)) {
- try (DirectoryStream<Path> ds = Files.newDirectoryStream(localPath)) {
- for (Path entry : ds) {
- String name = entry.getFileName().toString();
- transferLocalDir(sftp, localPath.resolve(name), remotePath + "/" + name, stdout, verbose);
- }
- }
- } else if (Files.isRegularFile(localPath)) {
- transferFile(sftp, localPath, remotePath, true, stdout, verbose);
- } else {
- if (verbose) {
- stdout.append('\t').append("Skip local special file ").println(localPath);
- }
- }
- }
-
- protected void executeCommand(String args, boolean upload, PrintStream stdout) throws IOException {
- String[] comps = GenericUtils.split(args, ' ');
- int numArgs = GenericUtils.length(comps);
- ValidateUtils.checkTrue((numArgs >= 1) && (numArgs <= 3), "Invalid number of arguments: %s", args);
-
- String src = comps[0];
- boolean recursive = false;
- boolean verbose = false;
- int tgtIndex = 1;
- if (src.charAt(0) == '-') {
- ValidateUtils.checkTrue(src.length() > 1, "Missing flags specification: %s", args);
- ValidateUtils.checkTrue(numArgs >= 2, "Missing source specification: %s", args);
-
- for (int index = 1; index < src.length(); index++) {
- char ch = src.charAt(index);
- switch(ch) {
- case 'r' :
- recursive = true;
- break;
- case 'v':
- verbose = true;
- break;
- default:
- throw new IllegalArgumentException("Unknown flag (" + String.valueOf(ch) + ")");
- }
- }
- src = comps[1];
- tgtIndex++;
- }
-
- String tgt = (tgtIndex < numArgs) ? comps[tgtIndex] : null;
- String localPath;
- String remotePath;
- if (upload) {
- localPath = src;
- remotePath = ValidateUtils.checkNotNullAndNotEmpty(tgt, "No remote target specified: %s", args);
- } else {
- localPath = GenericUtils.isEmpty(tgt) ? getCurrentLocalDirectory() : tgt;
- remotePath = src;
- }
-
- SftpClient sftp = getClient();
- Path local = Paths.get(resolveLocalPath(localPath)).normalize().toAbsolutePath();
- String remote = resolveRemotePath(remotePath);
- if (recursive) {
- if (upload) {
- ValidateUtils.checkTrue(Files.isDirectory(local), "Local path not a directory or does not exist: %s", local);
- transferLocalDir(sftp, local, remote, stdout, verbose);
- } else {
- Attributes attrs = sftp.stat(remote);
- ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path not a directory: %s", remote);
- transferRemoteDir(sftp, local, remote, attrs, stdout, verbose);
- }
- } else {
- if (Files.exists(local) && Files.isDirectory(local)) {
- int pos = remote.lastIndexOf('/');
- String name = (pos >= 0) ? remote.substring(pos + 1) : remote;
- local = local.resolve(name);
- }
-
- transferFile(sftp, local, remote, upload, stdout, verbose);
- }
- }
- }
-
- private class GetCommandExecutor extends TransferCommandExecutor {
- GetCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "get";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- executeCommand(args, false, stdout);
- return false;
- }
- }
-
- private class PutCommandExecutor extends TransferCommandExecutor {
- PutCommandExecutor() {
- super();
- }
-
- @Override
- public String getName() {
- return "put";
- }
-
- @Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- executeCommand(args, true, stdout);
- return false;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/536effdc/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
deleted file mode 100644
index 4b34f92..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.client.subsystem.sftp;
-
-/**
- * Just a test class used to invoke {@link SftpCommand#main(String[])} in
- * order to have logging - which is in {@code test} scope
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class SftpCommandMain {
- private SftpCommandMain() {
- throw new UnsupportedOperationException("No instance");
- }
-
- public static void main(String[] args) throws Exception {
- SftpCommand.main(args);
- }
-}
[2/6] mina-sshd git commit: [SSHD-816] Provide some way for the SSHD
command line server to detect and configure the (split) SFTP subsystem
Posted by lg...@apache.org.
[SSHD-816] Provide some way for the SSHD command line server to detect and configure the (split) SFTP subsystem
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/2b7e22a3
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/2b7e22a3
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/2b7e22a3
Branch: refs/heads/master
Commit: 2b7e22a31fb7ae0c3cb1933304b3d6d57ad54212
Parents: 536effd
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Tue Apr 17 12:15:54 2018 +0300
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Tue Apr 17 15:32:31 2018 +0300
----------------------------------------------------------------------
README.md | 16 +
assembly/src/main/distribution/bin/sshd.bat | 2 +-
assembly/src/main/distribution/bin/sshd.sh | 2 +-
.../java/org/apache/sshd/cli/CliSupport.java | 1 -
.../sshd/cli/server/SshServerCliSupport.java | 184 +++++++++++
.../apache/sshd/cli/server/SshServerMain.java | 210 ++++++++++++
.../apache/sshd/cli/server/SshFsMounter.java | 328 +++++++++++++++++++
.../cli/server/SshServerMainDevelopment.java | 36 ++
.../java/org/apache/sshd/server/SshServer.java | 240 --------------
.../org/apache/sshd/server/SshServerMain.java | 36 --
...pache.sshd.server.subsystem.SubsystemFactory | 20 ++
.../server/subsystem/sftp/SshFsMounter.java | 327 ------------------
12 files changed, 796 insertions(+), 606 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index bcef46f..bb4f586 100644
--- a/README.md
+++ b/README.md
@@ -1293,6 +1293,22 @@ In order to use this CLI code as part of another project, one needs to include t
</dependency>
```
+### Command line SSH daemon
+
+* **Port** - by default the SSH server sets up to list on port 8000 in order to avoid conflicts with any running SSH O/S daemon. This can be modified by providing a `-p NNNN`
+or `-o Port=NNNN` command line option.
+
+* **Subsystem(s)** - the server automatically detects subsystems using the [Java ServiceLoader mechanism](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
+This can be overwritten as follows (in this order):
+
+1. Provide a `org.apache.sshd.server.subsystem.SubsystemFactory` system property containing comma-separated fully-qualified names of classes implementing
+this interface. The implementations must have a public no-args constructor for instantiating them. The order of the provided subsystems will be according
+to their order in the specified list
+
+2. Provide a `-o Subsystem=xxx,yyy` command line argument where value is a comma-separated list of the **name**(s) of the auto-detected factories via
+the `ServiceLoader` mechanism. The special value `none` may be used to indicate that no subsystem is to be configured. **Note:** no specific order is
+provided when subsystems are auto-detected and/or filtered.
+
## GIT support
The _sshd-git_ artifact contains both client and server-side command factories for issuing and handling some _git_ commands. The code is based on [JGit](https://github.com/eclipse/jgit)
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/assembly/src/main/distribution/bin/sshd.bat
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/sshd.bat b/assembly/src/main/distribution/bin/sshd.bat
index 0863f7f..86d740f 100644
--- a/assembly/src/main/distribution/bin/sshd.bat
+++ b/assembly/src/main/distribution/bin/sshd.bat
@@ -91,7 +91,7 @@ goto :EOF
SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
rem Execute the Java Virtual Machine
cd %SSHD_HOME%
-"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.server.SshServer %ARGS%
+"%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dsshd.home="%SSHD_HOME%" org.apache.sshd.cli.server.SshServerMain %ARGS%
rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/assembly/src/main/distribution/bin/sshd.sh
----------------------------------------------------------------------
diff --git a/assembly/src/main/distribution/bin/sshd.sh b/assembly/src/main/distribution/bin/sshd.sh
index d07e203..a7b21ed 100644
--- a/assembly/src/main/distribution/bin/sshd.sh
+++ b/assembly/src/main/distribution/bin/sshd.sh
@@ -255,7 +255,7 @@ run() {
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
cd $SSHD_BASE
- exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.server.SshServer "$@"
+ exec $JAVA $JAVA_OPTS -Dsshd.home="$SSHD_HOME" $OPTS -classpath "$CLASSPATH" org.apache.sshd.cli.server.SshServerMain "$@"
}
main() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
index 14737bb..c0067be 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
@@ -34,5 +34,4 @@ public abstract class CliSupport {
stderr.println(message);
return true;
}
-
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
new file mode 100644
index 0000000..903128a
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.server;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.ServiceLoader;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.cli.CliSupport;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.MappedKeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.ServerAuthenticationManager;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.config.SshServerConfigFileReader;
+import org.apache.sshd.server.forward.ForwardingFilter;
+import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.subsystem.SubsystemFactory;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class SshServerCliSupport extends CliSupport {
+ protected SshServerCliSupport() {
+ super();
+ }
+
+ public static KeyPairProvider setupServerKeys(SshServer sshd, String hostKeyType, int hostKeySize, Collection<String> keyFiles) throws Exception {
+ if (GenericUtils.isEmpty(keyFiles)) {
+ AbstractGeneratorHostKeyProvider hostKeyProvider;
+ Path hostKeyFile;
+ if (SecurityUtils.isBouncyCastleRegistered()) {
+ hostKeyFile = new File("key.pem").toPath();
+ hostKeyProvider = SecurityUtils.createGeneratorHostKeyProvider(hostKeyFile);
+ } else {
+ hostKeyFile = new File("key.ser").toPath();
+ hostKeyProvider = new SimpleGeneratorHostKeyProvider(hostKeyFile);
+ }
+ hostKeyProvider.setAlgorithm(hostKeyType);
+ if (hostKeySize != 0) {
+ hostKeyProvider.setKeySize(hostKeySize);
+ }
+
+ List<KeyPair> keys = ValidateUtils.checkNotNullAndNotEmpty(hostKeyProvider.loadKeys(),
+ "Failed to load keys from %s", hostKeyFile);
+ KeyPair kp = keys.get(0);
+ PublicKey pubKey = kp.getPublic();
+ String keyAlgorithm = pubKey.getAlgorithm();
+ if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) {
+ keyAlgorithm = KeyUtils.EC_ALGORITHM;
+ } else if (BuiltinIdentities.Constants.ED25519.equals(keyAlgorithm)) {
+ keyAlgorithm = SecurityUtils.EDDSA;
+ // TODO change the hostKeyProvider to one that supports read/write of EDDSA keys - see SSHD-703
+ }
+
+ // force re-generation of host key if not same algorithm
+ if (!Objects.equals(keyAlgorithm, hostKeyType)) {
+ Files.deleteIfExists(hostKeyFile);
+ hostKeyProvider.clearLoadedKeys();
+ }
+
+ return hostKeyProvider;
+ } else {
+ List<KeyPair> pairs = new ArrayList<>(keyFiles.size());
+ for (String keyFilePath : keyFiles) {
+ Path path = Paths.get(keyFilePath);
+ try (InputStream inputStream = Files.newInputStream(path)) {
+ KeyPair kp = SecurityUtils.loadKeyPairIdentity(keyFilePath, inputStream, null);
+ pairs.add(kp);
+ } catch (Exception e) {
+ System.err.append("Failed (" + e.getClass().getSimpleName() + ")"
+ + " to load host key file=" + keyFilePath
+ + ": " + e.getMessage());
+ throw e;
+ }
+ }
+
+ return new MappedKeyPairProvider(pairs);
+ }
+ }
+
+ public static ForwardingFilter setupServerForwarding(SshServer server, PropertyResolver options) {
+ ForwardingFilter forwardFilter = SshServerConfigFileReader.resolveServerForwarding(options);
+ server.setForwardingFilter(forwardFilter);
+ return forwardFilter;
+ }
+
+ public static Object setupServerBanner(ServerFactoryManager server, PropertyResolver options) {
+ Object banner = SshServerConfigFileReader.resolveBanner(options);
+ PropertyResolverUtils.updateProperty(server, ServerAuthenticationManager.WELCOME_BANNER, banner);
+ return banner;
+ }
+
+ public static List<NamedFactory<Command>> setupServerSubsystems(SshServer server, PropertyResolver options) {
+ ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(SubsystemFactory.class);
+ String classList = System.getProperty(SubsystemFactory.class.getName());
+ if (GenericUtils.isNotEmpty(classList)) {
+ String[] classes = GenericUtils.split(classList, ',');
+ List<NamedFactory<Command>> subsystems = new ArrayList<>(classes.length);
+ for (String fqcn : classes) {
+ try {
+ Class<?> clazz = cl.loadClass(fqcn);
+ SubsystemFactory factory = (SubsystemFactory) clazz.newInstance();
+ subsystems.add(factory);
+ } catch (Throwable t) {
+ System.err.append("Failed (").append(t.getClass().getSimpleName()).append(')')
+ .append(" to instantiate subsystem=").append(fqcn)
+ .append(": ").println(t.getMessage());
+ System.err.flush();
+ throw GenericUtils.toRuntimeException(t, true);
+ }
+ }
+
+ return subsystems;
+ }
+
+ String nameList = (options == null) ? null : options.getString(SshConfigFileReader.SUBSYSTEM_CONFIG_PROP);
+ if ("none".equalsIgnoreCase(nameList)) {
+ return Collections.emptyList();
+ }
+
+ boolean havePreferences = GenericUtils.isNotEmpty(nameList);
+ Collection<String> preferredNames = (!havePreferences)
+ ? Collections.emptySet()
+ : Stream.of(GenericUtils.split(nameList, ','))
+ .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
+ ServiceLoader<SubsystemFactory> loader = ServiceLoader.load(SubsystemFactory.class, cl);
+ List<NamedFactory<Command>> subsystems = new ArrayList<>();
+ for (SubsystemFactory factory : loader) {
+ String name = factory.getName();
+ if (havePreferences && (!preferredNames.contains(name))) {
+ continue;
+ }
+
+ subsystems.add(factory);
+ }
+
+ return subsystems;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
new file mode 100644
index 0000000..78e43a9
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.server;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
+import org.apache.sshd.server.config.keys.ServerIdentity;
+import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.server.shell.ProcessShellFactory;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshServerMain extends SshServerCliSupport {
+ public SshServerMain() {
+ super(); // in case someone wants to extend it
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static void main(String[] args) throws Exception {
+ int port = 8000;
+ String provider;
+ boolean error = false;
+ String hostKeyType = AbstractGeneratorHostKeyProvider.DEFAULT_ALGORITHM;
+ int hostKeySize = 0;
+ Collection<String> keyFiles = null;
+ Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+ int numArgs = GenericUtils.length(args);
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ if ("-p".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+ port = Integer.parseInt(args[++i]);
+ } else if ("-key-type".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+
+ if (keyFiles != null) {
+ System.err.println("option conflicts with -key-file: " + argName);
+ error = true;
+ break;
+ }
+ hostKeyType = args[++i].toUpperCase();
+ } else if ("-key-size".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+
+ if (keyFiles != null) {
+ System.err.println("option conflicts with -key-file: " + argName);
+ error = true;
+ break;
+ }
+
+ hostKeySize = Integer.parseInt(args[++i]);
+ } else if ("-key-file".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+
+ String keyFilePath = args[++i];
+ if (keyFiles == null) {
+ keyFiles = new LinkedList<>();
+ }
+ keyFiles.add(keyFilePath);
+ } else if ("-io".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+ provider = args[++i];
+ if ("mina".equals(provider)) {
+ System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.MINA.getFactoryClassName());
+ } else if ("nio2".endsWith(provider)) {
+ System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.NIO2.getFactoryClassName());
+ } else {
+ System.err.println("provider should be mina or nio2: " + argName);
+ error = true;
+ break;
+ }
+ } else if ("-o".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires and argument: " + argName);
+ error = true;
+ break;
+ }
+
+ String opt = args[++i];
+ int idx = opt.indexOf('=');
+ if (idx <= 0) {
+ System.err.println("bad syntax for option: " + opt);
+ error = true;
+ break;
+ }
+
+ String optName = opt.substring(0, idx);
+ String optValue = opt.substring(idx + 1);
+ if (ServerIdentity.HOST_KEY_CONFIG_PROP.equals(optName)) {
+ if (keyFiles == null) {
+ keyFiles = new LinkedList<>();
+ }
+ keyFiles.add(optValue);
+ } else if (SshConfigFileReader.PORT_CONFIG_PROP.equals(optName)) {
+ port = Integer.parseInt(optValue);
+ } else {
+ options.put(optName, optValue);
+ }
+ } else if (argName.startsWith("-")) {
+ System.err.println("illegal option: " + argName);
+ error = true;
+ break;
+ } else {
+ System.err.println("extra argument: " + argName);
+ error = true;
+ break;
+ }
+ }
+ if (error) {
+ System.err.println("usage: sshd [-p port] [-io mina|nio2] [-key-type RSA|DSA|EC] [-key-size NNNN] [-key-file <path>] [-o option=value]");
+ System.exit(-1);
+ }
+
+ SshServer sshd = SshServer.setUpDefaultServer();
+ Map<String, Object> props = sshd.getProperties();
+ props.putAll(options);
+
+ PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
+ KeyPairProvider hostKeyProvider = setupServerKeys(sshd, hostKeyType, hostKeySize, keyFiles);
+ sshd.setKeyPairProvider(hostKeyProvider);
+ // Should come AFTER key pair provider setup so auto-welcome can be generated if needed
+ setupServerBanner(sshd, resolver);
+ sshd.setPort(port);
+
+ String macsOverride = resolver.getString(SshConfigFileReader.MACS_CONFIG_PROP);
+ if (GenericUtils.isNotEmpty(macsOverride)) {
+ SshConfigFileReader.configureMacs(sshd, macsOverride, true, true);
+ }
+
+ sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+ sshd.setPasswordAuthenticator((username, password, session) -> Objects.equals(username, password));
+ sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
+ setupServerForwarding(sshd, resolver);
+ sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(
+ command -> new ProcessShellFactory(GenericUtils.split(command, ' ')).create()
+ ).build());
+
+ List<NamedFactory<Command>> subsystems = setupServerSubsystems(sshd, resolver);
+ if (GenericUtils.isNotEmpty(subsystems)) {
+ System.out.append("Setup subsystems=").println(NamedResource.getNames(subsystems));
+ sshd.setSubsystemFactories(subsystems);
+ }
+
+ System.err.println("Starting SSHD on port " + port);
+ sshd.start();
+ Thread.sleep(Long.MAX_VALUE);
+ System.err.println("Exiting after a very (very very) long time");
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
new file mode 100644
index 0000000..aad7cd5
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.Utils;
+
+/**
+ * A basic implementation to allow remote mounting of the local file system via SFTP
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SshFsMounter extends SshServerCliSupport {
+ public static class MounterCommand extends AbstractLoggingBean implements Command, SessionAware, Runnable {
+ private final String command;
+ private final String cmdName;
+ private final List<String> args;
+ private String username;
+ private InputStream stdin;
+ private PrintStream stdout;
+ private PrintStream stderr;
+ private ExitCallback callback;
+ private ExecutorService executor;
+ private Future<?> future;
+
+ public MounterCommand(String command) {
+ this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No command");
+
+ String[] comps = GenericUtils.split(this.command, ' ');
+ int numComps = GenericUtils.length(comps);
+ cmdName = GenericUtils.trimToEmpty(ValidateUtils.checkNotNullAndNotEmpty(comps[0], "No command name"));
+ if (numComps > 1) {
+ args = new ArrayList<>(numComps - 1);
+ for (int index = 1; index < numComps; index++) {
+ String c = GenericUtils.trimToEmpty(comps[index]);
+ if (GenericUtils.isEmpty(c)) {
+ continue;
+ }
+
+ args.add(c);
+ }
+ } else {
+ args = Collections.emptyList();
+ }
+
+ log.info("<init>(" + command + ")");
+ }
+
+ @Override
+ public void run() {
+ try {
+ log.info("run(" + username + ")[" + command + "] start");
+ if ("id".equals(cmdName)) {
+ int numArgs = GenericUtils.size(args);
+ if (numArgs <= 0) {
+ stdout.println("uid=0(root) gid=0(root) groups=0(root)");
+ } else if (numArgs == 1) {
+ String modifier = args.get(0);
+ if ("-u".equals(modifier) || "-G".equals(modifier)) {
+ stdout.println("0");
+ } else {
+ throw new IllegalArgumentException("Unknown modifier: " + modifier);
+ }
+ } else {
+ throw new IllegalArgumentException("Unexpected extra command arguments");
+ }
+ } else {
+ throw new UnsupportedOperationException("Unknown command");
+ }
+
+ log.info("run(" + username + ")[" + command + "] end");
+ callback.onExit(0);
+ } catch (Exception e) {
+ log.error("run(" + username + ")[" + command + "] " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
+ stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
+ callback.onExit(-1, e.toString());
+ }
+ }
+
+ @Override
+ public void setSession(ServerSession session) {
+ username = session.getUsername();
+ }
+
+ @Override
+ public void setInputStream(InputStream in) {
+ this.stdin = in;
+ }
+
+ @Override
+ public void setOutputStream(OutputStream out) {
+ this.stdout = new PrintStream(out, true);
+ }
+
+ @Override
+ public void setErrorStream(OutputStream err) {
+ this.stderr = new PrintStream(err, true);
+ }
+
+ @Override
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void start(Environment env) throws IOException {
+ executor = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
+ future = executor.submit(this);
+ }
+
+ @Override
+ public void destroy() {
+ stopCommand();
+
+ if (stdout != null) {
+ try {
+ log.info("destroy(" + username + ")[" + command + "] close stdout");
+ stdout.close();
+ log.info("destroy(" + username + ")[" + command + "] stdout closed");
+ } finally {
+ stdout = null;
+ }
+ }
+
+ if (stderr != null) {
+ try {
+ log.info("destroy(" + username + ")[" + command + "] close stderr");
+ stderr.close();
+ log.info("destroy(" + username + ")[" + command + "] stderr closed");
+ } finally {
+ stderr = null;
+ }
+ }
+
+ if (stdin != null) {
+ try {
+ log.info("destroy(" + username + ")[" + command + "] close stdin");
+ stdin.close();
+ log.info("destroy(" + username + ")[" + command + "] stdin closed");
+ } catch (IOException e) {
+ log.warn("destroy(" + username + ")[" + command + "] failed (" + e.getClass().getSimpleName() + ") to close stdin: " + e.getMessage());
+ if (log.isDebugEnabled()) {
+ log.debug("destroy(" + username + ")[" + command + "] failure details", e);
+ }
+ } finally {
+ stdin = null;
+ }
+ }
+ }
+
+ private void stopCommand() {
+ if ((future != null) && (!future.isDone())) {
+ try {
+ log.info("stopCommand(" + username + ")[" + command + "] cancelling");
+ future.cancel(true);
+ log.info("stopCommand(" + username + ")[" + command + "] cancelled");
+ } finally {
+ future = null;
+ }
+ }
+
+ if ((executor != null) && (!executor.isShutdown())) {
+ try {
+ log.info("stopCommand(" + username + ")[" + command + "] shutdown executor");
+ executor.shutdownNow();
+ log.info("stopCommand(" + username + ")[" + command + "] executor shut down");
+ } finally {
+ executor = null;
+ }
+ }
+ }
+ }
+
+ public static class MounterCommandFactory implements CommandFactory {
+ public static final MounterCommandFactory INSTANCE = new MounterCommandFactory();
+
+ public MounterCommandFactory() {
+ super();
+ }
+
+ @Override
+ public Command createCommand(String command) {
+ return new MounterCommand(command);
+ }
+ }
+
+ private SshFsMounter() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+
+ public static void main(String[] args) throws Exception {
+ int port = SshConfigFileReader.DEFAULT_PORT;
+ boolean error = false;
+ Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ int numArgs = GenericUtils.length(args);
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ if ("-p".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ break;
+ }
+ port = Integer.parseInt(args[++i]);
+ } else if ("-io".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ break;
+ }
+
+ String provider = args[++i];
+ if ("mina".equals(provider)) {
+ System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.MINA.getFactoryClassName());
+ } else if ("nio2".endsWith(provider)) {
+ System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.NIO2.getFactoryClassName());
+ } else {
+ System.err.println("provider should be mina or nio2: " + argName);
+ error = true;
+ break;
+ }
+ } else if ("-o".equals(argName)) {
+ if ((i + 1) >= numArgs) {
+ System.err.println("option requires and argument: " + argName);
+ error = true;
+ break;
+ }
+ String opt = args[++i];
+ int idx = opt.indexOf('=');
+ if (idx <= 0) {
+ System.err.println("bad syntax for option: " + opt);
+ error = true;
+ break;
+ }
+ options.put(opt.substring(0, idx), opt.substring(idx + 1));
+ } else if (argName.startsWith("-")) {
+ System.err.println("illegal option: " + argName);
+ error = true;
+ break;
+ } else {
+ System.err.println("extra argument: " + argName);
+ error = true;
+ break;
+ }
+ }
+ if (error) {
+ System.err.println("usage: sshfs [-p port] [-io mina|nio2] [-o option=value]");
+ System.exit(-1);
+ }
+
+ SshServer sshd = Utils.setupTestServer(SshFsMounter.class);
+ Map<String, Object> props = sshd.getProperties();
+ props.putAll(options);
+ PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
+ File targetFolder = Objects.requireNonNull(Utils.detectTargetFolder(MounterCommandFactory.class), "Failed to detect target folder");
+ if (SecurityUtils.isBouncyCastleRegistered()) {
+ sshd.setKeyPairProvider(SecurityUtils.createGeneratorHostKeyProvider(new File(targetFolder, "key.pem").toPath()));
+ } else {
+ sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File(targetFolder, "key.ser")));
+ }
+ // Should come AFTER key pair provider setup so auto-welcome can be generated if needed
+ setupServerBanner(sshd, resolver);
+
+ sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+ sshd.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
+ sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+ sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(MounterCommandFactory.INSTANCE).build());
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshd.setPort(port);
+
+ System.err.println("Starting SSHD on port " + port);
+ sshd.start();
+ Thread.sleep(Long.MAX_VALUE);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshServerMainDevelopment.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshServerMainDevelopment.java b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshServerMainDevelopment.java
new file mode 100644
index 0000000..28409fa
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshServerMainDevelopment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.server;
+
+/**
+ * Just a test class used to invoke {@link SshServerMain#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class SshServerMainDevelopment {
+ private SshServerMainDevelopment() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void main(String[] args) throws Exception {
+ SshServerMain.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 255668d..f93c5d0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -18,69 +18,41 @@
*/
package org.apache.sshd.server;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyPair;
-import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.ServiceFactory;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.config.keys.BuiltinIdentities;
-import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.helpers.AbstractFactoryManager;
-import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoServiceFactory;
import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.keyprovider.MappedKeyPairProvider;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.server.auth.UserAuth;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
-import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
-import org.apache.sshd.server.config.SshServerConfigFileReader;
-import org.apache.sshd.server.config.keys.ServerIdentity;
-import org.apache.sshd.server.forward.ForwardingFilter;
-import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.server.scp.ScpCommandFactory;
import org.apache.sshd.server.session.ServerConnectionServiceFactory;
import org.apache.sshd.server.session.ServerProxyAcceptor;
import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
import org.apache.sshd.server.session.SessionFactory;
-import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
-import org.apache.sshd.server.shell.ProcessShellFactory;
/**
* <p>
@@ -426,216 +398,4 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
public static SshServer setUpDefaultServer() {
return ServerBuilder.builder().build();
}
-
- /*=================================
- Main class implementation
- *=================================*/
-
- public static KeyPairProvider setupServerKeys(SshServer sshd, String hostKeyType, int hostKeySize, Collection<String> keyFiles) throws Exception {
- if (GenericUtils.isEmpty(keyFiles)) {
- AbstractGeneratorHostKeyProvider hostKeyProvider;
- Path hostKeyFile;
- if (SecurityUtils.isBouncyCastleRegistered()) {
- hostKeyFile = new File("key.pem").toPath();
- hostKeyProvider = SecurityUtils.createGeneratorHostKeyProvider(hostKeyFile);
- } else {
- hostKeyFile = new File("key.ser").toPath();
- hostKeyProvider = new SimpleGeneratorHostKeyProvider(hostKeyFile);
- }
- hostKeyProvider.setAlgorithm(hostKeyType);
- if (hostKeySize != 0) {
- hostKeyProvider.setKeySize(hostKeySize);
- }
-
- List<KeyPair> keys = ValidateUtils.checkNotNullAndNotEmpty(hostKeyProvider.loadKeys(),
- "Failed to load keys from %s", hostKeyFile);
- KeyPair kp = keys.get(0);
- PublicKey pubKey = kp.getPublic();
- String keyAlgorithm = pubKey.getAlgorithm();
- if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) {
- keyAlgorithm = KeyUtils.EC_ALGORITHM;
- } else if (BuiltinIdentities.Constants.ED25519.equals(keyAlgorithm)) {
- keyAlgorithm = SecurityUtils.EDDSA;
- // TODO change the hostKeyProvider to one that supports read/write of EDDSA keys - see SSHD-703
- }
-
- // force re-generation of host key if not same algorithm
- if (!Objects.equals(keyAlgorithm, hostKeyType)) {
- Files.deleteIfExists(hostKeyFile);
- hostKeyProvider.clearLoadedKeys();
- }
-
- return hostKeyProvider;
- } else {
- List<KeyPair> pairs = new ArrayList<>(keyFiles.size());
- for (String keyFilePath : keyFiles) {
- Path path = Paths.get(keyFilePath);
- try (InputStream inputStream = Files.newInputStream(path)) {
- KeyPair kp = SecurityUtils.loadKeyPairIdentity(keyFilePath, inputStream, null);
- pairs.add(kp);
- } catch (Exception e) {
- System.err.append("Failed (" + e.getClass().getSimpleName() + ")"
- + " to load host key file=" + keyFilePath
- + ": " + e.getMessage());
- throw e;
- }
- }
-
- return new MappedKeyPairProvider(pairs);
- }
- }
-
- public static void main(String[] args) throws Exception {
- int port = 8000;
- String provider;
- boolean error = false;
- String hostKeyType = AbstractGeneratorHostKeyProvider.DEFAULT_ALGORITHM;
- int hostKeySize = 0;
- Collection<String> keyFiles = null;
- Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
- int numArgs = GenericUtils.length(args);
- for (int i = 0; i < numArgs; i++) {
- String argName = args[i];
- if ("-p".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- error = true;
- break;
- }
- port = Integer.parseInt(args[++i]);
- } else if ("-key-type".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- error = true;
- break;
- }
-
- if (keyFiles != null) {
- System.err.println("option conflicts with -key-file: " + argName);
- error = true;
- break;
- }
- hostKeyType = args[++i].toUpperCase();
- } else if ("-key-size".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- error = true;
- break;
- }
-
- if (keyFiles != null) {
- System.err.println("option conflicts with -key-file: " + argName);
- error = true;
- break;
- }
-
- hostKeySize = Integer.parseInt(args[++i]);
- } else if ("-key-file".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- error = true;
- break;
- }
-
- String keyFilePath = args[++i];
- if (keyFiles == null) {
- keyFiles = new LinkedList<>();
- }
- keyFiles.add(keyFilePath);
- } else if ("-io".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- error = true;
- break;
- }
- provider = args[++i];
- if ("mina".equals(provider)) {
- System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.MINA.getFactoryClassName());
- } else if ("nio2".endsWith(provider)) {
- System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.NIO2.getFactoryClassName());
- } else {
- System.err.println("provider should be mina or nio2: " + argName);
- error = true;
- break;
- }
- } else if ("-o".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires and argument: " + argName);
- error = true;
- break;
- }
- String opt = args[++i];
- int idx = opt.indexOf('=');
- if (idx <= 0) {
- System.err.println("bad syntax for option: " + opt);
- error = true;
- break;
- }
-
- String optName = opt.substring(0, idx);
- String optValue = opt.substring(idx + 1);
- if (ServerIdentity.HOST_KEY_CONFIG_PROP.equals(optName)) {
- if (keyFiles == null) {
- keyFiles = new LinkedList<>();
- }
- keyFiles.add(optValue);
- } else {
- options.put(optName, optValue);
- }
- } else if (argName.startsWith("-")) {
- System.err.println("illegal option: " + argName);
- error = true;
- break;
- } else {
- System.err.println("extra argument: " + argName);
- error = true;
- break;
- }
- }
- if (error) {
- System.err.println("usage: sshd [-p port] [-io mina|nio2] [-key-type RSA|DSA|EC] [-key-size NNNN] [-key-file <path>] [-o option=value]");
- System.exit(-1);
- }
-
- SshServer sshd = SshServer.setUpDefaultServer();
- Map<String, Object> props = sshd.getProperties();
- props.putAll(options);
-
- PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
- KeyPairProvider hostKeyProvider = setupServerKeys(sshd, hostKeyType, hostKeySize, keyFiles);
- sshd.setKeyPairProvider(hostKeyProvider);
- // Should come AFTER key pair provider setup so auto-welcome can be generated if needed
- setupServerBanner(sshd, resolver);
- sshd.setPort(port);
-
- String macsOverride = resolver.getString(SshConfigFileReader.MACS_CONFIG_PROP);
- if (GenericUtils.isNotEmpty(macsOverride)) {
- SshConfigFileReader.configureMacs(sshd, macsOverride, true, true);
- }
-
- sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
- sshd.setPasswordAuthenticator((username, password, session) -> Objects.equals(username, password));
- sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
- setupServerForwarding(sshd, resolver);
- sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(
- command -> new ProcessShellFactory(GenericUtils.split(command, ' ')).create()
- ).build());
-
- System.err.println("Starting SSHD on port " + port);
- sshd.start();
- Thread.sleep(Long.MAX_VALUE);
- }
-
- public static ForwardingFilter setupServerForwarding(SshServer server, PropertyResolver options) {
- ForwardingFilter forwardFilter = SshServerConfigFileReader.resolveServerForwarding(options);
- server.setForwardingFilter(forwardFilter);
- return forwardFilter;
- }
-
- public static Object setupServerBanner(ServerFactoryManager server, PropertyResolver options) {
- Object banner = SshServerConfigFileReader.resolveBanner(options);
- PropertyResolverUtils.updateProperty(server, ServerAuthenticationManager.WELCOME_BANNER, banner);
- return banner;
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-core/src/test/java/org/apache/sshd/server/SshServerMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/SshServerMain.java b/sshd-core/src/test/java/org/apache/sshd/server/SshServerMain.java
deleted file mode 100644
index d4f3c60..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/server/SshServerMain.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.server;
-
-/**
- * Just a test class used to invoke {@link SshServer#main(String[])} in
- * order to have logging - which is in {@code test} scope
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class SshServerMain {
- private SshServerMain() {
- throw new UnsupportedOperationException("No instance");
- }
-
- public static void main(String[] args) throws Exception {
- SshServer.main(args);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-sftp/src/main/filtered-resources/META-INF/services/org.apache.sshd.server.subsystem.SubsystemFactory
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/filtered-resources/META-INF/services/org.apache.sshd.server.subsystem.SubsystemFactory b/sshd-sftp/src/main/filtered-resources/META-INF/services/org.apache.sshd.server.subsystem.SubsystemFactory
new file mode 100644
index 0000000..aa3de97
--- /dev/null
+++ b/sshd-sftp/src/main/filtered-resources/META-INF/services/org.apache.sshd.server.subsystem.SubsystemFactory
@@ -0,0 +1,20 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you 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.
+##
+
+org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2b7e22a3/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
deleted file mode 100644
index e6b10e0..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.sshd.server.subsystem.sftp;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
-import org.apache.sshd.common.io.IoServiceFactory;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.common.util.threads.ThreadUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
-import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.server.scp.ScpCommandFactory;
-import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
-import org.apache.sshd.util.test.Utils;
-
-/**
- * A basic implementation to allow remote mounting of the local file system via SFTP
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class SshFsMounter {
- public static class MounterCommand extends AbstractLoggingBean implements Command, SessionAware, Runnable {
- private final String command;
- private final String cmdName;
- private final List<String> args;
- private String username;
- private InputStream stdin;
- private PrintStream stdout;
- private PrintStream stderr;
- private ExitCallback callback;
- private ExecutorService executor;
- private Future<?> future;
-
- public MounterCommand(String command) {
- this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No command");
-
- String[] comps = GenericUtils.split(this.command, ' ');
- int numComps = GenericUtils.length(comps);
- cmdName = GenericUtils.trimToEmpty(ValidateUtils.checkNotNullAndNotEmpty(comps[0], "No command name"));
- if (numComps > 1) {
- args = new ArrayList<>(numComps - 1);
- for (int index = 1; index < numComps; index++) {
- String c = GenericUtils.trimToEmpty(comps[index]);
- if (GenericUtils.isEmpty(c)) {
- continue;
- }
-
- args.add(c);
- }
- } else {
- args = Collections.emptyList();
- }
-
- log.info("<init>(" + command + ")");
- }
-
- @Override
- public void run() {
- try {
- log.info("run(" + username + ")[" + command + "] start");
- if ("id".equals(cmdName)) {
- int numArgs = GenericUtils.size(args);
- if (numArgs <= 0) {
- stdout.println("uid=0(root) gid=0(root) groups=0(root)");
- } else if (numArgs == 1) {
- String modifier = args.get(0);
- if ("-u".equals(modifier) || "-G".equals(modifier)) {
- stdout.println("0");
- } else {
- throw new IllegalArgumentException("Unknown modifier: " + modifier);
- }
- } else {
- throw new IllegalArgumentException("Unexpected extra command arguments");
- }
- } else {
- throw new UnsupportedOperationException("Unknown command");
- }
-
- log.info("run(" + username + ")[" + command + "] end");
- callback.onExit(0);
- } catch (Exception e) {
- log.error("run(" + username + ")[" + command + "] " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
- stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
- callback.onExit(-1, e.toString());
- }
- }
-
- @Override
- public void setSession(ServerSession session) {
- username = session.getUsername();
- }
-
- @Override
- public void setInputStream(InputStream in) {
- this.stdin = in;
- }
-
- @Override
- public void setOutputStream(OutputStream out) {
- this.stdout = new PrintStream(out, true);
- }
-
- @Override
- public void setErrorStream(OutputStream err) {
- this.stderr = new PrintStream(err, true);
- }
-
- @Override
- public void setExitCallback(ExitCallback callback) {
- this.callback = callback;
- }
-
- @Override
- public void start(Environment env) throws IOException {
- executor = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
- future = executor.submit(this);
- }
-
- @Override
- public void destroy() {
- stopCommand();
-
- if (stdout != null) {
- try {
- log.info("destroy(" + username + ")[" + command + "] close stdout");
- stdout.close();
- log.info("destroy(" + username + ")[" + command + "] stdout closed");
- } finally {
- stdout = null;
- }
- }
-
- if (stderr != null) {
- try {
- log.info("destroy(" + username + ")[" + command + "] close stderr");
- stderr.close();
- log.info("destroy(" + username + ")[" + command + "] stderr closed");
- } finally {
- stderr = null;
- }
- }
-
- if (stdin != null) {
- try {
- log.info("destroy(" + username + ")[" + command + "] close stdin");
- stdin.close();
- log.info("destroy(" + username + ")[" + command + "] stdin closed");
- } catch (IOException e) {
- log.warn("destroy(" + username + ")[" + command + "] failed (" + e.getClass().getSimpleName() + ") to close stdin: " + e.getMessage());
- if (log.isDebugEnabled()) {
- log.debug("destroy(" + username + ")[" + command + "] failure details", e);
- }
- } finally {
- stdin = null;
- }
- }
- }
-
- private void stopCommand() {
- if ((future != null) && (!future.isDone())) {
- try {
- log.info("stopCommand(" + username + ")[" + command + "] cancelling");
- future.cancel(true);
- log.info("stopCommand(" + username + ")[" + command + "] cancelled");
- } finally {
- future = null;
- }
- }
-
- if ((executor != null) && (!executor.isShutdown())) {
- try {
- log.info("stopCommand(" + username + ")[" + command + "] shutdown executor");
- executor.shutdownNow();
- log.info("stopCommand(" + username + ")[" + command + "] executor shut down");
- } finally {
- executor = null;
- }
- }
- }
- }
-
- public static class MounterCommandFactory implements CommandFactory {
- public static final MounterCommandFactory INSTANCE = new MounterCommandFactory();
-
- public MounterCommandFactory() {
- super();
- }
-
- @Override
- public Command createCommand(String command) {
- return new MounterCommand(command);
- }
- }
-
- private SshFsMounter() {
- throw new UnsupportedOperationException("No instance");
- }
-
- //////////////////////////////////////////////////////////////////////////
-
- public static void main(String[] args) throws Exception {
- int port = SshConfigFileReader.DEFAULT_PORT;
- boolean error = false;
- Map<String, Object> options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- int numArgs = GenericUtils.length(args);
- for (int i = 0; i < numArgs; i++) {
- String argName = args[i];
- if ("-p".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- break;
- }
- port = Integer.parseInt(args[++i]);
- } else if ("-io".equals(argName)) {
- if (i + 1 >= numArgs) {
- System.err.println("option requires an argument: " + argName);
- break;
- }
-
- String provider = args[++i];
- if ("mina".equals(provider)) {
- System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.MINA.getFactoryClassName());
- } else if ("nio2".endsWith(provider)) {
- System.setProperty(IoServiceFactory.class.getName(), BuiltinIoServiceFactoryFactories.NIO2.getFactoryClassName());
- } else {
- System.err.println("provider should be mina or nio2: " + argName);
- error = true;
- break;
- }
- } else if ("-o".equals(argName)) {
- if ((i + 1) >= numArgs) {
- System.err.println("option requires and argument: " + argName);
- error = true;
- break;
- }
- String opt = args[++i];
- int idx = opt.indexOf('=');
- if (idx <= 0) {
- System.err.println("bad syntax for option: " + opt);
- error = true;
- break;
- }
- options.put(opt.substring(0, idx), opt.substring(idx + 1));
- } else if (argName.startsWith("-")) {
- System.err.println("illegal option: " + argName);
- error = true;
- break;
- } else {
- System.err.println("extra argument: " + argName);
- error = true;
- break;
- }
- }
- if (error) {
- System.err.println("usage: sshfs [-p port] [-io mina|nio2] [-o option=value]");
- System.exit(-1);
- }
-
- SshServer sshd = Utils.setupTestServer(SshFsMounter.class);
- Map<String, Object> props = sshd.getProperties();
- props.putAll(options);
- PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(options);
- File targetFolder = Objects.requireNonNull(Utils.detectTargetFolder(MounterCommandFactory.class), "Failed to detect target folder");
- if (SecurityUtils.isBouncyCastleRegistered()) {
- sshd.setKeyPairProvider(SecurityUtils.createGeneratorHostKeyProvider(new File(targetFolder, "key.pem").toPath()));
- } else {
- sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File(targetFolder, "key.ser")));
- }
- // Should come AFTER key pair provider setup so auto-welcome can be generated if needed
- SshServer.setupServerBanner(sshd, resolver);
-
- sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
- sshd.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
- sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
- sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(MounterCommandFactory.INSTANCE).build());
- sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
- sshd.setPort(port);
-
- System.err.println("Starting SSHD on port " + port);
- sshd.start();
- Thread.sleep(Long.MAX_VALUE);
- }
-}