From 052c58f3770e0702077221dbfa1913c15854519a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 3 Sep 2022 20:17:29 +0200 Subject: [PATCH] rudimentary bind 2 implementation --- .../conversations/parser/MessageParser.java | 6 +- .../eu/siacs/conversations/xml/Namespace.java | 3 +- .../conversations/xmpp/XmppConnection.java | 116 ++++++++++++++---- 3 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 50743312c..76945c472 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -313,7 +313,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece private boolean handleErrorMessage(final Account account, final MessagePacket packet) { if (packet.getType() == MessagePacket.TYPE_ERROR) { if (packet.fromServer(account)) { - final Pair forwarded = packet.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); + final Pair forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS); if (forwarded != null) { return handleErrorMessage(account, forwarded.first); } @@ -389,8 +389,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return; } else if (original.fromServer(account)) { Pair f; - f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2"); - f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f; + f = original.getForwardedMessagePacket("received", Namespace.CARBONS); + f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f; packet = f != null ? f.first : original; if (handleErrorMessage(account, packet)) { return; diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index e28e69add..a4fd8c063 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -25,9 +25,10 @@ public final class Namespace { public static final String NICK = "http://jabber.org/protocol/nick"; public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline"; public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind"; - public static final String BIND2 = "urn:xmpp:bind2:0"; + public static final String BIND2 = "urn:xmpp:bind2:1"; public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3"; public static final String CSI = "urn:xmpp:csi:0"; + public static final String CARBONS = "urn:xmpp:carbons:2"; public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0"; public static final String BOOKMARKS = "storage:bookmarks"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 6efbfbf15..525151d62 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -13,6 +13,7 @@ import android.util.SparseArray; import androidx.annotation.NonNull; import com.google.common.base.Strings; +import com.google.common.collect.Collections2; import org.xmlpull.v1.XmlPullParserException; @@ -32,6 +33,7 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -274,7 +276,7 @@ public class XmppConnection implements Runnable { this.attempt++; this.verifiedHostname = null; // will be set if user entered hostname is being used or hostname was verified - // with dnssec + // with dnssec try { Socket localSocket; shouldAuthenticate = !account.isOptionSet(Account.OPTION_REGISTER); @@ -409,7 +411,7 @@ public class XmppConnection implements Runnable { if (startXmpp(localSocket)) { localSocket.setSoTimeout( 0); // reset to 0; once the connection is established we don’t - // want this + // want this if (!hardcoded && !result.equals(storedBackupResult)) { mXmppConnectionService.databaseBackend.saveResolverResult( domain, result); @@ -615,24 +617,9 @@ public class XmppConnection implements Runnable { throw new StateChangingException(Account.State.UNAUTHORIZED); } tagWriter.writeElement(response); - } else if (nextTag.isStart("enabled")) { + } else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) { final Element enabled = tagReader.readElement(nextTag); - if (enabled.getAttributeAsBoolean("resume")) { - this.streamId = enabled.getAttribute("id"); - Log.d( - Config.LOGTAG, - account.getJid().asBareJid().toString() - + ": stream management enabled (resumable)"); - } else { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid().toString() - + ": stream management enabled"); - } - this.stanzasReceived = 0; - this.inSmacksSession = true; - final RequestPacket r = new RequestPacket(); - tagWriter.writeStanzaAsync(r); + processEnabled(enabled); } else if (nextTag.isStart("resumed")) { final Element resumed = tagReader.readElement(nextTag); processResumed(resumed); @@ -771,13 +758,31 @@ public class XmppConnection implements Runnable { + ": jid changed during SASL 2.0. updating database"); mXmppConnectionService.databaseBackend.updateAccount(account); } + final Element bound = success.findChild("bound", Namespace.BIND2); final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3"); final Element failed = success.findChild("failed", "urn:xmpp:sm:3"); + // TODO check if resumed and bound exist and throw bind failure if (resumed != null && streamId != null) { processResumed(resumed); } else if (failed != null) { processFailed(failed, false); // wait for new stream features } + if (bound != null) { + this.isBound = true; + final Element streamManagementEnabled = + bound.findChild("enabled", Namespace.STREAM_MANAGEMENT); + final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS); + if (streamManagementEnabled != null) { + processEnabled(streamManagementEnabled); + } + if (carbonsEnabled != null) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": successfully enabled carbons"); + features.carbonsEnabled = true; + } + sendPostBindInitialization(streamManagementEnabled != null, carbonsEnabled != null); + } } if (version == SaslMechanism.Version.SASL) { tagReader.reset(); @@ -794,6 +799,27 @@ public class XmppConnection implements Runnable { } } + private void processEnabled(final Element enabled) { + final String streamId; + if (enabled.getAttributeAsBoolean("resume")) { + streamId = enabled.getAttribute("id"); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": stream management enabled (resumable)"); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + ": stream management enabled"); + streamId = null; + } + this.streamId = streamId; + this.stanzasReceived = 0; + this.inSmacksSession = true; + final RequestPacket r = new RequestPacket(); + // tagWriter.writeStanzaAsync(r); + } + private void processResumed(final Element resumed) throws StateChangingException { this.inSmacksSession = true; this.isBound = true; @@ -1241,6 +1267,16 @@ public class XmppConnection implements Runnable { final boolean inlineStreamManagement = inline != null && inline.hasChild("sm", "urn:xmpp:sm:3"); final boolean inlineBind2 = inline != null && inline.hasChild("bind", Namespace.BIND2); + final Element inlineBindFeatures = + this.streamFeatures.findChild("inline", Namespace.BIND2); + if (inlineBind2 && inlineBindFeatures != null) { + final Element bind = + generateBindRequest( + Collections2.transform( + inlineBindFeatures.getChildren(), + c -> c == null ? null : c.getAttribute("var"))); + authenticate.addChild(bind); + } if (inlineStreamManagement && streamId != null) { final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived); this.mSmCatchupMessageCounter.set(0); @@ -1259,9 +1295,26 @@ public class XmppConnection implements Runnable { + "/" + saslMechanism.getMechanism()); authenticate.setAttribute("mechanism", saslMechanism.getMechanism()); + Log.d(Config.LOGTAG, "authenticate " + authenticate); tagWriter.writeElement(authenticate); } + private Element generateBindRequest(final Collection bindFeatures) { + Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures); + final Element bind = new Element("bind", Namespace.BIND2); + final Element clientId = bind.addChild("client-id"); + clientId.setAttribute("tag", mXmppConnectionService.getString(R.string.app_name)); + clientId.setContent(account.getUuid()); + final Element features = bind.addChild("features"); + if (bindFeatures.contains(Namespace.CARBONS)) { + features.addChild("enable", Namespace.CARBONS); + } + if (bindFeatures.contains(Namespace.STREAM_MANAGEMENT)) { + features.addChild("enable", Namespace.STREAM_MANAGEMENT); + } + return bind; + } + private static List extractMechanisms(final Element stream) { final ArrayList mechanisms = new ArrayList<>(stream.getChildren().size()); for (final Element child : stream.getChildren()) { @@ -1469,7 +1522,8 @@ public class XmppConnection implements Runnable { .hasChild("optional")) { sendStartSession(); } else { - sendPostBindInitialization(); + final boolean waitForDisco = enableStreamManagement(); + sendPostBindInitialization(waitForDisco, false); } return; } catch (final IllegalArgumentException e) { @@ -1565,7 +1619,8 @@ public class XmppConnection implements Runnable { startSession, (account, packet) -> { if (packet.getType() == IqPacket.TYPE.RESULT) { - sendPostBindInitialization(); + final boolean waitForDisco = enableStreamManagement(); + sendPostBindInitialization(waitForDisco, false); } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { throw new StateChangingError(Account.State.SESSION_FAILURE); } @@ -1573,7 +1628,7 @@ public class XmppConnection implements Runnable { true); } - private void sendPostBindInitialization() { + private boolean enableStreamManagement() { final boolean streamManagement = this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT); if (streamManagement) { @@ -1583,15 +1638,22 @@ public class XmppConnection implements Runnable { stanzasSent = 0; mStanzaQueue.clear(); } + return true; + } else { + return false; } - features.carbonsEnabled = false; + } + + private void sendPostBindInitialization( + final boolean waitForDisco, final boolean carbonsEnabled) { + features.carbonsEnabled = carbonsEnabled; features.blockListRequested = false; synchronized (this.disco) { this.disco.clear(); } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": starting service discovery"); mPendingServiceDiscoveries.set(0); - if (!streamManagement + if (!waitForDisco || Patches.DISCO_EXCEPTIONS.contains( account.getJid().getDomain().toEscapedString())) { Log.d( @@ -1819,11 +1881,11 @@ public class XmppConnection implements Runnable { private void sendEnableCarbons() { final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); - iq.addChild("enable", "urn:xmpp:carbons:2"); + iq.addChild("enable", Namespace.CARBONS); this.sendIqPacket( iq, (account, packet) -> { - if (!packet.hasChild("error")) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": successfully enabled carbons"); @@ -2309,7 +2371,7 @@ public class XmppConnection implements Runnable { } public boolean carbons() { - return hasDiscoFeature(account.getDomain(), "urn:xmpp:carbons:2"); + return hasDiscoFeature(account.getDomain(), Namespace.CARBONS); } public boolean commands() {