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 <lgoldst...@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:d...@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();