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/06 15:16:55 UTC

mina-sshd git commit: [SSHD-528] Use byte[] as the SFTP Handle identifier

Repository: mina-sshd
Updated Branches:
  refs/heads/master f7f21bc85 -> 883efa01b


[SSHD-528] Use byte[] as the SFTP Handle identifier


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

Branch: refs/heads/master
Commit: 883efa01b2213687a04b5a2702a1606a5b11d3f9
Parents: f7f21bc
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jul 6 16:16:44 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jul 6 16:16:44 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/sshd/client/SshClient.java  |   1 +
 .../subsystem/sftp/AbstractSftpClient.java      |  45 +--
 .../subsystem/sftp/DefaultCloseableHandle.java  |   2 +-
 .../sshd/client/subsystem/sftp/SftpClient.java  |  51 ++-
 .../sshd/client/subsystem/sftp/SftpCommand.java | 335 +++++++++++++++++++
 .../subsystem/sftp/SftpFileSystemProvider.java  | 152 +++++++--
 .../impl/AbstractMD5HashExtension.java          |   8 +-
 .../extensions/impl/MD5HandleExtensionImpl.java |   2 +-
 .../sshd/common/random/AbstractRandom.java      |  34 ++
 .../apache/sshd/common/random/JceRandom.java    |   2 +-
 .../org/apache/sshd/common/random/Random.java   |   7 +-
 .../common/random/SingletonRandomFactory.java   |   2 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   |  20 +-
 .../apache/sshd/common/util/SecurityUtils.java  |   3 +-
 .../sshd/common/util/buffer/BufferUtils.java    |   5 +-
 .../server/subsystem/sftp/SftpSubsystem.java    |  55 ++-
 .../org/apache/sshd/client/scp/ScpTest.java     |   2 +-
 .../sftp/DefaultCloseableHandleTest.java        |   9 +-
 .../client/subsystem/sftp/SftpCommandMain.java  |  31 ++
 19 files changed, 674 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index ae0b758..85b19ac 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -584,6 +584,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
             if (error) {
                 System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-D socksPort] [-l login] [-p port] [-o option=value] hostname/user@host [command]");
                 System.exit(-1);
+                return;
             }
 
             try(SshClient client = (SshClient) session.getFactoryManager()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
index bd1988c..2461fbd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -277,7 +277,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         }
     }
 
-    protected String checkHandle(Buffer buffer) throws IOException {
+    protected byte[] checkHandle(Buffer buffer) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
@@ -290,8 +290,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             }
             throw new SftpException(substatus, msg);
         } else if (type == SSH_FXP_HANDLE) {
-            String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
-            return handle;
+            return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
         } else {
             throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
@@ -616,8 +615,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("close(" + handle + ") client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */);
+        buffer.putBytes(id);
         checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
     }
 
@@ -673,8 +673,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */);
+        buffer.putBytes(id);
         buffer.putLong(fileOffset);
         buffer.putInt(len);
         return checkData(receive(send(SSH_FXP_READ, buffer)), dstOffset, dst);
@@ -723,8 +724,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + len + Long.SIZE /* some extra fields */);
+        buffer.putBytes(id);
         buffer.putLong(fileOffset);
         buffer.putBytes(src, srcOffset, len);
         checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
@@ -776,8 +778,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("readDir(" + handle + ") client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */);
+        buffer.putBytes(id);
         return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
     }
 
@@ -867,8 +870,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("stat(" + handle + ") client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* a bit extra */);
+        buffer.putBytes(id);
 
         int version = getVersion();
         if (version >= SFTP_V4) {
@@ -896,8 +900,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */);
+        buffer.putBytes(id);
         writeAttributes(buffer, attributes);
         checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
     }
@@ -942,8 +947,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */);
+        buffer.putBytes(id);
         buffer.putLong(offset);
         buffer.putLong(length);
         buffer.putInt(mask);
@@ -956,8 +962,9 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
         }
 
-        Buffer buffer = new ByteArrayBuffer();
-        buffer.putString(handle.id);
+        byte[] id = handle.getIdentifier();
+        Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */);
+        buffer.putBytes(id);
         buffer.putLong(offset);
         buffer.putLong(length);
         checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
index 868ff10..842e63a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
@@ -32,7 +32,7 @@ public class DefaultCloseableHandle extends CloseableHandle {
     private final AtomicBoolean open = new AtomicBoolean(true);
     private final SftpClient client;
 
-    public DefaultCloseableHandle(SftpClient client, String id) {
+    public DefaultCloseableHandle(SftpClient client, byte[] id) {
         super(id);
         this.client = ValidateUtils.checkNotNull(client, "No client for id=%s", id);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index bc3d5f5..fb73149 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -40,6 +40,8 @@ import org.apache.sshd.client.subsystem.SubsystemClient;
 import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.bouncycastle.util.Arrays;
 
 /**
  * @author <a href="http://mina.apache.org">Apache MINA Project</a>
@@ -72,19 +74,56 @@ public interface SftpClient extends SubsystemClient {
     }
 
     public static class Handle {
-        public final String id;
-        public Handle(String id) {
-            this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY);
+        private final byte[] id;
+
+        public Handle(byte[] id) {
+            // clone the original so the handle is imutable
+            this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY).clone();
+        }
+
+        /**
+         * @return A <U>cloned</U> instance of the identifier in order to
+         * avoid inadvertent modifications to the handle contents
+         */
+        public byte[] getIdentifier() {
+            return id.clone();
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(id);
         }
-        
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            
+            if (obj == this) {
+                return true;
+            }
+
+            // we do not ask getClass() == obj.getClass() in order to allow for derived classes equality
+            if (!(obj instanceof Handle)) {
+                return false;
+            }
+            
+            if (Arrays.areEqual(id, ((Handle) obj).id)) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
         @Override
         public String toString() {
-            return id;
+            return BufferUtils.printHex(BufferUtils.EMPTY_HEX_SEPARATOR, id);
         }
     }
 
     public static abstract class CloseableHandle extends Handle implements Channel, Closeable {
-        protected CloseableHandle(String id) {
+        protected CloseableHandle(byte[] id) {
             super(id);
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/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
new file mode 100644
index 0000000..c0d7edd
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
@@ -0,0 +1,335 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.channels.Channel;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+
+/**
+ * Implements a simple command line SFTP client similar to the Linux one
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpCommand implements Channel {
+    private final SftpClient client;
+    private String cwdRemote;
+    private final Map<String,CommandExecutor> commandsMap = 
+            Collections.unmodifiableMap(new TreeMap<String,CommandExecutor>() {
+                private static final long serialVersionUID = 1L;    // we're not serializing it
+                
+                {
+                    for (CommandExecutor e : Arrays.<CommandExecutor>asList(
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "exit";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+                                            stdout.println("Exiting");
+                                            return true;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "pwd";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+                                            stdout.append('\t').println(getCurrentRemoteDirectory());
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "version";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+                                            SftpClient sftp = getClient();
+                                            stdout.append('\t').println(sftp.getVersion());
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "cd";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+
+                                            String newPath = resolveRemotePath(args);
+                                            SftpClient sftp = getClient();
+                                            setCurrentRemoteDirectory(sftp.canonicalPath(newPath));
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "mkdir";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+
+                                            String path = resolveRemotePath(args);
+                                            SftpClient sftp = getClient();
+                                            sftp.mkdir(path);
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "ls";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            String[] comps = GenericUtils.split(args, ' ');
+                                            // ignore all flag
+                                            String pathArg = GenericUtils.isEmpty(comps) ? null : comps[comps.length - 1];
+                                            String cwd = getCurrentRemoteDirectory();
+                                            if (GenericUtils.isEmpty(pathArg) || (pathArg.charAt(0) == '-')) {
+                                                pathArg = cwd;
+                                            }
+
+                                            String path = resolveRemotePath(pathArg);
+                                            SftpClient sftp = getClient();
+                                            for (SftpClient.DirEntry entry : sftp.readDir(path)) {
+                                                SftpClient.Attributes attrs = entry.attributes;
+                                                stdout.append('\t').append(entry.filename)
+                                                      .append('\t').append(Long.toString(attrs.size))
+                                                      .append('\t').println(SftpFileSystemProvider.getRWXPermissions(attrs.perms));
+                                            }
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "rm";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+
+                                            String path = resolveRemotePath(args);
+                                            SftpClient sftp = getClient();
+                                            sftp.remove(path);
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "rmdir";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+
+                                            String path = resolveRemotePath(args);
+                                            SftpClient sftp = getClient();
+                                            sftp.rmdir(path);
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "rename";
+                                        }
+    
+                                        @Override
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            String[] comps = GenericUtils.split(args, ' ');
+                                            ValidateUtils.checkTrue(GenericUtils.length(comps) == 2, "Invalid number of arguments: %s", args);
+
+                                            String oldPath = resolveRemotePath(comps[0]);
+                                            String newPath = resolveRemotePath(comps[1]);
+                                            SftpClient sftp = getClient();
+                                            sftp.rename(oldPath, newPath);
+                                            return false;
+                                        }
+                                    },
+                                new CommandExecutor() {
+                                        @Override
+                                        public String getName() {
+                                            return "help";
+                                        }
+    
+                                        @Override
+                                        @SuppressWarnings("synthetic-access")
+                                        public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+                                            ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
+                                            for (String cmd : commandsMap.keySet()) {
+                                                stdout.append('\t').println(cmd);
+                                            }
+                                            return false;
+                                        }
+                                    }
+                            )) {
+                        put(e.getName(), e);
+                    }
+                }
+            });
+
+    public SftpCommand(SftpClient client) {
+        this.client = ValidateUtils.checkNotNull(client, "No client", GenericUtils.EMPTY_OBJECT_ARRAY);
+    }
+
+    public final SftpClient getClient() {
+        return client;
+    }
+
+    public void doInteractive(BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+        SftpClient sftp = getClient();
+        setCurrentRemoteDirectory(sftp.canonicalPath("."));
+        while(true) {
+            stdout.append(getCurrentRemoteDirectory()).append(" > ").flush();
+            String line = stdin.readLine();
+            if (line == null) { // EOF
+                break;
+            }
+            
+            line = line.trim();
+            if (GenericUtils.isEmpty(line)) {
+                continue;
+            }
+            
+            String cmd, args;
+            int pos = line.indexOf(' ');
+            if (pos > 0) {
+                cmd = line.substring(0, pos);
+                args = line.substring(pos + 1).trim();
+            } else {
+                cmd = line;
+                args = "";
+            }
+            
+            CommandExecutor exec = commandsMap.get(cmd);
+            try {
+                if (exec == null) {
+                    stderr.append("Unknown command: ").println(line);
+                } else {
+                    try {
+                        if (exec.executeCommand(args, stdin, stdout, stderr)) {
+                            break;
+                        }
+                    } catch(Exception e) {
+                        stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage());
+                    } finally {
+                        stdout.flush();
+                    }
+                }
+            } finally {
+                stderr.flush(); // just makings sure
+            }
+        }
+    }
+
+    protected String resolveRemotePath(String pathArg) {
+        String cwd = getCurrentRemoteDirectory();
+        if (GenericUtils.isEmpty(pathArg)) {
+            return cwd;
+        }
+        
+        if (pathArg.charAt(0) == '/') {
+            return pathArg;
+        } else {
+            return cwd + "/" + pathArg;
+        }
+    }
+    public String getCurrentRemoteDirectory() {
+        return cwdRemote;
+    }
+
+    public void setCurrentRemoteDirectory(String path) {
+        cwdRemote = path;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return client.isOpen();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (isOpen()) {
+            client.close();
+        }
+    }
+
+    public static interface CommandExecutor extends NamedResource {
+        // return value is whether to stop running
+        boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception; 
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+
+    public static void main(String[] args) throws Exception {
+        PrintStream stdout=System.out, stderr=System.err;
+        try(BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
+            ClientSession session=SshClient.setupClientSession("-P", stdin, stdout, stderr, args);
+            if (session == null) {
+                System.err.println("usage: sftp [-l login] [-P port] [-o option=value] hostname/user@host");
+                System.exit(-1);
+                return;
+            }
+            
+            try(SshClient client = (SshClient) session.getFactoryManager()) {
+                try(SftpCommand sftp = new SftpCommand(session.createSftpClient())) {
+                    sftp.doInteractive(stdin, stdout, stderr);
+                }
+            } finally {
+                session.close();
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 796f678..072b85d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -18,17 +18,6 @@
  */
 package org.apache.sshd.client.subsystem.sftp;
 
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRGRP;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IROTH;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWGRP;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWOTH;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXGRP;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXOTH;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXUSR;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -390,7 +379,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
                 sftp.mkdir(dir.toString());
             } catch (SftpException e) {
                 int sftpStatus=e.getStatus();
-                if ((sftp.getVersion() == SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
+                if ((sftp.getVersion() == SftpConstants.SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
                     try {
                         Attributes attributes = sftp.stat(dir.toString());
                         if (attributes != null) {
@@ -940,31 +929,31 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         for (PosixFilePermission p : perms) {
             switch (p) {
                 case OWNER_READ:
-                    pf |= S_IRUSR;
+                    pf |= SftpConstants.S_IRUSR;
                     break;
                 case OWNER_WRITE:
-                    pf |= S_IWUSR;
+                    pf |= SftpConstants.S_IWUSR;
                     break;
                 case OWNER_EXECUTE:
-                    pf |= S_IXUSR;
+                    pf |= SftpConstants.S_IXUSR;
                     break;
                 case GROUP_READ:
-                    pf |= S_IRGRP;
+                    pf |= SftpConstants.S_IRGRP;
                     break;
                 case GROUP_WRITE:
-                    pf |= S_IWGRP;
+                    pf |= SftpConstants.S_IWGRP;
                     break;
                 case GROUP_EXECUTE:
-                    pf |= S_IXGRP;
+                    pf |= SftpConstants.S_IXGRP;
                     break;
                 case OTHERS_READ:
-                    pf |= S_IROTH;
+                    pf |= SftpConstants.S_IROTH;
                     break;
                 case OTHERS_WRITE:
-                    pf |= S_IWOTH;
+                    pf |= SftpConstants.S_IWOTH;
                     break;
                 case OTHERS_EXECUTE:
-                    pf |= S_IXOTH;
+                    pf |= SftpConstants.S_IXOTH;
                     break;
                 default:
                     if (log.isTraceEnabled()) {
@@ -976,39 +965,142 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         return pf;
     }
 
+    public static String getRWXPermissions(int perms) {
+        StringBuilder sb=new StringBuilder(10 /* 3 * rwx + (d)irectory */);
+        if ((perms & SftpConstants.S_IFLNK) != 0) {
+            sb.append('l');
+        } else if ((perms & SftpConstants.S_IFDIR) != 0) {
+            sb.append('d');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IRUSR) != 0) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWUSR) != 0) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXUSR) != 0) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IRGRP) != 0) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWGRP) != 0) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXGRP) != 0) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IROTH) != 0) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWOTH) != 0) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXOTH) != 0) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+        
+        return sb.toString();
+    }
+
+    public static String getOctalPermissions(int perms) {
+        return getOctalPermissions(permissionsToAttributes(perms));
+    }
 
     public static Set<PosixFilePermission> permissionsToAttributes(int perms) {
         Set<PosixFilePermission> p = new HashSet<>();
-        if ((perms & S_IRUSR) != 0) {
+        if ((perms & SftpConstants.S_IRUSR) != 0) {
             p.add(PosixFilePermission.OWNER_READ);
         }
-        if ((perms & S_IWUSR) != 0) {
+        if ((perms & SftpConstants.S_IWUSR) != 0) {
             p.add(PosixFilePermission.OWNER_WRITE);
         }
-        if ((perms & S_IXUSR) != 0) {
+        if ((perms & SftpConstants.S_IXUSR) != 0) {
             p.add(PosixFilePermission.OWNER_EXECUTE);
         }
-        if ((perms & S_IRGRP) != 0) {
+        if ((perms & SftpConstants.S_IRGRP) != 0) {
             p.add(PosixFilePermission.GROUP_READ);
         }
-        if ((perms & S_IWGRP) != 0) {
+        if ((perms & SftpConstants.S_IWGRP) != 0) {
             p.add(PosixFilePermission.GROUP_WRITE);
         }
-        if ((perms & S_IXGRP) != 0) {
+        if ((perms & SftpConstants.S_IXGRP) != 0) {
             p.add(PosixFilePermission.GROUP_EXECUTE);
         }
-        if ((perms & S_IROTH) != 0) {
+        if ((perms & SftpConstants.S_IROTH) != 0) {
             p.add(PosixFilePermission.OTHERS_READ);
         }
-        if ((perms & S_IWOTH) != 0) {
+        if ((perms & SftpConstants.S_IWOTH) != 0) {
             p.add(PosixFilePermission.OTHERS_WRITE);
         }
-        if ((perms & S_IXOTH) != 0) {
+        if ((perms & SftpConstants.S_IXOTH) != 0) {
             p.add(PosixFilePermission.OTHERS_EXECUTE);
         }
         return p;
     }
 
+    public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
+        int pf = 0;
+
+        for (PosixFilePermission p : perms) {
+            switch (p) {
+                case OWNER_READ:
+                    pf |= SftpConstants.S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= SftpConstants.S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= SftpConstants.S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= SftpConstants.S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= SftpConstants.S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= SftpConstants.S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= SftpConstants.S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= SftpConstants.S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= SftpConstants.S_IXOTH;
+                    break;
+                default:    // ignored
+            }
+        }
+
+        return String.format("%04o", Integer.valueOf(pf));
+    }
+
     /**
      * Uses the host, port and username to create a unique identifier
      * @param uri The {@link URI} - <B>Note:</B> not checked to make sure

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/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 12c51ca..7aa7778 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
@@ -38,11 +38,15 @@ public abstract class AbstractMD5HashExtension extends AbstractSftpClientExtensi
         super(name, client, raw, extras);
     }
 
-    protected byte[] doGetHash(String target, long offset, long length, byte[] quickHash) throws IOException {
+    protected byte[] doGetHash(Object target, long offset, long length, byte[] quickHash) throws IOException {
         Buffer buffer = new ByteArrayBuffer();
         String opcode = getName();
         buffer.putString(opcode);
-        buffer.putString(target);
+        if (target instanceof CharSequence) {
+            buffer.putString(target.toString());
+        } else {
+            buffer.putBytes((byte[]) target);
+        }
         buffer.putLong(offset);
         buffer.putLong(length);
         buffer.putBytes((quickHash == null) ? GenericUtils.EMPTY_BYTE_ARRAY : quickHash);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
index e6ab476..b5a482f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
@@ -37,7 +37,7 @@ public class MD5HandleExtensionImpl extends AbstractMD5HashExtension implements
 
     @Override
     public byte[] getHash(SftpClient.Handle handle, long offset, long length, byte[] quickHash) throws IOException {
-        return doGetHash(handle.id, offset, length, quickHash);
+        return doGetHash(handle.getIdentifier(), offset, length, quickHash);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/random/AbstractRandom.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/AbstractRandom.java b/sshd-core/src/main/java/org/apache/sshd/common/random/AbstractRandom.java
new file mode 100644
index 0000000..372de9b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/AbstractRandom.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.common.random;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractRandom implements Random {
+    protected AbstractRandom() {
+        super();
+    }
+    
+    @Override     // TODO in JDK-8 make this a default method
+    public void fill(byte[] bytes) {
+        fill(bytes, 0, bytes.length);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java b/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
index 6aaec25..7e1b3a5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/JceRandom.java
@@ -25,7 +25,7 @@ import java.security.SecureRandom;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class JceRandom implements Random {
+public class JceRandom extends AbstractRandom {
 
     private byte[] tmp = new byte[16];
     private final SecureRandom random = new SecureRandom();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/random/Random.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/Random.java b/sshd-core/src/main/java/org/apache/sshd/common/random/Random.java
index e3f2c78..1729001 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/Random.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/Random.java
@@ -24,10 +24,15 @@ package org.apache.sshd.common.random;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public interface Random {
+    /**
+     * Fill the buffer with random values
+     * @param bytes The bytes to fill
+     * @see #fill(byte[], int, int)
+     */
+    void fill(byte[] bytes);    // TODO in JDK-8 make this a default method
 
     /**
      * Fill part of bytes with random values.
-     *
      * @param bytes byte array to be filled.
      * @param start index to start filling at.
      * @param len length of segment to fill.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
index 8fc7d48..b53b2da 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/random/SingletonRandomFactory.java
@@ -27,7 +27,7 @@ import org.apache.sshd.common.OptionalFeature;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class SingletonRandomFactory implements Random, RandomFactory {
+public class SingletonRandomFactory extends AbstractRandom implements RandomFactory {
 
     private final NamedFactory<Random> factory;
     private final Random random;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 1614985..16b9733 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -213,7 +213,7 @@ public class ScpHelper extends AbstractLoggingBean {
             throw new IOException("Expected a D message but got '" + header + "'");
         }
 
-        Set<PosixFilePermission> perms = parseOctalPerms(header.substring(1, 5));
+        Set<PosixFilePermission> perms = parseOctalPermissions(header.substring(1, 5));
         int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
         String name = header.substring(header.indexOf(' ', 6) + 1);
 
@@ -310,7 +310,7 @@ public class ScpHelper extends AbstractLoggingBean {
             throw new IOException("receiveStream(" + resolver + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")");
         }
 
-        Set<PosixFilePermission> perms = parseOctalPerms(header.substring(1, 5));
+        Set<PosixFilePermission> perms = parseOctalPermissions(header.substring(1, 5));
         final long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6)));
         String name = header.substring(header.indexOf(' ', 6) + 1);
         if (length < 0L) { // TODO consider throwing an exception...
@@ -528,7 +528,7 @@ public class ScpHelper extends AbstractLoggingBean {
         }
 
         Set<PosixFilePermission> perms = EnumSet.copyOf(resolver.getPermissions());
-        String octalPerms = preserve ? getOctalPerms(perms) : "0644";
+        String octalPerms = preserve ? getOctalPermissions(perms) : "0644";
         String fileName = resolver.getFileName();
         String cmd = new StringBuilder(octalPerms.length() + fileName.length() + Long.SIZE /* some extra delimiters */)
             .append('C').append(octalPerms)
@@ -581,7 +581,7 @@ public class ScpHelper extends AbstractLoggingBean {
         Set<PosixFilePermission> perms = IoUtils.getPermissions(path, options);
         StringBuilder buf = new StringBuilder();
         buf.append("D");
-        buf.append(preserve ? getOctalPerms(perms) : "0755");
+        buf.append(preserve ? getOctalPermissions(perms) : "0755");
         buf.append(" ");
         buf.append("0"); // length
         buf.append(" ");
@@ -615,11 +615,11 @@ public class ScpHelper extends AbstractLoggingBean {
         readAck(false);
     }
 
-    public static String getOctalPerms(Path path, LinkOption ... options) throws IOException {
-        return getOctalPerms(IoUtils.getPermissions(path, options));
+    public static String getOctalPermissions(Path path, LinkOption ... options) throws IOException {
+        return getOctalPermissions(IoUtils.getPermissions(path, options));
     }
 
-    public static String getOctalPerms(Collection<PosixFilePermission> perms) {
+    public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
         int pf = 0;
 
         for (PosixFilePermission p : perms) {
@@ -658,13 +658,13 @@ public class ScpHelper extends AbstractLoggingBean {
         return String.format("%04o", Integer.valueOf(pf));
     }
 
-    public static Set<PosixFilePermission> setOctalPerms(Path path, String str) throws IOException {
-        Set<PosixFilePermission> perms = parseOctalPerms(str);
+    public static Set<PosixFilePermission> setOctalPermissions(Path path, String str) throws IOException {
+        Set<PosixFilePermission> perms = parseOctalPermissions(str);
         IoUtils.setPermissions(path, perms);
         return perms;
     }
 
-    public static Set<PosixFilePermission> parseOctalPerms(String str) {
+    public static Set<PosixFilePermission> parseOctalPermissions(String str) {
         int perms = Integer.parseInt(str, 8);
         Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
         if ((perms & S_IRUSR) != 0) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
index 5c6d6fe..aa80440 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/SecurityUtils.java
@@ -42,6 +42,7 @@ import javax.crypto.Mac;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.keyprovider.AbstractClassLoadableResourceKeyPairProvider;
 import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider;
+import org.apache.sshd.common.random.AbstractRandom;
 import org.apache.sshd.common.random.JceRandomFactory;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.random.RandomFactory;
@@ -312,7 +313,7 @@ public class SecurityUtils {
      *
      * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
      */
-    private static class BouncyCastleRandom implements Random {
+    private static class BouncyCastleRandom extends AbstractRandom {
 
         private final RandomGenerator random;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
index 42697bc..0e3cb09 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
@@ -41,8 +41,9 @@ public class BufferUtils {
         return printHex(array, 0, GenericUtils.length(array), sep);
     }
 
+    public static final char DEFAULT_HEX_SEPARATOR = ' ', EMPTY_HEX_SEPARATOR = '\0';
     public static String printHex(byte[] array, int offset, int len) {
-        return printHex(array, offset, len, ' ');
+        return printHex(array, offset, len, DEFAULT_HEX_SEPARATOR);
     }
 
     public static final String  HEX_DIGITS="0123456789abcdef";
@@ -55,7 +56,7 @@ public class BufferUtils {
         StringBuilder sb = new StringBuilder(len * 3 /* 2 HEX + sep */);
         for (int curOffset = offset, maxOffset = offset + len; curOffset < maxOffset; curOffset++) {
             byte b = array[curOffset];
-            if (sb.length() > 0) {
+            if ((sb.length() > 0) && (sep != EMPTY_HEX_SEPARATOR)) {
                 sb.append(sep);
             }
             sb.append(HEX_DIGITS.charAt((b >> 4) & 0x0F));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/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 0ff4943..43e3bc2 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
@@ -72,21 +72,23 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.config.VersionProperties;
 import org.apache.sshd.common.digest.BuiltinDigests;
 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.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -110,6 +112,16 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
      * Properties key for the maximum of available open handles per session.
      */
     public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
+        public static final int DEFAULT_MAX_OPEN_HANDLES = Integer.MAX_VALUE;
+
+    /**
+     * Size in bytes of the opaque handle value
+     * @see #DEFAULT_FILE_HANDLE_SIZE
+     */
+    public static final String FILE_HANDLE_SIZE = "sftp-handle-size";
+        public static final int MIN_FILE_FILE_HANDLE_SIZE = 4;  // ~uint32
+        public static final int DEFAULT_FILE_HANDLE_SIZE = 16;
+        public static final int MAX_FILE_FILE_HANDLE_SIZE = 64;  // ~sha512 
 
     /**
      * Force the use of a given sftp version
@@ -170,12 +182,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     private OutputStream out;
     private OutputStream err;
     private Environment env;
+    private Random randomizer;
+    private int fileHandleSize = DEFAULT_FILE_HANDLE_SIZE;
     private ServerSession session;
-    private boolean closed = false;
+    private boolean closed;
 	private ExecutorService executors;
 	private boolean shutdownExecutor;
 	private Future<?> pendingFuture;
-	private final byte[] workBuf = new byte[Integer.SIZE / Byte.SIZE]; // TODO in JDK-8 use Integer.BYTES
+	private byte[] workBuf = new byte[Math.max(DEFAULT_FILE_HANDLE_SIZE, Integer.SIZE / Byte.SIZE)]; // TODO in JDK-8 use Integer.BYTES
     private FileSystem fileSystem = FileSystems.getDefault();
     private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
     private long requestsCount;
@@ -428,6 +442,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     @Override
     public void setSession(ServerSession session) {
         this.session = session;
+        
+        FactoryManager manager = session.getFactoryManager();
+        Factory<? extends Random> factory = manager.getRandomFactory();
+        this.randomizer = factory.create();
+        this.fileHandleSize = FactoryManagerUtils.getIntProperty(manager, FILE_HANDLE_SIZE, DEFAULT_FILE_HANDLE_SIZE);
+        ValidateUtils.checkTrue(this.fileHandleSize >= MIN_FILE_FILE_HANDLE_SIZE, "File handle size too small: %d", this.fileHandleSize);
+        ValidateUtils.checkTrue(this.fileHandleSize <= MAX_FILE_FILE_HANDLE_SIZE, "File handle size too big: %d", this.fileHandleSize);
+
+        if (workBuf.length < this.fileHandleSize) {
+            workBuf = new byte[this.fileHandleSize];
+        }
     }
 
     @Override
@@ -707,8 +732,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             Digest digest = BuiltinDigests.md5.create();
             digest.init();
 
-            byte[] workBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
-            ByteBuffer bb = ByteBuffer.wrap(workBuf);
+            byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
+            ByteBuffer bb = ByteBuffer.wrap(digestBuf);
             boolean hashMatches = false;
             byte[] hashValue = null;
 
@@ -729,7 +754,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                 } else {
                     int readLen = channel.read(bb);
                     effectiveLength -= readLen;
-                    digest.update(workBuf, 0, readLen);
+                    digest.update(digestBuf, 0, readLen);
     
                     hashValue = digest.digest();
                     hashMatches = Arrays.equals(quickCheckHash, hashValue);
@@ -744,7 +769,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                         if (effectiveLength > 0L) {
                             digest = BuiltinDigests.md5.create();
                             digest.init();
-                            digest.update(workBuf, 0, readLen);
+                            digest.update(digestBuf, 0, readLen);
                             hashValue = null;   // start again
                         }
                     } else {
@@ -761,7 +786,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                         bb.clear();
                         int readLen = channel.read(bb); 
                         effectiveLength -= readLen;
-                        digest.update(workBuf, 0, readLen);
+                        digest.update(digestBuf, 0, readLen);
                     }
                     
                     if (hashValue == null) {    // check if did any more iterations after the quick hash
@@ -1282,7 +1307,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
             } else if (!Files.isReadable(p)) {
                 sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
             } else {
-                String handle = UUID.randomUUID().toString();
+                String handle = generateFileHandle(p);
                 handles.put(handle, new DirectoryHandle(p));
                 sendHandle(id, handle);
             }
@@ -1447,7 +1472,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     protected void doOpen(Buffer buffer, int id) throws IOException {
         int curHandleCount = handles.size();
-        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, Integer.MAX_VALUE);
+        int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, DEFAULT_MAX_OPEN_HANDLES);
         if (curHandleCount > maxHandleCount) {
             sendStatus(id, SSH_FX_FAILURE, "Too many open handles: current=" + curHandleCount + ", max.=" + maxHandleCount);
             return;
@@ -1512,7 +1537,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
         try {
             Path file = resolveFile(path);
-            String handle = UUID.randomUUID().toString();
+            String handle = generateFileHandle(file);
             handles.put(handle, new FileHandle(file, pflags, access, attrs));
             sendHandle(id, handle);
         } catch (IOException e) {
@@ -1520,6 +1545,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         }
     }
 
+    // we stringify our handles and treat them as such on decoding as well as it is easier to use as a map key
+    protected String generateFileHandle(Path file) {
+        randomizer.fill(workBuf, 0, fileHandleSize);
+        String handle = BufferUtils.printHex(workBuf, 0, fileHandleSize, BufferUtils.EMPTY_HEX_SEPARATOR);
+        log.trace("generateFileHandle({}) {}", file, handle);
+        return handle;
+    }
+
     protected void doInit(Buffer buffer, int id) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Received SSH_FXP_INIT (version={})", Integer.valueOf(id));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
index 00f3d27..895852c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -723,7 +723,7 @@ public class ScpTest extends BaseTestSupport {
         String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
         String fileName = getCurrentTestName() + ".txt";
         Path remoteFile = remoteDir.resolve(fileName);
-        String mode = ScpHelper.getOctalPerms(EnumSet.of(
+        String mode = ScpHelper.getOctalPermissions(EnumSet.of(
                     PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE,
                     PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE,
                     PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
index 8dbab78..1f9c6db 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
@@ -20,10 +20,9 @@
 package org.apache.sshd.client.subsystem.sftp;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.sshd.client.subsystem.sftp.DefaultCloseableHandle;
-import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
 import org.apache.sshd.util.BaseTestSupport;
@@ -46,14 +45,14 @@ public class DefaultCloseableHandleTest extends BaseTestSupport {
 
     @Test
     public void testChannelBehavior() throws IOException {
-        final String id = getCurrentTestName();
+        final byte[] id = getCurrentTestName().getBytes(StandardCharsets.UTF_8);
         SftpClient client = Mockito.mock(SftpClient.class);
         Mockito.doAnswer(new Answer<Void>() {
                 @Override
                 public Void answer(InvocationOnMock invocation) throws Throwable {
                     Object[] args = invocation.getArguments();
                     Handle handle = (Handle) args[0];
-                    assertEquals("Mismatched closing handle", id, handle.id);
+                    assertArrayEquals("Mismatched closing handle", id, handle.getIdentifier());
                     return null;
                 }
             }).when(client).close(Matchers.any(Handle.class));
@@ -82,7 +81,7 @@ public class DefaultCloseableHandleTest extends BaseTestSupport {
                 }
             }).when(client).close(Matchers.any(Handle.class));
 
-        CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName());
+        CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName().getBytes(StandardCharsets.UTF_8));
         for (int index=0; index < Byte.SIZE; index++) {
             handle.close();
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/883efa01/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
new file mode 100644
index 0000000..8cca06f
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpCommandMain.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Just a test class used to invoke {@link SftpCommand#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpCommandMain {
+    public static void main(String[] args) throws Exception {
+        SftpCommand.main(args);
+    }
+}