You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/06/28 11:24:35 UTC

mina-sshd git commit: [SSHD-504] UserAuthKeyboardInteractive fails to match if prompt doesn't start password

Repository: mina-sshd
Updated Branches:
  refs/heads/master 0a4447a3d -> 1729c5ca5


[SSHD-504] UserAuthKeyboardInteractive fails to match if prompt doesn't start password


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1729c5ca
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1729c5ca
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1729c5ca

Branch: refs/heads/master
Commit: 1729c5ca5efb18ce539773d5c590792dfeea61d4
Parents: 0a4447a
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Sun Jun 28 12:24:21 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Sun Jun 28 12:24:21 2015 +0300

----------------------------------------------------------------------
 .gitignore                                      |   1 +
 .../sshd/agent/local/AgentServerProxy.java      |   1 -
 .../sshd/client/ClientFactoryManager.java       |  48 +++----
 .../java/org/apache/sshd/client/SshClient.java  |   2 +-
 .../org/apache/sshd/client/UserInteraction.java |   3 +-
 .../auth/UserAuthKeyboardInteractive.java       | 116 +++++++++++++---
 .../client/session/ClientConnectionService.java |   8 +-
 .../client/session/ClientUserAuthService.java   |   5 +-
 .../auth/UserAuthKeyboardInteractive.java       |  55 ++++++--
 .../server/session/ServerUserAuthService.java   |   9 +-
 .../java/org/apache/sshd/WelcomeBannerTest.java |   2 +-
 .../java/org/apache/sshd/client/ClientTest.java | 136 +++++++++++++++++--
 .../deprecated/UserAuthKeyboardInteractive.java |   2 +-
 .../git/transport/GitSshdSessionFactory.java    |   2 +-
 14 files changed, 303 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 61789e1..62ee886 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 target/
 Servers/
 .metadata/
+.recommenders/
 .settings/
 RemoteSystemsTempFiles/
 .classpath

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentServerProxy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentServerProxy.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentServerProxy.java
index 81885bb..0919be1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentServerProxy.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentServerProxy.java
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentServer;
-import org.apache.sshd.client.future.OpenFuture;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.util.GenericUtils;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 8af56a2..0af6014 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -22,7 +22,6 @@ import java.util.List;
 
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.forward.TcpipForwarderFactory;
 
 /**
  * The <code>ClientFactoryManager</code> enable the retrieval of additional
@@ -33,26 +32,40 @@ import org.apache.sshd.common.forward.TcpipForwarderFactory;
 public interface ClientFactoryManager extends FactoryManager {
 
     /**
-     * Key used to set the heartbeat interval in milliseconds (0 to disable which is the default value)
+     * Key used to set the heartbeat interval in milliseconds (0 to disable = default)
      */
-    public static final String HEARTBEAT_INTERVAL = "hearbeat-interval";
+    String HEARTBEAT_INTERVAL = "hearbeat-interval";
+        /**
+         * Default value for {@link #HEARTBEAT_INTERVAL} if none configured
+         */
+        long DEFAULT_HEARTBEAT_INTERVAL = 0L;
 
     /**
-     * Key used to check the hearbeat request that should be sent to the server (default is keepalive@sshd.apache.org).
+     * Key used to check the heartbeat request that should be sent to the server
      */
-    public static final String HEARTBEAT_REQUEST = "heartbeat-request";
+    String HEARTBEAT_REQUEST = "heartbeat-request";
+        /**
+         * Default value for {@link ClientFactoryManager#HEARTBEAT_REQUEST} is none configured
+         */
+        String DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING = "keepalive@sshd.apache.org";
 
     /**
      * Ordered comma separated list of authentications methods.
      * Authentications methods accepted by the server will be tried in the given order.
+     * If not configured or {@code null}/empty, then the session's {@link #getUserAuthFactories()}
+     * is used as-is
      */
-    public static final String PREFERRED_AUTHS = "preferred-auths";
+    String PREFERRED_AUTHS = "preferred-auths";
 
     /**
      * Specifies the number of password prompts before giving up.
-     * The argument to this keyword must be an integer.  The default is 3.
+     * The argument to this keyword must be an integer.
      */
-    public static final String PASSWORD_PROMPTS = "password-prompts";
+    String PASSWORD_PROMPTS = "password-prompts";
+        /**
+         * Default value for {@link #PASSWORD_PROMPTS} if none configured
+         */
+        int DEFAULT_PASSWORD_PROMPTS = 3;
 
     /**
      * Retrieve the server key verifier to be used to check the key when connecting
@@ -63,25 +76,14 @@ public interface ClientFactoryManager extends FactoryManager {
     ServerKeyVerifier getServerKeyVerifier();
 
     /**
-     * Retrieve the TcpipForwarder factory to be used to accept incoming connections
-     * and forward data.
-     *
-     * @return A <code>TcpipForwarderFactory</code>
-     */
-    @Override
-    TcpipForwarderFactory getTcpipForwarderFactory();
-
-    /**
-     * Retrieve the UserInteraction object to communicate with the user.
-     *
-     * @return A <code>UserInteraction</code> or <code>null</code>
+     * @return A {@link UserInteraction} object to communicate with the user
+     * (may be {@code null} to indicate that no such communication is allowed)
      */
     UserInteraction getUserInteraction();
 
     /**
-     * Retrieve a list of UserAuth factories.
-     *
-     * @return a list of named <code>UserAuth</code> factories, never <code>null</code>
+     * @return a {@link List} of {@link UserAuth} {@link NamedFactory}-ies - never
+     * {@code null}/empty
      */
     List<NamedFactory<UserAuth>> getUserAuthFactories();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index c1b70e8..9b814d7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -462,7 +462,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                     }
         
                     @Override
-                    public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+                    public String[] interactive(String destination, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
                         String[] answers = new String[prompt.length];
                         try {
                             for (int i = 0; i < prompt.length; i++) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/UserInteraction.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/UserInteraction.java b/sshd-core/src/main/java/org/apache/sshd/client/UserInteraction.java
index 840ec2c..d828a6b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/UserInteraction.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/UserInteraction.java
@@ -20,8 +20,8 @@ package org.apache.sshd.client;
 
 /**
  * Interface used by the ssh client to communicate with the end user.
- *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://www.ietf.org/rfc/rfc4256.txt">RFC 4256</A>
  */
 public interface UserInteraction {
 
@@ -35,6 +35,7 @@ public interface UserInteraction {
     String[] interactive(String destination,
                          String name,
                          String instruction,
+                         String lang,
                          String[] prompt,
                          boolean[] echo);
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
index 6adc678..4cbdc8f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthKeyboardInteractive.java
@@ -38,8 +38,8 @@ import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
- * TODO Add javadoc
- *
+ * Manages a &quot;keyboard-interactive&quot; exchange according to
+ * <A HREF="https://www.ietf.org/rfc/rfc4256.txt">RFC4256</A>
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements UserAuth {
@@ -83,7 +83,7 @@ public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements
             }
         }
         this.passwords = pwds.iterator();
-        this.maxTrials = session.getIntProperty(ClientFactoryManager.PASSWORD_PROMPTS, 3);
+        this.maxTrials = session.getIntProperty(ClientFactoryManager.PASSWORD_PROMPTS, ClientFactoryManager.DEFAULT_PASSWORD_PROMPTS);
     }
 
     @Override
@@ -96,7 +96,11 @@ public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements
             } else {
                 return false;
             }
-            log.debug("Send SSH_MSG_USERAUTH_REQUEST for keyboard-interactive");
+            
+            String username = session.getUsername();
+            if (log.isDebugEnabled()) {
+                log.debug("Send SSH_MSG_USERAUTH_REQUEST for keyboard-interactive - user={}, service={}", username, service);
+            }
             buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
             buffer.putString(session.getUsername());
             buffer.putString(service);
@@ -106,6 +110,7 @@ public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements
             session.writePacket(buffer);
             return true;
         }
+
         byte cmd = buffer.getByte();
         if (cmd == SSH_MSG_USERAUTH_INFO_REQUEST) {
             log.debug("Received SSH_MSG_USERAUTH_INFO_REQUEST");
@@ -113,40 +118,41 @@ public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements
             String instruction = buffer.getString();
             String language_tag = buffer.getString();
             if (log.isDebugEnabled()) {
-                log.debug("SSH_MSG_USERAUTH_INFO_REQUEST {} {} {}", name, instruction, language_tag);
+                log.debug("SSH_MSG_USERAUTH_INFO_REQUEST name={} instruction={} language={}", name, instruction, language_tag);
             }
+
             int num = buffer.getInt();
             String[] prompt = new String[num];
             boolean[] echo = new boolean[num];
             for (int i = 0; i < num; i++) {
+                // according to RFC4256: "The prompt field(s) MUST NOT be empty strings."
                 prompt[i] = buffer.getString();
                 echo[i] = (buffer.getByte() != 0);
             }
             
             if (log.isDebugEnabled()) {
-                log.debug("Promt: {}", Arrays.toString(prompt));
+                log.debug("Prompt: {}", Arrays.toString(prompt));
                 log.debug("Echo: {}", echo);
             }
 
-            String[] rep = null;
-            if (num == 0) {
-                rep = GenericUtils.EMPTY_STRING_ARRAY;
-            } else if (num == 1 && current != null && !echo[0] && prompt[0].toLowerCase().startsWith("password:")) {
-                rep = new String[] { current };
-            } else {
-                UserInteraction ui = session.getUserInteraction();
-                if (ui == null) {
-                    ui = session.getFactoryManager().getUserInteraction();
-                }
-                if (ui != null) {
-                    String dest = session.getUsername() + "@" + session.getIoSession().getRemoteAddress().toString();
-                    rep = ui.interactive(dest, name, instruction, prompt, echo);
-                }
-            }
+            String[] rep = getUserResponses(name, instruction, language_tag, prompt, echo);
             if (rep == null) {
                 return false;
             }
 
+            /*
+             * According to RFC4256:
+             * 
+             *      If the num-responses field does not match the num-prompts
+             *      field in the request message, the server MUST send a failure
+             *      message.
+             *
+             * However it is the server's (!) responsibility to fail, so we only warn...
+             */
+            if (num != rep.length) {
+                log.warn("Mismatched prompts (" + num + ") vs. responses count (" + rep.length + ")");
+            }
+
             buffer = session.createBuffer(SSH_MSG_USERAUTH_INFO_RESPONSE);
             buffer.putInt(rep.length);
             for (String r : rep) {
@@ -155,9 +161,75 @@ public class UserAuthKeyboardInteractive extends AbstractLoggingBean implements
             session.writePacket(buffer);
             return true;
         }
-        throw new IllegalStateException("Received unknown packet");
+        throw new IllegalStateException("Received unknown packet: cmd=" + cmd);
+    }
+
+    protected String getCurrentPasswordCandidate() {
+        return current;
+    }
+
+    /**
+     * @param name The interaction name - may be empty
+     * @param instruction The instruction - may be empty
+     * @param lang The language tag - may be empty
+     * @param prompt The prompts - may be empty
+     * @param echo Whether to echo the response for the prompt or not - same
+     * length as the prompts
+     * @return The response for each prompt - if {@code null} then the assumption
+     * is that some internal error occurred and no response is sent. <B>Note:</B>
+     * according to <A HREF="https://www.ietf.org/rfc/rfc4256.txt">RFC4256</A>
+     * the number of responses should be <U>exactly</U> the same as the number
+     * of prompts. However, since it is the <U>server's</U> responsibility to
+     * enforce this we do not validate the response (other than logging it as
+     * a warning...)  
+     */
+    protected String[] getUserResponses(String name, String instruction, String lang, String[] prompt, boolean[] echo) {
+        int num = GenericUtils.length(prompt);
+        if (num == 0) {
+            return GenericUtils.EMPTY_STRING_ARRAY;
+        }
+
+        String candidate = getCurrentPasswordCandidate();
+        if (useCurrentPassword(candidate, name, instruction, lang, prompt, echo)) { 
+            return new String[] { candidate };
+        } else {
+            UserInteraction ui = session.getUserInteraction();
+            if (ui == null) {
+                ClientFactoryManager manager = session.getFactoryManager(); 
+                ui = manager.getUserInteraction();
+            }
+
+            if (ui != null) {
+                String dest = session.getUsername() + "@" + session.getIoSession().getRemoteAddress().toString();
+                return ui.interactive(dest, name, instruction, lang, prompt, echo);
+            }
+        }
+        
+        return null;
+    }
+
+    protected boolean useCurrentPassword(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 = prompt[0].toLowerCase();
+        int passPos = value.lastIndexOf("password");
+        if (passPos < 0) {  // no password keyword in prompt
+            return false;
+        }
+
+        int sepPos = value.lastIndexOf(':');
+        if (sepPos <= passPos) {    // no prompt separator or separator before the password keyword
+            return false;
+        }
+        
+        return true;
     }
 
+
     @Override
     public void destroy() {
         // nothing

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
index b3ec2d1..9be89e3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientConnectionService.java
@@ -41,10 +41,6 @@ import org.apache.sshd.common.util.buffer.Buffer;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class ClientConnectionService extends AbstractConnectionService {
-
-    public static final String DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING = "keepalive@sshd.apache.org";
-    public static final long DEFAULT_HEARTBEAT_INTERVAL = 0L;
-
     public static class Factory implements ServiceFactory {
 
         @Override
@@ -73,7 +69,7 @@ public class ClientConnectionService extends AbstractConnectionService {
     }
 
     protected void startHeartBeat() {
-        long interval = FactoryManagerUtils.getLongProperty(session, ClientFactoryManager.HEARTBEAT_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL);
+        long interval = FactoryManagerUtils.getLongProperty(session, ClientFactoryManager.HEARTBEAT_INTERVAL, ClientFactoryManager.DEFAULT_HEARTBEAT_INTERVAL);
         if (interval > 0L) {
             FactoryManager manager = session.getFactoryManager();
             ScheduledExecutorService service = manager.getScheduledExecutorService();
@@ -90,7 +86,7 @@ public class ClientConnectionService extends AbstractConnectionService {
     }
 
     protected void sendHeartBeat() {
-        String request = FactoryManagerUtils.getStringProperty(session, ClientFactoryManager.HEARTBEAT_REQUEST, DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING);
+        String request = FactoryManagerUtils.getStringProperty(session, ClientFactoryManager.HEARTBEAT_REQUEST, ClientFactoryManager.DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING);
         try {
             Buffer buf = session.createBuffer(SshConstants.SSH_MSG_GLOBAL_REQUEST);
             buf.putString(request);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
index 72d8601..1658e48 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
@@ -81,10 +81,11 @@ public class ClientUserAuthService extends CloseableUtils.AbstractCloseable impl
         }
         session = (ClientSessionImpl) s;
         authFuture = new DefaultAuthFuture(session.getLock());
-        authFactories = session.getFactoryManager().getUserAuthFactories();
+        ClientFactoryManager manager = session.getFactoryManager();
+        authFactories = manager.getUserAuthFactories();
         clientMethods = new ArrayList<>();
 
-        String prefs = FactoryManagerUtils.getString(session, ClientFactoryManager.PREFERRED_AUTHS);
+        String prefs = FactoryManagerUtils.getString(manager, ClientFactoryManager.PREFERRED_AUTHS);
         if (!GenericUtils.isEmpty(prefs)) {
             for (String pref : prefs.split(",")) {
                 if (NamedFactory.Utils.get(authFactories, pref) != null) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
index 2972a9d..867dda0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthKeyboardInteractive.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sshd.server.auth;
 
+import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
@@ -30,12 +31,10 @@ import org.apache.sshd.server.UserAuth;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
- * TODO Add javadoc
- *
+ * Issue a &quot;keyboard-interactive&quot; command according to <A HREF="https://www.ietf.org/rfc/rfc4256.txt">RFC4256</A>
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthKeyboardInteractive extends AbstractUserAuth {
-
     public static class UserAuthKeyboardInteractiveFactory implements NamedFactory<UserAuth> {
         public static final UserAuthKeyboardInteractiveFactory INSTANCE = new UserAuthKeyboardInteractiveFactory();
 
@@ -54,6 +53,18 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
         }
     }
 
+    // configuration parameters on the FactoryManager to configure the message values
+    public static final String KB_INTERACTIVE_NAME_PROP = "kb-interactive-name";
+        public static final String DEFAULT_KB_INTERACTIVE_NAME = "Password authentication";
+    public static final String KB_INTERACTIVE_INSTRUCTION_PROP = "kb-interactive-instruction";
+        public static final String DEFAULT_KB_INTERACTIVE_INSTRUCTION = "";
+    public static final String KB_INTERACTIVE_LANG_PROP = "kb-interactive-language";
+        public static final String DEFAULT_KB_INTERACTIVE_LANG = "en-US";
+    public static final String KB_INTERACTIVE_PROMPT_PROP = "kb-interactive-prompt";
+        public static final String DEFAULT_KB_INTERACTIVE_PROMPT = "Password: ";
+    public static final String KB_INTERACTIVE_ECHO_PROMPT_PROP = "kb-interactive-echo-prompt";
+        public static final boolean DEFAULT_KB_INTERACTIVE_ECHO_PROMPT = false;
+
     public UserAuthKeyboardInteractive() {
         super();
     }
@@ -63,12 +74,12 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
         if (init) {
             // Prompt for password
             buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST);
-            buffer.putString("Password authentication");
-            buffer.putString("");
-            buffer.putString("en-US");
+            buffer.putString(getInteractionName());
+            buffer.putString(getInteractionInstruction());
+            buffer.putString(getInteractionLanguage());
             buffer.putInt(1);
-            buffer.putString("Password: ");
-            buffer.putBoolean(false);
+            buffer.putString(getInteractionPrompt());
+            buffer.putBoolean(isInteractionPromptEchoEnabled());
             session.writePacket(buffer);
             return null;
         } else {
@@ -77,6 +88,13 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
                 throw new SshException("Received unexpected message: " + cmd);
             }
             int num = buffer.getInt();
+            /*
+             * According to RFC4256:
+             * 
+             *      If the num-responses field does not match the num-prompts
+             *      field in the request message, the server MUST send a failure
+             *      message.
+             */
             if (num != 1) {
                 throw new SshException("Expected 1 response from user but received " + num);
             }
@@ -85,6 +103,26 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
         }
     }
 
+    protected String getInteractionName() {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_NAME_PROP, DEFAULT_KB_INTERACTIVE_NAME);
+    }
+
+    protected String getInteractionInstruction() {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_INSTRUCTION_PROP, DEFAULT_KB_INTERACTIVE_INSTRUCTION);
+    }
+
+    protected String getInteractionLanguage() {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_LANG_PROP, DEFAULT_KB_INTERACTIVE_LANG);
+    }
+
+    protected String getInteractionPrompt() {
+        return FactoryManagerUtils.getStringProperty(session, KB_INTERACTIVE_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_PROMPT);
+    }
+
+    protected boolean isInteractionPromptEchoEnabled() {
+        return FactoryManagerUtils.getBooleanProperty(session, KB_INTERACTIVE_ECHO_PROMPT_PROP, DEFAULT_KB_INTERACTIVE_ECHO_PROMPT);
+    }
+
     protected boolean checkPassword(ServerSession session, String username, String password) throws Exception {
         ServerFactoryManager manager = session.getFactoryManager();
         PasswordAuthenticator auth = ValidateUtils.checkNotNull(
@@ -93,5 +131,4 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
                 GenericUtils.EMPTY_BYTE_ARRAY);
         return auth.authenticate(username, password, session);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
index 75bb3df..6a7a95d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java
@@ -58,6 +58,7 @@ public class ServerUserAuthService extends CloseableUtils.AbstractCloseable impl
         }
     }
 
+    public static final int DEFAULT_MAX_AUTH_REQUESTS = 20;
     private final ServerSession session;
     private List<NamedFactory<UserAuth>> userAuthFactories;
     private List<List<String>> authMethods;
@@ -66,7 +67,7 @@ public class ServerUserAuthService extends CloseableUtils.AbstractCloseable impl
     private String authService;
     private UserAuth currentAuth;
 
-    private int maxAuthRequests = 20;
+    private int maxAuthRequests;
     private int nbAuthRequests;
 
     public ServerUserAuthService(Session s) throws SshException {
@@ -76,13 +77,13 @@ public class ServerUserAuthService extends CloseableUtils.AbstractCloseable impl
         }
 
         this.session = (ServerSession) s;
-        maxAuthRequests = session.getIntProperty(ServerFactoryManager.MAX_AUTH_REQUESTS, maxAuthRequests);
+        maxAuthRequests = session.getIntProperty(ServerFactoryManager.MAX_AUTH_REQUESTS, DEFAULT_MAX_AUTH_REQUESTS);
 
-        userAuthFactories = new ArrayList<>(getFactoryManager().getUserAuthFactories());
+        ServerFactoryManager  manager=getFactoryManager();
+        userAuthFactories = new ArrayList<>(manager.getUserAuthFactories());
         // Get authentication methods
         authMethods = new ArrayList<>();
         
-        ServerFactoryManager  manager=getFactoryManager();
         String mths = FactoryManagerUtils.getString(manager, ServerFactoryManager.AUTH_METHODS);
         if (GenericUtils.isEmpty(mths)) {
             for (NamedFactory<UserAuth> uaf : manager.getUserAuthFactories()) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
index 1f05de4..624be01 100644
--- a/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/WelcomeBannerTest.java
@@ -74,7 +74,7 @@ public class WelcomeBannerTest extends BaseTestSupport {
                     welcome.set(banner);
                 }
                 @Override
-                public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+                public String[] interactive(String destination, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
                     return null;
                 }
             });

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
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 d85ff14..b214aff 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
@@ -29,6 +29,10 @@ import java.net.SocketAddress;
 import java.security.KeyPair;
 import java.security.PublicKey;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -65,6 +69,7 @@ import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Transformer;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
@@ -292,8 +297,8 @@ public class ClientTest extends BaseTestSupport {
 
                 channel.setOut(stdout);
                 channel.setErr(stderr);
-                channel.open().await();
-                Thread.sleep(100);
+                channel.open().verify(9L, TimeUnit.SECONDS);
+                Thread.sleep(100L);
                 try {
                     for (int i = 0; i < 100; i++) {
                         channel.getInvertedIn().write("a".getBytes());
@@ -567,8 +572,8 @@ public class ClientTest extends BaseTestSupport {
             AuthFuture authFuture = session.auth();
             CloseFuture closeFuture = session.close(false);
             authLatch.countDown();
-            authFuture.await();
-            closeFuture.await();
+            assertTrue("Authentication writing not completed in time", authFuture.await(11L, TimeUnit.SECONDS));
+            assertTrue("Session closing not complete in time", closeFuture.await(8L, TimeUnit.SECONDS));
             assertNotNull("No authentication exception", authFuture.getException());
             assertTrue("Future not closed", closeFuture.isClosed());
         } finally {
@@ -595,8 +600,8 @@ public class ClientTest extends BaseTestSupport {
 
                 OpenFuture openFuture = channel.open();
                 CloseFuture closeFuture = session.close(false);
-                openFuture.await();
-                closeFuture.await();
+                assertTrue("Channel not open in time", openFuture.await(11L, TimeUnit.SECONDS));
+                assertTrue("Session closing not complete in time", closeFuture.await(8L, TimeUnit.SECONDS));
                 assertTrue("Not open", openFuture.isOpened());
                 assertTrue("Not closed", closeFuture.isClosed());
             }
@@ -626,8 +631,8 @@ public class ClientTest extends BaseTestSupport {
                 OpenFuture openFuture = channel.open();
                 CloseFuture closeFuture = session.close(true);
                 channelLatch.countDown();
-                openFuture.await();
-                closeFuture.await();
+                assertTrue("Channel not open in time", openFuture.await(11L, TimeUnit.SECONDS));
+                assertTrue("Session closing not complete in time", closeFuture.await(8L, TimeUnit.SECONDS));
                 assertNotNull("No open exception", openFuture.getException());
                 assertTrue("Not closed", closeFuture.isClosed());
             }
@@ -740,6 +745,107 @@ public class ClientTest extends BaseTestSupport {
         }
     }
 
+    @Test   // see SSHD-504
+    public void testKeyboardInteractivePasswordPromptLocationIndependence() throws Exception {
+        final Collection<String> mismatchedPrompts = new LinkedList<String>();
+        client.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(new UserAuthKeyboardInteractive.UserAuthKeyboardInteractiveFactory() {
+                @Override
+                public UserAuth create() {
+                    return new UserAuthKeyboardInteractive() {
+                        @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;
+                        }
+                    };
+                }
+            }));
+        client.start();
+
+        final Transformer<String,String> stripper = new Transformer<String,String>() {
+                @Override
+                public String transform(String input) {
+                    int pos = GenericUtils.isEmpty(input) ? (-1) : input.lastIndexOf(':');
+                    if (pos < 0) {
+                        return input;
+                    } else {
+                        return input.substring(0, pos);
+                    }
+                }
+            };
+        final List<Transformer<String,String>> xformers = 
+                Collections.unmodifiableList(Arrays.<Transformer<String,String>>asList(
+                            new Transformer<String,String>() {  // prefixed
+                                    @Override
+                                    public String transform(String input) {
+                                        return getCurrentTestName() + " " + input;
+                                    }
+                                },
+                            new Transformer<String,String>() {  // suffixed
+                                    @Override
+                                    public String transform(String input) {
+                                        return stripper.transform(input) + " " + getCurrentTestName() + ":";
+                                    }
+                                },
+                            new Transformer<String,String>() {  // infix
+                                    @Override
+                                    public String transform(String input) {
+                                        return getCurrentTestName() + " " + stripper.transform(input) + " " + getCurrentTestName() + ":";
+                                    }
+                                }
+                        ));
+        sshd.setUserAuthFactories(Arrays.<NamedFactory<org.apache.sshd.server.UserAuth>>asList(
+                new org.apache.sshd.server.auth.UserAuthKeyboardInteractive.UserAuthKeyboardInteractiveFactory() {
+                    private int xformerIndex;
+
+                    @Override
+                    public org.apache.sshd.server.UserAuth create() {
+                        return new org.apache.sshd.server.auth.UserAuthKeyboardInteractive() {
+    
+                            @SuppressWarnings("synthetic-access")
+                            @Override
+                            protected String getInteractionPrompt() {
+                                String original = super.getInteractionPrompt();
+                                if (xformerIndex < xformers.size()) {
+                                    Transformer<String,String> x = xformers.get(xformerIndex);
+                                    xformerIndex++;
+                                    return x.transform(original);
+                                } else {
+                                    return original;
+                                }
+                            }
+                        };
+                    }
+            }));
+
+        try {
+            for (int index = 0; index < xformers.size(); index++) {
+                try(ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(700L, TimeUnit.SECONDS).getSession()) {
+                    String password = "bad-" + getCurrentTestName() + "-" + index;
+                    session.addPasswordIdentity(password);
+                    
+                    AuthFuture future = session.auth();
+                    assertTrue("Failed to verify password=" + password + " in time", future.await(500L, TimeUnit.SECONDS));
+                    assertFalse("Unexpected success for password=" + password, future.isSuccess());
+                    session.removePasswordIdentity(password);
+                }
+            }
+
+            try(ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(700L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(500L, TimeUnit.SECONDS);
+                assertTrue("Mismatched prompts evaluation results", mismatchedPrompts.isEmpty());
+            }
+        } finally {
+            client.stop();
+        }
+    }
+
     @Test
     public void testKeyboardInteractiveWithFailures() throws Exception {
         final AtomicInteger count = new AtomicInteger();
@@ -753,7 +859,7 @@ public class ClientTest extends BaseTestSupport {
                 // ignored
             }
             @Override
-            public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+            public String[] interactive(String destination, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
                 count.incrementAndGet();
                 return new String[] { "bad" };
             }
@@ -788,7 +894,7 @@ public class ClientTest extends BaseTestSupport {
                     }
         
                     @Override
-                    public String[] interactive(String destination, String name, String instruction,
+                    public String[] interactive(String destination, String name, String instruction, String lang,
                                                 String[] prompt, boolean[] echo) {
                         count.incrementAndGet();
                         return new String[] { getCurrentTestName() };
@@ -821,14 +927,14 @@ public class ClientTest extends BaseTestSupport {
                 }
     
                 @Override
-                public String[] interactive(String destination, String name, String instruction,
+                public String[] interactive(String destination, String name, String instruction, String lang,
                                             String[] prompt, boolean[] echo) {
-                    count.incrementAndGet();
-                    return new String[] { "bad" };
+                    int attemptId = count.incrementAndGet();
+                    return new String[] { "bad#" + attemptId };
                 }
             });
             AuthFuture future = session.auth();
-            future.await();
+            assertTrue("Authentication not completed in time", future.await(11L, TimeUnit.SECONDS));
             assertTrue("Authentication not, marked as failure", future.isFailure());
             assertEquals("Mismatched authentication retry count", MAX_PROMPTS, count.get());
         } finally {
@@ -864,7 +970,7 @@ public class ClientTest extends BaseTestSupport {
                     buffer.putString("Cancel");
                     buffer.putString("");
                     IoWriteFuture f = cs.writePacket(buffer);
-                    f.await();
+                    assertTrue("Packet writing not completed in time", f.await(11L, TimeUnit.SECONDS));
                     suspend(cs.getIoSession());
     
                     TestEchoShellFactory.TestEchoShell.latch.await();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthKeyboardInteractive.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthKeyboardInteractive.java b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthKeyboardInteractive.java
index 5e41bd7..d0f4c78 100644
--- a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthKeyboardInteractive.java
+++ b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthKeyboardInteractive.java
@@ -86,7 +86,7 @@ public class UserAuthKeyboardInteractive extends AbstractUserAuth {
                         UserInteraction ui = session.getFactoryManager().getUserInteraction();
                         if (ui != null) {
                             String dest = session.getUsername() + "@" + session.getIoSession().getRemoteAddress().toString();
-                            rep = ui.interactive(dest, name, instruction, prompt, echo);
+                            rep = ui.interactive(dest, name, instruction, language_tag, prompt, echo);
                         }
                     }
                     if (rep == null) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1729c5ca/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
index f14b0fb..ec7a544 100644
--- a/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
+++ b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
@@ -115,7 +115,7 @@ public class GitSshdSessionFactory extends SshSessionFactory {
             if (pass2 != null) {
                 session.addPasswordIdentity(new String(pass2));
             }
-            session.auth().verify(FactoryManagerUtils.getLongProperty(client, AUTH_TIMEOUT_PROP, DEFAULT_AUTH_TIMEOUT);
+            session.auth().verify(FactoryManagerUtils.getLongProperty(client, AUTH_TIMEOUT_PROP, DEFAULT_AUTH_TIMEOUT));
         }
 
         @Override