You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2015/03/31 09:25:56 UTC

mina-sshd git commit: [SSHD-444] Handle correctly inaccessible paths in Scp and Sftp subsystems

Repository: mina-sshd
Updated Branches:
  refs/heads/master 82b0f82ec -> cccec812b


[SSHD-444] Handle correctly inaccessible paths in Scp and Sftp subsystems


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

Branch: refs/heads/master
Commit: cccec812bf9a0571da7bffee651e9c741c402e39
Parents: 82b0f82
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Mar 31 09:25:49 2015 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Mar 31 09:25:49 2015 +0200

----------------------------------------------------------------------
 .../main/java/org/apache/sshd/SshServer.java    |   2 +-
 .../sshd/client/scp/DefaultScpClient.java       |  14 +-
 .../client/sftp/SftpFileSystemProvider.java     |  41 ++-
 .../file/root/RootedFileSystemProvider.java     |   7 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   | 173 +++++++---
 .../org/apache/sshd/common/util/IoUtils.java    |  39 ++-
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 334 ++++++++++++-------
 .../test/java/org/apache/sshd/ServerMain.java   |  32 ++
 8 files changed, 453 insertions(+), 189 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
index a98f5b0..9e45c44 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
@@ -431,7 +431,7 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
         SshServer sshd = SshServer.setUpDefaultServer();
         sshd.getProperties().putAll(options);
         sshd.setPort(port);
-        sshd.getProperties().put(SshServer.WELCOME_BANNER, "Welcome to SSHD\n");
+        sshd.getProperties().put(ServerFactoryManager.WELCOME_BANNER, "Welcome to SSHD\n");
         if (SecurityUtils.isBouncyCastleRegistered()) {
             sshd.setKeyPairProvider(new PEMGeneratorHostKeyProvider("key.pem"));
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index bb5019b..d034d09 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -22,11 +22,11 @@ import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.nio.file.FileSystem;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumSet;
-import java.util.List;
 import java.util.Set;
 
 import org.apache.sshd.ClientSession;
@@ -36,6 +36,7 @@ import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.scp.ScpHelper;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.IoUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -93,10 +94,15 @@ public class DefaultScpClient implements ScpClient {
         try {
             Path target = fs.getPath(local);
             if (options.contains(Option.TargetIsDirectory)) {
-                if (!Files.exists(target)) {
-                    throw new SshException("Target directory " + target.toString() + " does not exists");
+                LinkOption[]    opts = IoUtils.getLinkOptions(false);
+                Boolean         status = IoUtils.checkFileExists(target, opts);
+                if (status == null) {
+                    throw new SshException("Target directory " + target.toString() + " is probaly inaccesible");
                 }
-                if (!Files.isDirectory(target)) {
+                if (!status.booleanValue()) {
+                    throw new SshException("Target directory " + target.toString() + " does not exist");
+                }
+                if (!Files.isDirectory(target, opts)) {
                     throw new SshException("Target directory " + target.toString() + " is not a directory");
                 }
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
index cc77055..8ffa4d8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
@@ -272,7 +272,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         SftpPath src = toSftpPath(source);
         SftpPath dst = toSftpPath(target);
         if (src.getFileSystem() != dst.getFileSystem()) {
-            throw new ProviderMismatchException();
+            throw new ProviderMismatchException("Mismatched file system providers");
         }
         checkAccess(src);
 
@@ -294,10 +294,18 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             throw new IOException("Copying of symbolic links not supported");
 
         // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status=IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
+        }
+
         if (replaceExisting) {
             deleteIfExists(target);
-        } else if (Files.exists(target))
-            throw new FileAlreadyExistsException(target.toString());
+        } else {
+            if (status.booleanValue()) {
+                throw new FileAlreadyExistsException(target.toString());
+            }
+        }
 
         // create directory or copy file
         if (attrs.isDirectory()) {
@@ -355,9 +363,14 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             throw new IOException("Copying of symbolic links not supported");
 
         // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status=IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for move target " + target);
+        }
+
         if (replaceExisting) {
             deleteIfExists(target);
-        } else if (Files.exists(target))
+        } else if (status.booleanValue())
             throw new FileAlreadyExistsException(target.toString());
 
         try (SftpClient sftp = src.getFileSystem().getClient()) {
@@ -432,16 +445,16 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         boolean x = false;
         for (AccessMode mode : modes) {
             switch (mode) {
-            case READ:
-                break;
-            case WRITE:
-                w = true;
-                break;
-            case EXECUTE:
-                x = true;
-                break;
-            default:
-                throw new UnsupportedOperationException();
+                case READ:
+                    break;
+                case WRITE:
+                    w = true;
+                    break;
+                case EXECUTE:
+                    x = true;
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unsupported mode: " + mode);
             }
         }
         BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
index 38e6181..b355036 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
@@ -48,6 +48,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
+import org.apache.sshd.common.util.IoUtils;
+
 /**
  * File system provider which provides a rooted file system.
  * The file system only gives access to files under the root directory.
@@ -89,9 +91,10 @@ public class RootedFileSystemProvider extends FileSystemProvider {
             try {
                 fileSystem = fileSystems.get(uriToPath(uri).toRealPath());
             } catch (IOException ignore) {
+                // ignored
             }
             if (fileSystem == null) {
-                throw new FileSystemNotFoundException();
+                throw new FileSystemNotFoundException(uri.toString());
             }
             return fileSystem;
         }
@@ -121,7 +124,7 @@ public class RootedFileSystemProvider extends FileSystemProvider {
     }
 
     private boolean ensureDirectory(Path path) {
-        if (!Files.isDirectory(path)) {
+        if (!Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
             throw new UnsupportedOperationException();
         }
         return true;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/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 b48611e..d9392f2 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
@@ -25,9 +25,11 @@ import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.AccessDeniedException;
 import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystem;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
@@ -61,7 +63,7 @@ public class ScpHelper {
     /**
      * Default size (in bytes) of send / receive buffer size
      */
-    public static final int DEFAULT_COPY_BUFFER_SIZE = 8192;
+    public static final int DEFAULT_COPY_BUFFER_SIZE = IoUtils.DEFAULT_COPY_SIZE;
     public static final int DEFAULT_RECEIVE_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE;
     public static final int DEFAULT_SEND_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE;
 
@@ -96,13 +98,19 @@ public class ScpHelper {
 
     public void receive(Path path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
         if (shouldBeDir) {
-            if (!Files.exists(path)) {
-                throw new SshException("Target directory " + path.toString() + " does not exists");
+            LinkOption[]    options=IoUtils.getLinkOptions(false);
+            Boolean         status=IoUtils.checkFileExists(path, options);
+            if (status == null) {
+                throw new SshException("Target directory " + path.toString() + " is most like inaccessible");
             }
-            if (!Files.isDirectory(path)) {
+            if (!status.booleanValue()) {
+                throw new SshException("Target directory " + path.toString() + " does not exist");
+            }
+            if (!Files.isDirectory(path, options)) {
                 throw new SshException("Target directory " + path.toString() + " is not a directory");
             }
         }
+
         ack();
         long[] time = null;
         for (;;)
@@ -165,16 +173,40 @@ public class ScpHelper {
         if (length != 0) {
             throw new IOException("Expected 0 length for directory but got " + length);
         }
-        Path file;
-        if (Files.exists(path) && Files.isDirectory(path)) {
+
+        LinkOption[]    options=IoUtils.getLinkOptions(false);
+        Boolean         status=IoUtils.checkFileExists(path, options);
+        if (status == null) {
+            throw new AccessDeniedException("Receive directory existence status cannot be determined: " + path);
+        }
+
+        Path file=null;
+        if (status.booleanValue() && Files.isDirectory(path, options)) {
             String localName = name.replace('/', File.separatorChar);
             file = path.resolve(localName);
-        } else if (!Files.exists(path) && Files.exists(path.getParent()) && Files.isDirectory(path.getParent())) {
-            file = path;
-        } else {
-            throw new IOException("Can not write to " + path);
+        } else if (!status.booleanValue()) {
+            Path    parent=path.getParent();
+
+            status = IoUtils.checkFileExists(parent, options);
+            if (status == null) {
+                throw new AccessDeniedException("Receive directory parent (" + parent + ") existence status cannot be determined for " + path);
+            }
+                
+            if (status.booleanValue() && Files.isDirectory(parent, options)) { 
+                file = path;
+            }
+        }
+
+        if (file == null) {
+            throw new IOException("Cannot write to " + path);
+        }
+
+        status = IoUtils.checkFileExists(file, options);
+        if (status == null) {
+            throw new AccessDeniedException("Receive directory file existence status cannot be determined: " + file);
         }
-        if (!(Files.exists(file) && Files.isDirectory(file))) {
+
+        if (!(status.booleanValue() && Files.isDirectory(file, options))) {
             Files.createDirectory(file);
         }
 
@@ -255,22 +287,45 @@ public class ScpHelper {
             bufSize = MIN_RECEIVE_BUFFER_SIZE;
         }
 
-        Path file;
-        if (Files.exists(path) && Files.isDirectory(path)) {
+        LinkOption[]    options=IoUtils.getLinkOptions(false);
+        Boolean         status=IoUtils.checkFileExists(path, options);
+        if (status == null) {
+            throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
+        }
+
+        Path file=null;
+        if (status.booleanValue() && Files.isDirectory(path, options)) {
             String localName = name.replace('/', File.separatorChar);
             file = path.resolve(localName);
-        } else if (Files.exists(path) && Files.isRegularFile(path)) {
-            file = path;
-        } else if (!Files.exists(path) && Files.exists(path.getParent()) && Files.isDirectory(path.getParent())) {
+        } else if (status.booleanValue() && Files.isRegularFile(path, options)) {
             file = path;
-        } else {
+        } else if (!status.booleanValue()) {
+            Path    parent=path.getParent();
+            
+            status = IoUtils.checkFileExists(parent, options);
+            if (status == null) {
+                throw new AccessDeniedException("Receive file parent (" + parent + ") existence status cannot be determined for " + path);
+            }
+
+            if (status.booleanValue() && Files.isDirectory(parent, options)) {
+                file = path;
+            }
+        }
+        
+        if (file == null) {
             throw new IOException("Can not write to " + path);
         }
+        
+        status = IoUtils.checkFileExists(file, options);
+        if (status == null) {
+            throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
+        }
 
-        if (Files.exists(file)) {
-            if (Files.isDirectory(file)) {
+        if (status.booleanValue()) {
+            if (Files.isDirectory(file, options)) {
                 throw new IOException("File is a directory: " + file);
             }
+
             if (!Files.isWritable(file)) {
                 throw new IOException("Can not write to file: " + file);
             }
@@ -329,6 +384,8 @@ public class ScpHelper {
 
     public void send(List<String> paths, boolean recursive, boolean preserve, int bufferSize) throws IOException {
         readAck(false);
+        
+        LinkOption[]    options=IoUtils.getLinkOptions(false);
         for (String pattern : paths) {
             pattern = pattern.replace('/', File.separatorChar);
 
@@ -343,9 +400,9 @@ public class ScpHelper {
                 String[] included = new DirectoryScanner(basedir, pattern).scan();
                 for (String path : included) {
                     Path file = resolveLocalPath(basedir, path);
-                    if (Files.isRegularFile(file)) {
+                    if (Files.isRegularFile(file, options)) {
                         sendFile(file, preserve, bufferSize);
-                    } else if (Files.isDirectory(file)) {
+                    } else if (Files.isDirectory(file, options)) {
                         if (!recursive) {
                             out.write(ScpHelper.WARNING);
                             out.write((path.toString().replace(File.separatorChar, '/') + " not a regular file\n").getBytes());
@@ -364,13 +421,19 @@ public class ScpHelper {
                     basedir = pattern.substring(0, lastSep);
                     pattern = pattern.substring(lastSep + 1);
                 }
-                Path file = resolveLocalPath(basedir, pattern);
-                if (!Files.exists(file)) {
+
+                Path    file = resolveLocalPath(basedir, pattern);
+                Boolean status = IoUtils.checkFileExists(file, options);
+                if (status == null) {
+                    throw new AccessDeniedException("Send file existence status cannot be determined: " + file);
+                }
+                if (!status.booleanValue()) {
                     throw new IOException(file + ": no such file or directory");
                 }
-                if (Files.isRegularFile(file)) {
+
+                if (Files.isRegularFile(file, options)) {
                     sendFile(file, preserve, bufferSize);
-                } else if (Files.isDirectory(file)) {
+                } else if (Files.isDirectory(file, options)) {
                     if (!recursive) {
                         throw new IOException(file + " not a regular file");
                     } else {
@@ -508,10 +571,11 @@ public class ScpHelper {
             listener.startFolderEvent(FileOperation.SEND, path, perms);
 
             try {
+                LinkOption[]    options = IoUtils.getLinkOptions(false);
                 for (Path child : children) {
-                    if (Files.isRegularFile(child)) {
+                    if (Files.isRegularFile(child, options)) {
                         sendFile(child, preserve, bufferSize);
-                    } else if (Files.isDirectory(child)) {
+                    } else if (Files.isDirectory(child, options)) {
                         sendDir(child, preserve, bufferSize);
                     }
                 }
@@ -542,33 +606,34 @@ public class ScpHelper {
 
         for (PosixFilePermission p : perms) {
             switch (p) {
-            case OWNER_READ:
-                pf |= S_IRUSR;
-                break;
-            case OWNER_WRITE:
-                pf |= S_IWUSR;
-                break;
-            case OWNER_EXECUTE:
-                pf |= S_IXUSR;
-                break;
-            case GROUP_READ:
-                pf |= S_IRGRP;
-                break;
-            case GROUP_WRITE:
-                pf |= S_IWGRP;
-                break;
-            case GROUP_EXECUTE:
-                pf |= S_IXGRP;
-                break;
-            case OTHERS_READ:
-                pf |= S_IROTH;
-                break;
-            case OTHERS_WRITE:
-                pf |= S_IWOTH;
-                break;
-            case OTHERS_EXECUTE:
-                pf |= S_IXOTH;
-                break;
+                case OWNER_READ:
+                    pf |= S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= S_IXOTH;
+                    break;
+                default:    // ignored
             }
         }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
index e62b231..7483542 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
@@ -43,7 +43,6 @@ import java.util.Set;
 public class IoUtils {
 
     public static final LinkOption[] EMPTY_OPTIONS = new LinkOption[0];
-
     private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
 
     public static LinkOption[] getLinkOptions(boolean followLinks) {
@@ -54,8 +53,10 @@ public class IoUtils {
         }
     }
 
+    public static final int DEFAULT_COPY_SIZE=8192;
+
     public static long copy(InputStream source, OutputStream sink) throws IOException {
-        return copy(source, sink, 8192);
+        return copy(source, sink, DEFAULT_COPY_SIZE);
     }
 
     public static long copy(InputStream source, OutputStream sink, int bufferSize) throws IOException {
@@ -191,4 +192,38 @@ public class IoUtils {
         f.setExecutable(executable, false);
     }
 
+    /**
+     * <P>Checks if a file exists - <B>Note:</B> according to the
+     * <A HREF="http://docs.oracle.com/javase/tutorial/essential/io/check.html">Java tutorial - Checking a File or Directory</A>:
+     * </P></BR>
+     * <PRE>
+     *      The methods in the Path class are syntactic, meaning that they operate
+     *      on the Path instance. But eventually you must access the file system
+     *      to verify that a particular Path exists, or does not exist. You can do
+     *      so with the exists(Path, LinkOption...) and the notExists(Path, LinkOption...)
+     *      methods. Note that !Files.exists(path) is not equivalent to Files.notExists(path).
+     *      When you are testing a file's existence, three results are possible:
+     *
+     *      - The file is verified to exist.
+     *      - The file is verified to not exist.
+     *      - The file's status is unknown.
+     *      
+     *      This result can occur when the program does not have access to the file.
+     *      If both exists and notExists return false, the existence of the file cannot
+     *      be verified.
+     * </PRE>
+     * @param path The {@link Path} to be tested
+     * @param options The {@link LinkOption}s to use
+     * @return {@link Boolean#TRUE}/{@link Boolean#FALSE} or {@code null}
+     * according to the file status as explained above
+     */
+    public static Boolean checkFileExists(Path path, LinkOption ... options) {
+        if (Files.exists(path, options)) {
+            return Boolean.TRUE;
+        } else if (Files.notExists(path, options)) {
+            return Boolean.FALSE;
+        } else {
+            return null;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
index 92c043d..218b27d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd.server.sftp;
 
+import static org.apache.sshd.common.sftp.SftpConstants.*;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.EOFException;
@@ -70,6 +72,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 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;
@@ -92,8 +95,6 @@ import org.apache.sshd.server.session.ServerSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.sshd.common.sftp.SftpConstants.*;
-
 /**
  * SFTP subsystem
  *
@@ -163,20 +164,23 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         public void close() throws IOException {
             // ignored
         }
+
+        @Override
+        public String toString() {
+            return Objects.toString(getFile());
+        }
     }
 
     protected static class DirectoryHandle extends Handle implements Iterator<Path> {
-        boolean done;
+        private boolean done;
         // the directory should be read once at "open directory"
-        DirectoryStream<Path> ds;
-        Iterator<Path> fileList = null;
-        int fileIndex;
+        private DirectoryStream<Path> ds;
+        private Iterator<Path> fileList;
 
         public DirectoryHandle(Path file) throws IOException {
             super(file);
             ds = Files.newDirectoryStream(file);
             fileList = ds.iterator();
-            fileIndex = 0;
         }
 
         public boolean isDone() {
@@ -196,7 +200,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
 
         public void remove() {
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException("Not allowed to remove " + toString());
         }
 
         public void clearFileList() {
@@ -798,8 +802,13 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
 
         try {
             if (version < SFTP_V6) {
-                Path p = resolveFile(path).toAbsolutePath().normalize();
-                if (!Files.exists(p, IoUtils.EMPTY_OPTIONS)) {
+                Path f = resolveFile(path);
+                Path abs = f.toAbsolutePath();
+                Path p = abs.normalize();
+                Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_OPTIONS);
+                if (status == null) {
+                    p = handleUnknownRealPathStatus(path, abs, p);
+                } else if (!status.booleanValue()) {
                     throw new FileNotFoundException(p.toString());
                 }
                 sendPath(id, p, Collections.<String, Object>emptyMap());
@@ -837,13 +846,31 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
+    protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
+                break;
+            case ThrowException:
+                throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
+            
+            default:
+                log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
+                       + " - unknown policy: " + unsupportedAttributePolicy);
+        }
+        
+        return absolute;
+    }
+
     protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
         String path = buffer.getString();
         log.debug("Received SSH_FXP_RMDIR (path={})", path);
         // attrs
         try {
             Path p = resolveFile(path);
-            if (Files.isDirectory(p)) {
+            if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
                 Files.delete(p);
                 sendStatus(id, SSH_FX_OK, "");
             } else {
@@ -861,9 +888,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         log.debug("Received SSH_FXP_MKDIR (path={})", path);
         // attrs
         try {
-            Path p = resolveFile(path);
-            if (Files.exists(p)) {
-                if (Files.isDirectory(p)) {
+            Path            p = resolveFile(path);
+            LinkOption[]    options = IoUtils.getLinkOptions(false);
+            Boolean         status = IoUtils.checkFileExists(p, options);
+            if (status == null) {
+                throw new AccessDeniedException("Cannot make-directory existence for " + p);
+            }
+            if (status.booleanValue()) {
+                if (Files.isDirectory(p, options)) {
                     sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
                 } else {
                     sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
@@ -882,10 +914,15 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         String path = buffer.getString();
         log.debug("Received SSH_FXP_REMOVE (path={})", path);
         try {
-            Path p = resolveFile(path);
-            if (!Files.exists(p)) {
+            Path            p = resolveFile(path);
+            LinkOption[]    options = IoUtils.getLinkOptions(false);
+            Boolean         status = IoUtils.checkFileExists(p, options);
+            if (status == null) {
+                throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
+            }
+            if (!status.booleanValue()) {
                 sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-            } else if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
+            } else if (Files.isDirectory(p, options)) {
                 sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
             } else {
                 Files.delete(p);
@@ -899,18 +936,31 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     protected void doReadDir(Buffer buffer, int id) throws IOException {
         String handle = buffer.getString();
         log.debug("Received SSH_FXP_READDIR (handle={})", handle);
+        Handle p = handles.get(handle);
         try {
-            Handle p = handles.get(handle);
             if (!(p instanceof DirectoryHandle)) {
                 sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
-            } else if (((DirectoryHandle) p).isDone()) {
+                return;
+            }
+            
+            if (((DirectoryHandle) p).isDone()) {
                 sendStatus(id, SSH_FX_EOF, "", "");
-            } else if (!Files.exists(p.getFile())) {
-                sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().toString());
-            } else if (!Files.isDirectory(p.getFile())) {
-                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, p.getFile().toString());
-            } else if (!Files.isReadable(p.getFile())) {
-                sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().toString());
+                return;
+            }
+
+            Path            file = p.getFile();
+            LinkOption[]    options = IoUtils.getLinkOptions(false);
+            Boolean         status = IoUtils.checkFileExists(file, options);
+            if (status == null) {
+                throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
+            }
+
+            if (!status.booleanValue()) {
+                sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
+            } else if (!Files.isDirectory(file, options)) {
+                sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
+            } else if (!Files.isReadable(file)) {
+                sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
             } else {
                 DirectoryHandle dh = (DirectoryHandle) p;
                 if (dh.hasNext()) {
@@ -939,10 +989,16 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         String path = buffer.getString();
         log.debug("Received SSH_FXP_OPENDIR (path={})", path);
         try {
-            Path p = resolveFile(path);
-            if (!Files.exists(p)) {
+            Path            p = resolveFile(path);
+            LinkOption[]    options = IoUtils.getLinkOptions(false);
+            Boolean         status = IoUtils.checkFileExists(p, options);
+            if (status == null) {
+                throw new AccessDeniedException("Cannot determine open-dir existence of " + p);
+            }
+
+            if (!status.booleanValue()) {
                 sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
-            } else if (!Files.isDirectory(p)) {
+            } else if (!Files.isDirectory(p, options)) {
                 sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
             } else if (!Files.isReadable(p)) {
                 sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
@@ -1314,15 +1370,25 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         int wpos = buffer.wpos();
         buffer.putInt(0);
         int nb = 0;
-        while (files.hasNext() && buffer.wpos() < MAX_PACKET_LENGTH) {
-            Path f = files.next();
-            buffer.putString(getShortName(f), StandardCharsets.UTF_8);
+        while (files.hasNext() && (buffer.wpos() < MAX_PACKET_LENGTH)) {
+            Path    f = files.next();
+            String  shortName = getShortName(f);
+            buffer.putString(shortName, StandardCharsets.UTF_8);
             if (version == SFTP_V3) {
-                buffer.putString(getLongName(f), StandardCharsets.UTF_8); // Format specified in the specs
+                String  longName = getLongName(f);
+                buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
+                if (log.isTraceEnabled()) {
+                    log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName + " [" + longName + "]");
+                }
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName);
+                }
             }
             writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
             nb++;
         }
+
         int oldpos = buffer.wpos();
         buffer.wpos(wpos);
         buffer.putInt(nb);
@@ -1475,11 +1541,17 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     }
 
     protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
-        LinkOption[] options = IoUtils.getLinkOptions(followLinks);
-        if (!Files.exists(file, options)) {
+        LinkOption[]    options = IoUtils.getLinkOptions(followLinks);
+        Boolean         status = IoUtils.checkFileExists(file, options);
+        Map<String, Object> attributes;
+        if (status == null) {
+            attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
+        } else if (!status.booleanValue()) {
             throw new FileNotFoundException(file.toString());
+        } else {
+            attributes = getAttributes(file, flags, followLinks);
         }
-        Map<String, Object> attributes = getAttributes(file, flags, followLinks);
+
         writeAttrs(buffer, attributes);
     }
 
@@ -1571,27 +1643,82 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
     }
 
+    public static final List<String>    DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
+
+    protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case ThrowException:
+                throw new AccessDeniedException("Cannot determine existence for attributes of " + file);
+            case Warn:
+                log.warn("handleUnknownStatusFileAttributes(" + file + ") cannot determine existence");
+                break;
+            default:
+                log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
+        }
+        
+        return getAttributes(file, flags, followLinks);
+    }
+
     protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
-        FileSystem fs = file.getFileSystem();
-        Collection<String> views = fs.supportedFileAttributeViews();
-        LinkOption[] opts = IoUtils.getLinkOptions(followLinks);
-        // TODO: support flags
-        if (views.contains("unix")) {
-            return Files.readAttributes(file, "unix:*", opts);
+        FileSystem          fs=file.getFileSystem();
+        Collection<String>  supportedViews=fs.supportedFileAttributeViews();
+        LinkOption[]        opts=IoUtils.getLinkOptions(followLinks);
+        Map<String,Object>  attrs=new HashMap<>();
+        Collection<String>  views;
+
+        if (GenericUtils.isEmpty(supportedViews)) {
+            views = Collections.<String>emptyList();
+        } else if (supportedViews.contains("unix")) {
+            views = DEFAULT_UNIX_VIEW;
         } else {
-            Map<String, Object> a = new HashMap<>();
-            for (String view : views) {
-                Map<String, Object> ta = Files.readAttributes(file, view + ":*", opts);
-                a.putAll(ta);
+            views = new ArrayList<String>(supportedViews.size());
+            for (String v : supportedViews) {
+                views.add(v + ":*");
             }
-            if (OsUtils.isWin32() && (!a.containsKey("permissions"))) {
-                Set<PosixFilePermission> perms = IoUtils.getPermissionsFromFile(file.toFile());
-                a.put("permissions", perms);
-            }
-            return a;
+        }
+
+        for (String v : views) {
+            Map<String, Object> ta=readFileAttributes(file, v, opts);
+            attrs.putAll(ta);
+        }
+
+        // if did not get permissions from the supported views return a best approximation
+        if (!attrs.containsKey("permissions")) {
+            Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
+            attrs.put("permissions", perms);
+        }
+
+        return attrs;
+    }
+
+    protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
+        try {
+            return Files.readAttributes(file, view, opts);
+        } catch(IOException e) {
+            return handleReadFileAttributesException(file, view, opts, e);
         }
     }
 
+    protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
+        switch(unsupportedAttributePolicy) {
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
+                break;
+            case ThrowException:
+                throw e;
+            default:
+                log.warn("handleReadFileAttributesException(" + file + ")[" + view + "]"
+                       + " Unknown policy (" + unsupportedAttributePolicy + ")"
+                       + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
+        }
+        
+        return Collections.emptyMap();
+    }
+
     protected void setAttributes(Path file, Map<String, Object>  attributes) throws IOException {
         Set<String> unsupported = new HashSet<>();
         for (String attribute : attributes.keySet()) {
@@ -1645,15 +1772,15 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 sb.append(attr);
             }
             switch (unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case Warn:
-                log.warn("Unsupported attributes: " + sb.toString());
-                break;
-            case ThrowException:
-                throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
-            default:
-                log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
+                case Ignore:
+                    break;
+                case Warn:
+                    log.warn("Unsupported attributes: " + sb.toString());
+                    break;
+                case ThrowException:
+                    throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
+                default:
+                    log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
             }
         }
     }
@@ -1682,23 +1809,23 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
     }
 
-    private void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
+    protected void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
         /* According to Javadoc:
          * 
          *      "Where an implementation does not support any notion of group
          *      or user then this method always throws UserPrincipalNotFoundException."
          */
         switch (unsupportedAttributePolicy) {
-        case Ignore:
-            break;
-        case Warn:
-            log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
-                   + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
-            break;
-        case ThrowException:
-            throw e;
-        default:
-            log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
+            case Ignore:
+                break;
+            case Warn:
+                log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
+                       + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
+                break;
+            case ThrowException:
+                throw e;
+            default:
+                log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
         }
     }
 
@@ -2088,74 +2215,57 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         return sb.toString();
     }
 
-    protected static class DefaultUserPrincipal implements UserPrincipal {
-
+    protected static class PrincipalBase implements Principal {
         private final String name;
 
-        public DefaultUserPrincipal(String name) {
+        public PrincipalBase(String name) {
             if (name == null) {
                 throw new IllegalArgumentException("name is null");
             }
             this.name = name;
         }
 
-        public String getName() {
+        public final String getName() {
             return name;
         }
 
         @Override
         public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            DefaultGroupPrincipal that = (DefaultGroupPrincipal) o;
-            if (!name.equals(that.name)) return false;
-            return true;
+            if (this == o) {
+                return true;
+            }
+            if ((o == null) || (getClass() != o.getClass())) {
+                return false;
+            }
+
+            Principal that = (Principal) o;
+            if (Objects.equals(getName(),that.getName())) {
+                return true;
+            } else {
+                return false;    // debug breakpoint
+            }
         }
 
         @Override
         public int hashCode() {
-            return name.hashCode();
+            return Objects.hashCode(getName());
         }
 
         @Override
         public String toString() {
-            return name;
+            return getName();
         }
     }
 
-    protected static class DefaultGroupPrincipal implements GroupPrincipal {
-
-        private final String name;
-
-        public DefaultGroupPrincipal(String name) {
-            if (name == null) {
-                throw new IllegalArgumentException("name is null");
-            }
-            this.name = name;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            DefaultGroupPrincipal that = (DefaultGroupPrincipal) o;
-            if (!name.equals(that.name)) return false;
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            return name.hashCode();
+    protected static class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
+        public DefaultUserPrincipal(String name) {
+            super(name);
         }
+    }
 
-        @Override
-        public String toString() {
-            return name;
+    protected static class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
+        public DefaultGroupPrincipal(String name) {
+            super(name);
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/cccec812/sshd-core/src/test/java/org/apache/sshd/ServerMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ServerMain.java b/sshd-core/src/test/java/org/apache/sshd/ServerMain.java
new file mode 100644
index 0000000..41070a5
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/ServerMain.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;
+
+
+/**
+ * An activator for the {@link SshServer#main(String[])} - the reason it is
+ * here is because the logging configuration is available only for test scope
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ServerMain {
+    public static void main(String[] args) throws Throwable {
+        SshServer.main(args);
+    }
+}