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 2020/11/12 17:59:27 UTC
[mina-sshd] branch master updated: Updated documentation for some
components
This is an automated email from the ASF dual-hosted git repository.
lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push:
new ad18585 Updated documentation for some components
ad18585 is described below
commit ad185853e032932fb400a53417d26d137d08ff9b
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Thu Nov 12 19:33:38 2020 +0200
Updated documentation for some components
---
docs/client-setup.md | 127 +++---
docs/commands.md | 151 ++++---
docs/event-listeners.md | 165 ++++----
docs/extensions.md | 2 +
docs/git.md | 64 ++-
docs/internals.md | 80 ++--
docs/port-forwarding.md | 27 +-
docs/scp.md | 63 +--
docs/server-setup.md | 26 +-
docs/sftp.md | 439 ++++++++++-----------
.../main/java/org/apache/sshd/common/Property.java | 7 +-
11 files changed, 563 insertions(+), 588 deletions(-)
diff --git a/docs/client-setup.md b/docs/client-setup.md
index f2d2b5c..3a95bec 100644
--- a/docs/client-setup.md
+++ b/docs/client-setup.md
@@ -10,7 +10,7 @@ This is simply done by calling
```java
- SshClient client = SshClient.setupDefaultClient();
+SshClient client = SshClient.setupDefaultClient();
```
@@ -119,40 +119,39 @@ Furthermore, one can change almost any configured `SshClient` parameter - althou
sessions depends on the actual changed configuration. Here is how a typical usage would look like
```java
+SshClient client = SshClient.setupDefaultClient();
+// override any default configuration...
+client.setSomeConfiguration(...);
+client.setOtherConfiguration(...);
+client.start();
+
+ // using the client for multiple sessions...
+ try (ClientSession session = client.connect(user, host, port)
+ .verify(...timeout...)
+ .getSession()) {
+ session.addPasswordIdentity(...password..); // for password-based authentication
+ // or
+ session.addPublicKeyIdentity(...key-pair...); // for password-less authentication
+ // Note: can add BOTH password AND public key identities - depends on the client/server security setup
+
+ session.auth().verify(...timeout...);
+ // start using the session to run commands, do SCP/SFTP, create local/remote port forwarding, etc...
+ }
+
+ // NOTE: this is just an example - one can open multiple concurrent sessions using the same client.
+ // No need to close the previous session before establishing a new one
+ try (ClientSession anotherSession = client.connect(otherUser, otherHost, port)
+ .verify(...timeout...)
+ .getSession()) {
+ anotherSession.addPasswordIdentity(...password..); // for password-based authentication
+ anotherSession.addPublicKeyIdentity(...key-pair...); // for password-less authentication
+ anotherSession.auth().verify(...timeout...);
+ // start using the session to run commands, do SCP/SFTP, create local/remote port forwarding, etc...
+ }
- SshClient client = SshClient.setupDefaultClient();
- // override any default configuration...
- client.setSomeConfiguration(...);
- client.setOtherConfiguration(...);
- client.start();
-
- // using the client for multiple sessions...
- try (ClientSession session = client.connect(user, host, port)
- .verify(...timeout...)
- .getSession()) {
- session.addPasswordIdentity(...password..); // for password-based authentication
- // or
- session.addPublicKeyIdentity(...key-pair...); // for password-less authentication
- // Note: can add BOTH password AND public key identities - depends on the client/server security setup
-
- session.auth().verify(...timeout...);
- // start using the session to run commands, do SCP/SFTP, create local/remote port forwarding, etc...
- }
-
- // NOTE: this is just an example - one can open multiple concurrent sessions using the same client.
- // No need to close the previous session before establishing a new one
- try (ClientSession anotherSession = client.connect(otherUser, otherHost, port)
- .verify(...timeout...)
- .getSession()) {
- anotherSession.addPasswordIdentity(...password..); // for password-based authentication
- anotherSession.addPublicKeyIdentity(...key-pair...); // for password-less authentication
- anotherSession.auth().verify(...timeout...);
- // start using the session to run commands, do SCP/SFTP, create local/remote port forwarding, etc...
- }
-
- // exiting in an orderly fashion once the code no longer needs to establish SSH session
- // NOTE: this can/should be done when the application exits.
- client.stop();
+// exiting in an orderly fashion once the code no longer needs to establish SSH session
+// NOTE: this can/should be done when the application exits.
+client.stop();
```
@@ -164,10 +163,11 @@ participate in it. By default, the client sends its identification string immedi
this can be modified so that the client waits for the server's identification before sending its own.
```java
- SshClient client = ...setup client...
- PropertyResolverUtils.updateProperty(
- client, ClientFactoryManager.SEND_IMMEDIATE_IDENTIFICATION, false);
- client.start();
+SshClient client = ...setup client...
+PropertyResolverUtils.updateProperty(
+ client, CoreModuleProperties.SEND_IMMEDIATE_IDENTIFICATION.getName(), false);
+client.start();
+
```
A similar configuration can be applied to sending the initial `SSH_MSG_KEXINIT` message - i.e., the client can be configured
@@ -175,10 +175,11 @@ to wait until the server's identification is received before sending the message
customize the KEX phase according to the parsed server identification.
```java
- SshClient client = ...setup client...
- PropertyResolverUtils.updateProperty(
- client, ClientFactoryManager.SEND_IMMEDIATE_KEXINIT, false);
- client.start();
+SshClient client = ...setup client...
+PropertyResolverUtils.updateProperty(
+ client, CoreModuleProperties.SEND_IMMEDIATE_KEXINIT.getName(), false);
+client.start();
+
```
**Note:** if immediate sending of the client's identification is disabled, `SSH_MSG_KEXINIT` message sending is also
@@ -208,7 +209,7 @@ regardless of the user's own traffic:
* Sending `keepalive@...` [global requests](https://tools.ietf.org/html/rfc4254#section-4).
- The feature is controlled via the `ClientFactoryManager#HEARTBEAT_REQUEST` and `HEARTBEAT_INTERVAL` properties - see the relevant
+ The feature is controlled via the `CoreModuleProperties#HEARTBEAT_REQUEST` and `HEARTBEAT_INTERVAL` properties - see the relevant
documentation for these features. The simplest way to activate this feature is to set the `HEARTBEAT_INTERVAL` property value
to the **milliseconds** value of the requested heartbeat interval.
@@ -259,29 +260,29 @@ reported environment variables. By default, unless specific instructions are pro
defaults - which however, might not be adequate for the specific client/server.
```java
- // Assuming one has obtained a ClientSession as already shown
- try (ClientChannel channel = session.createShellChannel(/* use internal defaults */)) {
- channel.setIn(...stdin...);
- channel.setOut(...stdout...);
- channel.setErr(...stderr...);
- // ... spawn the thread(s) that will pump the STDIN/OUT/ERR
- try {
- channel.open().verify(...some timeout...);
- // Wait (forever) for the channel to close - signalling shell exited
- channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
- } finally {
- // ... stop the pumping threads ...
- }
+// Assuming one has obtained a ClientSession as already shown
+try (ClientChannel channel = session.createShellChannel(/* use internal defaults */)) {
+ channel.setIn(...stdin...);
+ channel.setOut(...stdout...);
+ channel.setErr(...stderr...);
+ // ... spawn the thread(s) that will pump the STDIN/OUT/ERR
+ try {
+ channel.open().verify(...some timeout...);
+ // Wait (forever) for the channel to close - signalling shell exited
+ channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
+ } finally {
+ // ... stop the pumping threads ...
}
+}
- // In order to override the PTY and/or environment
- Map<String, ?> env = ...some environment...
- PtyChannelConfiguration ptyConfig = ...some configuration...
- try (ClientChannel channel = session.createShellChannel(ptyConfig, env)) {
- ... same code as before ...
- }
+// In order to override the PTY and/or environment
+Map<String, ?> env = ...some environment...
+PtyChannelConfiguration ptyConfig = ...some configuration...
+try (ClientChannel channel = session.createShellChannel(ptyConfig, env)) {
+ ... same code as before ...
+}
- // the same code can be used when opening a ChannelExec in order to run a single command
+// the same code can be used when opening a ChannelExec in order to run a single command
```
diff --git a/docs/commands.md b/docs/commands.md
index 50bfaf4..15f7e19 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -10,15 +10,14 @@ deciding what is the logged-in user's file system view and then use a `RootedFil
file system where the logged-in user can access only the files under the specified root and no others.
```java
-
- SshServer sshd = SshServer.setupDefaultServer();
- sshd.setFileSystemFactory(new VirtualFileSystemFactory() {
- @Override
- public Path getUserHomeDir(SessionContext session) throws IOException {
- ...use whatever information ...
- return somePath;
- }
- });
+SshServer sshd = SshServer.setupDefaultServer();
+sshd.setFileSystemFactory(new VirtualFileSystemFactory() {
+ @Override
+ public Path getUserHomeDir(SessionContext session) throws IOException {
+ ...use whatever information ...
+ return somePath;
+ }
+});
```
@@ -42,18 +41,17 @@ and take care of shutting it down when SSHD is done with (provided, of course, t
remain active afterwards...).
```java
-
- /*
- * An example user-provided executor service for SFTP - there are other such locations.
- * By default, the SftpSubsystem implementation creates a single-threaded executor
- * for each session, uses it to spawn the SFTP command handler and shuts
- * it down when the command is destroyed
- */
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
- .withExecutorServiceProvider(() -> new NoCloseExecutor(mySuperDuperExecutorService))
- .build();
- SshServer sshd = SshServer.setupDefaultServer();
- sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(factory));
+/*
+ * An example user-provided executor service for SFTP - there are other such locations.
+ * By default, the SftpSubsystem implementation creates a single-threaded executor
+ * for each session, uses it to spawn the SFTP command handler and shuts
+ * it down when the command is destroyed
+ */
+SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+ .withExecutorServiceProvider(() -> new NoCloseExecutor(mySuperDuperExecutorService))
+ .build();
+SshServer sshd = SshServer.setupDefaultServer();
+sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(factory));
```
@@ -61,13 +59,14 @@ If a single `CloseableExecutorService` is shared between several services, it ne
`ThreadUtils.noClose(executor)` method.
```java
- CloseableExecutorService sharedService = ...obtain/create an instance...;
+CloseableExecutorService sharedService = ...obtain/create an instance...;
+
+SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+ .withExecutorServiceProvider(() -> ThreadUtils.noClose(sharedService))
+ .build();
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
- .withExecutorServiceProvider(() -> ThreadUtils.noClose(sharedService))
- .build();
+ChannelAgentForwarding forward = new ChannelAgentForwarding(ThreadUtils.noClose(sharedService));
- ChannelAgentForwarding forward = new ChannelAgentForwarding(ThreadUtils.noClose(sharedService));
```
**Note:** Do not share the instance returned by `ThreadUtils.noClose` between services as it interferes with
@@ -92,66 +91,66 @@ and the provided result status code is sent as an `exit-status` message as descr
The provided message is simply logged at DEBUG level.
```java
+// A simple command implementation example
+class MyCommand implements Command, Runnable {
+ private InputStream in;
+ private OutputStream out, err;
+ private ExitCallback callback;
+
+ public MyCommand() {
+ super();
+ }
- // A simple command implementation example
- class MyCommand implements Command, Runnable {
- private InputStream in;
- private OutputStream out, err;
- private ExitCallback callback;
-
- public MyCommand() {
- super();
- }
+ @Override
+ public void setInputStream(InputStream in) {
+ this.in = in;
+ }
- @Override
- public void setInputStream(InputStream in) {
- this.in = in;
- }
+ @Override
+ public void setOutputStream(OutputStream out) {
+ this.out = out;
+ }
- @Override
- public void setOutputStream(OutputStream out) {
- this.out = out;
- }
+ @Override
+ public void setErrorStream(OutputStream err) {
+ this.err = err;
+ }
- @Override
- public void setErrorStream(OutputStream err) {
- this.err = err;
- }
+ @Override
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
- @Override
- public void setExitCallback(ExitCallback callback) {
- this.callback = callback;
- }
+ @Override
+ public void start(Environment env) throws IOException {
+ spawnHandlerThread(this);
+ }
- @Override
- public void start(Environment env) throws IOException {
- spawnHandlerThread(this);
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ String cmd = readCommand(in);
+ if ("exit".equals(cmd)) {
+ break;
+ }
+
+ handleCommand(cmd, out);
+ } catch (Exception e) {
+ writeError(err, e);
+ callback.onExit(-1, e.getMessage());
+ return;
}
- @Override
- public void run() {
- while(true) {
- try {
- String cmd = readCommand(in);
- if ("exit".equals(cmd)) {
- break;
- }
-
- handleCommand(cmd, out);
- } catch (Exception e) {
- writeError(err, e);
- callback.onExit(-1, e.getMessage());
- return;
- }
-
- callback.onExit(0);
- }
+ callback.onExit(0);
+ }
- @Override
- public void destroy() throws Exception {
- ...release any allocated resources...
- }
+ @Override
+ public void destroy() throws Exception {
+ ...release any allocated resources...
}
+}
+
```
### `Aware` interfaces
diff --git a/docs/event-listeners.md b/docs/event-listeners.md
index 6aa59cf..d360140 100644
--- a/docs/event-listeners.md
+++ b/docs/event-listeners.md
@@ -10,21 +10,20 @@ listeners registered on a specific `Channel` - e.g.,
```java
-
- // Any channel event will be signalled to ALL the registered listeners
- sshClient/Server.addChannelListener(new Listener1());
- sshClient/Server.addSessionListener(new SessionListener() {
- @Override
- public void sessionCreated(Session session) {
- session.addChannelListener(new Listener2());
- session.addChannelListener(new ChannelListener() {
- @Override
- public void channelInitialized(Channel channel) {
- channel.addChannelListener(new Listener3());
- }
- });
- }
- });
+// Any channel event will be signalled to ALL the registered listeners
+sshClient/Server.addChannelListener(new Listener1());
+sshClient/Server.addSessionListener(new SessionListener() {
+ @Override
+ public void sessionCreated(Session session) {
+ session.addChannelListener(new Listener2());
+ session.addChannelListener(new ChannelListener() {
+ @Override
+ public void channelInitialized(Channel channel) {
+ channel.addChannelListener(new Listener3());
+ }
+ });
+ }
+});
```
@@ -45,30 +44,29 @@ In this context, it is worth mentioning that one can attach to sessions **arbitr
```java
+public static final AttributeKey<String> STR_KEY = new AttributeKey<>();
+public static final AttributeKey<Long> LONG_KEY = new AttributeKey<>();
- public static final AttributeKey<String> STR_KEY = new AttributeKey<>();
- public static final AttributeKey<Long> LONG_KEY = new AttributeKey<>();
-
- sshClient/Server.addSessionListener(new SessionListener() {
- @Override
- public void sessionEstablished(Session session) {
- // examine the peer address or the connection context and set some attributes
- }
+sshClient/Server.addSessionListener(new SessionListener() {
+ @Override
+ public void sessionEstablished(Session session) {
+ // examine the peer address or the connection context and set some attributes
+ }
- @Override
- public void sessionCreated(Session session) {
- session.setAttribute(STR_KEY, "Some string value");
- session.setAttribute(LONG_KEY, 3777347L);
- // ...etc...
- }
+ @Override
+ public void sessionCreated(Session session) {
+ session.setAttribute(STR_KEY, "Some string value");
+ session.setAttribute(LONG_KEY, 3777347L);
+ // ...etc...
+ }
- @Override
- public void sessionClosed(Session session) {
- String str = session.getAttribute(STR_KEY);
- Long l = session.getAttribute(LONG_KEY);
- // ... do something with the retrieved attributes ...
- }
- });
+ @Override
+ public void sessionClosed(Session session) {
+ String str = session.getAttribute(STR_KEY);
+ Long l = session.getAttribute(LONG_KEY);
+ // ... do something with the retrieved attributes ...
+ }
+});
```
The attributes cache is automatically cleared once the session is closed.
@@ -114,62 +112,61 @@ or [server](./server-setup.md#providing-server-side-heartbeat).
message received in the session as well.
```java
-
- class MyClientSideReservedSessionMessagesHandler implements ReservedSessionMessagesHandler {
- @Override
- public boolean handleUnimplementedMessage(Session session, int cmd, Buffer buffer) throws Exception {
- switch(cmd) {
- case MY_SPECIAL_CMD1:
- ....
- return true;
- case MY_SPECIAL_CMD2:
- ....
- return true;
- default:
- return false; // send SSH_MSG_UNIMPLEMENTED reply if necessary
- }
+class MyClientSideReservedSessionMessagesHandler implements ReservedSessionMessagesHandler {
+ @Override
+ public boolean handleUnimplementedMessage(Session session, int cmd, Buffer buffer) throws Exception {
+ switch(cmd) {
+ case MY_SPECIAL_CMD1:
+ ....
+ return true;
+ case MY_SPECIAL_CMD2:
+ ....
+ return true;
+ default:
+ return false; // send SSH_MSG_UNIMPLEMENTED reply if necessary
}
}
-
- // client side
- SshClient client = SshClient.setupDefaultClient();
- // This is the default for ALL sessions unless specifically overridden
- client.setReservedSessionMessagesHandler(new MyClientSideReservedSessionMessagesHandler());
- // Adding it via a session listener
- client.setSessionListener(new SessionListener() {
- @Override
- public void sessionCreated(Session session) {
- // Overrides the one set at the client level.
- if (isSomeSessionOfInterest(session)) {
- session.setReservedSessionMessagesHandler(new MyClientSessionReservedSessionMessagesHandler(session));
- }
+}
+
+// client side
+SshClient client = SshClient.setupDefaultClient();
+// This is the default for ALL sessions unless specifically overridden
+client.setReservedSessionMessagesHandler(new MyClientSideReservedSessionMessagesHandler());
+// Adding it via a session listener
+client.setSessionListener(new SessionListener() {
+ @Override
+ public void sessionCreated(Session session) {
+ // Overrides the one set at the client level.
+ if (isSomeSessionOfInterest(session)) {
+ session.setReservedSessionMessagesHandler(new MyClientSessionReservedSessionMessagesHandler(session));
}
- });
+ }
+});
- try (ClientSession session = client.connect(user, host, port).verify(...timeout...).getSession()) {
- // setting it explicitly
- session.setReservedSessionMessagesHandler(new MyOtherClientSessionReservedSessionMessagesHandler(session));
- session.addPasswordIdentity(password);
- session.auth().verify(...timeout...);
+try (ClientSession session = client.connect(user, host, port).verify(...timeout...).getSession()) {
+ // setting it explicitly
+ session.setReservedSessionMessagesHandler(new MyOtherClientSessionReservedSessionMessagesHandler(session));
+ session.addPasswordIdentity(password);
+ session.auth().verify(...timeout...);
- ...use the session...
- }
+ ...use the session...
+}
- // server side
- SshServer server = SshServer.setupDefaultServer();
- // This is the default for ALL sessions unless specifically overridden
- server.setReservedSessionMessagesHandler(new MyServerSideReservedSessionMessagesHandler());
- // Adding it via a session listener
- server.setSessionListener(new SessionListener() {
- @Override
- public void sessionCreated(Session session) {
- // Overrides the one set at the server level.
- if (isSomeSessionOfInterest(session)) {
- session.setReservedSessionMessagesHandler(new MyServerSessionReservedSessionMessagesHandler(session));
- }
+// server side
+SshServer server = SshServer.setupDefaultServer();
+// This is the default for ALL sessions unless specifically overridden
+server.setReservedSessionMessagesHandler(new MyServerSideReservedSessionMessagesHandler());
+// Adding it via a session listener
+server.setSessionListener(new SessionListener() {
+ @Override
+ public void sessionCreated(Session session) {
+ // Overrides the one set at the server level.
+ if (isSomeSessionOfInterest(session)) {
+ session.setReservedSessionMessagesHandler(new MyServerSessionReservedSessionMessagesHandler(session));
}
- });
+ }
+});
```
diff --git a/docs/extensions.md b/docs/extensions.md
index 1f762ae..fa2c61a 100644
--- a/docs/extensions.md
+++ b/docs/extensions.md
@@ -27,6 +27,7 @@ try (ClientSession session = client.connect(login, host, port).await().getSessio
session.auth.verify(...timeout...);
... continue with the authenticated session ...
}
+
```
or
@@ -40,6 +41,7 @@ try (ClientSession session = client.connect(login, host, port).await().getSessio
session.auth.verify(...timeout...);
... continue with the authenticated session ...
}
+
```
**Note:** `UserInteraction#isInteractionAllowed` is consulted prior to invoking `getUpdatedPassword` - if it
diff --git a/docs/git.md b/docs/git.md
index 0d27b25..b04a1f5 100644
--- a/docs/git.md
+++ b/docs/git.md
@@ -13,19 +13,18 @@ created - which is started and stopped as necessary. However, this can be pretty
that access GIT repositories via SSH, one should maintain a **single** client instance and re-use it:
```java
+SshClient client = ...create and setup the client...
+try {
+ client.start();
- SshClient client = ...create and setup the client...
- try {
- client.start();
+ GitSshdSessionFactory sshdFactory = new GitSshdSessionFactory(client); // re-use the same client for all SSH sessions
+ org.eclipse.jgit.transport.SshSessionFactory.setInstance(sshdFactory); // replace the JSCH-based factory
- GitSshdSessionFactory sshdFactory = new GitSshdSessionFactory(client); // re-use the same client for all SSH sessions
- org.eclipse.jgit.transport.SshSessionFactory.setInstance(sshdFactory); // replace the JSCH-based factory
+ ... issue GIT commands that access remote repositories via SSH ....
- ... issue GIT commands that access remote repositories via SSH ....
-
- } finally {
- client.stop();
- }
+} finally {
+ client.stop();
+}
```
### Server-side
@@ -35,33 +34,31 @@ that is invoked in order to allow the user to decide which is the correct GIT re
with all the relevant details - including the command and server session through which the command was received:
```java
-
- GitLocationResolver resolver = (cmd, session, fs) -> ...consult some code - perhaps based on the authenticated username...
- sshd.setCommandFactory(new GitPackCommandFactory().withGitLocationResolver(resolver));
+GitLocationResolver resolver = (cmd, session, fs) -> ...consult some code - perhaps based on the authenticated username...
+sshd.setCommandFactory(new GitPackCommandFactory().withGitLocationResolver(resolver));
```
These command factories also accept a delegate to which non-_git_ commands are routed:
```java
-
- sshd.setCommandFactory(new GitPackCommandFactory()
+sshd.setCommandFactory(new GitPackCommandFactory()
+ .withDelegate(new MyCommandFactory())
+ .withGitLocationResolver(resolver));
+
+// Here is how it looks if SCP is also requested
+sshd.setCommandFactory(new GitPackCommandFactory()
+ .withDelegate(new ScpCommandFactory()
+ .withDelegate(new MyCommandFactory()))
+ .withGitLocationResolver(resolver));
+
+// or
+sshd.setCommandFactory(new ScpCommandFactory()
+ .withDelegate(new GitPackCommandFactory()
.withDelegate(new MyCommandFactory())
- .withGitLocationResolver(resolver));
-
- // Here is how it looks if SCP is also requested
- sshd.setCommandFactory(new GitPackCommandFactory()
- .withDelegate(new ScpCommandFactory()
- .withDelegate(new MyCommandFactory()))
- .withGitLocationResolver(resolver));
+ .withGitLocationResolver(resolver)));
- // or
- sshd.setCommandFactory(new ScpCommandFactory()
- .withDelegate(new GitPackCommandFactory()
- .withDelegate(new MyCommandFactory())
- .withGitLocationResolver(resolver)));
-
- // or any other combination ...
+// or any other combination ...
```
@@ -71,10 +68,9 @@ is completed (regardless of whether successful or not):
```java
-
- sshd.setCommandFactory(new GitPackCommandFactory(resolver)
- .withDelegate(new MyCommandFactory())
- .withExecutorService(myService)
- .withShutdownOnExit(false));
+sshd.setCommandFactory(new GitPackCommandFactory(resolver)
+ .withDelegate(new MyCommandFactory())
+ .withExecutorService(myService)
+ .withShutdownOnExit(false));
```
diff --git a/docs/internals.md b/docs/internals.md
index 50a7152..2826f34 100644
--- a/docs/internals.md
+++ b/docs/internals.md
@@ -4,12 +4,11 @@ As part of the their initialization, both client and server code require the spe
that is used to initialize network connections.
```java
+SshServer server = ...create server instance...
+server.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
- SshServer server = ...create server instance...
- server.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
-
- SshClient client = ... create client instance ...
- client.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
+SshClient client = ... create client instance ...
+client.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
```
@@ -53,10 +52,11 @@ The easiest way to configure a target instance (client/server/session/channel) i
`updateProperty` methods:
```java
- PropertyResolverUtils.updateProperty(client, "prop1", 5L);
- PropertyResolverUtils.updateProperty(server, "prop2", someInteger);
- PropertyResolverUtils.updateProperty(session, "prop3", "hello world");
- PropertyResolverUtils.updateProperty(channel, "prop4", false);
+PropertyResolverUtils.updateProperty(client, "prop1", 5L);
+PropertyResolverUtils.updateProperty(server, "prop2", someInteger);
+PropertyResolverUtils.updateProperty(session, "prop3", "hello world");
+PropertyResolverUtils.updateProperty(channel, "prop4", false);
+
```
**Note**: the `updateProperty` method(s) accept **any** `Object` so care must be taken to provide the expected type. However, at
@@ -68,6 +68,7 @@ least for **primitive** values, the various `getXXXProperty` methods automatical
// all will yield 7365 converted to the relevant type
Long value = PropertyResolverUtils.getLongProperty(client, "prop1");
Integer value = PropertyResolverUtils.getLongProperty(client, "prop1");
+
```
including strings
@@ -78,6 +79,7 @@ including strings
// all will yield 7365
Long value = PropertyResolverUtils.getLongProperty(client, "prop1");
Integer value = PropertyResolverUtils.getLongProperty(client, "prop1");
+
```
### Using the inheritance model for fine-grained/targeted configuration
@@ -103,7 +105,7 @@ configured/customized.
### Welcome banner content customization
-The welcome banner contents are controlled by the `ServerAuthenticationManager.WELCOME_BANNER` configuration
+The welcome banner contents are controlled by the `CoreModuleProperties#WELCOME_BANNER` configuration
key - there are several possible values for this key:
* A simple string - in which case its contents are the welcome banner.
@@ -117,7 +119,7 @@ and its contents are read.
* A [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) or
a [Path](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html) - in this case, the file's contents are __re-loaded__ every time it is required and sent as the banner contents.
-* The special value `ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE` which generates a combined "random art" of
+* The special value `CoreModuleProperties#AUTO_WELCOME_BANNER_VALUE` which generates a combined "random art" of
all the server's keys as described in `Perrig A.` and `Song D.`-s article
[Hash Visualization: a New Technique to improve Real-World Security](http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf) - _International Workshop on Cryptographic Techniques and E-Commerce (CrypTEC '99)_
@@ -129,7 +131,7 @@ all the server's keys as described in `Perrig A.` and `Song D.`-s article
2. If the banner is loaded from a file or URL resource, then one can configure the [Charset](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html) used to convert the file's contents into a string via the `ServerAuthenticationManager.WELCOME_BANNER_CHARSET` configuration key (default=`UTF-8`).
-3. In this context, see also the `ServerAuthenticationManager.WELCOME_BANNER_LANGUAGE` configuration key - which
+3. In this context, see also the `CoreModuleProperties#WELCOME_BANNER_LANGUAGE` configuration key - which
provides control over the declared language tag, although most clients seem to ignore it.
### Welcome banner sending phase
@@ -138,8 +140,8 @@ According to [RFC 4252 - section 5.4](https://tools.ietf.org/html/rfc4252#sectio
> The SSH server may send an SSH_MSG_USERAUTH_BANNER message at any time after this authentication protocol starts and before authentication is successful.
-The code contains a `WelcomeBannerPhase` enumeration that can be used to configure via the `ServerAuthenticationManager.WELCOME_BANNER_PHASE`
-configuration key the authentication phase at which the welcome banner is sent (see also the `ServerAuthenticationManager.DEFAULT_BANNER_PHASE` value).
+The code contains a `WelcomeBannerPhase` enumeration that can be used to configure via the `CoreModuleProperties#WELCOME_BANNER_PHASE`
+configuration key the authentication phase at which the welcome banner is sent (see also the `CoreModuleProperties#DEFAULT_BANNER_PHASE` value).
In this context, note that if the `NEVER` phase is configured, no banner will be sent even if one has been configured via one of the methods mentioned previously.
## `HostConfigEntryResolver`
@@ -150,7 +152,6 @@ client instance follows the [SSH config file](https://www.digitalocean.com/commu
standards, but the interface can be replaced so as to implement whatever proprietary logic is required.
```java
-
SshClient client = SshClient.setupDefaultClient();
client.setHostConfigEntryResolver(new MyHostConfigEntryResolver());
client.start();
@@ -163,6 +164,7 @@ standards, but the interface can be replaced so as to implement whatever proprie
session.addPasswordIdentity(...password1...);
session.auth().verify(...timeout...);
}
+
```
### SSH Jumps
@@ -183,6 +185,7 @@ to connect to the server:
ConnectFuture future = client.connect(new HostConfigEntry(
"", host, port, user,
proxyUser + "@" + proxyHost + ":" + proxyPort));
+
```
The configuration options specified in the configuration file for the jump hosts are also honored.
@@ -206,14 +209,14 @@ if they are malformed - i.e., they never reach the handler.
[RFC 4253 - section 9](https://tools.ietf.org/html/rfc4253#section-9) recommends re-exchanging keys every once in a while
based on the amount of traffic and the selected cipher - the matter is further clarified in [RFC 4251 - section 9.3.2](https://tools.ietf.org/html/rfc4251#section-9.3.2).
-These recommendations are mirrored in the code via the `FactoryManager` related `REKEY_TIME_LIMIT`, `REKEY_PACKETS_LIMIT`
+These recommendations are mirrored in the code via the `CoreModuleProperties` related `REKEY_TIME_LIMIT`, `REKEY_PACKETS_LIMIT`
and `REKEY_BLOCKS_LIMIT` configuration properties that can be used to configure said behavior - please be sure to read
the relevant _Javadoc_ as well as the aforementioned RFC section(s) when manipulating them. This behavior can also be
controlled programmatically by overriding the `AbstractSession#isRekeyRequired()` method.
As an added security mechanism [RFC 4251 - section 9.3.1](https://tools.ietf.org/html/rfc4251#section-9.3.1) recommends adding
"spurious" [SSH_MSG_IGNORE](https://tools.ietf.org/html/rfc4253#section-11.2) messages. This functionality is mirrored in the
-`FactoryManager` related `IGNORE_MESSAGE_FREQUENCY`, `IGNORE_MESSAGE_VARIANCE` and `IGNORE_MESSAGE_SIZE`
+`CoreModuleProperties` related `IGNORE_MESSAGE_FREQUENCY`, `IGNORE_MESSAGE_VARIANCE` and `IGNORE_MESSAGE_SIZE`
configuration properties that can be used to configure said behavior - please be sure to read the relevant _Javadoc_ as well
as the aforementioned RFC section when manipulating them. This behavior can also be controlled programmatically by overriding
the `AbstractSession#resolveIgnoreBufferDataLength()` method.
@@ -226,17 +229,16 @@ ones are derived from `ChannelRequestHandler`(s). In order to add a handler one
it is detected. For global request handlers this is done by registering them on the server:
```java
-
- // NOTE: the following code can be employed on BOTH client and server - the example is for the server
- SshServer server = SshServer.setUpDefaultServer();
- List<RequestHandler<ConnectionService>> oldGlobals = server.getGlobalRequestHandlers();
- // Create a copy in case current one is null/empty/un-modifiable
- List<RequestHandler<ConnectionService>> newGlobals = new ArrayList<>();
- if (GenericUtils.size(oldGlobals) > 0) {
- newGlobals.addAll(oldGLobals);
- }
- newGlobals.add(new MyGlobalRequestHandler());
- server.setGlobalRequestHandlers(newGlobals);
+// NOTE: the following code can be employed on BOTH client and server - the example is for the server
+SshServer server = SshServer.setUpDefaultServer();
+List<RequestHandler<ConnectionService>> oldGlobals = server.getGlobalRequestHandlers();
+// Create a copy in case current one is null/empty/un-modifiable
+List<RequestHandler<ConnectionService>> newGlobals = new ArrayList<>();
+if (GenericUtils.size(oldGlobals) > 0) {
+ newGlobals.addAll(oldGLobals);
+}
+newGlobals.add(new MyGlobalRequestHandler());
+server.setGlobalRequestHandlers(newGlobals);
```
@@ -258,21 +260,21 @@ the handler may choose to build and send the response within its own code, in wh
`Result.Replied` value indicating that it has done so.
```java
+public class MySpecialChannelRequestHandler implements ChannelRequestHandler {
+ ...
- public class MySpecialChannelRequestHandler implements ChannelRequestHandler {
- ...
-
- @Override
- public Result process(Channel channel, String request, boolean wantReply, Buffer buffer) throws Exception {
- if (!"my-special-request".equals(request)) {
- return Result.Unsupported; // Not mine - maybe someone else can handle it
- }
+ @Override
+ public Result process(Channel channel, String request, boolean wantReply, Buffer buffer) throws Exception {
+ if (!"my-special-request".equals(request)) {
+ return Result.Unsupported; // Not mine - maybe someone else can handle it
+ }
- ...handle the request - can read more parameters from the message buffer...
+ ...handle the request - can read more parameters from the message buffer...
- return Result.ReplySuccess/Failure/Replied; // signal processing result
- }
+ return Result.ReplySuccess/Failure/Replied; // signal processing result
}
+}
+
```
#### Default registered handlers
diff --git a/docs/port-forwarding.md b/docs/port-forwarding.md
index d5a1dc0..2ace615 100644
--- a/docs/port-forwarding.md
+++ b/docs/port-forwarding.md
@@ -9,19 +9,19 @@ the tracker is `close()`-d:
```java
+client.addPortForwardingEventListener(new MySuperDuperListener());
- client.addPortForwardingEventListener(new MySuperDuperListener());
+try (ClientSession session = client.connect(user, host, port).verify(...timeout...).getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth().verify(...timeout...);
- try (ClientSession session = client.connect(user, host, port).verify(...timeout...).getSession()) {
- session.addPasswordIdentity(password);
- session.auth().verify(...timeout...);
+ try (PortForwardingTracker tracker = session.createLocal/RemotePortForwardingTracker(...)) {
+ ...do something that requires the tunnel...
+ }
- try (PortForwardingTracker tracker = session.createLocal/RemotePortForwardingTracker(...)) {
- ...do something that requires the tunnel...
- }
+ // Tunnel is torn down when code reaches this point
+}
- // Tunnel is torn down when code reaches this point
- }
```
### Standard port forwarding
@@ -33,11 +33,12 @@ and server. By default, this capability is **disabled** - i.e., the user must pr
`setForwardingFilter` method on the client/server.
```java
- SshClient client = ...create/obtain an instance...
- client.setForwardingFilter(...filter instance...);
+SshClient client = ...create/obtain an instance...
+client.setForwardingFilter(...filter instance...);
+
+SshServer server = ...create/obtain an instance...
+server.setForwardingFilter(...filter instance...);
- SshServer server = ...create/obtain an instance...
- server.setForwardingFilter(...filter instance...);
```
The code contains 2 simple implementations - an `AcceptAllForwardingFilter` and a `RejectAllForwardingFilter` one that can be used for
diff --git a/docs/scp.md b/docs/scp.md
index b3f131c..d7274b1 100644
--- a/docs/scp.md
+++ b/docs/scp.md
@@ -18,24 +18,24 @@ to add this additional dependency to your maven project:
Callback to inform about SCP related events. `ScpTransferEventListener`(s) can be registered on *both* client and server side:
```java
+// Server side
+ScpCommandFactory factory = new ScpCommandFactory(...with/out delegate..);
+factory.addEventListener(new MyServerSideScpTransferEventListener());
+sshd.setCommandFactory(factory);
- // Server side
- ScpCommandFactory factory = new ScpCommandFactory(...with/out delegate..);
- factory.addEventListener(new MyServerSideScpTransferEventListener());
- sshd.setCommandFactory(factory);
+// Client side
+try (ClientSession session = client.connect(user, host, port)
+ .verify(...timeout...)
+ .getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth().verify(...timeout...);
- // Client side
- try (ClientSession session = client.connect(user, host, port)
- .verify(...timeout...)
- .getSession()) {
- session.addPasswordIdentity(password);
- session.auth().verify(...timeout...);
+ ScpClientCreator creator = ... obtain an instance ...
+ ScpClient client = creator.createScpClient(session, new MySuperDuperListener());
- ScpClientCreator creator = ... obtain an instance ...
- ScpClient client = creator.createScpClient(session, new MySuperDuperListener());
+ ...scp.upload/download...
+}
- ...scp.upload/download...
- }
```
## Client-side SCP
@@ -43,12 +43,12 @@ Callback to inform about SCP related events. `ScpTransferEventListener`(s) can b
In order to obtain an `ScpClient` one needs to use an `ScpClientCreator`:
```java
-
try (ClientSession session = ... obtain an instance ...) {
ScpClientCreator creator = ... obtain an instance ...
ScpClient client = creator.createScpClient(session);
... use client ...
}
+
```
A default `ScpClientCreator` instance is provided as part of the module - see `ScpClientCreator.instance()`
@@ -68,13 +68,13 @@ CloseableScpClient createScpClient(...) {
ScpClient client = creator.createScpClient(session);
return CloseableScpClient.singleSessionInstance(client);
}
+
```
The `ScpClientCreator` can also be used to attach a default `ScpTransferEventListener` that will be automatically
add to **all** created SCP client instances through that creator - unless specifically overridden:
```java
-
ClientSession session = ... obtain an instance ...
ScpClientCreator creator = ... obtain an instance ...
creator.setScpTransferEventListener(new MySuperDuperListener());
@@ -93,7 +93,6 @@ the user may replace it and intercept the calls - e.g., for logging, monitoring
The user may attach a default opener that will be automatically attached to **all** clients created unless specifically overridden:
```java
-
/**
* Example of using a non-default opener for monitoring and reporting on transfer progress
*/
@@ -126,6 +125,7 @@ creator.setScpFileOpener(ScpTransferProgressMonitor.INSTANCE);
ScpClient client1 = creator.createScpClient(session); // <<== automatically uses ScpTransferProgressMonitor
ScpClient client2 = creator.createScpClient(session, new SomeOtherOpener()); // <<== uses SomeOtherOpener instead of ScpTransferProgressMonitor
+
```
**Note(s):**
@@ -134,7 +134,7 @@ ScpClient client2 = creator.createScpClient(session, new SomeOtherOpener()); /
**before** the file opener is invoked - so there are a few limitations on what one can do within this interface implementation.
* By default, SCP synchronizes the local copied file data with the file system using the [Java SYNC open option](https://docs.oracle.com/javase/8/docs/api/java/nio/file/StandardOpenOption.html#SYNC).
-This behavior can be controlled by setting the `scp-auto-sync-on-write` (a.k.a. `ScpFileOpener#PROP_AUTO_SYNC_FILE_ON_WRITE`) property to _false_
+This behavior can be controlled by setting the `scp-auto-sync-on-write` (a.k.a. `ScpModuleProperties#PROP_AUTO_SYNC_FILE_ON_WRITE`) property to _false_
or overriding the `DefaultScpFileOpener#resolveOpenOptions`, or even overriding the `ScpFileOpener#openWrite` method altogether.
* Patterns used in `ScpFileOpener#getMatchingFilesToSend` are matched using case sensitivity derived from the O/S as detected by
@@ -159,6 +159,7 @@ ScpCommandFactory factory = new ScpCommandFactory.Builder()
SshServer sshd = ...create an instance...
sshd.setCommandFactory(factory);
+
```
The `ScpCommandFactory` allows users to attach an `ScpFileOpener` and/or `ScpTransferEventListener` having the same behavior as the client - i.e.,
@@ -178,14 +179,14 @@ is likely to require. For this purpose, the `ScpCommandFactory` also implements
```java
+ScpCommandFactory factory = new ScpCommandFactory.Builder()
+ .with(...)
+ .with(...)
+ .build()
+ ;
+sshd.setCommandFactory(factory);
+sshd.setShellFactory(factory);
- ScpCommandFactory factory = new ScpCommandFactory.Builder()
- .with(...)
- .with(...)
- .build()
- ;
- sshd.setCommandFactory(factory);
- sshd.setShellFactory(factory);
```
**Note:** a similar result can be achieved if activating SSHD from the command line by specifying `-o ShellFactory=scp`
@@ -196,12 +197,12 @@ The code provides an `ScpTransferHelper` class that enables copying files betwee
the local file system.
```java
- ClientSession src = ...;
- ClientSession dst = ...;
- // Can also provide a listener in the constructor in order to be informed of the actual transfer progress
- ScpRemote2RemoteTransferHelper helper = new ScpRemote2RemoteTransferHelper(src, dst);
- // can be repeated for as many files as necessary using the same helper
- helper.transferFile("remote/src/path/file1", "remote/dst/path/file2");
+ClientSession src = ...;
+ClientSession dst = ...;
+// Can also provide a listener in the constructor in order to be informed of the actual transfer progress
+ScpRemote2RemoteTransferHelper helper = new ScpRemote2RemoteTransferHelper(src, dst);
+// can be repeated for as many files as necessary using the same helper
+helper.transferFile("remote/src/path/file1", "remote/dst/path/file2");
```
diff --git a/docs/server-setup.md b/docs/server-setup.md
index 16e374c..d420751 100644
--- a/docs/server-setup.md
+++ b/docs/server-setup.md
@@ -9,8 +9,7 @@ server - for more details refer to the `SshServer` class.
Creating an instance of `SshServer` is as simple as creating a new object
```java
-
- SshServer sshd = SshServer.setUpDefaultServer();
+SshServer sshd = SshServer.setUpDefaultServer();
```
@@ -46,8 +45,7 @@ implementation that you can use if you want. This implementation will create a p
so it's mostly useful to launch the OS native shell. E.g.,
```java
-
- sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
+sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
```
@@ -66,8 +64,7 @@ SSHD provides a `CommandFactory` to support SCP that can be configured in the fo
```java
-
- sshd.setCommandFactory(new ScpCommandFactory());
+sshd.setCommandFactory(new ScpCommandFactory());
```
@@ -76,8 +73,7 @@ of the `ScpCommandFactory`. The `ScpCommandFactory` will intercept SCP commands
other commands to the delegate `CommandFactory`
```java
-
- sshd.setCommandFactory(new ScpCommandFactory(myCommandFactory));
+sshd.setCommandFactory(new ScpCommandFactory(myCommandFactory));
```
@@ -95,11 +91,10 @@ The SSHD server security layer has to be customized to suit your needs. This lay
These custom classes can be configured on the SSHD server using the respective setter methods:
```java
-
- sshd.setPasswordAuthenticator(new MyPasswordAuthenticator());
- sshd.setPublickeyAuthenticator(new MyPublickeyAuthenticator());
- sshd.setKeyboardInteractiveAuthenticator(new MyKeyboardInteractiveAuthenticator());
- ...etc...
+sshd.setPasswordAuthenticator(new MyPasswordAuthenticator());
+sshd.setPublickeyAuthenticator(new MyPublickeyAuthenticator());
+sshd.setKeyboardInteractiveAuthenticator(new MyKeyboardInteractiveAuthenticator());
+...etc...
```
@@ -118,9 +113,8 @@ The list of supported implementations can be changed to suit one's needs, or one
Configuring supported factories can be done with the following code:
```java
-
- sshd.setCipherFactories(Arrays.asList(BuiltinCiphers.aes256ctr, BuiltinCiphers.aes192ctr, BuiltinCiphers.aes128ctr));
- sshd.setKeyExchangeFactories(Arrays.asList(new MyKex1(), new MyKex2(), BuiltinKeyExchange.A, ...etc...));
+sshd.setCipherFactories(Arrays.asList(BuiltinCiphers.aes256ctr, BuiltinCiphers.aes192ctr, BuiltinCiphers.aes128ctr));
+sshd.setKeyExchangeFactories(Arrays.asList(new MyKex1(), new MyKex2(), BuiltinKeyExchange.A, ...etc...));
```
diff --git a/docs/sftp.md b/docs/sftp.md
index 3fd7cc9..447bd3c 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -18,12 +18,11 @@ in the `sshd-sftp` artifact, so one needs to add this additional dependency to o
On the server side, the following code needs to be added:
```java
-
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
- ...with...
- ...with...
- .build();
- server.setSubsystemFactories(Collections.singletonList(factory));
+SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+ ...with...
+ ...with...
+ .build();
+server.setSubsystemFactories(Collections.singletonList(factory));
```
@@ -33,12 +32,11 @@ it must be protected from shutdown if the user needs it to remain active between
This can be done via the `ThreadUtils#noClose` utility:
```java
-
- CloseableExecutorService mySpecialExecutor = ...;
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
- .withExecutorServiceProvider(() -> ThreadUtils.noClose(mySpecialExecutor))
- .build();
- server.setSubsystemFactories(Collections.singletonList(factory));
+CloseableExecutorService mySpecialExecutor = ...;
+SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+ .withExecutorServiceProvider(() -> ThreadUtils.noClose(mySpecialExecutor))
+ .build();
+server.setSubsystemFactories(Collections.singletonList(factory));
```
@@ -48,29 +46,29 @@ Provides information about major SFTP protocol events. The provided `File/Direct
store user-defined attributes via its `AttributeStore` implementation. The listener is registered at the `SftpSubsystemFactory`:
```java
- public class MySfpEventListener implements SftpEventListener {
- private static final AttributeKey<SomeType> MY_SPECIAL_KEY = new Attribute<SomeType>();
+public class MySfpEventListener implements SftpEventListener {
+ private static final AttributeKey<SomeType> MY_SPECIAL_KEY = new Attribute<SomeType>();
- ...
- @Override
- public void opening(ServerSession session, String remoteHandle, Handle localHandle) throws IOException {
- localHandle.setAttribute(MY_SPECIAL_KEY, instanceOfSomeType);
- }
+ ...
+ @Override
+ public void opening(ServerSession session, String remoteHandle, Handle localHandle) throws IOException {
+ localHandle.setAttribute(MY_SPECIAL_KEY, instanceOfSomeType);
+ }
- @Override
- public void writing(
- ServerSession session, String remoteHandle, FileHandle localHandle,
- long offset, byte[] data, int dataOffset, int dataLen)
- throws IOException {
- SomeType myData = localHandle.getAttribute(MY_SPECIAL_KEY);
- ...do something based on my data...
- }
+ @Override
+ public void writing(
+ ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen)
+ throws IOException {
+ SomeType myData = localHandle.getAttribute(MY_SPECIAL_KEY);
+ ...do something based on my data...
}
+}
- SftpSubsystemFactory factory = new SftpSubsystemFactory();
- factory.addSftpEventListener(new MySftpEventListener());
- sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(factory));
+SftpSubsystemFactory factory = new SftpSubsystemFactory();
+factory.addSftpEventListener(new MySftpEventListener());
+sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(factory));
```
@@ -86,11 +84,10 @@ and thus be able to track and/or intervene in all opened files and folders throu
The accessor is registered/overwritten in via the `SftpSubSystemFactory`:
```java
-
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
- .withFileSystemAccessor(new MySftpFileSystemAccessor())
- .build();
- server.setSubsystemFactories(Collections.singletonList(factory));
+SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+ .withFileSystemAccessor(new MySftpFileSystemAccessor())
+ .build();
+server.setSubsystemFactories(Collections.singletonList(factory));
```
@@ -142,15 +139,14 @@ In order to obtain an `SftpClient` instance one needs to use an `SftpClientFacto
```java
-
- try (ClientSession session = ...obtain session...) {
- SftpClientFactory factory = ...obtain factory...
- try (SftpClient client = factory.createSftpClient(session)) {
- ... use the SFTP client...
- }
-
- // NOTE: session is still alive here...
+try (ClientSession session = ...obtain session...) {
+ SftpClientFactory factory = ...obtain factory...
+ try (SftpClient client = factory.createSftpClient(session)) {
+ ... use the SFTP client...
}
+
+ // NOTE: session is still alive here...
+}
```
@@ -160,7 +156,6 @@ If the intended use of the client instance is "one-shot" - i.e., the client sess
SFTP client instance is closed, then it is possible to obtain a special wrapper that implements this functionality:
```java
-
// The underlying session will also be closed when the client is
try (SftpClient client = createSftpClient(....)) {
... use the SFTP client...
@@ -172,6 +167,7 @@ SftpClient createSftpClient(...) {
SftpClient client = factory.createSftpClient(session);
return client.singleSessionInstance();
}
+
```
### Using a custom `SftpClientFactory`
@@ -181,18 +177,17 @@ The code creates `SftpClient`-s and `SftpFileSystem`-s using a default built-in
implementations - e.g., in order to override some default behavior - e.g.:
```java
+SshClient client = ... setup client...
- SshClient client = ... setup client...
+try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth.verify(timeout);
- try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
- session.addPasswordIdentity(password);
- session.auth.verify(timeout);
-
- // User-specific factory
- try (SftpClient sftp = MySpecialSessionSftpClientFactory.INSTANCE.createSftpClient(session)) {
- ... instance created through SpecialSessionSftpClientFactory ...
- }
+ // User-specific factory
+ try (SftpClient sftp = MySpecialSessionSftpClientFactory.INSTANCE.createSftpClient(session)) {
+ ... instance created through SpecialSessionSftpClientFactory ...
}
+}
```
@@ -204,24 +199,23 @@ range.
```java
+SftpVersionSelector myVersionSelector = new SftpVersionSelector() {
+ @Override
+ public int selectVersion(ClientSession session, boolean initial, int current, List<Integer> available) {
+ int selectedVersion = ...run some logic to decide...;
+ return selectedVersion;
+ }
+};
- SftpVersionSelector myVersionSelector = new SftpVersionSelector() {
- @Override
- public int selectVersion(ClientSession session, boolean initial, int current, List<Integer> available) {
- int selectedVersion = ...run some logic to decide...;
- return selectedVersion;
- }
- };
-
- try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
- session.addPasswordIdentity(password);
- session.auth.verify(timeout);
+try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth.verify(timeout);
- SftpClientFactory factory = SftpClientFactory.instance();
- try (SftpClient sftp = factory.createSftpClient(session, myVersionSelector)) {
- ... do SFTP related stuff...
- }
+ SftpClientFactory factory = SftpClientFactory.instance();
+ try (SftpClient sftp = factory.createSftpClient(session, myVersionSelector)) {
+ ... do SFTP related stuff...
}
+}
```
@@ -248,28 +242,27 @@ standard [java.nio](https://docs.oracle.com/javase/8/docs/api/java/nio/package-f
system.
```java
+// Direct URI
+Path remotePath = Paths.get(new URI("sftp://user:password@host/some/remote/path"));
+// Releasing the file-system once no longer necessary
+try (FileSystem fs = remotePath.getFileSystem()) {
+ ... work with the remote path...
+}
- // Direct URI
- Path remotePath = Paths.get(new URI("sftp://user:password@host/some/remote/path"));
- // Releasing the file-system once no longer necessary
- try (FileSystem fs = remotePath.getFileSystem()) {
- ... work with the remote path...
- }
-
- // "Mounting" a file system
- URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
- try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap())) {
- Path remotePath = fs.getPath("/some/remote/path");
- ...
- }
+// "Mounting" a file system
+URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
+try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap())) {
+ Path remotePath = fs.getPath("/some/remote/path");
+ ...
+}
- // Full programmatic control
- SshClient client = ...setup and start the SshClient instance...
- SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
- URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
- try (FileSystem fs = provider.newFileSystem(uri, Collections.<String, Object>emptyMap())) {
- Path remotePath = fs.getPath("/some/remote/path");
- }
+// Full programmatic control
+SshClient client = ...setup and start the SshClient instance...
+SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
+URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
+try (FileSystem fs = provider.newFileSystem(uri, Collections.<String, Object>emptyMap())) {
+ Path remotePath = fs.getPath("/some/remote/path");
+}
```
@@ -277,21 +270,21 @@ system.
```java
+try (InputStream input = Files.newInputStream(remotePath)) {
+ ...read from remote file...
+}
- try (InputStream input = Files.newInputStream(remotePath)) {
- ...read from remote file...
- }
-
- try (DirectoryStream<Path> ds = Files.newDirectoryStream(remoteDir)) {
- for (Path remoteFile : ds) {
- if (Files.isRegularFile(remoteFile)) {
- System.out.println("Delete " + remoteFile + " size=" + Files.size(remoteFile));
- Files.delete(remoteFile);
- } else if (Files.isDirectory(remoteFile)) {
- System.out.println(remoteFile + " - directory");
- }
+try (DirectoryStream<Path> ds = Files.newDirectoryStream(remoteDir)) {
+ for (Path remoteFile : ds) {
+ if (Files.isRegularFile(remoteFile)) {
+ System.out.println("Delete " + remoteFile + " size=" + Files.size(remoteFile));
+ Files.delete(remoteFile);
+ } else if (Files.isDirectory(remoteFile)) {
+ System.out.println(remoteFile + " - directory");
}
}
+}
+
```
It is highly recommended to `close()` the mounted file system once no longer necessary in order to release the
@@ -313,25 +306,24 @@ configuration keys and values.
```java
+// Using explicit parameters
+Map<String, Object> params = new HashMap<>();
+params.put("param1", value1);
+params.put("param2", value2);
+...etc...
+
+URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
+try (FileSystem fs = FileSystems.newFileSystem(uri, params)) {
+ Path remotePath = fs.getPath("/some/remote/path");
+ ... work with the remote path...
+}
- // Using explicit parameters
- Map<String, Object> params = new HashMap<>();
- params.put("param1", value1);
- params.put("param2", value2);
- ...etc...
-
- URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password);
- try (FileSystem fs = FileSystems.newFileSystem(uri, params)) {
- Path remotePath = fs.getPath("/some/remote/path");
- ... work with the remote path...
- }
-
- // Using URI parameters
- Path remotePath = Paths.get(new URI("sftp://user:password@host/some/remote/path?param1=value1¶m2=value2..."));
- // Releasing the file-system once no longer necessary
- try (FileSystem fs = remotePath.getFileSystem()) {
- ... work with the remote path...
- }
+// Using URI parameters
+Path remotePath = Paths.get(new URI("sftp://user:password@host/some/remote/path?param1=value1¶m2=value2..."));
+// Releasing the file-system once no longer necessary
+try (FileSystem fs = remotePath.getFileSystem()) {
+ ... work with the remote path...
+}
```
@@ -339,17 +331,16 @@ configuration keys and values.
```java
-
- Map<String, Object> params = new HashMap<>();
- params.put("param1", value1);
- params.put("param2", value2);
-
- // The value of 'param1' is overridden in the URI
- try (FileSystem fs = FileSystems.newFileSystem(
- new URI("sftp://user:password@host/some/remote/path?param1=otherValue1", params)) {
- Path remotePath = fs.getPath("/some/remote/path");
- ... work with the remote path...
- }
+Map<String, Object> params = new HashMap<>();
+params.put("param1", value1);
+params.put("param2", value2);
+
+// The value of 'param1' is overridden in the URI
+try (FileSystem fs = FileSystems.newFileSystem(
+ new URI("sftp://user:password@host/some/remote/path?param1=otherValue1", params)) {
+ Path remotePath = fs.getPath("/some/remote/path");
+ ... work with the remote path...
+}
```
@@ -362,25 +353,24 @@ to override some options or provide their own - e.g., execute a password-less au
the (default) password-based one:
```java
-
- SftpFileSystemProvider provider = ... obtain/create a provider ...
- provider.setSftpFileSystemClientSessionInitializer(new SftpFileSystemClientSessionInitializer() {
- @Override
- public void authenticateClientSession(
- SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
- throws IOException {
- /*
- * Set up password-less login instead of password-based using the specified key
- *
- * Note: if SSH client and/or session already have a KeyPairProvider set up and the code
- * knows that these keys are already registered with the remote server, then no need to
- * add the public key identitiy - can simply call sesssion.auth().verify(context.getMaxAuthTime()).
- */
- KeyPair kp = ... obtain a registered key-pair...
- session.addPublicKeyIdentity(kp);
- sesssion.auth().verify(context.getMaxAuthTime());
- }
- });
+SftpFileSystemProvider provider = ... obtain/create a provider ...
+provider.setSftpFileSystemClientSessionInitializer(new SftpFileSystemClientSessionInitializer() {
+ @Override
+ public void authenticateClientSession(
+ SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
+ throws IOException {
+ /*
+ * Set up password-less login instead of password-based using the specified key
+ *
+ * Note: if SSH client and/or session already have a KeyPairProvider set up and the code
+ * knows that these keys are already registered with the remote server, then no need to
+ * add the public key identitiy - can simply call sesssion.auth().verify(context.getMaxAuthTime()).
+ */
+ KeyPair kp = ... obtain a registered key-pair...
+ session.addPublicKeyIdentity(kp);
+ sesssion.auth().verify(context.getMaxAuthTime());
+ }
+});
```
@@ -391,18 +381,17 @@ and thus the "visible" names by the client become corrupted, or even worse - cau
a `get/setNameDecodingCharset` method which enables the user to modify the charset - even while the SFTP session is in progress - e.g.:
```java
+try (SftpClient client = ...obtain an instance...) {
+ client.setNameDecodingCharset(Charset.forName("ISO-8859-8"));
+ for (DirEntry entry : client.readDir(...some path...)) {
+ ...handle entry assuming ISO-8859-8 encoded names...
+ }
- try (SftpClient client = ...obtain an instance...) {
- client.setNameDecodingCharset(Charset.forName("ISO-8859-8"));
- for (DirEntry entry : client.readDir(...some path...)) {
- ...handle entry assuming ISO-8859-8 encoded names...
- }
-
- client.setNameDecodingCharset(Charset.forName("ISO-8859-4"));
- for (DirEntry entry : client.readDir(...some other path...)) {
- ...handle entry assuming ISO-8859-4 encoded names...
- }
+ client.setNameDecodingCharset(Charset.forName("ISO-8859-4"));
+ for (DirEntry entry : client.readDir(...some other path...)) {
+ ...handle entry assuming ISO-8859-4 encoded names...
}
+}
```
@@ -410,32 +399,31 @@ The initial charset can be pre-configured on the client/session by using the `sf
UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset.Charset` instance - e.g.:
```java
+SshClient client = ... setup/obtain an instance...
+// default for ALL SFTP clients obtained through this client
+PropertyResolverUtils.updateProperty(client, SftpModuleProperties.NAME_DECODING_CHARSET.getName(), "ISO-8859-8");
- SshClient client = ... setup/obtain an instance...
- // default for ALL SFTP clients obtained through this client
- PropertyResolverUtils.updateProperty(client, SftpClient.NAME_DECODING_CHARSET, "ISO-8859-8");
-
- try (ClientSession session = client.connect(...)) {
- session.addPasswordIdentity(password);
- session.auth().verify(timeout);
+try (ClientSession session = client.connect(...)) {
+ session.addPasswordIdentity(password);
+ session.auth().verify(timeout);
- // default for ALL SFTP clients obtained through the session - overrides client setting
- PropertyResolverUtils.updateProperty(session, SftpClient.NAME_DECODING_CHARSET, "ISO-8859-4");
+ // default for ALL SFTP clients obtained through the session - overrides client setting
+ PropertyResolverUtils.updateProperty(session, SftpModuleProperties.NAME_DECODING_CHARSET.getName(), "ISO-8859-4");
- SftpClientFactory factory = SftpClientFactory.instance();
- try (SftpClient sftp = factory.createSftpClient(session)) {
- for (DirEntry entry : sftp.readDir(...some path...)) {
- ...handle entry assuming ISO-8859-4 (inherited from the session) encoded names...
- }
+ SftpClientFactory factory = SftpClientFactory.instance();
+ try (SftpClient sftp = factory.createSftpClient(session)) {
+ for (DirEntry entry : sftp.readDir(...some path...)) {
+ ...handle entry assuming ISO-8859-4 (inherited from the session) encoded names...
+ }
- // override the inherited default from the session
- sftp.setNameDecodingCharset(Charset.forName("ISO-8859-1"));
+ // override the inherited default from the session
+ sftp.setNameDecodingCharset(Charset.forName("ISO-8859-1"));
- for (DirEntry entry : sftp.readDir(...some other path...)) {
- ...handle entry assuming ISO-8859-1 encoded names...
- }
+ for (DirEntry entry : sftp.readDir(...some other path...)) {
+ ...handle entry assuming ISO-8859-1 encoded names...
}
}
+}
```
@@ -469,16 +457,17 @@ Then scan results from `root` are expected as follows for the given patterns
classes for supported patterns and matching - include case sensitive vs. insensitive match.
```java
- // Using an SftpPathDirectoryScanner
- FileSystem fs = ... obtain an SFTP file system instance ...
- Path rootDir = fs.getPath(...remote path...);
- DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, ...pattern...);
- Collection<Path> matches = ds.scan();
-
- // Using an SftpClientDirectoryScanner
- SftpClient client = ... obtain a client instance ...
- SftpClientDirectoryScanner ds = new SftpClientDirectoryScanner(basedir, ...pattern...);
- Collection<ScanDirEntry> matches = ds.scan(client);
+// Using an SftpPathDirectoryScanner
+FileSystem fs = ... obtain an SFTP file system instance ...
+Path rootDir = fs.getPath(...remote path...);
+DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, ...pattern...);
+Collection<Path> matches = ds.scan();
+
+// Using an SftpClientDirectoryScanner
+SftpClient client = ... obtain a client instance ...
+SftpClientDirectoryScanner ds = new SftpClientDirectoryScanner(basedir, ...pattern...);
+Collection<ScanDirEntry> matches = ds.scan(client);
+
```
## Extensions
@@ -514,55 +503,53 @@ can easily add support for more extensions in a similar manner as the existing o
and then registering it at the `ParserUtils` - see the existing ones for details how this can be achieved.
```java
-
- // properietary/special extension parser
- ParserUtils.registerExtension(new MySpecialExtension());
-
- try (ClientSession session = client.connect(username, host, port).verify(timeout).getSession()) {
- session.addPasswordIdentity(password);
- session.auth().verify(timeout);
-
- SftpClientFactory factory = SftpClientFactory.instance();
- try (SftpClient sftp = factory.createSftpClient(session)) {
- Map<String, byte[]> extensions = sftp.getServerExtensions();
- // Key=extension name, value=registered parser instance
- Map<String, ?> data = ParserUtils.parse(extensions);
- for (Map.Entry<String, ?> de : data.entrySet()) {
- String extName = de.getKey();
- Object extValue = de.getValue();
- if (SftpConstants.EXT_ACL_SUPPORTED.equalsIgnoreCase(extName)) {
- AclCapabilities capabilities = (AclCapabilities) extValue;
- ...see what other information can be gleaned from it...
- } else if (SftpConstants.EXT_VERSIONS.equalsIgnoreCase(extName)) {
- Versions versions = (Versions) extValue;
- ...see what other information can be gleaned from it...
- } else if ("my-special-extension".equalsIgnoreCase(extName)) {
- MySpecialExtension special = (MySpecialExtension) extValue;
- ...see what other information can be gleaned from it...
- } // ...etc....
- }
+// properietary/special extension parser
+ParserUtils.registerExtension(new MySpecialExtension());
+
+try (ClientSession session = client.connect(username, host, port).verify(timeout).getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth().verify(timeout);
+
+ SftpClientFactory factory = SftpClientFactory.instance();
+ try (SftpClient sftp = factory.createSftpClient(session)) {
+ Map<String, byte[]> extensions = sftp.getServerExtensions();
+ // Key=extension name, value=registered parser instance
+ Map<String, ?> data = ParserUtils.parse(extensions);
+ for (Map.Entry<String, ?> de : data.entrySet()) {
+ String extName = de.getKey();
+ Object extValue = de.getValue();
+ if (SftpConstants.EXT_ACL_SUPPORTED.equalsIgnoreCase(extName)) {
+ AclCapabilities capabilities = (AclCapabilities) extValue;
+ ...see what other information can be gleaned from it...
+ } else if (SftpConstants.EXT_VERSIONS.equalsIgnoreCase(extName)) {
+ Versions versions = (Versions) extValue;
+ ...see what other information can be gleaned from it...
+ } else if ("my-special-extension".equalsIgnoreCase(extName)) {
+ MySpecialExtension special = (MySpecialExtension) extValue;
+ ...see what other information can be gleaned from it...
+ } // ...etc....
}
}
+}
```
One can skip all the conditional code if a specific known extension is required:
```java
-
- try (ClientSession session = client.connect(username, host, port).verify(timeout).getSession()) {
- session.addPasswordIdentity(password);
- session.auth().verify(timeout);
-
- SftpClientFactory factory = SftpClientFactory.instance();
- try (SftpClient sftp = factory.createSftpClient(session)) {
- // Returns null if extension is not supported by remote server
- SpaceAvailableExtension space = sftp.getExtension(SpaceAvailableExtension.class);
- if (space != null) {
- ...use it...
- }
+try (ClientSession session = client.connect(username, host, port).verify(timeout).getSession()) {
+ session.addPasswordIdentity(password);
+ session.auth().verify(timeout);
+
+ SftpClientFactory factory = SftpClientFactory.instance();
+ try (SftpClient sftp = factory.createSftpClient(session)) {
+ // Returns null if extension is not supported by remote server
+ SpaceAvailableExtension space = sftp.getExtension(SpaceAvailableExtension.class);
+ if (space != null) {
+ ...use it...
}
}
+}
```
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/Property.java b/sshd-common/src/main/java/org/apache/sshd/common/Property.java
index 6c3d63d..a8055c2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/Property.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/Property.java
@@ -36,7 +36,7 @@ import org.apache.sshd.common.util.ValidateUtils;
* @param <T> The generic property type
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public interface Property<T> {
+public interface Property<T> extends NamedResource {
static Property<String> string(String name) {
return string(name, null);
@@ -382,11 +382,6 @@ public interface Property<T> {
}
/**
- * @return Property name
- */
- String getName();
-
- /**
* @return Property type - <B>Note:</B> for primitive types the wrapper equivalent is returned
*/
Class<T> getType();