This is an automated email from the ASF dual-hosted git repository.

tomaswolf pushed a commit to branch rc-3.0.0-M6
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 6b551fe9af7e4aff6bffb3e9beb4641d5db7223a
Merge: 8738fd87f 1c97396ff
Author: Thomas Wolf <[email protected]>
AuthorDate: Fri Sep 25 22:42:06 2026 +0200

    Merge branch 'master' into dev_3.0

 docs/changes/2.19.0.md                             |  11 +-
 pom.xml                                            |   2 +-
 .../apache/sshd/common/RuntimeSshException.java    |   2 +-
 .../bouncycastle/BouncyCastleKEMAccessor.java      |   2 +-
 .../common/util/security/bouncycastle/MLKEM.java   |  14 +-
 .../auth/keyboard/UserAuthKeyboardInteractive.java |  22 +-
 .../common/channel/ChannelPipedInputStream.java    |   7 +-
 .../org/apache/sshd/common/forward/SocksProxy.java | 258 +++++++++++-----
 .../auth/keyboard/UserAuthKeyboardInteractive.java |  11 +-
 .../password/PasswordChangeRequiredException.java  |   2 +-
 ...AuthorizedKeyEntriesPublickeyAuthenticator.java | 139 ++++++++-
 .../server/auth/pubkey/PublickeyAuthenticator.java |  12 +-
 .../sshd/server/auth/pubkey/UserAuthPublicKey.java |  25 ++
 .../sshd/server/session/ServerUserAuthService.java |  49 ++-
 .../src/test/java/org/apache/sshd/ProxyTest.java   |  90 ++++++
 .../java/org/apache/sshd/client/ClientTest.java    |   2 +-
 .../sshd/client/auth/pubkey/MultiAuthTest.java     |  24 ++
 .../common/auth/PublicKeyAuthenticationTest.java   |  48 +++
 .../channel/ChannelPipedInputStreamTest.java       |  36 +++
 ...orizedKeyEntriesPublickeyAuthenticatorTest.java |  83 +++++
 .../server/auth/CertificateReuseMultiAuthTest.java | 112 +++++++
 .../sshd/server/auth/PublicKeyMultiAuthTest.java   | 124 ++++++++
 .../auth/ServerUserAuthServiceAsyncTest.java       | 333 +++++++++++++++++++++
 .../apache/sshd/git/pgm/EmbeddedCommandRunner.java |   3 +-
 .../org/apache/sshd/git/pgm/GitPgmCommandTest.java |  87 +++++-
 .../org/apache/sshd/ldap/LdapNetworkConnector.java |  75 ++++-
 .../sshd/ldap/LdapPasswordAuthenticator.java       |   6 +-
 .../org/apache/sshd/ldap/LdapEncodingTest.java     |  47 +++
 .../sshd/ldap/LdapPasswordAuthenticatorTest.java   |   7 +
 .../sshd/ldap/LdapPublickeyAuthenticatorTest.java  |  11 +-
 .../apache/sshd/scp/common/helpers/ScpIoUtils.java |  11 +
 .../org/apache/sshd/scp/server/ScpCommand.java     |   2 +-
 .../org/apache/sshd/scp/common/ScpIoUtilsTest.java |  88 ++++++
 .../sshd/sftp/client/impl/DefaultSftpClient.java   | 180 ++++++-----
 .../org/apache/sshd/sftp/common/SftpConstants.java |   3 +
 .../sftp/server/AbstractSftpSubsystemHelper.java   | 113 ++++---
 36 files changed, 1763 insertions(+), 278 deletions(-)

diff --cc pom.xml
index a72f56046,135b933c0..94c3bc0c0
--- a/pom.xml
+++ b/pom.xml
@@@ -81,20 -80,24 +81,20 @@@
  
  
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 -        
<project.build.outputTimestamp>2026-06-30T21:47:32Z</project.build.outputTimestamp>
 +        
<project.build.outputTimestamp>2026-06-30T22:29:09Z</project.build.outputTimestamp>
  
          <java.sdk.version>8</java.sdk.version>
 -        <javac.source>${java.sdk.version}</javac.source>
 -        <project.build.java.source>${javac.source}</project.build.java.source>
 -        <maven.compiler.source>${javac.source}</maven.compiler.source>
 +        <javac.release>${java.sdk.version}</javac.release>
 +        <maven.compiler.release>${java.sdk.version}</maven.compiler.release>
          <ant.version>1.10.15</ant.version>
 -        <ant.build.javac.source>${javac.source}</ant.build.javac.source>
          
<build-helper-maven-plugin.version>3.6.1</build-helper-maven-plugin.version>
  
 -        <javac.target>${javac.source}</javac.target>
 -        <required.java.version>[${javac.target},)</required.java.version>
 -        <project.build.java.target>${javac.target}</project.build.java.target>
 -        <maven.compiler.target>${javac.target}</maven.compiler.target>
 -        <ant.build.javac.target>${javac.target}</ant.build.javac.target>
 +        <required.java.version>[${java.sdk.version},)</required.java.version>
  
          <groovy.version>4.0.17</groovy.version>
-         <bouncycastle.version>1.84</bouncycastle.version>
+         <bouncycastle.version>1.86</bouncycastle.version>
 +        <!-- BC FIPS has version numbers 2.x.y. -->
 +        <bouncycastle.upper.bound>3</bouncycastle.upper.bound>
              <!-- NOTE: upgrading slf4j beyond this version causes
  
              Execution verify-style of goal 
org.apache.maven.plugins:maven-checkstyle-plugin:3.1.2:check failed.: 
NullPointerException
diff --cc 
sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
index 209a23541,0ba631b7b..a65c24eb7
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
@@@ -16,36 -16,24 +16,36 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
 -package org.apache.sshd.deprecated;
 +package org.apache.sshd.common.util.security.bouncycastle;
  
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
 -import java.io.IOException;
++import org.bouncycastle.crypto.params.MLKEMParameters;
  
 -import org.apache.sshd.common.util.buffer.Buffer;
 +final class BouncyCastleKEMAccessor {
  
 -/**
 - * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
 - */
 -@FunctionalInterface
 -public interface UserAuth {
 +    static final BouncyCastleKEMAccessor INSTANCE = new 
BouncyCastleKEMAccessor();
 +
 +    private BouncyCastleKEMAccessor() {
 +        super();
 +    }
  
 -    enum Result {
 -        Success,
 -        Failure,
 -        Continued
 +    public boolean isSupported() {
 +        try {
 +            return Inner.isSupported();
 +        } catch (Throwable t) {
 +            return false;
 +        }
      }
  
 -    Result next(Buffer buffer) throws IOException;
 +    private static final class Inner {
  
 +        private Inner() {
 +            super();
 +        }
 +
 +        static boolean isSupported() {
 +            // Just something that forces class loading.
 +            return MLKEMParameters.ml_kem_768 != null;
 +        }
 +
 +    }
  }
diff --cc 
sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
index 3dad32c07,f44b7769d..a05ce2920
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
@@@ -23,16 -23,15 +23,16 @@@ import java.util.Objects
  
  import org.apache.sshd.common.OptionalFeature;
  import org.apache.sshd.common.random.JceRandom;
 +import org.apache.sshd.common.util.security.KEM;
  import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
  import org.bouncycastle.crypto.SecretWithEncapsulation;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyGenerationParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyPairGenerator;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
+ import org.bouncycastle.crypto.generators.MLKEMKeyPairGenerator;
+ import org.bouncycastle.crypto.kems.MLKEMExtractor;
+ import org.bouncycastle.crypto.kems.MLKEMGenerator;
+ import org.bouncycastle.crypto.params.MLKEMKeyGenerationParameters;
+ import org.bouncycastle.crypto.params.MLKEMParameters;
+ import org.bouncycastle.crypto.params.MLKEMPrivateKeyParameters;
+ import org.bouncycastle.crypto.params.MLKEMPublicKeyParameters;
  
  /**
   * An implementation of the mlkem768 key encapsulation method (KEM), formerly 
known as Kyber, using Bouncy Castle. But
diff --cc sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
index a779aa65f,96edb2ced..caf7f4974
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
@@@ -115,30 -113,12 +113,40 @@@ public class SocksProxy extends Abstrac
              }
          }
  
-         protected void sendReply(Buffer message, boolean success, LocalWindow 
window, long windowSize) {
 -        protected int getUByte(Buffer buffer) {
 -            return buffer.getUByte();
 -        }
 -
 -        protected int getUShort(Buffer buffer) {
 -            return (getUByte(buffer) << Byte.SIZE) + getUByte(buffer);
++        protected void sendReply(Buffer message, boolean success, Runnable 
completion, LocalWindow window, long windowSize) {
 +            try {
-                 session.writeBuffer(message);
++                session.writeBuffer(message).addListener(future -> {
++                    if (future.isWritten()) {
++                        if (success) {
++                            // Open the channel window: we're ready to accept 
data now.
++                            try {
++                                window.release(windowSize);
++                            } catch (IOException e) {
++                                log.error("Could not open channel window to 
{} on channel {}: {}", windowSize, channel, e);
++                                service.unregisterChannel(channel);
++                                channel.close(true);
++                                session.close(true);
++                            }
++                        }
++                        if (completion != null) {
++                            completion.run();
++                        }
++                        if (success) {
++                            session.resumeRead();
++                        }
++                    } else {
++                        session.close(true);
++                    }
++                });
 +            } catch (IOException e) {
 +                log.error("Failed ({}) to send channel open response for {}: 
{}", e.getClass().getSimpleName(), channel,
 +                        e.getMessage());
 +                if (success) {
 +                    service.unregisterChannel(channel);
 +                    channel.close(true);
 +                }
 +                throw new IllegalStateException("Failed to send packet", e);
 +            }
-             if (success) {
-                 session.resumeRead();
-                 // Open the channel window: we're ready to accept data now.
-                 try {
-                     window.release(windowSize);
-                 } catch (IOException e) {
-                     log.error("Could not open channel window to {} on channel 
{}: {}", windowSize, channel, e);
-                     service.unregisterChannel(channel);
-                     channel.close(true);
-                     throw new IllegalStateException("Failed to open window", 
e);
-                 }
-             }
          }
      }
  
@@@ -155,12 -135,14 +163,14 @@@
          protected void onMessage(Buffer buffer) throws IOException {
              if (channel == null) {
                  int cmd = buffer.getUByte();
 -                if (cmd != 1) {
 +                if (cmd != SocksConstants.Socks4.CMD_CONNECT) {
                      throw new IllegalStateException("Unsupported socks 
command: " + cmd);
                  }
 -                int port = getUShort(buffer);
 -                String host = Integer.toString(getUByte(buffer)) + "."
 -                              + Integer.toString(getUByte(buffer)) + "."
 -                              + Integer.toString(getUByte(buffer)) + "."
 -                              + Integer.toString(getUByte(buffer));
 +                int port = buffer.getUShort();
-                 String host = Integer.toString(buffer.getUByte()) + "." + 
Integer.toString(buffer.getUByte()) + "."
-                               + Integer.toString(buffer.getUByte()) + "." + 
Integer.toString(buffer.getUByte());
++                String host = Integer.toString(buffer.getUShort()) + "."
++                              + Integer.toString(buffer.getUShort()) + "."
++                              + Integer.toString(buffer.getUShort()) + "."
++                              + Integer.toString(buffer.getUShort());
                  String userId = getNTString(buffer);
                  // Socks4a
                  if (host.startsWith("0.0.0.")) {
@@@ -175,36 -157,39 +185,30 @@@
                  channel.setStreaming(Streaming.Async);
                  session.suspendRead();
                  service.registerChannel(channel);
 -                channel.open().addListener(this::onChannelOpened);
 +                // Open the channel, but don't accept input data yet!
 +                LocalWindow window = channel.getLocalWindow();
-                 long windowSize = window.getSize();
-                 window.consume(windowSize);
++                long windowSize = window.consumeAll();
 +                channel.open().addListener(f -> onChannelOpened(f, window, 
windowSize));
              } else {
                  super.onMessage(buffer);
              }
          }
  
          @SuppressWarnings("synthetic-access")
 -        protected void onChannelOpened(OpenFuture future) {
 -            session.resumeRead();
 +        protected void onChannelOpened(OpenFuture future, LocalWindow window, 
long windowSize) {
-             session.resumeRead();
              Buffer buffer = new ByteArrayBuffer(Long.SIZE, false);
              buffer.putByte((byte) 0x00);
              Throwable t = future.getException();
              if (t != null) {
                  service.unregisterChannel(channel);
                  channel.close(true);
 -                buffer.putByte((byte) 0x5b);
 +                buffer.putByte(SocksConstants.Socks4.REPLY_FAILURE);
              } else {
 -                buffer.putByte((byte) 0x5a);
 -            }
 -            buffer.putByte((byte) 0x00);
 -            buffer.putByte((byte) 0x00);
 -            buffer.putByte((byte) 0x00);
 -            buffer.putByte((byte) 0x00);
 -            buffer.putByte((byte) 0x00);
 -            buffer.putByte((byte) 0x00);
 -            try {
 -                session.writeBuffer(buffer);
 -            } catch (IOException e) {
 -                // TODO Auto-generated catch block
 -                log.error("Failed ({}) to send channel open packet for {}: 
{}", e.getClass().getSimpleName(), channel,
 -                        e.getMessage());
 -                throw new IllegalStateException("Failed to send packet", e);
 +                buffer.putByte(SocksConstants.Socks4.REPLY_SUCCESS);
              }
-             buffer.putByte((byte) 0x00);
-             buffer.putByte((byte) 0x00);
-             buffer.putByte((byte) 0x00);
-             buffer.putByte((byte) 0x00);
-             buffer.putByte((byte) 0x00);
-             buffer.putByte((byte) 0x00);
-             sendReply(buffer, t == null, window, windowSize);
++            buffer.putInt(0);
++            buffer.putShort(0);
++            sendReply(buffer, t == null, () -> session.close(false), window, 
windowSize);
          }
  
          protected String getNTString(Buffer buffer) {
@@@ -220,8 -213,8 +232,9 @@@
       * @see <A HREF="https://en.wikipedia.org/wiki/SOCKS#SOCKS5";>SOCKS5</A>
       */
      public class Socks5 extends Proxy {
 +
-         private byte[] authMethods;
+         private Socks5State state = Socks5State.GREETING;
+         private Buffer pending = new ByteArrayBuffer();
  
          public Socks5(IoSession session) {
              super(session);
@@@ -229,56 -222,115 +242,112 @@@
  
          @SuppressWarnings("synthetic-access")
          @Override
-         protected void onMessage(Buffer buffer) throws IOException {
-             boolean debugEnabled = log.isDebugEnabled();
-             if (authMethods == null) {
-                 int nbAuthMethods = buffer.getUByte();
-                 authMethods = new byte[nbAuthMethods];
-                 buffer.getRawBytes(authMethods);
+         protected synchronized void onMessage(Buffer buffer) throws 
IOException {
+             if (state == Socks5State.FORWARDING && pending.available() == 0) {
+                 super.onMessage(buffer);
+                 return;
+             }
+             pending.putBuffer(buffer);
+             processPending();
+         }
+ 
+         protected void processPending() throws IOException {
+             if (state == Socks5State.OPENING_CHANNEL || state == 
Socks5State.CLOSED) {
+                 return;
+             }
+ 
+             if (state == Socks5State.FORWARDING) {
 -                forwardPending();
++                forwardPending(true);
+                 return;
+             }
+ 
+             if (state == Socks5State.GREETING) {
+                 if (pending.available() < 1) {
+                     return;
+                 }
+                 int authMethodsCount = pending.rawByte(pending.rpos()) & 0xFF;
+                 if (pending.available() < authMethodsCount + 1) {
+                     return;
+                 }
+ 
+                 pending.getUByte();
                  boolean foundNoAuth = false;
-                 for (int i = 0; i < nbAuthMethods; i++) {
-                     foundNoAuth |= authMethods[i] == 0;
 -                for (int index = 0; index < authMethodsCount; index++) {
++                for (int i = 0; i < authMethodsCount; i++) {
+                     foundNoAuth |= pending.getUByte() == 0;
                  }
-                 buffer = new ByteArrayBuffer(Byte.SIZE, false);
-                 buffer.putByte(SocksConstants.Socks5.VERSION);
-                 buffer.putByte((byte) (foundNoAuth ? 0x00 : 0xFF));
-                 session.writeBuffer(buffer);
+                 pending.compact();
+                 Buffer response = new ByteArrayBuffer(2, false);
 -                response.putByte((byte) 0x05);
++                response.putByte(SocksConstants.Socks5.VERSION);
+                 response.putByte((byte) (foundNoAuth ? 0x00 : 0xFF));
+                 session.writeBuffer(response);
                  if (!foundNoAuth) {
                      throw new IllegalStateException("Received socks5 greeting 
without NoAuth method");
-                 } else if (debugEnabled) {
+                 } else if (log.isDebugEnabled()) {
                      log.debug("Received socks5 greeting");
                  }
-             } else if (channel == null) {
-                 int version = buffer.getUByte();
+                 state = Socks5State.CONNECT_REQUEST;
+             }
+ 
+             if (state == Socks5State.CONNECT_REQUEST) {
+                 if (pending.available() < 4) {
+                     return;
+                 }
+                 int offset = pending.rpos();
+                 int version = pending.rawByte(offset) & 0xFF;
 -                if (version != 0x05) {
 +                if (version != SocksConstants.Socks5.VERSION) {
                      throw new IllegalStateException("Unexpected version: " + 
version);
                  }
-                 int cmd = buffer.getUByte();
+                 int cmd = pending.rawByte(offset + 1) & 0xFF;
 -                if (cmd != 1) { // establish a TCP/IP stream connection
 +                if (cmd != SocksConstants.Socks5.CMD_CONNECT) { // establish 
a TCP/IP stream connection
                      throw new IllegalStateException("Unsupported socks 
command: " + cmd);
                  }
-                 int res = buffer.getUByte();
-                 if (res != 0 && debugEnabled) {
+                 int res = pending.rawByte(offset + 2) & 0xFF;
 -                if (res != 0) {
 -                    if (log.isDebugEnabled()) {
 -                        log.debug("No zero reserved value: {}", res);
 -                    }
++                if (res != 0 && log.isDebugEnabled()) {
 +                    log.debug("No zero reserved value: {}", res);
                  }
- 
-                 int type = buffer.getUByte();
-                 String host;
+                 int type = pending.rawByte(offset + 3) & 0xFF;
+                 int addressLength;
 -                if (type == 0x01) {
 +                if (type == SocksConstants.Socks5.ADDRESS_IPV4) {
-                     host = Integer.toString(buffer.getUByte()) + "." + 
Integer.toString(buffer.getUByte()) + "."
-                            + Integer.toString(buffer.getUByte()) + "." + 
Integer.toString(buffer.getUByte());
+                     addressLength = 4;
 -                } else if (type == 0x03) {
 +                } else if (type == SocksConstants.Socks5.ADDRESS_FQDN) {
-                     host = getBLString(buffer);
+                     if (pending.available() < 5) {
+                         return;
+                     }
+                     addressLength = pending.rawByte(offset + 4) & 0xFF;
 -                } else if (type == 0x04) {
 +                } else if (type == SocksConstants.Socks5.ADDRESS_IPV6) {
-                     host = Integer.toHexString(buffer.getUShort()) + ":" + 
Integer.toHexString(buffer.getUShort()) + ":"
-                            + Integer.toHexString(buffer.getUShort()) + ":" + 
Integer.toHexString(buffer.getUShort()) + ":"
-                            + Integer.toHexString(buffer.getUShort()) + ":" + 
Integer.toHexString(buffer.getUShort()) + ":"
-                            + Integer.toHexString(buffer.getUShort()) + ":" + 
Integer.toHexString(buffer.getUShort());
+                     addressLength = 16;
                  } else {
                      throw new IllegalStateException("Unsupported address 
type: " + type);
                  }
-                 int port = buffer.getUShort();
-                 if (debugEnabled) {
 -                int requestLength = 4 + (type == 0x03 ? 1 : 0) + 
addressLength + 2;
++                int requestLength = 4 + (type == 
SocksConstants.Socks5.ADDRESS_FQDN ? 1 : 0) + addressLength + 2;
+                 if (pending.available() < requestLength) {
+                     return;
+                 }
 -
 -                getUByte(pending); // version already checked above
 -                getUByte(pending); // command already checked above
 -                getUByte(pending); // reserved byte
 -                getUByte(pending); // address type
 -                String host;
 -                if (type == 0x01) {
 -                    host = Integer.toString(getUByte(pending)) + "."
 -                           + Integer.toString(getUByte(pending)) + "."
 -                           + Integer.toString(getUByte(pending)) + "."
 -                           + Integer.toString(getUByte(pending));
 -                } else if (type == 0x03) {
++                pending.getUByte(); // version already checked above
++                pending.getUByte(); // command already checked above
++                pending.getUByte(); // reserved byte
++                pending.getUByte(); // address type
++                String host = null;
++                if (type == SocksConstants.Socks5.ADDRESS_IPV4) {
++                    host = Integer.toString(pending.getUByte()) + "."
++                           + Integer.toString(pending.getUByte()) + "."
++                           + Integer.toString(pending.getUByte()) + "."
++                           + Integer.toString(pending.getUByte());
++                } else if (type == SocksConstants.Socks5.ADDRESS_FQDN) {
+                     host = getBLString(pending);
 -                } else {
++                } else if (type == SocksConstants.Socks5.ADDRESS_IPV6) {
+                     StringBuilder address = new StringBuilder();
 -                    for (int index = 0; index < 8; index++) {
 -                        if (index > 0) {
++                    for (int i = 0; i < 8; i++) {
++                        if (i > 0) {
+                             address.append(':');
+                         }
 -                        
address.append(Integer.toHexString(getUShort(pending)));
++                        
address.append(Integer.toHexString(pending.getUShort()));
+                     }
+                     host = address.toString();
+                 }
 -                int port = getUShort(pending);
++                int port = pending.getUShort();
+                 pending.compact();
+                 if (log.isDebugEnabled()) {
                      log.debug("Received socks5 connection request to {}:{}", 
host, port);
                  }
                  SshdSocketAddress remote = new SshdSocketAddress(host, port);
@@@ -286,48 -338,74 +355,73 @@@
                  channel.setStreaming(Streaming.Async);
                  session.suspendRead();
                  service.registerChannel(channel);
+                 state = Socks5State.OPENING_CHANNEL;
 -                channel.open().addListener(this::onChannelOpened);
 +                // Open the channel, but don't accept input data yet!
 +                LocalWindow window = channel.getLocalWindow();
 +                long windowSize = window.consumeAll();
 +                channel.open().addListener(f -> onChannelOpened(f, window, 
windowSize));
-             } else {
-                 if (debugEnabled) {
-                     log.debug("Received socks5 connection message");
-                 }
-                 super.onMessage(buffer);
              }
          }
  
          @SuppressWarnings("synthetic-access")
-         protected void onChannelOpened(OpenFuture future, LocalWindow window, 
long windowSize) {
-             Buffer response = new ByteArrayBuffer(2);
 -        protected synchronized void onChannelOpened(OpenFuture future) {
++        protected synchronized void onChannelOpened(OpenFuture future, 
LocalWindow window, long windowSize) {
++            Buffer response = new ByteArrayBuffer(10, false);
 +            response.putByte(SocksConstants.Socks5.VERSION);
              Throwable t = future.getException();
++            Runnable completion;
              if (t != null) {
                  service.unregisterChannel(channel);
                  channel.close(true);
+                 pending.clear(true);
+                 state = Socks5State.CLOSED;
 -                sendReply((byte) 0x01, () -> session.close(false));
 -                return;
 -            }
 -
 -            state = Socks5State.FORWARDING;
 -            sendReply((byte) 0x00, this::forwardPending);
 -        }
 -
 -        protected void sendReply(byte status, Runnable completion) {
 -            Buffer response = new ByteArrayBuffer(10, false);
 -            // VER, REP, RSV, ATYP=IPv4, BND.ADDR=0.0.0.0, BND.PORT=0
 -            response.putRawBytes(new byte[] { 0x05, status, 0x00, 0x01, 0, 0, 
0, 0, 0, 0 });
 -            try {
 -                session.writeBuffer(response).addListener(future -> {
 -                    if (future.isWritten()) {
 -                        completion.run();
 -                    } else {
 -                        session.close(true);
 -                    }
 -                });
 -            } catch (IOException e) {
 -                log.error("Failed ({}) to send channel open response for {}: 
{}", e.getClass().getSimpleName(), channel,
 -                        e.getMessage());
 -                session.close(true);
 +                response.putByte(SocksConstants.Socks5.REPLY_FAILURE);
++                completion = () -> session.close(false);
 +            } else {
++                state = Socks5State.FORWARDING;
 +                response.putByte(SocksConstants.Socks5.REPLY_SUCCESS);
++                completion = () -> forwardPending(false);
              }
 +            response.putByte((byte) 0x00); // reserved
-             SocketAddress bound = session.getAcceptanceAddress();
-             if (bound instanceof InetSocketAddress) {
-                 byte[] ip = ((InetSocketAddress) 
bound).getAddress().getAddress();
-                 if (ip.length == 4) {
-                     response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
-                 } else {
-                     response.putByte(SocksConstants.Socks5.ADDRESS_IPV6);
-                 }
-                 response.putRawBytes(ip);
-                 response.putShort(((InetSocketAddress) bound).getPort());
-             } else {
-                 // Just fill in some dummy values.
-                 response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
-                 response.putLong(0);
-                 response.putShort(0);
++            response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
++            response.putInt(0);
++            response.putShort(0);
++            sendReply(response, t == null, completion, window, windowSize);
+         }
+ 
 -        protected synchronized void forwardPending() {
++        protected synchronized void forwardPending(boolean suspend) {
+             if (state != Socks5State.FORWARDING) {
+                 return;
+             }
+             Buffer payload = pending;
+             pending = new ByteArrayBuffer();
 -            if (payload.available() == 0) {
 -                session.resumeRead();
++            int payloadSize = payload.available();
++            if (payloadSize == 0) {
+                 return;
+             }
 -            int payloadSize = payload.available();
 -            session.suspendRead();
++            if (suspend) {
++                session.suspendRead();
++            }
+             try {
 -                ThreadUtils.runAsInternal(channel.getAsyncIn(), out -> 
out.writeBuffer(payload).addListener(future -> {
 -                    if (future.getException() == null) {
++                channel.getAsyncIn().writeBuffer(payload).addListener(future 
-> {
++                    Throwable t = future.getException();
++                    if (t == null) {
+                         if (log.isDebugEnabled()) {
+                             log.debug("Forwarded {} queued socks5 bytes", 
payloadSize);
+                         }
 -                        session.resumeRead();
++                        if (suspend) {
++                            session.resumeRead();
++                        }
+                     } else {
++                        log.error("Failed ({}) to forward {} queued bytes for 
{}: {}", t.getClass().getSimpleName(),
++                                payloadSize, channel, t.getMessage());
+                         session.close(true);
+                     }
 -                }));
++                });
+             } catch (IOException e) {
+                 log.error("Failed ({}) to forward {} queued bytes for {}: 
{}", e.getClass().getSimpleName(), payloadSize,
+                         channel, e.getMessage());
+                 session.close(true);
              }
-             sendReply(response, t == null, window, windowSize);
          }
  
          protected String getBLString(Buffer buffer) {
diff --cc 
sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index 703c5887d,2ef3dfc37..dcdfa0ec3
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@@ -45,13 -44,12 +44,12 @@@ public interface PublickeyAuthenticato
       * certificate authority, and that the given user name is listed in the 
certificate's principals.
       * </p>
       *
--     * @param  username           the username
--     * @param  key                the key
--     * @param  session            the server session
--     * @return                    {@code true} if the key may be used; {@code 
false} otherwise
-      * @throws AsyncAuthException If the authentication is performed 
asynchronously
++     * @param  username the username
++     * @param  key      the key
++     * @param  session  the server session
++     * @return          {@code true} if the key may be used; {@code false} 
otherwise
       */
-     boolean authenticate(String username, PublicKey key, ServerSession 
session) throws AsyncAuthException;
+     boolean authenticate(String username, PublicKey key, ServerSession 
session);
  
      /**
       * @param  id                       Some kind of mnemonic identifier for 
the authenticator - used also in
diff --cc 
sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
index aa3aacccc,18cd5990b..cbc0e99e3
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
@@@ -196,10 -214,32 +214,17 @@@ public class UserAuthPublicKey extends 
          return Boolean.TRUE;
      }
  
+     protected boolean alreadyAuthenticated(PublicKey key, List<PublicKey> 
previouslyAuthenticatedKeys) {
+         if (GenericUtils.isEmpty(previouslyAuthenticatedKeys)) {
+             return false;
+         }
+         return previouslyAuthenticatedKeys.stream().anyMatch(k -> 
KeyUtils.compareKeys(key, k));
+     }
+ 
      protected void verifyCertificateSignature(ServerSession session, 
OpenSshCertificate cert) throws Exception {
 -        PublicKey signatureKey = cert.getCaPubKey();
 -        String keyAlg = KeyUtils.getKeyType(signatureKey);
 -        String keyId = cert.getId();
 -
 -        String sigAlg = cert.getSignatureAlgorithm();
 -        if (!keyAlg.equals(KeyUtils.getCanonicalKeyType(sigAlg))) {
 +        if (!OpenSshCertificate.verifySignature(cert, 
SignatureFactoriesManager.resolveSignatureFactories(this, session))) {
              throw new CertificateException(
 -                    "Found invalid signature alg " + sigAlg + " for key ID=" 
+ keyId + " using a " + keyAlg + " CA key");
 -        }
 -
 -        Signature verif = ValidateUtils.checkNotNull(
 -                
NamedFactory.create(SignatureFactoriesManager.resolveSignatureFactories(this, 
session), sigAlg),
 -                "No CA verifier located for algorithm=%s of key ID=%s", 
sigAlg, keyId);
 -        verif.initVerifier(session, signatureKey);
 -        verif.update(session, cert.getMessage());
 -
 -        if (!verif.verify(session, cert.getSignature())) {
 -            throw new CertificateException("CA signature verification failed 
for key type=" + keyAlg + " of key ID=" + keyId);
 +                    "CA signature verification failed for key type=" + 
cert.getKeyType() + " of key ID=" + cert.getId());
          }
      }
  

Reply via email to