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 "keyboard-interactive" 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 "keyboard-interactive" 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