This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push: new 925af4b [SSHD-1233] Added support for 'lim...@openssh.com' SFTP extension 925af4b is described below commit 925af4b3f6b0496499c2ca6b9302fe21987db227 Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Thu Dec 23 12:56:53 2021 +0200 [SSHD-1233] Added support for 'lim...@openssh.com' SFTP extension --- CHANGES.md | 16 +++ docs/sftp.md | 4 +- .../apache/sshd/cli/client/SftpCommandMain.java | 64 +++++++-- .../apache/sshd/util/test/JUnitTestSupport.java | 17 +++ .../common/session/helpers/AbstractSession.java | 2 +- .../org/apache/sshd/sftp/SftpModuleProperties.java | 18 ++- .../extensions/BuiltinSftpClientExtensions.java | 10 ++ .../extensions/openssh/OpenSSHFsyncExtension.java | 2 +- ...cExtension.java => OpenSSHLimitsExtension.java} | 9 +- .../openssh/OpenSSHLimitsExtensionInfo.java | 150 +++++++++++++++++++++ .../openssh/OpenSSHStatExtensionInfo.java | 18 +-- .../helpers/OpenSSHLimitsExtensionImpl.java | 52 +++++++ .../org/apache/sshd/sftp/client/fs/SftpPath.java | 2 +- .../apache/sshd/sftp/client/impl/SftpPathImpl.java | 4 +- .../sshd/sftp/common/extensions/ParserUtils.java | 4 +- .../openssh/FstatVfsExtensionParser.java | 1 + .../extensions/openssh/FsyncExtensionParser.java | 2 +- .../openssh/HardLinkExtensionParser.java | 2 +- .../openssh/LSetStatExtensionParser.java | 2 +- ...nsionParser.java => LimitsExtensionParser.java} | 10 +- .../openssh/PosixRenameExtensionParser.java | 2 +- .../extensions/openssh/StatVfsExtensionParser.java | 2 +- .../server/AbstractSftpEventListenerAdapter.java | 44 +++++- .../sftp/server/AbstractSftpSubsystemHelper.java | 91 ++++++++----- .../org/apache/sshd/sftp/server/SftpSubsystem.java | 9 +- .../java/org/apache/sshd/sftp/client/SftpTest.java | 4 +- .../sftp/client/extensions/SftpExtensionsTest.java | 2 +- .../openssh/helpers/OpenSSHExtensionsTest.java | 33 ++--- 28 files changed, 471 insertions(+), 105 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 691aed2..c884827 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,9 +20,25 @@ ## Potential compatibility issues +* A **new** SFTP configuration property has been introduced that limits the maximum amount of data that can be sent in a single *SSH_FXP_WRITE* packet - default=256KB + +```java + /** + * Force the use of a max. packet length for {@link AbstractSftpSubsystemHelper#doWrite(Buffer, int)} protection + * against malicious packets + */ + public static final Property<Integer> MAX_WRITE_DATA_PACKET_LENGTH + = Property.integer("sftp-max-writedata-packet-length", 256 * 1024); +``` + +This might cause SFTP write failures for clients that might have sent larger buffers and they have been accepted so far. If this happens, simply increase +this value (though the choice of 256KB should be compatible with the vast majority of clients). + ## Minor code helpers ## Behavioral changes and enhancements * [SSHD-1231](https://issues.apache.org/jira/browse/SSHD-1231) Public key authentication: wrong signature algorithm used (ed25519 key with ssh-rsa signature) +* [SSHD-1233](https://issues.apache.org/jira/browse/SSHD-1233) Added support for "lim...@openssh.com" SFTP extension + diff --git a/docs/sftp.md b/docs/sftp.md index e76ddf3..224f7bf 100644 --- a/docs/sftp.md +++ b/docs/sftp.md @@ -629,6 +629,7 @@ Furthermore several [OpenSSH SFTP extensions](https://github.com/openssh/openssh * `posix-ren...@openssh.com` - only client side * `stat...@openssh.com` * `lsets...@openssh.com` +* `lim...@openssh.com` On the server side, the reported standard extensions are configured via the `SftpModuleProperties.CLIENT_EXTENSIONS` configuration key, and the _OpenSSH_ ones via the `SftpModuleProperties.OPENSSH_EXTENSIONS`. @@ -693,7 +694,7 @@ try (ClientSession session = client.connect(username, host, port).verify(timeout * Add the code to handle the new extension in `AbstractSftpSubsystemHelper#executeExtendedCommand` -* Declare the extension name in `DEFAULT_SUPPORTED_CLIENT_EXTENSIONS` (same class) +* Declare the extension name in `DEFAULT_SUPPORTED_CLIENT_EXTENSIONS` or `DEFAULT_OPEN_SSH_EXTENSIONS` (same class) - according to the extension type (generic or *OpenSSH* one). * In the `org.apache.sshd.sftp.client.extensions.helpers` package implement an extension of `AbstractSftpClientExtension` for sending and receiving the newly added extension. @@ -744,7 +745,6 @@ sshd.setSubsystemFactories(Collections.singletonList(factory)); ``` - **Note:** * The code assumes that the extension name is a **string** - the draft specification actually allows an array of bytes as well, but we chose simplicity. diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java index feb5d98..2e98012 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java @@ -80,12 +80,15 @@ import org.apache.sshd.sftp.client.SftpClientFactory; import org.apache.sshd.sftp.client.SftpClientHolder; import org.apache.sshd.sftp.client.SftpVersionSelector; import org.apache.sshd.sftp.client.SftpVersionSelector.NamedVersionSelector; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatExtensionInfo; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatPathExtension; import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider; import org.apache.sshd.sftp.common.SftpConstants; import org.apache.sshd.sftp.common.SftpException; import org.apache.sshd.sftp.common.extensions.ParserUtils; +import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser; import org.slf4j.Logger; @@ -132,6 +135,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo new GetCommandExecutor(), new PutCommandExecutor(), new ProgressCommandExecutor(), + new LimitsCommandExecutor(), new HelpCommandExecutor())) { String name = e.getName(); ValidateUtils.checkTrue(map.put(name, e) == null, "Multiple commands named '%s'", name); @@ -781,7 +785,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo } else if (Files.isRegularFile(local)) { displayLocalPathInfo(local, stdout); } else { - stderr.println("Unsupported special file"); + stderr.println("[" + flags + "] Unsupported special file"); } return false; @@ -941,6 +945,35 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo /* -------------------------------------------------------------------- */ + private class LimitsCommandExecutor implements SftpCommandExecutor { + LimitsCommandExecutor() { + super(); + } + + @Override + public String getName() { + return LimitsExtensionParser.NAME; + } + + @Override + public boolean executeCommand( + String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) + throws Exception { + String[] comps = GenericUtils.split(args, ' '); + int numArgs = GenericUtils.length(comps); + ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args); + + SftpClient sftp = getClient(); + OpenSSHLimitsExtension ext = sftp.getExtension(OpenSSHLimitsExtension.class); + ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName()); + OpenSSHLimitsExtensionInfo info = ext.limits(); + printFieldsValues(info, stdout); + return false; + } + } + + /* -------------------------------------------------------------------- */ + private class StatVfsCommandExecutor implements SftpCommandExecutor { StatVfsCommandExecutor() { super(); @@ -966,18 +999,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo String remPath = resolveRemotePath( (numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args)); OpenSSHStatExtensionInfo info = ext.stat(remPath); - Field[] fields = info.getClass().getFields(); - for (Field f : fields) { - String name = f.getName(); - int mod = f.getModifiers(); - if (Modifier.isStatic(mod)) { - continue; - } - - Object value = f.get(info); - stdout.append(" ").append(name).append(": ").println(value); - } - + printFieldsValues(info, stdout); return false; } } @@ -1323,4 +1345,20 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo return false; } } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + public static void printFieldsValues(Object info, PrintStream stdout) throws Exception { + Field[] fields = info.getClass().getFields(); + for (Field f : fields) { + String name = f.getName(); + int mod = f.getModifiers(); + if (Modifier.isStatic(mod)) { + continue; + } + + Object value = f.get(info); + stdout.append(" ").append(name).append(": ").println(value); + } + } } diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java index 3a0bf0e..e3eca3d 100644 --- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java +++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java @@ -22,6 +22,8 @@ package org.apache.sshd.util.test; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -348,6 +350,21 @@ public abstract class JUnitTestSupport extends Assert { assertFalse(message + "[non-empty-actual]", actual.hasNext()); } + public static <T> void assertFieldsEqual(String extension, T expected, T actual) throws Exception { + Field[] fields = expected.getClass().getFields(); + for (Field f : fields) { + String name = f.getName(); + int mod = f.getModifiers(); + if (Modifier.isStatic(mod)) { + continue; + } + + Object expValue = f.get(expected); + Object actValue = f.get(actual); + assertEquals(extension + "[" + name + "]", expValue, actValue); + } + } + public static Path assertHierarchyTargetFolderExists(Path folder, LinkOption... options) throws IOException { if (Files.exists(folder, options)) { assertTrue("Target is an existing file instead of a folder: " + folder, Files.isDirectory(folder, options)); diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java index 6222804..08e5d7e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java @@ -1828,7 +1828,7 @@ public abstract class AbstractSession extends SessionHelper { log.debug("setOutputEncoding({}): cipher {}; mac {}; compression {}; blocks limit {}", this, outCipher, outMac, outCompression, maxRekeyBlocks); } - }; + } /** * Installs the current prepared {@link #inSettings} so that they are effective and will be applied to any future diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java index 3303913..977735d 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/SftpModuleProperties.java @@ -156,18 +156,30 @@ public final class SftpModuleProperties { public static final Property<String> NEWLINE_VALUE = Property.string("sftp-newline", IoUtils.EOL); + public static final int MIN_READDATA_PACKET_LENGTH = 32768; /** * Force the use of a max. packet length for {@link AbstractSftpSubsystemHelper#doRead(Buffer, int)} protection * against malicious packets */ public static final Property<Integer> MAX_READDATA_PACKET_LENGTH - = Property.integer("sftp-max-readdata-packet-length", 63 * 1024); + = Property.validating(Property.integer("sftp-max-readdata-packet-length", 63 * 1024), + l -> ValidateUtils.checkTrue(l >= MIN_READDATA_PACKET_LENGTH, "Length is below min.: %d", l)); + public static final int MIN_WRITEDATA_PACKET_LENGTH = 32768; /** - * Properties key for the maximum of available open handles per session. + * Force the use of a max. packet length for {@link AbstractSftpSubsystemHelper#doWrite(Buffer, int)} protection + * against malicious packets + */ + public static final Property<Integer> MAX_WRITEDATA_PACKET_LENGTH + = Property.validating(Property.integer("sftp-max-writedata-packet-length", 256 * 1024), + l -> ValidateUtils.checkTrue(l >= MIN_WRITEDATA_PACKET_LENGTH, "Length is below min.: %d", l)); + + /** + * Properties key for the maximum of available open handles per session. By default we impose virtually no limit and + * relay on the O/S. */ public static final Property<Integer> MAX_OPEN_HANDLES_PER_SESSION - = Property.integer("max-open-handles-per-session", Integer.MAX_VALUE); + = Property.integer("max-open-handles-per-session", Integer.MAX_VALUE - 1); public static final int MIN_FILE_HANDLE_SIZE = 4; // ~uint32 public static final int DEFAULT_FILE_HANDLE_SIZE = 16; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java index b531240..b5c919a 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/BuiltinSftpClientExtensions.java @@ -35,10 +35,12 @@ import org.apache.sshd.sftp.client.extensions.helpers.MD5FileExtensionImpl; import org.apache.sshd.sftp.client.extensions.helpers.MD5HandleExtensionImpl; import org.apache.sshd.sftp.client.extensions.helpers.SpaceAvailableExtensionImpl; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHFsyncExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHPosixRenameExtension; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatHandleExtension; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatPathExtension; import org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHFsyncExtensionImpl; +import org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHLimitsExtensionImpl; import org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHPosixRenameExtensionImpl; import org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHStatHandleExtensionImpl; import org.apache.sshd.sftp.client.extensions.openssh.helpers.OpenSSHStatPathExtensionImpl; @@ -46,6 +48,7 @@ import org.apache.sshd.sftp.common.SftpConstants; import org.apache.sshd.sftp.common.extensions.ParserUtils; import org.apache.sshd.sftp.common.extensions.openssh.FstatVfsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser; +import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser; @@ -130,6 +133,13 @@ public enum BuiltinSftpClientExtensions implements SftpClientExtensionFactory { return new OpenSSHPosixRenameExtensionImpl(client, raw, extensions); } }, + OPENSSH_LIMITS(LimitsExtensionParser.NAME, OpenSSHLimitsExtension.class) { + @Override // co-variant return + public OpenSSHLimitsExtension create( + SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) { + return new OpenSSHLimitsExtensionImpl(client, raw, extensions); + } + }, ; public static final Set<BuiltinSftpClientExtensions> VALUES diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java index cbd5666..e0885ac 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java @@ -28,7 +28,7 @@ import org.apache.sshd.sftp.client.extensions.SftpClientExtension; * Implements the "fs...@openssh.com" extension * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.6</A> */ public interface OpenSSHFsyncExtension extends SftpClientExtension { void fsync(Handle fileHandle) throws IOException; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java similarity index 79% copy from sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java copy to sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java index cbd5666..91716a6 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHFsyncExtension.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtension.java @@ -21,15 +21,12 @@ package org.apache.sshd.sftp.client.extensions.openssh; import java.io.IOException; -import org.apache.sshd.sftp.client.SftpClient.Handle; import org.apache.sshd.sftp.client.extensions.SftpClientExtension; /** - * Implements the "fs...@openssh.com" extension - * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.8</A> */ -public interface OpenSSHFsyncExtension extends SftpClientExtension { - void fsync(Handle fileHandle) throws IOException; +public interface OpenSSHLimitsExtension extends SftpClientExtension { + OpenSSHLimitsExtensionInfo limits() throws IOException; } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java new file mode 100644 index 0000000..f365902 --- /dev/null +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHLimitsExtensionInfo.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.sftp.client.extensions.openssh; + +import org.apache.sshd.common.PropertyResolver; +import org.apache.sshd.common.SshConstants; +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.sftp.SftpModuleProperties; + +/** + * Response for the "lim...@openssh.com" request + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.8</A> + */ +public class OpenSSHLimitsExtensionInfo implements Cloneable { + // CHECKSTYLE:OFF + /** The total number of bytes in a single SFTP packet. */ + public long maxPacketLength; + + /** + * The largest length in a SSH_FXP_READ packet. Even if the client requests a larger size,\ + * servers will usually respond with a shorter SSH_FXP_DATA packet + */ + public long maxReadLength; + + /** The largest length in a SSH_FXP_WRITE packet the server will accept */ + public long maxWriteLength; + + /** + * The maximum number of active handles that the server allows (e.g. handles created + * by SSH_FXP_OPEN and SSH_FXP_OPENDIR packets). If the server doesn't enforce a + * specific limit, then the field may be set to 0. This implies the server relies on + * the OS to enforce limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed reasonable limits. + */ + public long maxOpenHandles; + // CHECKSTYLE:ON + + public OpenSSHLimitsExtensionInfo() { + super(); + } + + public OpenSSHLimitsExtensionInfo(Buffer buffer) { + decode(buffer, this); + } + + public OpenSSHLimitsExtensionInfo(PropertyResolver resolver) { + fill(resolver, this); + } + + public <B extends Buffer> B encode(B buffer) { + return encode(buffer, this); + } + + @Override + public int hashCode() { + return NumberUtils.hashCode(maxPacketLength, maxReadLength, maxWriteLength, maxOpenHandles); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + + OpenSSHLimitsExtensionInfo other = (OpenSSHLimitsExtensionInfo) obj; + return (this.maxPacketLength == other.maxPacketLength) + && (this.maxReadLength == other.maxReadLength) + && (this.maxWriteLength == other.maxWriteLength) + && (this.maxOpenHandles == other.maxOpenHandles); + } + + @Override + public OpenSSHLimitsExtensionInfo clone() { + try { + return getClass().cast(super.clone()); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Failed to close " + toString() + ": " + e.getMessage()); + } + } + + @Override + public String toString() { + return "maxPacketLength=" + maxPacketLength + + ", maxReadLength=" + maxReadLength + + ", maxWriteLength=" + maxWriteLength + + ", maxOpenHandles=" + maxOpenHandles; + } + + public static <B extends Buffer> B encode(B buffer, OpenSSHLimitsExtensionInfo info) { + buffer.putLong(info.maxPacketLength); + buffer.putLong(info.maxReadLength); + buffer.putLong(info.maxWriteLength); + buffer.putLong(info.maxOpenHandles); + return buffer; + } + + public static <I extends OpenSSHLimitsExtensionInfo> I decode(Buffer buffer, I info) { + info.maxPacketLength = buffer.getLong(); + info.maxReadLength = buffer.getLong(); + info.maxWriteLength = buffer.getLong(); + info.maxOpenHandles = buffer.getLong(); + return info; + } + + public static <I extends OpenSSHLimitsExtensionInfo> I fill(PropertyResolver resolver, I info) { + info.maxReadLength = SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.getRequired(resolver); + info.maxWriteLength = SftpModuleProperties.MAX_WRITEDATA_PACKET_LENGTH.getRequired(resolver); + info.maxPacketLength = Math.max(info.maxReadLength, info.maxWriteLength) + + SshConstants.SSH_PACKET_HEADER_LEN + + (3 * Integer.BYTES) // id, type, len + ; + info.maxOpenHandles = SftpModuleProperties.MAX_OPEN_HANDLES_PER_SESSION.getRequired(resolver); + /* + * Quote: + * + * If the server doesn't enforce a specific limit, then the field may be set to 0. + * This implies the server relies on the OS to enforce limits. + */ + if (info.maxOpenHandles >= (Integer.MAX_VALUE - 1)) { + info.maxOpenHandles = 0; + } + return info; + } +} diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java index 4f0ae07..cc610b0 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/OpenSSHStatExtensionInfo.java @@ -26,9 +26,7 @@ import org.apache.sshd.common.util.buffer.Buffer; * Response for the "stat...@openssh.com" and "fstat...@openssh.com" extension commands. * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF= - * "http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL?rev=1.28&content-type=text/plain">OpenSSH - * section 3.4</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.4</A> */ public class OpenSSHStatExtensionInfo implements Cloneable { // The values of the f_flag bitmask @@ -57,6 +55,10 @@ public class OpenSSHStatExtensionInfo implements Cloneable { decode(buffer, this); } + public <B extends Buffer> B encode(B buffer) { + return encode(buffer, this); + } + @Override public int hashCode() { return NumberUtils.hashCode(this.f_bsize, this.f_frsize, this.f_blocks, @@ -115,7 +117,7 @@ public class OpenSSHStatExtensionInfo implements Cloneable { + ",f_namemax=" + f_namemax; } - public static void encode(Buffer buffer, OpenSSHStatExtensionInfo info) { + public static <B extends Buffer> B encode(B buffer, OpenSSHStatExtensionInfo info) { buffer.putLong(info.f_bsize); buffer.putLong(info.f_frsize); buffer.putLong(info.f_blocks); @@ -127,15 +129,14 @@ public class OpenSSHStatExtensionInfo implements Cloneable { buffer.putLong(info.f_fsid); buffer.putLong(info.f_flag); buffer.putLong(info.f_namemax); + return buffer; } public static OpenSSHStatExtensionInfo decode(Buffer buffer) { - OpenSSHStatExtensionInfo info = new OpenSSHStatExtensionInfo(); - decode(buffer, info); - return info; + return decode(buffer, new OpenSSHStatExtensionInfo()); } - public static void decode(Buffer buffer, OpenSSHStatExtensionInfo info) { + public static <I extends OpenSSHStatExtensionInfo> I decode(Buffer buffer, I info) { info.f_bsize = buffer.getLong(); info.f_frsize = buffer.getLong(); info.f_blocks = buffer.getLong(); @@ -147,5 +148,6 @@ public class OpenSSHStatExtensionInfo implements Cloneable { info.f_fsid = buffer.getLong(); info.f_flag = buffer.getLong(); info.f_namemax = buffer.getLong(); + return info; } } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java new file mode 100644 index 0000000..e07f005 --- /dev/null +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHLimitsExtensionImpl.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.sftp.client.extensions.openssh.helpers; + +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.util.Map; + +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.sftp.client.RawSftpClient; +import org.apache.sshd.sftp.client.SftpClient; +import org.apache.sshd.sftp.client.extensions.helpers.AbstractSftpClientExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo; +import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class OpenSSHLimitsExtensionImpl extends AbstractSftpClientExtension implements OpenSSHLimitsExtension { + public OpenSSHLimitsExtensionImpl(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions) { + super(LimitsExtensionParser.NAME, client, raw, extensions); + } + + @Override + public OpenSSHLimitsExtensionInfo limits() throws IOException { + Buffer buffer = getCommandBuffer(Integer.BYTES); + buffer = checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer))); + if (buffer == null) { + throw new StreamCorruptedException("Missing extended reply data"); + } + + return new OpenSSHLimitsExtensionInfo(buffer); + } +} diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java index 11221a5..a8c6468 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java @@ -28,7 +28,7 @@ import org.apache.sshd.common.file.util.BasePath; import org.apache.sshd.sftp.client.SftpClient; /** - * A {@link Path} on an {@link SftpFileSystem}. + * A {@link java.nio.file.Path} on an {@link SftpFileSystem}. */ public class SftpPath extends BasePath<SftpPath, SftpFileSystem> { diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java index 35e9f59..d9d76cc 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java @@ -57,7 +57,7 @@ public class SftpPathImpl extends SftpPath { * * @param doCache whether to start caching (increasing the cache level) or to stop caching (decreasing the cache * level) - * @see {@link #withAttributeCache(Path, IOFunction)} + * @see #withAttributeCache(Path, IOFunction) */ protected void cacheAttributes(boolean doCache) { if (doCache) { @@ -133,7 +133,7 @@ public class SftpPathImpl extends SftpPath { * @return the result of the {@code operation} * @throws IOException if thrown by the {@code operation} * - * @see {@link #withAttributeCache(IOFunction)} + * @see #withAttributeCache(IOFunction) */ public static <T> T withAttributeCache(Path path, IOFunction<Path, T> operation) throws IOException { if (path instanceof SftpPathImpl) { diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java index 5e28cee..600d1c2 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java @@ -42,6 +42,7 @@ import org.apache.sshd.sftp.common.extensions.openssh.FstatVfsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.HardLinkExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.LSetStatExtensionParser; +import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.StatVfsExtensionParser; @@ -65,7 +66,8 @@ public final class ParserUtils { FstatVfsExtensionParser.INSTANCE, HardLinkExtensionParser.INSTANCE, FsyncExtensionParser.INSTANCE, - LSetStatExtensionParser.INSTANCE)); + LSetStatExtensionParser.INSTANCE, + LimitsExtensionParser.INSTANCE)); private static final NavigableMap<String, ExtensionParser<?>> PARSERS_MAP = Collections.unmodifiableNavigableMap( BUILT_IN_PARSERS.stream() diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java index 612a487..ff7d824 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FstatVfsExtensionParser.java @@ -21,6 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.4</A> */ public class FstatVfsExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "fstat...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java index e5ab96c..2ee19b9 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java @@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.6</A> */ public class FsyncExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "fs...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java index 90d559c..ea2d4d1 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/HardLinkExtensionParser.java @@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.5</A> */ public class HardLinkExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "hardl...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java index 69856a4..b6884ff 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LSetStatExtensionParser.java @@ -23,7 +23,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; * Replicates the functionality of the existing {@code SSH_FXP_SETSTAT} operation but does not follow symbolic links * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://www.openssh.com/txt/release-8.0">OpenSSH v8.0 release notes</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.7</A> */ public class LSetStatExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "lsets...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java similarity index 77% copy from sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java copy to sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java index e5ab96c..73b4d06 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/FsyncExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/LimitsExtensionParser.java @@ -21,13 +21,13 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.8</A> */ -public class FsyncExtensionParser extends AbstractOpenSSHExtensionParser { - public static final String NAME = "fs...@openssh.com"; - public static final FsyncExtensionParser INSTANCE = new FsyncExtensionParser(); +public class LimitsExtensionParser extends AbstractOpenSSHExtensionParser { + public static final String NAME = "lim...@openssh.com"; + public static final LimitsExtensionParser INSTANCE = new LimitsExtensionParser(); - public FsyncExtensionParser() { + public LimitsExtensionParser() { super(NAME); } } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java index 9f8690c..f71da6b 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/PosixRenameExtensionParser.java @@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 3.3</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.3</A> */ public class PosixRenameExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "posix-ren...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java index 8f28fe5..8ce333f 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/openssh/StatVfsExtensionParser.java @@ -21,7 +21,7 @@ package org.apache.sshd.sftp.common.extensions.openssh; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 3.4</A> + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 4.4</A> */ public class StatVfsExtensionParser extends AbstractOpenSSHExtensionParser { public static final String NAME = "stat...@openssh.com"; diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java index 9d4fd4f..43f2e06 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java @@ -42,9 +42,16 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe } @Override - public void initialized(ServerSession session, int version) throws IOException { + public void receivedExtension(ServerSession session, String extension, int id) throws IOException { if (log.isTraceEnabled()) { - log.trace("initialized(" + session + ") version: " + version); + log.trace("receivedExtension({}) id={}, extension={}", session, id, extension); + } + } + + @Override + public void initialized(ServerSession session, int version) throws IOException { + if (log.isDebugEnabled()) { + log.debug("initialized(" + session + ") version=" + version); } } @@ -65,6 +72,16 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe } @Override + public void openFailed( + ServerSession session, String remotePath, Path localPath, boolean isDirectory, Throwable thrown) + throws IOException { + if (log.isTraceEnabled()) { + log.trace("openFailed({}) remotePath={}, localPath={}, isDir={}, thrown={}", + session, remotePath, localPath, isDirectory, thrown); + } + } + + @Override public void open(ServerSession session, String remoteHandle, Handle localHandle) throws IOException { if (log.isTraceEnabled()) { Path path = localHandle.getFile(); @@ -74,6 +91,14 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe } @Override + public void readingEntries(ServerSession session, String remoteHandle, DirectoryHandle localHandle) + throws IOException { + if (log.isTraceEnabled()) { + log.trace("readingEntries({}) handle={}[{}]", session, remoteHandle, localHandle.getFile()); + } + } + + @Override public void readEntries(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries) throws IOException { int numEntries = MapEntryUtils.size(entries); @@ -181,6 +206,14 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe } @Override + public void closed(ServerSession session, String remoteHandle, Handle localHandle, Throwable thrown) + throws IOException { + if (log.isTraceEnabled()) { + log.trace("closed({}) handle={}[{}], thrown={}", session, remoteHandle, localHandle.getFile(), thrown); + } + } + + @Override public void creating(ServerSession session, Path path, Map<String, ?> attrs) throws IOException { if (log.isTraceEnabled()) { @@ -264,4 +297,11 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage()))); } } + + @Override + public void exiting(ServerSession session, Handle handle) throws IOException { + if (log.isDebugEnabled()) { + log.debug("exiting({}) handle={}[{}]", session, handle.getFile(), handle.getFileHandle()); + } + } } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java index 812f24b..b3e32bf 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java @@ -93,6 +93,7 @@ import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.sftp.SftpModuleProperties; import org.apache.sshd.sftp.client.SftpClient; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo; import org.apache.sshd.sftp.client.fs.SftpPath; import org.apache.sshd.sftp.client.impl.SftpPathImpl; import org.apache.sshd.sftp.common.SftpConstants; @@ -104,6 +105,7 @@ import org.apache.sshd.sftp.common.extensions.openssh.AbstractOpenSSHExtensionPa import org.apache.sshd.sftp.common.extensions.openssh.FsyncExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.HardLinkExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.LSetStatExtensionParser; +import org.apache.sshd.sftp.common.extensions.openssh.LimitsExtensionParser; import org.apache.sshd.sftp.common.extensions.openssh.PosixRenameExtensionParser; /** @@ -137,7 +139,8 @@ public abstract class AbstractSftpSubsystemHelper new OpenSSHExtension(FsyncExtensionParser.NAME, "1"), new OpenSSHExtension(HardLinkExtensionParser.NAME, "1"), new OpenSSHExtension(LSetStatExtensionParser.NAME, "1"), - new OpenSSHExtension(PosixRenameExtensionParser.NAME, "1"))); + new OpenSSHExtension(PosixRenameExtensionParser.NAME, "1"), + new OpenSSHExtension(LimitsExtensionParser.NAME, "1"))); public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES = Collections.unmodifiableList( NamedResource.getNameList(DEFAULT_OPEN_SSH_EXTENSIONS)); @@ -790,7 +793,7 @@ public abstract class AbstractSftpSubsystemHelper int id, String existingPath, String linkPath, boolean symLink) throws IOException; - // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10 + // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 4.5 protected void doOpenSSHHardLink(Buffer buffer, int id) throws IOException { String srcFile = buffer.getString(); String dstFile = buffer.getString(); @@ -806,6 +809,21 @@ public abstract class AbstractSftpSubsystemHelper sendStatus(prepareReply(buffer), id, SftpConstants.SSH_FX_OK, ""); } + // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 4.8 + protected void doOpenSSHLimits(Buffer buffer, int id) throws IOException { + OpenSSHLimitsExtensionInfo info = getOpenSSHLimitsExtensionInfo(id, getServerChannelSession()); + buffer = prepareReply(buffer); + buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY); + buffer.putInt(id); + info.encode(buffer); + send(buffer); + + } + + protected OpenSSHLimitsExtensionInfo getOpenSSHLimitsExtensionInfo(int id, ChannelSession channel) throws IOException { + return new OpenSSHLimitsExtensionInfo(channel); + } + protected void doOpenSSHHardLink(int id, String srcFile, String dstFile) throws IOException { if (log.isDebugEnabled()) { log.debug("doOpenSSHHardLink({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, dst={})", @@ -1766,6 +1784,9 @@ public abstract class AbstractSftpSubsystemHelper case PosixRenameExtensionParser.NAME: doPosixRename(buffer, id); break; + case LimitsExtensionParser.NAME: + doOpenSSHLimits(buffer, id); + break; default: doUnsupportedExtension(buffer, id, extension); } @@ -1866,6 +1887,10 @@ public abstract class AbstractSftpSubsystemHelper return extList; } + if (log.isDebugEnabled()) { + log.debug("appendOpenSSHExtensions({}): {}", session, extList); + } + for (OpenSSHExtension ext : extList) { buffer.putString(ext.getName()); buffer.putString(ext.getVersion()); @@ -1943,15 +1968,16 @@ public abstract class AbstractSftpSubsystemHelper * 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 - * @param session The {@link ServerSession} for which this extension is added - * @see SftpConstants#EXT_VERSIONS + * @param buffer The {@link Buffer} to append to + * @param value The recommended value - ignored if {@code null}/empty + * @param session The {@link ServerSession} for which this extension is added + * @return The apended value + * @see SftpConstants#EXT_VERSIONS */ - protected void appendVersionsExtension( + protected String appendVersionsExtension( Buffer buffer, String value, ServerSession session) { if (GenericUtils.isEmpty(value)) { - return; + return value; } if (log.isDebugEnabled()) { @@ -1960,30 +1986,33 @@ public abstract class AbstractSftpSubsystemHelper buffer.putString(SftpConstants.EXT_VERSIONS); buffer.putString(value); + return 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 session The {@link ServerSession} for which this extension is added - * @see SftpConstants#EXT_NEWLINE - * @see #resolveNewlineValue(ServerSession) + * @param buffer The {@link Buffer} to append to + * @param session The {@link ServerSession} for which this extension is added + * @return The appended value + * @see SftpConstants#EXT_NEWLINE + * @see #resolveNewlineValue(ServerSession) */ - protected void appendNewlineExtension(Buffer buffer, ServerSession session) { + protected String appendNewlineExtension(Buffer buffer, ServerSession session) { String value = resolveNewlineValue(session); if (GenericUtils.isEmpty(value)) { - return; + return value; } if (log.isDebugEnabled()) { log.debug("appendNewlineExtension({}) value={}", - getServerSession(), BufferUtils.toHex(':', value.getBytes(StandardCharsets.UTF_8))); + session, BufferUtils.toHex(':', value.getBytes(StandardCharsets.UTF_8))); } buffer.putString(SftpConstants.EXT_NEWLINE); buffer.putString(value); + return value; } protected String resolveNewlineValue(ServerSession session) { @@ -1994,24 +2023,25 @@ public abstract class AbstractSftpSubsystemHelper * 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> - * @param session The {@link ServerSession} for which these properties are added - * @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> + * @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> + * @param session The {@link ServerSession} for which these properties are added + * @return The version properties + * @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( + protected Map<String, ?> appendVendorIdExtension( Buffer buffer, Map<String, ?> versionProperties, ServerSession session) { if (MapEntryUtils.isEmpty(versionProperties)) { - return; + return versionProperties; } if (log.isDebugEnabled()) { @@ -2029,6 +2059,7 @@ public abstract class AbstractSftpSubsystemHelper buffer.putString(resolver.getStringProperty("version", FactoryManager.DEFAULT_VERSION)); // product-version buffer.putLong(0L); // product-build-number BufferUtils.updateLengthPlaceholder(buffer, lenPos); + return versionProperties; } /** diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java index d985c5d..34421cb 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java @@ -822,9 +822,10 @@ public class SftpSubsystem throws IOException { Handle h = handles.get(handle); ServerSession session = getServerSession(); + int maxAllowed = SftpModuleProperties.MAX_WRITEDATA_PACKET_LENGTH.getRequired(session); if (log.isTraceEnabled()) { - log.trace("doWrite({})[id={}] SSH_FXP_WRITE (handle={}[{}], offset={}, data=byte[{}])", - session, id, handle, h, offset, length); + log.trace("doWrite({})[id={}] SSH_FXP_WRITE (handle={}[{}], offset={}, length={}, maxAllowed={})", + session, id, handle, h, offset, length, maxAllowed); } FileHandle fh = validateHandle(handle, h, FileHandle.class); @@ -838,6 +839,10 @@ public class SftpSubsystem + ": required=" + length + ", available=" + remaining); } + if (length > maxAllowed) { + throw new IOException("Reuested write size (" + length + ") exceeds max. allowed (" + maxAllowed + ")"); + } + SftpEventListener listener = getSftpEventListenerProxy(); listener.writing(session, handle, fh, offset, data, doff, length); try { diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java index 35422f8..ce4904d 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java @@ -212,8 +212,8 @@ public class SftpTest extends AbstractSftpClientTestSupport { Path parentPath = targetPath.getParent(); Path lclSftp = CommonTestSupportUtils.resolve( targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Path testFile = assertHierarchyTargetFolderExists(lclSftp).resolve("file.txt"); - byte[] expected = new byte[1024]; + Path testFile = assertHierarchyTargetFolderExists(lclSftp).resolve("file.bin"); + byte[] expected = new byte[(SftpModuleProperties.MIN_READDATA_PACKET_LENGTH + 16) * 4]; Factory<? extends Random> factory = sshd.getRandomFactory(); Random rnd = factory.create(); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java index 22ac2b5..1dbeb45 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/SftpExtensionsTest.java @@ -80,7 +80,7 @@ public class SftpExtensionsTest extends AbstractSftpClientTestSupport { } } - @Test // see SSHD-1266 + @Test // see SSHD-1166 public void testCustomFileExtensionAttributes() throws IOException { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java index bdcd9c9..8427844 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java @@ -22,8 +22,6 @@ package org.apache.sshd.sftp.client.extensions.openssh.helpers; import java.io.IOException; import java.io.OutputStream; import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -42,6 +40,8 @@ import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport; import org.apache.sshd.sftp.client.SftpClient; import org.apache.sshd.sftp.client.SftpClient.CloseableHandle; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHFsyncExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtension; +import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHPosixRenameExtension; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatExtensionInfo; import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHStatHandleExtension; @@ -144,8 +144,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { Path parentPath = targetPath.getParent(); String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); - final AtomicReference<String> extensionHolder = new AtomicReference<>(null); - final OpenSSHStatExtensionInfo expected = new OpenSSHStatExtensionInfo(); + OpenSSHStatExtensionInfo expected = new OpenSSHStatExtensionInfo(); expected.f_bavail = Short.MAX_VALUE; expected.f_bfree = Integer.MAX_VALUE; expected.f_blocks = Short.MAX_VALUE; @@ -158,6 +157,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { expected.f_fsid = 1L; expected.f_namemax = 256; + AtomicReference<String> extensionHolder = new AtomicReference<>(null); sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() { @Override public Command createSubsystem(ChannelSession channel) throws IOException { @@ -206,32 +206,25 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath); String invokedExtension = extensionHolder.getAndSet(null); assertEquals("Mismatched invoked extension", pathStat.getName(), invokedExtension); - assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual); + assertFieldsEqual(invokedExtension, expected, actual); try (CloseableHandle handle = sftp.open(srcPath)) { OpenSSHStatHandleExtension handleStat = assertExtensionCreated(sftp, OpenSSHStatHandleExtension.class); actual = handleStat.stat(handle); invokedExtension = extensionHolder.getAndSet(null); assertEquals("Mismatched invoked extension", handleStat.getName(), invokedExtension); - assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual); + assertFieldsEqual(invokedExtension, expected, actual); } } } - private static void assertOpenSSHStatExtensionInfoEquals( - String extension, OpenSSHStatExtensionInfo expected, OpenSSHStatExtensionInfo actual) - throws Exception { - Field[] fields = expected.getClass().getFields(); - for (Field f : fields) { - String name = f.getName(); - int mod = f.getModifiers(); - if (Modifier.isStatic(mod)) { - continue; - } - - Object expValue = f.get(expected); - Object actValue = f.get(actual); - assertEquals(extension + "[" + name + "]", expValue, actValue); + @Test // see SSHD-1233 + public void testLimits() throws Exception { + try (SftpClient sftp = createSingleSessionClient()) { + OpenSSHLimitsExtension ext = assertExtensionCreated(sftp, OpenSSHLimitsExtension.class); + OpenSSHLimitsExtensionInfo expected = new OpenSSHLimitsExtensionInfo(sftp.getClientChannel()); + OpenSSHLimitsExtensionInfo actual = ext.limits(); + assertFieldsEqual(ext.getName(), expected, actual); } } }