You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2017/10/07 15:58:41 UTC
[2/5] mina-sshd git commit: [SSHD-775] SftpSubSystem::sendStatus
leaks Exception information
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2529a4c3/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index cc66d02..5d87d8e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -19,106 +19,52 @@
package org.apache.sshd.server.subsystem.sftp;
import java.io.EOFException;
-import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.charset.StandardCharsets;
+import java.net.UnknownServiceException;
import java.nio.file.AccessDeniedException;
-import java.nio.file.CopyOption;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemLoopException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
-import java.util.EnumSet;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.IntUnaryOperator;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.FactoryManager;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.OptionalFeature;
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.config.VersionProperties;
import org.apache.sshd.common.digest.BuiltinDigests;
-import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.digest.DigestFactory;
import org.apache.sshd.common.file.FileSystemAware;
import org.apache.sshd.common.random.Random;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
import org.apache.sshd.common.subsystem.sftp.SftpHelper;
-import org.apache.sshd.common.subsystem.sftp.extensions.AclSupportedParser;
-import org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo;
-import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
import org.apache.sshd.common.subsystem.sftp.extensions.openssh.HardLinkExtensionParser;
-import org.apache.sshd.common.util.EventListenerUtils;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.NumberUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.Pair;
-import org.apache.sshd.common.util.SelectorUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.FileInfoExtractor;
import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.common.util.threads.ThreadUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.server.session.ServerSessionHolder;
/**
* SFTP subsystem
@@ -126,8 +72,8 @@ import org.apache.sshd.server.session.ServerSessionHolder;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SftpSubsystem
- extends AbstractLoggingBean
- implements Command, Runnable, SessionAware, FileSystemAware, ServerSessionHolder, SftpEventListenerManager {
+ extends AbstractSftpSubsystemHelper
+ implements Command, Runnable, SessionAware, FileSystemAware {
/**
* Properties key for the maximum of available open handles per session.
@@ -158,26 +104,6 @@ public class SftpSubsystem
public static final int MAX_FILE_HANDLE_ROUNDS = MAX_FILE_HANDLE_SIZE;
/**
- * Force the use of a given sftp version
- */
- public static final String SFTP_VERSION = "sftp-version";
-
- public static final int LOWER_SFTP_IMPL = SftpConstants.SFTP_V3; // Working implementation from v3
- public static final int HIGHER_SFTP_IMPL = SftpConstants.SFTP_V6; // .. up to and including
- public static final String ALL_SFTP_IMPL = IntStream.rangeClosed(LOWER_SFTP_IMPL, HIGHER_SFTP_IMPL)
- .mapToObj(Integer::toString)
- .collect(Collectors.joining(","));
-
- /**
- * Force the use of a max. packet length for {@link #doRead(Buffer, int)} protection
- * against malicious packets
- *
- * @see #DEFAULT_MAX_READDATA_PACKET_LENGTH
- */
- public static final String MAX_READDATA_PACKET_LENGTH_PROP = "sftp-max-readdata-packet-length";
- public static final int DEFAULT_MAX_READDATA_PACKET_LENGTH = 63 * 1024;
-
- /**
* Maximum amount of data allocated for listing the contents of a directory
* in any single invocation of {@link #doReadDir(Buffer, int)}
*
@@ -186,99 +112,6 @@ public class SftpSubsystem
public static final String MAX_READDIR_DATA_SIZE_PROP = "sftp-max-readdir-data-size";
public static final int DEFAULT_MAX_READDIR_DATA_SIZE = 16 * 1024;
- /**
- * Allows controlling reports of which client extensions are supported
- * (and reported via "support" and "support2" server
- * extensions) as a comma-separate list of names. <B>Note:</B> requires
- * overriding the {@link #executeExtendedCommand(Buffer, int, String)}
- * command accordingly. If empty string is set then no server extensions
- * are reported
- *
- * @see #DEFAULT_SUPPORTED_CLIENT_EXTENSIONS
- */
- public static final String CLIENT_EXTENSIONS_PROP = "sftp-client-extensions";
-
- /**
- * The default reported supported client extensions
- */
- public static final Map<String, OptionalFeature> DEFAULT_SUPPORTED_CLIENT_EXTENSIONS =
- // TODO text-seek - see http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-13.txt
- // TODO home-directory - see http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
- GenericUtils.<String, OptionalFeature>mapBuilder()
- .put(SftpConstants.EXT_VERSION_SELECT, OptionalFeature.TRUE)
- .put(SftpConstants.EXT_COPY_FILE, OptionalFeature.TRUE)
- .put(SftpConstants.EXT_MD5_HASH, BuiltinDigests.md5)
- .put(SftpConstants.EXT_MD5_HASH_HANDLE, BuiltinDigests.md5)
- .put(SftpConstants.EXT_CHECK_FILE_HANDLE, OptionalFeature.any(BuiltinDigests.VALUES))
- .put(SftpConstants.EXT_CHECK_FILE_NAME, OptionalFeature.any(BuiltinDigests.VALUES))
- .put(SftpConstants.EXT_COPY_DATA, OptionalFeature.TRUE)
- .put(SftpConstants.EXT_SPACE_AVAILABLE, OptionalFeature.TRUE)
- .immutable();
-
- /**
- * Comma-separated list of which {@code OpenSSH} extensions are reported and
- * what version is reported for each - format: {@code name=version}. If empty
- * value set, then no such extensions are reported. Otherwise, the
- * {@link #DEFAULT_OPEN_SSH_EXTENSIONS} are used
- */
- public static final String OPENSSH_EXTENSIONS_PROP = "sftp-openssh-extensions";
- public static final List<OpenSSHExtension> DEFAULT_OPEN_SSH_EXTENSIONS =
- Collections.unmodifiableList(
- Arrays.asList(
- new OpenSSHExtension(FsyncExtensionParser.NAME, "1"),
- new OpenSSHExtension(HardLinkExtensionParser.NAME, "1")
- ));
-
- public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES =
- Collections.unmodifiableList(NamedResource.getNameList(DEFAULT_OPEN_SSH_EXTENSIONS));
-
- public static final List<String> DEFAULT_UNIX_VIEW = Collections.singletonList("unix:*");
-
- /**
- * Comma separate list of {@code SSH_ACL_CAP_xxx} names - where name can be without
- * the prefix. If not defined then {@link #DEFAULT_ACL_SUPPORTED_MASK} is used
- */
- public static final String ACL_SUPPORTED_MASK_PROP = "sftp-acl-supported-mask";
- public static final Set<Integer> DEFAULT_ACL_SUPPORTED_MASK =
- Collections.unmodifiableSet(
- new HashSet<>(Arrays.asList(
- SftpConstants.SSH_ACL_CAP_ALLOW,
- SftpConstants.SSH_ACL_CAP_DENY,
- SftpConstants.SSH_ACL_CAP_AUDIT,
- SftpConstants.SSH_ACL_CAP_ALARM)));
-
- /**
- * Property that can be used to set the reported NL value.
- * If not set, then {@link IoUtils#EOL} is used
- */
- public static final String NEWLINE_VALUE = "sftp-newline";
-
- /**
- * A {@link Map} of {@link FileInfoExtractor}s to be used to complete
- * attributes that are deemed important enough to warrant an extra
- * effort if not accessible via the file system attributes views
- */
- public static final Map<String, FileInfoExtractor<?>> FILEATTRS_RESOLVERS =
- GenericUtils.<String, FileInfoExtractor<?>>mapBuilder(String.CASE_INSENSITIVE_ORDER)
- .put("isRegularFile", FileInfoExtractor.ISREG)
- .put("isDirectory", FileInfoExtractor.ISDIR)
- .put("isSymbolicLink", FileInfoExtractor.ISSYMLINK)
- .put("permissions", FileInfoExtractor.PERMISSIONS)
- .put("size", FileInfoExtractor.SIZE)
- .put("lastModifiedTime", FileInfoExtractor.LASTMODIFIED)
- .immutable();
-
- /**
- * Whether to automatically follow symbolic links when resolving paths
- * @see #DEFAULT_AUTO_FOLLOW_LINKS
- */
- public static final String AUTO_FOLLOW_LINKS = "sftp-auto-follow-links";
-
- /**
- * Default value of {@value #AUTO_FOLLOW_LINKS}
- */
- public static final boolean DEFAULT_AUTO_FOLLOW_LINKS = true;
-
protected ExitCallback callback;
protected InputStream in;
protected OutputStream out;
@@ -297,13 +130,9 @@ public class SftpSubsystem
protected int version;
protected final Map<String, byte[]> extensions = new TreeMap<>(Comparator.naturalOrder());
protected final Map<String, Handle> handles = new HashMap<>();
- protected final UnsupportedAttributePolicy unsupportedAttributePolicy;
private ServerSession serverSession;
private final AtomicBoolean closed = new AtomicBoolean(false);
- private final Collection<SftpEventListener> sftpEventListeners = new CopyOnWriteArraySet<>();
- private final SftpEventListener sftpEventListenerProxy;
- private final SftpFileSystemAccessor fileSystemAccessor;
/**
* @param executorService The {@link ExecutorService} to be used by
@@ -315,9 +144,14 @@ public class SftpSubsystem
* @param policy The {@link UnsupportedAttributePolicy} to use if failed to access
* some local file attributes
* @param accessor The {@link SftpFileSystemAccessor} to use for opening files and directories
+ * @param errorStatusDataHandler The (never {@code null}) {@link SftpErrorStatusDataHandler} to
+ * use when generating failed commands error messages
* @see ThreadUtils#newSingleThreadExecutor(String)
*/
- public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy, SftpFileSystemAccessor accessor) {
+ public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy,
+ SftpFileSystemAccessor accessor, SftpErrorStatusDataHandler errorStatusDataHandler) {
+ super(policy, accessor, errorStatusDataHandler);
+
if (executorService == null) {
executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
shutdownExecutor = true; // we always close the ad-hoc executor service
@@ -325,41 +159,16 @@ public class SftpSubsystem
executors = executorService;
shutdownExecutor = shutdownOnExit;
}
-
- unsupportedAttributePolicy = Objects.requireNonNull(policy, "No policy provided");
- fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor");
- sftpEventListenerProxy = EventListenerUtils.proxyWrapper(SftpEventListener.class, getClass().getClassLoader(), sftpEventListeners);
}
+ @Override
public int getVersion() {
return version;
}
- public final UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
- return unsupportedAttributePolicy;
- }
-
- public final SftpFileSystemAccessor getFileSystemAccessor() {
- return fileSystemAccessor;
- }
-
- @Override
- public SftpEventListener getSftpEventListenerProxy() {
- return sftpEventListenerProxy;
- }
-
- @Override
- public boolean addSftpEventListener(SftpEventListener listener) {
- return sftpEventListeners.add(SftpEventListener.validateListener(listener));
- }
-
@Override
- public boolean removeSftpEventListener(SftpEventListener listener) {
- if (listener == null) {
- return false;
- }
-
- return sftpEventListeners.remove(SftpEventListener.validateListener(listener));
+ public Path getDefaultDirectory() {
+ return defaultDir;
}
@Override
@@ -477,6 +286,7 @@ public class SftpSubsystem
}
}
+ @Override
protected void process(Buffer buffer) throws IOException {
int length = buffer.getInt();
int type = buffer.getUByte();
@@ -570,16 +380,7 @@ public class SftpSubsystem
}
}
- protected void doExtended(Buffer buffer, int id) throws IOException {
- executeExtendedCommand(buffer, id, buffer.getString());
- }
-
- /**
- * @param buffer The command {@link Buffer}
- * @param id The request id
- * @param extension The extension name
- * @throws IOException If failed to execute the extension
- */
+ @Override
protected void executeExtendedCommand(Buffer buffer, int id, String extension) throws IOException {
switch (extension) {
case SftpConstants.EXT_TEXT_SEEK:
@@ -620,76 +421,32 @@ public class SftpSubsystem
}
}
- // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10
- protected void doOpenSSHHardLink(Buffer buffer, int id) throws IOException {
- String srcFile = buffer.getString();
- String dstFile = buffer.getString();
-
- try {
- doOpenSSHHardLink(id, srcFile, dstFile);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doOpenSSHHardLink(int id, String srcFile, String dstFile) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("doOpenSSHHardLink({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, dst={})",
- getServerSession(), id, HardLinkExtensionParser.NAME, srcFile, dstFile);
- }
-
- createLink(id, srcFile, dstFile, false);
- }
-
- protected void doSpaceAvailable(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- SpaceAvailableExtensionInfo info;
- try {
- info = doSpaceAvailable(id, path);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- buffer.clear();
- buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
- buffer.putInt(id);
- SpaceAvailableExtensionInfo.encode(buffer, info);
- send(buffer);
- }
-
- protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException {
- Path nrm = resolveNormalizedLocation(path);
+ @Override
+ protected void createLink(int id, String existingPath, String linkPath, boolean symLink) throws IOException {
+ Path link = resolveFile(linkPath);
+ Path existing = fileSystem.getPath(existingPath);
if (log.isDebugEnabled()) {
- log.debug("doSpaceAvailable({})[id={}] path={}[{}]", getServerSession(), id, path, nrm);
- }
-
- FileStore store = Files.getFileStore(nrm);
- if (log.isTraceEnabled()) {
- log.trace("doSpaceAvailable({})[id={}] path={}[{}] - {}[{}]",
- getServerSession(), id, path, nrm, store.name(), store.type());
+ log.debug("createLink({})[id={}], existing={}[{}], link={}[{}], symlink={})",
+ getServerSession(), id, linkPath, link, existingPath, existing, symLink);
}
- return new SpaceAvailableExtensionInfo(store);
- }
-
- protected void doTextSeek(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long line = buffer.getLong();
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.linking(session, link, existing, symLink);
try {
- // TODO : implement text-seek - see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-6.3
- doTextSeek(id, handle, line);
+ if (symLink) {
+ Files.createSymbolicLink(link, existing);
+ } else {
+ Files.createLink(link, existing);
+ }
} catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
+ listener.linked(session, link, existing, symLink, e);
+ throw e;
}
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
+ listener.linked(session, link, existing, symLink, null);
}
+ @Override
protected void doTextSeek(int id, String handle, long line) throws IOException {
Handle h = handles.get(handle);
if (log.isDebugEnabled()) {
@@ -698,22 +455,10 @@ public class SftpSubsystem
}
FileHandle fileHandle = validateHandle(handle, h, FileHandle.class);
- throw new UnsupportedOperationException("doTextSeek(" + fileHandle + ")");
- }
-
- // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10
- protected void doOpenSSHFsync(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- try {
- doOpenSSHFsync(id, handle);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
+ throw new UnknownServiceException("doTextSeek(" + fileHandle + ")");
}
+ @Override
protected void doOpenSSHFsync(int id, String handle) throws IOException {
Handle h = handles.get(handle);
if (log.isDebugEnabled()) {
@@ -726,30 +471,11 @@ public class SftpSubsystem
accessor.syncFileData(session, this, fileHandle.getFile(), fileHandle.getFileHandle(), fileHandle.getFileChannel());
}
- protected void doCheckFileHash(Buffer buffer, int id, String targetType) throws IOException {
- String target = buffer.getString();
- String algList = buffer.getString();
- String[] algos = GenericUtils.split(algList, ',');
- long startOffset = buffer.getLong();
- long length = buffer.getLong();
- int blockSize = buffer.getInt();
- try {
- buffer.clear();
- buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
- buffer.putInt(id);
- buffer.putString(SftpConstants.EXT_CHECK_FILE);
- doCheckFileHash(id, targetType, target, Arrays.asList(algos), startOffset, length, blockSize, buffer);
- } catch (Exception e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- send(buffer);
- }
-
- protected void doCheckFileHash(int id, String targetType, String target, Collection<String> algos,
- long startOffset, long length, int blockSize, Buffer buffer)
- throws Exception {
+ @Override
+ protected void doCheckFileHash(
+ int id, String targetType, String target, Collection<String> algos,
+ long startOffset, long length, int blockSize, Buffer buffer)
+ throws Exception {
Path path;
if (SftpConstants.EXT_CHECK_FILE_HANDLE.equalsIgnoreCase(targetType)) {
Handle h = handles.get(target);
@@ -764,7 +490,7 @@ public class SftpSubsystem
*/
int access = fileHandle.getAccessMask();
if ((access & SftpConstants.ACE4_READ_DATA) == 0) {
- throw new AccessDeniedException("File not opened for read: " + path);
+ throw new AccessDeniedException(path.toString(), path.toString(), "File not opened for read");
}
} else {
path = resolveFile(target);
@@ -780,7 +506,7 @@ public class SftpSubsystem
}
if (Files.isSymbolicLink(path)) {
- throw new FileSystemLoopException(target + " yields a circular or too long chain of symlinks");
+ throw new FileSystemLoopException(target);
}
if (Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
@@ -802,121 +528,7 @@ public class SftpSubsystem
doCheckFileHash(id, path, factory, startOffset, length, blockSize, buffer);
}
- protected void doCheckFileHash(int id, Path file, NamedFactory<? extends Digest> factory,
- long startOffset, long length, int blockSize, Buffer buffer)
- throws Exception {
- ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", startOffset);
- ValidateUtils.checkTrue(length >= 0L, "Invalid length: %d", length);
- ValidateUtils.checkTrue((blockSize == 0) || (blockSize >= SftpConstants.MIN_CHKFILE_BLOCKSIZE), "Invalid block size: %d", blockSize);
- Objects.requireNonNull(factory, "No digest factory provided");
- buffer.putString(factory.getName());
-
- long effectiveLength = length;
- long totalLength = Files.size(file);
- if (effectiveLength == 0L) {
- effectiveLength = totalLength - startOffset;
- } else {
- long maxRead = startOffset + length;
- if (maxRead > totalLength) {
- effectiveLength = totalLength - startOffset;
- }
- }
- ValidateUtils.checkTrue(effectiveLength > 0L, "Non-positive effective hash data length: %d", effectiveLength);
-
- byte[] digestBuf = (blockSize == 0)
- ? new byte[Math.min((int) effectiveLength, IoUtils.DEFAULT_COPY_SIZE)]
- : new byte[Math.min((int) effectiveLength, blockSize)];
- ByteBuffer wb = ByteBuffer.wrap(digestBuf);
- SftpFileSystemAccessor accessor = getFileSystemAccessor();
- try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, file, "", Collections.emptySet())) {
- channel.position(startOffset);
-
- Digest digest = factory.create();
- digest.init();
-
- if (blockSize == 0) {
- while (effectiveLength > 0L) {
- int remainLen = Math.min(digestBuf.length, (int) effectiveLength);
- ByteBuffer bb = wb;
- if (remainLen < digestBuf.length) {
- bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
- }
- bb.clear(); // prepare for next read
-
- int readLen = channel.read(bb);
- if (readLen < 0) {
- break;
- }
-
- effectiveLength -= readLen;
- digest.update(digestBuf, 0, readLen);
- }
-
- byte[] hashValue = digest.digest();
- if (log.isTraceEnabled()) {
- log.trace("doCheckFileHash({})[{}] offset={}, length={} - algo={}, hash={}",
- getServerSession(), file, startOffset, length,
- digest.getAlgorithm(), BufferUtils.toHex(':', hashValue));
- }
- buffer.putBytes(hashValue);
- } else {
- for (int count = 0; effectiveLength > 0L; count++) {
- int remainLen = Math.min(digestBuf.length, (int) effectiveLength);
- ByteBuffer bb = wb;
- if (remainLen < digestBuf.length) {
- bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
- }
- bb.clear(); // prepare for next read
-
- int readLen = channel.read(bb);
- if (readLen < 0) {
- break;
- }
-
- effectiveLength -= readLen;
- digest.update(digestBuf, 0, readLen);
-
- byte[] hashValue = digest.digest(); // NOTE: this also resets the hash for the next read
- if (log.isTraceEnabled()) {
- log.trace("doCheckFileHash({})({})[{}] offset={}, length={} - algo={}, hash={}",
- getServerSession(), file, count, startOffset, length,
- digest.getAlgorithm(), BufferUtils.toHex(':', hashValue));
- }
- buffer.putBytes(hashValue);
- }
- }
- }
- }
-
- protected void doMD5Hash(Buffer buffer, int id, String targetType) throws IOException {
- String target = buffer.getString();
- long startOffset = buffer.getLong();
- long length = buffer.getLong();
- byte[] quickCheckHash = buffer.getBytes();
- byte[] hashValue;
-
- try {
- hashValue = doMD5Hash(id, targetType, target, startOffset, length, quickCheckHash);
- if (log.isTraceEnabled()) {
- log.trace("doMD5Hash({})({})[{}] offset={}, length={}, quick-hash={} - hash={}",
- getServerSession(), targetType, target, startOffset, length,
- BufferUtils.toHex(':', quickCheckHash),
- BufferUtils.toHex(':', hashValue));
- }
-
- } catch (Exception e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- buffer.clear();
- buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
- buffer.putInt(id);
- buffer.putString(targetType);
- buffer.putBytes(hashValue);
- send(buffer);
- }
-
+ @Override
protected byte[] doMD5Hash(
int id, String targetType, String target, long startOffset, long length, byte[] quickCheckHash)
throws Exception {
@@ -941,7 +553,7 @@ public class SftpSubsystem
*/
int access = fileHandle.getAccessMask();
if ((access & SftpConstants.ACE4_READ_DATA) == 0) {
- throw new AccessDeniedException("File not opened for read: " + path);
+ throw new AccessDeniedException(path.toString(), path.toString(), "File not opened for read");
}
} else {
path = resolveFile(target);
@@ -969,105 +581,6 @@ public class SftpSubsystem
return doMD5Hash(id, path, startOffset, effectiveLength, quickCheckHash);
}
- protected byte[] doMD5Hash(int id, Path path, long startOffset, long length, byte[] quickCheckHash) throws Exception {
- ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", startOffset);
- ValidateUtils.checkTrue(length > 0L, "Invalid length: %d", length);
- if (!BuiltinDigests.md5.isSupported()) {
- throw new UnsupportedOperationException(BuiltinDigests.md5.getAlgorithm() + " hash not supported");
- }
-
- Digest digest = BuiltinDigests.md5.create();
- digest.init();
-
- long effectiveLength = length;
- byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
- ByteBuffer wb = ByteBuffer.wrap(digestBuf);
- boolean hashMatches = false;
- byte[] hashValue = null;
- SftpFileSystemAccessor accessor = getFileSystemAccessor();
- try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, path, null, EnumSet.of(StandardOpenOption.READ))) {
- channel.position(startOffset);
-
- /*
- * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1:
- *
- * If this is a zero length string, the client does not have the
- * data, and is requesting the hash for reasons other than comparing
- * with a local file. The server MAY return SSH_FX_OP_UNSUPPORTED in
- * this case.
- */
- if (NumberUtils.length(quickCheckHash) <= 0) {
- // TODO consider limiting it - e.g., if the requested effective length is <= than some (configurable) threshold
- hashMatches = true;
- } else {
- int readLen = channel.read(wb);
- if (readLen < 0) {
- throw new EOFException("EOF while read initial buffer from " + path);
- }
- effectiveLength -= readLen;
- digest.update(digestBuf, 0, readLen);
-
- hashValue = digest.digest();
- hashMatches = Arrays.equals(quickCheckHash, hashValue);
- if (hashMatches) {
- /*
- * Need to re-initialize the digester due to the Javadoc:
- *
- * "The digest method can be called once for a given number
- * of updates. After digest has been called, the MessageDigest
- * object is reset to its initialized state."
- */
- if (effectiveLength > 0L) {
- digest = BuiltinDigests.md5.create();
- digest.init();
- digest.update(digestBuf, 0, readLen);
- hashValue = null; // start again
- }
- } else {
- if (log.isTraceEnabled()) {
- log.trace("doMD5Hash({})({}) offset={}, length={} - quick-hash mismatched expected={}, actual={}",
- getServerSession(), path, startOffset, length,
- BufferUtils.toHex(':', quickCheckHash),
- BufferUtils.toHex(':', hashValue));
- }
- }
- }
-
- if (hashMatches) {
- while (effectiveLength > 0L) {
- int remainLen = Math.min(digestBuf.length, (int) effectiveLength);
- ByteBuffer bb = wb;
- if (remainLen < digestBuf.length) {
- bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
- }
- bb.clear(); // prepare for next read
-
- int readLen = channel.read(bb);
- if (readLen < 0) {
- break; // user may have specified more than we have available
- }
- effectiveLength -= readLen;
- digest.update(digestBuf, 0, readLen);
- }
-
- if (hashValue == null) { // check if did any more iterations after the quick hash
- hashValue = digest.digest();
- }
- } else {
- hashValue = GenericUtils.EMPTY_BYTE_ARRAY;
- }
- }
-
- if (log.isTraceEnabled()) {
- log.trace("doMD5Hash({})({}) offset={}, length={} - matches={}, quick={} hash={}",
- getServerSession(), path, startOffset, length, hashMatches,
- BufferUtils.toHex(':', quickCheckHash),
- BufferUtils.toHex(':', hashValue));
- }
-
- return hashValue;
- }
-
protected void doVersionSelect(Buffer buffer, int id) throws IOException {
String proposed = buffer.getString();
ServerSession session = getServerSession();
@@ -1101,100 +614,7 @@ public class SftpSubsystem
}
}
- /**
- * @param buffer The {@link Buffer} holding the request
- * @param id The request id
- * @param proposed The proposed value
- * @return A {@link Boolean} indicating whether to accept/reject the proposal.
- * If {@code null} then rejection response has been sent, otherwise and
- * appropriate response is generated
- * @throws IOException If failed send an independent rejection response
- */
- protected Boolean validateProposedVersion(Buffer buffer, int id, String proposed) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("validateProposedVersion({})[id={}] SSH_FXP_EXTENDED(version-select) (version={})",
- getServerSession(), id, proposed);
- }
-
- if (GenericUtils.length(proposed) != 1) {
- return Boolean.FALSE;
- }
-
- char digit = proposed.charAt(0);
- if ((digit < '0') || (digit > '9')) {
- return Boolean.FALSE;
- }
-
- int value = digit - '0';
- String all = checkVersionCompatibility(buffer, id, value, SftpConstants.SSH_FX_FAILURE);
- if (GenericUtils.isEmpty(all)) { // validation failed
- return null;
- } else {
- return Boolean.TRUE;
- }
- }
-
- /**
- * Checks if a proposed version is within supported range. <B>Note:</B>
- * if the user forced a specific value via the {@link #SFTP_VERSION}
- * property, then it is used to validate the proposed value
- *
- * @param buffer The {@link Buffer} containing the request
- * @param id The SSH message ID to be used to send the failure message
- * if required
- * @param proposed The proposed version value
- * @param failureOpcode The failure opcode to send if validation fails
- * @return A {@link String} of comma separated values representing all
- * the supported version - {@code null} if validation failed and an
- * appropriate status message was sent
- * @throws IOException If failed to send the failure status message
- */
- protected String checkVersionCompatibility(Buffer buffer, int id, int proposed, int failureOpcode) throws IOException {
- int low = LOWER_SFTP_IMPL;
- int hig = HIGHER_SFTP_IMPL;
- String available = ALL_SFTP_IMPL;
- // check if user wants to use a specific version
- ServerSession session = getServerSession();
- Integer sftpVersion = session.getInteger(SFTP_VERSION);
- if (sftpVersion != null) {
- int forcedValue = sftpVersion;
- if ((forcedValue < LOWER_SFTP_IMPL) || (forcedValue > HIGHER_SFTP_IMPL)) {
- throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available);
- }
- hig = sftpVersion;
- low = hig;
- available = sftpVersion.toString();
- }
-
- if (log.isTraceEnabled()) {
- log.trace("checkVersionCompatibility({})[id={}] - proposed={}, available={}",
- getServerSession(), id, proposed, available);
- }
-
- if ((proposed < low) || (proposed > hig)) {
- sendStatus(BufferUtils.clear(buffer), id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available);
- return null;
- }
-
- return available;
- }
-
- 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();
-
- try {
- doBlock(id, handle, offset, length, mask);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
+ @Override
protected void doBlock(int id, String handle, long offset, long length, int mask) throws IOException {
Handle p = handles.get(handle);
if (log.isDebugEnabled()) {
@@ -1215,20 +635,7 @@ public class SftpSubsystem
listener.blocked(session, handle, fileHandle, offset, length, mask, null);
}
- protected void doUnblock(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- long length = buffer.getLong();
- try {
- doUnblock(id, handle, offset, length);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
+ @Override
protected void doUnblock(int id, String handle, long offset, long length) throws IOException {
Handle p = handles.get(handle);
if (log.isDebugEnabled()) {
@@ -1249,172 +656,7 @@ public class SftpSubsystem
listener.unblocked(session, handle, fileHandle, offset, length, null);
}
- protected void doLink(Buffer buffer, int id) throws IOException {
- String targetPath = buffer.getString();
- String linkPath = buffer.getString();
- boolean symLink = buffer.getBoolean();
-
- try {
- if (log.isDebugEnabled()) {
- log.debug("doLink({})[id={}] SSH_FXP_LINK linkpath={}, targetpath={}, symlink={}",
- getServerSession(), id, linkPath, targetPath, symLink);
- }
-
- doLink(id, targetPath, linkPath, symLink);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doLink(int id, String targetPath, String linkPath, boolean symLink) throws IOException {
- createLink(id, targetPath, linkPath, symLink);
- }
-
- protected void doSymLink(Buffer buffer, int id) throws IOException {
- String targetPath = buffer.getString();
- String linkPath = buffer.getString();
- try {
- if (log.isDebugEnabled()) {
- log.debug("doSymLink({})[id={}] SSH_FXP_SYMLINK linkpath={}, targetpath={}",
- getServerSession(), id, targetPath, linkPath);
- }
- doSymLink(id, targetPath, linkPath);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doSymLink(int id, String targetPath, String linkPath) throws IOException {
- createLink(id, targetPath, linkPath, true);
- }
-
- protected void createLink(int id, String existingPath, String linkPath, boolean symLink) throws IOException {
- Path link = resolveFile(linkPath);
- Path existing = fileSystem.getPath(existingPath);
- if (log.isDebugEnabled()) {
- log.debug("createLink({})[id={}], existing={}[{}], link={}[{}], symlink={})",
- getServerSession(), id, linkPath, link, existingPath, existing, symLink);
- }
-
- SftpEventListener listener = getSftpEventListenerProxy();
- ServerSession session = getServerSession();
- listener.linking(session, link, existing, symLink);
- try {
- if (symLink) {
- Files.createSymbolicLink(link, existing);
- } else {
- Files.createLink(link, existing);
- }
- } catch (IOException | RuntimeException e) {
- listener.linked(session, link, existing, symLink, e);
- throw e;
- }
- listener.linked(session, link, existing, symLink, null);
- }
-
- protected void doReadLink(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- String l;
- try {
- if (log.isDebugEnabled()) {
- log.debug("doReadLink({})[id={}] SSH_FXP_READLINK path={}",
- getServerSession(), id, path);
- }
- l = doReadLink(id, path);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendLink(BufferUtils.clear(buffer), id, l);
- }
-
- protected String doReadLink(int id, String path) throws IOException {
- Path f = resolveFile(path);
- Path t = Files.readSymbolicLink(f);
- if (log.isDebugEnabled()) {
- log.debug("doReadLink({})[id={}] path={}[{}]: {}",
- getServerSession(), id, path, f, t);
- }
- return t.toString();
- }
-
- protected void doRename(Buffer buffer, int id) throws IOException {
- String oldPath = buffer.getString();
- String newPath = buffer.getString();
- int flags = 0;
- if (version >= SftpConstants.SFTP_V5) {
- flags = buffer.getInt();
- }
- try {
- doRename(id, oldPath, newPath, flags);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doRename(int id, String oldPath, String newPath, int flags) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("doRename({})[id={}] SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
- getServerSession(), id, oldPath, newPath, Integer.toHexString(flags));
- }
-
- Collection<CopyOption> opts = Collections.emptyList();
- if (flags != 0) {
- opts = new ArrayList<>();
- if ((flags & SftpConstants.SSH_FXP_RENAME_ATOMIC) == SftpConstants.SSH_FXP_RENAME_ATOMIC) {
- opts.add(StandardCopyOption.ATOMIC_MOVE);
- }
- if ((flags & SftpConstants.SSH_FXP_RENAME_OVERWRITE) == SftpConstants.SSH_FXP_RENAME_OVERWRITE) {
- opts.add(StandardCopyOption.REPLACE_EXISTING);
- }
- }
-
- doRename(id, oldPath, newPath, opts);
- }
-
- protected void doRename(int id, String oldPath, String newPath, Collection<CopyOption> opts) throws IOException {
- Path o = resolveFile(oldPath);
- Path n = resolveFile(newPath);
- SftpEventListener listener = getSftpEventListenerProxy();
- ServerSession session = getServerSession();
-
- listener.moving(session, o, n, opts);
- try {
- Files.move(o, n, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
- } catch (IOException | RuntimeException e) {
- listener.moved(session, o, n, opts, e);
- throw e;
- }
- listener.moved(session, o, n, opts, null);
- }
-
- // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
- protected void doCopyData(Buffer buffer, int id) throws IOException {
- String readHandle = buffer.getString();
- long readOffset = buffer.getLong();
- long readLength = buffer.getLong();
- String writeHandle = buffer.getString();
- long writeOffset = buffer.getLong();
- try {
- doCopyData(id, readHandle, readOffset, readLength, writeHandle, writeOffset);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
+ @Override
@SuppressWarnings("resource")
protected void doCopyData(int id, String readHandle, long readOffset, long readLength, String writeHandle, long writeOffset) throws IOException {
boolean inPlaceCopy = readHandle.equals(writeHandle);
@@ -1431,7 +673,7 @@ public class SftpSubsystem
Path srcPath = srcHandle.getFile();
int srcAccess = srcHandle.getAccessMask();
if ((srcAccess & SftpConstants.ACE4_READ_DATA) != SftpConstants.ACE4_READ_DATA) {
- throw new AccessDeniedException("File not opened for read: " + srcPath);
+ throw new AccessDeniedException(srcPath.toString(), srcPath.toString(), "Source file not opened for read");
}
ValidateUtils.checkTrue(readLength >= 0L, "Invalid read length: %d", readLength);
@@ -1452,7 +694,7 @@ public class SftpSubsystem
FileHandle dstHandle = inPlaceCopy ? srcHandle : validateHandle(writeHandle, wh, FileHandle.class);
int dstAccess = dstHandle.getAccessMask();
if ((dstAccess & SftpConstants.ACE4_WRITE_DATA) != SftpConstants.ACE4_WRITE_DATA) {
- throw new AccessDeniedException("File not opened for write: " + srcHandle);
+ throw new AccessDeniedException(srcHandle.toString(), srcHandle.toString(), "Source handle not opened for write");
}
ValidateUtils.checkTrue(writeOffset >= 0L, "Invalid write offset: %d", writeOffset);
@@ -1488,344 +730,7 @@ public class SftpSubsystem
}
}
- // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6
- protected void doCopyFile(Buffer buffer, int id) throws IOException {
- String srcFile = buffer.getString();
- String dstFile = buffer.getString();
- boolean overwriteDestination = buffer.getBoolean();
-
- try {
- doCopyFile(id, srcFile, dstFile, overwriteDestination);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doCopyFile(int id, String srcFile, String dstFile, boolean overwriteDestination) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("doCopyFile({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, dst={}, overwrite=0x{})",
- getServerSession(), id, SftpConstants.EXT_COPY_FILE,
- srcFile, dstFile, overwriteDestination);
- }
-
- doCopyFile(id, srcFile, dstFile,
- overwriteDestination
- ? Collections.singletonList(StandardCopyOption.REPLACE_EXISTING)
- : Collections.emptyList());
- }
-
- protected void doCopyFile(int id, String srcFile, String dstFile, Collection<CopyOption> opts) throws IOException {
- Path src = resolveFile(srcFile);
- Path dst = resolveFile(dstFile);
- Files.copy(src, dst, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
- }
-
- protected void doStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
- if (version >= SftpConstants.SFTP_V4) {
- flags = buffer.getInt();
- }
-
- Map<String, Object> attrs;
- try {
- attrs = doStat(id, path, flags);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendAttrs(BufferUtils.clear(buffer), id, attrs);
- }
-
- protected Map<String, Object> doStat(int id, String path, int flags) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("doStat({})[id={}] SSH_FXP_STAT (path={}, flags=0x{})",
- getServerSession(), id, path, Integer.toHexString(flags));
- }
-
- /*
- * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT
- * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not.
- */
- Path p = resolveFile(path);
- return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(true));
- }
-
- protected void doRealPath(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({})[id={}] SSH_FXP_REALPATH (path={})", getServerSession(), id, path);
- }
- path = GenericUtils.trimToEmpty(path);
- if (GenericUtils.isEmpty(path)) {
- path = ".";
- }
-
- Map<String, ?> attrs = Collections.emptyMap();
- Pair<Path, Boolean> result;
- try {
- if (version < SftpConstants.SFTP_V6) {
- /*
- * See http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt:
- *
- * The SSH_FXP_REALPATH request can be used to have the server
- * canonicalize any given path name to an absolute path.
- *
- * See also SSHD-294
- */
- Path p = resolveFile(path);
- LinkOption[] options =
- getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
- result = doRealPathV345(id, path, p, options);
- } else {
- /*
- * See https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
- *
- * This field is optional, and if it is not present in the packet, it
- * is assumed to be SSH_FXP_REALPATH_NO_CHECK.
- */
- int control = SftpConstants.SSH_FXP_REALPATH_NO_CHECK;
- if (buffer.available() > 0) {
- control = buffer.getUByte();
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({}) - control=0x{} for path={}",
- getServerSession(), Integer.toHexString(control), path);
- }
- }
-
- Collection<String> extraPaths = new LinkedList<>();
- while (buffer.available() > 0) {
- extraPaths.add(buffer.getString());
- }
-
- Path p = resolveFile(path);
- LinkOption[] options =
- getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
- result = doRealPathV6(id, path, extraPaths, p, options);
-
- p = result.getFirst();
- options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
- Boolean status = result.getSecond();
- switch (control) {
- case SftpConstants.SSH_FXP_REALPATH_STAT_IF:
- if (status == null) {
- attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
- } else if (status) {
- try {
- attrs = getAttributes(p, options);
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({}) - failed ({}) to retrieve attributes of {}: {}",
- getServerSession(), e.getClass().getSimpleName(), p, e.getMessage());
- }
- if (log.isTraceEnabled()) {
- log.trace("doRealPath(" + getServerSession() + ")[" + p + "] attributes retrieval failure details", e);
- }
- }
- } else {
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({}) - dummy attributes for non-existing file: {}", getServerSession(), p);
- }
- }
- break;
- case SftpConstants.SSH_FXP_REALPATH_STAT_ALWAYS:
- if (status == null) {
- attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
- } else if (status) {
- attrs = getAttributes(p, options);
- } else {
- throw new FileNotFoundException(p.toString());
- }
- break;
- case SftpConstants.SSH_FXP_REALPATH_NO_CHECK:
- break;
- default:
- log.warn("doRealPath({}) unknown control value 0x{} for path={}",
- getServerSession(), Integer.toHexString(control), p);
- }
- }
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendPath(BufferUtils.clear(buffer), id, result.getFirst(), attrs);
- }
-
- protected Pair<Path, Boolean> doRealPathV6(
- int id, String path, Collection<String> extraPaths, Path p, LinkOption... options) throws IOException {
- int numExtra = GenericUtils.size(extraPaths);
- if (numExtra > 0) {
- if (log.isDebugEnabled()) {
- log.debug("doRealPathV6({})[id={}] path={}, extra={}",
- getServerSession(), id, path, extraPaths);
- }
- StringBuilder sb = new StringBuilder(GenericUtils.length(path) + numExtra * 8);
- sb.append(path);
-
- for (String p2 : extraPaths) {
- p = p.resolve(p2);
- options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
- sb.append('/').append(p2);
- }
-
- path = sb.toString();
- }
-
- return validateRealPath(id, path, p, options);
- }
-
- protected Pair<Path, Boolean> doRealPathV345(int id, String path, Path p, LinkOption... options) throws IOException {
- return validateRealPath(id, path, p, options);
- }
-
- /**
- * @param id The request identifier
- * @param path The original path
- * @param f The resolve {@link Path}
- * @param options The {@link LinkOption}s to use to verify file existence and access
- * @return A {@link Pair} whose left-hand is the <U>absolute <B>normalized</B></U>
- * {@link Path} and right-hand is a {@link Boolean} indicating its status
- * @throws IOException If failed to validate the file
- * @see IoUtils#checkFileExists(Path, LinkOption...)
- */
- protected Pair<Path, Boolean> validateRealPath(int id, String path, Path f, LinkOption... options) throws IOException {
- Path p = normalize(f);
- Boolean status = IoUtils.checkFileExists(p, options);
- return new Pair<>(p, status);
- }
-
- protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- try {
- doRemoveDirectory(id, path, IoUtils.getLinkOptions(false));
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doRemoveDirectory(int id, String path, LinkOption... options) throws IOException {
- Path p = resolveFile(path);
- if (log.isDebugEnabled()) {
- log.debug("doRemoveDirectory({})[id={}] SSH_FXP_RMDIR (path={})[{}]",
- getServerSession(), id, path, p);
- }
- if (Files.isDirectory(p, options)) {
- doRemove(id, p);
- } else {
- throw new NotDirectoryException(p.toString());
- }
- }
-
- /**
- * Called when need to delete a file / directory - also informs the {@link SftpEventListener}
- *
- * @param id Deletion request ID
- * @param p {@link Path} to delete
- * @throws IOException If failed to delete
- */
- protected void doRemove(int id, Path p) throws IOException {
- SftpEventListener listener = getSftpEventListenerProxy();
- ServerSession session = getServerSession();
- listener.removing(session, p);
- try {
- Files.delete(p);
- } catch (IOException | RuntimeException e) {
- listener.removed(session, p, e);
- throw e;
- }
- listener.removed(session, p, null);
- }
-
- protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
- try {
- doMakeDirectory(id, path, attrs, IoUtils.getLinkOptions(false));
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doMakeDirectory(int id, String path, Map<String, ?> attrs, LinkOption... options) throws IOException {
- Path p = resolveFile(path);
- if (log.isDebugEnabled()) {
- log.debug("doMakeDirectory({})[id={}] SSH_FXP_MKDIR (path={}[{}], attrs={})",
- getServerSession(), id, path, p, attrs);
- }
-
- Boolean status = IoUtils.checkFileExists(p, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot validate make-directory existence for " + p);
- }
-
- if (status) {
- if (Files.isDirectory(p, options)) {
- throw new FileAlreadyExistsException(p.toString(), p.toString(), "Target directory already exists");
- } else {
- throw new FileNotFoundException(p.toString() + " already exists as a file");
- }
- } else {
- SftpEventListener listener = getSftpEventListenerProxy();
- ServerSession session = getServerSession();
- listener.creating(session, p, attrs);
- try {
- Files.createDirectory(p);
- doSetAttributes(p, attrs);
- } catch (IOException | RuntimeException e) {
- listener.created(session, p, attrs, e);
- throw e;
- }
- listener.created(session, p, attrs, null);
- }
- }
-
- protected void doRemove(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- try {
- /*
- * If 'filename' is a symbolic link, the link is removed,
- * not the file it points to.
- */
- doRemove(id, path, IoUtils.getLinkOptions(false));
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doRemove(int id, String path, LinkOption... options) throws IOException {
- Path p = resolveFile(path);
- if (log.isDebugEnabled()) {
- log.debug("doRemove({})[id={}] SSH_FXP_REMOVE (path={}[{}])",
- getServerSession(), id, path, p);
- }
-
- Boolean status = IoUtils.checkFileExists(p, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
- }
- if (!status) {
- throw new FileNotFoundException(p.toString());
- } else if (Files.isDirectory(p, options)) {
- throw new SftpException(SftpConstants.SSH_FX_FILE_IS_A_DIRECTORY, p.toString() + " is a folder");
- } else {
- doRemove(id, p);
- }
- }
-
+ @Override
protected void doReadDir(Buffer buffer, int id) throws IOException {
String handle = buffer.getString();
Handle h = handles.get(handle);
@@ -1847,15 +752,15 @@ public class SftpSubsystem
getPathResolutionLinkOption(SftpConstants.SSH_FXP_READDIR, "", file);
Boolean status = IoUtils.checkFileExists(file, options);
if (status == null) {
- throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
+ throw new AccessDeniedException(file.toString(), file.toString(), "Cannot determine existence of read-dir");
}
if (!status) {
- throw new FileNotFoundException(file.toString());
+ throw new NoSuchFileException(file.toString(), file.toString(), "Non-existant directory");
} else if (!Files.isDirectory(file, options)) {
throw new NotDirectoryException(file.toString());
} else if (!Files.isReadable(file)) {
- throw new AccessDeniedException("Not readable: " + file.toString());
+ throw new AccessDeniedException(file.toString(), file.toString(), "Not readable");
}
if (dh.isSendDot() || dh.isSendDotDot() || dh.hasNext()) {
@@ -1892,47 +797,26 @@ public class SftpSubsystem
Objects.requireNonNull(reply, "No reply buffer created");
} catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
+ sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_READDIR, handle);
return;
}
send(reply);
}
- protected void doOpenDir(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- String handle;
-
- try {
- Path p = resolveNormalizedLocation(path);
- if (log.isDebugEnabled()) {
- log.debug("doOpenDir({})[id={}] SSH_FXP_OPENDIR (path={})[{}]",
- getServerSession(), id, path, p);
- }
-
- LinkOption[] options =
- getPathResolutionLinkOption(SftpConstants.SSH_FXP_OPENDIR, "", p);
- handle = doOpenDir(id, path, p, options);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendHandle(BufferUtils.clear(buffer), id, handle);
- }
-
+ @Override
protected String doOpenDir(int id, String path, Path p, LinkOption... options) throws IOException {
Boolean status = IoUtils.checkFileExists(p, options);
if (status == null) {
- throw new AccessDeniedException("Cannot determine open-dir existence for " + p);
+ throw new AccessDeniedException(p.toString(), p.toString(), "Cannot determine open-dir existence");
}
if (!status) {
- throw new FileNotFoundException(path);
+ throw new NoSuchFileException(path, path, "Referenced target directory N/A");
} else if (!Files.isDirectory(p, options)) {
throw new NotDirectoryException(path);
} else if (!Files.isReadable(p)) {
- throw new AccessDeniedException("Not readable: " + p);
+ throw new AccessDeniedException(p.toString(), p.toString(), "Not readable");
} else {
String handle = generateFileHandle(p);
DirectoryHandle dirHandle = new DirectoryHandle(this, p, handle);
@@ -1941,19 +825,7 @@ public class SftpSubsystem
}
}
- protected void doFSetStat(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
- try {
- doFSetStat(id, handle, attrs);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
+ @Override
protected void doFSetStat(int id, String handle, Map<String, ?> attrs) throws IOException {
Handle h = handles.get(handle);
if (log.isDebugEnabled()) {
@@ -1964,102 +836,19 @@ public class SftpSubsystem
doSetAttributes(validateHandle(handle, h, Handle.class).getFile(), attrs);
}
- protected void doSetStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
- try {
- doSetStat(id, path, attrs);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
- }
-
- protected void doSetStat(int id, String path, Map<String, ?> attrs) throws IOException {
+ @Override
+ protected Map<String, Object> doFStat(int id, String handle, int flags) throws IOException {
+ Handle h = handles.get(handle);
if (log.isDebugEnabled()) {
- log.debug("doSetStat({})[id={}] SSH_FXP_SETSTAT (path={}, attrs={})",
- getServerSession(), id, path, attrs);
+ log.debug("doFStat({})[id={}] SSH_FXP_FSTAT (handle={}[{}], flags=0x{})",
+ getServerSession(), id, handle, h, Integer.toHexString(flags));
}
- Path p = resolveFile(path);
- doSetAttributes(p, attrs);
- }
-
- protected void doFStat(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
- if (version >= SftpConstants.SFTP_V4) {
- flags = buffer.getInt();
- }
-
- Map<String, ?> attrs;
- try {
- attrs = doFStat(id, handle, flags);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendAttrs(BufferUtils.clear(buffer), id, attrs);
- }
-
- protected Map<String, Object> doFStat(int id, String handle, int flags) throws IOException {
- Handle h = handles.get(handle);
- if (log.isDebugEnabled()) {
- log.debug("doFStat({})[id={}] SSH_FXP_FSTAT (handle={}[{}], flags=0x{})",
- getServerSession(), id, handle, h, Integer.toHexString(flags));
- }
-
- return resolveFileAttributes(validateHandle(handle, h, Handle.class).getFile(), flags, IoUtils.getLinkOptions(true));
- }
-
- protected void doLStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
- if (version >= SftpConstants.SFTP_V4) {
- flags = buffer.getInt();
- }
-
- Map<String, ?> attrs;
- try {
- attrs = doLStat(id, path, flags);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendAttrs(BufferUtils.clear(buffer), id, attrs);
- }
-
- protected Map<String, Object> doLStat(int id, String path, int flags) throws IOException {
- Path p = resolveFile(path);
- if (log.isDebugEnabled()) {
- log.debug("doLStat({})[id={}] SSH_FXP_LSTAT (path={}[{}], flags=0x{})",
- getServerSession(), id, path, p, Integer.toHexString(flags));
- }
-
- /*
- * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT
- * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not.
- */
- return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false));
- }
-
- protected void doWrite(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- int length = buffer.getInt();
- try {
- doWrite(id, handle, offset, length, buffer.array(), buffer.rpos(), buffer.available());
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
+
+ Handle fileHandle = validateHandle(handle, h, Handle.class);
+ return resolveFileAttributes(fileHandle.getFile(), flags, IoUtils.getLinkOptions(true));
}
+ @Override
protected void doWrite(int id, String handle, long offset, int length, byte[] data, int doff, int remaining) throws IOException {
Handle h = handles.get(handle);
if (log.isTraceEnabled()) {
@@ -2091,43 +880,7 @@ public class SftpSubsystem
listener.written(getServerSession(), handle, fh, offset, data, doff, length, null);
}
- protected void doRead(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- int requestedLength = buffer.getInt();
- int maxAllowed = getServerSession().getIntProperty(MAX_READDATA_PACKET_LENGTH_PROP, DEFAULT_MAX_READDATA_PACKET_LENGTH);
- int readLen = Math.min(requestedLength, maxAllowed);
- if (log.isTraceEnabled()) {
- log.trace("doRead({})[id={}]({})[offset={}] - req={}, max={}, effective={}",
- getServerSession(), id, handle, offset, requestedLength, maxAllowed, readLen);
- }
-
- try {
- ValidateUtils.checkTrue(readLen >= 0, "Illegal requested read length: %d", readLen);
-
- buffer.clear();
- buffer.ensureCapacity(readLen + Long.SIZE /* the header */, IntUnaryOperator.identity());
-
- buffer.putByte((byte) SftpConstants.SSH_FXP_DATA);
- buffer.putInt(id);
- int lenPos = buffer.wpos();
- buffer.putInt(0);
-
- int startPos = buffer.wpos();
- int len = doRead(id, handle, offset, readLen, buffer.array(), startPos);
- if (len < 0) {
- throw new EOFException("Unable to read " + readLen + " bytes from offset=" + offset + " of " + handle);
- }
- buffer.wpos(startPos + len);
- BufferUtils.updateLengthPlaceholder(buffer, lenPos, len);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- send(buffer);
- }
-
+ @Override
protected int doRead(int id, String handle, long offset, int length, byte[] data, int doff) throws IOException {
Handle h = handles.get(handle);
if (log.isTraceEnabled()) {
@@ -2151,18 +904,7 @@ public class SftpSubsystem
return readLen;
}
- protected void doClose(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- try {
- doClose(id, handle);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "", "");
- }
-
+ @Override
protected void doClose(int id, String handle) throws IOException {
Handle h = handles.remove(handle);
if (log.isDebugEnabled()) {
@@ -2175,81 +917,7 @@ public class SftpSubsystem
listener.close(getServerSession(), handle, h);
}
- protected void doOpen(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- /*
- * Be consistent with FileChannel#open - if no mode specified then READ is assumed
- */
- int access = 0;
- if (version >= SftpConstants.SFTP_V5) {
- access = buffer.getInt();
- if (access == 0) {
- access = SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES;
- }
- }
-
- int pflags = buffer.getInt();
- if (pflags == 0) {
- pflags = SftpConstants.SSH_FXF_READ;
- }
-
- if (version < SftpConstants.SFTP_V5) {
- int flags = pflags;
- pflags = 0;
- switch (flags & (SftpConstants.SSH_FXF_READ | SftpConstants.SSH_FXF_WRITE)) {
- case SftpConstants.SSH_FXF_READ:
- access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES;
- break;
- case SftpConstants.SSH_FXF_WRITE:
- access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES;
- break;
- default:
- access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES;
- access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES;
- break;
- }
- if ((flags & SftpConstants.SSH_FXF_APPEND) != 0) {
- access |= SftpConstants.ACE4_APPEND_DATA;
- pflags |= SftpConstants.SSH_FXF_APPEND_DATA | SftpConstants.SSH_FXF_APPEND_DATA_ATOMIC;
- }
- if ((flags & SftpConstants.SSH_FXF_CREAT) != 0) {
- if ((flags & SftpConstants.SSH_FXF_EXCL) != 0) {
- pflags |= SftpConstants.SSH_FXF_CREATE_NEW;
- } else if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) {
- pflags |= SftpConstants.SSH_FXF_CREATE_TRUNCATE;
- } else {
- pflags |= SftpConstants.SSH_FXF_OPEN_OR_CREATE;
- }
- } else {
- if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) {
- pflags |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
- } else {
- pflags |= SftpConstants.SSH_FXF_OPEN_EXISTING;
- }
- }
- }
-
- Map<String, Object> attrs = readAttrs(buffer);
- String handle;
- try {
- handle = doOpen(id, path, pflags, access, attrs);
- } catch (IOException | RuntimeException e) {
- sendStatus(BufferUtils.clear(buffer), id, e);
- return;
- }
-
- sendHandle(BufferUtils.clear(buffer), id, handle);
- }
-
- /**
- * @param id Request id
- * @param path Path
- * @param pflags Open mode flags - see {@code SSH_FXF_XXX} flags
- * @param access Access mode flags - see {@code ACE4_XXX} flags
- * @param attrs Requested attributes
- * @return The assigned (opaque) handle
- * @throws IOException if failed to execute
- */
+ @Override
protected String doOpen(int id, String path, int pflags, int access, Map<String, Object> attrs) throws IOException {
if (log.isDebugEnabled()) {
log.debug("doOpen({})[id={}] SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
@@ -2320,1068 +988,7 @@ public class SftpSubsystem
send(buffer);
}
- protected void appendExtensions(Buffer buffer, String supportedVersions) {
- appendVersionsExtension(buffer, supportedVersions);
- appendNewlineExtension(buffer, resolveNewlineValue(getServerSession()));
- appendVendorIdExtension(buffer, VersionProperties.getVersionProperties());
- appendOpenSSHExtensions(buffer);
- appendAclSupportedExtension(buffer);
-
- Map<String, OptionalFeature> extensions = getSupportedClientExtensions();
- int numExtensions = GenericUtils.size(extensions);
- List<String> extras = (numExtensions <= 0) ? Collections.emptyList() : new ArrayList<>(numExtensions);
- if (numExtensions > 0) {
- ServerSession session = getServerSession();
- extensions.forEach((name, f) -> {
- if (!f.isSupported()) {
- if (log.isDebugEnabled()) {
- log.debug("appendExtensions({}) skip unsupported extension={}", session, name);
- }
- return;
- }
-
- extras.add(name);
- });
- }
- appendSupportedExtension(buffer, extras);
- appendSupported2Extension(buffer, extras);
- }
-
- protected int appendAclSupportedExtension(Buffer buffer) {
- ServerSession session = getServerSession();
- Collection<Integer> maskValues = resolveAclSupportedCapabilities(session);
- int mask = AclSupportedParser.AclCapabilities.constructAclCapabilities(maskValues);
- if (mask != 0) {
- if (log.isTraceEnabled()) {
- log.trace("appendAclSupportedExtension({}) capabilities={}",
- session, AclSupportedParser.AclCapabilities.decodeAclCapabilities(mask));
- }
-
- buffer.putString(SftpConstants.EXT_ACL_SUPPORTED);
-
- // placeholder for length
- int lenPos = buffer.wpos();
- buffer.putInt(0);
- buffer.putInt(mask);
- BufferUtils.updateLengthPlaceholder(buffer, lenPos);
- }
-
- return mask;
- }
-
- protected Collection<Integer> resolveAclSupportedCapabilities(ServerSession session) {
- String override = session.getString(ACL_SUPPORTED_MASK_PROP);
- if (override == null) {
- return DEFAULT_ACL_SUPPORTED_MASK;
- }
-
- // empty means not supported
- if (log.isDebugEnabled()) {
- log.debug("resolveAclSupportedCapabilities({}) override='{}'", session, override);
- }
-
- if (override.length() == 0) {
- return Collections.emptySet();
- }
-
- String[] names = GenericUtils.split(override, ',');
- Set<Integer> maskValues = new HashSet<>(names.length);
- for (String n : names) {
- Integer v = ValidateUtils.checkNotNull(
- AclSupportedParser.AclCapabilities.getAclCapabilityValue(n), "Unknown ACL capability: %s", n);
- maskValues.add(v);
- }
-
- return maskValues;
- }
-
- protected List<OpenSSHExtension> appendOpenSSHExtensions(Buffer buffer) {
- List<OpenSSHExtension> extList = resolveOpenSSHExtensions(getServerSession());
- if (GenericUtils.isEmpty(extList)) {
- return extList;
- }
-
- for (OpenSSHExtension ext : extList) {
- buffer.putString(ext.getName());
- buffer.putString(ext.getVersion());
- }
-
- return extList;
- }
-
- protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) {
- String value = session.getString(OPENSSH_EXTENSIONS_PROP);
- if (value == null) { // No override
- return DEFAULT_OPEN_SSH_EXTENSIONS;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("resolveOpenSSHExtensions({}) override='{}'", session, value);
- }
-
- String[] pairs = GenericUtils.split(value, ',');
- int numExts = GenericUtils.length(pairs);
- if (numExts <= 0) { // User does not want to report ANY extensions
- return Collections.emptyList();
- }
-
- List<OpenSSHExtension> extList = new ArrayList<>(numExts);
- for (String nvp : pairs) {
- nvp = GenericUtils.trimToEmpty(nvp);
- if (GenericUtils.isEmpty(nvp)) {
- continue;
- }
-
- int pos = nvp.indexOf('=');
- ValidateUtils.checkTrue((pos > 0) && (pos < (nvp.length() - 1)), "Malformed OpenSSH extension spec: %s", nvp);
- String name = GenericUtils.trimToEmpty(nvp.substring(0, pos));
- String version = GenericUtils.trimToEmpty(nvp.substring(pos + 1));
- extList.add(new OpenSSHExtension(name, ValidateUtils.checkNotNullAndNotEmpty(version, "No version specified for OpenSSH extension %s", name)));
- }
-
- return extList;
- }
-
- protected Map<String, OptionalFeature> getSupportedClientExtensions() {
- ServerSession session = getServerSession();
- String value = session.getString(CLIENT_EXTENSIONS_PROP);
- if (value == null) {
- return DEFAULT_SUPPORTED_CLIENT_EXTENSIONS;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("getSupportedClientExtensions({}) override='{}'", session, value);
- }
-
- if (value.length() <= 0) { // means don't report any extensions
- return Collections.emptyMap();
- }
-
- if (value.indexOf(',') <= 0) {
- return Collections.singletonMap(value, OptionalFeature.TRUE);
- }
-
- String[] comps = GenericUtils.split(value, ',');
- Map<String, OptionalFeature> result = new LinkedHashMap<>(comps.length);
- for (String c : comps) {
- result.put(c, OptionalFeature.TRUE);
- }
-
- return result;
- }
-
- /**
- * Appends the "versions" extension to the buffer. <B>Note:</B>
- * if overriding this method make sure you either do not append anything
- * or use the correct extension name
- *
- * @param buffer The {@link Buffer} to append to
- * @param value The recommended value - ignored if {@code null}/empty
- * @see SftpConstants#EXT_VERSIONS
- */
- protected void appendVersionsExtension(Buffer buffer, String value) {
- if (GenericUtils.isEmpty(value)) {
- return;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("appendVersionsExtension({}) value={}", getServerSession(), value);
- }
-
- buffer.putString(SftpConstants.EXT_VERSIONS);
- buffer.putString(value);
- }
-
- /**
- * Appends the "newline" extension to the buffer. <B>Note:</B>
- * if overriding this method make sure you either do not append anything
- * or use the correct extension name
- *
- * @param buffer The {@link Buffer} to append to
- * @param value The recommended value - ignored if {@code null}/empty
- * @see SftpConstants#EXT_NEWLINE
- */
- protected void appendNewlineExtension(Buffer buffer, String value) {
- if (GenericUtils.isEmpty(value)) {
- return;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("appendNewlineExtension({}) value={}",
- getServerSession(), BufferUtils.toHex(':', value.getBytes(StandardCharsets.UTF_8)));
- }
-
- buffer.putString(SftpConstants.EXT_NEWLINE);
- buffer.putString(value);
- }
-
- protected String resolveNewlineValue(ServerSession session) {
- String value = session.getString(NEWLINE_VALUE);
- if (value == null) {
- return IoUtils.EOL;
- } else {
- return value; // empty means disabled
- }
- }
-
- /**
- * Appends the "vendor-id" extension to the buffer. <B>Note:</B>
- * if overriding this method make sure you either do not append anything
- * or use the correct extension name
- *
- * @param buffer The {@link Buffer} to append to
- * @param versionProperties The currently available version properties - ignored
- * if {@code null}/empty. The code expects the following values:
- * <UL>
- * <LI>{@code groupId} - as the vendor name</LI>
- * <LI>{@code artifactId} - as the product name</LI>
- * <LI>{@code version} - as the product version</LI>
- * </UL>
- * @see SftpConstants#EXT_VENDOR_ID
- * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 4.4</A>
- */
- protected void appendVendorIdExtension(Buffer buffer, Map<String, ?> versionProperties) {
- if (GenericUtils.isEmpty(versionProperties)) {
- return;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("appendVendorIdExtension({}): {}", getServerSession(), versionProperties);
- }
- buffer.putString(SftpConstants.EXT_VENDOR_ID);
-
- PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(Collections.unmodifiableMap(versionProperties));
- // placeholder for length
- int lenPos = buffer.wpos();
- buffer.putInt(0);
- buffer.putString(resolver.getStringProperty("groupId", getClass().getPackage().getName())); // vendor-name
- buffer.putString(resolver.getStringProperty("artifactId", getClass().getSimpleName())); // product-name
- buffer.putString(resolver.getStringProperty("version", FactoryManager.DEFAULT_VERSION)); // product-version
- buffer.putLong(0L); // product-build-number
- BufferUtils.updateLengthPlaceholder(buffer, lenPos);
- }
-
- /**
- * Appends the "supported" extension to the buffer. <B>Note:</B>
- * if overriding this method make sure you either do not append anything
- * or use the correct extension name
- *
- * @param buffer The {@link Buffer} to append to
- * @param extras The extra extensions that are available and can be reported
- * - may be {@code null}/empty
- */
- protected void appendSupportedExtension(Buffer buffer, Collection<String> extras) {
- buffer.putString(SftpConstants.EXT_SUPPORTED);
-
- int lenPos = buffer.wpos();
- buffer.putInt(0); // length placeholder
- // supported-attribute-mask
- buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_SIZE | SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS
- | SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME | SftpConstants.SSH_FILEXFER_ATTR_CREATETIME
- | SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME | SftpCo
<TRUNCATED>