From 5b2444ea139a4209920dac01505526971fbcd8ce Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 13 Oct 2023 08:29:23 +0200 Subject: [PATCH] implement see-other-host stream error --- .../siacs/conversations/entities/Account.java | 3 + .../java/eu/siacs/conversations/utils/IP.java | 12 ++++ .../siacs/conversations/utils/Resolver.java | 62 +++++++++++++++++++ .../conversations/xmpp/XmppConnection.java | 27 +++++++- .../xmpp/jingle/JingleRtpConnection.java | 4 +- src/main/res/values/strings.xml | 1 + 6 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index bfbe817cb..0fb748d88 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -787,6 +787,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable BIND_FAILURE, HOST_UNKNOWN, STREAM_ERROR, + SEE_OTHER_HOST, STREAM_OPENING_ERROR, POLICY_VIOLATION, PAYMENT_REQUIRED, @@ -874,6 +875,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable return R.string.account_status_stream_opening_error; case PAYMENT_REQUIRED: return R.string.payment_required; + case SEE_OTHER_HOST: + return R.string.reconnect_on_other_host; case MISSING_INTERNET_PERMISSION: return R.string.missing_internet_permission; case TEMPORARY_AUTH_FAILURE: diff --git a/src/main/java/eu/siacs/conversations/utils/IP.java b/src/main/java/eu/siacs/conversations/utils/IP.java index a7182e207..948f7537a 100644 --- a/src/main/java/eu/siacs/conversations/utils/IP.java +++ b/src/main/java/eu/siacs/conversations/utils/IP.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import com.google.common.net.InetAddresses; + import java.util.regex.Pattern; public class IP { @@ -27,4 +29,14 @@ public class IP { } } + public static String unwrapIPv6(final String host) { + if (host.length() > 2 && host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') { + final String ip = host.substring(1,host.length() -1); + if (InetAddresses.isInetAddress(ip)) { + return ip; + } + } + return host; + } + } diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index d6d1adaf0..915209413 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -6,7 +6,10 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.google.common.base.Strings; import com.google.common.base.Throwables; +import com.google.common.net.InetAddresses; +import com.google.common.primitives.Ints; import java.io.IOException; import java.lang.reflect.Field; @@ -446,6 +449,65 @@ public class Resolver { contentValues.put(AUTHENTICATED, authenticated ? 1 : 0); return contentValues; } + + public Result seeOtherHost(final String seeOtherHost) { + final String hostname = seeOtherHost.trim(); + if (hostname.isEmpty()) { + return null; + } + final Result result = new Result(); + result.directTls = this.directTls; + final int portSegmentStart = hostname.lastIndexOf(':'); + if (hostname.charAt(hostname.length() - 1) != ']' + && portSegmentStart >= 0 + && hostname.length() >= portSegmentStart + 1) { + final String hostPart = hostname.substring(0, portSegmentStart); + final String portPart = hostname.substring(portSegmentStart + 1); + final Integer port = Ints.tryParse(portPart); + if (port == null || Strings.isNullOrEmpty(hostPart)) { + return null; + } + final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostPart); + result.port = port; + if (InetAddresses.isInetAddress(host)) { + final InetAddress inetAddress; + try { + inetAddress = InetAddresses.forString(host); + } catch (final IllegalArgumentException e) { + return null; + } + result.ip = inetAddress; + } else { + if (hostPart.trim().isEmpty()) { + return null; + } + try { + result.hostname = DNSName.from(hostPart.trim()); + } catch (final Exception e) { + return null; + } + } + } else { + final String host = eu.siacs.conversations.utils.IP.unwrapIPv6(hostname); + if (InetAddresses.isInetAddress(host)) { + final InetAddress inetAddress; + try { + inetAddress = InetAddresses.forString(host); + } catch (final IllegalArgumentException e) { + return null; + } + result.ip = inetAddress; + } else { + try { + result.hostname = DNSName.from(hostname); + } catch (final Exception e) { + return null; + } + } + result.port = port; + } + return result; + } } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 93ab1ebf5..8a2b2e7cd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -189,6 +189,8 @@ public class XmppConnection implements Runnable { private HashedToken.Mechanism hashTokenRequest; private HttpUrl redirectionUrl = null; private String verifiedHostname = null; + private Resolver.Result currentResolverResult; + private Resolver.Result seeOtherHostResolverResult; private volatile Thread mThread; private CountDownLatch mStreamCountDownLatch; @@ -360,7 +362,12 @@ public class XmppConnection implements Runnable { + storedBackupResult); } } - for (Iterator iterator = results.iterator(); + final Resolver.Result seeOtherHost = this.seeOtherHostResolverResult; + if (seeOtherHost != null) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected see-other-host on position 0"); + results.add(0, seeOtherHost); + } + for (final Iterator iterator = results.iterator(); iterator.hasNext(); ) { final Resolver.Result result = iterator.next(); if (Thread.currentThread().isInterrupted()) { @@ -374,7 +381,6 @@ public class XmppConnection implements Runnable { features.encryptionEnabled = result.isDirectTls(); verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null; - Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname); final InetSocketAddress addr; if (result.getIp() != null) { addr = new InetSocketAddress(result.getIp(), result.getPort()); @@ -422,6 +428,8 @@ public class XmppConnection implements Runnable { mXmppConnectionService.databaseBackend.saveResolverResult( domain, result); } + this.currentResolverResult = result; + this.seeOtherHostResolverResult = null; break; // successfully connected to server that speaks xmpp } else { FileBackend.close(localSocket); @@ -2166,6 +2174,21 @@ public class XmppConnection implements Runnable { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text); failPendingMessages(text); throw new StateChangingException(Account.State.POLICY_VIOLATION); + } else if (streamError.hasChild("see-other-host")) { + final String seeOtherHost = streamError.findChildContent("see-other-host"); + final Resolver.Result currentResolverResult = this.currentResolverResult; + if (Strings.isNullOrEmpty(seeOtherHost) || currentResolverResult == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream error " + streamError); + throw new StateChangingException(Account.State.STREAM_ERROR); + } + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": see other host: "+seeOtherHost+" "+currentResolverResult); + final Resolver.Result seeOtherResult = currentResolverResult.seeOtherHost(seeOtherHost); + if (seeOtherResult != null) { + this.seeOtherHostResolverResult = seeOtherResult; + throw new StateChangingException(Account.State.SEE_OTHER_HOST); + } else { + throw new StateChangingException(Account.State.STREAM_ERROR); + } } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream error " + streamError); throw new StateChangingException(Account.State.STREAM_ERROR); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 2f26c7fc9..af6d219b0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -553,13 +553,13 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); return; } - processCandidates(receivedContentAccept.contents.entrySet()); - updateEndUserState(); Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": remote has accepted content-add " + ContentAddition.summary(receivedContentAccept)); + processCandidates(receivedContentAccept.contents.entrySet()); + updateEndUserState(); } private void receiveContentModify(final JinglePacket jinglePacket) { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4981128de..cb97f7afb 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -587,6 +587,7 @@ Web browser Console Payment required + Reconnect on other host Grant permission to use the Internet Me Contact asks for presence subscription