Fix SFTP subsystem support for links Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/ca990bdb Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/ca990bdb Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/ca990bdb
Branch: refs/heads/master Commit: ca990bdbcdf7894b95f5bbc3e65b5239f34e2a6b Parents: 21c1cee Author: Guillaume Nodet <[email protected]> Authored: Mon Jul 22 18:22:52 2013 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Mon Jul 22 18:22:52 2013 +0200 ---------------------------------------------------------------------- .../java/org/apache/sshd/client/SftpClient.java | 18 +- .../sshd/client/sftp/DefaultSftpClient.java | 1 + .../org/apache/sshd/common/file/SshFile.java | 27 ++- .../file/nativefs/NativeFileSystemView.java | 2 +- .../common/file/nativefs/NativeSshFile.java | 88 ++++---- .../apache/sshd/server/sftp/SftpSubsystem.java | 203 +++++++++++++++---- 6 files changed, 232 insertions(+), 107 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java index 5150a81..99a7cd2 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java @@ -28,6 +28,9 @@ import java.util.EnumSet; */ public interface SftpClient { + // + // Permission flags + // int S_IFMT = 0170000; // bitmask for the file type bitfields int S_IFSOCK = 0140000; // socket int S_IFLNK = 0120000; // symbolic link @@ -102,21 +105,12 @@ public interface SftpClient { this.mtime = mtime; return this; } - public boolean isDirectory() { - return (perms & S_IFMT) == S_IFDIR; - } - public boolean isRegularFile() { - return (perms & S_IFMT) == S_IFREG; - } - public boolean isSymlink() { - return (perms & S_IFMT) == S_IFLNK; - } } public static class DirEntry { - public final String filename; - public final String longFilename; - public final Attributes attributes; + public String filename; + public String longFilename; + public Attributes attributes; public DirEntry(String filename, String longFilename, Attributes attributes) { this.filename = filename; this.longFilename = longFilename; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java index 1dd6351..da7645e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java @@ -405,6 +405,7 @@ public class DefaultSftpClient implements SftpClient { } } buffer.putInt(mode); + buffer.putInt(0); return checkHandle(receive(send(SSH_FXP_OPEN, buffer))); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java index 1da1748..0b204c8 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/file/SshFile.java @@ -33,6 +33,9 @@ import java.util.Set; */ public interface SshFile { + // + // File attributes + // enum Attribute { Size, // long Uid, // int @@ -42,12 +45,28 @@ public interface SshFile { IsDirectory, // boolean IsRegularFile, // boolean IsSymbolicLink, // boolean - Permissions, // int + Permissions, // EnumSet<Permission> CreationTime, // long LastModifiedTime, // long LastAccessTime // long } + // + // File permissions + // + enum Permission { + UserRead, + UserWrite, + UserExecute, + GroupRead, + GroupWrite, + GroupExecute, + OthersRead, + OthersWrite, + OthersExecute + } + + /** * Get the full path from the base directory of the FileSystemView. * @return a path where the path separator is '/' (even if the operating system @@ -61,14 +80,16 @@ public interface SshFile { */ String getName(); - Map<Attribute,Object> getAttributes() throws IOException; + Map<Attribute,Object> getAttributes(boolean followLinks) throws IOException; void setAttributes(Map<Attribute, Object> attributes) throws IOException; - Object getAttribute(Attribute attribute) throws IOException; + Object getAttribute(Attribute attribute, boolean followLinks) throws IOException; void setAttribute(Attribute attribute, Object value) throws IOException; + String readSymbolicLink() throws IOException; + /** * Get the owner name of the file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java index 9ba8994..dbf07bc 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemView.java @@ -113,7 +113,7 @@ public class NativeFileSystemView implements FileSystemView { return System.getProperty("user.dir"); } - public NativeSshFile createNativeSshFile(final String fileName2, final File fileObj, final String userName2) { + public NativeSshFile createNativeSshFile(final String fileName2, final File fileObj, final String userName2) { return new NativeSshFile(this, fileName2, fileObj, userName2); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java index ee7cd6e..2366706 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java @@ -27,9 +27,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.lang.reflect.Method; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; +import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.PosixFilePermission; @@ -38,6 +38,7 @@ import java.nio.file.attribute.UserPrincipalLookupService; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -604,8 +605,11 @@ public class NativeSshFile implements SshFile { return fileName; } - public Map<Attribute, Object> getAttributes() throws IOException { - Map<String, Object> a = Files.readAttributes(file.toPath(), "unix:size,uid,owner,gid,group,isDirectory,isRegularFile,isSymbolicLink,permissions,creationTime,lastModifiedTime,lastAccessTime", LinkOption.NOFOLLOW_LINKS); + public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException { + Map<String, Object> a = Files.readAttributes( + file.toPath(), + "unix:size,uid,owner,gid,group,isDirectory,isRegularFile,isSymbolicLink,permissions,creationTime,lastModifiedTime,lastAccessTime", + followLinks ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS }); Map<Attribute, Object> map = new HashMap<Attribute, Object>(); map.put(Attribute.Size, a.get("size")); map.put(Attribute.Uid, a.get("uid")); @@ -622,19 +626,19 @@ public class NativeSshFile implements SshFile { return map; } - private int fromPerms(Set<PosixFilePermission> perms) { - int p = 0; + private EnumSet<Permission> fromPerms(Set<PosixFilePermission> perms) { + EnumSet<Permission> p = EnumSet.noneOf(Permission.class); for (PosixFilePermission perm : perms) { switch (perm) { - case OWNER_READ: p |= 0000400; break; - case OWNER_WRITE: p |= 0000200; break; - case OWNER_EXECUTE: p |= 0000100; break; - case GROUP_READ: p |= 0000040; break; - case GROUP_WRITE: p |= 0000020; break; - case GROUP_EXECUTE: p |= 0000010; break; - case OTHERS_READ: p |= 0000004; break; - case OTHERS_WRITE: p |= 0000002; break; - case OTHERS_EXECUTE: p |= 0000001; break; + case OWNER_READ: p.add(Permission.UserRead); break; + case OWNER_WRITE: p.add(Permission.UserWrite); break; + case OWNER_EXECUTE: p.add(Permission.UserExecute); break; + case GROUP_READ: p.add(Permission.GroupRead); break; + case GROUP_WRITE: p.add(Permission.GroupWrite); break; + case GROUP_EXECUTE: p.add(Permission.GroupExecute); break; + case OTHERS_READ: p.add(Permission.OthersRead); break; + case OTHERS_WRITE: p.add(Permission.OthersWrite); break; + case OTHERS_EXECUTE: p.add(Permission.OthersExecute); break; } } return p; @@ -652,7 +656,7 @@ public class NativeSshFile implements SshFile { case CreationTime: name = "unix:creationTime"; value = FileTime.fromMillis((Long) value); break; case LastModifiedTime: name = "unix:lastModifiedTime"; value = FileTime.fromMillis((Long) value); break; case LastAccessTime: name = "unix:lastAccessTime"; value = FileTime.fromMillis((Long) value); break; - case Permissions: name = "unix:permissions"; value = toPerms((Integer) value); break; + case Permissions: name = "unix:permissions"; value = toPerms((EnumSet<Permission>) value); break; } if (name != null && value != null) { Files.setAttribute(file.toPath(), name, value, LinkOption.NOFOLLOW_LINKS); @@ -670,40 +674,26 @@ public class NativeSshFile implements SshFile { return lookupService.lookupPrincipalByName(name); } - private Set<PosixFilePermission> toPerms(int perms) { - Set<PosixFilePermission> p = new HashSet<PosixFilePermission>(); - if ((perms & 0000400) != 0) { - p.add(PosixFilePermission.OWNER_READ); - } - if ((perms & 0000200) != 0) { - p.add(PosixFilePermission.OWNER_WRITE); - } - if ((perms & 0000100) != 0) { - p.add(PosixFilePermission.OWNER_EXECUTE); - } - if ((perms & 0000040) != 0) { - p.add(PosixFilePermission.GROUP_READ); - } - if ((perms & 0000020) != 0) { - p.add(PosixFilePermission.GROUP_WRITE); - } - if ((perms & 0000010) != 0) { - p.add(PosixFilePermission.GROUP_EXECUTE); - } - if ((perms & 0000004) != 0) { - p.add(PosixFilePermission.OTHERS_READ); - } - if ((perms & 0000002) != 0) { - p.add(PosixFilePermission.OTHERS_WRITE); - } - if ((perms & 0000001) != 0) { - p.add(PosixFilePermission.OTHERS_EXECUTE); + private Set<PosixFilePermission> toPerms(EnumSet<Permission> perms) { + Set<PosixFilePermission> set = new HashSet<PosixFilePermission>(); + for (Permission p : perms) { + switch (p) { + case UserRead: set.add(PosixFilePermission.OWNER_READ); break; + case UserWrite: set.add(PosixFilePermission.OWNER_WRITE); break; + case UserExecute: set.add(PosixFilePermission.OWNER_EXECUTE); break; + case GroupRead: set.add(PosixFilePermission.GROUP_READ); break; + case GroupWrite: set.add(PosixFilePermission.GROUP_WRITE); break; + case GroupExecute: set.add(PosixFilePermission.GROUP_EXECUTE); break; + case OthersRead: set.add(PosixFilePermission.OTHERS_READ); break; + case OthersWrite: set.add(PosixFilePermission.OTHERS_WRITE); break; + case OthersExecute: set.add(PosixFilePermission.OTHERS_EXECUTE); break; + } } - return p; + return set; } - public Object getAttribute(Attribute attribute) throws IOException { - return getAttributes().get(attribute); + public Object getAttribute(Attribute attribute, boolean followLinks) throws IOException { + return getAttributes(followLinks).get(attribute); } public void setAttribute(Attribute attribute, Object value) throws IOException { @@ -711,4 +701,10 @@ public class NativeSshFile implements SshFile { map.put(attribute, value); setAttributes(map); } + + public String readSymbolicLink() throws IOException { + Path path = file.toPath(); + Path link = Files.readSymbolicLink(path); + return link.toString(); + } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ca990bdb/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 39f80f9..c2c5b92 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 @@ -25,9 +25,9 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; @@ -228,22 +228,16 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } protected static class FileHandle extends Handle { - int flags; OutputStream output; long outputPos; InputStream input; long inputPos; long length; - public FileHandle(SshFile sshFile, int flags) { + public FileHandle(SshFile sshFile) { super(sshFile); - this.flags = flags; } - public int getFlags() { - return flags; - } - public int read(byte[] data, long offset) throws IOException { if (input != null && offset >= length) { return -1; @@ -374,6 +368,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste int id = buffer.getInt(); switch (type) { case SSH_FXP_INIT: { + log.debug("Received SSH_FXP_INIT (version={})", version); if (length != 5) { throw new IllegalArgumentException(); } @@ -405,7 +400,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste String path = buffer.getString(); int pflags = buffer.getInt(); - // attrs + Map<SshFile.Attribute, Object> attrs = readAttrs(buffer); + log.debug("Received SSH_FXP_OPEN (path={}, pflags={}, attrs={})", new Object[] { path, pflags, attrs }); try { SshFile file = resolveFile(path); if (file.doesExist()) { @@ -427,8 +423,9 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste if ((pflags & SSH_FXF_TRUNC) != 0) { file.truncate(); } + file.setAttributes(attrs); String handle = UUID.randomUUID().toString(); - handles.put(handle, new FileHandle(file, pflags)); // handle flags conversion + handles.put(handle, new FileHandle(file)); sendHandle(id, handle); } catch (IOException e) { sendStatus(id, SSH_FX_FAILURE, e.getMessage() == null ? "" : e.getMessage()); @@ -437,6 +434,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_CLOSE: { String handle = buffer.getString(); + log.debug("Received SSH_FXP_CLOSE (handle={})", handle); try { Handle h = handles.get(handle); if (h == null) { @@ -455,6 +453,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste String handle = buffer.getString(); long offset = buffer.getLong(); int len = buffer.getInt(); + log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})", new Object[] { handle, offset, len }); try { Handle p = handles.get(handle); if (!(p instanceof FileHandle)) { @@ -482,6 +481,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste String handle = buffer.getString(); long offset = buffer.getLong(); byte[] data = buffer.getBytes(); + log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=...)", handle, offset); try { Handle p = handles.get(handle); if (!(p instanceof FileHandle)) { @@ -500,12 +500,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } break; } - case SSH_FXP_LSTAT: - case SSH_FXP_STAT: { + case SSH_FXP_LSTAT: { String path = buffer.getString(); + log.debug("Received SSH_FXP_LSTAT (path={})", path); try { SshFile p = resolveFile(path); - sendAttrs(id, p); + sendAttrs(id, p, false); } catch (FileNotFoundException e) { sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); } catch (IOException e) { @@ -515,12 +515,47 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_FSTAT: { String handle = buffer.getString(); + log.debug("Received SSH_FXP_FSTAT (handle={})", handle); try { Handle p = handles.get(handle); if (p == null) { sendStatus(id, SSH_FX_FAILURE, handle); } else { - sendAttrs(id, p.getFile()); + sendAttrs(id, p.getFile(), true); + } + } catch (FileNotFoundException e) { + sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); + } catch (IOException e) { + sendStatus(id, SSH_FX_FAILURE, e.getMessage()); + } + break; + } + case SSH_FXP_SETSTAT: { + String path = buffer.getString(); + Map<SshFile.Attribute, Object> attrs = readAttrs(buffer); + log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs); + try { + SshFile p = resolveFile(path); + p.setAttributes(attrs); + sendStatus(id, SSH_FX_OK, ""); + } catch (FileNotFoundException e) { + sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); + } catch (IOException e) { + sendStatus(id, SSH_FX_FAILURE, e.getMessage()); + } + break; + } + case SSH_FXP_FSETSTAT: { + String handle = buffer.getString(); + Map<SshFile.Attribute, Object> attrs = readAttrs(buffer); + log.debug("Received SSH_FXP_FSETSTAT (handle={}, attrs={})", handle, attrs); + try { + Handle p = handles.get(handle); + if (p == null) { + sendStatus(id, SSH_FX_FAILURE, handle); + } else { + p.getFile().setAttributes(attrs); + sendStatus(id, SSH_FX_OK, ""); } } catch (FileNotFoundException e) { sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); @@ -531,6 +566,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_OPENDIR: { String path = buffer.getString(); + log.debug("Received SSH_FXP_OPENDIR (path={})", path); try { SshFile p = resolveFile(path); if (!p.doesExist()) { @@ -551,6 +587,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_READDIR: { String handle = buffer.getString(); + log.debug("Received SSH_FXP_READDIR (handle={})", handle); try { Handle p = handles.get(handle); if (!(p instanceof DirectoryHandle)) { @@ -589,6 +626,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_REMOVE: { String path = buffer.getString(); + log.debug("Received SSH_FXP_REMOVE (path={})", path); try { SshFile p = resolveFile(path); if (!p.doesExist()) { @@ -607,6 +645,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_MKDIR: { String path = buffer.getString(); + log.debug("Received SSH_FXP_MKDIR (path={})", path); // attrs try { SshFile p = resolveFile(path); @@ -630,6 +669,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_RMDIR: { String path = buffer.getString(); + log.debug("Received SSH_FXP_RMDIR (path={})", path); // attrs try { SshFile p = resolveFile(path); @@ -657,6 +697,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } case SSH_FXP_REALPATH: { String path = buffer.getString(); + log.debug("Received SSH_FXP_REALPATH (path={})", path); if (path.trim().length() == 0) { path = "."; } @@ -672,9 +713,23 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } break; } + case SSH_FXP_STAT: { + String path = buffer.getString(); + log.debug("Received SSH_FXP_STAT (path={})", path); + try { + SshFile p = resolveFile(path); + sendAttrs(id, p, true); + } catch (FileNotFoundException e) { + sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage()); + } catch (IOException e) { + sendStatus(id, SSH_FX_FAILURE, e.getMessage()); + } + break; + } case SSH_FXP_RENAME: { String oldPath = buffer.getString(); String newPath = buffer.getString(); + log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={})", oldPath, newPath); try { SshFile o = resolveFile(oldPath); SshFile n = resolveFile(newPath); @@ -692,15 +747,18 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } break; } - case SSH_FXP_SETSTAT: - case SSH_FXP_FSETSTAT: { - // This is required for WinSCP / Cyberduck to upload properly - // Blindly reply "OK" - // TODO implement it - sendStatus(id, SSH_FX_OK, ""); + case SSH_FXP_READLINK: { + String path = buffer.getString(); + log.debug("Received SSH_FXP_READLINK (path={})", path); + try { + SshFile f = resolveFile(path); + String l = f.readSymbolicLink(); + sendLink(id, l); + } catch (IOException e) { + sendStatus(id, SSH_FX_FAILURE, e.getMessage()); + } break; } - default: { log.error("Received: {}", type); sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented"); @@ -717,11 +775,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste send(buffer); } - protected void sendAttrs(int id, SshFile file) throws IOException { + protected void sendAttrs(int id, SshFile file, boolean followLinks) throws IOException { Buffer buffer = new Buffer(); buffer.putByte((byte) SSH_FXP_ATTRS); buffer.putInt(id); - writeAttrs(buffer, file); + writeAttrs(buffer, file, followLinks); send(buffer); } @@ -745,6 +803,18 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste send(buffer); } + protected void sendLink(int id, String link) throws IOException { + Buffer buffer = new Buffer(); + buffer.putByte((byte) SSH_FXP_NAME); + buffer.putInt(id); + buffer.putInt(1); + //normalize the given path, use *nix style separator + buffer.putString(link); + buffer.putString(link); + buffer.putInt(0); + send(buffer); + } + protected void sendName(int id, Iterator<SshFile> files) throws IOException { Buffer buffer = new Buffer(); buffer.putByte((byte) SSH_FXP_NAME); @@ -756,7 +826,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste SshFile f = files.next(); buffer.putString(f.getName()); buffer.putString(getLongName(f)); // Format specified in the specs - writeAttrs(buffer, f); + writeAttrs(buffer, f, false); nb++; } int oldpos = buffer.wpos(); @@ -767,7 +837,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } private String getLongName(SshFile f) throws IOException { - Map<SshFile.Attribute, Object> attributes = f.getAttributes(); + Map<SshFile.Attribute, Object> attributes = f.getAttributes(true); String username = (String) attributes.get(SshFile.Attribute.Owner); if (username.length() > 8) { username = username.substring(0, 8); @@ -790,19 +860,19 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste boolean isDirectory = (Boolean) attributes.get(SshFile.Attribute.IsDirectory); boolean isLink = (Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink); - int perms = (Integer) attributes.get(SshFile.Attribute.Permissions); + int perms = getPermissions(attributes); StringBuilder sb = new StringBuilder(); sb.append(isDirectory ? "d" : isLink ? "l" : "-"); - sb.append((perms & 0000400) != 0 ? "r" : "-"); - sb.append((perms & 0000200) != 0 ? "w" : "-"); - sb.append((perms & 0000100) != 0 ? "x" : "-"); - sb.append((perms & 0000040) != 0 ? "r" : "-"); - sb.append((perms & 0000020) != 0 ? "w" : "-"); - sb.append((perms & 0000010) != 0 ? "x" : "-"); - sb.append((perms & 0000004) != 0 ? "r" : "-"); - sb.append((perms & 0000002) != 0 ? "w" : "-"); - sb.append((perms & 0000001) != 0 ? "x" : "-"); + sb.append((perms & S_IRUSR) != 0 ? "r" : "-"); + sb.append((perms & S_IWUSR) != 0 ? "w" : "-"); + sb.append((perms & S_IXUSR) != 0 ? "x" : "-"); + sb.append((perms & S_IRGRP) != 0 ? "r" : "-"); + sb.append((perms & S_IWGRP) != 0 ? "w" : "-"); + sb.append((perms & S_IXGRP) != 0 ? "x" : "-"); + sb.append((perms & S_IROTH) != 0 ? "r" : "-"); + sb.append((perms & S_IWOTH) != 0 ? "w" : "-"); + sb.append((perms & S_IXOTH) != 0 ? "x" : "-"); sb.append(" "); sb.append(" 1"); sb.append(" "); @@ -819,31 +889,53 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste return sb.toString(); } - protected void writeAttrs(Buffer buffer, SshFile file) throws IOException { + protected int getPermissions(Map<SshFile.Attribute, Object> attributes) { + boolean isReg = (Boolean) attributes.get(SshFile.Attribute.IsRegularFile); + boolean isDir = (Boolean) attributes.get(SshFile.Attribute.IsDirectory); + boolean isLnk = (Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink); + int pf = 0; + EnumSet<SshFile.Permission> perms = (EnumSet<SshFile.Permission>) attributes.get(SshFile.Attribute.Permissions); + for (SshFile.Permission p : perms) { + switch (p) { + case UserRead: pf |= S_IRUSR; break; + case UserWrite: pf |= S_IWUSR; break; + case UserExecute: pf |= S_IXUSR; break; + case GroupRead: pf |= S_IRGRP; break; + case GroupWrite: pf |= S_IWGRP; break; + case GroupExecute: pf |= S_IXGRP; break; + case OthersRead: pf |= S_IROTH; break; + case OthersWrite: pf |= S_IWOTH; break; + case OthersExecute: pf |= S_IXOTH; break; + } + } + pf |= isReg ? S_IFREG : 0; + pf |= isDir ? S_IFDIR : 0; + pf |= isLnk ? S_IFLNK : 0; + return pf; + } + + protected void writeAttrs(Buffer buffer, SshFile file, boolean followLinks) throws IOException { if (!file.doesExist()) { throw new FileNotFoundException(file.getAbsolutePath()); } - Map<SshFile.Attribute, Object> attributes = file.getAttributes(); + Map<SshFile.Attribute, Object> attributes = file.getAttributes(followLinks); boolean isReg = (Boolean) attributes.get(SshFile.Attribute.IsRegularFile); boolean isDir = (Boolean) attributes.get(SshFile.Attribute.IsDirectory); boolean isLnk = (Boolean) attributes.get(SshFile.Attribute.IsSymbolicLink); - int p = (Integer) attributes.get(SshFile.Attribute.Permissions); - p |= isReg ? S_IFREG : 0; - p |= isDir ? S_IFDIR : 0; - p |= isLnk ? S_IFLNK : 0; - if (isReg) { + int pf = getPermissions(attributes); + if (isReg || isLnk) { buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); buffer.putLong((Long) attributes.get(SshFile.Attribute.Size)); buffer.putInt((Integer) attributes.get(SshFile.Attribute.Uid)); buffer.putInt((Integer) attributes.get(SshFile.Attribute.Gid)); - buffer.putInt(p); + buffer.putInt(pf); buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastAccessTime)) / 1000); buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastModifiedTime)) / 1000); - } else if (isDir || isLnk) { + } else if (isDir) { buffer.putInt(SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); buffer.putInt((Integer) attributes.get(SshFile.Attribute.Uid)); buffer.putInt((Integer) attributes.get(SshFile.Attribute.Gid)); - buffer.putInt(p); + buffer.putInt(pf); buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastAccessTime)) / 1000); buffer.putInt(((Long) attributes.get(SshFile.Attribute.LastModifiedTime)) / 1000); } else { @@ -851,11 +943,32 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste } } + protected Map<SshFile.Attribute, Object> readAttrs(Buffer buffer) throws IOException { + Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>(); + int flags = buffer.getInt(); + if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) { + attrs.put(SshFile.Attribute.Size, buffer.getLong()); + } + if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) { + attrs.put(SshFile.Attribute.Uid, buffer.getInt()); + attrs.put(SshFile.Attribute.Gid, buffer.getInt()); + } + if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + attrs.put(SshFile.Attribute.Permissions, buffer.getInt()); + } + if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) { + attrs.put(SshFile.Attribute.LastAccessTime, ((long) buffer.getInt()) * 1000); + attrs.put(SshFile.Attribute.LastModifiedTime, ((long) buffer.getInt()) * 1000); + } + return attrs; + } + protected void sendStatus(int id, int substatus, String msg) throws IOException { sendStatus(id, substatus, msg, ""); } protected void sendStatus(int id, int substatus, String msg, String lang) throws IOException { + log.debug("Send SSH_FXP_STATUS (substatus={}, msg={})", substatus, msg); Buffer buffer = new Buffer(); buffer.putByte((byte) SSH_FXP_STATUS); buffer.putInt(id);
