Repository: mina-sshd Updated Branches: refs/heads/master 3b3d1159f -> 080c52878
[SSHD-733] SSHD server displays file symlinks instead of dir symlinks Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/080c5287 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/080c5287 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/080c5287 Branch: refs/heads/master Commit: 080c52878cd5a48e34517805a42a9ea58f8c5f02 Parents: 3b3d115 Author: Lyor Goldstein <lyor.goldst...@gmail.com> Authored: Wed Mar 29 20:46:45 2017 +0300 Committer: Lyor Goldstein <lyor.goldst...@gmail.com> Committed: Wed Mar 29 20:47:23 2017 +0300 ---------------------------------------------------------------------- .../sshd/common/subsystem/sftp/SftpHelper.java | 4 + .../server/subsystem/sftp/SftpSubsystem.java | 100 ++++++++++++++----- 2 files changed, 77 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/080c5287/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java index de4c7c3..8023c1b 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.AccessDeniedException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystemLoopException; import java.nio.file.InvalidPathException; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; @@ -434,6 +435,7 @@ public final class SftpHelper { * @param t The thrown {@link Throwable} * @return The matching sub-status */ + @SuppressWarnings("checkstyle:ReturnCount") public static int resolveSubstatus(Throwable t) { if ((t instanceof NoSuchFileException) || (t instanceof FileNotFoundException)) { return SftpConstants.SSH_FX_NO_SUCH_FILE; @@ -461,6 +463,8 @@ public final class SftpHelper { return SftpConstants.SSH_FX_OP_UNSUPPORTED; } else if (t instanceof UserPrincipalNotFoundException) { return SftpConstants.SSH_FX_UNKNOWN_PRINCIPAL; + } else if (t instanceof FileSystemLoopException) { + return SftpConstants.SSH_FX_LINK_LOOP; } else if (t instanceof SftpException) { return ((SftpException) t).getStatus(); } else { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/080c5287/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 c10bcce..fcdb4ad 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 @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -267,6 +268,17 @@ public class SftpSubsystem .put("lastModifiedTime", FileInfoExtractor.LASTMODIFIED) .immutable(); + /** + * Whether to automatically follow symbolic links when resolving paths + * @see #DEFAULT_AUTO_FOLLOW_LINKS + */ + public static final String AUTO_FOLLOW_LINKS = "sftp-auto-follow-links"; + + /** + * Default value of {@value #AUTO_FOLLOW_LINKS} + */ + public static final boolean DEFAULT_AUTO_FOLLOW_LINKS = true; + protected ExitCallback callback; protected InputStream in; protected OutputStream out; @@ -905,7 +917,9 @@ public class SftpSubsystem send(buffer); } - protected byte[] doMD5Hash(int id, String targetType, String target, long startOffset, long length, byte[] quickCheckHash) throws Exception { + protected byte[] doMD5Hash( + int id, String targetType, String target, long startOffset, long length, byte[] quickCheckHash) + throws Exception { if (log.isDebugEnabled()) { log.debug("doMD5Hash({})({})[{}] offset={}, length={}, quick-hash={}", getServerSession(), targetType, target, startOffset, length, @@ -931,7 +945,7 @@ public class SftpSubsystem } } else { path = resolveFile(target); - if (Files.isDirectory(path, IoUtils.getLinkOptions(false))) { + if (Files.isDirectory(path, IoUtils.getLinkOptions(true))) { throw new NotDirectoryException(path.toString()); } } @@ -1532,8 +1546,13 @@ public class SftpSubsystem log.debug("doStat({})[id={}] SSH_FXP_STAT (path={}, flags=0x{})", getServerSession(), id, path, Integer.toHexString(flags)); } + + /* + * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT + * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not. + */ Path p = resolveFile(path); - return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false)); + return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(true)); } protected void doRealPath(Buffer buffer, int id) throws IOException { @@ -1549,7 +1568,6 @@ public class SftpSubsystem Map<String, ?> attrs = Collections.emptyMap(); Pair<Path, Boolean> result; try { - LinkOption[] options = IoUtils.getLinkOptions(false); if (version < SftpConstants.SFTP_V6) { /* * See http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt: @@ -1559,7 +1577,10 @@ public class SftpSubsystem * * See also SSHD-294 */ - result = doRealPathV345(id, path, options); + Path p = resolveFile(path); + LinkOption[] options = + getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); + result = doRealPathV345(id, path, p, options); } else { /* * See https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 @@ -1581,9 +1602,13 @@ public class SftpSubsystem extraPaths.add(buffer.getString()); } - result = doRealPathV6(id, path, extraPaths, options); + Path p = resolveFile(path); + LinkOption[] options = + getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); + result = doRealPathV6(id, path, extraPaths, p, options); - Path p = result.getFirst(); + p = result.getFirst(); + options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); Boolean status = result.getSecond(); switch (control) { case SftpConstants.SSH_FXP_REALPATH_STAT_IF: @@ -1591,7 +1616,7 @@ public class SftpSubsystem attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options); } else if (status) { try { - attrs = getAttributes(p, IoUtils.getLinkOptions(false)); + attrs = getAttributes(p, options); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("doRealPath({}) - failed ({}) to retrieve attributes of {}: {}", @@ -1631,8 +1656,8 @@ public class SftpSubsystem sendPath(BufferUtils.clear(buffer), id, result.getFirst(), attrs); } - protected Pair<Path, Boolean> doRealPathV6(int id, String path, Collection<String> extraPaths, LinkOption... options) throws IOException { - Path p = resolveFile(path); + protected Pair<Path, Boolean> doRealPathV6( + int id, String path, Collection<String> extraPaths, Path p, LinkOption... options) throws IOException { int numExtra = GenericUtils.size(extraPaths); if (numExtra > 0) { if (log.isDebugEnabled()) { @@ -1644,6 +1669,7 @@ public class SftpSubsystem for (String p2 : extraPaths) { p = p.resolve(p2); + options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); sb.append('/').append(p2); } @@ -1653,8 +1679,8 @@ public class SftpSubsystem return validateRealPath(id, path, p, options); } - protected Pair<Path, Boolean> doRealPathV345(int id, String path, LinkOption... options) throws IOException { - return validateRealPath(id, path, resolveFile(path), options); + protected Pair<Path, Boolean> doRealPathV345(int id, String path, Path p, LinkOption... options) throws IOException { + return validateRealPath(id, path, p, options); } /** @@ -1767,6 +1793,10 @@ public class SftpSubsystem protected void doRemove(Buffer buffer, int id) throws IOException { String path = buffer.getString(); try { + /* + * If 'filename' is a symbolic link, the link is removed, + * not the file it points to. + */ doRemove(id, path, IoUtils.getLinkOptions(false)); } catch (IOException | RuntimeException e) { sendStatus(BufferUtils.clear(buffer), id, e); @@ -1813,7 +1843,8 @@ public class SftpSubsystem } Path file = dh.getFile(); - LinkOption[] options = IoUtils.getLinkOptions(false); + LinkOption[] options = + getPathResolutionLinkOption(SftpConstants.SSH_FXP_READDIR, "", file); Boolean status = IoUtils.checkFileExists(file, options); if (status == null) { throw new AccessDeniedException("Cannot determine existence of read-dir for " + file); @@ -1840,14 +1871,16 @@ public class SftpSubsystem reply.putInt(0); ServerSession session = getServerSession(); - int maxDataSize = PropertyResolverUtils.getIntProperty(session, MAX_READDIR_DATA_SIZE_PROP, DEFAULT_MAX_READDIR_DATA_SIZE); - int count = doReadDir(id, handle, dh, reply, maxDataSize); + int maxDataSize = + PropertyResolverUtils.getIntProperty(session, MAX_READDIR_DATA_SIZE_PROP, DEFAULT_MAX_READDIR_DATA_SIZE); + int count = doReadDir(id, handle, dh, reply, maxDataSize, options); BufferUtils.updateLengthPlaceholder(reply, lenPos, count); if ((!dh.isSendDot()) && (!dh.isSendDotDot()) && (!dh.hasNext())) { dh.markDone(); } - Boolean indicator = SftpHelper.indicateEndOfNamesList(reply, getVersion(), session, dh.isDone()); + Boolean indicator = + SftpHelper.indicateEndOfNamesList(reply, getVersion(), session, dh.isDone()); if (log.isDebugEnabled()) { log.debug("doReadDir({})({})[{}] - seding {} entries - eol={}", session, handle, h, count, indicator); } @@ -1872,7 +1905,15 @@ public class SftpSubsystem String handle; try { - handle = doOpenDir(id, path, IoUtils.getLinkOptions(false)); + Path p = resolveNormalizedLocation(path); + if (log.isDebugEnabled()) { + log.debug("doOpenDir({})[id={}] SSH_FXP_OPENDIR (path={})[{}]", + getServerSession(), id, path, p); + } + + LinkOption[] options = + getPathResolutionLinkOption(SftpConstants.SSH_FXP_OPENDIR, "", p); + handle = doOpenDir(id, path, p, options); } catch (IOException | RuntimeException e) { sendStatus(BufferUtils.clear(buffer), id, e); return; @@ -1881,13 +1922,7 @@ public class SftpSubsystem sendHandle(BufferUtils.clear(buffer), id, handle); } - protected String doOpenDir(int id, String path, LinkOption... options) throws IOException { - Path p = resolveNormalizedLocation(path); - if (log.isDebugEnabled()) { - log.debug("doOpenDir({})[id={}] SSH_FXP_OPENDIR (path={})[{}]", - getServerSession(), id, path, p); - } - + protected String doOpenDir(int id, String path, Path p, LinkOption... options) throws IOException { Boolean status = IoUtils.checkFileExists(p, options); if (status == null) { throw new AccessDeniedException("Cannot determine open-dir existence for " + p); @@ -2005,6 +2040,10 @@ public class SftpSubsystem getServerSession(), id, path, p, Integer.toHexString(flags)); } + /* + * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT + * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not. + */ return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false)); } @@ -2670,13 +2709,14 @@ public class SftpSubsystem * @param dir The {@link DirectoryHandle} * @param buffer The {@link Buffer} to write the results * @param maxSize Max. buffer size + * @param options The {@link LinkOption}-s to use when querying the directory contents * @return Number of written entries * @throws IOException If failed to generate an entry */ - protected int doReadDir(int id, String handle, DirectoryHandle dir, Buffer buffer, int maxSize) throws IOException { + protected int doReadDir( + int id, String handle, DirectoryHandle dir, Buffer buffer, int maxSize, LinkOption ... options) throws IOException { int nb = 0; - LinkOption[] options = IoUtils.getLinkOptions(false); - Map<String, Path> entries = new TreeMap<>(); + Map<String, Path> entries = new TreeMap<>(Comparator.naturalOrder()); while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && (buffer.wpos() < maxSize)) { if (dir.isSendDot()) { writeDirEntry(id, dir, entries, buffer, nb, dir.getFile(), ".", options); @@ -2994,6 +3034,12 @@ public class SftpSubsystem listener.modifiedAttributes(session, file, attributes, null); } + protected LinkOption[] getPathResolutionLinkOption(int cmd, String extension, Path path) throws IOException { + ServerSession session = getServerSession(); + boolean followLinks = PropertyResolverUtils.getBooleanProperty(session, AUTO_FOLLOW_LINKS, DEFAULT_AUTO_FOLLOW_LINKS); + return IoUtils.getLinkOptions(followLinks); + } + protected void setFileAttributes(Path file, Map<String, ?> attributes, LinkOption... options) throws IOException { Set<String> unsupported = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); // Cannot use forEach because of the potential IOException being thrown