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/02/23 16:30:10 UTC
[09/15] mina-sshd git commit: [SSHD-408] Implement sftp v4, v5 and v6
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/22581fb8/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 5313f6e..700e61d 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,35 +25,52 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
-import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.AccessDeniedException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntryFlag;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.AclEntry;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
-import java.util.EnumSet;
+import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.file.FileSystemAware;
@@ -131,40 +148,6 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
}
- //
- // File attributes
- //
- enum Attribute {
- Size, // long
- Uid, // int
- Owner, // String
- Gid, // int
- Group, // String
- IsDirectory, // boolean
- IsRegularFile, // boolean
- IsSymbolicLink, // boolean
- Permissions, // EnumSet<Permission>
- CreationTime, // long
- LastModifiedTime, // long
- LastAccessTime, // long
- NLink // int
- }
-
- //
- // File permissions
- //
- enum Permission {
- UserRead,
- UserWrite,
- UserExecute,
- GroupRead,
- GroupWrite,
- GroupExecute,
- OthersRead,
- OthersWrite,
- OthersExecute
- }
-
public enum UnsupportedAttributePolicy {
Ignore,
Warn,
@@ -176,9 +159,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
*/
public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
+ /**
+ * Force the use of a given sftp version
+ */
+ public static final String SFTP_VERSION = "sftp-version";
+
public static final int LOWER_SFTP_IMPL = 3; // Working implementation from v3
- public static final int HIGHER_SFTP_IMPL = 3; // .. up to
- public static final String ALL_SFTP_IMPL = "3";
+ public static final int HIGHER_SFTP_IMPL = 6; // .. up to
+ public static final String ALL_SFTP_IMPL = "3,4,5,6";
public static final int MAX_PACKET_LENGTH = 1024 * 16;
public static final int SSH_FXP_INIT = 1;
@@ -200,7 +188,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
public static final int SSH_FXP_STAT = 17;
public static final int SSH_FXP_RENAME = 18;
public static final int SSH_FXP_READLINK = 19;
- public static final int SSH_FXP_SYMLINK = 20;
+ public static final int SSH_FXP_SYMLINK = 20; // v3 -> v5
+ public static final int SSH_FXP_LINK = 21; // v6
+ public static final int SSH_FXP_BLOCK = 22; // v6
+ public static final int SSH_FXP_UNBLOCK = 23; // v6
public static final int SSH_FXP_STATUS = 101;
public static final int SSH_FXP_HANDLE = 102;
public static final int SSH_FXP_DATA = 103;
@@ -209,23 +200,81 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
public static final int SSH_FXP_EXTENDED = 200;
public static final int SSH_FXP_EXTENDED_REPLY = 201;
- public static final int SSH_FX_OK = 0;
- public static final int SSH_FX_EOF = 1;
- public static final int SSH_FX_NO_SUCH_FILE = 2;
- public static final int SSH_FX_PERMISSION_DENIED = 3;
- public static final int SSH_FX_FAILURE = 4;
- public static final int SSH_FX_BAD_MESSAGE = 5;
- public static final int SSH_FX_NO_CONNECTION = 6;
- public static final int SSH_FX_CONNECTION_LOST = 7;
- public static final int SSH_FX_OP_UNSUPPORTED = 8;
-
- public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; // Not in v3, but we need it
-
- public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001;
- public static final int SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
- public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
- public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; //v3 naming convention
- public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
+ public static final int SSH_FX_OK = 0;
+ public static final int SSH_FX_EOF = 1;
+ public static final int SSH_FX_NO_SUCH_FILE = 2;
+ public static final int SSH_FX_PERMISSION_DENIED = 3;
+ public static final int SSH_FX_FAILURE = 4;
+ public static final int SSH_FX_BAD_MESSAGE = 5;
+ public static final int SSH_FX_NO_CONNECTION = 6;
+ public static final int SSH_FX_CONNECTION_LOST = 7;
+ public static final int SSH_FX_OP_UNSUPPORTED = 8;
+ public static final int SSH_FX_INVALID_HANDLE = 9;
+ public static final int SSH_FX_NO_SUCH_PATH = 10;
+ public static final int SSH_FX_FILE_ALREADY_EXISTS = 11;
+ public static final int SSH_FX_WRITE_PROTECT = 12;
+ public static final int SSH_FX_NO_MEDIA = 13;
+ public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14;
+ public static final int SSH_FX_QUOTA_EXCEEDED = 15;
+ public static final int SSH_FX_UNKNOWN_PRINCIPLE = 16;
+ public static final int SSH_FX_LOCK_CONFLICT = 17;
+ public static final int SSH_FX_DIR_NOT_EMPTY = 18;
+ public static final int SSH_FX_NOT_A_DIRECTORY = 19;
+ public static final int SSH_FX_INVALID_FILENAME = 20;
+ public static final int SSH_FX_LINK_LOOP = 21;
+ public static final int SSH_FX_CANNOT_DELETE = 22;
+ public static final int SSH_FX_INVALID_PARAMETER = 23;
+ public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24;
+ public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25;
+ public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26;
+ public static final int SSH_FX_DELETE_PENDING = 27;
+ public static final int SSH_FX_FILE_CORRUPT = 28;
+ public static final int SSH_FX_OWNER_INVALID = 29;
+ public static final int SSH_FX_GROUP_INVALID = 30;
+ public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31;
+
+ public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001;
+ public static final int SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
+ public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
+ public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; // v3 naming convention
+ public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; // v4
+ public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; // v4
+ public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; // v4
+ public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; // v4
+ public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; // v4
+ public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; // v5
+ public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; // v5
+ public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; // v6
+ public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; // v6
+ public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; // v6
+ public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; // v6
+ public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; // v6
+ public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; // v6
+ public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
+
+ public static final int SSH_FILEXFER_ATTR_ALL = 0x0000FFFF; // All attributes
+
+ public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400;
+
+ public static final int SSH_FILEXFER_TYPE_REGULAR = 1;
+ public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2;
+ public static final int SSH_FILEXFER_TYPE_SYMLINK = 3;
+ public static final int SSH_FILEXFER_TYPE_SPECIAL = 4;
+ public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5;
+ public static final int SSH_FILEXFER_TYPE_SOCKET = 6; // v5
+ public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; // v5
+ public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; // v5
+ public static final int SSH_FILEXFER_TYPE_FIFO = 9; // v5
public static final int SSH_FXF_READ = 0x00000001;
public static final int SSH_FXF_WRITE = 0x00000002;
@@ -233,6 +282,63 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
public static final int SSH_FXF_CREAT = 0x00000008;
public static final int SSH_FXF_TRUNC = 0x00000010;
public static final int SSH_FXF_EXCL = 0x00000020;
+ public static final int SSH_FXF_TEXT = 0x00000040;
+
+ public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007;
+ public static final int SSH_FXF_CREATE_NEW = 0x00000000;
+ public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001;
+ public static final int SSH_FXF_OPEN_EXISTING = 0x00000002;
+ public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003;
+ public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004;
+ public static final int SSH_FXF_APPEND_DATA = 0x00000008;
+ public static final int SSH_FXF_APPEND_DATA_ATOMIC = 0x00000010;
+ public static final int SSH_FXF_TEXT_MODE = 0x00000020;
+ public static final int SSH_FXF_READ_LOCK = 0x00000040;
+ public static final int SSH_FXF_WRITE_LOCK = 0x00000080;
+ public static final int SSH_FXF_DELETE_LOCK = 0x00000100;
+
+ public static final int SSH_FXP_RENAME_OVERWRITE = 0x00000001;
+ public static final int SSH_FXP_RENAME_ATOMIC = 0x00000002;
+ public static final int SSH_FXP_RENAME_NATIVE = 0x00000004;
+
+ public static final int SSH_FXP_REALPATH_NO_CHECK = 0x00000001;
+ public static final int SSH_FXP_REALPATH_STAT_IF = 0x00000002;
+ public static final int SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003;
+
+ public static final int SSH_FXF_RENAME_OVERWRITE = 0x00000001;
+ public static final int SSH_FXF_RENAME_ATOMIC = 0x00000002;
+ public static final int SSH_FXF_RENAME_NATIVE = 0x00000004;
+
+ public static final int ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000;
+ public static final int ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001;
+ public static final int ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002;
+ public static final int ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003;
+
+ public static final int ACE4_FILE_INHERIT_ACE = 0x00000001;
+ public static final int ACE4_DIRECTORY_INHERIT_ACE = 0x00000002;
+ public static final int ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004;
+ public static final int ACE4_INHERIT_ONLY_ACE = 0x00000008;
+ public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010;
+ public static final int ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020;
+ public static final int ACE4_IDENTIFIER_GROUP = 0x00000040;
+
+ public static final int ACE4_READ_DATA = 0x00000001;
+ public static final int ACE4_LIST_DIRECTORY = 0x00000001;
+ public static final int ACE4_WRITE_DATA = 0x00000002;
+ public static final int ACE4_ADD_FILE = 0x00000002;
+ public static final int ACE4_APPEND_DATA = 0x00000004;
+ public static final int ACE4_ADD_SUBDIRECTORY = 0x00000004;
+ public static final int ACE4_READ_NAMED_ATTRS = 0x00000008;
+ public static final int ACE4_WRITE_NAMED_ATTRS = 0x00000010;
+ public static final int ACE4_EXECUTE = 0x00000020;
+ public static final int ACE4_DELETE_CHILD = 0x00000040;
+ public static final int ACE4_READ_ATTRIBUTES = 0x00000080;
+ public static final int ACE4_WRITE_ATTRIBUTES = 0x00000100;
+ public static final int ACE4_DELETE = 0x00010000;
+ public static final int ACE4_READ_ACL = 0x00020000;
+ public static final int ACE4_WRITE_ACL = 0x00040000;
+ public static final int ACE4_WRITE_OWNER = 0x00080000;
+ public static final int ACE4_SYNCHRONIZE = 0x00100000;
public static final int S_IFMT = 0170000; // bitmask for the file type bitfields
public static final int S_IFSOCK = 0140000; // socket
@@ -255,6 +361,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
public static final int S_IWOTH = 0000002;
public static final int S_IXOTH = 0000001;
+ public static int SFTP_V3 = 3;
+ public static int SFTP_V4 = 4;
+ public static int SFTP_V5 = 5;
+ public static int SFTP_V6 = 6;
private ExitCallback callback;
private InputStream in;
@@ -271,7 +381,8 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
private int version;
- private Map<String, Handle> handles = new HashMap<>();
+ private final Map<String, byte[]> extensions = new HashMap<>();
+ private final Map<String, Handle> handles = new HashMap<>();
private UnsupportedAttributePolicy unsupportedAttributePolicy = UnsupportedAttributePolicy.Warn;
@@ -335,25 +446,64 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
}
- protected static class FileHandle extends Handle {
- SeekableByteChannel channel;
+ protected class FileHandle extends Handle {
+ final FileChannel channel;
+ final List<FileLock> locks = new ArrayList<>();
- public FileHandle(Path file, int flags) throws IOException {
+ public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
super(file);
Set<OpenOption> options = new HashSet<>();
- if ((flags & SSH_FXF_READ) != 0) {
+ if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
options.add(StandardOpenOption.READ);
}
- if ((flags & SSH_FXF_WRITE) != 0) {
+ if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
options.add(StandardOpenOption.WRITE);
}
- if ((flags & SSH_FXF_APPEND) != 0) {
+ switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
+ case SSH_FXF_CREATE_NEW:
+ options.add(StandardOpenOption.CREATE_NEW);
+ break;
+ case SSH_FXF_CREATE_TRUNCATE:
+ options.add(StandardOpenOption.CREATE);
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ case SSH_FXF_OPEN_EXISTING:
+ break;
+ case SSH_FXF_OPEN_OR_CREATE:
+ options.add(StandardOpenOption.CREATE);
+ break;
+ case SSH_FXF_TRUNCATE_EXISTING:
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ }
+ if ((flags & SSH_FXF_APPEND_DATA) != 0) {
options.add(StandardOpenOption.APPEND);
}
- if ((flags & SSH_FXF_TRUNC) != 0) {
- options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ FileAttribute<?>[] attributes = new FileAttribute<?>[attrs.size()];
+ int index = 0;
+ for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+ final String key = attr.getKey();
+ final Object val = attr.getValue();
+ attributes[index++] = new FileAttribute<Object>() {
+ @Override
+ public String name() {
+ return key;
+ }
+
+ @Override
+ public Object value() {
+ return val;
+ }
+ };
+ }
+ FileChannel channel;
+ try {
+ channel = FileChannel.open(file, options, attributes);
+ } catch (UnsupportedOperationException e) {
+ channel = FileChannel.open(file, options);
+ setAttributes(file, attrs);
}
- channel = Files.newByteChannel(file, options);
+ this.channel = channel;
}
public int read(byte[] data, long offset) throws IOException {
@@ -370,6 +520,32 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
public void close() throws IOException {
channel.close();
}
+
+ public void lock(long offset, long length, int mask) throws IOException {
+ long size = length == 0 ? channel.size() - offset : length;
+ FileLock lock = channel.tryLock(offset, size, false);
+ synchronized (locks) {
+ locks.add(lock);
+ }
+ }
+
+ public boolean unlock(long offset, long length) throws IOException {
+ long size = length == 0 ? channel.size() - offset : length;
+ FileLock lock = null;
+ for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
+ FileLock l = iterator.next();
+ if (l.position() == offset && l.size() == size) {
+ iterator.remove();
+ lock = l;
+ break;
+ }
+ }
+ if (lock != null) {
+ lock.release();
+ return true;
+ }
+ return false;
+ }
}
public SftpSubsystem() {
@@ -411,7 +587,10 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
public void setFileSystem(FileSystem fileSystem) {
- this.fileSystem = fileSystem;
+ if (fileSystem != this.fileSystem) {
+ this.fileSystem = fileSystem;
+ this.defaultDir = fileSystem.getRootDirectories().iterator().next();
+ }
}
public void setExitCallback(ExitCallback callback) {
@@ -435,7 +614,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
try {
pendingFuture = executors.submit(this);
} catch (RuntimeException e) { // e.g., RejectedExecutionException
- log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.getMessage(), e);
+ log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
throw new IOException(e);
}
}
@@ -495,405 +674,95 @@ 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={})", id);
- // see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt - section 4 - Protocol Initialization
- if (length < 5) { // we don't care about extensions
- throw new IllegalArgumentException("Incomplete SSH_FXP_INIT data: length=" + length);
- }
- version = id;
- if (version >= LOWER_SFTP_IMPL) {
- version = Math.min(version, HIGHER_SFTP_IMPL);
- buffer.clear();
- buffer.putByte((byte) SSH_FXP_VERSION);
- buffer.putInt(version);
- send(buffer);
- } else {
- // We only support version 3 (Version 1 and 2 are not common)
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + ALL_SFTP_IMPL);
- }
-
+ doInit(buffer, id);
break;
}
case SSH_FXP_OPEN: {
- if (session.getFactoryManager().getProperties() != null) {
- String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
- if (maxHandlesString != null) {
- int maxHandleCount = Integer.parseInt(maxHandlesString);
- if (handles.size() > maxHandleCount) {
- sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
- break;
- }
- }
- }
-
- String path = buffer.getString();
- int pflags = buffer.getInt();
- Map<Attribute, Object> attrs = readAttrs(buffer);
- log.debug("Received SSH_FXP_OPEN (path={}, pflags={}, attrs={})", new Object[] { path, pflags, attrs });
- try {
- Path file = resolveFile(path);
- if (Files.exists(file)) {
- if ((pflags & SSH_FXF_READ) != 0 && !Files.isReadable(file)) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not read " + path);
- return;
- }
- if ((pflags & SSH_FXF_WRITE) != 0 && !Files.isWritable(file)) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not write " + path);
- return;
- }
- if (((pflags & SSH_FXF_CREAT) != 0) && ((pflags & SSH_FXF_EXCL) != 0)) {
- sendStatus(id, SSH_FX_FAILURE, path);
- return;
- }
- } else {
- if (((pflags & SSH_FXF_CREAT) != 0)) {
- Files.createFile(file);
- } else {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
- return;
- }
- }
- if (((pflags & SSH_FXF_CREAT) != 0)) {
- setAttributes(file, attrs);
- }
- String handle = UUID.randomUUID().toString();
- handles.put(handle, new FileHandle(file, pflags));
- sendHandle(id, handle);
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage() == null ? "" : e.getMessage());
- }
+ doOpen(buffer, id);
break;
}
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) {
- sendStatus(id, SSH_FX_FAILURE, handle, "");
- } else {
- handles.remove(handle);
- h.close();
- sendStatus(id, SSH_FX_OK, "", "");
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doClose(buffer, id);
break;
}
case SSH_FXP_READ: {
- 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)) {
- sendStatus(id, SSH_FX_FAILURE, handle);
- } else {
- FileHandle fh = (FileHandle) p;
- byte[] b = new byte[Math.min(len, Buffer.MAX_LEN)];
- len = fh.read(b, offset);
- if (len >= 0) {
- Buffer buf = new Buffer(len + 5);
- buf.putByte((byte) SSH_FXP_DATA);
- buf.putInt(id);
- buf.putBytes(b, 0, len);
- send(buf);
- } else {
- sendStatus(id, SSH_FX_EOF, "");
- }
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doRead(buffer, id);
break;
}
case SSH_FXP_WRITE: {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- byte[] data = buffer.getBytes();
- log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, data.length });
- try {
- Handle p = handles.get(handle);
- if (!(p instanceof FileHandle)) {
- sendStatus(id, SSH_FX_FAILURE, handle);
- } else {
- FileHandle fh = (FileHandle) p;
- fh.write(data, offset);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doWrite(buffer, id);
break;
}
case SSH_FXP_LSTAT: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_LSTAT (path={})", path);
- try {
- Path p = resolveFile(path);
- sendAttrs(id, p, false);
- } catch (FileNotFoundException e) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doLStat(buffer, id);
break;
}
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(), true);
- }
- } catch (FileNotFoundException e) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doFStat(buffer, id);
break;
}
case SSH_FXP_SETSTAT: {
- String path = buffer.getString();
- Map<Attribute, Object> attrs = readAttrs(buffer);
- log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
- try {
- Path p = resolveFile(path);
- setAttributes(p, 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());
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_FAILURE, "");
- }
+ doSetStat(buffer, id);
break;
}
case SSH_FXP_FSETSTAT: {
- String handle = buffer.getString();
- Map<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 {
- setAttributes(p.getFile(), attrs);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (FileNotFoundException e) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
- } catch (IOException | UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doFSetStat(buffer, id);
break;
}
case SSH_FXP_OPENDIR: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_OPENDIR (path={})", path);
- try {
- Path p = resolveFile(path);
- if (!Files.exists(p)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
- } else if (!Files.isDirectory(p)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
- } else if (!Files.isReadable(p)) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
- } else {
- String handle = UUID.randomUUID().toString();
- handles.put(handle, new DirectoryHandle(p));
- sendHandle(id, handle);
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doOpenDir(buffer, id);
break;
}
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)) {
- sendStatus(id, SSH_FX_FAILURE, handle);
- } else 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_NO_SUCH_FILE, p.getFile().toString());
- } else if (!Files.isReadable(p.getFile())) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().toString());
- } else {
- DirectoryHandle dh = (DirectoryHandle) p;
- if (dh.hasNext()) {
- // There is at least one file in the directory.
- // Send only a few files at a time to not create packets of a too
- // large size or have a timeout to occur.
- sendName(id, dh);
- if (!dh.hasNext()) {
- // if no more files to send
- dh.setDone(true);
- dh.clearFileList();
- }
- } else {
- // empty directory
- dh.setDone(true);
- dh.clearFileList();
- sendStatus(id, SSH_FX_EOF, "", "");
- }
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doReadDir(buffer, id);
break;
}
case SSH_FXP_REMOVE: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_REMOVE (path={})", path);
- try {
- Path p = resolveFile(path);
- if (!Files.exists(p)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- } else if (Files.isDirectory(p)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- } else {
- Files.delete(p);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doRemove(buffer, id);
break;
}
case SSH_FXP_MKDIR: {
- String path = buffer.getString();
- Map<Attribute, Object> attrs = readAttrs(buffer);
-
- log.debug("Received SSH_FXP_MKDIR (path={})", path);
- // attrs
- try {
- Path p = resolveFile(path);
- if (Files.exists(p)) {
- if (Files.isDirectory(p)) {
- sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
- } else {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- }
- } else {
- Files.createDirectory(p);
- setAttributes(p, attrs);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (AccessDeniedException e) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, e.getMessage());
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doMakeDirectory(buffer, id);
break;
}
case SSH_FXP_RMDIR: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_RMDIR (path={})", path);
- // attrs
- try {
- Path p = resolveFile(path);
- if (Files.isDirectory(p)) {
- Files.delete(p);
- sendStatus(id, SSH_FX_OK, "");
- } else {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doRemoveDirectory(buffer, id);
break;
}
case SSH_FXP_REALPATH: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_REALPATH (path={})", path);
- if (path.trim().length() == 0) {
- path = ".";
- }
- try {
- Path p = resolveFile(path).toAbsolutePath().normalize();
- sendPath(id, p, false);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
- } catch (IOException e) {
- e.printStackTrace();
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doRealPath(buffer, id);
break;
}
case SSH_FXP_STAT: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_STAT (path={})", path);
- try {
- Path 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());
- }
+ doStat(buffer, id);
break;
}
case SSH_FXP_RENAME: {
- String oldPath = buffer.getString();
- String newPath = buffer.getString();
- log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={})", oldPath, newPath);
- try {
- Path o = resolveFile(oldPath);
- Path n = resolveFile(newPath);
- if (!Files.exists(o)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, o.toString());
- } else if (Files.exists(n)) {
- sendStatus(id, SSH_FX_FAILURE, n.toString());
- } else {
- Files.move(o, n);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doRename(buffer, id);
break;
}
case SSH_FXP_READLINK: {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_READLINK (path={})", path);
- try {
- Path f = resolveFile(path);
- String l = Files.readSymbolicLink(f).toString();
- sendLink(id, l);
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doReadLink(buffer, id);
break;
}
case SSH_FXP_SYMLINK: {
- String linkpath = buffer.getString();
- String targetpath = buffer.getString();
- log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
- try {
- Path link = resolveFile(linkpath);
- Path target = fileSystem.getPath(targetpath);
- Files.createSymbolicLink(link, target);
- sendStatus(id, SSH_FX_OK, "");
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
- } catch (IOException e) {
- sendStatus(id, SSH_FX_FAILURE, e.getMessage());
- }
+ doSymLink(buffer, id);
+ break;
+ }
+ case SSH_FXP_LINK: {
+ doLink(buffer, id);
+ break;
+ }
+ case SSH_FXP_BLOCK: {
+ doBlock(buffer, id);
+ break;
+ }
+ case SSH_FXP_UNBLOCK: {
+ doUnblock(buffer, id);
+ break;
+ }
+ case SSH_FXP_EXTENDED: {
+ doExtended(buffer, id);
break;
}
default: {
@@ -904,6 +773,627 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
}
+ protected void doExtended(Buffer buffer, int id) throws IOException {
+ String extension = buffer.getString();
+ switch (extension) {
+ case "text-seek":
+ doTextSeek(buffer, id);
+ break;
+ case "version-select":
+ doVersionSelect(buffer, id);
+ break;
+ default:
+ log.error("Received unsupported SSH_FXP_EXTENDED({})", extension);
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
+ break;
+ }
+ }
+
+ protected void doTextSeek(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long line = buffer.getLong();
+ log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, line);
+ // TODO : implement text-seek
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
+ }
+
+ protected void doVersionSelect(Buffer buffer, int id) throws IOException {
+ String ver = buffer.getString();
+ log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", version);
+ if (Integer.toString(SFTP_V3).equals(ver)) {
+ version = SFTP_V3;
+ } else if (Integer.toString(SFTP_V4).equals(ver)) {
+ version = SFTP_V4;
+ } else if (Integer.toString(SFTP_V5).equals(ver)) {
+ version = SFTP_V5;
+ } else if (Integer.toString(SFTP_V6).equals(ver)) {
+ version = SFTP_V6;
+ } else {
+ sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
+ return;
+ }
+ sendStatus(id, SSH_FX_OK, "");
+ }
+
+ protected void doBlock(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ long length = buffer.getLong();
+ int mask = buffer.getInt();
+ log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask={})", new Object[] { handle, offset, length, mask });
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ return;
+ }
+ FileHandle fileHandle = (FileHandle) p;
+ fileHandle.lock(offset, length, mask);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException | OverlappingFileLockException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doUnblock(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ long length = buffer.getLong();
+ log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})", new Object[] { handle, offset, length });
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ return;
+ }
+ FileHandle fileHandle = (FileHandle) p;
+ boolean found = fileHandle.unlock(offset, length);
+ sendStatus(id, found ? SSH_FX_OK : SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK, "");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doLink(Buffer buffer, int id) throws IOException {
+ String targetpath = buffer.getString();
+ String linkpath = buffer.getString();
+ boolean symLink = buffer.getBoolean();
+ log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})", new Object[] { linkpath, targetpath, symLink });
+ try {
+ Path link = resolveFile(linkpath);
+ Path target = fileSystem.getPath(targetpath);
+ if (symLink) {
+ Files.createSymbolicLink(link, target);
+ } else {
+ Files.createLink(link, target);
+ }
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doSymLink(Buffer buffer, int id) throws IOException {
+ String targetpath = buffer.getString();
+ String linkpath = buffer.getString();
+ log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
+ try {
+ Path link = resolveFile(linkpath);
+ Path target = fileSystem.getPath(targetpath);
+ Files.createSymbolicLink(link, target);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doReadLink(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_READLINK (path={})", path);
+ try {
+ Path f = resolveFile(path);
+ String l = Files.readSymbolicLink(f).toString();
+ sendLink(id, l);
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_READLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRename(Buffer buffer, int id) throws IOException {
+ String oldPath = buffer.getString();
+ String newPath = buffer.getString();
+ int flags = 0;
+ if (version >= SFTP_V5) {
+ flags = buffer.getInt();
+ }
+ log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags={})", new Object[] { oldPath, newPath, flags });
+ try {
+ List<CopyOption> opts = new ArrayList<>();
+ if ((flags & SSH_FXP_RENAME_ATOMIC) != 0) {
+ opts.add(StandardCopyOption.ATOMIC_MOVE);
+ }
+ if ((flags & SSH_FXP_RENAME_OVERWRITE) != 0) {
+ opts.add(StandardCopyOption.REPLACE_EXISTING);
+ }
+ Path o = resolveFile(oldPath);
+ Path n = resolveFile(newPath);
+ Files.move(o, n, opts.toArray(new CopyOption[opts.size()]));
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, flags);
+ try {
+ Path p = resolveFile(path);
+ sendAttrs(id, p, flags, true);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRealPath(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_REALPATH (path={})", path);
+ if (path.trim().length() == 0) {
+ path = ".";
+ }
+ try {
+ if (version < SFTP_V6) {
+ Path p = resolveFile(path).toAbsolutePath().normalize();
+ if (!Files.exists(p)) {
+ throw new FileNotFoundException(p.toString());
+ }
+ sendPath(id, p, Collections.<String, Object>emptyMap());
+ } else {
+ // Read control byte
+ int control = 0;
+ if (buffer.available() > 0) {
+ control = buffer.getByte();
+ }
+ List<String> paths = new ArrayList<>();
+ while (buffer.available() > 0) {
+ paths.add(buffer.getString());
+ }
+ // Resolve path
+ Path p = resolveFile(path);
+ for (String p2 : paths) {
+ p = p.resolve(p2);
+ }
+ p = p.toAbsolutePath().normalize();
+ Map<String, Object> attrs = Collections.emptyMap();
+ if (control == SSH_FXP_REALPATH_STAT_IF) {
+ try {
+ attrs = getAttributes(p, false);
+ } catch (IOException e) {
+ // ignore
+ }
+ } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
+ attrs = getAttributes(p, false);
+ }
+ sendPath(id, p, attrs);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ 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)) {
+ Files.delete(p);
+ sendStatus(id, SSH_FX_OK, "");
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ Map<String, Object> attrs = readAttrs(buffer);
+
+ log.debug("Received SSH_FXP_MKDIR (path={})", path);
+ // attrs
+ try {
+ Path p = resolveFile(path);
+ if (Files.exists(p)) {
+ if (Files.isDirectory(p)) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ }
+ } else {
+ Files.createDirectory(p);
+ setAttributes(p, attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRemove(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_REMOVE (path={})", path);
+ try {
+ Path p = resolveFile(path);
+ if (!Files.exists(p)) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ } else if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ } else {
+ Files.delete(p);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doReadDir(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ log.debug("Received SSH_FXP_READDIR (handle={})", handle);
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof DirectoryHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else 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());
+ } else {
+ DirectoryHandle dh = (DirectoryHandle) p;
+ if (dh.hasNext()) {
+ // There is at least one file in the directory.
+ // Send only a few files at a time to not create packets of a too
+ // large size or have a timeout to occur.
+ sendName(id, dh);
+ if (!dh.hasNext()) {
+ // if no more files to send
+ dh.setDone(true);
+ dh.clearFileList();
+ }
+ } else {
+ // empty directory
+ dh.setDone(true);
+ dh.clearFileList();
+ sendStatus(id, SSH_FX_EOF, "", "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doOpenDir(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_OPENDIR (path={})", path);
+ try {
+ Path p = resolveFile(path);
+ if (!Files.exists(p)) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+ } else if (!Files.isDirectory(p)) {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
+ } else if (!Files.isReadable(p)) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
+ } else {
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new DirectoryHandle(p));
+ sendHandle(id, handle);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doFSetStat(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ Map<String, 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_INVALID_HANDLE, handle);
+ } else {
+ setAttributes(p.getFile(), attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException | UnsupportedOperationException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doSetStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ Map<String, Object> attrs = readAttrs(buffer);
+ log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
+ try {
+ Path p = resolveFile(path);
+ setAttributes(p, attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException | UnsupportedOperationException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doFStat(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, flags);
+ try {
+ Handle p = handles.get(handle);
+ if (p == null) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ sendAttrs(id, p.getFile(), flags, true);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doLStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, flags);
+ try {
+ Path p = resolveFile(path);
+ sendAttrs(id, p, flags, false);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doWrite(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ byte[] data = buffer.getBytes();
+ log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])", new Object[] { handle, offset, data.length });
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ fh.write(data, offset);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRead(Buffer buffer, int id) throws IOException {
+ 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)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ byte[] b = new byte[Math.min(len, Buffer.MAX_LEN)];
+ len = fh.read(b, offset);
+ if (len >= 0) {
+ Buffer buf = new Buffer(len + 5);
+ buf.putByte((byte) SSH_FXP_DATA);
+ buf.putInt(id);
+ buf.putBytes(b, 0, len);
+ send(buf);
+ } else {
+ sendStatus(id, SSH_FX_EOF, "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doClose(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ log.debug("Received SSH_FXP_CLOSE (handle={})", handle);
+ try {
+ Handle h = handles.get(handle);
+ if (h == null) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
+ } else {
+ handles.remove(handle);
+ h.close();
+ sendStatus(id, SSH_FX_OK, "", "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doOpen(Buffer buffer, int id) throws IOException {
+ if (session.getFactoryManager().getProperties() != null) {
+ String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
+ if (maxHandlesString != null) {
+ int maxHandleCount = Integer.parseInt(maxHandlesString);
+ if (handles.size() > maxHandleCount) {
+ sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
+ return;
+ }
+ }
+ }
+
+ String path = buffer.getString();
+ int access = 0;
+ if (version >= SFTP_V5) {
+ access = buffer.getInt();
+ }
+ int pflags = buffer.getInt();
+ if (version < SFTP_V5) {
+ int flags = pflags;
+ pflags = 0;
+ switch (flags & (SSH_FXF_READ | SSH_FXF_WRITE)) {
+ case SSH_FXF_READ:
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ break;
+ case SSH_FXF_WRITE:
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ break;
+ default:
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ break;
+ }
+ if ((flags & SSH_FXF_APPEND) != 0) {
+ access |= ACE4_APPEND_DATA;
+ pflags |= SSH_FXF_APPEND_DATA | SSH_FXF_APPEND_DATA_ATOMIC;
+ }
+ if ((flags & SSH_FXF_CREAT) != 0) {
+ if ((flags & SSH_FXF_EXCL) != 0) {
+ pflags |= SSH_FXF_CREATE_NEW;
+ } else if ((flags & SSH_FXF_TRUNC) != 0) {
+ pflags |= SSH_FXF_CREATE_TRUNCATE;
+ } else {
+ pflags |= SSH_FXF_OPEN_OR_CREATE;
+ }
+ } else {
+ if ((flags & SSH_FXF_TRUNC) != 0) {
+ pflags |= SSH_FXF_TRUNCATE_EXISTING;
+ } else {
+ pflags |= SSH_FXF_OPEN_EXISTING;
+ }
+ }
+ }
+ Map<String, Object> attrs = readAttrs(buffer);
+ log.debug("Received SSH_FXP_OPEN (path={}, access={}, pflags={}, attrs={})", new Object[]{path, access, pflags, attrs});
+ try {
+ Path file = resolveFile(path);
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new FileHandle(file, pflags, access, attrs));
+ sendHandle(id, handle);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doInit(Buffer buffer, int id) throws IOException {
+ log.debug("Received SSH_FXP_INIT (version={})", id);
+ version = id;
+ while (buffer.available() > 0) {
+ String name = buffer.getString();
+ byte[] data = buffer.getBytes();
+ extensions.put(name, data);
+ }
+
+ int low = LOWER_SFTP_IMPL;
+ int hig = HIGHER_SFTP_IMPL;
+ String all = ALL_SFTP_IMPL;
+
+ if (session.getFactoryManager().getProperties() != null) {
+ String sftpVersion = session.getFactoryManager().getProperties().get(SFTP_VERSION);
+ if (sftpVersion != null) {
+ low = hig = Integer.parseInt(sftpVersion);
+ all = sftpVersion;
+ }
+ }
+ if (version >= low) {
+ version = Math.min(version, hig);
+ buffer.clear();
+ buffer.putByte((byte) SSH_FXP_VERSION);
+ buffer.putInt(version);
+
+ // newline
+ buffer.putString("newline");
+ buffer.putString(System.getProperty("line.separator"));
+
+ // versions
+ buffer.putString("versions");
+ buffer.putString(all);
+
+ // supported
+ buffer.putString("supported");
+ buffer.putInt(5 * 4); // length of 5 integers
+ // supported-attribute-mask
+ buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+ | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+ | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+ | SSH_FILEXFER_ATTR_BITS);
+ // TODO: supported-attribute-bits
+ buffer.putInt(0);
+ // supported-open-flags
+ buffer.putInt(SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
+ | SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_EXCL);
+ // TODO: supported-access-mask
+ buffer.putInt(0);
+ // max-read-size
+ buffer.putInt(0);
+
+ // supported2
+ buffer.putString("supported2");
+ buffer.putInt(8 * 4); // length of 7 integers + 2 shorts
+ // supported-attribute-mask
+ buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+ | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+ | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+ | SSH_FILEXFER_ATTR_BITS);
+ // TODO: supported-attribute-bits
+ buffer.putInt(0);
+ // supported-open-flags
+ buffer.putInt(SSH_FXF_ACCESS_DISPOSITION | SSH_FXF_APPEND_DATA);
+ // TODO: supported-access-mask
+ buffer.putInt(0);
+ // max-read-size
+ buffer.putInt(0);
+ // supported-open-block-vector
+ buffer.putShort(0);
+ // supported-block-vector
+ buffer.putShort(0);
+ // attrib-extension-count
+ buffer.putInt(0);
+ // extension-count
+ buffer.putInt(0);
+
+ /*
+ buffer.putString("acl-supported");
+ buffer.putInt(4);
+ // capabilities
+ buffer.putInt(0);
+ */
+
+ send(buffer);
+ } else {
+ // We only support version >= 3 (Version 1 and 2 are not common)
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + all);
+ }
+ }
+
protected void sendHandle(int id, String handle) throws IOException {
Buffer buffer = new Buffer();
buffer.putByte((byte) SSH_FXP_HANDLE);
@@ -912,19 +1402,15 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
send(buffer);
}
- protected void sendAttrs(int id, Path file, boolean followLinks) throws IOException {
+ protected void sendAttrs(int id, Path file, int flags, boolean followLinks) throws IOException {
Buffer buffer = new Buffer();
buffer.putByte((byte) SSH_FXP_ATTRS);
buffer.putInt(id);
- writeAttrs(buffer, file, followLinks);
+ writeAttrs(buffer, file, flags, followLinks);
send(buffer);
}
- protected void sendPath(int id, Path f) throws IOException {
- sendPath(id, f, true);
- }
-
- protected void sendPath(int id, Path f, boolean sendAttrs) throws IOException {
+ protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
Buffer buffer = new Buffer();
buffer.putByte((byte) SSH_FXP_NAME);
buffer.putInt(id);
@@ -934,13 +1420,19 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
if (normalizedPath.length() == 0) {
normalizedPath = "/";
}
- buffer.putString(normalizedPath);
+ buffer.putString(normalizedPath, StandardCharsets.UTF_8);
f = resolveFile(normalizedPath);
if (f.getFileName() == null) {
f = resolveFile(".");
}
- buffer.putString(getLongName(f, sendAttrs)); // Format specified in the specs
- buffer.putInt(0);
+ if (version == SFTP_V3) {
+ buffer.putString(getLongName(f, attrs), StandardCharsets.UTF_8); // Format specified in the specs
+ buffer.putInt(0);
+ } else if (version >= SFTP_V4) {
+ writeAttrs(buffer, attrs);
+ } else {
+ throw new IllegalStateException();
+ }
send(buffer);
}
@@ -965,9 +1457,11 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
int nb = 0;
while (files.hasNext() && buffer.wpos() < MAX_PACKET_LENGTH) {
Path f = files.next();
- buffer.putString(f.getFileName().toString());
- buffer.putString(getLongName(f)); // Format specified in the specs
- writeAttrs(buffer, f, false);
+ buffer.putString(f.getFileName().toString(), StandardCharsets.UTF_8);
+ if (version == SFTP_V3) {
+ buffer.putString(getLongName(f), StandardCharsets.UTF_8); // Format specified in the specs
+ }
+ writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
nb++;
}
int oldpos = buffer.wpos();
@@ -982,21 +1476,22 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
private String getLongName(Path f, boolean sendAttrs) throws IOException {
- Map<Attribute, Object> attributes;
+ Map<String, Object> attributes;
if (sendAttrs) {
attributes = getAttributes(f, false);
} else {
- attributes = new HashMap<>();
- attributes.put(Attribute.Owner, "owner");
- attributes.put(Attribute.Group, "group");
- attributes.put(Attribute.Size, (long) 0);
- attributes.put(Attribute.IsDirectory, false);
- attributes.put(Attribute.IsSymbolicLink, false);
- attributes.put(Attribute.IsRegularFile, false);
- attributes.put(Attribute.Permissions, EnumSet.noneOf(Permission.class));
- attributes.put(Attribute.LastModifiedTime, (long) 0);
- }
- String username = (String) attributes.get(Attribute.Owner);
+ attributes = Collections.emptyMap();
+ }
+ return getLongName(f, attributes);
+ }
+
+ private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
+ String username;
+ if (attributes.containsKey("owner")) {
+ username = attributes.get("owner").toString();
+ } else {
+ username = "owner";
+ }
if (username.length() > 8) {
username = username.substring(0, 8);
} else {
@@ -1004,7 +1499,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
username = username + " ";
}
}
- String group = (String) attributes.get(Attribute.Group);
+ String group;
+ if (attributes.containsKey("group")) {
+ group = attributes.get("group").toString();
+ } else {
+ group = "group";
+ }
if (group.length() > 8) {
group = group.substring(0, 8);
} else {
@@ -1013,27 +1513,25 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
}
}
- long length = (Long) attributes.get(Attribute.Size);
+ Long length = (Long) attributes.get("size");
+ if (length == null) {
+ length = 0l;
+ }
String lengthString = String.format("%1$8s", length);
- boolean isDirectory = (Boolean) attributes.get(Attribute.IsDirectory);
- boolean isLink = (Boolean) attributes.get(Attribute.IsSymbolicLink);
- int perms = getPermissions(attributes);
+ Boolean isDirectory = (Boolean) attributes.get("isDirectory");
+ Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
+ Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+ if (perms == null) {
+ perms = new HashSet<>();
+ }
StringBuilder sb = new StringBuilder();
- sb.append(isDirectory ? "d" : isLink ? "l" : "-");
- 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((isDirectory != null && isDirectory) ? "d" : (isLink != null && isLink) ? "l" : "-");
+ sb.append(PosixFilePermissions.toString(perms));
sb.append(" ");
- sb.append(attributes.containsKey(Attribute.NLink)
- ? attributes.get(Attribute.NLink) : "1");
+ sb.append(attributes.containsKey("nlink")
+ ? attributes.get("nlink") : "1");
sb.append(" ");
sb.append(username);
sb.append(" ");
@@ -1041,73 +1539,50 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
sb.append(" ");
sb.append(lengthString);
sb.append(" ");
- sb.append(getUnixDate((Long) attributes.get(Attribute.LastModifiedTime)));
+ sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
sb.append(" ");
sb.append(f.getFileName().toString());
return sb.toString();
}
- protected Map<Attribute, Object> getPermissions(int perms) {
- Map<Attribute, Object> attrs = new HashMap<>();
- if ((perms & S_IFMT) == S_IFREG) {
- attrs.put(Attribute.IsRegularFile, Boolean.TRUE);
- }
- if ((perms & S_IFMT) == S_IFDIR) {
- attrs.put(Attribute.IsDirectory, Boolean.TRUE);
- }
- if ((perms & S_IFMT) == S_IFLNK) {
- attrs.put(Attribute.IsSymbolicLink, Boolean.TRUE);
- }
- EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
- if ((perms & S_IRUSR) != 0) {
- p.add(Permission.UserRead);
- }
- if ((perms & S_IWUSR) != 0) {
- p.add(Permission.UserWrite);
- }
- if ((perms & S_IXUSR) != 0) {
- p.add(Permission.UserExecute);
- }
- if ((perms & S_IRGRP) != 0) {
- p.add(Permission.GroupRead);
- }
- if ((perms & S_IWGRP) != 0) {
- p.add(Permission.GroupWrite);
- }
- if ((perms & S_IXGRP) != 0) {
- p.add(Permission.GroupExecute);
- }
- if ((perms & S_IROTH) != 0) {
- p.add(Permission.OthersRead);
- }
- if ((perms & S_IWOTH) != 0) {
- p.add(Permission.OthersWrite);
- }
- if ((perms & S_IXOTH) != 0) {
- p.add(Permission.OthersExecute);
- }
- attrs.put(Attribute.Permissions, p);
- return attrs;
- }
-
- protected int getPermissions(Map<Attribute, Object> attributes) {
- boolean isReg = (Boolean) attributes.get(Attribute.IsRegularFile);
- boolean isDir = (Boolean) attributes.get(Attribute.IsDirectory);
- boolean isLnk = (Boolean) attributes.get(Attribute.IsSymbolicLink);
+ protected int attributesToPermissions(Map<String, Object> attributes) {
+ boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+ boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+ boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
int pf = 0;
- EnumSet<Permission> perms = (EnumSet<Permission>) attributes.get(Attribute.Permissions);
- for (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;
+ Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+ if (perms != null) {
+ 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;
+ }
}
}
pf |= isReg ? S_IFREG : 0;
@@ -1116,41 +1591,93 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
return pf;
}
- protected void writeAttrs(Buffer buffer, Path file, boolean followLinks) throws IOException {
- if (!Files.exists(file)) {
+ protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
+ LinkOption[] options = followLinks ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
+ if (!Files.exists(file, options)) {
throw new FileNotFoundException(file.toString());
}
- Map<Attribute, Object> attributes = getAttributes(file, followLinks);
- boolean isReg = getBool((Boolean) attributes.get(Attribute.IsRegularFile));
- boolean isDir = getBool((Boolean) attributes.get(Attribute.IsDirectory));
- boolean isLnk = getBool((Boolean) attributes.get(Attribute.IsSymbolicLink));
- int flags = 0;
- if ((isReg || isLnk) && attributes.containsKey(Attribute.Size)) {
- flags |= SSH_FILEXFER_ATTR_SIZE;
- }
- if (attributes.containsKey(Attribute.Uid) && attributes.containsKey(Attribute.Gid)) {
- flags |= SSH_FILEXFER_ATTR_UIDGID;
- }
- if (attributes.containsKey(Attribute.Permissions)) {
- flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
- }
- if (attributes.containsKey(Attribute.LastAccessTime) && attributes.containsKey(Attribute.LastModifiedTime)) {
- flags |= SSH_FILEXFER_ATTR_ACMODTIME;
- }
- buffer.putInt(flags);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong((Long) attributes.get(Attribute.Size));
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- buffer.putInt((Integer) attributes.get(Attribute.Uid));
- buffer.putInt((Integer) attributes.get(Attribute.Gid));
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(getPermissions(attributes));
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- buffer.putInt(((Long) attributes.get(Attribute.LastAccessTime)) / 1000);
- buffer.putInt(((Long) attributes.get(Attribute.LastModifiedTime)) / 1000);
+ Map<String, Object> attributes = getAttributes(file, flags, followLinks);
+ writeAttrs(buffer, attributes);
+ }
+
+ protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
+ if (version == SFTP_V3) {
+ boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+ boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+ boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+ int flags =
+ ((isReg || isLnk) && attributes.containsKey("size") ? SSH_FILEXFER_ATTR_SIZE : 0) |
+ (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
+ (attributes.containsKey("permissions") ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+ (attributes.containsKey("lastModifiedTime") && attributes.containsKey("lastAccessTime") ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
+ buffer.putInt(flags);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong((Long) attributes.get("size"));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ buffer.putInt((Integer) attributes.get("uid"));
+ buffer.putInt((Integer) attributes.get("gid"));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributesToPermissions(attributes));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ buffer.putInt(((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.SECONDS));
+ buffer.putInt(((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.SECONDS));
+ }
+ } else if (version >= SFTP_V4) {
+ boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+ boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+ boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+ int flags =
+ ((isReg || isLnk) && attributes.containsKey("size") ? SSH_FILEXFER_ATTR_SIZE : 0) |
+ (attributes.containsKey("owner") && attributes.containsKey("group") ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
+ (attributes.containsKey("permissions") ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+ (attributes.containsKey("lastModifiedTime") ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
+ (attributes.containsKey("creationTime") ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
+ (attributes.containsKey("lastAccessTime") ? SSH_FILEXFER_ATTR_ACCESSTIME : 0);
+ buffer.putInt(flags);
+ buffer.putByte((byte) (isReg ? SSH_FILEXFER_TYPE_REGULAR :
+ isDir ? SSH_FILEXFER_TYPE_DIRECTORY :
+ isLnk ? SSH_FILEXFER_TYPE_SYMLINK :
+ SSH_FILEXFER_TYPE_UNKNOWN));
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong((Long) attributes.get("size"));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ buffer.putString(attributes.get("owner").toString(), StandardCharsets.UTF_8);
+ buffer.putString(attributes.get("group").toString(), StandardCharsets.UTF_8);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributesToPermissions(attributes));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ buffer.putLong(((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = ((FileTime) attributes.get("lastAccessTime")).to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ buffer.putLong(((FileTime) attributes.get("creationTime")).to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = ((FileTime) attributes.get("creationTime")).to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ buffer.putLong(((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = ((FileTime) attributes.get("lastModifiedTime")).to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ }
+ // TODO: acls
+ // TODO: bits
+ // TODO: extended
}
}
@@ -1158,98 +1685,51 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
return bool != null && bool;
}
- protected Map<Attribute, Object> getAttributes(Path file, boolean followLinks) throws IOException {
- String[] attrs = new String[] { "unix:*", "posix:*", "*" };
- Map<String, Object> a = null;
- for (String attr : attrs) {
- try {
- a = Files.readAttributes(
- file, attr,
- followLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
- break;
- } catch (UnsupportedOperationException e) {
- // Ignore
- }
- }
- if (a == null) {
- throw new IllegalStateException();
- }
- Map<Attribute, Object> map = new HashMap<>();
- map.put(Attribute.Size, a.get("size"));
- if (a.containsKey("uid")) {
- map.put(Attribute.Uid, a.get("uid"));
- }
- if (a.containsKey("owner")) {
- map.put(Attribute.Owner, ((UserPrincipal) a.get("owner")).getName());
- } else {
- map.put(Attribute.Owner, session.getUsername());
- }
- if (a.containsKey("gid")) {
- map.put(Attribute.Gid, a.get("gid"));
- }
- if (a.containsKey("group")) {
- map.put(Attribute.Group, ((GroupPrincipal) a.get("group")).getName());
- } else {
- map.put(Attribute.Group, session.getUsername());
- }
- if (a.containsKey("nlink")) {
- map.put(Attribute.NLink, a.get("nlink"));
- }
- map.put(Attribute.IsDirectory, a.get("isDirectory"));
- map.put(Attribute.IsRegularFile, a.get("isRegularFile"));
- map.put(Attribute.IsSymbolicLink, a.get("isSymbolicLink"));
- map.put(Attribute.CreationTime, ((FileTime) a.get("creationTime")).toMillis());
- map.put(Attribute.LastModifiedTime, ((FileTime) a.get("lastModifiedTime")).toMillis());
- map.put(Attribute.LastAccessTime, ((FileTime) a.get("lastAccessTime")).toMillis());
- if (a.containsKey("permissions")) {
- map.put(Attribute.Permissions, fromPerms((Set<PosixFilePermission>) a.get("permissions")));
+ protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
+ return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
+ }
+
+ protected Map<String, Object
<TRUNCATED>