diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index c1af2723d..bab35ff11 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -316,8 +316,15 @@ class PacketReader { resetParser(); } else if (parser.getName().equals("failure")) { - // TLS negotiation has failed so close the connection. - throw new Exception("TLS negotiation has failed"); + if ("urn:ietf:params:xml:ns:xmpp-tls".equals(parser.getNamespace(null))) { + // TLS negotiation has failed. The server will close the connection + throw new Exception("TLS negotiation has failed"); + } + else { + // SASL authentication has failed. The server may close the connection + // depending on the number of retries + connection.getSASLAuthentication().authenticationFailed(); + } } else if (parser.getName().equals("challenge")) { // The server is challenging the SASL authentication made by the client diff --git a/source/org/jivesoftware/smack/SASLAuthentication.java b/source/org/jivesoftware/smack/SASLAuthentication.java index 1715409ce..a3e9e65ac 100644 --- a/source/org/jivesoftware/smack/SASLAuthentication.java +++ b/source/org/jivesoftware/smack/SASLAuthentication.java @@ -69,6 +69,11 @@ public class SASLAuthentication implements UserAuthentication { * Boolean indicating if SASL negotiation has finished and was successful. */ private boolean saslNegotiated = false; + /** + * Boolean indication if SASL authentication has failed. When failed the server may end + * the connection. + */ + private boolean saslFailed = false; private boolean resourceBinded = false; private boolean sessionSupported = false; @@ -186,12 +191,20 @@ public class SASLAuthentication implements UserAuthentication { // Wait until SASL negotiation finishes synchronized (this) { - try { - wait(30000); - } catch (InterruptedException e) { + if (!saslNegotiated && !saslFailed) { + try { + wait(30000); + } catch (InterruptedException e) { + } } } + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + throw new XMPPException("SASL authentication failed"); + } + if (saslNegotiated) { // We now need to bind a resource for the connection // Open a new stream and wait for the response @@ -249,8 +262,11 @@ public class SASLAuthentication implements UserAuthentication { return new NonSASLAuthentication(connection) .authenticate(username, password, resource); } - - } catch (Exception e) { + } + catch (XMPPException e) { + throw e; + } + catch (Exception e) { e.printStackTrace(); // SASL authentication failed so try a Non-SASL authentication return new NonSASLAuthentication(connection) @@ -280,7 +296,7 @@ public class SASLAuthentication implements UserAuthentication { // Wait until SASL negotiation finishes synchronized (this) { - if (!saslNegotiated) { + if (!saslNegotiated && !saslFailed) { try { wait(5000); } catch (InterruptedException e) { @@ -288,6 +304,12 @@ public class SASLAuthentication implements UserAuthentication { } } + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + throw new XMPPException("SASL authentication failed"); + } + if (saslNegotiated) { // Bind a resource for this connection and return bindResourceAndEstablishSession(null); @@ -366,6 +388,15 @@ public class SASLAuthentication implements UserAuthentication { this.serverMechanisms = mechanisms; } + /** + * Returns true if the user was able to authenticate with the server usins SASL. + * + * @return true if the user was able to authenticate with the server usins SASL. + */ + public boolean isAuthenticated() { + return saslNegotiated; + } + /** * The server is challenging the SASL authentication we just sent. Forward the challenge * to the current SASLMechanism we are using. The SASLMechanism will send a response to @@ -391,6 +422,18 @@ public class SASLAuthentication implements UserAuthentication { } } + /** + * Notification message saying that SASL authentication has failed. The server may have + * closed the connection depending on the number of possible retries. + */ + void authenticationFailed() { + synchronized (this) { + saslFailed = true; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + /** * Notification message saying that the server requires the client to bind a * resource to the stream. diff --git a/test/org/jivesoftware/smack/LoginTest.java b/test/org/jivesoftware/smack/LoginTest.java index 3850177d5..f69a75592 100644 --- a/test/org/jivesoftware/smack/LoginTest.java +++ b/test/org/jivesoftware/smack/LoginTest.java @@ -12,6 +12,7 @@ package org.jivesoftware.smack; import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.util.StringUtils; /** * Includes set of login tests. @@ -38,8 +39,10 @@ public class LoginTest extends SmackTestCase { fail("Invalid user was able to log into the server"); } catch (XMPPException e) { - assertEquals("Incorrect error code while login with an invalid user", 401, - e.getXMPPError().getCode()); + if (e.getXMPPError() != null) { + assertEquals("Incorrect error code while login with an invalid user", 401, + e.getXMPPError().getCode()); + } } // Wait here while trying tests with exodus //Thread.sleep(300); @@ -91,7 +94,16 @@ public class LoginTest extends SmackTestCase { } } conn.login("user_1", "user_1", null); - fail("User with no resource was able to log into the server"); + if (conn.getSASLAuthentication().isAuthenticated()) { + // Check that the server assigned a resource + assertNotNull("JID assigned by server is missing", conn.getUser()); + assertNotNull("JID assigned by server does not have a resource", + StringUtils.parseResource(conn.getUser())); + conn.close(); + } + else { + fail("User with no resource was able to log into the server"); + } } catch (XMPPException e) { assertEquals("Wrong error code returned", 406, e.getXMPPError().getCode());