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 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 mechanismClass = implementedMechanisms.get(selectedMechanism); - - Constructor 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 mechanismClass = implementedMechanisms.get(selectedMechanism); - try { - Constructor 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