diff --git a/.travis.yml b/.travis.yml
index f5c99a7f6..382050bb7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1 +1,10 @@
-language: java
\ No newline at end of file
+language: java
+
+before_install:
+ - sudo apt-get update -qq
+ - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch; fi
+ - wget http://dl.google.com/android/android-sdk_r23.0.2-linux.tgz
+ - tar xzf android-*
+ - export ANDROID_HOME=$PWD/android-sdk-linux
+ - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
+ - echo "y" | android update sdk --filter platform-tools,android-8 --no-ui --force
diff --git a/build.gradle b/build.gradle
index 2d43e6f45..b0e231db1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -237,7 +237,7 @@ subprojects {
}
}
-['smack-resolver-javax'].each { name ->
+['smack-resolver-javax', 'smack-sasl-javax', 'smack-sasl-provided'].each { name ->
project(":$name") {
jar {
manifest {
diff --git a/settings.gradle b/settings.gradle
index 21ac3fcea..f12514295 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,8 +6,11 @@ include 'smack-core',
'smack-resolver-dnsjava',
'smack-resolver-minidns',
'smack-resolver-javax',
+ 'smack-sasl-javax',
+ 'smack-sasl-provided',
'smack-compression-jzlib',
'smack-legacy',
'smack-jingle',
'smack-bosh',
- 'smack-java7'
\ No newline at end of file
+ 'smack-android',
+ 'smack-java7'
diff --git a/smack-android/build.gradle b/smack-android/build.gradle
new file mode 100644
index 000000000..92eb8ee5b
--- /dev/null
+++ b/smack-android/build.gradle
@@ -0,0 +1,50 @@
+description = """\
+Smack for Android.
+All the required dependencies to run Smack on Android"""
+
+ext {
+ smackMinAndroidSdk = 8
+ androidProjects = [':smack-tcp',':smack-core', ':smack-resolver-minidns', ':smack-sasl-provided', ':smack-extensions', ':smack-experimental'].collect{ project(it) }
+}
+
+// Note that the test dependencies (junit, …) are inferred from the
+// sourceSet.test of the core subproject
+dependencies {
+ androidProjects.each { project ->
+ compile project
+ }
+}
+
+def getAndroidRuntimeJar() {
+ def androidHome = new File("$System.env.ANDROID_HOME")
+ if (!androidHome.isDirectory()) throw new Exception("ANDROID_HOME not found or set")
+ new File("$androidHome/platforms/android-$smackMinAndroidSdk/android.jar")
+}
+
+def getAndroidJavadocOffline() {
+ def androidHome = new File("$System.env.ANDROID_HOME")
+ if (!androidHome.isDirectory()) throw new Exception("ANDROID_HOME not found or set")
+ return "$System.env.ANDROID_HOME" + "/docs/reference"
+}
+
+compileJava {
+ options.bootClasspath = getAndroidRuntimeJar()
+}
+
+// See http://stackoverflow.com/a/2823592/194894
+// TODO this doesn't seem to work right now. But on the other hand it
+// is not really required, just to avoid a javadoc compiler warning
+javadoc {
+ options.linksOffline "http://developer.android.com/reference", getAndroidJavadocOffline()
+}
+
+configure (androidProjects) {
+ task compileAndroid(type: JavaCompile) {
+ source = compileJava.source
+ classpath = compileJava.classpath
+ destinationDir = new File(buildDir, 'android')
+ options.bootClasspath = getAndroidRuntimeJar()
+ }
+}
+
+test { dependsOn androidProjects*.compileAndroid }
\ No newline at end of file
diff --git a/smack-android/src/main/java/org/jivesoftware/smackx/debugger/android/AndroidDebugger.java b/smack-android/src/main/java/org/jivesoftware/smackx/debugger/android/AndroidDebugger.java
new file mode 100644
index 000000000..efdcc6469
--- /dev/null
+++ b/smack-android/src/main/java/org/jivesoftware/smackx/debugger/android/AndroidDebugger.java
@@ -0,0 +1,174 @@
+/**
+ *
+ * Copyright © 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.debugger.android;
+
+import org.jivesoftware.smack.debugger.SmackDebugger;
+import org.jivesoftware.smack.AbstractConnectionListener;
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.*;
+
+import android.util.Log;
+
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * Very simple debugger that prints to the android log the sent and received stanzas. Use
+ * this debugger with caution since printing to the console is an expensive operation that may
+ * even block the thread since only one thread may print at a time.
+ *
+ * It is possible to not only print the raw sent and received stanzas but also the interpreted
+ * packets by Smack. By default interpreted packets won't be printed. To enable this feature
+ * just change the printInterpreted static variable to true.
+ *
+ */
+public class AndroidDebugger implements SmackDebugger {
+
+ public static boolean printInterpreted = false;
+
+ private XMPPConnection connection = null;
+
+ private PacketListener listener = null;
+ private ConnectionListener connListener = null;
+
+ private Writer writer;
+ private Reader reader;
+ private ReaderListener readerListener;
+ private WriterListener writerListener;
+
+ public AndroidDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+ this.connection = connection;
+ this.writer = writer;
+ this.reader = reader;
+ createDebug();
+ }
+
+ /**
+ * Creates the listeners that will print in the console when new activity is detected.
+ */
+ private void createDebug() {
+ // Create a special Reader that wraps the main Reader and logs data to the GUI.
+ ObservableReader debugReader = new ObservableReader(reader);
+ readerListener = new ReaderListener() {
+ public void read(String str) {
+ Log.d("SMACK", "RCV (" + connection.getConnectionCounter() +
+ "): " + str);
+ }
+ };
+ debugReader.addReaderListener(readerListener);
+
+ // Create a special Writer that wraps the main Writer and logs data to the GUI.
+ ObservableWriter debugWriter = new ObservableWriter(writer);
+ writerListener = new WriterListener() {
+ public void write(String str) {
+ Log.d("SMACK", "SENT (" + connection.getConnectionCounter() +
+ "): " + str);
+ }
+ };
+ debugWriter.addWriterListener(writerListener);
+
+ // Assign the reader/writer objects to use the debug versions. The packet reader
+ // and writer will use the debug versions when they are created.
+ reader = debugReader;
+ writer = debugWriter;
+
+ // Create a thread that will listen for all incoming packets and write them to
+ // the GUI. This is what we call "interpreted" packet data, since it's the packet
+ // data as Smack sees it and not as it's coming in as raw XML.
+ listener = new PacketListener() {
+ public void processPacket(Packet packet) {
+ if (printInterpreted) {
+ Log.d("SMACK", "RCV PKT (" + connection.getConnectionCounter() +
+ "): " + packet.toXML());
+ }
+ }
+ };
+
+ connListener = new AbstractConnectionListener() {
+ public void connectionClosed() {
+ Log.d("SMACK", "Connection closed (" + connection.getConnectionCounter() + ")");
+ }
+
+ public void connectionClosedOnError(Exception e) {
+ Log.d("SMACK", "Connection closed due to an exception (" +
+ connection.getConnectionCounter() + ")");
+ }
+ public void reconnectionFailed(Exception e) {
+ Log.d("SMACK", "Reconnection failed due to an exception (" +
+ connection.getConnectionCounter() + ")");
+ }
+ public void reconnectionSuccessful() {
+ Log.d("SMACK", "Connection reconnected (" +
+ connection.getConnectionCounter() + ")");
+ }
+ public void reconnectingIn(int seconds) {
+ Log.d("SMACK", "Connection (" + connection.getConnectionCounter() +
+ ") will reconnect in " + seconds);
+ }
+ };
+ }
+
+ public Reader newConnectionReader(Reader newReader) {
+ ((ObservableReader)reader).removeReaderListener(readerListener);
+ ObservableReader debugReader = new ObservableReader(newReader);
+ debugReader.addReaderListener(readerListener);
+ reader = debugReader;
+ return reader;
+ }
+
+ public Writer newConnectionWriter(Writer newWriter) {
+ ((ObservableWriter)writer).removeWriterListener(writerListener);
+ ObservableWriter debugWriter = new ObservableWriter(newWriter);
+ debugWriter.addWriterListener(writerListener);
+ writer = debugWriter;
+ return writer;
+ }
+
+ public void userHasLogged(String user) {
+ String title =
+ "User logged (" + connection.getConnectionCounter() + "): "
+ + user
+ + "@"
+ + connection.getServiceName()
+ + ":"
+ + connection.getPort();
+ Log.d("SMACK", title);
+ // Add the connection listener to the connection so that the debugger can be notified
+ // whenever the connection is closed.
+ connection.addConnectionListener(connListener);
+ }
+
+ public Reader getReader() {
+ return reader;
+ }
+
+ public Writer getWriter() {
+ return writer;
+ }
+
+ public PacketListener getReaderListener() {
+ return listener;
+ }
+
+ public PacketListener getWriterListener() {
+ return null;
+ }
+}
+
diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHPacketReader.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHPacketReader.java
index 91a557c68..7a456a742 100644
--- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHPacketReader.java
+++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHPacketReader.java
@@ -20,9 +20,9 @@ package org.jivesoftware.smack.bosh;
import java.io.StringReader;
import org.jivesoftware.smack.packet.Packet;
-import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
-import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
-import org.jivesoftware.smack.sasl.SASLMechanism.Success;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -97,8 +97,9 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"),
connection.getServiceName())
.build());
- connection.getSASLAuthentication().authenticated();
- connection.processPacket(new Success(parser.nextText()));
+ Success success = new Success(parser.nextText());
+ connection.getSASLAuthentication().authenticated(success);
+ connection.processPacket(success);
} else if (parser.getName().equals("features")) {
parseFeatures(parser);
} else if (parser.getName().equals("failure")) {
diff --git a/smack-core/build.gradle b/smack-core/build.gradle
index b0d4a8547..847876ae3 100644
--- a/smack-core/build.gradle
+++ b/smack-core/build.gradle
@@ -4,6 +4,7 @@ Smack core components."""
configurations {
compression
dns
+ sasl
}
dependencies {
@@ -34,9 +35,23 @@ task dnsJar(type: Jar) {
include('org/jivesoftware/smack/initializer/**')
}
+task saslJar(type: Jar) {
+ appendix = 'sasl'
+ dependsOn classes
+ from sourceSets.main.output
+ include('org/jivesoftware/smack/SASLAuthentication.class')
+ include('org/jivesoftware/smack/SmackException.class')
+ include('org/jivesoftware/smack/XMPPConnection.class')
+ include('org/jivesoftware/smack/sasl/**')
+ include('org/jivesoftware/smack/initializer/**')
+ include('org/jivesoftware/smack/util/StringUtils.class')
+ include('org/jivesoftware/smack/util/ByteUtils.class')
+}
+
artifacts {
compression compressionJar
dns dnsJar
+ sasl saslJar
}
class CreateFileTask extends DefaultTask {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
index dea05c0a7..358d65778 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -33,8 +33,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.security.sasl.SaslException;
-
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.ConnectionException;
@@ -60,7 +58,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private static final String[] DEBUGGERS = new String[] {
"org.jivesoftware.smackx.debugger.EnhancedDebugger",
"org.jivesoftware.smackx.debugger.android.AndroidDebugger",
- "org.jivesoftware.smack.debugger.LiteDebugger" };
+ "org.jivesoftware.smack.debugger.LiteDebugger",
+ "org.jivesoftware.smack.debugger.ConsoleDebugger" };
/**
* Counter to uniquely identify connections that are created.
@@ -347,9 +346,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException
- * @throws SaslException
*/
- public void login(String username, String password) throws XMPPException, SmackException, SaslException, IOException {
+ public void login(String username, String password) throws XMPPException, SmackException, IOException {
login(username, password, "Smack");
}
@@ -378,9 +376,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException
- * @throws SaslException
*/
- public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException;
+ public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, IOException;
/**
* Logs in to the server anonymously. Very few servers are configured to support anonymous
@@ -391,9 +388,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException
- * @throws SaslException
*/
- public abstract void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException;
+ public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
/**
* Notification message saying that the server requires the client to bind a
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java
index 263e40e89..70e7d2085 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java
@@ -18,54 +18,37 @@
package org.jivesoftware.smack;
import org.jivesoftware.smack.SmackException.NoResponseException;
-import org.jivesoftware.smack.SmackException.NotConnectedException;
-import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
-import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.sasl.SASLAnonymous;
-import org.jivesoftware.smack.sasl.SASLCramMD5Mechanism;
-import org.jivesoftware.smack.sasl.SASLDigestMD5Mechanism;
import org.jivesoftware.smack.sasl.SASLErrorException;
-import org.jivesoftware.smack.sasl.SASLExternalMechanism;
-import org.jivesoftware.smack.sasl.SASLGSSAPIMechanism;
import org.jivesoftware.smack.sasl.SASLMechanism;
-import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
-import org.jivesoftware.smack.sasl.SASLPlainMechanism;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
import javax.security.auth.callback.CallbackHandler;
-import javax.security.sasl.SaslException;
import java.io.IOException;
-import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* This class is responsible authenticating the user using SASL, binding the resource
* to the connection and establishing a session with the server.
*
* Once TLS has been negotiated (i.e. the connection has been secured) it is possible to
- * register with the server, authenticate using Non-SASL or authenticate using SASL. If the
- * server supports SASL then Smack will first try to authenticate using SASL. But if that
- * fails then Non-SASL will be tried.
+ * register with the server or authenticate using SASL. If the
+ * server supports SASL then Smack will try to authenticate using SASL..
*
* The server may support many SASL mechanisms to use for authenticating. Out of the box
* Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use
- * {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered
- * mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default,
- * the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}.
- *
- * Once the user has been authenticated with SASL, it is necessary to bind a resource for
- * the connection. If no resource is passed in {@link #authenticate(String, String, String)}
- * then the server will assign a resource for the connection. In case a resource is passed
- * then the server will receive the desired resource but may assign a modified resource for
- * the connection.
- *
- * Once a resource has been binded and if the server supports sessions then Smack will establish
- * a session so that instant messaging and presence functionalities may be used.
+ * {@link #registerSASLMechanism(SASLMechanism)} to register a new mechanisms.
*
* @see org.jivesoftware.smack.sasl.SASLMechanism
*
@@ -74,114 +57,91 @@ import java.util.Map;
*/
public class SASLAuthentication {
- private static Map> implementedMechanisms = new HashMap>();
- private static List mechanismsPreferences = new ArrayList();
+ private static final List REGISTERED_MECHANISMS = new ArrayList();
- private XMPPConnection connection;
+ private static final Set BLACKLISTED_MECHANISMS = new HashSet();
+
+ /**
+ * Registers a new SASL mechanism
+ *
+ * @param mechanism a SASLMechanism subclass.
+ */
+ public static void registerSASLMechanism(SASLMechanism mechanism) {
+ synchronized (REGISTERED_MECHANISMS) {
+ REGISTERED_MECHANISMS.add(mechanism);
+ Collections.sort(REGISTERED_MECHANISMS);
+ }
+ }
+
+ /**
+ * Returns the registered SASLMechanism sorted by the level of preference.
+ *
+ * @return the registered SASLMechanism sorted by the level of preference.
+ */
+ public static Map getRegisterdSASLMechanisms() {
+ Map answer = new HashMap();
+ synchronized (REGISTERED_MECHANISMS) {
+ for (SASLMechanism mechanism : REGISTERED_MECHANISMS) {
+ answer.put(mechanism.getClass().getName(), mechanism.getName());
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Unregister a SASLMechanism by it's full class name. For example
+ * "org.jivesoftware.smack.sasl.javax.SASLCramMD5Mechanism".
+ *
+ * @param clazz the SASLMechanism class's name
+ * @return true if the given SASLMechanism was removed, false otherwise
+ */
+ public static boolean unregisterSASLMechanism(String clazz) {
+ synchronized (REGISTERED_MECHANISMS) {
+ Iterator it = REGISTERED_MECHANISMS.iterator();
+ while (it.hasNext()) {
+ SASLMechanism mechanism = it.next();
+ if (mechanism.getClass().getName().equals(clazz)) {
+ it.remove();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean blacklistSASLMechanism(String mechansim) {
+ synchronized(BLACKLISTED_MECHANISMS) {
+ return BLACKLISTED_MECHANISMS.add(mechansim);
+ }
+ }
+
+ public static boolean unBlacklistSASLMechanism(String mechanism) {
+ synchronized(BLACKLISTED_MECHANISMS) {
+ return BLACKLISTED_MECHANISMS.remove(mechanism);
+ }
+ }
+
+ public static Set getBlacklistedSASLMechanisms() {
+ synchronized(BLACKLISTED_MECHANISMS) {
+ return new HashSet(BLACKLISTED_MECHANISMS);
+ }
+ }
+
+ private final AbstractXMPPConnection connection;
private Collection serverMechanisms = new ArrayList();
private SASLMechanism currentMechanism = null;
+
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean saslNegotiated;
/**
- * The SASL related error condition if there was one provided by the server.
+ * Either of type {@link SmackException} or {@link SASLErrorException}
*/
- private SASLFailure saslFailure;
+ private Exception saslException;
- static {
-
- // Register SASL mechanisms supported by Smack
- registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class);
- registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class);
- registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class);
- registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class);
- registerSASLMechanism("PLAIN", SASLPlainMechanism.class);
- registerSASLMechanism("ANONYMOUS", SASLAnonymous.class);
-
- supportSASLMechanism("GSSAPI",0);
- supportSASLMechanism("DIGEST-MD5",1);
- supportSASLMechanism("CRAM-MD5",2);
- supportSASLMechanism("PLAIN",3);
- supportSASLMechanism("ANONYMOUS",4);
-
- }
-
- /**
- * Registers a new SASL mechanism
- *
- * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
- * @param mClass a SASLMechanism subclass.
- */
- public static void registerSASLMechanism(String name, Class extends SASLMechanism> mClass) {
- implementedMechanisms.put(name, mClass);
- }
-
- /**
- * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't
- * be possible to authenticate users using the removed SASL mechanism. It also removes the
- * mechanism from the supported list.
- *
- * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
- */
- public static void unregisterSASLMechanism(String name) {
- implementedMechanisms.remove(name);
- mechanismsPreferences.remove(name);
- }
-
-
- /**
- * Registers a new SASL mechanism in the specified preference position. The client will try
- * to authenticate using the most prefered SASL mechanism that is also supported by the server.
- * The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)}
- *
- * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
- */
- public static void supportSASLMechanism(String name) {
- mechanismsPreferences.add(0, name);
- }
-
- /**
- * Registers a new SASL mechanism in the specified preference position. The client will try
- * to authenticate using the most prefered SASL mechanism that is also supported by the server.
- * Use the index parameter to set the level of preference of the new SASL mechanism.
- * A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be
- * registered via {@link #registerSASLMechanism(String, Class)}
- *
- * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
- * @param index preference position amongst all the implemented SASL mechanism. Starts with 0.
- */
- public static void supportSASLMechanism(String name, int index) {
- mechanismsPreferences.add(index, name);
- }
-
- /**
- * Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't
- * be possible to authenticate users using the removed SASL mechanism. Note that the mechanism
- * is still registered, but will just not be used.
- *
- * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
- */
- public static void unsupportSASLMechanism(String name) {
- mechanismsPreferences.remove(name);
- }
-
- /**
- * Returns the registerd SASLMechanism classes sorted by the level of preference.
- *
- * @return the registerd SASLMechanism classes sorted by the level of preference.
- */
- public static List> getRegisterSASLMechanisms() {
- List> answer = new ArrayList>();
- for (String mechanismsPreference : mechanismsPreferences) {
- answer.add(implementedMechanisms.get(mechanismsPreference));
- }
- return answer;
- }
-
- SASLAuthentication(XMPPConnection connection) {
- super();
+ SASLAuthentication(AbstractXMPPConnection connection) {
this.connection = connection;
this.init();
}
@@ -216,42 +176,16 @@ public class SASLAuthentication {
* @param cbh the CallbackHandler used to get information from the user
* @throws IOException
* @throws XMPPErrorException
- * @throws NoResponseException
* @throws SASLErrorException
- * @throws ResourceBindingNotOfferedException
- * @throws NotConnectedException
+ * @throws SmackException
*/
public void authenticate(String resource, CallbackHandler cbh) throws IOException,
- NoResponseException, XMPPErrorException, SASLErrorException, ResourceBindingNotOfferedException, NotConnectedException {
- // Locate the SASLMechanism to use
- String selectedMechanism = null;
- for (String mechanism : mechanismsPreferences) {
- if (implementedMechanisms.containsKey(mechanism)
- && serverMechanisms.contains(mechanism)) {
- selectedMechanism = mechanism;
- break;
- }
- }
+ XMPPErrorException, SASLErrorException, SmackException {
+ SASLMechanism selectedMechanism = selectMechanism();
if (selectedMechanism != null) {
- // A SASL mechanism was found. Authenticate using the selected mechanism and then
- // proceed to bind a resource
- Class extends SASLMechanism> mechanismClass = implementedMechanisms.get(selectedMechanism);
-
- Constructor extends SASLMechanism> constructor;
- try {
- constructor = mechanismClass.getConstructor(SASLAuthentication.class);
- currentMechanism = constructor.newInstance(this);
- }
- catch (Exception e) {
- throw new SaslException("Exception when creating the SASLAuthentication instance",
- e);
- }
-
+ currentMechanism = selectedMechanism;
synchronized (this) {
- // Trigger SASL authentication with the selected mechanism. We use
- // connection.getHost() since GSAPI requires the FQDN of the server, which
- // may not match the XMPP domain.
- currentMechanism.authenticate(connection.getHost(), cbh);
+ currentMechanism.authenticate(connection.getHost(), connection.getServiceName(), cbh);
try {
// Wait until SASL negotiation finishes
wait(connection.getPacketReplyTimeout());
@@ -261,18 +195,14 @@ public class SASLAuthentication {
}
}
- if (saslFailure != null) {
- // SASL authentication failed and the server may have closed the connection
- // so throw an exception
- throw new SASLErrorException(selectedMechanism, saslFailure);
- }
+ maybeThrowException();
if (!saslNegotiated) {
throw new NoResponseException();
}
}
else {
- throw new SaslException(
+ throw new SmackException(
"SASL Authentication failed. No known authentication mechanisims.");
}
}
@@ -291,47 +221,18 @@ public class SASLAuthentication {
* @throws XMPPErrorException
* @throws SASLErrorException
* @throws IOException
- * @throws SaslException
* @throws SmackException
*/
public void authenticate(String username, String password, String resource)
- throws XMPPErrorException, SASLErrorException, SaslException, IOException,
+ throws XMPPErrorException, SASLErrorException, IOException,
SmackException {
- // Locate the SASLMechanism to use
- String selectedMechanism = null;
- for (String mechanism : mechanismsPreferences) {
- if (implementedMechanisms.containsKey(mechanism)
- && serverMechanisms.contains(mechanism)) {
- selectedMechanism = mechanism;
- break;
- }
- }
+ SASLMechanism selectedMechanism = selectMechanism();
if (selectedMechanism != null) {
- // A SASL mechanism was found. Authenticate using the selected mechanism and then
- // proceed to bind a resource
- Class extends SASLMechanism> mechanismClass = implementedMechanisms.get(selectedMechanism);
- try {
- Constructor extends SASLMechanism> constructor = mechanismClass.getConstructor(SASLAuthentication.class);
- currentMechanism = constructor.newInstance(this);
- }
- catch (Exception e) {
- throw new SaslException("Exception when creating the SASLAuthentication instance",
- e);
- }
+ currentMechanism = selectedMechanism;
synchronized (this) {
- // Trigger SASL authentication with the selected mechanism. We use
- // connection.getHost() since GSAPI requires the FQDN of the server, which
- // may not match the XMPP domain.
-
- // The serviceName is basically the value that XMPP server sends to the client as
- // being
- // the location of the XMPP service we are trying to connect to. This should have
- // the
- // format: host ["/" serv-name ] as per RFC-2831 guidelines
- String serviceName = connection.getServiceName();
- currentMechanism.authenticate(username, connection.getHost(), serviceName, password);
-
+ currentMechanism.authenticate(username, connection.getHost(),
+ connection.getServiceName(), password);
try {
// Wait until SASL negotiation finishes
wait(connection.getPacketReplyTimeout());
@@ -339,21 +240,16 @@ public class SASLAuthentication {
catch (InterruptedException e) {
// Ignore
}
-
}
- if (saslFailure != null) {
- // SASL authentication failed and the server may have closed the connection
- // so throw an exception
- throw new SASLErrorException(selectedMechanism, saslFailure);
- }
+ maybeThrowException();
if (!saslNegotiated) {
throw new NoResponseException();
}
}
else {
- throw new SaslException(
+ throw new SmackException(
"SASL Authentication failed. No known authentication mechanisims.");
}
}
@@ -367,14 +263,12 @@ public class SASLAuthentication {
* no username.
*
* @throws SASLErrorException
- * @throws IOException
- * @throws SaslException
* @throws XMPPErrorException if an error occures while authenticating.
* @throws SmackException if there was no response from the server.
*/
- public void authenticateAnonymously() throws SASLErrorException, SaslException, IOException,
+ public void authenticateAnonymously() throws SASLErrorException,
SmackException, XMPPErrorException {
- currentMechanism = new SASLAnonymous(this);
+ currentMechanism = (new SASLAnonymous()).instanceForAuthentication(connection);
// Wait until SASL negotiation finishes
synchronized (this) {
@@ -387,17 +281,24 @@ public class SASLAuthentication {
}
}
- if (saslFailure != null) {
- // SASL authentication failed and the server may have closed the connection
- // so throw an exception
- throw new SASLErrorException(currentMechanism.toString(), saslFailure);
- }
+ maybeThrowException();
if (!saslNegotiated) {
throw new NoResponseException();
}
}
+ private void maybeThrowException() throws SmackException, SASLErrorException {
+ if (saslException != null){
+ if (saslException instanceof SmackException) {
+ throw (SmackException) saslException;
+ } else if (saslException instanceof SASLErrorException) {
+ throw (SASLErrorException) saslException;
+ } else {
+ throw new IllegalStateException("Unexpected exception type" , saslException);
+ }
+ }
+ }
/**
* Sets the available SASL mechanism reported by the server. The server will report the
* available SASL mechanism once the TLS negotiation was successful. This information is
@@ -411,33 +312,49 @@ public class SASLAuthentication {
}
/**
- * 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.
+ * Wrapper for {@link #challengeReceived(String, boolean)}, with finalChallenge
set
+ * to false
.
+ *
+ * @param challenge a base64 encoded string representing the challenge.
+ * @throws SmackException
*/
- public boolean isAuthenticated() {
- return saslNegotiated;
+ public void challengeReceived(String challenge) throws SmackException {
+ challengeReceived(challenge, false);
}
/**
* 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
+ * to the current SASLMechanism we are using. The SASLMechanism will eventually send a response to
* the server. The length of the challenge-response sequence varies according to the
* SASLMechanism in use.
*
* @param challenge a base64 encoded string representing the challenge.
- * @throws IOException If a network error occures while authenticating.
- * @throws NotConnectedException
+ * @param finalChallenge true if this is the last challenge send by the server within the success stanza
+ * @throws SmackException
*/
- public void challengeReceived(String challenge) throws IOException, NotConnectedException {
- currentMechanism.challengeReceived(challenge);
+ public void challengeReceived(String challenge, boolean finalChallenge) throws SmackException {
+ try {
+ currentMechanism.challengeReceived(challenge, finalChallenge);
+ } catch (SmackException e) {
+ authenticationFailed(e);
+ throw e;
+ }
}
/**
* Notification message saying that SASL authentication was successful. The next step
* would be to bind the resource.
+ * @throws SmackException
*/
- public void authenticated() {
+ public void authenticated(Success success) throws SmackException {
+ // RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP
+ // "receiving entity") can include "additional data with success" if appropriate for the
+ // SASL mechanism in use. In XMPP, this is done by including the additional data as the XML
+ // character data of the element." The used SASL mechanism should be able to
+ // verify the data send by the server in the success stanza, if any.
+ if (success.getData() != null) {
+ challengeReceived(success.getData(), true);
+ }
saslNegotiated = true;
// Wake up the thread that is waiting in the #authenticate method
synchronized (this) {
@@ -453,18 +370,17 @@ public class SASLAuthentication {
* @see RFC6120 6.5
*/
public void authenticationFailed(SASLFailure saslFailure) {
- this.saslFailure = saslFailure;
+ authenticationFailed(new SASLErrorException(currentMechanism.getName(), saslFailure));
+ }
+
+ public void authenticationFailed(Exception exception) {
+ saslException = exception;
// Wake up the thread that is waiting in the #authenticate method
synchronized (this) {
notify();
}
}
- public void send(Packet stanza) throws NotConnectedException {
- connection.sendPacket(stanza);
- }
-
-
/**
* Initializes the internal state in order to be able to be reused. The authentication
* is used by the connection at the first login and then reused after the connection
@@ -472,6 +388,28 @@ public class SASLAuthentication {
*/
protected void init() {
saslNegotiated = false;
- saslFailure = null;
+ saslException = null;
+ }
+
+ private SASLMechanism selectMechanism() {
+ // Locate the SASLMechanism to use
+ SASLMechanism selectedMechanism = null;
+ Iterator it = REGISTERED_MECHANISMS.iterator();
+ // Iterate in SASL Priority order over registered mechanisms
+ while (it.hasNext()) {
+ SASLMechanism mechanism = it.next();
+ String mechanismName = mechanism.getName();
+ synchronized (BLACKLISTED_MECHANISMS) {
+ if (BLACKLISTED_MECHANISMS.contains(mechanismName)) {
+ continue;
+ }
+ }
+ if (serverMechanisms.contains(mechanismName)) {
+ // Create a new instance of the SASLMechanism for every authentication attempt.
+ selectedMechanism = mechanism.instanceForAuthentication(connection);
+ break;
+ }
+ }
+ return selectedMechanism;
}
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
index 11f225b04..1a20f6e96 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
@@ -432,7 +432,7 @@ public final class SmackConfiguration {
if (SmackInitializer.class.isAssignableFrom(initClass)) {
SmackInitializer initializer = (SmackInitializer) initClass.newInstance();
List exceptions = initializer.initialize();
- if (exceptions.size() == 0) {
+ if (exceptions == null || exceptions.size() == 0) {
LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className);
} else {
for (Exception e : exceptions) {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLAnonymous.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLAnonymous.java
index 9234293e7..d22a9942c 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLAnonymous.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLAnonymous.java
@@ -16,10 +16,7 @@
*/
package org.jivesoftware.smack.sasl;
-import org.jivesoftware.smack.SASLAuthentication;
-import org.jivesoftware.smack.SmackException.NotConnectedException;
-
-import java.io.IOException;
+import org.jivesoftware.smack.SmackException;
import javax.security.auth.callback.CallbackHandler;
@@ -30,32 +27,30 @@ import javax.security.auth.callback.CallbackHandler;
*/
public class SASLAnonymous extends SASLMechanism {
- public SASLAnonymous(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
- }
-
- protected String getName() {
+ public String getName() {
return "ANONYMOUS";
}
- public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, NotConnectedException {
- authenticate();
+ @Override
+ public int getPriority() {
+ return 500;
}
- public void authenticate(String username, String host, String password) throws IOException, NotConnectedException {
- authenticate();
+ @Override
+ protected void authenticateInternal(CallbackHandler cbh)
+ throws SmackException {
+ // Nothing to do here
}
- protected void authenticate() throws IOException, NotConnectedException {
- // Send the authentication to the server
- getSASLAuthentication().send(new AuthMechanism(getName(), null));
+ @Override
+ protected String getAuthenticationText() throws SmackException {
+ // TODO Auto-generated method stub
+ return null;
}
- public void challengeReceived(String challenge) throws IOException, NotConnectedException {
- // Build the challenge response stanza encoding the response text
- // and send the authentication to the server
- getSASLAuthentication().send(new Response());
+ @Override
+ public SASLAnonymous newInstance() {
+ return new SASLAnonymous();
}
-
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java
index 460f9ff13..756a1cfa5 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java
@@ -20,7 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.XMPPException;
-import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
public class SASLErrorException extends XMPPException {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java
deleted file mode 100644
index 21bb01ad9..000000000
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- *
- * Copyright the original author or authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jivesoftware.smack.sasl;
-
-import org.jivesoftware.smack.SASLAuthentication;
-import org.jivesoftware.smack.SmackException.NotConnectedException;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.HashMap;
-
-import javax.security.sasl.Sasl;
-import javax.security.sasl.SaslException;
-import javax.security.auth.callback.CallbackHandler;
-
-/**
- * Implementation of the SASL GSSAPI mechanism
- *
- * @author Jay Kline
- */
-public class SASLGSSAPIMechanism extends SASLMechanism {
-
- public SASLGSSAPIMechanism(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
-
- System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
- System.setProperty("java.security.auth.login.config","gss.conf");
-
- }
-
- protected String getName() {
- return "GSSAPI";
- }
-
- /**
- * Builds and sends the auth stanza to the server.
- * This overrides from the abstract class because the initial token
- * needed for GSSAPI is binary, and not safe to put in a string, thus
- * getAuthenticationText() cannot be used.
- *
- * @param username the username of the user being authenticated.
- * @param host the hostname where the user account resides.
- * @param cbh the CallbackHandler (not used with GSSAPI)
- * @throws IOException If a network error occures while authenticating.
- * @throws NotConnectedException
- */
- public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
- String[] mechanisms = { getName() };
- Map props = new HashMap();
- props.put(Sasl.SERVER_AUTH,"TRUE");
- sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
- authenticate();
- }
-
- /**
- * Builds and sends the auth stanza to the server.
- * This overrides from the abstract class because the initial token
- * needed for GSSAPI is binary, and not safe to put in a string, thus
- * getAuthenticationText() cannot be used.
- *
- * @param username the username of the user being authenticated.
- * @param host the hostname where the user account resides.
- * @param password the password of the user (ignored for GSSAPI)
- * @throws IOException If a network error occures while authenticating.
- * @throws NotConnectedException
- */
- public void authenticate(String username, String host, String password) throws IOException, SaslException, NotConnectedException {
- String[] mechanisms = { getName() };
- Map props = new HashMap();
- props.put(Sasl.SERVER_AUTH,"TRUE");
- sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
- authenticate();
- }
-
-}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java
index fc896f72b..3ed4b3ed2 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2003-2007 Jive Software.
+ * Copyright 2003-2007 Jive Software, 2014 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,14 @@
*/
package org.jivesoftware.smack.sasl;
-import org.jivesoftware.smack.SASLAuthentication;
+import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
-import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.AuthMechanism;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Response;
import org.jivesoftware.smack.util.StringUtils;
-import java.io.IOException;
-import java.util.Map;
-import java.util.HashMap;
-
import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.sasl.RealmCallback;
-import javax.security.sasl.RealmChoiceCallback;
-import javax.security.sasl.Sasl;
-import javax.security.sasl.SaslClient;
-import javax.security.sasl.SaslException;
/**
* Base class for SASL mechanisms. Subclasses must implement these methods:
@@ -44,9 +33,9 @@ import javax.security.sasl.SaslException;
* Subclasses will likely want to implement their own versions of these methods:
* {@link #authenticate(String, String, String, String)} -- Initiate authentication stanza using the
* deprecated method.
- * {@link #authenticate(String, CallbackHandler)} -- Initiate authentication stanza
+ * {@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza
* using the CallbackHandler method.
- * {@link #challengeReceived(String)} -- Handle a challenge from the server.
+ * {@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.
*
*
* Basic XMPP SASL authentication steps:
@@ -73,26 +62,40 @@ import javax.security.sasl.SaslException;
*
* @author Jay Kline
*/
-public abstract class SASLMechanism implements CallbackHandler {
+public abstract class SASLMechanism implements Comparable {
- private SASLAuthentication saslAuthentication;
- protected SaslClient sc;
+ public static final String CRAMMD5 = "CRAM-MD5";
+ public static final String DIGESTMD5 = "DIGEST-MD5";
+ public static final String EXTERNAL = "EXTERNAL";
+ public static final String GSSAPI = "GSSAPI";
+ public static final String PLAIN = "PLAIN";
+
+ protected XMPPConnection connection;
+
+ /**
+ * authcid
+ */
protected String authenticationId;
- protected String password;
- protected String hostname;
- public SASLMechanism(SASLAuthentication saslAuthentication) {
- this.saslAuthentication = saslAuthentication;
- }
+ /**
+ * The name of the XMPP service
+ */
+ protected String serviceName;
+
+ /**
+ * The users password
+ */
+ protected String password;
+ protected String host;
/**
* Builds and sends the auth stanza to the server. Note that this method of
- * authentication is not recommended, since it is very inflexable. Use
- * {@link #authenticate(String, CallbackHandler)} whenever possible.
+ * authentication is not recommended, since it is very inflexible. Use
+ * {@link #authenticate(String, String, CallbackHandler)} whenever possible.
*
* Explanation of auth stanza:
*
- * The client authentication stanza needs to include the digest-uri of the form: xmpp/serverName
+ * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName
* From RFC-2831:
* digest-uri = "digest-uri" "=" digest-uri-value
* digest-uri-value = serv-type "/" host [ "/" serv-name ]
@@ -129,70 +132,67 @@ public abstract class SASLMechanism implements CallbackHandler {
* @param serviceName the xmpp service location - used by the SASL client in digest-uri creation
* serviceName format is: host [ "/" serv-name ] as per RFC-2831
* @param password the password for this account.
- * @throws IOException If a network error occurs while authenticating.
- * @throws SaslException
+ * @throws SmackException If a network error occurs while authenticating.
* @throws NotConnectedException
*/
- public void authenticate(String username, String host, String serviceName, String password) throws IOException, SaslException, NotConnectedException {
- //Since we were not provided with a CallbackHandler, we will use our own with the given
- //information
-
- //Set the authenticationID as the username, since they must be the same in this case.
+ public final void authenticate(String username, String host, String serviceName, String password)
+ throws SmackException, NotConnectedException {
this.authenticationId = username;
+ this.host = host;
+ this.serviceName = serviceName;
this.password = password;
- this.hostname = host;
-
- String[] mechanisms = { getName() };
- Map props = new HashMap();
- sc = Sasl.createSaslClient(mechanisms, null, "xmpp", serviceName, props, this);
+ authenticateInternal();
authenticate();
}
+ protected void authenticateInternal() throws SmackException {
+ }
+
/**
* Builds and sends the auth stanza to the server. The callback handler will handle
* any additional information, such as the authentication ID or realm, if it is needed.
*
* @param host the hostname where the user account resides.
+ * @param serviceName the xmpp service location
* @param cbh the CallbackHandler to obtain user information.
- * @throws IOException If a network error occures while authenticating.
- * @throws SaslException If a protocol error occurs or the user is not authenticated.
+ * @throws SmackException
* @throws NotConnectedException
*/
- public void authenticate(String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
- String[] mechanisms = { getName() };
- Map props = new HashMap();
- sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
+ public void authenticate(String host,String serviceName, CallbackHandler cbh)
+ throws SmackException, NotConnectedException {
+ this.host = host;
+ this.serviceName = serviceName;
+ authenticateInternal(cbh);
authenticate();
}
- protected void authenticate() throws IOException, SaslException, NotConnectedException {
- String authenticationText = null;
- if (sc.hasInitialResponse()) {
- byte[] response = sc.evaluateChallenge(new byte[0]);
- authenticationText = StringUtils.encodeBase64(response, false);
- }
+ protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;
+ private final void authenticate() throws SmackException, NotConnectedException {
+ String authenticationText = getAuthenticationText();
// Send the authentication to the server
- getSASLAuthentication().send(new AuthMechanism(getName(), authenticationText));
+ connection.sendPacket(new AuthMechanism(getName(), authenticationText));
}
+ protected abstract String getAuthenticationText() throws SmackException;
+
/**
* The server is challenging the SASL mechanism for the stanza he just sent. Send a
* response to the server's challenge.
*
- * @param challenge a base64 encoded string representing the challenge.
- * @throws IOException if an exception sending the response occurs.
- * @throws NotConnectedException
+ * @param challengeString a base64 encoded string representing the challenge.
+ * @param finalChallenge true if this is the last challenge send by the server within the success stanza
+ * @throws NotConnectedException
+ * @throws SmackException
*/
- public void challengeReceived(String challenge) throws IOException, NotConnectedException {
- byte response[];
- if(challenge != null) {
- response = sc.evaluateChallenge(StringUtils.decodeBase64(challenge));
- } else {
- response = sc.evaluateChallenge(new byte[0]);
+ public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, NotConnectedException {
+ byte[] challenge = StringUtils.decodeBase64(challengeString);
+ byte response[] = evaluateChallenge(challenge);
+ if (finalChallenge) {
+ return;
}
- Packet responseStanza;
+ Response responseStanza;
if (response == null) {
responseStanza = new Response();
}
@@ -201,7 +201,15 @@ public abstract class SASLMechanism implements CallbackHandler {
}
// Send the authentication to the server
- getSASLAuthentication().send(responseStanza);
+ connection.sendPacket(responseStanza);
+ }
+
+ protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
+ return null;
+ }
+
+ public final int compareTo(SASLMechanism other) {
+ return getPriority() - other.getPriority();
}
/**
@@ -209,184 +217,15 @@ public abstract class SASLMechanism implements CallbackHandler {
*
* @return the common name of the SASL mechanism.
*/
- protected abstract String getName();
+ public abstract String getName();
- protected SASLAuthentication getSASLAuthentication() {
- return saslAuthentication;
+ public abstract int getPriority();
+
+ public SASLMechanism instanceForAuthentication(XMPPConnection connection) {
+ SASLMechanism saslMechansim = newInstance();
+ saslMechansim.connection = connection;
+ return saslMechansim;
}
- /**
- *
- */
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (int i = 0; i < callbacks.length; i++) {
- if (callbacks[i] instanceof NameCallback) {
- NameCallback ncb = (NameCallback)callbacks[i];
- ncb.setName(authenticationId);
- } else if(callbacks[i] instanceof PasswordCallback) {
- PasswordCallback pcb = (PasswordCallback)callbacks[i];
- pcb.setPassword(password.toCharArray());
- } else if(callbacks[i] instanceof RealmCallback) {
- RealmCallback rcb = (RealmCallback)callbacks[i];
- //Retrieve the REALM from the challenge response that the server returned when the client initiated the authentication
- //exchange. If this value is not null or empty, *this value* has to be sent back to the server in the client's response
- //to the server's challenge
- String text = rcb.getDefaultText();
- //The SASL client (sc) created in smack uses rcb.getText when creating the negotiatedRealm to send it back to the server
- //Make sure that this value matches the server's realm
- rcb.setText(text);
- } else if(callbacks[i] instanceof RealmChoiceCallback){
- //unused
- //RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i];
- } else {
- throw new UnsupportedCallbackException(callbacks[i]);
- }
- }
- }
-
- /**
- * Initiating SASL authentication by select a mechanism.
- */
- public static class AuthMechanism extends Packet {
- final private String name;
- final private String authenticationText;
-
- public AuthMechanism(String name, String authenticationText) {
- if (name == null) {
- throw new NullPointerException("SASL mechanism name shouldn't be null.");
- }
- this.name = name;
- this.authenticationText = authenticationText;
- }
-
- public String toXML() {
- StringBuilder stanza = new StringBuilder();
- stanza.append("");
- if (authenticationText != null &&
- authenticationText.trim().length() > 0) {
- stanza.append(authenticationText);
- }
- stanza.append("");
- return stanza.toString();
- }
- }
-
- /**
- * A SASL challenge stanza.
- */
- public static class Challenge extends Packet {
- final private String data;
-
- public Challenge(String data) {
- this.data = data;
- }
-
- public String toXML() {
- StringBuilder stanza = new StringBuilder();
- stanza.append("");
- if (data != null &&
- data.trim().length() > 0) {
- stanza.append(data);
- }
- stanza.append("");
- return stanza.toString();
- }
- }
-
- /**
- * A SASL response stanza.
- */
- public static class Response extends Packet {
- final private String authenticationText;
-
- public Response() {
- authenticationText = null;
- }
-
- public Response(String authenticationText) {
- if (authenticationText == null || authenticationText.trim().length() == 0) {
- this.authenticationText = null;
- }
- else {
- this.authenticationText = authenticationText;
- }
- }
-
- public String toXML() {
- StringBuilder stanza = new StringBuilder();
- stanza.append("");
- if (authenticationText != null) {
- stanza.append(authenticationText);
- }
- stanza.append("");
- return stanza.toString();
- }
- }
-
- /**
- * A SASL success stanza.
- */
- public static class Success extends Packet {
- final private String data;
-
- public Success(String data) {
- this.data = data;
- }
-
- public String toXML() {
- StringBuilder stanza = new StringBuilder();
- stanza.append("");
- if (data != null &&
- data.trim().length() > 0) {
- stanza.append(data);
- }
- stanza.append("");
- return stanza.toString();
- }
- }
-
- /**
- * A SASL failure stanza.
- */
- public static class SASLFailure extends Packet {
- private final SASLError saslError;
- private final String saslErrorString;
-
- public SASLFailure(String saslError) {
- SASLError error = SASLError.fromString(saslError);
- if (error == null) {
- // RFC6120 6.5 states that unknown condition must be treat as generic authentication failure.
- this.saslError = SASLError.not_authorized;
- } else {
- this.saslError = error;
- }
- this.saslErrorString = saslError;
- }
-
- /**
- * Get the SASL related error condition.
- *
- * @return the SASL related error condition.
- */
- public SASLError getSASLError() {
- return saslError;
- }
-
- /**
- *
- * @return the SASL error as String
- */
- public String getSASLErrorString() {
- return saslErrorString;
- }
-
- public String toXML() {
- StringBuilder stanza = new StringBuilder();
- stanza.append("");
- stanza.append("<").append(saslErrorString).append("/>");
- stanza.append("");
- return stanza.toString();
- }
- }
+ protected abstract SASLMechanism newInstance();
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStanzas.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStanzas.java
new file mode 100644
index 000000000..689fa4741
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStanzas.java
@@ -0,0 +1,186 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.packet;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.sasl.SASLError;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.util.XmlStringBuilder;
+
+public class SaslStanzas {
+ public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
+
+ /**
+ * Initiating SASL authentication by select a mechanism.
+ */
+ public static class AuthMechanism extends Packet {
+ public static final String ELEMENT = "auth";
+
+ private final String mechanism;
+ private final String authenticationText;
+
+ public AuthMechanism(String mechanism, String authenticationText) {
+ if (mechanism == null) {
+ throw new NullPointerException("SASL mechanism shouldn't be null.");
+ }
+ this.mechanism = mechanism;
+ this.authenticationText = StringUtils.returnIfNotEmptyTrimmed(authenticationText);
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder();
+ xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).attribute("mechanism", mechanism).rightAngelBracket();
+ xml.optAppend(authenticationText);
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+ }
+
+ /**
+ * A SASL challenge stanza.
+ */
+ public static class Challenge extends Packet {
+ public static final String ELEMENT = "challenge";
+
+ private final String data;
+
+ public Challenge(String data) {
+ this.data = StringUtils.returnIfNotEmptyTrimmed(data);
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder().halfOpenElement(ELEMENT).xmlnsAttribute(
+ NAMESPACE).rightAngelBracket();
+ xml.optAppend(data);
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+ }
+
+ /**
+ * A SASL response stanza.
+ */
+ public static class Response extends Packet {
+ public static final String ELEMENT = "response";
+
+ private final String authenticationText;
+
+ public Response() {
+ authenticationText = null;
+ }
+
+ public Response(String authenticationText) {
+ this.authenticationText = StringUtils.returnIfNotEmptyTrimmed(authenticationText);
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder();
+ xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngelBracket();
+ xml.optAppend(authenticationText);
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+ }
+
+ /**
+ * A SASL success stanza.
+ */
+ public static class Success extends Packet {
+ public static final String ELEMENT = "success";
+
+ final private String data;
+
+ /**
+ * Construct a new SASL success stanza with optional additional data for the SASL layer
+ * (RFC6120 6.3.10)
+ *
+ * @param data additional data for the SASL layer or null
+ */
+ public Success(String data) {
+ this.data = StringUtils.returnIfNotEmptyTrimmed(data);
+ }
+
+ /**
+ * Returns additional data for the SASL layer or null
.
+ *
+ * @return additional data or null
+ */
+ public String getData() {
+ return data;
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder();
+ xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngelBracket();
+ xml.optAppend(data);
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+ }
+
+ /**
+ * A SASL failure stanza.
+ */
+ public static class SASLFailure extends Packet {
+ public static final String ELEMENT = "failure";
+
+ private final SASLError saslError;
+ private final String saslErrorString;
+
+ public SASLFailure(String saslError) {
+ SASLError error = SASLError.fromString(saslError);
+ if (error == null) {
+ // RFC6120 6.5 states that unknown condition must be treat as generic authentication
+ // failure.
+ this.saslError = SASLError.not_authorized;
+ }
+ else {
+ this.saslError = error;
+ }
+ this.saslErrorString = saslError;
+ }
+
+ /**
+ * Get the SASL related error condition.
+ *
+ * @return the SASL related error condition.
+ */
+ public SASLError getSASLError() {
+ return saslError;
+ }
+
+ /**
+ * @return the SASL error as String
+ */
+ public String getSASLErrorString() {
+ return saslErrorString;
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder();
+ xml.halfOpenElement(ELEMENT).xmlnsAttribute(ELEMENT).rightAngelBracket();
+ xml.emptyElement(saslErrorString);
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ByteUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ByteUtils.java
new file mode 100644
index 000000000..c9f70f5c6
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ByteUtils.java
@@ -0,0 +1,48 @@
+/**
+ *
+ * Copyright © 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class ByteUtils {
+
+ private static MessageDigest md5Digest;
+
+ public synchronized static byte[] md5(byte[] data) {
+ if (md5Digest == null) {
+ try {
+ md5Digest = MessageDigest.getInstance(StringUtils.MD5);
+ }
+ catch (NoSuchAlgorithmException nsae) {
+ // Smack wont be able to function normally if this exception is thrown, wrap it into
+ // an ISE and make the user aware of the problem.
+ throw new IllegalStateException(nsae);
+ }
+ }
+ md5Digest.update(data);
+ return md5Digest.digest();
+ }
+
+ public static byte[] concact(byte[] arrayOne, byte[] arrayTwo) {
+ int combinedLength = arrayOne.length + arrayTwo.length;
+ byte[] res = new byte[combinedLength];
+ System.arraycopy(arrayOne, 0, res, 0, arrayOne.length);
+ System.arraycopy(arrayTwo, 0, res, arrayOne.length, arrayTwo.length);
+ return res;
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
index cd2926585..882292650 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
@@ -44,7 +44,7 @@ import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
-import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
index 7044e4b4c..ac5059848 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
@@ -28,6 +28,7 @@ import java.util.Random;
*/
public class StringUtils {
+ public static final String MD5 = "MD5";
public static final String SHA1 = "SHA-1";
public static final String UTF8 = "UTF-8";
@@ -37,6 +38,8 @@ public class StringUtils {
public static final String LT_ENCODE = "<";
public static final String GT_ENCODE = ">";
+ public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+
/**
* Escapes all necessary characters in the String so that it can be used
* in an XML doc.
@@ -129,14 +132,7 @@ public class StringUtils {
}
}
// Now, compute hash.
- try {
- digest.update(data.getBytes(UTF8));
- }
- catch (UnsupportedEncodingException e) {
- // Smack wont be able to function normally if this exception is thrown, wrap it into an
- // ISE and make the user aware of the problem.
- throw new IllegalStateException(e);
- }
+ digest.update(toBytes(data));
return encodeHex(digest.digest());
}
@@ -147,16 +143,22 @@ public class StringUtils {
* @return generated hex string.
*/
public static String encodeHex(byte[] bytes) {
- StringBuilder hex = new StringBuilder(bytes.length * 2);
-
- for (byte aByte : bytes) {
- if (((int) aByte & 0xff) < 0x10) {
- hex.append("0");
- }
- hex.append(Integer.toString((int) aByte & 0xff, 16));
+ char[] hexChars = new char[bytes.length * 2];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = HEX_CHARS[v >>> 4];
+ hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
}
+ return new String(hexChars);
+ }
- return hex.toString();
+ public static byte[] toBytes(String string) {
+ try {
+ return string.getBytes(StringUtils.UTF8);
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("UTF-8 encoding not supported by platform", e);
+ }
}
/**
@@ -166,13 +168,7 @@ public class StringUtils {
* @return a base64 encoded String.
*/
public static String encodeBase64(String data) {
- byte [] bytes = null;
- try {
- bytes = data.getBytes("ISO-8859-1");
- }
- catch (UnsupportedEncodingException uee) {
- throw new IllegalStateException(uee);
- }
+ byte [] bytes = toBytes(data);
return encodeBase64(bytes);
}
@@ -218,17 +214,14 @@ public class StringUtils {
* @return the decoded String.
*/
public static byte[] decodeBase64(String data) {
- byte[] bytes;
- try {
- bytes = data.getBytes("UTF-8");
- } catch (java.io.UnsupportedEncodingException uee) {
- bytes = data.getBytes();
- }
-
- bytes = Base64.decode(bytes, 0, bytes.length, Base64.NO_OPTIONS);
- return bytes;
+ byte[] bytes = toBytes(data);
+ return decodeBase64(bytes);
}
+ public static byte[] decodeBase64(byte[] data) {
+ return Base64.decode(data, 0, data.length, Base64.NO_OPTIONS);
+ }
+
/**
* Pseudo-random number generator object for use with randomString().
* The Random class is not considered to be cryptographically secure, so
@@ -306,4 +299,15 @@ public class StringUtils {
res = res.substring(0, res.length() - 1);
return res;
}
+
+ public static String returnIfNotEmptyTrimmed(String string) {
+ if (string == null)
+ return null;
+ String trimmedString = string.trim();
+ if (trimmedString.length() > 0) {
+ return trimmedString;
+ } else {
+ return null;
+ }
+ }
}
diff --git a/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml b/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml
index 500b048ff..e10003e36 100644
--- a/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml
+++ b/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml
@@ -12,5 +12,7 @@
org.jivesoftware.smack.initializer.extensions.ExtensionsInitializer
org.jivesoftware.smack.initializer.experimental.ExperimentalInitializer
org.jivesoftware.smack.initializer.legacy.LegacyInitializer
+ org.jivesoftware.smack.sasl.javax.SASLJavaXSmackInitializer
+ org.jivesoftware.smack.sasl.provided.SASLProvidedSmackInitializer
diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java
new file mode 100644
index 000000000..b22a858bb
--- /dev/null
+++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java
@@ -0,0 +1,31 @@
+/**
+ *
+ * Copyright © 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl;
+
+import org.jivesoftware.smack.DummyConnection;
+import org.jivesoftware.smack.XMPPConnection;
+
+public class AbstractSaslTest {
+
+ protected final XMPPConnection xmppConnection = new DummyConnection();
+ protected final SASLMechanism saslMechanism;
+
+ protected AbstractSaslTest(SASLMechanism saslMechanism) {
+ this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection);
+ }
+
+}
diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java
new file mode 100644
index 000000000..e8eb6bad9
--- /dev/null
+++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java
@@ -0,0 +1,61 @@
+/**
+ *
+ * Copyright © 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
+import org.jivesoftware.smack.util.StringUtils;
+
+public class DigestMd5SaslTest extends AbstractSaslTest {
+
+ protected static final String challenge = "realm=\"xmpp.org\",nonce=\"aTUr3GXqUtyy2B7HVDW6C+gQs+j+0EhWWjoBKkkg\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";
+ protected static final byte[] challengeBytes = StringUtils.toBytes(challenge);
+
+ public DigestMd5SaslTest(SASLMechanism saslMechanism) {
+ super(saslMechanism);
+ }
+
+ protected void runTest() throws NotConnectedException, SmackException {
+ saslMechanism.authenticate("florian", "irrelevant", "xmpp.org", "secret");
+
+ byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
+ String responseString = new String(response);
+ String[] responseParts = responseString.split(",");
+ Map responsePairs = new HashMap();
+ for (String part : responseParts) {
+ String[] keyValue = part.split("=");
+ assertTrue(keyValue.length == 2);
+ String key = keyValue[0];
+ String value = keyValue[1].replace("\"", "");
+ responsePairs.put(key, value);
+ }
+ assertMapValue("username", "florian", responsePairs);
+ assertMapValue("realm", "xmpp.org", responsePairs);
+ assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs);
+ assertMapValue("qop", "auth", responsePairs);
+ }
+
+ private static void assertMapValue(String key, String value, Map map) {
+ assertEquals(map.get(key), value);
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/debugger/LiteDebugger.java b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/LiteDebugger.java
similarity index 99%
rename from smack-core/src/main/java/org/jivesoftware/smack/debugger/LiteDebugger.java
rename to smack-debug/src/main/java/org/jivesoftware/smackx/debugger/LiteDebugger.java
index f17621fb8..f0fd82b4c 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/debugger/LiteDebugger.java
+++ b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/LiteDebugger.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.jivesoftware.smack.debugger;
+package org.jivesoftware.smackx.debugger;
import java.awt.*;
import java.awt.datatransfer.*;
@@ -25,6 +25,7 @@ import java.io.*;
import javax.swing.*;
import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.util.*;
import org.jxmpp.util.XmppStringUtils;
diff --git a/smack-java7/build.gradle b/smack-java7/build.gradle
index 16efa8d77..6a53eb668 100644
--- a/smack-java7/build.gradle
+++ b/smack-java7/build.gradle
@@ -11,6 +11,7 @@ dependencies {
compile project(":smack-extensions")
compile project(":smack-tcp")
compile project(":smack-resolver-javax")
+ compile project(":smack-sasl-javax")
}
javadoc {
diff --git a/smack-sasl-javax/build.gradle b/smack-sasl-javax/build.gradle
new file mode 100644
index 000000000..94d1a9e2a
--- /dev/null
+++ b/smack-sasl-javax/build.gradle
@@ -0,0 +1,8 @@
+description = """\
+SASL with javax.security.sasl
+Use javax.security.sasl for SASL."""
+
+dependencies {
+ compile project(path: ':smack-core', configuration: 'sasl')
+ testCompile project(':smack-core').sourceSets.test.runtimeClasspath
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLCramMD5Mechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLCramMD5Mechanism.java
similarity index 65%
rename from smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLCramMD5Mechanism.java
rename to smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLCramMD5Mechanism.java
index 1e75be788..f6822e30a 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLCramMD5Mechanism.java
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLCramMD5Mechanism.java
@@ -14,22 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jivesoftware.smack.sasl;
-
-import org.jivesoftware.smack.SASLAuthentication;
+package org.jivesoftware.smack.sasl.javax;
/**
* Implementation of the SASL CRAM-MD5 mechanism
*
* @author Jay Kline
*/
-public class SASLCramMD5Mechanism extends SASLMechanism {
+public class SASLCramMD5Mechanism extends SASLJavaXMechanism {
- public SASLCramMD5Mechanism(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
+ public static final String NAME = CRAMMD5;
+
+ public String getName() {
+ return NAME;
}
- protected String getName() {
- return "CRAM-MD5";
+ @Override
+ public int getPriority() {
+ return 300;
+ }
+
+ @Override
+ public SASLCramMD5Mechanism newInstance() {
+ return new SASLCramMD5Mechanism();
}
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLDigestMD5Mechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Mechanism.java
similarity index 64%
rename from smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLDigestMD5Mechanism.java
rename to smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Mechanism.java
index 9c7bb115a..c4959807b 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLDigestMD5Mechanism.java
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Mechanism.java
@@ -14,22 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jivesoftware.smack.sasl;
-
-import org.jivesoftware.smack.SASLAuthentication;
+package org.jivesoftware.smack.sasl.javax;
/**
* Implementation of the SASL DIGEST-MD5 mechanism
*
* @author Jay Kline
*/
-public class SASLDigestMD5Mechanism extends SASLMechanism {
+public class SASLDigestMD5Mechanism extends SASLJavaXMechanism {
- public SASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
+ public static final String NAME = DIGESTMD5;
+
+ public String getName() {
+ return NAME;
}
- protected String getName() {
- return "DIGEST-MD5";
+ @Override
+ public int getPriority() {
+ return 200;
+ }
+
+ @Override
+ public SASLDigestMD5Mechanism newInstance() {
+ return new SASLDigestMD5Mechanism();
}
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLExternalMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLExternalMechanism.java
similarity index 81%
rename from smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLExternalMechanism.java
rename to smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLExternalMechanism.java
index f45ce5203..eff4697c4 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLExternalMechanism.java
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLExternalMechanism.java
@@ -14,9 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jivesoftware.smack.sasl;
-
-import org.jivesoftware.smack.SASLAuthentication;
+package org.jivesoftware.smack.sasl.javax;
/**
* Implementation of the SASL EXTERNAL mechanism.
@@ -44,13 +42,22 @@ import org.jivesoftware.smack.SASLAuthentication;
*
* @author Jay Kline
*/
-public class SASLExternalMechanism extends SASLMechanism {
+public class SASLExternalMechanism extends SASLJavaXMechanism {
- public SASLExternalMechanism(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
+ public static final String NAME = EXTERNAL;
+
+ @Override
+ public String getName() {
+ return EXTERNAL;
}
- protected String getName() {
- return "EXTERNAL";
+ @Override
+ public int getPriority() {
+ return 500;
+ }
+
+ @Override
+ public SASLExternalMechanism newInstance() {
+ return new SASLExternalMechanism();
}
}
diff --git a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java
new file mode 100644
index 000000000..ae45cbd47
--- /dev/null
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java
@@ -0,0 +1,69 @@
+/**
+ *
+ * Copyright the original author or authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.javax;
+
+import java.util.Map;
+
+import javax.security.sasl.Sasl;
+
+/**
+ * Implementation of the SASL GSSAPI mechanism
+ *
+ * @author Jay Kline
+ */
+public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
+
+ public static final String NAME = GSSAPI;
+
+ static {
+ System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
+ System.setProperty("java.security.auth.login.config","gss.conf");
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ protected Map getSaslProps() {
+ Map props = super.getSaslProps();
+ props.put(Sasl.SERVER_AUTH,"TRUE");
+ return props;
+ }
+
+ /**
+ * GSSAPI differs from all other SASL mechanism such that it required the FQDN host name as
+ * server name and not the serviceName (At least that is what old code comments of Smack tell
+ * us).
+ */
+ @Override
+ protected String getServerName() {
+ return host;
+ }
+
+ @Override
+ public int getPriority() {
+ return 100;
+ }
+
+ @Override
+ public SASLGSSAPIMechanism newInstance() {
+ return new SASLGSSAPIMechanism();
+ }
+
+}
diff --git a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXMechanism.java
new file mode 100644
index 000000000..c19289bdd
--- /dev/null
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXMechanism.java
@@ -0,0 +1,147 @@
+/**
+ *
+ * Copyright © 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.javax;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.RealmChoiceCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.sasl.SASLMechanism;
+import org.jivesoftware.smack.util.StringUtils;
+
+public abstract class SASLJavaXMechanism extends SASLMechanism {
+
+ protected SaslClient sc;
+
+ @Override
+ public abstract String getName();
+
+ @Override
+ protected void authenticateInternal()
+ throws SmackException {
+ String[] mechanisms = { getName() };
+ Map props = getSaslProps();
+ try {
+ sc = Sasl.createSaslClient(mechanisms, null, "xmpp", getServerName(), props,
+ new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException,
+ UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof NameCallback) {
+ NameCallback ncb = (NameCallback) callbacks[i];
+ ncb.setName(authenticationId);
+ }
+ else if (callbacks[i] instanceof PasswordCallback) {
+ PasswordCallback pcb = (PasswordCallback) callbacks[i];
+ pcb.setPassword(password.toCharArray());
+ }
+ else if (callbacks[i] instanceof RealmCallback) {
+ RealmCallback rcb = (RealmCallback) callbacks[i];
+ // Retrieve the REALM from the challenge response that
+ // the server returned when the client initiated the
+ // authentication exchange. If this value is not null or
+ // empty, *this value* has to be sent back to the server
+ // in the client's response to the server's challenge
+ String text = rcb.getDefaultText();
+ // The SASL client (sc) created in smack uses
+ // rcb.getText when creating the negotiatedRealm to send
+ // it back to the server. Make sure that this value
+ // matches the server's realm
+ rcb.setText(text);
+ }
+ else if (callbacks[i] instanceof RealmChoiceCallback) {
+ // unused, prevents UnsupportedCallbackException
+ // RealmChoiceCallback rccb =
+ // (RealmChoiceCallback)callbacks[i];
+ }
+ else {
+ throw new UnsupportedCallbackException(callbacks[i]);
+ }
+ }
+ }
+
+ });
+ }
+ catch (SaslException e) {
+ throw new SmackException(e);
+ }
+ }
+
+ @Override
+ protected void authenticateInternal(CallbackHandler cbh)
+ throws SmackException {
+ String[] mechanisms = { getName() };
+ Map props = getSaslProps();
+ try {
+ sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
+ }
+ catch (SaslException e) {
+ throw new SmackException(e);
+ }
+ }
+
+ protected String getAuthenticationText() throws SmackException {
+ String authenticationText = null;
+ if (sc.hasInitialResponse()) {
+ byte[] response;
+ try {
+ response = sc.evaluateChallenge(new byte[0]);
+ }
+ catch (SaslException e) {
+ throw new SmackException(e);
+ }
+ authenticationText = StringUtils.encodeBase64(response, false);
+ }
+ return authenticationText;
+ }
+
+ @Override
+ protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
+ try {
+ if (challenge != null) {
+ return sc.evaluateChallenge(challenge);
+ }
+ else {
+ return sc.evaluateChallenge(new byte[0]);
+ }
+ }
+ catch (SaslException e) {
+ throw new SmackException(e);
+ }
+ }
+
+ protected Map getSaslProps() {
+ return new HashMap();
+ }
+
+ protected String getServerName() {
+ return serviceName;
+ }
+}
diff --git a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXSmackInitializer.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXSmackInitializer.java
new file mode 100644
index 000000000..735fe214e
--- /dev/null
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLJavaXSmackInitializer.java
@@ -0,0 +1,42 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.javax;
+
+import java.util.List;
+
+import org.jivesoftware.smack.SASLAuthentication;
+import org.jivesoftware.smack.initializer.SmackAndOsgiInitializer;
+
+public class SASLJavaXSmackInitializer extends SmackAndOsgiInitializer {
+
+ @Override
+ public List initialize() {
+ SASLAuthentication.registerSASLMechanism(new SASLExternalMechanism());
+ SASLAuthentication.registerSASLMechanism(new SASLGSSAPIMechanism());
+ SASLAuthentication.registerSASLMechanism(new SASLDigestMD5Mechanism());
+ SASLAuthentication.registerSASLMechanism(new SASLCramMD5Mechanism());
+ SASLAuthentication.registerSASLMechanism(new SASLPlainMechanism());
+
+ return null;
+ }
+
+ @Override
+ public List initialize(ClassLoader classLoader) {
+ return initialize();
+ }
+
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLPlainMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLPlainMechanism.java
similarity index 65%
rename from smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLPlainMechanism.java
rename to smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLPlainMechanism.java
index e80f2069f..4f02de9c0 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLPlainMechanism.java
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLPlainMechanism.java
@@ -14,22 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jivesoftware.smack.sasl;
-
-import org.jivesoftware.smack.SASLAuthentication;
+package org.jivesoftware.smack.sasl.javax;
/**
* Implementation of the SASL PLAIN mechanism
*
* @author Jay Kline
*/
-public class SASLPlainMechanism extends SASLMechanism {
+public class SASLPlainMechanism extends SASLJavaXMechanism {
- public SASLPlainMechanism(SASLAuthentication saslAuthentication) {
- super(saslAuthentication);
+ public static final String NAME = PLAIN;
+
+ public String getName() {
+ return NAME;
}
- protected String getName() {
- return "PLAIN";
+ @Override
+ public int getPriority() {
+ return 400;
+ }
+
+ @Override
+ public SASLPlainMechanism newInstance() {
+ return new SASLPlainMechanism();
}
}
diff --git a/smack-sasl-javax/src/main/resources/org.jivesoftware.smack/smack-sasl-javax-components.xml b/smack-sasl-javax/src/main/resources/org.jivesoftware.smack/smack-sasl-javax-components.xml
new file mode 100644
index 000000000..ebbd5403e
--- /dev/null
+++ b/smack-sasl-javax/src/main/resources/org.jivesoftware.smack/smack-sasl-javax-components.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/smack-sasl-javax/src/test/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Test.java b/smack-sasl-javax/src/test/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Test.java
new file mode 100644
index 000000000..3b1659bf2
--- /dev/null
+++ b/smack-sasl-javax/src/test/java/org/jivesoftware/smack/sasl/javax/SASLDigestMD5Test.java
@@ -0,0 +1,34 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.javax;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
+import org.jivesoftware.smack.sasl.DigestMd5SaslTest;
+import org.junit.Test;
+
+public class SASLDigestMD5Test extends DigestMd5SaslTest {
+
+ public SASLDigestMD5Test() {
+ super(new SASLDigestMD5Mechanism());
+ }
+
+ @Test
+ public void testDigestMD5() throws NotConnectedException, SmackException {
+ runTest();
+ }
+}
diff --git a/smack-sasl-provided/build.gradle b/smack-sasl-provided/build.gradle
new file mode 100644
index 000000000..7b3e33824
--- /dev/null
+++ b/smack-sasl-provided/build.gradle
@@ -0,0 +1,8 @@
+description = """\
+SASL with Smack provided code
+Use Smack provided code for SASL."""
+
+dependencies {
+ compile project(path: ':smack-core', configuration: 'sasl')
+ testCompile project(':smack-core').sourceSets.test.runtimeClasspath
+}
diff --git a/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Mechanism.java b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Mechanism.java
new file mode 100644
index 000000000..72bc8dc7a
--- /dev/null
+++ b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Mechanism.java
@@ -0,0 +1,206 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.provided;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.sasl.SASLMechanism;
+import org.jivesoftware.smack.util.ByteUtils;
+import org.jivesoftware.smack.util.StringUtils;
+
+public class SASLDigestMD5Mechanism extends SASLMechanism {
+
+ public static final String NAME = DIGESTMD5;
+
+ private static final String INITAL_NONCE = "00000001";
+
+ /**
+ * The only 'qop' value supported by this implementation
+ */
+ private static final String QOP_VALUE = "auth";
+
+ private enum State {
+ INITIAL,
+ RESPONSE_SENT,
+ VALID_SERVER_RESPONSE,
+ }
+
+ private static boolean verifyServerResponse = true;
+
+ public static void setVerifyServerResponse(boolean verifyServerResponse) {
+ SASLDigestMD5Mechanism.verifyServerResponse = verifyServerResponse;
+ }
+
+ /**
+ * The state of the this instance of SASL DIGEST-MD5 authentication.
+ */
+ private State state = State.INITIAL;
+
+ private String nonce;
+ private String cnonce;
+ private String digestUri;
+ private String hex_hashed_a1;
+
+ @Override
+ protected void authenticateInternal(CallbackHandler cbh) throws SmackException {
+ throw new UnsupportedOperationException("CallbackHandler not (yet) supported");
+ }
+
+ @Override
+ protected String getAuthenticationText() throws SmackException {
+ // DIGEST-MD5 has no initial response, return null
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public int getPriority() {
+ return 210;
+ }
+
+ @Override
+ public SASLDigestMD5Mechanism newInstance() {
+ return new SASLDigestMD5Mechanism();
+ }
+
+ @Override
+ protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
+ if (challenge.length == 0) {
+ throw new SmackException("Initial challenge has zero length");
+ }
+ String[] challengeParts = (new String(challenge)).split(",");
+ byte[] response = null;
+ switch (state) {
+ case INITIAL:
+ for (String part : challengeParts) {
+ String[] keyValue = part.split("=");
+ assert (keyValue.length == 2);
+ String key = keyValue[0];
+ String value = keyValue[1];
+ if ("nonce".equals(key)) {
+ if (nonce != null) {
+ throw new SmackException("Nonce value present multiple times");
+ }
+ nonce = value.replace("\"", "");
+ }
+ else if ("qop".equals(key)) {
+ value = value.replace("\"", "");
+ if (!value.equals("auth")) {
+ throw new SmackException("Unsupported qop operation: " + value);
+ }
+ }
+ }
+ if (nonce == null) {
+ // RFC 2831 2.1.1 about nonce "This directive is required and MUST appear exactly
+ // once; if not present, or if multiple instances are present, the client should
+ // abort the authentication exchange."
+ throw new SmackException("nonce value not present in initial challenge");
+ }
+ // RFC 2831 2.1.2.1 defines A1, A2, KD and response-value
+ byte[] a1FirstPart = ByteUtils.md5(toBytes(authenticationId + ':' + serviceName + ':'
+ + password));
+ cnonce = StringUtils.randomString(32);
+ byte[] a1 = ByteUtils.concact(a1FirstPart, toBytes(':' + nonce + ':' + cnonce));
+ digestUri = "xmpp/" + serviceName;
+ hex_hashed_a1 = StringUtils.encodeHex(ByteUtils.md5(a1));
+ String responseValue = calcResponse(DigestType.ClientResponse);
+ // @formatter:off
+ // See RFC 2831 2.1.2 digest-response
+ String saslString = "username=\"" + authenticationId + '"'
+ + ",realm=\"" + serviceName + '"'
+ + ",nonce=\"" + nonce + '"'
+ + ",cnonce=\"" + cnonce + '"'
+ + ",nc=" + INITAL_NONCE
+ + ",qop=auth"
+ + ",digest-uri=\"" + digestUri + '"'
+ + ",response=" + responseValue
+ + ",charset=utf-8";
+ // @formatter:on
+ response = toBytes(saslString);
+ state = State.RESPONSE_SENT;
+ break;
+ case RESPONSE_SENT:
+ if (verifyServerResponse) {
+ String serverResponse = null;
+ for (String part : challengeParts) {
+ String[] keyValue = part.split("=");
+ assert (keyValue.length == 2);
+ String key = keyValue[0];
+ String value = keyValue[1];
+ if ("rspauth".equals(key)) {
+ serverResponse = value;
+ break;
+ }
+ }
+ if (serverResponse == null) {
+ throw new SmackException("No server response received while performing " + NAME
+ + " authentication");
+ }
+ String expectedServerResponse = calcResponse(DigestType.ServerResponse);
+ if (!serverResponse.equals(expectedServerResponse)) {
+ throw new SmackException("Invalid server response while performing " + NAME
+ + " authentication");
+ }
+ }
+ state = State.VALID_SERVER_RESPONSE;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ return response;
+ }
+
+ private enum DigestType {
+ ClientResponse,
+ ServerResponse
+ }
+
+ private String calcResponse(DigestType digestType) {
+ StringBuilder a2 = new StringBuilder();
+ if (digestType == DigestType.ClientResponse) {
+ a2.append("AUTHENTICATE");
+ }
+ a2.append(':');
+ a2.append(digestUri);
+ String hex_hashed_a2 = StringUtils.encodeHex(ByteUtils.md5(toBytes(a2.toString())));
+
+ StringBuilder kd_argument = new StringBuilder();
+ kd_argument.append(hex_hashed_a1);
+ kd_argument.append(':');
+ kd_argument.append(nonce);
+ kd_argument.append(':');
+ kd_argument.append(INITAL_NONCE);
+ kd_argument.append(':');
+ kd_argument.append(cnonce);
+ kd_argument.append(':');
+ kd_argument.append(QOP_VALUE);
+ kd_argument.append(':');
+ kd_argument.append(hex_hashed_a2);
+ byte[] kd = ByteUtils.md5(toBytes(kd_argument.toString()));
+ String responseValue = StringUtils.encodeHex(kd);
+ return responseValue;
+ }
+
+ private static byte[] toBytes(String string) {
+ return StringUtils.toBytes(string);
+ }
+}
diff --git a/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLProvidedSmackInitializer.java b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLProvidedSmackInitializer.java
new file mode 100644
index 000000000..eb7eb920e
--- /dev/null
+++ b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLProvidedSmackInitializer.java
@@ -0,0 +1,37 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.provided;
+
+import java.util.List;
+
+import org.jivesoftware.smack.SASLAuthentication;
+import org.jivesoftware.smack.initializer.SmackAndOsgiInitializer;
+
+public class SASLProvidedSmackInitializer extends SmackAndOsgiInitializer {
+
+ @Override
+ public List initialize() {
+ SASLAuthentication.registerSASLMechanism(new SASLDigestMD5Mechanism());
+ return null;
+ }
+
+ @Override
+ public List initialize(ClassLoader classLoader) {
+ return initialize();
+ }
+
+}
diff --git a/smack-sasl-provided/src/main/resources/org.jivesoftware.smack/smack-sasl-provided-components.xml b/smack-sasl-provided/src/main/resources/org.jivesoftware.smack/smack-sasl-provided-components.xml
new file mode 100644
index 000000000..ee995e18b
--- /dev/null
+++ b/smack-sasl-provided/src/main/resources/org.jivesoftware.smack/smack-sasl-provided-components.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/smack-sasl-provided/src/test/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Test.java b/smack-sasl-provided/src/test/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Test.java
new file mode 100644
index 000000000..6684ac248
--- /dev/null
+++ b/smack-sasl-provided/src/test/java/org/jivesoftware/smack/sasl/provided/SASLDigestMD5Test.java
@@ -0,0 +1,34 @@
+/**
+ *
+ * Copyright 2014 Florian Schmaus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smack.sasl.provided;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
+import org.jivesoftware.smack.sasl.DigestMd5SaslTest;
+import org.jivesoftware.smack.sasl.provided.SASLDigestMD5Mechanism;
+import org.junit.Test;
+
+public class SASLDigestMD5Test extends DigestMd5SaslTest {
+ public SASLDigestMD5Test() {
+ super(new SASLDigestMD5Mechanism());
+ }
+
+ @Test
+ public void testDigestMD5() throws NotConnectedException, SmackException {
+ runTest();
+ }
+}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
index d59a53c3c..f29c8bcf1 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
@@ -35,9 +35,9 @@ import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.UnparsablePacket;
-import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
-import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
-import org.jivesoftware.smack.sasl.SASLMechanism.Success;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
+import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.TLSUtils;
@@ -54,7 +54,6 @@ import javax.net.ssl.SSLSocket;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
-import javax.security.sasl.SaslException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -247,7 +246,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
@Override
- public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException {
+ public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, IOException {
if (!isConnected()) {
throw new NotConnectedException();
}
@@ -266,7 +265,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
saslAuthentication.authenticate(resource, config.getCallbackHandler());
}
} else {
- throw new SaslException("No non-anonymous SASL authentication mechanism available");
+ throw new SmackException("No non-anonymous SASL authentication mechanism available");
}
// If compression is enabled then request the server to use stream compression. XEP-170
@@ -312,7 +311,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
@Override
- public synchronized void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException {
+ public synchronized void loginAnonymously() throws XMPPException, SmackException, IOException {
if (!isConnected()) {
throw new NotConnectedException();
}
@@ -324,7 +323,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
saslAuthentication.authenticateAnonymously();
}
else {
- throw new SaslException("No anonymous SASL authentication mechanism available");
+ throw new SmackException("No anonymous SASL authentication mechanism available");
}
String response = bindResourceAndEstablishSession(null);
@@ -1063,7 +1062,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
getSASLAuthentication().challengeReceived(challengeData);
}
else if (name.equals("success")) {
- processPacket(new Success(parser.nextText()));
+ Success success = new Success(parser.nextText());
+ processPacket(success);
// We now need to bind a resource for the connection
// Open a new stream and wait for the response
packetWriter.openStream();
@@ -1072,7 +1072,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
resetParser();
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
- getSASLAuthentication().authenticated();
+ getSASLAuthentication().authenticated(success);
}
else if (name.equals("compressed")) {
// Server confirmed that it's possible to use stream compression. Start