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 925af4b  [SSHD-1233] Added support for 'lim...@openssh.com' SFTP 
extension
925af4b is described below

commit 925af4b3f6b0496499c2ca6b9302fe21987db227
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Thu Dec 23 12:56:53 2021 +0200

    [SSHD-1233] Added support for 'lim...@openssh.com' SFTP extension
---
 CHANGES.md                                         |  16 +++
 docs/sftp.md                                       |   4 +-
 .../apache/sshd/cli/client/SftpCommandMain.java    |  64 +++++++--
 .../apache/sshd/util/test/JUnitTestSupport.java    |  17 +++
 .../common/session/helpers/AbstractSession.java    |   2 +-
 .../org/apache/sshd/sftp/SftpModuleProperties.java |  18 ++-
 .../extensions/BuiltinSftpClientExtensions.java    |  10 ++
 .../extensions/openssh/OpenSSHFsyncExtension.java  |   2 +-
 ...cExtension.java => OpenSSHLimitsExtension.java} |   9 +-
 .../openssh/OpenSSHLimitsExtensionInfo.java        | 150 +++++++++++++++++++++
 .../openssh/OpenSSHStatExtensionInfo.java          |  18 +--
 .../helpers/OpenSSHLimitsExtensionImpl.java        |  52 +++++++
 .../org/apache/sshd/sftp/client/fs/SftpPath.java   |   2 +-
 .../apache/sshd/sftp/client/impl/SftpPathImpl.java |   4 +-
 .../sshd/sftp/common/extensions/ParserUtils.java   |   4 +-
 .../openssh/FstatVfsExtensionParser.java           |   1 +
 .../extensions/openssh/FsyncExtensionParser.java   |   2 +-
 .../openssh/HardLinkExtensionParser.java           |   2 +-
 .../openssh/LSetStatExtensionParser.java           |   2 +-
 ...nsionParser.java => LimitsExtensionParser.java} |  10 +-
 .../openssh/PosixRenameExtensionParser.java        |   2 +-
 .../extensions/openssh/StatVfsExtensionParser.java |   2 +-
 .../server/AbstractSftpEventListenerAdapter.java   |  44 +++++-
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  91 ++++++++-----
 .../org/apache/sshd/sftp/server/SftpSubsystem.java |   9 +-
 .../java/org/apache/sshd/sftp/client/SftpTest.java |   4 +-
 .../sftp/client/extensions/SftpExtensionsTest.java |   2 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |  33 ++---
 28 files changed, 471 insertions(+), 105 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 691aed2..c884827 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -20,9 +20,25 @@
 
 ## Potential compatibility issues
 
+* A **new** SFTP configuration property has been introduced that limits the 
maximum amount of data that can be sent in a single *SSH_FXP_WRITE* packet - 
default=256KB
+
+```java
+    /**
+     * Force the use of a max. packet length for {@link 
AbstractSftpSubsystemHelper#doWrite(Buffer, int)} protection
+     * against malicious packets
+     */
+    public static final Property<Integer> MAX_WRITE_DATA_PACKET_LENGTH
+            = Property.integer("sftp-max-writedata-packet-length", 256 * 1024);
+```
+
+This might cause SFTP write failures for clients that might have sent larger 
buffers and they have been accepted so far. If this happens, simply increase
+this value (though the choice of 256KB should be compatible with the vast 
majority of clients).
+
 ## Minor code helpers
 
 ## Behavioral changes and enhancements
 
 * [SSHD-1231](https://issues.apache.org/jira/browse/SSHD-1231) Public key 
authentication: wrong signature algorithm used (ed25519 key with ssh-rsa 
signature)
+* [SSHD-1233](https://issues.apache.org/jira/browse/SSHD-1233) Added support 
for "lim...@openssh.com" SFTP extension
+
 
diff --git a/docs/sftp.md b/docs/sftp.md
index e76ddf3..224f7bf 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -629,6 +629,7 @@ Furthermore several [OpenSSH SFTP 
extensions](https://github.com/openssh/openssh
 * `posix-ren...@openssh.com` - only client side
 * `stat...@openssh.com`
 * `lsets...@openssh.com`
+* `lim...@openssh.com`
 
 On the server side, the reported standard extensions are configured via the 
`SftpModuleProperties.CLIENT_EXTENSIONS` configuration
 key, and the _OpenSSH_ ones via the `SftpModuleProperties.OPENSSH_EXTENSIONS`.
@@ -693,7 +694,7 @@ try (ClientSession session = client.connect(username, host, 
port).verify(timeout
 
 * Add the code to handle the new extension in 
`AbstractSftpSubsystemHelper#executeExtendedCommand`
 
-* Declare the extension name in `DEFAULT_SUPPORTED_CLIENT_EXTENSIONS` (same 
class)
+* Declare the extension name in `DEFAULT_SUPPORTED_CLIENT_EXTENSIONS` or 
`DEFAULT_OPEN_SSH_EXTENSIONS` (same class) - according to the extension type 
(generic or *OpenSSH* one).
 
 * In the `org.apache.sshd.sftp.client.extensions.helpers` package implement an 
extension of `AbstractSftpClientExtension`
 for sending and receiving the newly added extension.
@@ -744,7 +745,6 @@ 
sshd.setSubsystemFactories(Collections.singletonList(factory));
 ```
 
 
-
 **Note:**
 
 * The code assumes that the extension name is a **string** - the draft 
specification actually allows an array of bytes as well, but we chose 
simplicity.
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 feb5d98..2e98012 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
@@ -80,12 +80,15 @@ import org.apache.sshd.sftp.client.SftpClientFactory;
 import org.apache.sshd.sftp.client.SftpClientHolder;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.client.SftpVersionSelector.NamedVersionSelector;
+import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension;
+import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatExtensionInfo;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatPathExtension;
 import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
 import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.common.SftpException;
 import org.apache.sshd.sftp.common.extensions.ParserUtils;
+import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser;
 import org.slf4j.Logger;
 
@@ -132,6 +135,7 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
                 new GetCommandExecutor(),
                 new PutCommandExecutor(),
                 new ProgressCommandExecutor(),
+                new LimitsCommandExecutor(),
                 new HelpCommandExecutor())) {
             String name = e.getName();
             ValidateUtils.checkTrue(map.put(name, e) == null, "Multiple 
commands named '%s'", name);
@@ -781,7 +785,7 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
             } else if (Files.isRegularFile(local)) {
                 displayLocalPathInfo(local, stdout);
             } else {
-                stderr.println("Unsupported special file");
+                stderr.println("[" + flags + "] Unsupported special file");
             }
 
             return false;
@@ -941,6 +945,35 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
 
     /* -------------------------------------------------------------------- */
 
+    private class LimitsCommandExecutor implements SftpCommandExecutor {
+        LimitsCommandExecutor() {
+            super();
+        }
+
+        @Override
+        public String getName() {
+            return LimitsExtensionParser.NAME;
+        }
+
+        @Override
+        public boolean executeCommand(
+                String args, BufferedReader stdin, PrintStream stdout, 
PrintStream stderr)
+                throws Exception {
+            String[] comps = GenericUtils.split(args, ' ');
+            int numArgs = GenericUtils.length(comps);
+            ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of 
arguments: %s", args);
+
+            SftpClient sftp = getClient();
+            OpenSSHLimitsExtension ext = 
sftp.getExtension(OpenSSHLimitsExtension.class);
+            ValidateUtils.checkTrue(ext.isSupported(), "Extension not 
supported by server: %s", ext.getName());
+            OpenSSHLimitsExtensionInfo info = ext.limits();
+            printFieldsValues(info, stdout);
+            return false;
+        }
+    }
+
+    /* -------------------------------------------------------------------- */
+
     private class StatVfsCommandExecutor implements SftpCommandExecutor {
         StatVfsCommandExecutor() {
             super();
@@ -966,18 +999,7 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
             String remPath = resolveRemotePath(
                     (numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : 
GenericUtils.trimToEmpty(args));
             OpenSSHStatExtensionInfo info = ext.stat(remPath);
-            Field[] fields = info.getClass().getFields();
-            for (Field f : fields) {
-                String name = f.getName();
-                int mod = f.getModifiers();
-                if (Modifier.isStatic(mod)) {
-                    continue;
-                }
-
-                Object value = f.get(info);
-                stdout.append("    ").append(name).append(": ").println(value);
-            }
-
+            printFieldsValues(info, stdout);
             return false;
         }
     }
@@ -1323,4 +1345,20 @@ public class SftpCommandMain extends SshClientCliSupport 
implements SftpClientHo
             return false;
         }
     }
+
+    
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static void printFieldsValues(Object info, PrintStream stdout) 
throws Exception {
+        Field[] fields = info.getClass().getFields();
+        for (Field f : fields) {
+            String name = f.getName();
+            int mod = f.getModifiers();
+            if (Modifier.isStatic(mod)) {
+                continue;
+            }
+
+            Object value = f.get(info);
+            stdout.append("    ").append(name).append(": ").println(value);
+        }
+    }
 }
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java 
b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 3a0bf0e..e3eca3d 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -22,6 +22,8 @@ package org.apache.sshd.util.test;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
@@ -348,6 +350,21 @@ public abstract class JUnitTestSupport extends Assert {
         assertFalse(message + "[non-empty-actual]", actual.hasNext());
     }
 
+    public static <T> void assertFieldsEqual(String extension, T expected, T 
actual) throws Exception {
+        Field[] fields = expected.getClass().getFields();
+        for (Field f : fields) {
+            String name = f.getName();
+            int mod = f.getModifiers();
+            if (Modifier.isStatic(mod)) {
+                continue;
+            }
+
+            Object expValue = f.get(expected);
+            Object actValue = f.get(actual);
+            assertEquals(extension + "[" + name + "]", expValue, actValue);
+        }
+    }
+
     public static Path assertHierarchyTargetFolderExists(Path folder, 
LinkOption... options) throws IOException {
         if (Files.exists(folder, options)) {
             assertTrue("Target is an existing file instead of a folder: " + 
folder, Files.isDirectory(folder, options));
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
index 6222804..08e5d7e 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
@@ -1828,7 +1828,7 @@ public abstract class AbstractSession extends 
SessionHelper {
             log.debug("setOutputEncoding({}): cipher {}; mac {}; compression 
{}; blocks limit {}", this, outCipher, outMac,
                     outCompression, maxRekeyBlocks);
         }
-    };
+    }
 
     /**
      * Installs the current prepared {@link #inSettings} so that they are 
effective and will be applied to any future
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java
index 3303913..977735d 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java
@@ -156,18 +156,30 @@ public final class SftpModuleProperties {
     public static final Property<String> NEWLINE_VALUE
             = Property.string("sftp-newline", IoUtils.EOL);
 
+    public static final int MIN_READDATA_PACKET_LENGTH = 32768;
     /**
      * Force the use of a max. packet length for {@link 
AbstractSftpSubsystemHelper#doRead(Buffer, int)} protection
      * against malicious packets
      */
     public static final Property<Integer> MAX_READDATA_PACKET_LENGTH
-            = Property.integer("sftp-max-readdata-packet-length", 63 * 1024);
+            = 
Property.validating(Property.integer("sftp-max-readdata-packet-length", 63 * 
1024),
+                    l -> ValidateUtils.checkTrue(l >= 
MIN_READDATA_PACKET_LENGTH, "Length is below min.: %d", l));
 
+    public static final int MIN_WRITEDATA_PACKET_LENGTH = 32768;
     /**
-     * Properties key for the maximum of available open handles per session.
+     * Force the use of a max. packet length for {@link 
AbstractSftpSubsystemHelper#doWrite(Buffer, int)} protection
+     * against malicious packets
+     */
+    public static final Property<Integer> MAX_WRITEDATA_PACKET_LENGTH
+            = 
Property.validating(Property.integer("sftp-max-writedata-packet-length", 256 * 
1024),
+                    l -> ValidateUtils.checkTrue(l >= 
MIN_WRITEDATA_PACKET_LENGTH, "Length is below min.: %d", l));
+
+    /**
+     * Properties key for the maximum of available open handles per session. 
By default we impose virtually no limit and
+     * relay on the O/S.
      */
     public static final Property<Integer> MAX_OPEN_HANDLES_PER_SESSION
-            = Property.integer("max-open-handles-per-session", 
Integer.MAX_VALUE);
+            = Property.integer("max-open-handles-per-session", 
Integer.MAX_VALUE - 1);
 
     public static final int MIN_FILE_HANDLE_SIZE = 4; // ~uint32
     public static final int DEFAULT_FILE_HANDLE_SIZE = 16;
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java
index b531240..b5c919a 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java
@@ -35,10 +35,12 @@ import 
org.apache.sshd.sftp.client.extensions.helpers.MD5FileExtensionImpl;
 import org.apache.sshd.sftp.client.extensions.helpers.MD5HandleExtensionImpl;
 import 
org.apache.sshd.sftp.client.extensions.helpers.SpaceAvailableExtensionImpl;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension;
 import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHPosixRenameExtension;
 import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatHandleExtension;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatPathExtension;
 import 
org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHFsyncExtensionImpl;
+import 
org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHLimitsExtensionImpl;
 import 
org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHPosixRenameExtensionImpl;
 import 
org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHStatHandleExtensionImpl;
 import 
org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHStatPathExtensionImpl;
@@ -46,6 +48,7 @@ import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.common.extensions.ParserUtils;
 import org.apache.sshd.sftp.common.extensions.openssh.FstatVfsExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser;
 import 
org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser;
 
@@ -130,6 +133,13 @@ public enum BuiltinSftpClientExtensions implements 
SftpClientExtensionFactory {
             return new OpenSSHPosixRenameExtensionImpl(client, raw, 
extensions);
         }
     },
+    OPENSSH_LIMITS(LimitsExtensionParser.NAME, OpenSSHLimitsExtension.class) {
+        @Override // co-variant return
+        public OpenSSHLimitsExtension create(
+                SftpClient client, RawSftpClient raw, Map<String, byte[]> 
extensions, Map<String, ?> parsed) {
+            return new OpenSSHLimitsExtensionImpl(client, raw, extensions);
+        }
+    },
     ;
 
     public static final Set<BuiltinSftpClientExtensions> VALUES
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
index cbd5666..e0885ac 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
@@ -28,7 +28,7 @@ import 
org.apache.sshd.sftp.client.extensions.SftpClientExtension;
  * Implements the &quot;fs...@openssh.com&quot; extension
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 10</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.6</A>
  */
 public interface OpenSSHFsyncExtension extends SftpClientExtension {
     void fsync(Handle fileHandle) throws IOException;
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java
similarity index 79%
copy from 
sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
copy to 
sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java
index cbd5666..91716a6 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java
@@ -21,15 +21,12 @@ package org.apache.sshd.sftp.client.extensions.openssh;
 
 import java.io.IOException;
 
-import org.apache.sshd.sftp.client.SftpClient.Handle;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
 
 /**
- * Implements the &quot;fs...@openssh.com&quot; extension
- *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 10</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.8</A>
  */
-public interface OpenSSHFsyncExtension extends SftpClientExtension {
-    void fsync(Handle fileHandle) throws IOException;
+public interface OpenSSHLimitsExtension extends SftpClientExtension {
+    OpenSSHLimitsExtensionInfo limits() throws IOException;
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java
new file mode 100644
index 0000000..f365902
--- /dev/null
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java
@@ -0,0 +1,150 @@
+/*
+ * 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.sftp.client.extensions.openssh;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.sftp.SftpModuleProperties;
+
+/**
+ * Response for the &quot;lim...@openssh.com&quot; request
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.8</A>
+ */
+public class OpenSSHLimitsExtensionInfo implements Cloneable {
+    // CHECKSTYLE:OFF
+    /** The total number of bytes in a single SFTP packet. */
+    public long maxPacketLength;
+
+    /**
+     * The largest length in a SSH_FXP_READ packet. Even if the client 
requests a larger size,\
+     * servers will usually respond with a shorter SSH_FXP_DATA packet
+     */
+    public long maxReadLength;
+
+    /** The largest length in a SSH_FXP_WRITE packet the server will accept */
+    public long maxWriteLength;
+
+    /**
+     * The maximum number of active handles that the server allows (e.g. 
handles created
+     * by SSH_FXP_OPEN and SSH_FXP_OPENDIR packets). If the server doesn't 
enforce a
+     * specific limit, then the field may be set to 0. This implies the server 
relies on
+     * the OS to enforce limits (e.g. available memory or file handles), and 
such limits
+     * might be dynamic. The client SHOULD take care to not try to exceed 
reasonable limits.
+     */
+    public long maxOpenHandles;
+    // CHECKSTYLE:ON
+
+    public OpenSSHLimitsExtensionInfo() {
+        super();
+    }
+
+    public OpenSSHLimitsExtensionInfo(Buffer buffer) {
+        decode(buffer, this);
+    }
+
+    public OpenSSHLimitsExtensionInfo(PropertyResolver resolver) {
+        fill(resolver, this);
+    }
+
+    public <B extends Buffer> B encode(B buffer) {
+        return encode(buffer, this);
+    }
+
+    @Override
+    public int hashCode() {
+        return NumberUtils.hashCode(maxPacketLength, maxReadLength, 
maxWriteLength, maxOpenHandles);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        OpenSSHLimitsExtensionInfo other = (OpenSSHLimitsExtensionInfo) obj;
+        return (this.maxPacketLength == other.maxPacketLength)
+                && (this.maxReadLength == other.maxReadLength)
+                && (this.maxWriteLength == other.maxWriteLength)
+                && (this.maxOpenHandles == other.maxOpenHandles);
+    }
+
+    @Override
+    public OpenSSHLimitsExtensionInfo clone() {
+        try {
+            return getClass().cast(super.clone());
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Failed to close " + toString() + ": " 
+ e.getMessage());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "maxPacketLength=" + maxPacketLength
+               + ", maxReadLength=" + maxReadLength
+               + ", maxWriteLength=" + maxWriteLength
+               + ", maxOpenHandles=" + maxOpenHandles;
+    }
+
+    public static <B extends Buffer> B encode(B buffer, 
OpenSSHLimitsExtensionInfo info) {
+        buffer.putLong(info.maxPacketLength);
+        buffer.putLong(info.maxReadLength);
+        buffer.putLong(info.maxWriteLength);
+        buffer.putLong(info.maxOpenHandles);
+        return buffer;
+    }
+
+    public static <I extends OpenSSHLimitsExtensionInfo> I decode(Buffer 
buffer, I info) {
+        info.maxPacketLength = buffer.getLong();
+        info.maxReadLength = buffer.getLong();
+        info.maxWriteLength = buffer.getLong();
+        info.maxOpenHandles = buffer.getLong();
+        return info;
+    }
+
+    public static <I extends OpenSSHLimitsExtensionInfo> I 
fill(PropertyResolver resolver, I info) {
+        info.maxReadLength = 
SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.getRequired(resolver);
+        info.maxWriteLength = 
SftpModuleProperties.MAX_WRITEDATA_PACKET_LENGTH.getRequired(resolver);
+        info.maxPacketLength = Math.max(info.maxReadLength, 
info.maxWriteLength)
+                               + SshConstants.SSH_PACKET_HEADER_LEN
+                               + (3 * Integer.BYTES)   // id, type, len
+        ;
+        info.maxOpenHandles = 
SftpModuleProperties.MAX_OPEN_HANDLES_PER_SESSION.getRequired(resolver);
+        /*
+         * Quote:
+         *
+         *      If the server doesn't enforce a specific limit, then the field 
may be set to 0.
+         *      This implies the server relies on the OS to enforce limits.
+         */
+        if (info.maxOpenHandles >= (Integer.MAX_VALUE - 1)) {
+            info.maxOpenHandles = 0;
+        }
+        return info;
+    }
+}
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java
index 4f0ae07..cc610b0 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java
@@ -26,9 +26,7 @@ import org.apache.sshd.common.util.buffer.Buffer;
  * Response for the &quot;stat...@openssh.com&quot; and 
&quot;fstat...@openssh.com&quot; extension commands.
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A HREF=
- *         
"http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL?rev=1.28&content-type=text/plain";>OpenSSH
- *         section 3.4</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.4</A>
  */
 public class OpenSSHStatExtensionInfo implements Cloneable {
     // The values of the f_flag bitmask
@@ -57,6 +55,10 @@ public class OpenSSHStatExtensionInfo implements Cloneable {
         decode(buffer, this);
     }
 
+    public <B extends Buffer> B encode(B buffer) {
+        return encode(buffer, this);
+    }
+
     @Override
     public int hashCode() {
         return NumberUtils.hashCode(this.f_bsize, this.f_frsize, this.f_blocks,
@@ -115,7 +117,7 @@ public class OpenSSHStatExtensionInfo implements Cloneable {
                + ",f_namemax=" + f_namemax;
     }
 
-    public static void encode(Buffer buffer, OpenSSHStatExtensionInfo info) {
+    public static <B extends Buffer> B encode(B buffer, 
OpenSSHStatExtensionInfo info) {
         buffer.putLong(info.f_bsize);
         buffer.putLong(info.f_frsize);
         buffer.putLong(info.f_blocks);
@@ -127,15 +129,14 @@ public class OpenSSHStatExtensionInfo implements 
Cloneable {
         buffer.putLong(info.f_fsid);
         buffer.putLong(info.f_flag);
         buffer.putLong(info.f_namemax);
+        return buffer;
     }
 
     public static OpenSSHStatExtensionInfo decode(Buffer buffer) {
-        OpenSSHStatExtensionInfo info = new OpenSSHStatExtensionInfo();
-        decode(buffer, info);
-        return info;
+        return decode(buffer, new OpenSSHStatExtensionInfo());
     }
 
-    public static void decode(Buffer buffer, OpenSSHStatExtensionInfo info) {
+    public static <I extends OpenSSHStatExtensionInfo> I decode(Buffer buffer, 
I info) {
         info.f_bsize = buffer.getLong();
         info.f_frsize = buffer.getLong();
         info.f_blocks = buffer.getLong();
@@ -147,5 +148,6 @@ public class OpenSSHStatExtensionInfo implements Cloneable {
         info.f_fsid = buffer.getLong();
         info.f_flag = buffer.getLong();
         info.f_namemax = buffer.getLong();
+        return info;
     }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java
new file mode 100644
index 0000000..e07f005
--- /dev/null
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sftp.client.extensions.openssh.helpers;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.Map;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.sftp.client.RawSftpClient;
+import org.apache.sshd.sftp.client.SftpClient;
+import 
org.apache.sshd.sftp.client.extensions.helpers.AbstractSftpClientExtension;
+import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension;
+import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo;
+import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class OpenSSHLimitsExtensionImpl extends AbstractSftpClientExtension 
implements OpenSSHLimitsExtension {
+    public OpenSSHLimitsExtensionImpl(SftpClient client, RawSftpClient raw, 
Map<String, byte[]> extensions) {
+        super(LimitsExtensionParser.NAME, client, raw, extensions);
+    }
+
+    @Override
+    public OpenSSHLimitsExtensionInfo limits() throws IOException {
+        Buffer buffer = getCommandBuffer(Integer.BYTES);
+        buffer = 
checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer)));
+        if (buffer == null) {
+            throw new StreamCorruptedException("Missing extended reply data");
+        }
+
+        return new OpenSSHLimitsExtensionInfo(buffer);
+    }
+}
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java
index 11221a5..a8c6468 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java
@@ -28,7 +28,7 @@ import org.apache.sshd.common.file.util.BasePath;
 import org.apache.sshd.sftp.client.SftpClient;
 
 /**
- * A {@link Path} on an {@link SftpFileSystem}.
+ * A {@link java.nio.file.Path} on an {@link SftpFileSystem}.
  */
 public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
 
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java
index 35e9f59..d9d76cc 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java
@@ -57,7 +57,7 @@ public class SftpPathImpl extends SftpPath {
      *
      * @param doCache whether to start caching (increasing the cache level) or 
to stop caching (decreasing the cache
      *                level)
-     * @see           {@link #withAttributeCache(Path, IOFunction)}
+     * @see           #withAttributeCache(Path, IOFunction)
      */
     protected void cacheAttributes(boolean doCache) {
         if (doCache) {
@@ -133,7 +133,7 @@ public class SftpPathImpl extends SftpPath {
      * @return             the result of the {@code operation}
      * @throws IOException if thrown by the {@code operation}
      *
-     * @see                {@link #withAttributeCache(IOFunction)}
+     * @see                #withAttributeCache(IOFunction)
      */
     public static <T> T withAttributeCache(Path path, IOFunction<Path, T> 
operation) throws IOException {
         if (path instanceof SftpPathImpl) {
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
index 5e28cee..600d1c2 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
@@ -42,6 +42,7 @@ import 
org.apache.sshd.sftp.common.extensions.openssh.FstatVfsExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.HardLinkExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.LSetStatExtensionParser;
+import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser;
 import 
org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser;
 
@@ -65,7 +66,8 @@ public final class ParserUtils {
                     FstatVfsExtensionParser.INSTANCE,
                     HardLinkExtensionParser.INSTANCE,
                     FsyncExtensionParser.INSTANCE,
-                    LSetStatExtensionParser.INSTANCE));
+                    LSetStatExtensionParser.INSTANCE,
+                    LimitsExtensionParser.INSTANCE));
 
     private static final NavigableMap<String, ExtensionParser<?>> PARSERS_MAP 
= Collections.unmodifiableNavigableMap(
             BUILT_IN_PARSERS.stream()
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java
index 612a487..ff7d824 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java
@@ -21,6 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.4</A>
  */
 public class FstatVfsExtensionParser extends AbstractOpenSSHExtensionParser {
     public static final String NAME = "fstat...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
index e5ab96c..2ee19b9 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
@@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 10</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.6</A>
  */
 public class FsyncExtensionParser extends AbstractOpenSSHExtensionParser {
     public static final String NAME = "fs...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java
index 90d559c..ea2d4d1 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java
@@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 10</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.5</A>
  */
 public class HardLinkExtensionParser extends AbstractOpenSSHExtensionParser {
     public static final String NAME = "hardl...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java
index 69856a4..b6884ff 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java
@@ -23,7 +23,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
  * Replicates the functionality of the existing {@code SSH_FXP_SETSTAT} 
operation but does not follow symbolic links
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A HREF="https://www.openssh.com/txt/release-8.0";>OpenSSH v8.0 
release notes</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.7</A>
  */
 public class LSetStatExtensionParser extends AbstractOpenSSHExtensionParser {
     public static final String NAME = "lsets...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java
similarity index 77%
copy from 
sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
copy to 
sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java
index e5ab96c..73b4d06 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java
@@ -21,13 +21,13 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 10</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.8</A>
  */
-public class FsyncExtensionParser extends AbstractOpenSSHExtensionParser {
-    public static final String NAME = "fs...@openssh.com";
-    public static final FsyncExtensionParser INSTANCE = new 
FsyncExtensionParser();
+public class LimitsExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "lim...@openssh.com";
+    public static final LimitsExtensionParser INSTANCE = new 
LimitsExtensionParser();
 
-    public FsyncExtensionParser() {
+    public LimitsExtensionParser() {
         super(NAME);
     }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java
index 9f8690c..f71da6b 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java
@@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 3.3</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.3</A>
  */
 public class PosixRenameExtensionParser extends AbstractOpenSSHExtensionParser 
{
     public static final String NAME = "posix-ren...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java
index 8f28fe5..8ce333f 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java
@@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 3.4</A>
+ * @see    <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
- section 4.4</A>
  */
 public class StatVfsExtensionParser extends AbstractOpenSSHExtensionParser {
     public static final String NAME = "stat...@openssh.com";
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
index 9d4fd4f..43f2e06 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
@@ -42,9 +42,16 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
     }
 
     @Override
-    public void initialized(ServerSession session, int version) throws 
IOException {
+    public void receivedExtension(ServerSession session, String extension, int 
id) throws IOException {
         if (log.isTraceEnabled()) {
-            log.trace("initialized(" + session + ") version: " + version);
+            log.trace("receivedExtension({}) id={}, extension={}", session, 
id, extension);
+        }
+    }
+
+    @Override
+    public void initialized(ServerSession session, int version) throws 
IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("initialized(" + session + ") version=" + version);
         }
     }
 
@@ -65,6 +72,16 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
     }
 
     @Override
+    public void openFailed(
+            ServerSession session, String remotePath, Path localPath, boolean 
isDirectory, Throwable thrown)
+            throws IOException {
+        if (log.isTraceEnabled()) {
+            log.trace("openFailed({}) remotePath={}, localPath={}, isDir={}, 
thrown={}",
+                    session, remotePath, localPath, isDirectory, thrown);
+        }
+    }
+
+    @Override
     public void open(ServerSession session, String remoteHandle, Handle 
localHandle) throws IOException {
         if (log.isTraceEnabled()) {
             Path path = localHandle.getFile();
@@ -74,6 +91,14 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
     }
 
     @Override
+    public void readingEntries(ServerSession session, String remoteHandle, 
DirectoryHandle localHandle)
+            throws IOException {
+        if (log.isTraceEnabled()) {
+            log.trace("readingEntries({}) handle={}[{}]", session, 
remoteHandle, localHandle.getFile());
+        }
+    }
+
+    @Override
     public void readEntries(ServerSession session, String remoteHandle, 
DirectoryHandle localHandle, Map<String, Path> entries)
             throws IOException {
         int numEntries = MapEntryUtils.size(entries);
@@ -181,6 +206,14 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
     }
 
     @Override
+    public void closed(ServerSession session, String remoteHandle, Handle 
localHandle, Throwable thrown)
+            throws IOException {
+        if (log.isTraceEnabled()) {
+            log.trace("closed({}) handle={}[{}], thrown={}", session, 
remoteHandle, localHandle.getFile(), thrown);
+        }
+    }
+
+    @Override
     public void creating(ServerSession session, Path path, Map<String, ?> 
attrs)
             throws IOException {
         if (log.isTraceEnabled()) {
@@ -264,4 +297,11 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
                       + ((thrown == null) ? "" : (": " + 
thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
         }
     }
+
+    @Override
+    public void exiting(ServerSession session, Handle handle) throws 
IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("exiting({}) handle={}[{}]", session, handle.getFile(), 
handle.getFileHandle());
+        }
+    }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
index 812f24b..b3e32bf 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
@@ -93,6 +93,7 @@ import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.SftpClient;
+import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo;
 import org.apache.sshd.sftp.client.fs.SftpPath;
 import org.apache.sshd.sftp.client.impl.SftpPathImpl;
 import org.apache.sshd.sftp.common.SftpConstants;
@@ -104,6 +105,7 @@ import 
org.apache.sshd.sftp.common.extensions.openssh.AbstractOpenSSHExtensionPa
 import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.HardLinkExtensionParser;
 import org.apache.sshd.sftp.common.extensions.openssh.LSetStatExtensionParser;
+import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser;
 import 
org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser;
 
 /**
@@ -137,7 +139,8 @@ public abstract class AbstractSftpSubsystemHelper
                     new OpenSSHExtension(FsyncExtensionParser.NAME, "1"),
                     new OpenSSHExtension(HardLinkExtensionParser.NAME, "1"),
                     new OpenSSHExtension(LSetStatExtensionParser.NAME, "1"),
-                    new OpenSSHExtension(PosixRenameExtensionParser.NAME, 
"1")));
+                    new OpenSSHExtension(PosixRenameExtensionParser.NAME, "1"),
+                    new OpenSSHExtension(LimitsExtensionParser.NAME, "1")));
 
     public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES = 
Collections.unmodifiableList(
             NamedResource.getNameList(DEFAULT_OPEN_SSH_EXTENSIONS));
@@ -790,7 +793,7 @@ public abstract class AbstractSftpSubsystemHelper
             int id, String existingPath, String linkPath, boolean symLink)
             throws IOException;
 
-    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL 
section 10
+    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL 
section 4.5
     protected void doOpenSSHHardLink(Buffer buffer, int id) throws IOException 
{
         String srcFile = buffer.getString();
         String dstFile = buffer.getString();
@@ -806,6 +809,21 @@ public abstract class AbstractSftpSubsystemHelper
         sendStatus(prepareReply(buffer), id, SftpConstants.SSH_FX_OK, "");
     }
 
+    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL 
section 4.8
+    protected void doOpenSSHLimits(Buffer buffer, int id) throws IOException {
+        OpenSSHLimitsExtensionInfo info = getOpenSSHLimitsExtensionInfo(id, 
getServerChannelSession());
+        buffer = prepareReply(buffer);
+        buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
+        buffer.putInt(id);
+        info.encode(buffer);
+        send(buffer);
+
+    }
+
+    protected OpenSSHLimitsExtensionInfo getOpenSSHLimitsExtensionInfo(int id, 
ChannelSession channel) throws IOException {
+        return new OpenSSHLimitsExtensionInfo(channel);
+    }
+
     protected void doOpenSSHHardLink(int id, String srcFile, String dstFile) 
throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("doOpenSSHHardLink({})[id={}] SSH_FXP_EXTENDED[{}] 
(src={}, dst={})",
@@ -1766,6 +1784,9 @@ public abstract class AbstractSftpSubsystemHelper
             case PosixRenameExtensionParser.NAME:
                 doPosixRename(buffer, id);
                 break;
+            case LimitsExtensionParser.NAME:
+                doOpenSSHLimits(buffer, id);
+                break;
             default:
                 doUnsupportedExtension(buffer, id, extension);
         }
@@ -1866,6 +1887,10 @@ public abstract class AbstractSftpSubsystemHelper
             return extList;
         }
 
+        if (log.isDebugEnabled()) {
+            log.debug("appendOpenSSHExtensions({}): {}", session, extList);
+        }
+
         for (OpenSSHExtension ext : extList) {
             buffer.putString(ext.getName());
             buffer.putString(ext.getVersion());
@@ -1943,15 +1968,16 @@ public abstract class AbstractSftpSubsystemHelper
      * Appends the &quot;versions&quot; extension to the buffer. <B>Note:</B> 
if overriding this method make sure you
      * either do not append anything or use the correct extension name
      *
-     * @param buffer  The {@link Buffer} to append to
-     * @param value   The recommended value - ignored if {@code null}/empty
-     * @param session The {@link ServerSession} for which this extension is 
added
-     * @see           SftpConstants#EXT_VERSIONS
+     * @param  buffer  The {@link Buffer} to append to
+     * @param  value   The recommended value - ignored if {@code null}/empty
+     * @param  session The {@link ServerSession} for which this extension is 
added
+     * @return         The apended value
+     * @see            SftpConstants#EXT_VERSIONS
      */
-    protected void appendVersionsExtension(
+    protected String appendVersionsExtension(
             Buffer buffer, String value, ServerSession session) {
         if (GenericUtils.isEmpty(value)) {
-            return;
+            return value;
         }
 
         if (log.isDebugEnabled()) {
@@ -1960,30 +1986,33 @@ public abstract class AbstractSftpSubsystemHelper
 
         buffer.putString(SftpConstants.EXT_VERSIONS);
         buffer.putString(value);
+        return value;
     }
 
     /**
      * Appends the &quot;newline&quot; extension to the buffer. <B>Note:</B> 
if overriding this method make sure you
      * either do not append anything or use the correct extension name
      *
-     * @param buffer  The {@link Buffer} to append to
-     * @param session The {@link ServerSession} for which this extension is 
added
-     * @see           SftpConstants#EXT_NEWLINE
-     * @see           #resolveNewlineValue(ServerSession)
+     * @param  buffer  The {@link Buffer} to append to
+     * @param  session The {@link ServerSession} for which this extension is 
added
+     * @return         The appended value
+     * @see            SftpConstants#EXT_NEWLINE
+     * @see            #resolveNewlineValue(ServerSession)
      */
-    protected void appendNewlineExtension(Buffer buffer, ServerSession 
session) {
+    protected String appendNewlineExtension(Buffer buffer, ServerSession 
session) {
         String value = resolveNewlineValue(session);
         if (GenericUtils.isEmpty(value)) {
-            return;
+            return value;
         }
 
         if (log.isDebugEnabled()) {
             log.debug("appendNewlineExtension({}) value={}",
-                    getServerSession(), BufferUtils.toHex(':', 
value.getBytes(StandardCharsets.UTF_8)));
+                    session, BufferUtils.toHex(':', 
value.getBytes(StandardCharsets.UTF_8)));
         }
 
         buffer.putString(SftpConstants.EXT_NEWLINE);
         buffer.putString(value);
+        return value;
     }
 
     protected String resolveNewlineValue(ServerSession session) {
@@ -1994,24 +2023,25 @@ public abstract class AbstractSftpSubsystemHelper
      * Appends the &quot;vendor-id&quot; extension to the buffer. <B>Note:</B> 
if overriding this method make sure you
      * either do not append anything or use the correct extension name
      *
-     * @param buffer            The {@link Buffer} to append to
-     * @param versionProperties The currently available version properties - 
ignored if {@code null}/empty. The code
-     *                          expects the following values:
-     *                          <UL>
-     *                          <LI>{@code groupId} - as the vendor name</LI>
-     *                          <LI>{@code artifactId} - as the product 
name</LI>
-     *                          <LI>{@code version} - as the product 
version</LI>
-     *                          </UL>
-     * @param session           The {@link ServerSession} for which these 
properties are added
-     * @see                     SftpConstants#EXT_VENDOR_ID
-     * @see                     <A HREF=
-     *                          
"http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt";>DRAFT
-     *                          09 - section 4.4</A>
+     * @param  buffer            The {@link Buffer} to append to
+     * @param  versionProperties The currently available version properties - 
ignored if {@code null}/empty. The code
+     *                           expects the following values:
+     *                           <UL>
+     *                           <LI>{@code groupId} - as the vendor name</LI>
+     *                           <LI>{@code artifactId} - as the product 
name</LI>
+     *                           <LI>{@code version} - as the product 
version</LI>
+     *                           </UL>
+     * @param  session           The {@link ServerSession} for which these 
properties are added
+     * @return                   The version properties
+     * @see                      SftpConstants#EXT_VENDOR_ID
+     * @see                      <A HREF=
+     *                           
"http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt";>DRAFT
+     *                           09 - section 4.4</A>
      */
-    protected void appendVendorIdExtension(
+    protected Map<String, ?> appendVendorIdExtension(
             Buffer buffer, Map<String, ?> versionProperties, ServerSession 
session) {
         if (MapEntryUtils.isEmpty(versionProperties)) {
-            return;
+            return versionProperties;
         }
 
         if (log.isDebugEnabled()) {
@@ -2029,6 +2059,7 @@ public abstract class AbstractSftpSubsystemHelper
         buffer.putString(resolver.getStringProperty("version", 
FactoryManager.DEFAULT_VERSION)); // product-version
         buffer.putLong(0L); // product-build-number
         BufferUtils.updateLengthPlaceholder(buffer, lenPos);
+        return versionProperties;
     }
 
     /**
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
index d985c5d..34421cb 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
@@ -822,9 +822,10 @@ public class SftpSubsystem
             throws IOException {
         Handle h = handles.get(handle);
         ServerSession session = getServerSession();
+        int maxAllowed = 
SftpModuleProperties.MAX_WRITEDATA_PACKET_LENGTH.getRequired(session);
         if (log.isTraceEnabled()) {
-            log.trace("doWrite({})[id={}] SSH_FXP_WRITE (handle={}[{}], 
offset={}, data=byte[{}])",
-                    session, id, handle, h, offset, length);
+            log.trace("doWrite({})[id={}] SSH_FXP_WRITE (handle={}[{}], 
offset={}, length={}, maxAllowed={})",
+                    session, id, handle, h, offset, length, maxAllowed);
         }
 
         FileHandle fh = validateHandle(handle, h, FileHandle.class);
@@ -838,6 +839,10 @@ public class SftpSubsystem
                                             + ": required=" + length + ", 
available=" + remaining);
         }
 
+        if (length > maxAllowed) {
+            throw new IOException("Reuested write size (" + length + ") 
exceeds max. allowed (" + maxAllowed + ")");
+        }
+
         SftpEventListener listener = getSftpEventListenerProxy();
         listener.writing(session, handle, fh, offset, data, doff, length);
         try {
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
index 35422f8..ce4904d 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
@@ -212,8 +212,8 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
         Path parentPath = targetPath.getParent();
         Path lclSftp = CommonTestSupportUtils.resolve(
                 targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, 
getClass().getSimpleName(), getCurrentTestName());
-        Path testFile = 
assertHierarchyTargetFolderExists(lclSftp).resolve("file.txt");
-        byte[] expected = new byte[1024];
+        Path testFile = 
assertHierarchyTargetFolderExists(lclSftp).resolve("file.bin");
+        byte[] expected = new 
byte[(SftpModuleProperties.MIN_READDATA_PACKET_LENGTH + 16) * 4];
 
         Factory<? extends Random> factory = sshd.getRandomFactory();
         Random rnd = factory.create();
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java
index 22ac2b5..1dbeb45 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java
@@ -80,7 +80,7 @@ public class SftpExtensionsTest extends 
AbstractSftpClientTestSupport {
         }
     }
 
-    @Test   // see SSHD-1266
+    @Test   // see SSHD-1166
     public void testCustomFileExtensionAttributes() throws IOException {
         Path targetPath = detectTargetFolder();
         Path parentPath = targetPath.getParent();
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
index bdcd9c9..8427844 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
@@ -22,8 +22,6 @@ package 
org.apache.sshd.sftp.client.extensions.openssh.helpers;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -42,6 +40,8 @@ import 
org.apache.sshd.sftp.client.AbstractSftpClientTestSupport;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClient.CloseableHandle;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension;
+import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo;
 import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHPosixRenameExtension;
 import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatExtensionInfo;
 import 
org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatHandleExtension;
@@ -144,8 +144,7 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
         Path parentPath = targetPath.getParent();
         String srcPath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
 
-        final AtomicReference<String> extensionHolder = new 
AtomicReference<>(null);
-        final OpenSSHStatExtensionInfo expected = new 
OpenSSHStatExtensionInfo();
+        OpenSSHStatExtensionInfo expected = new OpenSSHStatExtensionInfo();
         expected.f_bavail = Short.MAX_VALUE;
         expected.f_bfree = Integer.MAX_VALUE;
         expected.f_blocks = Short.MAX_VALUE;
@@ -158,6 +157,7 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
         expected.f_fsid = 1L;
         expected.f_namemax = 256;
 
+        AtomicReference<String> extensionHolder = new AtomicReference<>(null);
         sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory() {
             @Override
             public Command createSubsystem(ChannelSession channel) throws 
IOException {
@@ -206,32 +206,25 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
             OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath);
             String invokedExtension = extensionHolder.getAndSet(null);
             assertEquals("Mismatched invoked extension", pathStat.getName(), 
invokedExtension);
-            assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, 
actual);
+            assertFieldsEqual(invokedExtension, expected, actual);
 
             try (CloseableHandle handle = sftp.open(srcPath)) {
                 OpenSSHStatHandleExtension handleStat = 
assertExtensionCreated(sftp, OpenSSHStatHandleExtension.class);
                 actual = handleStat.stat(handle);
                 invokedExtension = extensionHolder.getAndSet(null);
                 assertEquals("Mismatched invoked extension", 
handleStat.getName(), invokedExtension);
-                assertOpenSSHStatExtensionInfoEquals(invokedExtension, 
expected, actual);
+                assertFieldsEqual(invokedExtension, expected, actual);
             }
         }
     }
 
-    private static void assertOpenSSHStatExtensionInfoEquals(
-            String extension, OpenSSHStatExtensionInfo expected, 
OpenSSHStatExtensionInfo actual)
-            throws Exception {
-        Field[] fields = expected.getClass().getFields();
-        for (Field f : fields) {
-            String name = f.getName();
-            int mod = f.getModifiers();
-            if (Modifier.isStatic(mod)) {
-                continue;
-            }
-
-            Object expValue = f.get(expected);
-            Object actValue = f.get(actual);
-            assertEquals(extension + "[" + name + "]", expValue, actValue);
+    @Test   // see SSHD-1233
+    public void testLimits() throws Exception {
+        try (SftpClient sftp = createSingleSessionClient()) {
+            OpenSSHLimitsExtension ext = assertExtensionCreated(sftp, 
OpenSSHLimitsExtension.class);
+            OpenSSHLimitsExtensionInfo expected = new 
OpenSSHLimitsExtensionInfo(sftp.getClientChannel());
+            OpenSSHLimitsExtensionInfo actual = ext.limits();
+            assertFieldsEqual(ext.getName(), expected, actual);
         }
     }
 }

Reply via email to