You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/07/12 08:37:25 UTC

mina-sshd git commit: [SSHD-536] Add support for SFTP fsync@openssh.com extension

Repository: mina-sshd
Updated Branches:
  refs/heads/master e08cbe1aa -> 45549d1e8


[SSHD-536] Add support for SFTP fsync@openssh.com extension


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/45549d1e
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/45549d1e
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/45549d1e

Branch: refs/heads/master
Commit: 45549d1e85151fa6986f62c2dc2375d475fdafb1
Parents: e08cbe1
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Sun Jul 12 09:37:13 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Sun Jul 12 09:37:13 2015 +0300

----------------------------------------------------------------------
 .../sshd/client/subsystem/sftp/SftpCommand.java |   2 +-
 .../extensions/BuiltinSftpClientExtensions.java |   9 ++
 .../impl/AbstractCheckFileExtension.java        |   4 +-
 .../impl/AbstractMD5HashExtension.java          |   4 +-
 .../impl/AbstractSftpClientExtension.java       |  12 ++
 .../extensions/impl/CopyDataExtensionImpl.java  |  11 +-
 .../extensions/impl/CopyFileExtensionImpl.java  |   9 +-
 .../openssh/OpenSSHFsyncExtension.java          |  34 ++++++
 .../openssh/impl/OpenSSHFsyncExtensionImpl.java |  49 ++++++++
 .../subsystem/sftp/extensions/ParserUtils.java  |  14 ++-
 .../openssh/AbstractOpenSSHExtensionParser.java | 115 +++++++++++++++++++
 .../openssh/FstatVfsExtensionParser.java        |  32 ++++++
 .../openssh/FsyncExtensionParser.java           |  33 ++++++
 .../openssh/HardLinkExtensionParser.java        |  33 ++++++
 .../openssh/PosixRenameExtensionParser.java     |  33 ++++++
 .../openssh/StatVfsExtensionParser.java         |  33 ++++++
 .../server/subsystem/sftp/SftpSubsystem.java    |  97 +++++++++++++++-
 .../sshd/client/subsystem/sftp/SftpTest.java    |  12 +-
 .../sftp/extensions/OpenSSHExtensionsTest.java  |  95 +++++++++++++++
 19 files changed, 604 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
index 4a7f783..64e7578 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
@@ -88,7 +88,7 @@ public class SftpCommand implements Channel {
                                             ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
                                             SftpClient sftp = getClient();
                                             Map<String,byte[]> extensions = sftp.getServerExtensions();
-                                            Map<String,?> parsed = ParserUtils.parse(null);
+                                            Map<String,?> parsed = ParserUtils.parse(extensions);
                                             for (Map.Entry<String,byte[]> ee : extensions.entrySet()) {
                                                 String name = ee.getKey();
                                                 byte[] value = ee.getValue();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
index 467ef78..f1db773 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
@@ -32,9 +32,12 @@ import org.apache.sshd.client.subsystem.sftp.extensions.impl.CopyDataExtensionIm
 import org.apache.sshd.client.subsystem.sftp.extensions.impl.CopyFileExtensionImpl;
 import org.apache.sshd.client.subsystem.sftp.extensions.impl.MD5FileExtensionImpl;
 import org.apache.sshd.client.subsystem.sftp.extensions.impl.MD5HandleExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.impl.OpenSSHFsyncExtensionImpl;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -75,6 +78,12 @@ public enum BuiltinSftpClientExtensions implements SftpClientExtensionFactory {
             public CheckFileHandleExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
                 return new CheckFileHandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
             }
+        },
+    OPENSSH_FSYNC(FsyncExtensionParser.NAME, OpenSSHFsyncExtension.class) {
+            @Override   // co-variant return
+            public OpenSSHFsyncExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+                return new OpenSSHFsyncExtensionImpl(client, raw, extensions);
+            }
         };
 
     private final String name;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
index a31f8e5..d5bb304 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
@@ -31,7 +31,6 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Pair;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -42,9 +41,8 @@ public abstract class AbstractCheckFileExtension extends AbstractSftpClientExten
     }
 
     protected Pair<String,Collection<byte[]>> doGetHash(Object target, Collection<String> algorithms, long offset, long length, int blockSize) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+        Buffer buffer = getCommandBuffer(Byte.MAX_VALUE);
         String opcode = getName();
-        buffer.putString(opcode);
         if (target instanceof CharSequence) {
             buffer.putString(target.toString());
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
index b7c138b..03d5350 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
@@ -28,7 +28,6 @@ import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -39,9 +38,8 @@ public abstract class AbstractMD5HashExtension extends AbstractSftpClientExtensi
     }
 
     protected byte[] doGetHash(Object target, long offset, long length, byte[] quickHash) throws IOException {
-        Buffer buffer = new ByteArrayBuffer();
+        Buffer buffer = getCommandBuffer(Long.SIZE + 2 * (Long.SIZE / Byte.SIZE) + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(quickHash));
         String opcode = getName();
-        buffer.putString(opcode);
         if (target instanceof CharSequence) {
             buffer.putString(target.toString());
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
index 14eef9c..0778c2c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
@@ -32,6 +32,7 @@ import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 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.logging.AbstractLoggingBean;
 
 /**
@@ -98,6 +99,17 @@ public abstract class AbstractSftpClientExtension extends AbstractLoggingBean im
     }
 
     /**
+     * @param extraSize Extra size - besides the extension name
+     * @return A {@link Buffer} with the extension name set
+     */
+    protected Buffer getCommandBuffer(int extraSize) {
+        String opcode = getName();
+        Buffer buffer = new ByteArrayBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(opcode) + extraSize + Byte.SIZE);
+        buffer.putString(opcode);
+        return buffer;
+    }
+
+    /**
      * @param buffer The {@link Buffer} to check
      * @return The {@link Buffer} if this is an {@link SftpConstants#SSH_FXP_EXTENDED_REPLY},
      * or {@code null} if this is a {@link SftpConstants#SSH_FXP_STATUS} carrying

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImpl.java
index 8d28bbb..318d813 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyDataExtensionImpl.java
@@ -29,7 +29,6 @@ import org.apache.sshd.client.subsystem.sftp.extensions.CopyDataExtension;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -42,13 +41,9 @@ public class CopyDataExtensionImpl extends AbstractSftpClientExtension implement
     @Override
     public void copyData(Handle readHandle, long readOffset, long readLength, Handle writeHandle, long writeOffset) throws IOException {
         byte[] srcId = readHandle.getIdentifier(), dstId = writeHandle.getIdentifier();
-        String opcode = getName();
-        Buffer buffer = new ByteArrayBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(opcode)
-                                          + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(srcId)
-                                          + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(dstId)
-                                          + (3 * (Long.SIZE + (Integer.SIZE / Byte.SIZE)))
-                                          + Byte.SIZE);
-        buffer.putString(opcode);
+        Buffer buffer = getCommandBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(srcId)
+                                       + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(dstId)
+                                       + (3 * (Long.SIZE + (Integer.SIZE / Byte.SIZE))));
         buffer.putBytes(srcId);
         buffer.putLong(readOffset);
         buffer.putLong(readLength);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
index 7186df6..28d7b18 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
@@ -28,7 +28,6 @@ import org.apache.sshd.client.subsystem.sftp.extensions.CopyFileExtension;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -40,11 +39,9 @@ public class CopyFileExtensionImpl extends AbstractSftpClientExtension implement
 
     @Override
     public void copyFile(String src, String dst, boolean overwriteDestination) throws IOException {
-        Buffer buffer = new ByteArrayBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(getName())
-                                          + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(src)
-                                          + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(dst)
-                                          + 1 /* override destination */);
-        buffer.putString(getName());
+        Buffer buffer = getCommandBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(src)
+                                       + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(dst)
+                                       + 1 /* override destination */);
         buffer.putString(src);
         buffer.putString(dst);
         buffer.putBoolean(overwriteDestination);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/OpenSSHFsyncExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/OpenSSHFsyncExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/OpenSSHFsyncExtension.java
new file mode 100644
index 0000000..fdfc05b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/OpenSSHFsyncExtension.java
@@ -0,0 +1,34 @@
+/*
+ * 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.client.subsystem.sftp.extensions.openssh;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+
+/**
+ * Implements the &quot;fsync@openssh.com&quot; extension
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 10
+ */
+public interface OpenSSHFsyncExtension extends SftpClientExtension {
+    void fsync(Handle fileHandle) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHFsyncExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHFsyncExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHFsyncExtensionImpl.java
new file mode 100644
index 0000000..a6cfabd
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/impl/OpenSSHFsyncExtensionImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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.client.subsystem.sftp.extensions.openssh.impl;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.client.subsystem.sftp.extensions.impl.AbstractSftpClientExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OpenSSHFsyncExtensionImpl extends AbstractSftpClientExtension implements OpenSSHFsyncExtension {
+    public OpenSSHFsyncExtensionImpl(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions) {
+        super(FsyncExtensionParser.NAME, client, raw, (GenericUtils.size(extensions) > 0) && extensions.containsKey(FsyncExtensionParser.NAME));
+    }
+
+    @Override
+    public void fsync(Handle fileHandle) throws IOException {
+        byte[] handle = fileHandle.getIdentifier();
+        Buffer buffer = getCommandBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(handle));
+        buffer.putBytes(handle);
+        sendAndCheckExtendedCommandStatus(buffer);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
index f883c89..94c7151 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
@@ -31,11 +31,17 @@ import java.util.TreeSet;
 
 import org.apache.sshd.common.subsystem.sftp.extensions.Supported2Parser.Supported2;
 import org.apache.sshd.common.subsystem.sftp.extensions.SupportedParser.Supported;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FstatVfsExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.HardLinkExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.PosixRenameExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.StatVfsExtensionParser;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 3.4
  */
 public final class ParserUtils {
     public static final Collection<ExtensionParser<?>> BUILT_IN_PARSERS =
@@ -45,7 +51,13 @@ public final class ParserUtils {
                             NewlineParser.INSTANCE,
                             VersionsParser.INSTANCE,
                             SupportedParser.INSTANCE,
-                            Supported2Parser.INSTANCE
+                            Supported2Parser.INSTANCE,
+                            // OpenSSH extensions
+                            PosixRenameExtensionParser.INSTANCE,
+                            StatVfsExtensionParser.INSTANCE,
+                            FstatVfsExtensionParser.INSTANCE,
+                            HardLinkExtensionParser.INSTANCE,
+                            FsyncExtensionParser.INSTANCE
                     ));
 
     private static final Map<String,ExtensionParser<?>> parsersMap = new TreeMap<String,ExtensionParser<?>>(String.CASE_INSENSITIVE_ORDER) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
new file mode 100644
index 0000000..0b3735c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
@@ -0,0 +1,115 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.subsystem.sftp.extensions.AbstractParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Base class for various {@code XXX@openssh.com} extension data reports
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractOpenSSHExtensionParser extends AbstractParser<OpenSSHExtension> {
+    public static class OpenSSHExtension implements NamedResource, Cloneable {
+        private final String name;
+        private String version;
+
+        public OpenSSHExtension(String name) {
+            this(name, null);
+        }
+
+        public OpenSSHExtension(String name, String version) {
+            this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No extension name", GenericUtils.EMPTY_OBJECT_ARRAY);
+            this.version = version;
+        }
+
+        @Override
+        public final String getName() {
+            return name;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public void setVersion(String version) {
+            this.version = version;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(getName(), getVersion());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (this == obj) {
+                return true;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            
+            OpenSSHExtension other = (OpenSSHExtension) obj;
+            if (Objects.equals(getName(), other.getName())
+             && Objects.equals(getVersion(), other.getVersion())) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public OpenSSHExtension clone() {
+            try {
+                return getClass().cast(super.clone());
+            } catch(CloneNotSupportedException e) {
+                throw new RuntimeException("Unexpected clone exception: " + e.getLocalizedMessage());
+            }
+        }
+
+        @Override
+        public String toString() {
+            return getName() + " " + getVersion();
+        }
+    }
+    
+    protected AbstractOpenSSHExtensionParser(String name) {
+        super(name);
+    }
+
+    @Override
+    public OpenSSHExtension parse(byte[] input, int offset, int len) {
+        return parse(new String(input, offset, len, StandardCharsets.UTF_8));
+    }
+    
+    public OpenSSHExtension parse(String version) {
+        return new OpenSSHExtension(getName(), version);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FstatVfsExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FstatVfsExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FstatVfsExtensionParser.java
new file mode 100644
index 0000000..88eaa61
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FstatVfsExtensionParser.java
@@ -0,0 +1,32 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FstatVfsExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "fstatvfs@openssh.com";
+    public static final FstatVfsExtensionParser INSTANCE = new FstatVfsExtensionParser();
+
+    public FstatVfsExtensionParser() {
+        super(NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FsyncExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FsyncExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FsyncExtensionParser.java
new file mode 100644
index 0000000..e8dbd38
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/FsyncExtensionParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 10
+ */
+public class FsyncExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "fsync@openssh.com";
+    public static final FsyncExtensionParser INSTANCE = new FsyncExtensionParser();
+
+    public FsyncExtensionParser() {
+        super(NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/HardLinkExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/HardLinkExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/HardLinkExtensionParser.java
new file mode 100644
index 0000000..2bbfb02
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/HardLinkExtensionParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 10
+ */
+public class HardLinkExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "hardlink@openssh.com";
+    public static final HardLinkExtensionParser INSTANCE = new HardLinkExtensionParser();
+
+    public HardLinkExtensionParser() {
+        super(NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/PosixRenameExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/PosixRenameExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/PosixRenameExtensionParser.java
new file mode 100644
index 0000000..9871914
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/PosixRenameExtensionParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 3.3
+ */
+public class PosixRenameExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "posix-rename@openssh.com";
+    public static final PosixRenameExtensionParser INSTANCE = new PosixRenameExtensionParser();
+
+    public PosixRenameExtensionParser() {
+        super(NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/StatVfsExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/StatVfsExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/StatVfsExtensionParser.java
new file mode 100644
index 0000000..83f4b03
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/StatVfsExtensionParser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.subsystem.sftp.extensions.openssh;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH</A> section 3.4
+ */
+public class StatVfsExtensionParser extends AbstractOpenSSHExtensionParser {
+    public static final String NAME = "statvfs@openssh.com";
+    public static final StatVfsExtensionParser INSTANCE = new StatVfsExtensionParser();
+
+    public StatVfsExtensionParser() {
+        super(NAME);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 3f1bb4c..add23fe 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -88,6 +88,8 @@ import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.file.FileSystemAware;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.OsUtils;
@@ -184,6 +186,19 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                                         SftpConstants.EXT_COPYDATA
                                 )));
 
+    /**
+     * Comma-separated list of which {@code OpenSSH} extensions are reported and
+     * what version is reported for each - format: {@code name=version}. If empty
+     * value set, then no such extensions are reported. Otherwise, the 
+     * {@link DEFAULT_OPEN_SSH_EXTENSIONS} are used
+     */
+    public static final String OPENSSH_EXTENSIONS_PROP = "sftp-openssh-extensions";
+        public static final List<OpenSSHExtension> DEFAULT_OPEN_SSH_EXTENSIONS =
+                Collections.unmodifiableList(
+                        Arrays.asList(
+                                    new OpenSSHExtension(FsyncExtensionParser.NAME, "1")
+                                ));
+
     static {
         StringBuilder sb = new StringBuilder(2 * (1 + (HIGHER_SFTP_IMPL - LOWER_SFTP_IMPL)));
         for (int v = LOWER_SFTP_IMPL; v <= HIGHER_SFTP_IMPL; v++) {
@@ -313,7 +328,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     protected class FileHandle extends Handle {
         private final int access;
-        private final FileChannel channel;
+        private final FileChannel fileChannel;
         private long pos;
         private final List<FileLock> locks = new ArrayList<>();
 
@@ -373,10 +388,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 channel = FileChannel.open(file, options);
                 setAttributes(file, attrs);
             }
-            this.channel = channel;
+            this.fileChannel = channel;
             this.pos = 0;
         }
 
+        public final FileChannel getFileChannel() {
+            return fileChannel;
+        }
+
         public int getAccessMask() {
             return access;
         }
@@ -386,6 +405,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
 
         public int read(byte[] data, int doff, int length, long offset) throws IOException {
+            FileChannel channel = getFileChannel();
             if (pos != offset) {
                 channel.position(offset);
                 pos = offset;
@@ -400,6 +420,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
 
         public void write(byte[] data, int doff, int length, long offset) throws IOException {
+            FileChannel channel = getFileChannel();
             if (pos != offset) {
                 channel.position(offset);
                 pos = offset;
@@ -410,11 +431,13 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
         @Override
         public void close() throws IOException {
+            FileChannel channel = getFileChannel();
             channel.close();
         }
 
         public void lock(long offset, long length, int mask) throws IOException {
-            long size = length == 0 ? channel.size() - offset : length;
+            FileChannel channel = getFileChannel();
+            long size = (length == 0L) ? channel.size() - offset : length;
             FileLock lock = channel.tryLock(offset, size, false);
             synchronized (locks) {
                 locks.add(lock);
@@ -422,7 +445,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
 
         public boolean unlock(long offset, long length) throws IOException {
-            long size = length == 0 ? channel.size() - offset : length;
+            FileChannel channel = getFileChannel();
+            long size = (length == 0) ? channel.size() - offset : length;
             FileLock lock = null;
             for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
                 FileLock l = iterator.next();
@@ -703,6 +727,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             case SftpConstants.EXT_CHKFILE_NAME:
                 doCheckFileHash(buffer, id, extension);
                 break;
+            case FsyncExtensionParser.NAME:
+                doOpenSSHFsync(buffer, id);
+                break;
             default:
                 log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
                 sendStatus(BufferUtils.clear(buffer), id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
@@ -734,6 +761,30 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         throw new UnsupportedOperationException("doTextSeek(" + fileHandle + ")");
     }
 
+    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10
+    protected void doOpenSSHFsync(Buffer buffer, int id) throws IOException {
+        String handle = buffer.getString();
+        try {
+            doOpenSSHFsync(id, handle);
+        } catch(IOException | RuntimeException e) {
+            sendStatus(BufferUtils.clear(buffer), id, e);
+            return;
+        }
+
+        sendStatus(BufferUtils.clear(buffer), id, SSH_FX_OK, "");
+    }
+
+    protected void doOpenSSHFsync(int id, String handle) throws IOException {
+        Handle h = handles.get(handle);
+        if (log.isDebugEnabled()) {
+            log.debug("doOpenSSHFsync({})[{}]", handle, h);
+        }
+        
+        FileHandle fileHandle = validateHandle(handle, h, FileHandle.class);
+        FileChannel channel = fileHandle.getFileChannel();
+        channel.force(false);
+    }
+
     protected void doCheckFileHash(Buffer buffer, int id, String targetType) throws IOException {
         String target = buffer.getString();
         String algList = buffer.getString();
@@ -2091,6 +2142,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         appendVersionsExtension(buffer, supportedVersions);
         appendNewlineExtension(buffer, System.getProperty("line.separator"));
         appendVendorIdExtension(buffer, VersionProperties.getVersionProperties());
+        appendOpenSSHExtensions(buffer);
 
         /* TODO updateAvailableExtensions(extensions, appendAclSupportedExtension(...)
             buffer.putString("acl-supported");
@@ -2104,6 +2156,43 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         appendSupported2Extension(buffer, extras);
     }
 
+    protected List<OpenSSHExtension> appendOpenSSHExtensions(Buffer buffer) {
+        String value = FactoryManagerUtils.getString(session, OPENSSH_EXTENSIONS_PROP);
+        List<OpenSSHExtension> extList=Collections.emptyList();
+        if (value != null) {
+            String[] pairs = GenericUtils.split(value, ',');
+            int numExts = GenericUtils.length(pairs);
+            if (numExts > 0) {
+                extList = new ArrayList<OpenSSHExtension>(numExts);
+                for (String nvp : pairs) {
+                    nvp = GenericUtils.trimToEmpty(nvp);
+                    if (GenericUtils.isEmpty(nvp)) {
+                        continue;
+                    }
+                    
+                    int pos = nvp.indexOf('=');
+                    ValidateUtils.checkTrue((pos > 0) && (pos < (nvp.length() - 1)), "Malformed OpenSSH extension spec: %s", nvp);
+                    String name = GenericUtils.trimToEmpty(nvp.substring(0, pos));
+                    String version = GenericUtils.trimToEmpty(nvp.substring(pos + 1));
+                    extList.add(new OpenSSHExtension(name, ValidateUtils.checkNotNullAndNotEmpty(version, "No version specified for OpenSSH extension %s", name)));
+                }
+            }
+        } else {
+            extList = DEFAULT_OPEN_SSH_EXTENSIONS;
+        }
+        
+        if (GenericUtils.isEmpty(extList)) {
+            return extList;
+        }
+        
+        for (OpenSSHExtension ext : extList) {
+            buffer.putString(ext.getName());
+            buffer.putString(ext.getVersion());
+        }
+        
+        return extList;
+    }
+
     protected Collection<String> getSupportedClientExtensions() {
         String value = FactoryManagerUtils.getString(session, CLIENT_EXTENSIONS_PROP);
         if (value == null) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index f975d25..81d1525 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -57,6 +57,7 @@ import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
 import org.apache.sshd.common.subsystem.sftp.extensions.Supported2Parser.Supported2;
 import org.apache.sshd.common.subsystem.sftp.extensions.SupportedParser.Supported;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -579,7 +580,16 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                             assertSupportedExtensions(extName, ((Supported2) extValue).extensionNames);
                         }
                     }
-                    
+
+                    for (OpenSSHExtension expected : SftpSubsystem.DEFAULT_OPEN_SSH_EXTENSIONS) {
+                        String name = expected.getName();
+                        Object value = data.get(name);
+                        assertNotNull("OpenSSH extension not declared: " + name, value);
+
+                        OpenSSHExtension actual = (OpenSSHExtension) value;
+                        assertEquals("Mismatched version for OpenSSH extension=" + name, expected.getVersion(), actual.getVersion());
+                    }
+
                     for (BuiltinSftpClientExtensions type : BuiltinSftpClientExtensions.VALUES) {
                         String extensionName = type.getName();
                         SftpClientExtension instance = sftp.getExtension(extensionName);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/45549d1e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/OpenSSHExtensionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/OpenSSHExtensionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/OpenSSHExtensionsTest.java
new file mode 100644
index 0000000..a7c8406
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/OpenSSHExtensionsTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.client.subsystem.sftp.extensions;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.AbstractSftpClientTestSupport;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
+    public OpenSSHExtensionsTest() throws IOException {
+        super();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        setupServer();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        tearDownServer();
+    }
+
+    @Test
+    public void testFsync() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        Utils.deleteRecursive(lclSftp);
+        Files.createDirectories(lclSftp);
+
+        Path srcFile = lclSftp.resolve(getCurrentTestName() + ".txt");
+        byte[] expected=(getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+
+        Path parentPath = targetPath.getParent();
+        String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+        try(SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+                
+                try(SftpClient sftp = session.createSftpClient()) {
+                    OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, OpenSSHFsyncExtension.class);
+                    try(CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
+                        sftp.write(fileHandle, 0L, expected);
+                        fsync.fsync(fileHandle);
+
+                        byte[] actual = Files.readAllBytes(srcFile);
+                        assertArrayEquals("Mismatched written data", expected,  actual);
+                    }
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+}