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);
+ }
+}