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:d...@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>