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:06 UTC
[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
[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);
- }
-}