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

commit 9e17380d9837002d268258dc02f3b92037da3765
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Sat Nov 9 19:30:22 2019 +0200

    [SSHD-955] Provide configurable control over auto-detected password prompt 
in client-side 'UserAuthKeyboardInteractive' implementation
---
 CHANGES.md                                         |  3 +
 docs/client-setup.md                               |  6 ++
 .../sshd/cli/client/SshClientCliSupport.java       |  4 +-
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |  3 +-
 .../sshd/cli/server/SshServerCliSupport.java       |  4 +-
 .../apache/sshd/common/PropertyResolverUtils.java  | 19 +++++-
 .../common/config/ConfigFileReaderSupport.java     | 25 ++++----
 .../sshd/common/util/security/SecurityUtils.java   |  5 +-
 .../auth/keyboard/UserAuthKeyboardInteractive.java | 63 +++++++++++++++----
 .../sshd/client/auth/keyboard/UserInteraction.java | 62 +++++++++++++++++-
 .../server/auth/keyboard/InteractiveChallenge.java |  5 +-
 .../server/config/SshServerConfigFileReader.java   |  2 +-
 .../java/org/apache/sshd/client/ClientTest.java    | 73 ++++++++++++----------
 .../java/org/apache/sshd/server/ServerTest.java    |  3 +-
 .../sshd/server/auth/WelcomeBannerPhaseTest.java   |  8 ++-
 .../apache/sshd/server/auth/WelcomeBannerTest.java | 13 +++-
 .../sshd/common/util/net/LdapNetworkConnector.java |  7 ++-
 .../config/keys/loader/openpgp/PGPUtils.java       |  5 +-
 18 files changed, 232 insertions(+), 78 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index dfacf54..f656e5d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -69,6 +69,8 @@ the message type=30 (old request).
 
 * Provide configurable control over the client-side `ChannelSession` _stdin_ 
pump chunk size.
 
+* Client side `UserAuthKeyboardInteractive` allows configurable detection of 
plain-text password prompt.
+
 ## Behavioral changes and enhancements
 
 * [SSHD-926](https://issues.apache.org/jira/browse/SSHD-930) - Add support for 
OpenSSH 'lsets...@openssh.com' SFTP protocol extension.
@@ -101,3 +103,4 @@ for the server's identification before sending KEX-INIT 
message.
 
 * [SSHD-953](https://issues.apache.org/jira/browse/SSHD-953) - Parse and strip 
quoted command arguments when executing a server-side command via local shell.
 
+* [SSHD-955](https://issues.apache.org/jira/browse/SSHD-955) - Provide 
configurable control over auto-detected password prompt in client-side 
`UserAuthKeyboardInteractive` implementation.
diff --git a/docs/client-setup.md b/docs/client-setup.md
index 93292b5..6e275df 100644
--- a/docs/client-setup.md
+++ b/docs/client-setup.md
@@ -104,6 +104,12 @@ While RFC-4256 support is the primary purpose of this 
interface, it can also be
 in [RFC 4252 section 5.4](https://www.ietf.org/rfc/rfc4252.txt) as well as its 
initial identification string as described
 in [RFC 4253 section 4.2](https://tools.ietf.org/html/rfc4253#section-4.2).
 
+In this context, regardless of whether such interaction is configured, the 
default implementation for the client side contains code
+that attempts to auto-detect a password prompt. If it detects it, then it 
attempts to use one of the registered passwords (if any) as
+the interactive response to the server's challenge - (see client-side 
implementation of `UserAuthKeyboardInteractive#useCurrentPassword`
+method). Basically, detection occurs by checking if the server sent **exactly 
one** challenge with no requested echo, and the challenge
+string looks like `"... password ...:"` (**Note:** the auto-detection and 
password prompt detection patterns are configurable).
+
 ## Using the `SshClient` to connect to a server
 
 Once the `SshClient` instance is properly configured it needs to be 
`start()`-ed in order to connect to a server.
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
index ca1919b..0116ea8 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -502,7 +502,9 @@ public abstract class SshClientCliSupport extends 
CliSupport {
             }
 
             @Override
-            public String[] interactive(ClientSession clientSession, String 
name, String instruction, String lang, String[] prompt, boolean[] echo) {
+            public String[] interactive(
+                    ClientSession clientSession, String name, String 
instruction,
+                    String lang, String[] prompt, boolean[] echo) {
                 int numPropmts = GenericUtils.length(prompt);
                 String[] answers = new String[numPropmts];
                 try {
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
index 924ad60..3115ef7 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
@@ -220,7 +220,8 @@ public class SshKeyScanMain implements Channel, 
Callable<Void>, ServerKeyVerifie
 
                     @Override
                     public String[] interactive(
-                            ClientSession session, String name, String 
instruction, String lang, String[] prompt, boolean[] echo) {
+                            ClientSession session, String name, String 
instruction,
+                            String lang, String[] prompt, boolean[] echo) {
                         return null;
                     }
 
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
index eee5daa..3095497 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java
@@ -192,7 +192,7 @@ public abstract class SshServerCliSupport extends 
CliSupport {
 
         String nameList =
             (options == null) ? null : 
options.getString(ConfigFileReaderSupport.SUBSYSTEM_CONFIG_PROP);
-        if ("none".equalsIgnoreCase(nameList)) {
+        if (PropertyResolverUtils.isNoneValue(nameList)) {
             return Collections.emptyList();
         }
 
@@ -238,7 +238,7 @@ public abstract class SshServerCliSupport extends 
CliSupport {
             return DEFAULT_SHELL_FACTORY;
         }
 
-        if ("none".equalsIgnoreCase(factory)) {
+        if (PropertyResolverUtils.isNoneValue(factory)) {
             return null;
         }
 
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java 
b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
index 7e0a680..280c447 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
@@ -36,25 +36,37 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public final class PropertyResolverUtils {
+    public static final String NONE_VALUE = "none";
+
     /**
      * Case <U>insensitive</U> {@link NavigableSet} of values considered 
{@code true} by {@link #parseBoolean(String)}
      */
     public static final NavigableSet<String> TRUE_VALUES =
         Collections.unmodifiableNavigableSet(
-            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "true", 
"t", "yes", "y", "on"));
+            GenericUtils.asSortedSet(
+                String.CASE_INSENSITIVE_ORDER, "true", "t", "yes", "y", "on"));
 
     /**
      * Case <U>insensitive</U> {@link NavigableSet} of values considered 
{@code false} by {@link #parseBoolean(String)}
      */
     public static final NavigableSet<String> FALSE_VALUES =
         Collections.unmodifiableNavigableSet(
-            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "false", 
"f", "no", "n", "off"));
+            GenericUtils.asSortedSet(
+                String.CASE_INSENSITIVE_ORDER, "false", "f", "no", "n", 
"off"));
 
     private PropertyResolverUtils() {
         throw new UnsupportedOperationException("No instance allowed");
     }
 
     /**
+     * @param v Value to examine
+     * @return {@code true} if equals to {@value #NONE_VALUE} - case 
<U>insensitive</U>
+     */
+    public static boolean isNoneValue(String v) {
+        return NONE_VALUE.equalsIgnoreCase(v);
+    }
+
+    /**
      * @param resolver     The {@link PropertyResolver} instance - ignored if 
{@code null}
      * @param name         The property name
      * @param defaultValue The default value to return if the specified 
property
@@ -374,7 +386,8 @@ public final class PropertyResolverUtils {
      * @param defaultValue The default value to return if property not set or 
empty
      * @return The set value (if not {@code null}/empty) or default one
      */
-    public static String getStringProperty(PropertyResolver resolver, String 
name, String defaultValue) {
+    public static String getStringProperty(
+            PropertyResolver resolver, String name, String defaultValue) {
         String value = getString(resolver, name);
         if (GenericUtils.isEmpty(value)) {
             return defaultValue;
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
index 6a1fb96..05a107b 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
@@ -78,22 +78,24 @@ public final class ConfigFileReaderSupport {
     // see 
http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
     public static final String CIPHERS_CONFIG_PROP = "Ciphers";
     public static final String DEFAULT_CIPHERS =
-            
"aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour";
+        
"aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc"
+            + ",blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour";
     // see 
http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
     public static final String MACS_CONFIG_PROP = "MACs";
     public static final String DEFAULT_MACS =
-            
"hmac-md5,hmac-sha1,umac...@openssh.com,hmac-ripemd160,hmac-sha1-96,hmac-md5-96,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96";
+        "hmac-md5,hmac-sha1,umac...@openssh.com,hmac-ripemd160,hmac-sha1-96"
+            + 
",hmac-md5-96,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96";
     // see 
http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
     public static final String KEX_ALGORITHMS_CONFIG_PROP = "KexAlgorithms";
     public static final String DEFAULT_KEX_ALGORITHMS =
-            "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"
-                    + "," + 
"diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1"
-                    // RFC-8268 groups
-                    + "," + 
"diffie-hellman-group18-sha512,diffie-hellman-group17-sha512"
-                    + "," + 
"diffie-hellman-group16-sha512,diffie-hellman-group15-sha512"
-                    + "," + "diffie-hellman-group14-sha256"
-                    // Legacy groups
-                    + "," + 
"diffie-hellman-group14-sha1,diffie-hellman-group1-sha1";
+        "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"
+            + "," + 
"diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1"
+            // RFC-8268 groups
+            + "," + 
"diffie-hellman-group18-sha512,diffie-hellman-group17-sha512"
+            + "," + 
"diffie-hellman-group16-sha512,diffie-hellman-group15-sha512"
+            + "," + "diffie-hellman-group14-sha256"
+            // Legacy groups
+            + "," + "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1";
     // see http://linux.die.net/man/5/ssh_config
     public static final String HOST_KEY_ALGORITHMS_CONFIG_PROP = 
"HostKeyAlgorithms";
     // see https://tools.ietf.org/html/rfc5656
@@ -124,7 +126,8 @@ public final class ConfigFileReaderSupport {
     }
 
     public static Properties readConfigFile(InputStream input, boolean 
okToClose) throws IOException {
-        try (Reader reader = new 
InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), 
StandardCharsets.UTF_8)) {
+        try (Reader reader = new InputStreamReader(
+                NoCloseInputStream.resolveInputStream(input, okToClose), 
StandardCharsets.UTF_8)) {
             return readConfigFile(reader, true);
         }
     }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
index 2e2ce04..ae48186 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
@@ -57,6 +57,7 @@ import javax.crypto.Mac;
 import javax.crypto.spec.DHParameterSpec;
 
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
@@ -337,7 +338,7 @@ public final class SecurityUtils {
             }
 
             String name = System.getProperty(PROP_DEFAULT_SECURITY_PROVIDER);
-            choice = (GenericUtils.isEmpty(name) || 
"none".equalsIgnoreCase(name))
+            choice = (GenericUtils.isEmpty(name) || 
PropertyResolverUtils.isNoneValue(name))
                     ? SecurityProviderChoice.EMPTY
                     : SecurityProviderChoice.toSecurityProviderChoice(name);
             DEFAULT_PROVIDER_HOLDER.set(choice);
@@ -389,7 +390,7 @@ public final class SecurityUtils {
             String regsList = System.getProperty(SECURITY_PROVIDER_REGISTRARS,
                     GenericUtils.join(DEFAULT_SECURITY_PROVIDER_REGISTRARS, 
','));
             boolean bouncyCastleRegistered = false;
-            if ((GenericUtils.length(regsList) > 0) && 
(!"none".equalsIgnoreCase(regsList))) {
+            if ((GenericUtils.length(regsList) > 0) && 
(!PropertyResolverUtils.isNoneValue(regsList))) {
                 String[] classes = GenericUtils.split(regsList, ',');
                 Logger logger = LoggerFactory.getLogger(SecurityUtils.class);
                 boolean debugEnabled = logger.isDebugEnabled();
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserAuthKeyboardInteractive.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserAuthKeyboardInteractive.java
index 99635be..fa81847 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserAuthKeyboardInteractive.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserAuthKeyboardInteractive.java
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.sshd.client.ClientAuthenticationManager;
 import org.apache.sshd.client.auth.AbstractUserAuth;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.RuntimeSshException;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.util.GenericUtils;
@@ -200,7 +201,8 @@ public class UserAuthKeyboardInteractive extends 
AbstractUserAuth {
         }
 
         int numResponses = rep.length;
-        buffer = 
session.createBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE, numResponses 
* Long.SIZE + Byte.SIZE);
+        buffer = session.createBuffer(
+            SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE, numResponses * 
Long.SIZE + Byte.SIZE);
         buffer.putInt(numResponses);
         for (int index = 0; index < numResponses; index++) {
             String r = rep[index];
@@ -274,12 +276,16 @@ public class UserAuthKeyboardInteractive extends 
AbstractUserAuth {
             return GenericUtils.EMPTY_STRING_ARRAY;
         }
 
-        String candidate = getCurrentPasswordCandidate();
-        if (useCurrentPassword(candidate, name, instruction, lang, prompt, 
echo)) {
-            if (debugEnabled) {
-                log.debug("getUserResponses({}) use password candidate for 
interaction={}", session, name);
+        if (PropertyResolverUtils.getBooleanProperty(
+                session, UserInteraction.AUTO_DETECT_PASSWORD_PROMPT,
+                UserInteraction.DEFAULT_AUTO_DETECT_PASSWORD_PROMPT)) {
+            String candidate = getCurrentPasswordCandidate();
+            if (useCurrentPassword(session, candidate, name, instruction, 
lang, prompt, echo)) {
+                if (debugEnabled) {
+                    log.debug("getUserResponses({}) use password candidate for 
interaction={}", session, name);
+                }
+                return new String[]{candidate};
             }
-            return new String[]{candidate};
         }
 
         UserInteraction ui = session.getUserInteraction();
@@ -304,22 +310,55 @@ public class UserAuthKeyboardInteractive extends 
AbstractUserAuth {
         return null;
     }
 
-    protected boolean useCurrentPassword(String password, String name, String 
instruction, String lang, String[] prompt, boolean[] echo) {
+    /**
+     * Checks if we have a candidate password and <U>exactly one</U> prompt is 
requested
+     * with no echo, and the prompt matches a configurable pattern.
+     *
+     * @param session The {@link ClientSession} through which the request is 
received
+     * @param password The current password candidate to use
+     * @param name The service name
+     * @param instruction The request instruction
+     * @param lang The reported language tag
+     * @param prompt The requested prompts
+     * @param echo The matching prompts echo flags
+     * @return Whether to use the password candidate as reply to the prompts
+     * @see UserInteraction#INTERACTIVE_PASSWORD_PROMPT 
INTERACTIVE_PASSWORD_PROMPT
+     * @see UserInteraction#CHECK_INTERACTIVE_PASSWORD_DELIM 
CHECK_INTERACTIVE_PASSWORD_DELIM
+     */
+    protected boolean useCurrentPassword(
+            ClientSession session, String password, String name,
+            String instruction, String lang, String[] prompt, boolean[] echo) {
         int num = GenericUtils.length(prompt);
         if ((num != 1) || (password == null) || echo[0]) {
             return false;
         }
 
-        // check that prompt is something like "XXX password YYY:"
-        String value = GenericUtils.trimToEmpty(prompt[0]).toLowerCase();
-        int passPos = value.lastIndexOf("password");
+        // check if prompt is something like "XXX password YYY:"
+        String value = GenericUtils.trimToEmpty(prompt[0]);
+        // Don't care about the case
+        value = value.toLowerCase();
+
+        String promptList = PropertyResolverUtils.getStringProperty(
+            session, UserInteraction.INTERACTIVE_PASSWORD_PROMPT,
+            UserInteraction.DEFAULT_INTERACTIVE_PASSWORD_PROMPT);
+        int passPos = UserInteraction.findPromptComponentLastPosition(value, 
promptList);
         if (passPos < 0) {  // no password keyword in prompt
             return false;
         }
 
-        int sepPos = value.lastIndexOf(':');
-        return sepPos > passPos;
+        String delimList = PropertyResolverUtils.getStringProperty(
+            session, UserInteraction.CHECK_INTERACTIVE_PASSWORD_DELIM,
+            UserInteraction.DEFAULT_CHECK_INTERACTIVE_PASSWORD_DELIM);
+        if (PropertyResolverUtils.isNoneValue(delimList)) {
+            return true;
+        }
 
+        int sepPos = UserInteraction.findPromptComponentLastPosition(value, 
delimList);
+        if (sepPos < passPos) {
+            return false;
+        }
+
+        return true;
     }
 
     public static String getAuthCommandName(int cmd) {
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
 
b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
index 5c294fa..1381d13 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
@@ -21,6 +21,7 @@ package org.apache.sshd.client.auth.keyboard;
 import java.util.List;
 
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
 
 /**
  * Interface used by the ssh client to communicate with the end user.
@@ -30,6 +31,38 @@ import org.apache.sshd.client.session.ClientSession;
  */
 public interface UserInteraction {
     /**
+     * Whether to auto-detect password challenge prompt
+     *
+     * @see #INTERACTIVE_PASSWORD_PROMPT
+     * @see #CHECK_INTERACTIVE_PASSWORD_DELIM
+     */
+    String AUTO_DETECT_PASSWORD_PROMPT = 
"user-interaction-auto-detect-password-prompt";
+
+    /** Default value for {@value #AUTO_DETECT_PASSWORD_PROMPT} */
+    boolean DEFAULT_AUTO_DETECT_PASSWORD_PROMPT = true;
+
+    /**
+     * Comma separated list of values used to detect request for a
+     * password in interactive mode. <B>Note:</B> the matched prompt
+     * is assumed to be <U>lowercase</U>.
+     */
+    String INTERACTIVE_PASSWORD_PROMPT = "user-interaction-password-prompt";
+
+    /** Default value for {@value #INTERACTIVE_PASSWORD_PROMPT} */
+    String DEFAULT_INTERACTIVE_PASSWORD_PROMPT = "password";
+
+    /**
+     * If password prompt detected then check it ends with
+     * <U>any</U> of the comma separated list of these values.
+     * Use &quot;none&quot; to disable this extra check. <B>Note:</B>
+     * the matched prompt is assumed to be <U>lowercase</U>.
+     */
+    String CHECK_INTERACTIVE_PASSWORD_DELIM = 
"user-interaction-check-password-delimiter";
+
+    /** Default value of {@value #CHECK_INTERACTIVE_PASSWORD_DELIM} */
+    String DEFAULT_CHECK_INTERACTIVE_PASSWORD_DELIM = ":";
+
+    /**
      * A useful &quot;placeholder&quot; that indicates that no interaction is 
expected.
      * <B>Note:</B> throws {@link IllegalStateException} is any of the 
interaction
      * methods is called
@@ -41,7 +74,9 @@ public interface UserInteraction {
         }
 
         @Override
-        public String[] interactive(ClientSession session, String name, String 
instruction, String lang, String[] prompt, boolean[] echo) {
+        public String[] interactive(
+                ClientSession session, String name, String instruction,
+                String lang, String[] prompt, boolean[] echo) {
             throw new IllegalStateException("interactive(" + session + ")[" + 
name + "] unexpected call");
         }
 
@@ -105,7 +140,8 @@ public interface UserInteraction {
      * however we do not enforce it since it is defined as the <U>server's</U>
      * job to check and manage this violation.
      */
-    String[] interactive(ClientSession session, String name, String 
instruction, String lang, String[] prompt, boolean[] echo);
+    String[] interactive(
+        ClientSession session, String name, String instruction, String lang, 
String[] prompt, boolean[] echo);
 
     /**
      * Invoked when the server returns an {@code 
SSH_MSG_USERAUTH_PASSWD_CHANGEREQ}
@@ -121,4 +157,26 @@ public interface UserInteraction {
      * be it other passwords, public keys, etc...)
      */
     String getUpdatedPassword(ClientSession session, String prompt, String 
lang);
+
+    /**
+     * @param prompt The user interaction prompt
+     * @param tokensList A comma-separated list of tokens whose
+     * <U>last</U> index is prompt is sought.
+     * @return The position of any token in the prompt - negative if not found
+     */
+    static int findPromptComponentLastPosition(String prompt, String 
tokensList) {
+        if (GenericUtils.isEmpty(prompt) || GenericUtils.isEmpty(tokensList)) {
+            return -1;
+        }
+
+        String[] tokens = GenericUtils.split(tokensList, ',');
+        for (String t : tokens) {
+            int pos = prompt.lastIndexOf(t);
+            if (pos >= 0) {
+                return pos;
+            }
+        }
+
+        return -1;
+    }
 }
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
index a2f1b24..662cb58 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/keyboard/InteractiveChallenge.java
@@ -125,6 +125,9 @@ public class InteractiveChallenge implements Cloneable {
 
     @Override
     public String toString() {
-        return getInteractionName() + "[" + getInteractionInstruction() + "](" 
+ getLanguageTag() + "): " + getPrompts();
+        return getInteractionName()
+            + "[" + getInteractionInstruction() + "]"
+            + "(" + getLanguageTag() + ")"
+            + ": " + getPrompts();
     }
 }
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
index 1d15b21..d5746d3 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
@@ -161,7 +161,7 @@ public final class SshServerConfigFileReader {
 
         if (GenericUtils.isEmpty(bannerOption)) {
             return "Welcome to SSHD\n";
-        } else if ("none".equals(bannerOption)) {
+        } else if (PropertyResolverUtils.isNoneValue(bannerOption)) {
             return null;
         } else if 
(ServerAuthenticationManager.AUTO_WELCOME_BANNER_VALUE.equalsIgnoreCase(bannerOption))
 {
             return bannerOption;
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java 
b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 434ac66..d3403c1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -1143,24 +1143,29 @@ public class ClientTest extends BaseTestSupport {
     @Test   // see SSHD-504
     public void 
testDefaultKeyboardInteractivePasswordPromptLocationIndependence() throws 
Exception {
         Collection<String> mismatchedPrompts = new LinkedList<>();
-        client.setUserAuthFactories(Collections.singletonList(new 
UserAuthKeyboardInteractiveFactory() {
-            @Override
-            public UserAuthKeyboardInteractive createUserAuth(ClientSession 
session) throws IOException {
-                return new UserAuthKeyboardInteractive() {
+        client.setUserAuthFactories(
+            Collections.singletonList(
+                new UserAuthKeyboardInteractiveFactory() {
                     @Override
-                    protected boolean useCurrentPassword(
-                            String password, String name, String instruction, 
String lang, String[] prompt, boolean[] echo) {
-                        boolean expected = GenericUtils.length(password) > 0;
-                        boolean actual = super.useCurrentPassword(password, 
name, instruction, lang, prompt, echo);
-                        if (expected != actual) {
-                            System.err.println("Mismatched usage result for 
prompt=" + prompt[0] + ": expected=" + expected + ", actual=actual");
-                            mismatchedPrompts.add(prompt[0]);
-                        }
-                        return actual;
+                    public UserAuthKeyboardInteractive 
createUserAuth(ClientSession session) throws IOException {
+                        return new UserAuthKeyboardInteractive() {
+                            @Override
+                            protected boolean useCurrentPassword(
+                                    ClientSession session, String password, 
String name, String instruction,
+                                    String lang, String[] prompt, boolean[] 
echo) {
+                                boolean expected = 
GenericUtils.length(password) > 0;
+                                boolean actual = super.useCurrentPassword(
+                                    session, password, name, instruction, 
lang, prompt, echo);
+                                if (expected != actual) {
+                                    System.err.println("Mismatched usage 
result for prompt=" + prompt[0]
+                                        + ": expected=" + expected + ", 
actual=actual");
+                                    mismatchedPrompts.add(prompt[0]);
+                                }
+                                return actual;
+                            }
+                        };
                     }
-                };
-            }
-        }));
+                }));
         client.start();
 
         Function<String, String> stripper = input -> {
@@ -1179,21 +1184,22 @@ public class ClientTest extends BaseTestSupport {
                 input -> getCurrentTestName() + " " + stripper.apply(input) + 
" " + getCurrentTestName() + ":"
             ));
 
-        sshd.setKeyboardInteractiveAuthenticator(new 
DefaultKeyboardInteractiveAuthenticator() {
-            private int xformerIndex;
+        sshd.setKeyboardInteractiveAuthenticator(
+            new DefaultKeyboardInteractiveAuthenticator() {
+                private int xformerIndex;
 
-            @Override
-            protected String getInteractionPrompt(ServerSession session) {
-                String original = super.getInteractionPrompt(session);
-                if (xformerIndex < xformers.size()) {
-                    Function<String, String> x = xformers.get(xformerIndex);
-                    xformerIndex++;
-                    return x.apply(original);
-                } else {
-                    return original;
+                @Override
+                protected String getInteractionPrompt(ServerSession session) {
+                    String original = super.getInteractionPrompt(session);
+                    if (xformerIndex < xformers.size()) {
+                        Function<String, String> x = 
xformers.get(xformerIndex);
+                        xformerIndex++;
+                        return x.apply(original);
+                    } else {
+                        return original;
+                    }
                 }
-            }
-        });
+            });
 
         try {
             for (int index = 0; index < xformers.size(); index++) {
@@ -1249,7 +1255,8 @@ public class ClientTest extends BaseTestSupport {
 
             @Override
             public String[] interactive(
-                    ClientSession session, String name, String instruction, 
String lang, String[] prompt, boolean[] echo) {
+                    ClientSession session, String name, String instruction,
+                    String lang, String[] prompt, boolean[] echo) {
                 validateSession("interactive", session);
                 count.incrementAndGet();
                 return badResponse;
@@ -1320,7 +1327,8 @@ public class ClientTest extends BaseTestSupport {
 
                 @Override
                 public String[] interactive(
-                        ClientSession clientSession, String name, String 
instruction, String lang, String[] prompt, boolean[] echo) {
+                        ClientSession clientSession, String name, String 
instruction,
+                        String lang, String[] prompt, boolean[] echo) {
                     assertSame("Mismatched interactive session", session, 
clientSession);
                     count.incrementAndGet();
                     return new String[]{getCurrentTestName()};
@@ -1374,7 +1382,8 @@ public class ClientTest extends BaseTestSupport {
 
                 @Override
                 public String[] interactive(
-                        ClientSession clientSession, String name, String 
instruction, String lang, String[] prompt, boolean[] echo) {
+                        ClientSession clientSession, String name, String 
instruction,
+                        String lang, String[] prompt, boolean[] echo) {
                     assertSame("Mismatched interactive session", session, 
clientSession);
                     int attemptId = count.incrementAndGet();
                     return new String[]{"bad#" + attemptId};
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java 
b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index faebaf1..62f4371 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -860,7 +860,8 @@ public class ServerTest extends BaseTestSupport {
 
             @Override
             public String[] interactive(
-                    ClientSession session, String name, String instruction, 
String lang, String[] prompt, boolean[] echo) {
+                    ClientSession session, String name, String instruction,
+                    String lang, String[] prompt, boolean[] echo) {
                 clientCount.incrementAndGet();
                 return replies;
             }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
index bcb5311..b58fea3 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerPhaseTest.java
@@ -116,12 +116,16 @@ public class WelcomeBannerPhaseTest extends 
BaseTestSupport {
             }
 
             @Override
-            public String[] interactive(ClientSession session, String name, 
String instruction, String lang, String[] prompt, boolean[] echo) {
+            public String[] interactive(
+                    ClientSession session, String name, String instruction,
+                    String lang, String[] prompt, boolean[] echo) {
                 return null;
             }
         });
 
-        try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+        try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port)
+                .verify(7L, TimeUnit.SECONDS)
+                .getSession()) {
             session.addPasswordIdentity(getCurrentTestName());
             session.auth().verify(5L, TimeUnit.SECONDS);
         }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java 
b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
index f3a5400..5b567fb 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/auth/WelcomeBannerTest.java
@@ -173,7 +173,9 @@ public class WelcomeBannerTest extends BaseTestSupport {
                 }
 
                 @Override
-                public String[] interactive(ClientSession session, String 
name, String instruction, String lang, String[] prompt, boolean[] echo) {
+                public String[] interactive(
+                        ClientSession session, String name, String instruction,
+                        String lang, String[] prompt, boolean[] echo) {
                     throw new UnsupportedOperationException("Unexpected 
interactive call");
                 }
 
@@ -183,7 +185,10 @@ public class WelcomeBannerTest extends BaseTestSupport {
                 }
             });
             PropertyResolverUtils.updateProperty(sshd, 
ServerAuthenticationManager.WELCOME_BANNER, getCurrentTestName());
-            try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+
+            try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port)
+                    .verify(7L, TimeUnit.SECONDS)
+                    .getSession()) {
                 assertTrue("Welcome not signalled on time", 
sigSem.tryAcquire(11L, TimeUnit.SECONDS));
                 session.addPasswordIdentity(getCurrentTestName());
                 session.auth().verify(5L, TimeUnit.SECONDS);
@@ -228,7 +233,9 @@ public class WelcomeBannerTest extends BaseTestSupport {
                 }
 
                 @Override
-                public String[] interactive(ClientSession session, String 
name, String instruction, String lang, String[] prompt, boolean[] echo) {
+                public String[] interactive(
+                        ClientSession session, String name, String instruction,
+                        String lang, String[] prompt, boolean[] echo) {
                     validateSession("interactive", session);
                     return null;
                 }
diff --git 
a/sshd-ldap/src/main/java/org/apache/sshd/common/util/net/LdapNetworkConnector.java
 
b/sshd-ldap/src/main/java/org/apache/sshd/common/util/net/LdapNetworkConnector.java
index 9dbd853..5c42675 100644
--- 
a/sshd-ldap/src/main/java/org/apache/sshd/common/util/net/LdapNetworkConnector.java
+++ 
b/sshd-ldap/src/main/java/org/apache/sshd/common/util/net/LdapNetworkConnector.java
@@ -40,6 +40,7 @@ import javax.naming.directory.InitialDirContext;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
@@ -412,7 +413,9 @@ public class LdapNetworkConnector<C> extends 
NetworkConnector {
      * or just the original one with some changes in it
      * @throws NamingException If failed to set up the environment
      */
-    protected Map<String, Object> setupDirContextEnvironment(C queryContext, 
Map<String, Object> env, String username, String password) throws 
NamingException {
+    protected Map<String, Object> setupDirContextEnvironment(
+            C queryContext, Map<String, Object> env, String username, String 
password)
+                throws NamingException {
         if (!env.containsKey(Context.PROVIDER_URL)) {
             int port = getPort();
             ValidateUtils.checkTrue(port > 0, "No port configured");
@@ -423,7 +426,7 @@ public class LdapNetworkConnector<C> extends 
NetworkConnector {
         }
 
         String mode = 
Objects.toString(env.get(Context.SECURITY_AUTHENTICATION), null);
-        boolean anonymous = GenericUtils.isEmpty(mode) || 
"none".equalsIgnoreCase(mode);
+        boolean anonymous = GenericUtils.isEmpty(mode) || 
PropertyResolverUtils.isNoneValue(mode);
         if (!anonymous) {
             Object[] bindParams = {username, password};
             if (!env.containsKey(Context.SECURITY_PRINCIPAL)) {
diff --git 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
index e33fecb..95f7e5d 100644
--- 
a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
+++ 
b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
@@ -27,6 +27,7 @@ import java.util.NavigableMap;
 import java.util.Set;
 import java.util.TreeMap;
 
+import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
@@ -51,13 +52,13 @@ public final class PGPUtils {
     public static final String PGP_ENCRYPTED_FILE = 
"application/pgp-encrypted";
 
     /** Alias for {@link EncryptionAlgorithm#Unencrypted Unencrypted} */
-    public static final String NO_CIPHER_PLACEHOLDER = "none";
+    public static final String NO_CIPHER_PLACEHOLDER = 
PropertyResolverUtils.NONE_VALUE;
 
     public static final Set<EncryptionAlgorithm> CIPHERS =
         Collections.unmodifiableSet(EnumSet.allOf(EncryptionAlgorithm.class));
 
     /** Alias for {@link CompressionAlgorithm#Uncompressed Uncompressed} */
-    public static final String NO_COMPRESSION_PLACEHOLDER = "none";
+    public static final String NO_COMPRESSION_PLACEHOLDER = 
PropertyResolverUtils.NONE_VALUE;
 
     public static final Set<CompressionAlgorithm> COMPRESSIONS =
         Collections.unmodifiableSet(EnumSet.allOf(CompressionAlgorithm.class));

Reply via email to