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 da29581  [SSHD-1202] Provide SftpErrorDataHandler callback support for 
SFTP client
da29581 is described below

commit da2958172281ef20e51681afe036bd42c9cf843a
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Fri Aug 6 08:10:52 2021 +0300

    [SSHD-1202] Provide SftpErrorDataHandler callback support for SFTP client
---
 CHANGES.md                                         |   1 +
 docs/sftp.md                                       |  22 ++++
 .../main/java/org/apache/sshd/cli/CliLogger.java   |   2 +-
 .../org/apache/sshd/cli/client/ScpCommandMain.java |   2 +-
 .../apache/sshd/cli/client/SftpCommandMain.java    |  37 ++++++-
 .../sshd/cli/client/SshClientCliSupport.java       |   2 +-
 .../org/apache/sshd/cli/client/SshClientMain.java  |   4 +-
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |   2 +-
 .../apache/sshd/cli/client/ChannelExecMain.java    |   2 +-
 .../sshd/client/config/hosts/HostConfigEntry.java  |   6 +-
 .../sshd/client/config/hosts/KnownHostEntry.java   |   4 +-
 .../common/config/ConfigFileReaderSupport.java     |   4 +-
 .../common/config/keys/AuthorizedKeyEntry.java     |   4 +-
 .../common/config/keys/PrivateKeyEntryDecoder.java |   2 +-
 .../openssh/OpenSSHDSSPrivateKeyEntryDecoder.java  |   2 +-
 .../OpenSSHECDSAPrivateKeyEntryDecoder.java        |   2 +-
 .../openssh/OpenSSHRSAPrivateKeyDecoder.java       |   2 +-
 .../loader/pem/DSSPEMResourceKeyPairParser.java    |   2 +-
 .../loader/pem/ECDSAPEMResourceKeyPairParser.java  |   2 +-
 .../loader/pem/RSAPEMResourceKeyPairParser.java    |   2 +-
 .../openssh/OpenSSHKeyPairResourceWriter.java      |   2 +-
 .../sshd/common/util/buffer/BufferUtils.java       |  35 ++++++
 .../{NoCloseReader.java => LineDataConsumer.java}  |  34 +++---
 .../io/{ => input}/CloseableEmptyInputStream.java  |   2 +-
 .../util/io/{ => input}/EmptyInputStream.java      |   2 +-
 .../io/{ => input}/InputStreamWithChannel.java     |   2 +-
 .../util/io/{ => input}/LimitInputStream.java      |   2 +-
 .../util/io/{ => input}/NoCloseInputStream.java    |   2 +-
 .../common/util/io/{ => input}/NoCloseReader.java  |   2 +-
 .../util/io/{ => input}/NullInputStream.java       |   2 +-
 .../common/util/io/output/LineLevelAppender.java   | 118 +++++++++++++++++++++
 .../util/io/output/LineLevelAppenderStream.java    |  99 +++++++++++++++++
 .../common/util/io/output}/LineOutputStream.java   |   2 +-
 .../io/{ => output}/LoggingFilterOutputStream.java |   2 +-
 .../util/io/{ => output}/NoCloseOutputStream.java  |   2 +-
 .../common/util/io/{ => output}/NoCloseWriter.java |   2 +-
 .../util/io/{ => output}/NullOutputStream.java     |   2 +-
 .../util/io/{ => output}/NullPrintStream.java      |   2 +-
 .../io/{ => output}/OutputStreamWithChannel.java   |   2 +-
 .../{ => output}/SecureByteArrayOutputStream.java  |   2 +-
 .../eddsa/Ed25519PEMResourceKeyParser.java         |   2 +-
 .../OpenSSHEd25519PrivateKeyEntryDecoder.java      |   2 +-
 .../openssh/OpenSSHKeyPairResourceWriterTest.java  |   2 +-
 .../util/io/{ => input}/EmptyInputStreamTest.java  |   2 +-
 .../util/io/{ => input}/LimitInputStreamTest.java  |   2 +-
 .../io/{ => input}/NoCloseInputStreamTest.java     |   2 +-
 .../util/io/{ => input}/NoCloseReaderTest.java     |   2 +-
 .../util/io/{ => input}/NullInputStreamTest.java   |   2 +-
 .../util/io/output}/LineOutputStreamTest.java      |   2 +-
 .../io/{ => output}/NoCloseOutputStreamTest.java   |   2 +-
 .../util/io/{ => output}/NoCloseWriterTest.java    |   2 +-
 .../util/io/{ => output}/NullOutputStreamTest.java |   2 +-
 .../EndlessTarpitSenderSupportDevelopment.java     |   2 +-
 .../apache/sshd/client/session/ClientSession.java  |   4 +-
 .../apache/sshd/server/channel/ChannelSession.java |   2 +-
 .../sshd/server/channel/PipeDataReceiver.java      |   2 +-
 .../java/org/apache/sshd/KeyReExchangeTest.java    |   2 +-
 .../java/org/apache/sshd/WindowAdjustTest.java     |   2 +-
 .../java/org/apache/sshd/client/ClientTest.java    |   2 +-
 .../config/keys/AuthorizedKeysTestSupport.java     |   4 +-
 .../scp/client/ScpRemote2RemoteTransferHelper.java |   2 +-
 .../java/org/apache/sshd/scp/common/ScpHelper.java |   2 +-
 .../apache/sshd/sftp/client/SftpClientFactory.java |  54 ++++++++--
 .../sshd/sftp/client/SftpErrorDataHandler.java     |  36 +++----
 .../apache/sshd/sftp/client/fs/SftpFileSystem.java |  21 +++-
 .../fs/SftpFileSystemClientSessionInitializer.java |  20 ++--
 .../sftp/client/fs/SftpFileSystemProvider.java     |  44 ++++++--
 .../sshd/sftp/client/impl/AbstractSftpClient.java  |  21 +++-
 .../sshd/sftp/client/impl/DefaultSftpClient.java   |  45 +++++---
 .../sftp/client/impl/DefaultSftpClientFactory.java |  17 +--
 .../sftp/client/impl/SftpInputStreamAsync.java     |   2 +-
 .../sftp/client/impl/SftpOutputStreamAsync.java    |   2 +-
 .../sftp/client/SftpInputStreamWithChannel.java    |   2 +-
 .../sftp/client/SftpOutputStreamWithChannel.java   |   2 +-
 74 files changed, 582 insertions(+), 156 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 531127d..9c4838c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -33,3 +33,4 @@
 * [SSHD-1168](https://issues.apache.org/jira/browse/SSHD-1168) OpenSSH 
certificates: check certificate type
 * [SSHD-1171](https://issues.apache.org/jira/browse/SSHD-1171) 
OpenSSHCertificatesTest: certificates expire in 2030
 * [SSHD-1172](https://issues.apache.org/jira/browse/SSHD-1172) Expiration of 
OpenSshCertificates needs to compare timestamps as unsigned long
+* [SSHD-1202](https://issues.apache.org/jira/browse/SSHD-1202) Provide 
SftpErrorDataHandler callback support for SFTP client.
diff --git a/docs/sftp.md b/docs/sftp.md
index 4d60f86..99a2625 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -228,6 +228,28 @@ configuration key. The same can be achieved for the CLI 
SSHD code by specifying
 
 For more advanced restrictions one needs to sub-class `SftpSubSystem` and 
provide a non-default `SftpSubsystemFactory` that uses the sub-classed code.
 
+### Intercepting data sent via STDERR channel data from the server
+
+According to [SFTP version 4 - section 
3.1](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-3.1) the 
server MAY send error data through the STDERR pipeline.
+By default, the code ignores such data - however, users may register a 
`SftpErrorDataHandler` that will be invoked whenever such data is received from 
the server.
+
+```java
+ClientSession session = ...establish a session...
+SftpClientFactory factory = ...obtain a factory instance...
+
+try (SftpClient client = factory.createSftpClient(session, new 
MySftpErrorDataHandler())) {
+   ...
+}
+```
+
+The same applies to the `SftpFileSystem` - users may provide a custom error 
data handler that will be invoked whenever such data is received from the 
server.
+
+**Note:**
+
+* Error data handling must be **short** or it will cause the SSH session to 
hang - any long/blocking processing must be done in a separate thread.
+* The provided data buffer contents must be **copied** if they need to be used 
after the callback returns as the buffer contents might be re-used by the 
caller code.
+* Any exception thrown during handling of the data will cause the SFTP session 
to terminate.
+
 ### Using `SftpFileSystemProvider` to create an `SftpFileSystem`
 
 The code automatically registers the `SftpFileSystemProvider` as the handler 
for `sftp://` URL(s). Such URLs are
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
index 04c2019..9b0c064 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliLogger.java
@@ -32,7 +32,7 @@ import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.ConfigFileReaderSupport;
 import org.apache.sshd.common.config.LogLevelValue;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NullPrintStream;
+import org.apache.sshd.common.util.io.output.NullPrintStream;
 import org.apache.sshd.common.util.logging.SimplifiedLog;
 import org.apache.sshd.common.util.logging.SimplifiedLoggerSkeleton;
 import org.slf4j.Logger;
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
index c72f741..b089386 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java
@@ -46,7 +46,7 @@ import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.scp.client.ScpClient;
 import org.apache.sshd.scp.client.ScpClient.Option;
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index f81d4b2..feb5d98 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -29,6 +29,7 @@ import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.nio.channels.Channel;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -66,7 +67,10 @@ import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.output.LineLevelAppender;
+import org.apache.sshd.common.util.io.output.LineLevelAppenderStream;
+import org.apache.sshd.common.util.io.output.NullOutputStream;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.config.SshServerConfigFileReader;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -317,6 +321,33 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
         return SftpVersionSelector.resolveVersionSelector(selector);
     }
 
+    private static OutputStream resolveErrorDataHandlerStream(ClientSession 
session, Logger logger) {
+        String errCharset = session.getString("SftpErrorHandlerOutputCharset");
+        if (GenericUtils.safeCompare(errCharset, "NONE", false) == 0) {
+            return new NullOutputStream();
+        }
+
+        LineLevelAppender appender = new LineLevelAppender() {
+            @Override
+            public boolean isWriteEnabled() {
+                return logger.isErrorEnabled();
+            }
+
+            @Override
+            public void writeLineData(CharSequence lineData) throws 
IOException {
+                logger.error("errorChannel: {}", lineData);
+            }
+
+            @Override
+            public void close() throws IOException {
+                // ignored
+            }
+        };
+        return GenericUtils.isBlank(errCharset)
+                ? new LineLevelAppenderStream(StandardCharsets.US_ASCII, 
appender)
+                : new LineLevelAppenderStream(errCharset, appender);
+    }
+
     /* -------------------------------------------------------------------- */
 
     public static void main(String[] args) throws Exception {
@@ -356,7 +387,9 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
                     logger.info("Using version selector={}", versionSelector);
                 }
 
-                try (SftpClient sftpClient = 
clientFactory.createSftpClient(session, versionSelector);
+                try (OutputStream errStream = 
resolveErrorDataHandlerStream(session, logger);
+                     SftpClient sftpClient = clientFactory.createSftpClient(
+                             session, versionSelector, errStream::write);
                      SftpCommandMain sftp = new SftpCommandMain(sftpClient)) {
                     // TODO allow injection of extra CommandExecutor(s) via 
command line and/or service loading
                     sftp.doInteractive(stdin, stdout, stderr);
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
index 4af2024..82f87d8 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -82,7 +82,7 @@ import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
index 20f86fa..aa9e438 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientMain.java
@@ -40,8 +40,8 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.channel.Channel;
 import org.apache.sshd.common.channel.PtyChannelConfiguration;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.slf4j.Logger;
 
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
index 0686938..e3b1205 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
@@ -82,7 +82,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.logging.SimplifiedLog;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.common.util.security.SecurityUtils;
diff --git 
a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java 
b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
index e1cfc31..ec09974 100644
--- a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java
@@ -28,7 +28,7 @@ import org.apache.sshd.cli.CliLogger;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.util.test.BaseTestSupport;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
index ad8a604..88f8d93 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -56,9 +56,9 @@ import 
org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
-import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseReader;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
 
 /**
  * Represents an entry in the client's configuration file as defined by the
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
index 077c00d..03427c6 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java
@@ -39,8 +39,8 @@ import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseReader;
 
 /**
  * Contains a representation of an entry in the <code>known_hosts</code> file
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
index 9f9ebdf..8a5b248 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
@@ -35,8 +35,8 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseReader;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
index 2628136..096fd0c 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
@@ -45,8 +45,8 @@ import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseReader;
 
 /**
  * Represents an entry in the user's {@code authorized_keys} file according to 
the
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
index 0e61acc..c662720 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
@@ -35,7 +35,7 @@ import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 
 /**
  * @param  <PUB> Type of {@link PublicKey}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
index b543ea9..074a522 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHDSSPrivateKeyEntryDecoder.java
@@ -41,7 +41,7 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
index ee33129..3535fe7 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java
@@ -43,7 +43,7 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
index 096a413..1df1d52 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java
@@ -41,7 +41,7 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
index 796bf60..0a7aa05 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java
@@ -39,10 +39,10 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.der.ASN1Object;
 import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
index 08fe839..9ed808d 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -43,10 +43,10 @@ import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.der.ASN1Object;
 import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
index 161a0ac..40f8ed6 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java
@@ -40,10 +40,10 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.der.ASN1Object;
 import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
index dd91e85..f9402c4 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriter.java
@@ -51,7 +51,7 @@ import 
org.apache.sshd.common.config.keys.loader.openssh.kdf.BCrypt;
 import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
 import org.apache.sshd.common.config.keys.writer.KeyPairResourceWriter;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 
 /**
  * A {@link KeyPairResourceWriter} for writing keys in the modern OpenSSH 
format, using the OpenBSD bcrypt KDF for
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
index 8c935d1..5fce736 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
@@ -65,6 +65,41 @@ public final class BufferUtils {
         throw new UnsupportedOperationException("No instance allowed");
     }
 
+    /**
+     * <p>
+     * Finds the index of the given value in the array starting at the given 
index and checking up to specified number
+     * of elements.
+     * </p>
+     *
+     * <p>
+     * This method returns {@code -1}) for a {@code null} input array.
+     * </p>
+     *
+     * <p>
+     * A negative startIndex is treated as zero. A startIndex larger than the 
array length will return {@code -1}.
+     * </p>
+     *
+     * @param  array       the array to search through for the object, may be 
{@code null}
+     * @param  valueToFind the value to find
+     * @param  startIndex  the index to start searching at
+     * @param  len         the number of elements to search from the start 
index
+     * @return             the index of the value within the array, {@code -1} 
if not found or {@code null} array input
+     *                     or non-positive number of elements
+     */
+    public static int indexOf(byte[] array, byte valueToFind, int startIndex, 
int len) {
+        if (array == null) {
+            return -1;
+        }
+
+        for (int i = Math.max(startIndex, 0), l = 0; l < len; i++, l++) {
+            if (valueToFind == array[i]) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
     public static void dumpHex(
             SimplifiedLog logger, Level level, String prefix, PropertyResolver 
resolver, char sep, byte... data) {
         dumpHex(logger, level, prefix, resolver, sep, data, 0, 
NumberUtils.length(data));
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LineDataConsumer.java
similarity index 64%
copy from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
copy to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/LineDataConsumer.java
index 9c8b218..a58e690 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LineDataConsumer.java
@@ -19,28 +19,28 @@
 
 package org.apache.sshd.common.util.io;
 
-import java.io.FilterReader;
 import java.io.IOException;
-import java.io.Reader;
+import java.io.StreamCorruptedException;
+import java.util.Objects;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
-public class NoCloseReader extends FilterReader {
-    public NoCloseReader(Reader in) {
-        super(in);
-    }
+@FunctionalInterface
+public interface LineDataConsumer {
+    /**
+     * Ignores anything provided to it
+     */
+    LineDataConsumer IGNORE = lineData -> {
+        // do nothing
+    };
 
-    @Override
-    public void close() throws IOException {
-        // ignored
-    }
+    /**
+     * Throws {@link StreamCorruptedException} with the invoked line data
+     */
+    LineDataConsumer FAIL = lineData -> {
+        throw new StreamCorruptedException(Objects.toString(lineData));
+    };
 
-    public static Reader resolveReader(Reader r, boolean okToClose) {
-        if ((r == null) || okToClose) {
-            return r;
-        } else {
-            return new NoCloseReader(r);
-        }
-    }
+    void consume(CharSequence lineData) throws IOException;
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/CloseableEmptyInputStream.java
similarity index 98%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/CloseableEmptyInputStream.java
index c3c2209..5eb6aba 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/CloseableEmptyInputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.nio.channels.Channel;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/EmptyInputStream.java
similarity index 97%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/EmptyInputStream.java
index 4282adb..5674326 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/EmptyInputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/InputStreamWithChannel.java
similarity index 96%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/InputStreamWithChannel.java
index d847079..5cd1a5d 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/InputStreamWithChannel.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.InputStream;
 import java.nio.channels.Channel;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/LimitInputStream.java
similarity index 98%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/LimitInputStream.java
index 1d5b33b..3cd9956 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/LimitInputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseInputStream.java
similarity index 96%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseInputStream.java
index b9aedee..0cf9b04 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseInputStream.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseReader.java
similarity index 96%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseReader.java
index 9c8b218..a85e6d4 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NoCloseReader.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.FilterReader;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NullInputStream.java
similarity index 98%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NullInputStream.java
index 1b9b471..fc949ec 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/input/NullInputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.EOFException;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppender.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppender.java
new file mode 100644
index 0000000..df8c7e7
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppender.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.util.io.output;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.function.BooleanSupplier;
+
+import org.apache.sshd.common.util.io.LineDataConsumer;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public interface LineLevelAppender extends LineDataConsumer, Closeable {
+    /**
+     * A typical line length used in many textual standards
+     */
+    int TYPICAL_LINE_LENGTH = 80;
+
+    LineLevelAppender EMPTY = new LineLevelAppender() {
+        @Override
+        public void writeLineData(CharSequence lineData) throws IOException {
+            // ignored
+        }
+
+        @Override
+        public boolean isWriteEnabled() {
+            return false;
+        }
+
+        @Override
+        public void close() throws IOException {
+            // do nothing
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    /**
+     * @return {@code true} if OK to accumulate data in work buffer
+     */
+    boolean isWriteEnabled();
+
+    @Override
+    default void consume(CharSequence lineData) throws IOException {
+        writeLineData(lineData);
+    }
+
+    /**
+     * Called by the implementation once end-of-line is detected.
+     *
+     * @param  lineData    The &quot;pure&quot; line data - excluding any 
CR/LF(s).
+     * @throws IOException If failed to write the data
+     */
+    void writeLineData(CharSequence lineData) throws IOException;
+
+    static LineLevelAppender wrap(Appendable appendable) {
+        return wrap(appendable, () -> true);
+    }
+
+    static LineLevelAppender wrap(Appendable appendable, BooleanSupplier 
writeEnabled) {
+        Objects.requireNonNull(appendable, "No appendable to wrap");
+        return new LineLevelAppender() {
+            /**
+             * indicates whether a line has been written
+             */
+            private boolean writtenFirstLine;
+
+            @Override
+            public void close() throws IOException {
+                if (appendable instanceof Closeable) {
+                    ((Closeable) appendable).close();
+                }
+            }
+
+            @Override
+            public void writeLineData(CharSequence lineData) throws 
IOException {
+                if (writtenFirstLine) {
+                    appendable.append(System.lineSeparator());
+                }
+
+                appendable.append(lineData);
+                writtenFirstLine = true;
+            }
+
+            @Override
+            public boolean isWriteEnabled() {
+                return writeEnabled.getAsBoolean();
+            }
+
+            @Override
+            public String toString() {
+                return appendable.toString();
+            }
+        };
+    }
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppenderStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppenderStream.java
new file mode 100644
index 0000000..0270cce
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineLevelAppenderStream.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.util.io.output;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * <P>
+ * Accumulates all written data into a work buffer and calls the actual 
writing method only when LF detected.
+ * <B>Note:</B> it strips CR if found before the LF
+ * </P>
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class LineLevelAppenderStream extends LineOutputStream {
+    protected final CharsetDecoder csDecoder;
+    protected final LineLevelAppender appenderInstance;
+    protected char[] lineBuf;
+
+    public LineLevelAppenderStream(LineLevelAppender appender) {
+        this(Charset.defaultCharset(), appender);
+    }
+
+    public LineLevelAppenderStream(String charset, LineLevelAppender appender) 
{
+        this(Charset.forName(ValidateUtils.checkNotNullAndNotEmpty(charset, 
"No charset name")), appender);
+    }
+
+    public LineLevelAppenderStream(Charset charset, LineLevelAppender 
appender) {
+        this(Objects.requireNonNull(charset, "No charset").newDecoder(), 
appender);
+    }
+
+    public LineLevelAppenderStream(CharsetDecoder decoder, LineLevelAppender 
appender) {
+        csDecoder = Objects.requireNonNull(decoder, "No decoder");
+        appenderInstance = Objects.requireNonNull(appender, "No appender");
+    }
+
+    public final LineLevelAppender getLineLevelAppender() {
+        return appenderInstance;
+    }
+
+    @Override
+    protected void handleLine(byte[] b, int off, int len) throws IOException {
+        LineLevelAppender appender = getLineLevelAppender();
+        if (len <= 0) {
+            appender.writeLineData("");
+            return;
+        }
+
+        ByteBuffer bb = (b[off + len - 1] == '\r') ? ByteBuffer.wrap(b, off, 
len - 1) : ByteBuffer.wrap(b, off, len);
+        char[] lineChars = ensureCharDataCapacity(len);
+        CharBuffer cc = CharBuffer.wrap(lineChars);
+
+        csDecoder.reset();
+        CoderResult res = csDecoder.decode(bb, cc, true);
+        if (res.isError() || res.isMalformed() || res.isOverflow() || 
res.isUnmappable()) {
+            throw new StreamCorruptedException("Failed to decode line bytes: " 
+ res);
+        }
+
+        cc.flip();
+        appender.writeLineData(cc);
+    }
+
+    protected char[] ensureCharDataCapacity(int numBytes) {
+        float grwFactor = csDecoder.maxCharsPerByte(); // worst case
+        int reqChars = (grwFactor > 0.0f) ? (int) (numBytes * grwFactor) : 
numBytes;
+        if ((lineBuf == null) || (lineBuf.length < reqChars)) {
+            reqChars = Math.max(reqChars, 
LineLevelAppender.TYPICAL_LINE_LENGTH);
+            lineBuf = new char[reqChars + Byte.SIZE /* a little extra to avoid 
numerous growths */];
+        }
+
+        return lineBuf;
+    }
+}
diff --git 
a/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineOutputStream.java
similarity index 98%
rename from 
sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineOutputStream.java
index b5085b1..b093d1e 100644
--- 
a/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LineOutputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.contrib.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LoggingFilterOutputStream.java
similarity index 98%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LoggingFilterOutputStream.java
index b446de2..4511c88 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/LoggingFilterOutputStream.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.FilterOutputStream;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseOutputStream.java
similarity index 96%
copy from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
copy to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseOutputStream.java
index 4ba16d3..ae57f3c 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseOutputStream.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.FilterOutputStream;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseWriter.java
similarity index 96%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseWriter.java
index 0f92697..a2378cf 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NoCloseWriter.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.FilterWriter;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullOutputStream.java
similarity index 97%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullOutputStream.java
index 8a0435c..64ff73e 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullOutputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.EOFException;
 import java.io.IOException;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullPrintStream.java
similarity index 98%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullPrintStream.java
index a21c5d2..7466a1f 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullPrintStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/NullPrintStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.PrintStream;
 import java.util.Locale;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/OutputStreamWithChannel.java
similarity index 95%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/OutputStreamWithChannel.java
index 6f81872..23deb7c 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/OutputStreamWithChannel.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.OutputStream;
 import java.nio.channels.Channel;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/SecureByteArrayOutputStream.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/SecureByteArrayOutputStream.java
similarity index 97%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/SecureByteArrayOutputStream.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/io/output/SecureByteArrayOutputStream.java
index 3cd073f..a6193e9 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/SecureByteArrayOutputStream.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/io/output/SecureByteArrayOutputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.ByteArrayOutputStream;
 import java.util.Arrays;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
index 054111e..83f1775 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java
@@ -43,10 +43,10 @@ import 
org.apache.sshd.common.config.keys.FilePasswordProvider;
 import 
org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.der.ASN1Object;
 import org.apache.sshd.common.util.io.der.ASN1Type;
 import org.apache.sshd.common.util.io.der.DERParser;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
index b4cd0f6..478f97d 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -43,7 +43,7 @@ import 
org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
index 83f345c..f042f8d 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
@@ -45,7 +45,7 @@ import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
-import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
+import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
 import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/EmptyInputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/EmptyInputStreamTest.java
similarity index 99%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/EmptyInputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/input/EmptyInputStreamTest.java
index 113758d..2958f0e 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/EmptyInputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/EmptyInputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/LimitInputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/LimitInputStreamTest.java
similarity index 99%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/LimitInputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/input/LimitInputStreamTest.java
index e009527..4bd71db 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/LimitInputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/LimitInputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseInputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseInputStreamTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseInputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseInputStreamTest.java
index 33ab102..e22a2ba 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseInputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseInputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseReaderTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseReaderTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseReaderTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseReaderTest.java
index 14a72fc..471a012 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseReaderTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NoCloseReaderTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NullInputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NullInputStreamTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NullInputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NullInputStreamTest.java
index 31982da..0e8ee80 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NullInputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/input/NullInputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.input;
 
 import java.io.EOFException;
 import java.io.IOException;
diff --git 
a/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/LineOutputStreamTest.java
similarity index 98%
rename from 
sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/output/LineOutputStreamTest.java
index 7e58da1..54b2259 100644
--- 
a/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/LineOutputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.contrib.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseOutputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseOutputStreamTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseOutputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseOutputStreamTest.java
index f01f132..60db28b 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseOutputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseOutputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseWriterTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseWriterTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseWriterTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseWriterTest.java
index 090ac15..41cb5df 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NoCloseWriterTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NoCloseWriterTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NullOutputStreamTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NullOutputStreamTest.java
similarity index 98%
rename from 
sshd-common/src/test/java/org/apache/sshd/common/util/io/NullOutputStreamTest.java
rename to 
sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NullOutputStreamTest.java
index 0fe845f..0a8872e 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/util/io/NullOutputStreamTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/util/io/output/NullOutputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.common.util.io.output;
 
 import java.io.EOFException;
 import java.io.IOException;
diff --git 
a/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
 
b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
index 735c507..fa35b84 100644
--- 
a/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
+++ 
b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
@@ -52,7 +52,7 @@ import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.contrib.common.io.EndlessWriteFuture;
 import org.apache.sshd.contrib.common.io.ImmediateWriteFuture;
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index ff4c2f6..4700198 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -56,8 +56,8 @@ import org.apache.sshd.common.forward.PortForwardingManager;
 import org.apache.sshd.common.future.KeyExchangeFuture;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
-import org.apache.sshd.common.util.io.NullOutputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.output.NullOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 
 /**
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java 
b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index 963a14b..84b8b72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -60,7 +60,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.closeable.IoBaseCloseable;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.LoggingFilterOutputStream;
+import org.apache.sshd.common.util.io.output.LoggingFilterOutputStream;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ServerFactoryManager;
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/channel/PipeDataReceiver.java 
b/sshd-core/src/main/java/org/apache/sshd/server/channel/PipeDataReceiver.java
index cc7118d..d7f167f 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/channel/PipeDataReceiver.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/channel/PipeDataReceiver.java
@@ -26,7 +26,7 @@ import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.channel.ChannelPipedInputStream;
 import org.apache.sshd.common.channel.ChannelPipedOutputStream;
 import org.apache.sshd.common.channel.Window;
-import org.apache.sshd.common.util.io.LoggingFilterOutputStream;
+import org.apache.sshd.common.util.io.output.LoggingFilterOutputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
diff --git a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java 
b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
index a536252..874c5b1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
@@ -52,7 +52,7 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ProxyUtils;
-import org.apache.sshd.common.util.io.NullOutputStream;
+import org.apache.sshd.common.util.io.output.NullOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.Environment;
diff --git a/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java 
b/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
index bb43718..a7e2a70 100644
--- a/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/WindowAdjustTest.java
@@ -41,7 +41,7 @@ import org.apache.sshd.common.io.WritePendingException;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.Environment;
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java 
b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index cf2e0cd..b1ae9b1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -90,7 +90,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.core.CoreModuleProperties;
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
index bd8a715..b6df091 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
@@ -37,8 +37,8 @@ import java.util.Objects;
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.input.NoCloseReader;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator;
 import org.apache.sshd.util.test.BaseTestSupport;
diff --git 
a/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
 
b/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
index fc2a036..3d87921 100644
--- 
a/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
+++ 
b/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
@@ -35,7 +35,7 @@ import org.apache.sshd.client.channel.ChannelExec;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.LimitInputStream;
+import org.apache.sshd.common.util.io.input.LimitInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.scp.ScpModuleProperties;
 import org.apache.sshd.scp.client.ScpClient.Option;
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java 
b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
index d560f6f..d713b43 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
@@ -44,7 +44,7 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.io.LimitInputStream;
+import org.apache.sshd.common.util.io.input.LimitInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.scp.ScpModuleProperties;
 import org.apache.sshd.scp.common.ScpTransferEventListener.FileOperation;
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClientFactory.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClientFactory.java
index 84514d2..986085b 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClientFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClientFactory.java
@@ -65,7 +65,34 @@ public interface SftpClientFactory {
      * @return             The created {@link SftpClient} instance
      * @throws IOException If failed to create the client
      */
-    SftpClient createSftpClient(ClientSession session, SftpVersionSelector 
selector) throws IOException;
+    default SftpClient createSftpClient(ClientSession session, 
SftpVersionSelector selector) throws IOException {
+        return createSftpClient(session, selector, SftpErrorDataHandler.EMPTY);
+    }
+
+    /**
+     * Create an SFTP client from this session.
+     *
+     * @param  session          The {@link ClientSession} to be used for 
creating the SFTP client
+     * @param  errorDataHandler The {@link SftpErrorDataHandler} to handle 
incoming data through the error stream - if
+     *                          {@code null} the data is silently ignored
+     * @return                  The created {@link SftpClient}
+     * @throws IOException      if failed to create the client
+     */
+    default SftpClient createSftpClient(ClientSession session, 
SftpErrorDataHandler errorDataHandler) throws IOException {
+        return createSftpClient(session, SftpVersionSelector.CURRENT, 
errorDataHandler);
+    }
+
+    /**
+     * @param  session          The {@link ClientSession} to which the SFTP 
client should be attached
+     * @param  selector         The {@link SftpVersionSelector} to use in 
order to negotiate the SFTP version
+     * @param  errorDataHandler The {@link SftpErrorDataHandler} to handle 
incoming data through the error stream - if
+     *                          {@code null} the data is silently ignored
+     * @return                  The created {@link SftpClient} instance
+     * @throws IOException      If failed to create the client
+     */
+    SftpClient createSftpClient(
+            ClientSession session, SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler)
+            throws IOException;
 
     default SftpFileSystem createSftpFileSystem(ClientSession session) throws 
IOException {
         return createSftpFileSystem(session, SftpVersionSelector.CURRENT);
@@ -90,16 +117,25 @@ public interface SftpClientFactory {
         return createSftpFileSystem(session, SftpVersionSelector.CURRENT, 
readBufferSize, writeBufferSize);
     }
 
+    default SftpFileSystem createSftpFileSystem(
+            ClientSession session, SftpVersionSelector selector, int 
readBufferSize, int writeBufferSize)
+            throws IOException {
+        return createSftpFileSystem(session, selector, 
SftpErrorDataHandler.EMPTY, readBufferSize, writeBufferSize);
+    }
+
     /**
-     * @param  session         The {@link ClientSession} to which the SFTP 
client backing the file system should be
-     *                         attached
-     * @param  selector        The {@link SftpVersionSelector} to use in order 
to negotiate the SFTP version
-     * @param  readBufferSize  Default I/O read buffer size
-     * @param  writeBufferSize Default I/O write buffer size
-     * @return                 The created {@link SftpFileSystem} instance
-     * @throws IOException     If failed to create the instance
+     * @param  session          The {@link ClientSession} to which the SFTP 
client backing the file system should be
+     *                          attached
+     * @param  selector         The {@link SftpVersionSelector} to use in 
order to negotiate the SFTP version
+     * @param  errorDataHandler The {@link SftpErrorDataHandler} to handle 
incoming data through the error stream - if
+     *                          {@code null} the data is silently ignored
+     * @param  readBufferSize   Default I/O read buffer size
+     * @param  writeBufferSize  Default I/O write buffer size
+     * @return                  The created {@link SftpFileSystem} instance
+     * @throws IOException      If failed to create the instance
      */
     SftpFileSystem createSftpFileSystem(
-            ClientSession session, SftpVersionSelector selector, int 
readBufferSize, int writeBufferSize)
+            ClientSession session, SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler,
+            int readBufferSize, int writeBufferSize)
             throws IOException;
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
 b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpErrorDataHandler.java
similarity index 60%
rename from 
sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
rename to 
sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpErrorDataHandler.java
index 4ba16d3..76edf2a 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpErrorDataHandler.java
@@ -16,32 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.io;
 
-import java.io.FilterOutputStream;
+package org.apache.sshd.sftp.client;
+
 import java.io.IOException;
-import java.io.OutputStream;
 
 /**
- * TODO Add javadoc
+ * Callback for any error stream data sent by the server
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
-public class NoCloseOutputStream extends FilterOutputStream {
-    public NoCloseOutputStream(OutputStream out) {
-        super(out);
-    }
-
-    @Override
-    public void close() throws IOException {
-        // ignored
-    }
+@FunctionalInterface
+public interface SftpErrorDataHandler {
+    SftpErrorDataHandler EMPTY = (buf, start, len) -> {
+        /* ignored */ };
 
-    public static OutputStream resolveOutputStream(OutputStream output, 
boolean okToClose) {
-        if ((output == null) || okToClose) {
-            return output;
-        } else {
-            return new NoCloseOutputStream(output);
-        }
-    }
+    /**
+     * Receive binary data from server error stream
+     *
+     * @param  buf         The buffer of the incoming data
+     * @param  start       Offset in buffer to read the data
+     * @param  len         Available data in buffer
+     * @throws IOException If failed to receive incoming data
+     */
+    void errorData(byte[] buf, int start, int len) throws IOException;
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystem.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystem.java
index f962796..c29c333 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystem.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystem.java
@@ -52,6 +52,7 @@ import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.RawSftpClient;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClientFactory;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.client.impl.AbstractSftpClient;
 import org.apache.sshd.sftp.common.SftpConstants;
@@ -67,6 +68,7 @@ public class SftpFileSystem
     private final ClientSession clientSession;
     private final SftpClientFactory factory;
     private final SftpVersionSelector selector;
+    private final SftpErrorDataHandler errorDataHandler;
     private final Queue<SftpClient> pool;
     private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
     private final int version;
@@ -77,12 +79,14 @@ public class SftpFileSystem
     private final List<FileStore> stores;
 
     public SftpFileSystem(SftpFileSystemProvider provider, String id, 
ClientSession session,
-                          SftpClientFactory factory, SftpVersionSelector 
selector) throws IOException {
+                          SftpClientFactory factory, SftpVersionSelector 
selector, SftpErrorDataHandler errorDataHandler)
+                                                                               
                                           throws IOException {
         super(provider);
         this.id = id;
         this.clientSession = Objects.requireNonNull(session, "No client 
session");
         this.factory = factory != null ? factory : 
SftpClientFactory.instance();
         this.selector = selector;
+        this.errorDataHandler = errorDataHandler;
         this.stores = Collections.unmodifiableList(Collections.<FileStore> 
singletonList(new SftpFileStore(id, this)));
         this.pool = new 
LinkedBlockingQueue<>(SftpModuleProperties.POOL_SIZE.getRequired(session));
         try (SftpClient client = getClient()) {
@@ -104,6 +108,10 @@ public class SftpFileSystem
         return selector;
     }
 
+    public SftpErrorDataHandler getSftpErrorDataHandler() {
+        return errorDataHandler;
+    }
+
     public final String getId() {
         return id;
     }
@@ -171,10 +179,13 @@ public class SftpFileSystem
                 SftpClient client = pool.poll();
                 if (client == null) {
                     ClientSession session = getClientSession();
-                    client = factory.createSftpClient(session, 
getSftpVersionSelector());
+                    client = factory.createSftpClient(
+                            session, getSftpVersionSelector(), 
getSftpErrorDataHandler());
                 }
                 if (!client.isClosing()) {
-                    wrapper = new Wrapper(client, getReadBufferSize(), 
getWriteBufferSize());
+                    wrapper = new Wrapper(
+                            client,
+                            getSftpErrorDataHandler(), getReadBufferSize(), 
getWriteBufferSize());
                 }
             }
             wrappers.set(wrapper);
@@ -231,7 +242,9 @@ public class SftpFileSystem
         private final int readSize;
         private final int writeSize;
 
-        private Wrapper(SftpClient delegate, int readSize, int writeSize) {
+        private Wrapper(SftpClient delegate, SftpErrorDataHandler 
errorHandler, int readSize, int writeSize) {
+            super(errorHandler);
+
             this.delegate = delegate;
             this.readSize = readSize;
             this.writeSize = writeSize;
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemClientSessionInitializer.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemClientSessionInitializer.java
index fa27dd4..890864e 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemClientSessionInitializer.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemClientSessionInitializer.java
@@ -26,6 +26,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionCreator;
 import org.apache.sshd.common.auth.PasswordHolder;
 import org.apache.sshd.common.auth.UsernameHolder;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 
 /**
@@ -86,17 +87,20 @@ public interface SftpFileSystemClientSessionInitializer {
      * Invoked by the {@link 
SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method in order to 
create the
      * {@link SftpFileSystem} once session has been authenticated.
      *
-     * @param  provider    The {@link SftpFileSystemProvider} instance 
requesting the session
-     * @param  context     The initialization {@link 
SftpFileSystemInitializationContext}
-     * @param  session     The authenticated {@link ClientSession}
-     * @param  selector    The <U>resolved</U> {@link SftpVersionSelector} to 
use
-     * @return             The created {@link SftpFileSystem}
-     * @throws IOException If failed to create the file-system
+     * @param  provider         The {@link SftpFileSystemProvider} instance 
requesting the session
+     * @param  context          The initialization {@link 
SftpFileSystemInitializationContext}
+     * @param  session          The authenticated {@link ClientSession}
+     * @param  selector         The <U>resolved</U> {@link 
SftpVersionSelector} to use
+     * @param  errorDataHandler The {@link SftpErrorDataHandler} to handle 
incoming data through the error stream - if
+     *                          {@code null} the data is silently ignored
+     * @return                  The created {@link SftpFileSystem}
+     * @throws IOException      If failed to create the file-system
      */
     default SftpFileSystem createSftpFileSystem(
             SftpFileSystemProvider provider, 
SftpFileSystemInitializationContext context, ClientSession session,
-            SftpVersionSelector selector)
+            SftpVersionSelector selector, SftpErrorDataHandler 
errorDataHandler)
             throws IOException {
-        return new SftpFileSystem(provider, context.getId(), session, 
provider.getSftpClientFactory(), selector);
+        return new SftpFileSystem(
+                provider, context.getId(), session, 
provider.getSftpClientFactory(), selector, errorDataHandler);
     }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
index c5e0f0d..e671298 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
@@ -91,6 +91,7 @@ import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClient.Attributes;
 import org.apache.sshd.sftp.client.SftpClient.OpenMode;
 import org.apache.sshd.sftp.client.SftpClientFactory;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.client.extensions.CopyFileExtension;
 import org.apache.sshd.sftp.client.impl.SftpRemotePathChannel;
@@ -132,6 +133,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
     private final SshClient clientInstance;
     private final SftpClientFactory factory;
     private final SftpVersionSelector versionSelector;
+    private final SftpErrorDataHandler errorDataHandler;
     private final NavigableMap<String, SftpFileSystem> fileSystems = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
     private SftpFileSystemClientSessionInitializer fsSessionInitializer = 
SftpFileSystemClientSessionInitializer.DEFAULT;
 
@@ -143,23 +145,38 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         this(null, selector);
     }
 
-    /**
-     * @param client The {@link SshClient} to use - if {@code null} then a 
default one will be setup and started.
-     *               Otherwise, it is assumed that the client has already been 
started
-     * @see          SshClient#setUpDefaultClient()
-     */
     public SftpFileSystemProvider(SshClient client) {
         this(client, SftpVersionSelector.CURRENT);
     }
 
     public SftpFileSystemProvider(SshClient client, SftpVersionSelector 
selector) {
-        this(client, null, selector);
+        this(client, selector, SftpErrorDataHandler.EMPTY);
+    }
+
+    public SftpFileSystemProvider(SshClient client, SftpVersionSelector 
selector, SftpErrorDataHandler errorDataHandler) {
+        this(client, null, selector, errorDataHandler);
+
     }
 
     public SftpFileSystemProvider(SshClient client, SftpClientFactory factory, 
SftpVersionSelector selector) {
+        this(client, factory, selector, SftpErrorDataHandler.EMPTY);
+    }
+
+    /**
+     * @param client           The {@link SshClient} to use - if {@code null} 
then a default one will be setup and
+     *                         started. Otherwise, it is assumed that the 
client has already been started
+     * @param factory          The {@link SftpClientFactory} to use to 
generate SFTP client instances
+     * @param selector         The {@link SftpVersionSelector} to use in order 
to negotiate the SFTP version
+     * @param errorDataHandler The {@link SftpErrorDataHandler} to handle 
incoming data through the error stream - if
+     *                         {@code null} the data is silently ignored
+     * @see                    SshClient#setUpDefaultClient()
+     */
+    public SftpFileSystemProvider(SshClient client, SftpClientFactory factory,
+                                  SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler) {
         this.log = LoggerFactory.getLogger(getClass());
         this.factory = factory;
         this.versionSelector = selector;
+        this.errorDataHandler = errorDataHandler;
         if (client == null) {
             // TODO: make this configurable using system properties
             client = SshClient.setUpDefaultClient();
@@ -177,6 +194,10 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         return versionSelector;
     }
 
+    public SftpErrorDataHandler getSftpErrorDataHandler() {
+        return errorDataHandler;
+    }
+
     public final SshClient getClientInstance() {
         return clientInstance;
     }
@@ -218,6 +239,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         
context.setMaxAuthTime(SftpModuleProperties.AUTH_TIME.getRequired(resolver));
 
         SftpVersionSelector selector = resolveSftpVersionSelector(uri, 
getSftpVersionSelector(), resolver);
+        SftpErrorDataHandler errorHandler = resolveSftpErrorDataHandler(uri, 
getSftpErrorDataHandler(), resolver);
         Charset decodingCharset = 
SftpModuleProperties.NAME_DECODER_CHARSET.getRequired(resolver);
 
         SftpFileSystemClientSessionInitializer initializer = 
getSftpFileSystemClientSessionInitializer();
@@ -250,7 +272,8 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
 
                 initializer.authenticateClientSession(this, context, session);
 
-                fileSystem = initializer.createSftpFileSystem(this, context, 
session, selector);
+                fileSystem = initializer.createSftpFileSystem(
+                        this, context, session, selector, errorHandler);
                 fileSystems.put(id, fileSystem);
             } catch (Exception e) {
                 if (session != null) {
@@ -311,6 +334,11 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
         }
     }
 
+    protected SftpErrorDataHandler resolveSftpErrorDataHandler(
+            URI uri, SftpErrorDataHandler errorHandler, PropertyResolver 
resolver) {
+        return errorHandler;
+    }
+
     // NOTE: URI parameters override environment ones
     public static Map<String, Object> resolveFileSystemParameters(Map<String, 
?> env, Map<String, Object> uriParams) {
         if (MapEntryUtils.isEmpty(env)) {
@@ -395,7 +423,7 @@ public class SftpFileSystemProvider extends 
FileSystemProvider {
             if (fileSystems.containsKey(id)) {
                 throw new FileSystemAlreadyExistsException(id);
             }
-            fileSystem = new SftpFileSystem(this, id, session, factory, 
getSftpVersionSelector());
+            fileSystem = new SftpFileSystem(this, id, session, factory, 
getSftpVersionSelector(), getSftpErrorDataHandler());
             fileSystems.put(id, fileSystem);
         }
 
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
index 0d29b51..7a3ce00 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
@@ -45,6 +45,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.FullAccessSftpClient;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.extensions.BuiltinSftpClientExtensions;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtensionFactory;
@@ -56,13 +57,18 @@ import org.apache.sshd.sftp.common.extensions.ParserUtils;
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
-public abstract class AbstractSftpClient extends AbstractSubsystemClient 
implements FullAccessSftpClient {
+public abstract class AbstractSftpClient
+        extends AbstractSubsystemClient
+        implements FullAccessSftpClient, SftpErrorDataHandler {
     public static final int INIT_COMMAND_SIZE = Byte.BYTES /* command */ + 
Integer.BYTES /* version */;
 
+    protected final SftpErrorDataHandler errorDataHandler;
+
     private final Attributes fileOpenAttributes = new Attributes();
     private final AtomicReference<Map<String, Object>> parsedExtensionsHolder 
= new AtomicReference<>(null);
 
-    protected AbstractSftpClient() {
+    protected AbstractSftpClient(SftpErrorDataHandler delegateHandler) {
+        errorDataHandler = (delegateHandler == null) ? 
SftpErrorDataHandler.EMPTY : delegateHandler;
         fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR);
     }
 
@@ -846,6 +852,17 @@ public abstract class AbstractSftpClient extends 
AbstractSubsystemClient impleme
     }
 
     @Override
+    public void errorData(byte[] buf, int start, int len) throws IOException {
+        /*
+         * The protocol does not specify how to handle such data but we are 
lenient and ignore it - similar to
+         * /dev/null
+         */
+        if (errorDataHandler != null) {
+            errorDataHandler.errorData(buf, start, len);
+        }
+    }
+
+    @Override
     public void write(Handle handle, long fileOffset, byte[] src, int 
srcOffset, int len) throws IOException {
         // do some bounds checking first
         if ((fileOffset < 0L) || (srcOffset < 0) || (len < 0)) {
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
index ae3f1c7..21f21ac 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
@@ -56,9 +56,9 @@ import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.NullOutputStream;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.sftp.SftpModuleProperties;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.common.extensions.ParserUtils;
@@ -84,9 +84,16 @@ public class DefaultSftpClient extends AbstractSftpClient {
      * @param  clientSession          The {@link ClientSession}
      * @param  initialVersionSelector The initial {@link SftpVersionSelector} 
- if {@code null} then version 6 is
      *                                assumed.
+     * @param  errorDataHandler       The {@link SftpErrorDataHandler} to 
handle incoming data through the error stream
+     *                                - if {@code null} the data is silently 
ignored
      * @throws IOException            If failed to initialize
      */
-    public DefaultSftpClient(ClientSession clientSession, SftpVersionSelector 
initialVersionSelector) throws IOException {
+    public DefaultSftpClient(
+                             ClientSession clientSession, SftpVersionSelector 
initialVersionSelector,
+                             SftpErrorDataHandler errorDataHandler)
+                                                                    throws 
IOException {
+        super(errorDataHandler);
+
         this.nameDecodingCharset = 
SftpModuleProperties.NAME_DECODING_CHARSET.getRequired(clientSession);
         this.clientSession = Objects.requireNonNull(clientSession, "No client 
session");
         this.channel = createSftpChannelSubsystem(clientSession);
@@ -161,11 +168,11 @@ public class DefaultSftpClient extends AbstractSftpClient 
{
     }
 
     /**
-     * Receive binary data
+     * Receive binary data from server main stream
      *
-     * @param  buf         The buffer for the incoming data
-     * @param  start       Offset in buffer to place the data
-     * @param  len         Available space in buffer for the data
+     * @param  buf         The buffer containing the incoming data
+     * @param  start       Offset in buffer to read the data
+     * @param  len         Available data in buffer
      * @return             Actual size of received data
      * @throws IOException If failed to receive incoming data
      */
@@ -288,7 +295,8 @@ public class DefaultSftpClient extends AbstractSftpClient {
             buf.putBuffer(buffer);
         }
 
-        IoOutputStream asyncIn = channel.getAsyncIn();
+        ClientChannel clientChannel = getClientChannel();
+        IoOutputStream asyncIn = clientChannel.getAsyncIn();
         IoWriteFuture writeFuture = asyncIn.writeBuffer(buf);
         writeFuture.verify();
         return id;
@@ -364,8 +372,8 @@ public class DefaultSftpClient extends AbstractSftpClient {
         buf.putInt(initialVersion);
 
         boolean traceEnabled = log.isTraceEnabled();
-        IoOutputStream asyncIn = channel.getAsyncIn();
         ClientChannel clientChannel = getClientChannel();
+        IoOutputStream asyncIn = clientChannel.getAsyncIn();
         if (traceEnabled) {
             log.trace("init({}) send SSH_FXP_INIT - initial version={}", 
clientChannel, initialVersion);
         }
@@ -592,11 +600,22 @@ public class DefaultSftpClient extends AbstractSftpClient 
{
         }
 
         protected OutputStream createErrOutputStream(Session session) {
-            /*
-             * The protocol does not specify how to handle such data but we 
are lenient and ignore it - similar to
-             * /dev/null
-             */
-            return new NullOutputStream();
+            return new OutputStream() {
+                private final byte[] singleByte = new byte[1];
+
+                @Override
+                public void write(int b) throws IOException {
+                    synchronized (singleByte) {
+                        singleByte[0] = (byte) b;
+                        write(singleByte);
+                    }
+                }
+
+                @Override
+                public void write(byte[] b, int off, int len) throws 
IOException {
+                    errorData(b, off, len);
+                }
+            };
         }
     }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClientFactory.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClientFactory.java
index d62f28b..11ed039 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClientFactory.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClientFactory.java
@@ -26,6 +26,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClientFactory;
+import org.apache.sshd.sftp.client.SftpErrorDataHandler;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.client.fs.SftpFileSystem;
 import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
@@ -43,8 +44,10 @@ public class DefaultSftpClientFactory extends 
AbstractLoggingBean implements Sft
     }
 
     @Override
-    public SftpClient createSftpClient(ClientSession session, 
SftpVersionSelector selector) throws IOException {
-        DefaultSftpClient client = createDefaultSftpClient(session, selector);
+    public SftpClient createSftpClient(
+            ClientSession session, SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler)
+            throws IOException {
+        DefaultSftpClient client = createDefaultSftpClient(session, selector, 
errorDataHandler);
         try {
             client.negotiateVersion(selector);
         } catch (IOException | RuntimeException | Error e) {
@@ -57,17 +60,19 @@ public class DefaultSftpClientFactory extends 
AbstractLoggingBean implements Sft
         return client;
     }
 
-    protected DefaultSftpClient createDefaultSftpClient(ClientSession session, 
SftpVersionSelector selector)
+    protected DefaultSftpClient createDefaultSftpClient(
+            ClientSession session, SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler)
             throws IOException {
-        return new DefaultSftpClient(session, selector);
+        return new DefaultSftpClient(session, selector, errorDataHandler);
     }
 
     @Override
     public SftpFileSystem createSftpFileSystem(
-            ClientSession session, SftpVersionSelector selector, int 
readBufferSize, int writeBufferSize)
+            ClientSession session, SftpVersionSelector selector, 
SftpErrorDataHandler errorDataHandler,
+            int readBufferSize, int writeBufferSize)
             throws IOException {
         ClientFactoryManager manager = session.getFactoryManager();
-        SftpFileSystemProvider provider = new 
SftpFileSystemProvider((SshClient) manager, selector);
+        SftpFileSystemProvider provider = new 
SftpFileSystemProvider((SshClient) manager, selector, errorDataHandler);
         SftpFileSystem fs = provider.newFileSystem(session);
         if (readBufferSize > 0) {
             fs.setReadBufferSize(readBufferSize);
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
index 71d8251..3c822e8 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
@@ -35,7 +35,7 @@ import org.apache.sshd.common.channel.Window;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.input.InputStreamWithChannel;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClient.Attributes;
 import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpOutputStreamAsync.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpOutputStreamAsync.java
index 627d3f4..db6147e 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpOutputStreamAsync.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpOutputStreamAsync.java
@@ -28,7 +28,7 @@ import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.OutputStreamWithChannel;
+import org.apache.sshd.common.util.io.output.OutputStreamWithChannel;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
 import org.apache.sshd.sftp.client.SftpClient.OpenMode;
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpInputStreamWithChannel.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpInputStreamWithChannel.java
index e0ff3ab..27aa811 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpInputStreamWithChannel.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpInputStreamWithChannel.java
@@ -22,7 +22,7 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Objects;
 
-import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.input.InputStreamWithChannel;
 import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
 import org.apache.sshd.sftp.client.SftpClient.OpenMode;
 
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpOutputStreamWithChannel.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpOutputStreamWithChannel.java
index ee4b40a..e58d25b 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpOutputStreamWithChannel.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpOutputStreamWithChannel.java
@@ -22,7 +22,7 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Objects;
 
-import org.apache.sshd.common.util.io.OutputStreamWithChannel;
+import org.apache.sshd.common.util.io.output.OutputStreamWithChannel;
 import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
 import org.apache.sshd.sftp.client.SftpClient.OpenMode;
 

Reply via email to