1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-07-02 08:06:42 +02:00

Compare commits

..

No commits in common. "f0300dc906089564d57135f791c4e4900cfe224d" and "b83dacc60a3a23ea0b432079f7e70ffd455720f3" have entirely different histories.

35 changed files with 427 additions and 661 deletions

View file

@ -35,6 +35,10 @@
<property name="format" value="\.append\(&quot;(.|\\.)&quot;\)"/> <property name="format" value="\.append\(&quot;(.|\\.)&quot;\)"/>
<property name="message" value="Don&apos;t use StringBuilder.append(String) when you can use StringBuilder.append(char). Solution: Replace double quotes of append&apos;s argument with single quotes."/> <property name="message" value="Don&apos;t use StringBuilder.append(String) when you can use StringBuilder.append(char). Solution: Replace double quotes of append&apos;s argument with single quotes."/>
</module> </module>
<module name="RegexpSingleline">
<property name="format" value="^\s+$"/>
<property name="message" value="Line containing only whitespace character(s)"/>
</module>
<module name="FileTabCharacter"/> <module name="FileTabCharacter"/>
<module name="RegexpSingleline"> <module name="RegexpSingleline">
<!-- <!--

View file

@ -29,7 +29,6 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.StreamErrorException; import org.jivesoftware.smack.XMPPException.StreamErrorException;
@ -40,6 +39,8 @@ import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.CloseableUtil; import org.jivesoftware.smack.util.CloseableUtil;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
@ -217,7 +218,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException, protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
SmackException, IOException, InterruptedException { SmackException, IOException, InterruptedException {
// Authenticate using SASL // Authenticate using SASL
authenticate(username, password, config.getAuthzid(), null); saslAuthentication.authenticate(username, password, config.getAuthzid(), null);
bindResourceAndEstablishSession(resource); bindResourceAndEstablishSession(resource);
@ -394,26 +395,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
readerConsumer.start(); readerConsumer.start();
} }
@Override
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
// requires a special XML element ot be send after successful SASL authentication.
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
ComposableBody composeableBody = ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build();
try {
send(composeableBody);
} catch (BOSHException e) {
// jbosh's exception API does not really match the one of Smack.
throw new SmackException.SmackWrappedException(e);
}
}
/** /**
* A listener class which listen for a successfully established connection * A listener class which listen for a successfully established connection
* and connection errors and notifies the BOSHConnection. * and connection errors and notifies the BOSHConnection.
@ -509,9 +490,30 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
case Presence.ELEMENT: case Presence.ELEMENT:
parseAndProcessStanza(parser); parseAndProcessStanza(parser);
break; break;
case "challenge":
// The server is challenging the SASL authentication
// made by the client
final String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case "success":
send(ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build());
Success success = new Success(parser.nextText());
getSASLAuthentication().authenticated(success);
break;
case "features": case "features":
parseFeatures(parser); parseFeatures(parser);
break; break;
case "failure":
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
getSASLAuthentication().authenticationFailed(failure);
}
break;
case "error": case "error":
// Some BOSH error isn't stream error. // Some BOSH error isn't stream error.
if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) { if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) {
@ -520,9 +522,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
StanzaError.Builder builder = PacketParserUtils.parseError(parser); StanzaError.Builder builder = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, builder.build()); throw new XMPPException.XMPPErrorException(null, builder.build());
} }
default:
parseAndProcessNonza(parser);
break;
} }
break; break;
default: default:

View file

@ -60,7 +60,6 @@ import java.util.logging.Logger;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
@ -79,7 +78,6 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException; import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException; import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
import org.jivesoftware.smack.SmackException.SecurityRequiredException; import org.jivesoftware.smack.SmackException.SecurityRequiredException;
import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.SmackException.SmackWrappedException; import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture; import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException.FailedNonzaException; import org.jivesoftware.smack.XMPPException.FailedNonzaException;
@ -115,14 +113,9 @@ import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.ExtensionElementProvider; import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider; import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.util.Async; import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.util.ParserUtils;
@ -134,7 +127,6 @@ import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
@ -243,9 +235,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private XmlEnvironment incomingStreamXmlEnvironment; private XmlEnvironment incomingStreamXmlEnvironment;
protected XmlEnvironment outgoingStreamXmlEnvironment; final Map<QName, NonzaCallback> nonzaCallbacks = new HashMap<>();
final MultiMap<QName, NonzaCallback> nonzaCallbacksMap = new MultiMap<>();
protected final Lock connectionLock = new ReentrantLock(); protected final Lock connectionLock = new ReentrantLock();
@ -316,7 +306,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/** /**
* The SASLAuthentication manager that is responsible for authenticating with the server. * The SASLAuthentication manager that is responsible for authenticating with the server.
*/ */
private final SASLAuthentication saslAuthentication; protected final SASLAuthentication saslAuthentication;
/** /**
* A number to uniquely identify connections that are created. This is distinct from the * A number to uniquely identify connections that are created. This is distinct from the
@ -410,26 +400,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected AbstractXMPPConnection(ConnectionConfiguration configuration) { protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
saslAuthentication = new SASLAuthentication(this, configuration); saslAuthentication = new SASLAuthentication(this, configuration);
config = configuration; config = configuration;
// Install the SASL Nonza callbacks.
buildNonzaCallback()
.listenFor(SaslNonza.Challenge.class, c -> {
try {
saslAuthentication.challengeReceived(c);
} catch (SmackException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.Success.class, s -> {
try {
saslAuthentication.authenticated(s);
} catch (SmackSaslException | NotConnectedException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.SASLFailure.class, f -> saslAuthentication.authenticationFailed(f))
.install();
SmackDebuggerFactory debuggerFactory = configuration.getDebuggerFactory(); SmackDebuggerFactory debuggerFactory = configuration.getDebuggerFactory();
if (debuggerFactory != null) { if (debuggerFactory != null) {
debugger = debuggerFactory.create(this); debugger = debuggerFactory.create(this);
@ -528,6 +498,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Reset the connection state // Reset the connection state
initState(); initState();
saslAuthentication.init();
streamId = null; streamId = null;
try { try {
@ -838,50 +809,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
/** /**
* Authenticate a connection. * Returns the SASLAuthentication manager that is responsible for authenticating with
* the server.
* *
* @param username the username that is authenticating with the server. * @return the SASLAuthentication manager that is responsible for authenticating with
* @param password the password to send to the server. * the server.
* @param authzid the authorization identifier (typically null).
* @param sslSession the optional SSL/TLS session (if one was established)
* @return the used SASLMechanism.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws IOException if an I/O error occured.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackSaslException if a SASL specific error occured.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
* @throws SmackWrappedException in case of an exception.
* @see SASLAuthentication#authenticate(String, String, EntityBareJid, SSLSession)
*/ */
protected final SASLMechanism authenticate(String username, String password, EntityBareJid authzid, protected SASLAuthentication getSASLAuthentication() {
SSLSession sslSession) throws XMPPErrorException, SASLErrorException, SmackSaslException, return saslAuthentication;
NotConnectedException, NoResponseException, IOException, InterruptedException, SmackWrappedException {
SASLMechanism saslMechanism = saslAuthentication.authenticate(username, password, authzid, sslSession);
afterSaslAuthenticationSuccess();
return saslMechanism;
}
/**
* Hook for subclasses right after successful SASL authentication. RFC 6120 § 6.4.6. specifies a that the initiating
* entity, needs to initiate a new stream in this case. But some transports, like BOSH, requires a special handling.
* <p>
* Note that we can not reset XMPPTCPConnection's parser here, because this method is invoked by the thread calling
* {@link #login()}, but the parser reset has to be done within the reader thread.
* </p>
*
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackWrappedException in case of an exception.
*/
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
sendStreamOpen();
}
protected final boolean isSaslAuthenticated() {
return saslAuthentication.authenticationSuccessful();
} }
/** /**
@ -1289,9 +1224,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
protected final void parseAndProcessNonza(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException { protected final void parseAndProcessNonza(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
final String element = parser.getName(); final String element = parser.getName();
final String namespace = parser.getNamespace(); final String namespace = parser.getNamespace();
final QName key = new QName(namespace, element); final QName key = new QName(namespace, element);
@ -1299,27 +1231,22 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key); NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key);
if (nonzaProvider == null) { if (nonzaProvider == null) {
LOGGER.severe("Unknown nonza: " + key); LOGGER.severe("Unknown nonza: " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return; return;
} }
List<NonzaCallback> nonzaCallbacks; NonzaCallback nonzaCallback;
synchronized (nonzaCallbacksMap) { synchronized (nonzaCallbacks) {
nonzaCallbacks = nonzaCallbacksMap.getAll(key); nonzaCallback = nonzaCallbacks.get(key);
nonzaCallbacks = CollectionUtil.newListWith(nonzaCallbacks);
} }
if (nonzaCallbacks == null) { if (nonzaCallback == null) {
LOGGER.info("No nonza callback for " + key); LOGGER.info("No nonza callback for " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return; return;
} }
Nonza nonza = nonzaProvider.parse(parser, incomingStreamXmlEnvironment); Nonza nonza = nonzaProvider.parse(parser, incomingStreamXmlEnvironment);
for (NonzaCallback nonzaCallback : nonzaCallbacks) {
nonzaCallback.onNonzaReceived(nonza); nonzaCallback.onNonzaReceived(nonza);
} }
}
protected void parseAndProcessStanza(XmlPullParser parser) protected void parseAndProcessStanza(XmlPullParser parser)
throws XmlPullParserException, IOException, InterruptedException { throws XmlPullParserException, IOException, InterruptedException {
@ -2090,13 +2017,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
from = XmppStringUtils.completeJidFrom(localpart, to); from = XmppStringUtils.completeJidFrom(localpart, to);
} }
String id = getStreamId(); String id = getStreamId();
sendNonza(new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client));
StreamOpen streamOpen = new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client);
sendNonza(streamOpen);
XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder();
xmlEnvironmentBuilder.with(streamOpen);
outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build();
} }
public static final class SmackTlsContext { public static final class SmackTlsContext {

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2019 Florian Schmaus * Copyright 2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,11 +14,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.smack.util; package org.jivesoftware.smack;
// TODO: Replace with java.util.function.Consumer once Smack's minimum Android SDK level is 24 or higher. import org.jivesoftware.smack.packet.Element;
public interface Consumer<T> {
void accept(T t); public abstract class GenericElementListener<E extends Element> {
private final Class<? extends E> elementClass;
public GenericElementListener(Class<? extends E> elementClass) {
this.elementClass = elementClass;
}
public abstract void process(E element);
public final void processElement(Element element) {
E concreteEleement = elementClass.cast(element);
process(concreteEleement);
}
} }

View file

@ -16,7 +16,6 @@
*/ */
package org.jivesoftware.smack; package org.jivesoftware.smack;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -31,7 +30,7 @@ import org.jivesoftware.smack.util.XmppElementUtil;
public class NonzaCallback { public class NonzaCallback {
protected final AbstractXMPPConnection connection; protected final AbstractXMPPConnection connection;
protected final Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners; protected final Map<QName, GenericElementListener<? extends Nonza>> filterAndListeners;
private NonzaCallback(Builder builder) { private NonzaCallback(Builder builder) {
this.connection = builder.connection; this.connection = builder.connection;
@ -39,18 +38,21 @@ public class NonzaCallback {
install(); install();
} }
void onNonzaReceived(Nonza nonza) throws IOException { void onNonzaReceived(Nonza nonza) {
QName key = nonza.getQName(); QName key = nonza.getQName();
ClassAndConsumer<? extends Nonza> classAndConsumer = filterAndListeners.get(key); GenericElementListener<? extends Nonza> nonzaListener = filterAndListeners.get(key);
classAndConsumer.accept(nonza); nonzaListener.processElement(nonza);
} }
public void cancel() { public void cancel() {
for (Map.Entry<QName, ClassAndConsumer<? extends Nonza>> entry : filterAndListeners.entrySet()) { synchronized (connection.nonzaCallbacks) {
for (Map.Entry<QName, GenericElementListener<? extends Nonza>> entry : filterAndListeners.entrySet()) {
QName filterKey = entry.getKey(); QName filterKey = entry.getKey();
synchronized (connection.nonzaCallbacksMap) { NonzaCallback installedCallback = connection.nonzaCallbacks.get(filterKey);
connection.nonzaCallbacksMap.removeOne(filterKey, this); if (equals(installedCallback)) {
connection.nonzaCallbacks.remove(filterKey);
}
} }
} }
} }
@ -60,9 +62,9 @@ public class NonzaCallback {
return; return;
} }
synchronized (connection.nonzaCallbacks) {
for (QName key : filterAndListeners.keySet()) { for (QName key : filterAndListeners.keySet()) {
synchronized (connection.nonzaCallbacksMap) { connection.nonzaCallbacks.put(key, this);
connection.nonzaCallbacksMap.put(key, this);
} }
} }
} }
@ -72,35 +74,31 @@ public class NonzaCallback {
private SN successNonza; private SN successNonza;
private FN failedNonza; private FN failedNonza;
private NonzaResponseCallback(Class<SN> successNonzaClass, Class<FN> failedNonzaClass, private NonzaResponseCallback(Class<? extends SN> successNonzaClass, Class<? extends FN> failedNonzaClass,
Builder builder) { Builder builder) {
super(builder); super(builder);
final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass); final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass);
final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass); final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass);
final NonzaListener<SN> successListener = new NonzaListener<SN>() { final GenericElementListener<SN> successListener = new GenericElementListener<SN>(successNonzaClass) {
@Override @Override
public void accept(SN successNonza) { public void process(SN successNonza) {
NonzaResponseCallback.this.successNonza = successNonza; NonzaResponseCallback.this.successNonza = successNonza;
notifyResponse(); notifyResponse();
} }
}; };
final ClassAndConsumer<SN> successClassAndConsumer = new ClassAndConsumer<>(successNonzaClass,
successListener);
final NonzaListener<FN> failedListener = new NonzaListener<FN>() { final GenericElementListener<FN> failedListener = new GenericElementListener<FN>(failedNonzaClass) {
@Override @Override
public void accept(FN failedNonza) { public void process(FN failedNonza) {
NonzaResponseCallback.this.failedNonza = failedNonza; NonzaResponseCallback.this.failedNonza = failedNonza;
notifyResponse(); notifyResponse();
} }
}; };
final ClassAndConsumer<FN> failedClassAndConsumer = new ClassAndConsumer<>(failedNonzaClass,
failedListener);
filterAndListeners.put(successNonzaKey, successClassAndConsumer); filterAndListeners.put(successNonzaKey, successListener);
filterAndListeners.put(failedNonzaKey, failedClassAndConsumer); filterAndListeners.put(failedNonzaKey, failedListener);
install(); install();
} }
@ -141,16 +139,15 @@ public class NonzaCallback {
public static final class Builder { public static final class Builder {
private final AbstractXMPPConnection connection; private final AbstractXMPPConnection connection;
private Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners = new HashMap<>(); private Map<QName, GenericElementListener<? extends Nonza>> filterAndListeners = new HashMap<>();
Builder(AbstractXMPPConnection connection) { Builder(AbstractXMPPConnection connection) {
this.connection = connection; this.connection = connection;
} }
public <N extends Nonza> Builder listenFor(Class<N> nonza, NonzaListener<N> nonzaListener) { public <N extends Nonza> Builder listenFor(Class<? extends N> nonza, GenericElementListener<? extends N> nonzaListener) {
QName key = XmppElementUtil.getQNameFor(nonza); QName key = XmppElementUtil.getQNameFor(nonza);
ClassAndConsumer<N> classAndConsumer = new ClassAndConsumer<>(nonza, nonzaListener); filterAndListeners.put(key, nonzaListener);
filterAndListeners.put(key, classAndConsumer);
return this; return this;
} }
@ -159,25 +156,6 @@ public class NonzaCallback {
} }
} }
public interface NonzaListener<N extends Nonza> {
void accept(N nonza) throws IOException;
}
private static final class ClassAndConsumer<N extends Nonza> {
private final Class<N> clazz;
private final NonzaListener<N> consumer;
private ClassAndConsumer(Class<N> clazz, NonzaListener<N> consumer) {
this.clazz = clazz;
this.consumer = consumer;
}
private void accept(Object object) throws IOException {
N nonza = clazz.cast(object);
consumer.accept(nonza);
}
}
static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass, static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass,
Class<FN> failedNonzaClass) Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException { throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {

View file

@ -39,9 +39,8 @@ import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism; import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism;
import org.jivesoftware.smack.sasl.packet.SaslNonza; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Success;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
@ -155,9 +154,20 @@ public final class SASLAuthentication {
private final ConnectionConfiguration configuration; private final ConnectionConfiguration configuration;
private SASLMechanism currentMechanism = null; private SASLMechanism currentMechanism = null;
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackException} or {@link SASLErrorException}
*/
private Exception saslException;
SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) { SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) {
this.configuration = configuration; this.configuration = configuration;
this.connection = connection; this.connection = connection;
this.init();
} }
/** /**
@ -181,26 +191,23 @@ public final class SASLAuthentication {
* @throws NotConnectedException if the XMPP connection is not connected. * @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity. * @throws NoResponseException if there was no response from the remote entity.
*/ */
SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession) public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
throws XMPPErrorException, SASLErrorException, IOException, throws XMPPErrorException, SASLErrorException, IOException,
InterruptedException, SmackSaslException, NotConnectedException, NoResponseException { InterruptedException, SmackSaslException, NotConnectedException, NoResponseException {
final SASLMechanism mechanism = selectMechanism(authzid); currentMechanism = selectMechanism(authzid);
final CallbackHandler callbackHandler = configuration.getCallbackHandler(); final CallbackHandler callbackHandler = configuration.getCallbackHandler();
final String host = connection.getHost(); final String host = connection.getHost();
final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain(); final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain();
synchronized (this) { synchronized (this) {
currentMechanism = mechanism;
if (callbackHandler != null) { if (callbackHandler != null) {
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession); currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession);
} }
else { else {
currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession); currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession);
} }
final long deadline = System.currentTimeMillis() + connection.getReplyTimeout(); final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
while (!mechanism.isFinished()) { while (!authenticationSuccessful && saslException == null) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
if (now >= deadline) break; if (now >= deadline) break;
// Wait until SASL negotiation finishes // Wait until SASL negotiation finishes
@ -208,21 +215,35 @@ public final class SASLAuthentication {
} }
} }
mechanism.throwExceptionIfRequired(); if (saslException != null) {
if (saslException instanceof SmackSaslException) {
throw (SmackSaslException) saslException;
} else if (saslException instanceof SASLErrorException) {
throw (SASLErrorException) saslException;
} else if (saslException instanceof NotConnectedException) {
throw (NotConnectedException) saslException;
} else {
throw new IllegalStateException("Unexpected exception type" , saslException);
}
}
return mechanism; if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
return currentMechanism;
} }
/** /**
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set * Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
* to <code>false</code>. * to <code>false</code>.
* *
* @param challenge the challenge Nonza. * @param challenge a base64 encoded string representing the challenge.
* @throws SmackException if Smack detected an exceptional situation. * @throws SmackException if Smack detected an exceptional situation.
* @throws InterruptedException if the calling thread was interrupted. * @throws InterruptedException if the calling thread was interrupted.
*/ */
void challengeReceived(SaslNonza.Challenge challenge) throws SmackException, InterruptedException { public void challengeReceived(String challenge) throws SmackException, InterruptedException {
challengeReceived(challenge.getData(), false); challengeReceived(challenge, false);
} }
/** /**
@ -237,12 +258,13 @@ public final class SASLAuthentication {
* @throws NotConnectedException if the XMPP connection is not connected. * @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted. * @throws InterruptedException if the calling thread was interrupted.
*/ */
private void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException { public void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException {
SASLMechanism mechanism; try {
synchronized (this) { currentMechanism.challengeReceived(challenge, finalChallenge);
mechanism = currentMechanism; } catch (InterruptedException | SmackSaslException | NotConnectedException e) {
authenticationFailed(e);
throw e;
} }
mechanism.challengeReceived(challenge, finalChallenge);
} }
/** /**
@ -251,10 +273,8 @@ public final class SASLAuthentication {
* @param success result of the authentication. * @param success result of the authentication.
* @throws SmackException if Smack detected an exceptional situation. * @throws SmackException if Smack detected an exceptional situation.
* @throws InterruptedException if the calling thread was interrupted. * @throws InterruptedException if the calling thread was interrupted.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws SmackSaslException if a SASL specific error occured.
*/ */
void authenticated(Success success) throws InterruptedException, SmackSaslException, NotConnectedException { public void authenticated(Success success) throws SmackException, InterruptedException {
// RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP // 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 // "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 // SASL mechanism in use. In XMPP, this is done by including the additional data as the XML
@ -263,11 +283,10 @@ public final class SASLAuthentication {
if (success.getData() != null) { if (success.getData() != null) {
challengeReceived(success.getData(), true); challengeReceived(success.getData(), true);
} }
currentMechanism.checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
// Wake up the thread that is waiting in the #authenticate method // Wake up the thread that is waiting in the #authenticate method
synchronized (this) { synchronized (this) {
currentMechanism.afterFinalSaslChallenge();
notify(); notify();
} }
} }
@ -279,29 +298,30 @@ public final class SASLAuthentication {
* @param saslFailure the SASL failure as reported by the server * @param saslFailure the SASL failure as reported by the server
* @see <a href="https://tools.ietf.org/html/rfc6120#section-6.5">RFC6120 6.5</a> * @see <a href="https://tools.ietf.org/html/rfc6120#section-6.5">RFC6120 6.5</a>
*/ */
void authenticationFailed(SASLFailure saslFailure) { public void authenticationFailed(SASLFailure saslFailure) {
SASLErrorException saslErrorException; authenticationFailed(new SASLErrorException(currentMechanism.getName(), saslFailure));
synchronized (this) {
saslErrorException = new SASLErrorException(currentMechanism.getName(), saslFailure);
}
authenticationFailed(saslErrorException);
} }
void authenticationFailed(Exception exception) { private void authenticationFailed(Exception exception) {
saslException = exception;
// Wake up the thread that is waiting in the #authenticate method // Wake up the thread that is waiting in the #authenticate method
synchronized (this) { synchronized (this) {
currentMechanism.setException(exception);
notify(); notify();
} }
} }
public boolean authenticationSuccessful() { public boolean authenticationSuccessful() {
synchronized (this) { return authenticationSuccessful;
if (currentMechanism == null) {
return false;
}
return currentMechanism.isAuthenticationSuccessful();
} }
/**
* 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
* is disconnected and then reconnected.
*/
void init() {
authenticationSuccessful = false;
saslException = null;
} }
String getNameOfLastUsedSaslMechansism() { String getNameOfLastUsedSaslMechansism() {

View file

@ -108,6 +108,30 @@ public final class SmackConfiguration {
return SmackInitialization.SMACK_VERSION; return SmackInitialization.SMACK_VERSION;
} }
/**
* Returns the number of milliseconds to wait for a response from
* the server. The default value is 5000 ms.
*
* @return the milliseconds to wait for a response from the server
* @deprecated use {@link #getDefaultReplyTimeout()} instead.
*/
@Deprecated
public static int getDefaultPacketReplyTimeout() {
return getDefaultReplyTimeout();
}
/**
* Sets the number of milliseconds to wait for a response from
* the server.
*
* @param timeout the milliseconds to wait for a response from the server
* @deprecated use {@link #setDefaultReplyTimeout(int)} instead.
*/
@Deprecated
public static void setDefaultPacketReplyTimeout(int timeout) {
setDefaultReplyTimeout(timeout);
}
/** /**
* Returns the number of milliseconds to wait for a response from * Returns the number of milliseconds to wait for a response from
* the server. The default value is 5000 ms. * the server. The default value is 5000 ms.

View file

@ -37,9 +37,6 @@ import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider; import org.jivesoftware.smack.provider.BindIQProvider;
import org.jivesoftware.smack.provider.BodyElementProvider; import org.jivesoftware.smack.provider.BodyElementProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.provider.SaslChallengeProvider;
import org.jivesoftware.smack.provider.SaslFailureProvider;
import org.jivesoftware.smack.provider.SaslSuccessProvider;
import org.jivesoftware.smack.provider.TlsFailureProvider; import org.jivesoftware.smack.provider.TlsFailureProvider;
import org.jivesoftware.smack.provider.TlsProceedProvider; import org.jivesoftware.smack.provider.TlsProceedProvider;
import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.core.SASLAnonymous;
@ -129,9 +126,6 @@ public final class SmackInitialization {
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider()); ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider()); ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider());
ProviderManager.addNonzaProvider(SaslChallengeProvider.INSTANCE);
ProviderManager.addNonzaProvider(SaslSuccessProvider.INSTANCE);
ProviderManager.addNonzaProvider(SaslFailureProvider.INSTANCE);
ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE); ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE);
ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE); ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE);
ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE); ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE);

View file

@ -53,6 +53,8 @@ import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
@ -333,6 +335,19 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon
parseFeatures(parser); parseFeatures(parser);
afterFeaturesReceived(); afterFeaturesReceived();
break; break;
// SASL related top level stream elements
case Challenge.ELEMENT:
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case Success.ELEMENT:
Success success = new Success(parser.nextText());
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
getSASLAuthentication().authenticated(success);
sendStreamOpen();
break;
default: default:
parseAndProcessNonza(parser); parseAndProcessNonza(parser);
break; break;
@ -598,7 +613,7 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon
prepareToWaitForFeaturesReceived(); prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.loginContext; LoginContext loginContext = walkStateGraphContext.loginContext;
SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession()); SASLMechanism usedSaslMechanism = saslAuthentication.authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession());
// authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features. // authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features.
waitForFeaturesReceived("server stream features after SASL authentication"); waitForFeaturesReceived("server stream features after SASL authentication");

View file

@ -31,7 +31,6 @@ import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor; import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.MultiMap;
/** /**
@ -331,8 +330,7 @@ public class StateDescriptorGraph {
return res; return res;
} }
private static <E> void dfsVisit(GraphVertex<E> vertex, Consumer<GraphVertex<E>> dfsFinishedVertex, private static <E> void dfsVisit(GraphVertex<E> vertex, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
DfsEdgeFound<E> dfsEdgeFound) {
vertex.color = GraphVertex.VertexColor.grey; vertex.color = GraphVertex.VertexColor.grey;
final int totalEdgeCount = vertex.getOutgoingEdges().size(); final int totalEdgeCount = vertex.getOutgoingEdges().size();
@ -351,12 +349,11 @@ public class StateDescriptorGraph {
vertex.color = GraphVertex.VertexColor.black; vertex.color = GraphVertex.VertexColor.black;
if (dfsFinishedVertex != null) { if (dfsFinishedVertex != null) {
dfsFinishedVertex.accept(vertex); dfsFinishedVertex.onVertexFinished(vertex);
} }
} }
private static <E> void dfs(Collection<GraphVertex<E>> vertexes, Consumer<GraphVertex<E>> dfsFinishedVertex, private static <E> void dfs(Collection<GraphVertex<E>> vertexes, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) {
DfsEdgeFound<E> dfsEdgeFound) {
for (GraphVertex<E> vertex : vertexes) { for (GraphVertex<E> vertex : vertexes) {
if (vertex.color == GraphVertex.VertexColor.white) { if (vertex.color == GraphVertex.VertexColor.white) {
dfsVisit(vertex, dfsFinishedVertex, dfsEdgeFound); dfsVisit(vertex, dfsFinishedVertex, dfsEdgeFound);
@ -410,6 +407,12 @@ public class StateDescriptorGraph {
dotOut.append("}\n"); dotOut.append("}\n");
} }
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsFinishedVertex<E> {
void onVertexFinished(GraphVertex<E> vertex);
}
// TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack's minimum Android SDK level is 24 or higher.
private interface DfsEdgeFound<E> { private interface DfsEdgeFound<E> {
void onEdgeFound(GraphVertex<E> from, GraphVertex<E> to, int edgeId, int totalEdgeCount); void onEdgeFound(GraphVertex<E> from, GraphVertex<E> to, int edgeId, int totalEdgeCount);
} }

View file

@ -163,12 +163,6 @@ public class XmlEnvironment {
return this; return this;
} }
public Builder with(StreamOpen streamOpen) {
withNamespace(streamOpen.getNamespace());
withLanguage(streamOpen.getLanguage());
return this;
}
public XmlEnvironment build() { public XmlEnvironment build() {
return new XmlEnvironment(this); return new XmlEnvironment(this);
} }

View file

@ -1,40 +0,0 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslChallengeProvider extends NonzaProvider<SaslNonza.Challenge> {
public static final SaslChallengeProvider INSTANCE = new SaslChallengeProvider();
private SaslChallengeProvider() {
}
@Override
public SaslNonza.Challenge parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws IOException, XmlPullParserException {
String data = parser.nextText();
return new SaslNonza.Challenge(data);
}
}

View file

@ -1,62 +0,0 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import java.util.Map;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslFailureProvider extends NonzaProvider<SASLFailure> {
public static final SaslFailureProvider INSTANCE = new SaslFailureProvider();
private SaslFailureProvider() {
}
@Override
public SASLFailure parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
String condition = null;
Map<String, String> descriptiveTexts = null;
outerloop: while (true) {
XmlPullParser.TagEvent eventType = parser.nextTag();
switch (eventType) {
case START_ELEMENT:
String name = parser.getName();
if (name.equals("text")) {
descriptiveTexts = PacketParserUtils.parseDescriptiveTexts(parser, descriptiveTexts);
}
else {
assert condition == null;
condition = parser.getName();
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
return new SASLFailure(condition, descriptiveTexts);
}
}

View file

@ -1,40 +0,0 @@
/**
*
* Copyright 2019 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public final class SaslSuccessProvider extends NonzaProvider<SaslNonza.Success> {
public static final SaslSuccessProvider INSTANCE = new SaslSuccessProvider();
private SaslSuccessProvider() {
}
@Override
public SaslNonza.Success parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws IOException, XmlPullParserException {
String data = parser.nextText();
return new SaslNonza.Success(data);
}
}

View file

@ -20,7 +20,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
public class SASLErrorException extends XMPPException { public class SASLErrorException extends XMPPException {

View file

@ -23,12 +23,11 @@ import javax.net.ssl.SSLSession;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackSaslException; import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Response; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.util.stringencoder.Base64;
@ -57,17 +56,6 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
public static final String GSSAPI = "GSSAPI"; public static final String GSSAPI = "GSSAPI";
public static final String PLAIN = "PLAIN"; public static final String PLAIN = "PLAIN";
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackSaslException},{@link SASLErrorException}, {@link NotConnectedException} or
* {@link InterruptedException}.
*/
private Exception exception;
protected XMPPConnection connection; protected XMPPConnection connection;
protected ConnectionConfiguration connectionConfiguration; protected ConnectionConfiguration connectionConfiguration;
@ -287,18 +275,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
*/ */
public abstract int getPriority(); public abstract int getPriority();
/** public abstract void checkIfSuccessfulOrThrow() throws SmackSaslException;
* Check if the SASL mechanism was successful and if it was, then mark it so.
*
* @throws SmackSaslException in case of an SASL error.
*/
public final void afterFinalSaslChallenge() throws SmackSaslException {
checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
}
protected abstract void checkIfSuccessfulOrThrow() throws SmackSaslException;
public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) { public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
SASLMechanism saslMechansim = newInstance(); SASLMechanism saslMechansim = newInstance();
@ -311,39 +288,6 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
return false; return false;
} }
public boolean isAuthenticationSuccessful() {
return authenticationSuccessful;
}
public boolean isFinished() {
return isAuthenticationSuccessful() || exception != null;
}
public void throwExceptionIfRequired() throws SmackSaslException, SASLErrorException, NotConnectedException,
InterruptedException, NoResponseException {
if (exception != null) {
if (exception instanceof SmackSaslException) {
throw (SmackSaslException) exception;
} else if (exception instanceof SASLErrorException) {
throw (SASLErrorException) exception;
} else if (exception instanceof NotConnectedException) {
throw (NotConnectedException) exception;
} else if (exception instanceof InterruptedException) {
throw (InterruptedException) exception;
} else {
throw new IllegalStateException("Unexpected exception type", exception);
}
}
if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
}
public void setException(Exception exception) {
this.exception = exception;
}
protected abstract SASLMechanism newInstance(); protected abstract SASLMechanism newInstance();
protected static byte[] toBytes(String string) { protected static byte[] toBytes(String string) {

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014-2019 Florian Schmaus * Copyright 2014-2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,30 +18,21 @@ package org.jivesoftware.smack.sasl.packet;
import java.util.Map; import java.util.Map;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.AbstractError; import org.jivesoftware.smack.packet.AbstractError;
import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.sasl.SASLError; import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
public interface SaslNonza extends Nonza { public class SaslStreamElements {
String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl"; public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
@Override
default String getNamespace() {
return NAMESPACE;
}
/** /**
* Initiating SASL authentication by select a mechanism. * Initiating SASL authentication by select a mechanism.
*/ */
class AuthMechanism implements SaslNonza { public static class AuthMechanism implements Nonza {
public static final String ELEMENT = "auth"; public static final String ELEMENT = "auth";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String mechanism; private final String mechanism;
private final String authenticationText; private final String authenticationText;
@ -53,11 +44,11 @@ public interface SaslNonza extends Nonza {
} }
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); XmlStringBuilder xml = new XmlStringBuilder();
xml.attribute("mechanism", mechanism).rightAngleBracket(); xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).attribute("mechanism", mechanism).rightAngleBracket();
xml.escape(authenticationText); xml.optAppend(authenticationText);
xml.closeElement(this); xml.closeElement(ELEMENT);
return xml; return xml;
} }
@ -69,6 +60,11 @@ public interface SaslNonza extends Nonza {
return authenticationText; return authenticationText;
} }
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override @Override
public String getElementName() { public String getElementName() {
return ELEMENT; return ELEMENT;
@ -78,9 +74,8 @@ public interface SaslNonza extends Nonza {
/** /**
* A SASL challenge stream element. * A SASL challenge stream element.
*/ */
class Challenge implements SaslNonza { public static class Challenge implements Nonza {
public static final String ELEMENT = "challenge"; public static final String ELEMENT = "challenge";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String data; private final String data;
@ -88,15 +83,18 @@ public interface SaslNonza extends Nonza {
this.data = StringUtils.returnIfNotEmptyTrimmed(data); this.data = StringUtils.returnIfNotEmptyTrimmed(data);
} }
public String getData() { @Override
return data; public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder().halfOpenElement(ELEMENT).xmlnsAttribute(
NAMESPACE).rightAngleBracket();
xml.optAppend(data);
xml.closeElement(ELEMENT);
return xml;
} }
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { public String getNamespace() {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); return NAMESPACE;
xml.optTextChild(data, this);
return xml;
} }
@Override @Override
@ -108,9 +106,8 @@ public interface SaslNonza extends Nonza {
/** /**
* A SASL response stream element. * A SASL response stream element.
*/ */
class Response implements SaslNonza { public static class Response implements Nonza {
public static final String ELEMENT = "response"; public static final String ELEMENT = "response";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String authenticationText; private final String authenticationText;
@ -123,9 +120,11 @@ public interface SaslNonza extends Nonza {
} }
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); XmlStringBuilder xml = new XmlStringBuilder();
xml.optTextChild(authenticationText, this); xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
xml.optAppend(authenticationText);
xml.closeElement(ELEMENT);
return xml; return xml;
} }
@ -133,6 +132,11 @@ public interface SaslNonza extends Nonza {
return authenticationText; return authenticationText;
} }
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override @Override
public String getElementName() { public String getElementName() {
return ELEMENT; return ELEMENT;
@ -142,9 +146,8 @@ public interface SaslNonza extends Nonza {
/** /**
* A SASL success stream element. * A SASL success stream element.
*/ */
class Success implements SaslNonza { public static class Success implements Nonza {
public static final String ELEMENT = "success"; public static final String ELEMENT = "success";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String data; private final String data;
@ -168,12 +171,19 @@ public interface SaslNonza extends Nonza {
} }
@Override @Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); XmlStringBuilder xml = new XmlStringBuilder();
xml.optTextChild(data, this); xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
xml.optAppend(data);
xml.closeElement(ELEMENT);
return xml; return xml;
} }
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override @Override
public String getElementName() { public String getElementName() {
return ELEMENT; return ELEMENT;
@ -184,9 +194,8 @@ public interface SaslNonza extends Nonza {
* A SASL failure stream element, also called "SASL Error". * A SASL failure stream element, also called "SASL Error".
* @see <a href="http://xmpp.org/rfcs/rfc6120.html#sasl-errors">RFC 6120 6.5 SASL Errors</a> * @see <a href="http://xmpp.org/rfcs/rfc6120.html#sasl-errors">RFC 6120 6.5 SASL Errors</a>
*/ */
class SASLFailure extends AbstractError implements SaslNonza { public static class SASLFailure extends AbstractError implements Nonza {
public static final String ELEMENT = "failure"; public static final String ELEMENT = "failure";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final SASLError saslError; private final SASLError saslError;
private final String saslErrorString; private final String saslErrorString;
@ -241,6 +250,11 @@ public interface SaslNonza extends Nonza {
return toXML().toString(); return toXML().toString();
} }
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override @Override
public String getElementName() { public String getElementName() {
return ELEMENT; return ELEMENT;

View file

@ -51,9 +51,8 @@ public class CollectionUtil {
} }
public static <T> ArrayList<T> newListWith(Collection<? extends T> collection) { public static <T> ArrayList<T> newListWith(Collection<? extends T> collection) {
if (collection == null) { ArrayList<T> arrayList = new ArrayList<>(collection.size());
return null; arrayList.addAll(collection);
} return arrayList;
return new ArrayList<>(collection);
} }
} }

View file

@ -49,6 +49,7 @@ import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
import org.jivesoftware.smack.provider.ExtensionElementProvider; import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.xml.SmackXmlParser; import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smack.xml.XmlPullParserException;
@ -685,6 +686,44 @@ public class PacketParserUtils {
return descriptiveTexts; return descriptiveTexts;
} }
/**
* Parses SASL authentication error packets.
*
* @param parser the XML parser.
* @return a SASL Failure packet.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
*/
public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException {
final int initialDepth = parser.getDepth();
String condition = null;
Map<String, String> descriptiveTexts = null;
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
switch (eventType) {
case START_ELEMENT:
String name = parser.getName();
if (name.equals("text")) {
descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts);
}
else {
assert condition == null;
condition = parser.getName();
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
default:
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
break;
}
}
return new SASLFailure(condition, descriptiveTexts);
}
public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException { public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
return parseStreamError(parser, null); return parseStreamError(parser, null);
} }

View file

@ -18,10 +18,9 @@ package org.jivesoftware.smack.util;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.Iterator;
import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
@ -497,20 +496,17 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return this; return this;
} }
public XmlStringBuilder optAppend(Element element) { public XmlStringBuilder optAppend(CharSequence csq) {
if (element != null) { if (csq != null) {
append(element.toXML(effectiveXmlEnvironment)); append(csq);
} }
return this; return this;
} }
public XmlStringBuilder optTextChild(CharSequence sqc, NamedElement parentElement) { public XmlStringBuilder optAppend(Element element) {
if (sqc == null) { if (element != null) {
return closeEmptyElement(); append(element.toXML(effectiveXmlEnvironment));
} }
rightAngleBracket();
escape(sqc);
closeElement(parentElement);
return this; return this;
} }
@ -610,46 +606,24 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
return toString().hashCode(); return toString().hashCode();
} }
private static final class WrappedIoException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final IOException wrappedIoException;
private WrappedIoException(IOException wrappedIoException) {
this.wrappedIoException = wrappedIoException;
}
}
/** /**
* Write the contents of this <code>XmlStringBuilder</code> to a {@link Writer}. This will write * Write the contents of this <code>XmlStringBuilder</code> to a {@link Writer}. This will write
* the single parts one-by-one, avoiding allocation of a big continuous memory block holding the * the single parts one-by-one, avoiding allocation of a big continuous memory block holding the
* XmlStringBuilder contents. * XmlStringBuilder contents.
* *
* @param writer TODO javadoc me please * @param writer TODO javadoc me please
* @param enclosingXmlEnvironment the enclosing XML environment. * @param enclosingNamespace the enclosing XML namespace.
* @throws IOException if an I/O error occured. * @throws IOException if an I/O error occured.
*/ */
public void write(Writer writer, XmlEnvironment enclosingXmlEnvironment) throws IOException { public void write(Writer writer, String enclosingNamespace) throws IOException {
try { XmlEnvironment enclosingXmlEnvironment = XmlEnvironment.builder()
appendXmlTo(csq -> { .withNamespace(enclosingNamespace)
try { .build();
writer.append(csq); appendXmlTo(writer, enclosingXmlEnvironment);
} catch (IOException e) {
throw new WrappedIoException(e);
}
}, enclosingXmlEnvironment);
} catch (WrappedIoException e) {
throw e.wrappedIoException;
}
} }
public List<CharSequence> toList(XmlEnvironment enclosingXmlEnvironment) { public Iterator<CharSequence> getCharSequenceIterator() {
List<CharSequence> res = new ArrayList<>(sb.getAsList().size()); return sb.getAsList().iterator();
appendXmlTo(csq -> res.add(csq), enclosingXmlEnvironment);
return res;
} }
@Override @Override
@ -657,26 +631,29 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
// This is only the potential length, since the actual length depends on the given XmlEnvironment. // This is only the potential length, since the actual length depends on the given XmlEnvironment.
int potentialLength = length(); int potentialLength = length();
StringBuilder res = new StringBuilder(potentialLength); StringBuilder res = new StringBuilder(potentialLength);
try {
appendXmlTo(csq -> res.append(csq), enclosingXmlEnvironment); appendXmlTo(res, enclosingXmlEnvironment);
} catch (IOException e) {
// Should never happen.
throw new AssertionError(e);
}
return res; return res;
} }
private void appendXmlTo(Consumer<CharSequence> charSequenceSink, XmlEnvironment enclosingXmlEnvironment) { private void appendXmlTo(Appendable appendable, XmlEnvironment enclosingXmlEnvironment) throws IOException {
for (CharSequence csq : sb.getAsList()) { for (CharSequence csq : sb.getAsList()) {
if (csq instanceof XmlStringBuilder) { if (csq instanceof XmlStringBuilder) {
((XmlStringBuilder) csq).appendXmlTo(charSequenceSink, enclosingXmlEnvironment); ((XmlStringBuilder) csq).appendXmlTo(appendable, enclosingXmlEnvironment);
} }
else if (csq instanceof XmlNsAttribute) { else if (csq instanceof XmlNsAttribute) {
XmlNsAttribute xmlNsAttribute = (XmlNsAttribute) csq; XmlNsAttribute xmlNsAttribute = (XmlNsAttribute) csq;
if (!xmlNsAttribute.value.equals(enclosingXmlEnvironment.getEffectiveNamespace())) { if (!xmlNsAttribute.value.equals(enclosingXmlEnvironment.getEffectiveNamespace())) {
charSequenceSink.accept(xmlNsAttribute); appendable.append(xmlNsAttribute);
enclosingXmlEnvironment = new XmlEnvironment(xmlNsAttribute.value); enclosingXmlEnvironment = new XmlEnvironment(xmlNsAttribute.value);
} }
} }
else { else {
charSequenceSink.accept(csq); appendable.append(csq);
} }
} }
} }

View file

@ -1,78 +0,0 @@
/**
*
* Copyright (C) 2007 Jive Software, 2019 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.provider;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import java.io.IOException;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.xml.XmlPullParserException;
import com.jamesmurty.utils.XMLBuilder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
public class SaslProviderTest {
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void parseSASLFailureSimple(SmackTestUtil.XmlPullParserKind parserKind)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError,
XmlPullParserException, IOException, SmackParsingException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE)
.e(SASLError.account_disabled.toString())
.asString();
// @formatter:on
SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind);
assertXmlSimilar(saslFailureString, saslFailure.toString());
}
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void parseSASLFailureExtended(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException, TransformerException,
ParserConfigurationException, FactoryConfigurationError {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE)
.e(SASLError.account_disabled.toString())
.up()
.e("text").a("xml:lang", "en")
.t("Call 212-555-1212 for assistance.")
.up()
.e("text").a("xml:lang", "de")
.t("Bitte wenden sie sich an (04321) 123-4444")
.up()
.e("text")
.t("Wusel dusel")
.asString();
// @formatter:on
SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind);
assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE));
}
}

View file

@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.DummyConnection; import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
import org.jivesoftware.smack.sasl.packet.SaslNonza.Response; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.util.stringencoder.Base64;

View file

@ -125,19 +125,10 @@ public class SmackTestUtil {
return parser; return parser;
} }
@SuppressWarnings("unchecked")
private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) { private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
P provider; P provider;
// TODO: Consider adding a shortcut in case there is a static INSTANCE field holding an instance of the
try { // requested provider.
provider = (P) providerClass.getDeclaredField("INSTANCE").get(null);
return provider;
} catch (NoSuchFieldException e) {
// Continue with the next approach.
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
throw new AssertionError(e);
}
try { try {
provider = providerClass.getDeclaredConstructor().newInstance(); provider = providerClass.getDeclaredConstructor().newInstance();
} }

View file

@ -39,6 +39,9 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.test.util.SmackTestUtil; import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParser;
@ -811,6 +814,41 @@ public class PacketParserUtilsTest {
assertXmlSimilar(stanza, result.toString()); assertXmlSimilar(stanza, result.toString());
} }
@Test
public void parseSASLFailureSimple() throws FactoryConfigurationError, SAXException, IOException,
TransformerException, ParserConfigurationException, XmlPullParserException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE)
.e(SASLError.account_disabled.toString())
.asString();
// @formatter:on
XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT);
SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser);
assertXmlSimilar(saslFailureString, saslFailure.toString());
}
@Test
public void parseSASLFailureExtended() throws FactoryConfigurationError, TransformerException,
ParserConfigurationException, XmlPullParserException, IOException, SAXException {
// @formatter:off
final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE)
.e(SASLError.account_disabled.toString())
.up()
.e("text").a("xml:lang", "en")
.t("Call 212-555-1212 for assistance.")
.up()
.e("text").a("xml:lang", "de")
.t("Bitte wenden sie sich an (04321) 123-4444")
.up()
.e("text")
.t("Wusel dusel")
.asString();
// @formatter:on
XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT);
SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser);
assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE));
}
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
private static String determineNonDefaultLanguage() { private static String determineNonDefaultLanguage() {
String otherLanguage = "jp"; String otherLanguage = "jp";

View file

@ -225,7 +225,9 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override @Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.optTextChild(text, this); xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
return xml; return xml;
} }
@ -267,7 +269,9 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override @Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.optTextChild(text, this); xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
return xml; return xml;
} }
@ -309,7 +313,9 @@ public abstract class AbstractHttpOverXmpp extends IQ {
@Override @Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.optTextChild(text, this); xml.rightAngleBracket();
xml.optAppend(text);
xml.closeElement(this);
return xml; return xml;
} }

View file

@ -46,7 +46,7 @@ public class QueryArchiveTest extends MamTest {
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>" private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
+ "<result xmlns='urn:xmpp:mam:1' queryid='g27' id='34482-21985-73620'>" + "<result xmlns='urn:xmpp:mam:1' queryid='g27' id='34482-21985-73620'>"
+ "<forwarded xmlns='urn:xmpp:forward:0'>" + "<forwarded xmlns='urn:xmpp:forward:0'>"
+ "<delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37.000+00:00'/>" + "<message " + "<delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37.000+00:00'></delay>" + "<message "
+ "xmlns='jabber:client' from='coven@chat.shakespeare.lit/firstwitch' " + "id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2' " + "xmlns='jabber:client' from='coven@chat.shakespeare.lit/firstwitch' " + "id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2' "
+ "type='chat'>" + "<body>Thrice the brinded cat hath mew.</body>" + "</message>" + "</forwarded>" + "type='chat'>" + "<body>Thrice the brinded cat hath mew.</body>" + "</message>" + "</forwarded>"
+ "</result>" + "</message>"; + "</result>" + "</message>";

View file

@ -106,7 +106,9 @@ public class DelayInformation implements ExtensionElement {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.attribute("stamp", XmppDateTime.formatXEP0082Date(stamp)); xml.attribute("stamp", XmppDateTime.formatXEP0082Date(stamp));
xml.optAttribute("from", from); xml.optAttribute("from", from);
xml.optTextChild(reason, this); xml.rightAngleBracket();
xml.optAppend(reason);
xml.closeElement(this);
return xml; return xml;
} }

View file

@ -146,7 +146,7 @@ public class StreamInitiation extends IQ {
buf.rightAngleBracket(); buf.rightAngleBracket();
// Add the file section if there is one. // Add the file section if there is one.
buf.optElement(file); buf.optAppend(file.toXML());
break; break;
case result: case result:
buf.rightAngleBracket(); buf.rightAngleBracket();

View file

@ -8,7 +8,6 @@ dependencies {
compile project(":smack-core") compile project(":smack-core")
compile project(":smack-resolver-javax") compile project(":smack-resolver-javax")
compile project(":smack-sasl-javax") compile project(":smack-sasl-javax")
implementation project(":smack-xmlparser-stax")
} }
javadoc { javadoc {

View file

@ -29,6 +29,11 @@ public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
public static final String NAME = GSSAPI; public static final String NAME = GSSAPI;
static {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("java.security.auth.login.config", "gss.conf");
}
@Override @Override
public boolean authzidSupported() { public boolean authzidSupported() {
return true; return true;

View file

@ -1,21 +1,12 @@
.PHONY := all clean .PHONY := clean generate
GRADLE_QUITE_ARGS := --quiet --console plain GRADLE_QUITE_ARGS := --quiet --console plain
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png GENERATED_FILES := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG:.png=.dot) generate: $(GENERATED_FILES)
GENERATED_FILES := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG) $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT)
all: $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG)
clean: clean:
rm -f $(GENERATED_FILES) rm -f $(GENERATED_FILES)
%.png: %.dot src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png: src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
dot -Tpng -o $@ $^ gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph | dot -Tpng -o $@
$(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT): src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
# TODO: This also creates the dot file even if the command
# fails. It would be better if this was not the case.
gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph > $@

View file

@ -1,2 +1 @@
/XmppNioTcpConnectionStateGraph.png /XmppNioTcpConnectionStateGraph.png
/XmppNioTcpConnectionStateGraph.dot

View file

@ -87,8 +87,12 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls; import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.packet.SaslNonza; import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.sm.SMUtils; import org.jivesoftware.smack.sm.SMUtils;
import org.jivesoftware.smack.sm.StreamManagementException; import org.jivesoftware.smack.sm.StreamManagementException;
import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException; import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException;
@ -298,10 +302,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
} }
}); });
// Re-init the reader and writer in case of SASL <success/>. This is done to reset the parser since a new stream
// is initiated.
buildNonzaCallback().listenFor(SaslNonza.Success.class, s -> resetParser()).install();
} }
/** /**
@ -371,7 +371,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
SmackException, IOException, InterruptedException { SmackException, IOException, InterruptedException {
// Authenticate using SASL // Authenticate using SASL
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null; SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
authenticate(username, password, config.getAuthzid(), sslSession); saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
// Wait for stream features after the authentication. // Wait for stream features after the authentication.
// TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be // TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be
@ -857,7 +857,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
tlsHandled.reportSuccess(); tlsHandled.reportSuccess();
} }
if (isSaslAuthenticated()) { if (getSASLAuthentication().authenticationSuccessful()) {
// If we have received features after the SASL has been successfully completed, then we // If we have received features after the SASL has been successfully completed, then we
// have also *maybe* received, as it is an optional feature, the compression feature // have also *maybe* received, as it is an optional feature, the compression feature
// from the server. // from the server.
@ -865,17 +865,18 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
} }
private void resetParser() throws IOException { /**
try { * Resets the parser using the latest connection's reader. Resetting the parser is necessary
packetReader.parser = SmackXmlParser.newXmlParser(reader); * when the plain connection has been secured or when a new opening stream element is going
} catch (XmlPullParserException e) { * to be sent by the server.
throw new IOException(e); *
} * @throws SmackException if the parser could not be reset.
} * @throws InterruptedException if the calling thread was interrupted.
* @throws XmlPullParserException if an error in the XML parser occured.
private void openStreamAndResetParser() throws IOException, NotConnectedException, InterruptedException { */
void openStream() throws SmackException, InterruptedException, XmlPullParserException {
sendStreamOpen(); sendStreamOpen();
resetParser(); packetReader.parser = SmackXmlParser.newXmlParser(reader);
} }
protected class PacketReader { protected class PacketReader {
@ -920,7 +921,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
private void parsePackets() { private void parsePackets() {
boolean initialStreamOpenSend = false; boolean initialStreamOpenSend = false;
try { try {
openStreamAndResetParser(); openStream();
initialStreamOpenSend = true; initialStreamOpenSend = true;
XmlPullParser.Event eventType = parser.getEventType(); XmlPullParser.Event eventType = parser.getEventType();
while (!done) { while (!done) {
@ -956,7 +957,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// Secure the connection by negotiating TLS // Secure the connection by negotiating TLS
proceedTLSReceived(); proceedTLSReceived();
// Send a new opening stream to the server // Send a new opening stream to the server
openStreamAndResetParser(); openStream();
} }
catch (Exception e) { catch (Exception e) {
SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e); SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e);
@ -979,17 +980,35 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
compressSyncPoint.reportFailure(new SmackException.SmackMessageException( compressSyncPoint.reportFailure(new SmackException.SmackMessageException(
"Could not establish compression")); "Could not establish compression"));
break; break;
default: case SaslStreamElements.NAMESPACE:
parseAndProcessNonza(parser); // SASL authentication has failed. The server may close the connection
// depending on the number of retries
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
getSASLAuthentication().authenticationFailed(failure);
break;
} }
break; break;
case Challenge.ELEMENT:
// The server is challenging the SASL authentication made by the client
String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case Success.ELEMENT:
Success success = new Success(parser.nextText());
// We now need to bind a resource for the connection
// Open a new stream and wait for the response
openStream();
// The SASL authentication with the server was successful. The next step
// will be to bind the resource
getSASLAuthentication().authenticated(success);
break;
case Compressed.ELEMENT: case Compressed.ELEMENT:
// Server confirmed that it's possible to use stream compression. Start // Server confirmed that it's possible to use stream compression. Start
// stream compression // stream compression
// Initialize the reader and writer with the new compressed version // Initialize the reader and writer with the new compressed version
initReaderAndWriter(); initReaderAndWriter();
// Send a new opening stream to the server // Send a new opening stream to the server
openStreamAndResetParser(); openStream();
// Notify that compression is being used // Notify that compression is being used
compressSyncPoint.reportSuccess(); compressSyncPoint.reportSuccess();
break; break;
@ -1071,7 +1090,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
break; break;
default: default:
parseAndProcessNonza(parser); LOGGER.warning("Unknown top level stream element: " + name);
break; break;
} }
break; break;
@ -1328,9 +1347,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
maybeAddToUnacknowledgedStanzas(packet); maybeAddToUnacknowledgedStanzas(packet);
CharSequence elementXml = element.toXML(outgoingStreamXmlEnvironment); CharSequence elementXml = element.toXML(StreamOpen.CLIENT_NAMESPACE);
if (elementXml instanceof XmlStringBuilder) { if (elementXml instanceof XmlStringBuilder) {
((XmlStringBuilder) elementXml).write(writer, outgoingStreamXmlEnvironment); ((XmlStringBuilder) elementXml).write(writer, StreamOpen.CLIENT_NAMESPACE);
} }
else { else {
writer.write(elementXml.toString()); writer.write(elementXml.toString());

View file

@ -509,7 +509,7 @@ public class XmppNioTcpConnection extends AbstractXmppNioConnection {
CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE); CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE);
if (nextCharSequence instanceof XmlStringBuilder) { if (nextCharSequence instanceof XmlStringBuilder) {
XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence; XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence;
outgoingCharSequenceIterator = xmlStringBuilder.toList(outgoingStreamXmlEnvironment).iterator(); outgoingCharSequenceIterator = xmlStringBuilder.getCharSequenceIterator();
} else { } else {
outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator(); outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator();
} }

View file

@ -31,8 +31,7 @@ public class SmackXmlParser {
public static XmlPullParserFactory getXmlPullParserFactory() { public static XmlPullParserFactory getXmlPullParserFactory() {
Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator(); Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator();
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
throw new IllegalStateException( throw new IllegalStateException("Could not load a XmlPullParserFactory via Service Provider Interface (SPI)");
"No XmlPullParserFactory registered with Service Provider Interface (SPI). Is smack-xmlparser-xpp3 or smack-xmlparser-stax in classpath?");
} }
return iterator.next(); return iterator.next();
} }