Rework XmppConnection and DNS Resolver

(cherry picked from commit 1332036ec0)
This commit is contained in:
Arne 2024-05-03 08:51:39 +02:00
parent 57d8ff2539
commit eef175853d
3 changed files with 437 additions and 376 deletions

View file

@ -20,13 +20,13 @@
"filters": [ "filters": [
{ {
"filterType": "ABI", "filterType": "ABI",
"value": "armeabi-v7a" "value": "x86_64"
} }
], ],
"attributes": [], "attributes": [],
"versionCode": 17201, "versionCode": 17203,
"versionName": "1.7.9.6", "versionName": "1.7.9.6",
"outputFile": "monocles chat-1.7.9.6-git-armeabi-v7a-release.apk" "outputFile": "monocles chat-1.7.9.6-git-x86_64-release.apk"
}, },
{ {
"type": "ONE_OF_MANY", "type": "ONE_OF_MANY",
@ -41,19 +41,6 @@
"versionName": "1.7.9.6", "versionName": "1.7.9.6",
"outputFile": "monocles chat-1.7.9.6-git-x86-release.apk" "outputFile": "monocles chat-1.7.9.6-git-x86-release.apk"
}, },
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 17203,
"versionName": "1.7.9.6",
"outputFile": "monocles chat-1.7.9.6-git-x86_64-release.apk"
},
{ {
"type": "ONE_OF_MANY", "type": "ONE_OF_MANY",
"filters": [ "filters": [
@ -66,6 +53,19 @@
"versionCode": 17204, "versionCode": 17204,
"versionName": "1.7.9.6", "versionName": "1.7.9.6",
"outputFile": "monocles chat-1.7.9.6-git-arm64-v8a-release.apk" "outputFile": "monocles chat-1.7.9.6-git-arm64-v8a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 17201,
"versionName": "1.7.9.6",
"outputFile": "monocles chat-1.7.9.6-git-armeabi-v7a-release.apk"
} }
], ],
"elementType": "File" "elementType": "File"

View file

@ -6,13 +6,16 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull; import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Ints;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
@ -20,12 +23,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
//import de.gultsch.minidns.AndroidDNSClient;
import org.minidns.AbstractDnsClient; import org.minidns.AbstractDnsClient;
import org.minidns.DnsCache; import org.minidns.DnsCache;
import org.minidns.DnsClient; import org.minidns.DnsClient;
import org.minidns.cache.LruCache; import org.minidns.cache.LruCache;
import org.minidns.dnsmessage.Question; import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName; import org.minidns.dnsname.DnsName;
import org.minidns.dnssec.DnssecResultNotAuthenticException;
import org.minidns.dnssec.DnssecValidationFailedException; import org.minidns.dnssec.DnssecValidationFailedException;
import org.minidns.dnsserverlookup.AndroidUsingExec; import org.minidns.dnsserverlookup.AndroidUsingExec;
import org.minidns.hla.DnssecResolverApi; import org.minidns.hla.DnssecResolverApi;
@ -40,17 +45,10 @@ import org.minidns.record.InternetAddressRR;
import org.minidns.record.Record; import org.minidns.record.Record;
import org.minidns.record.SRV; import org.minidns.record.SRV;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.R;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Ints;
import com.google.common.base.Strings;
public class Resolver { public class Resolver {
public static final int DEFAULT_PORT_XMPP = 5222; public static final int DEFAULT_PORT_XMPP = 5222;
@ -61,114 +59,114 @@ public class Resolver {
private static XmppConnectionService SERVICE = null; private static XmppConnectionService SERVICE = null;
private static List<String> DNSSECLESS_TLDS = Arrays.asList( private static List<String> DNSSECLESS_TLDS = Arrays.asList(
"ae", "ae",
"aero", "aero",
"ai", "ai",
"al", "al",
"ao", "ao",
"aq", "aq",
"as", "as",
"ba", "ba",
"bb", "bb",
"bd", "bd",
"bf", "bf",
"bi", "bi",
"bj", "bj",
"bn", "bn",
"bo", "bo",
"bs", "bs",
"bw", "bw",
"cd", "cd",
"cf", "cf",
"cg", "cg",
"ci", "ci",
"ck", "ck",
"cm", "cm",
"cu", "cu",
"cv", "cv",
"cw", "cw",
"dj", "dj",
"dm", "dm",
"do", "do",
"ec", "ec",
"eg", "eg",
"eh", "eh",
"er", "er",
"et", "et",
"fj", "fj",
"fk", "fk",
"ga", "ga",
"ge", "ge",
"gf", "gf",
"gh", "gh",
"gm", "gm",
"gp", "gp",
"gq", "gq",
"gt", "gt",
"gu", "gu",
"hm", "hm",
"ht", "ht",
"im", "im",
"ir", "ir",
"je", "je",
"jm", "jm",
"jo", "jo",
"ke", "ke",
"kh", "kh",
"km", "km",
"kn", "kn",
"kp", "kp",
"kz", "kz",
"ls", "ls",
"mg", "mg",
"mh", "mh",
"mk", "mk",
"ml", "ml",
"mm", "mm",
"mo", "mo",
"mp", "mp",
"mq", "mq",
"ms", "ms",
"mt", "mt",
"mu", "mu",
"mv", "mv",
"mw", "mw",
"mz", "mz",
"ne", "ne",
"ng", "ng",
"ni", "ni",
"np", "np",
"nr", "nr",
"om", "om",
"pa", "pa",
"pf", "pf",
"pg", "pg",
"pk", "pk",
"pn", "pn",
"ps", "ps",
"py", "py",
"qa", "qa",
"rw", "rw",
"sd", "sd",
"sl", "sl",
"sm", "sm",
"so", "so",
"sr", "sr",
"sv", "sv",
"sy", "sy",
"sz", "sz",
"tc", "tc",
"td", "td",
"tg", "tg",
"tj", "tj",
"to", "to",
"tr", "tr",
"va", "va",
"vg", "vg",
"vi", "vi",
"ye", "ye",
"zm", "zm",
"zw" "zw"
); );
protected static final Map<String, String> knownSRV = ImmutableMap.of( protected static final Map<String, String> knownSRV = ImmutableMap.of(
@ -232,7 +230,7 @@ public class Resolver {
} }
public static List<Result> resolve(final String domain) { public static List<Result> resolve(final String domain) {
final List<Result> ipResults = fromIpAddress(domain, DEFAULT_PORT_XMPP); final List<Result> ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) { if (ipResults.size() > 0) {
return ipResults; return ipResults;
} }
@ -264,7 +262,7 @@ public class Resolver {
} }
}); });
threads[2] = new Thread(() -> { threads[2] = new Thread(() -> {
List<Result> list = resolveNoSrvRecords(DnsName.from(domain), DEFAULT_PORT_XMPP, true); List<Result> list = resolveNoSrvRecords(DnsName.from(domain), true);
synchronized (fallbackResults) { synchronized (fallbackResults) {
fallbackResults.addAll(list); fallbackResults.addAll(list);
} }
@ -298,14 +296,14 @@ public class Resolver {
} }
} }
private static List<Result> fromIpAddress(String domain, int port) { private static List<Result> fromIpAddress(String domain) {
if (!IP.matches(domain)) { if (!IP.matches(domain)) {
return Collections.emptyList(); return Collections.emptyList();
} }
try { try {
Result result = new Result(); Result result = new Result();
result.ip = InetAddress.getByName(domain); result.ip = InetAddress.getByName(domain);
result.port = port; result.port = DEFAULT_PORT_XMPP;
result.authenticated = true; result.authenticated = true;
return Collections.singletonList(result); return Collections.singletonList(result);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@ -372,25 +370,25 @@ public class Resolver {
return list; return list;
} }
private static List<Result> resolveNoSrvRecords(DnsName dnsName, int port, boolean withCnames) { private static List<Result> resolveNoSrvRecords(DnsName dnsName, boolean withCnames) {
final List<Result> results = new ArrayList<>(); final List<Result> results = new ArrayList<>();
try { try {
ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class); ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class);
for (A a : aResult.getAnswersOrEmptySet()) { for (A a : aResult.getAnswersOrEmptySet()) {
Result r = Result.createDefault(dnsName, a.getInetAddress(), port); Result r = Result.createDefault(dnsName, a.getInetAddress());
r.authenticated = aResult.isAuthenticData(); r.authenticated = aResult.isAuthenticData();
results.add(r); results.add(r);
} }
ResolverResult<AAAA> aaaaResult = resolveWithFallback(dnsName, AAAA.class); ResolverResult<AAAA> aaaaResult = resolveWithFallback(dnsName, AAAA.class);
for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) { for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) {
Result r = Result.createDefault(dnsName, aaaa.getInetAddress(), port); Result r = Result.createDefault(dnsName, aaaa.getInetAddress());
r.authenticated = aaaaResult.isAuthenticData(); r.authenticated = aaaaResult.isAuthenticData();
results.add(r); results.add(r);
} }
if (results.size() == 0 && withCnames) { if (results.size() == 0 && withCnames) {
ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class); ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class);
for (CNAME cname : cnameResult.getAnswersOrEmptySet()) { for (CNAME cname : cnameResult.getAnswersOrEmptySet()) {
for (Result r : resolveNoSrvRecords(cname.name, port, false)) { for (Result r : resolveNoSrvRecords(cname.name, false)) {
r.authenticated = r.authenticated && cnameResult.isAuthenticData(); r.authenticated = r.authenticated && cnameResult.isAuthenticData();
results.add(r); results.add(r);
} }
@ -401,7 +399,7 @@ public class Resolver {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
} }
} }
results.add(Result.createDefault(dnsName, port)); results.add(Result.createDefault(dnsName));
return results; return results;
} }
@ -441,10 +439,6 @@ public class Resolver {
private boolean directTls = false; private boolean directTls = false;
private boolean authenticated = false; private boolean authenticated = false;
private int priority; private int priority;
private long timeRequested;
private Socket socket;
private String logID = "";
static Result fromRecord(SRV srv, boolean directTls) { static Result fromRecord(SRV srv, boolean directTls) {
Result result = new Result(); Result result = new Result();
@ -455,18 +449,32 @@ public class Resolver {
return result; return result;
} }
static Result createDefault(DnsName hostname, InetAddress ip, int port) { static Result createDefault(DnsName hostname, InetAddress ip) {
Result result = new Result(); Result result = new Result();
result.timeRequested = System.currentTimeMillis(); result.port = DEFAULT_PORT_XMPP;
result.port = port;
result.hostname = hostname; result.hostname = hostname;
result.ip = ip; result.ip = ip;
result.directTls = useDirectTls(port);
return result; return result;
} }
static Result createDefault(DnsName hostname, int port) { static Result createDefault(DnsName hostname) {
return createDefault(hostname, null, port); return createDefault(hostname, null);
}
public static Result fromCursor(Cursor cursor) {
final Result result = new Result();
try {
result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
} catch (UnknownHostException e) {
result.ip = null;
}
final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME));
result.hostname = hostname == null ? null : DnsName.from(hostname);
result.port = cursor.getInt(cursor.getColumnIndex(PORT));
result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
return result;
} }
@Override @Override
@ -515,20 +523,11 @@ public class Resolver {
return authenticated; return authenticated;
} }
public boolean isOutdated() {
return (System.currentTimeMillis() - timeRequested) > 300_000;
}
public Socket getSocket() {
return socket;
}
@NotNull
@Override @Override
public String toString() { public String toString() {
return "Result{" + return "Result{" +
"ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' + "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
", hostname='" + (hostname == null ? null : hostname.toString()) + '\'' + ", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' +
", port=" + port + ", port=" + port +
", directTls=" + directTls + ", directTls=" + directTls +
", authenticated=" + authenticated + ", authenticated=" + authenticated +
@ -536,43 +535,6 @@ public class Resolver {
'}'; '}';
} }
public void connect() {
if (this.socket != null) {
this.disconnect();
}
if (this.ip == null || this.port == 0) {
Log.d(Config.LOGTAG, "Resolver did not get IP:port (" + this.ip + ":" + this.port + ")");
return;
}
final InetSocketAddress addr = new InetSocketAddress(this.ip, this.port);
this.socket = new Socket();
try {
long time = System.currentTimeMillis();
this.socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
time = System.currentTimeMillis() - time;
if (this.logID != null && !this.logID.isEmpty()) {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") connect: " + toString() + " after: " + time + " ms");
} else {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result connect: " + toString() + " after: " + time + " ms");
}
} catch (IOException e) {
e.printStackTrace();
this.disconnect();
}
}
public void disconnect() {
if (this.socket != null) {
FileBackend.close(this.socket);
this.socket = null;
if (this.logID != null && !this.logID.isEmpty()) {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") disconnect: " + toString());
} else {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result disconnect: " + toString());
}
}
}
@Override @Override
public int compareTo(@NonNull Result result) { public int compareTo(@NonNull Result result) {
if (result.priority == priority) { if (result.priority == priority) {
@ -596,31 +558,6 @@ public class Resolver {
} }
} }
public Result call() throws Exception {
this.connect();
if (this.socket != null && this.socket.isConnected()) {
return this;
}
throw new Exception("Resolver.Result was not possible to connect - should be catched by executor");
}
public static Result fromCursor(Cursor cursor) {
final Result result = new Result();
try {
result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
} catch (UnknownHostException e) {
result.ip = null;
}
final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME));
result.hostname = hostname == null ? null : DnsName.from(hostname);
result.port = cursor.getInt(cursor.getColumnIndex(PORT));
result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
result.timeRequested = cursor.getLong(cursor.getColumnIndex(TIME_REQUESTED));
return result;
}
public ContentValues toContentValues() { public ContentValues toContentValues() {
final ContentValues contentValues = new ContentValues(); final ContentValues contentValues = new ContentValues();
contentValues.put(IP, ip == null ? null : ip.getAddress()); contentValues.put(IP, ip == null ? null : ip.getAddress());
@ -629,7 +566,6 @@ public class Resolver {
contentValues.put(PRIORITY, priority); contentValues.put(PRIORITY, priority);
contentValues.put(DIRECT_TLS, directTls ? 1 : 0); contentValues.put(DIRECT_TLS, directTls ? 1 : 0);
contentValues.put(AUTHENTICATED, authenticated ? 1 : 0); contentValues.put(AUTHENTICATED, authenticated ? 1 : 0);
contentValues.put(TIME_REQUESTED, timeRequested);
return contentValues; return contentValues;
} }
@ -692,4 +628,5 @@ public class Resolver {
return result; return result;
} }
} }
} }

View file

@ -24,7 +24,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import eu.siacs.conversations.crypto.sasl.HashedToken;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -77,6 +77,7 @@ import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.sasl.ChannelBinding; import eu.siacs.conversations.crypto.sasl.ChannelBinding;
import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism; import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism;
import eu.siacs.conversations.crypto.sasl.HashedToken;
import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.crypto.sasl.SaslMechanism;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
@ -117,8 +118,50 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
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;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
public class XmppConnection implements Runnable { public class XmppConnection implements Runnable {
private static final int PACKET_IQ = 0; private static final int PACKET_IQ = 0;
@ -228,11 +271,8 @@ public class XmppConnection implements Runnable {
return mXmppConnectionService; return mXmppConnectionService;
} }
private void fixResource(Context context, Account account) { private static void fixResource(Context context, Account account) {
String resource = account.getResource(); String resource = account.getResource();
if (resource != null && !resource.startsWith(context.getString(R.string.app_name) + '[' + BuildConfig.VERSION_NAME + ']')) {
account.setResource(createNewResource());
}
int fixedPartLength = int fixedPartLength =
context.getString(R.string.app_name).length() + 1; // include the trailing dot context.getString(R.string.app_name).length() + 1; // include the trailing dot
int randomPartLength = 4; // 3 bytes int randomPartLength = 4; // 3 bytes
@ -320,7 +360,8 @@ public class XmppConnection implements Runnable {
mXmppConnectionService.resetSendingToWaiting(account); mXmppConnectionService.resetSendingToWaiting(account);
} }
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": connecting"); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": connecting");
features.encryptionEnabled = false; this.loginInfo = null;
this.features.encryptionEnabled = false;
this.inSmacksSession = false; this.inSmacksSession = false;
this.quickStartInProgress = false; this.quickStartInProgress = false;
this.isBound = false; this.isBound = false;
@ -361,6 +402,7 @@ public class XmppConnection implements Runnable {
destination = account.getHostname(); destination = account.getHostname();
this.verifiedHostname = destination; this.verifiedHostname = destination;
} }
final int port = account.getPort(); final int port = account.getPort();
final boolean directTls = Resolver.useDirectTls(port); final boolean directTls = Resolver.useDirectTls(port);
@ -452,7 +494,7 @@ public class XmppConnection implements Runnable {
} else { } else {
storedBackupResult = storedBackupResult =
mXmppConnectionService.databaseBackend.findResolverResult(domain); mXmppConnectionService.databaseBackend.findResolverResult(domain);
if (storedBackupResult != null && !results.contains(storedBackupResult) && !storedBackupResult.isOutdated()) { if (storedBackupResult != null && !results.contains(storedBackupResult)) {
results.add(storedBackupResult); results.add(storedBackupResult);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -464,12 +506,18 @@ public class XmppConnection implements Runnable {
final StreamId streamId = this.streamId; final StreamId streamId = this.streamId;
final Resolver.Result resumeLocation = streamId == null ? null : streamId.location; final Resolver.Result resumeLocation = streamId == null ? null : streamId.location;
if (resumeLocation != null) { if (resumeLocation != null) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected resume location on position 0"); Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": injected resume location on position 0");
results.add(0, resumeLocation); results.add(0, resumeLocation);
} }
final Resolver.Result seeOtherHost = this.seeOtherHostResolverResult; final Resolver.Result seeOtherHost = this.seeOtherHostResolverResult;
if (seeOtherHost != null) { if (seeOtherHost != null) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": injected see-other-host on position 0"); Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": injected see-other-host on position 0");
results.add(0, seeOtherHost); results.add(0, seeOtherHost);
} }
for (final Iterator<Resolver.Result> iterator = results.iterator(); for (final Iterator<Resolver.Result> iterator = results.iterator();
@ -611,8 +659,7 @@ public class XmppConnection implements Runnable {
tagReader.setInputStream(socket.getInputStream()); tagReader.setInputStream(socket.getInputStream());
tagWriter.beginDocument(); tagWriter.beginDocument();
final boolean quickStart; final boolean quickStart;
if (socket instanceof SSLSocket) { if (socket instanceof SSLSocket sslSocket) {
final SSLSocket sslSocket = (SSLSocket) socket;
SSLSockets.log(account, sslSocket); SSLSockets.log(account, sslSocket);
quickStart = establishStream(SSLSockets.version(sslSocket)); quickStart = establishStream(SSLSockets.version(sslSocket));
} else { } else {
@ -622,7 +669,16 @@ public class XmppConnection implements Runnable {
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
final boolean success = tag != null && tag.isStart("stream", Namespace.STREAMS); if (tag == null) {
return false;
}
final boolean success = tag.isStart("stream", Namespace.STREAMS);
if (success) {
final var from = tag.getAttribute("from");
if (from == null || !from.equals(account.getServer())) {
throw new StateChangingException(Account.State.HOST_UNKNOWN);
}
}
if (success && quickStart) { if (success && quickStart) {
this.quickStartInProgress = true; this.quickStartInProgress = true;
} }
@ -679,14 +735,18 @@ public class XmppConnection implements Runnable {
processStreamFeatures(nextTag); processStreamFeatures(nextTag);
} else if (nextTag.isStart("proceed", Namespace.TLS)) { } else if (nextTag.isStart("proceed", Namespace.TLS)) {
switchOverToTls(); switchOverToTls();
} else if (nextTag.isStart("failure", Namespace.TLS)) {
throw new StateChangingException(Account.State.TLS_ERROR);
} else if (account.isOptionSet(Account.OPTION_REGISTER)
&& nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
processIq(nextTag);
} else if (!isSecure() || this.loginInfo == null) {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} else if (nextTag.isStart("success")) { } else if (nextTag.isStart("success")) {
final Element success = tagReader.readElement(nextTag); final Element success = tagReader.readElement(nextTag);
if (processSuccess(success)) { if (processSuccess(success)) {
break; break;
} }
} else if (nextTag.isStart("failure", Namespace.TLS)) {
throw new StateChangingException(Account.State.TLS_ERROR);
} else if (nextTag.isStart("failure")) { } else if (nextTag.isStart("failure")) {
final Element failure = tagReader.readElement(nextTag); final Element failure = tagReader.readElement(nextTag);
processFailure(failure); processFailure(failure);
@ -694,24 +754,33 @@ public class XmppConnection implements Runnable {
// two step sasl2 - we dont support this yet // two step sasl2 - we dont support this yet
throw new StateChangingException(Account.State.INCOMPATIBLE_CLIENT); throw new StateChangingException(Account.State.INCOMPATIBLE_CLIENT);
} else if (nextTag.isStart("challenge")) { } else if (nextTag.isStart("challenge")) {
if (isSecure() && this.loginInfo != null) { final Element challenge = tagReader.readElement(nextTag);
final Element challenge = tagReader.readElement(nextTag); processChallenge(challenge);
processChallenge(challenge);
} else {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": received 'challenge on an unsecure connection");
throw new StateChangingException(Account.State.INCOMPATIBLE_CLIENT);
}
} else if (!LoginInfo.isSuccess(this.loginInfo)) { } else if (!LoginInfo.isSuccess(this.loginInfo)) {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} else if (this.streamId != null
&& nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) {
final Element resumed = tagReader.readElement(nextTag);
processResumed(resumed);
} else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) {
final Element failed = tagReader.readElement(nextTag);
processFailed(failed, true);
} else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
processIq(nextTag);
} else if (!isBound) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": server sent unexpected"
+ nextTag.identifier());
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} else if (nextTag.isStart("message", Namespace.JABBER_CLIENT)) {
processMessage(nextTag);
} else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) {
processPresence(nextTag);
} else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) { } else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) {
final Element enabled = tagReader.readElement(nextTag); final Element enabled = tagReader.readElement(nextTag);
processEnabled(enabled); processEnabled(enabled);
} else if (nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) {
final Element resumed = tagReader.readElement(nextTag);
processResumed(resumed);
} else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) { } else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) {
tagReader.readElement(nextTag); tagReader.readElement(nextTag);
if (Config.EXTENDED_SM_LOGGING) { if (Config.EXTENDED_SM_LOGGING) {
@ -766,15 +835,6 @@ public class XmppConnection implements Runnable {
if (acknowledgedMessages) { if (acknowledgedMessages) {
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();
} }
} else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) {
final Element failed = tagReader.readElement(nextTag);
processFailed(failed, true);
} else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
processIq(nextTag);
} else if (nextTag.isStart("message", Namespace.JABBER_CLIENT)) {
processMessage(nextTag);
} else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) {
processPresence(nextTag);
} else { } else {
Log.e( Log.e(
Config.LOGTAG, Config.LOGTAG,
@ -810,7 +870,9 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} }
try { try {
response.setContent(currentLoginInfo.saslMechanism.getResponse(challenge.getContent(), sslSocketOrNull(socket))); response.setContent(
currentLoginInfo.saslMechanism.getResponse(
challenge.getContent(), sslSocketOrNull(socket)));
} catch (final SaslMechanism.AuthenticationException e) { } catch (final SaslMechanism.AuthenticationException e) {
// TODO: Send auth abort tag. // TODO: Send auth abort tag.
Log.e(Config.LOGTAG, e.toString()); Log.e(Config.LOGTAG, e.toString());
@ -905,7 +967,10 @@ public class XmppConnection implements Runnable {
if (resumed != null && streamId != null) { if (resumed != null && streamId != null) {
if (this.boundStreamFeatures != null) { if (this.boundStreamFeatures != null) {
this.streamFeatures = this.boundStreamFeatures; this.streamFeatures = this.boundStreamFeatures;
Log.d(Config.LOGTAG, "putting previous stream features back in place: " + XmlHelper.printElementNames(this.boundStreamFeatures)); Log.d(
Config.LOGTAG,
"putting previous stream features back in place: "
+ XmlHelper.printElementNames(this.boundStreamFeatures));
} }
processResumed(resumed); processResumed(resumed);
} else if (failed != null) { } else if (failed != null) {
@ -925,7 +990,7 @@ public class XmppConnection implements Runnable {
processEnabled(streamManagementEnabled); processEnabled(streamManagementEnabled);
waitForDisco = true; waitForDisco = true;
} else { } else {
//if we did not enable stream management in bind do it now // if we did not enable stream management in bind do it now
waitForDisco = enableStreamManagement(); waitForDisco = enableStreamManagement();
} }
final boolean negotiatedCarbons; final boolean negotiatedCarbons;
@ -957,13 +1022,22 @@ public class XmppConnection implements Runnable {
tokenMechanism = null; tokenMechanism = null;
} }
if (tokenMechanism != null && !Strings.isNullOrEmpty(token)) { if (tokenMechanism != null && !Strings.isNullOrEmpty(token)) {
if (ChannelBinding.priority(tokenMechanism.channelBinding) >= ChannelBindingMechanism.getPriority(currentSaslMechanism)) { if (ChannelBinding.priority(tokenMechanism.channelBinding)
>= ChannelBindingMechanism.getPriority(currentSaslMechanism)) {
this.account.setFastToken(tokenMechanism, token); this.account.setFastToken(tokenMechanism, token);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": storing hashed token " + tokenMechanism); account.getJid().asBareJid()
+ ": storing hashed token "
+ tokenMechanism);
} else { } else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": not accepting hashed token "+ tokenMechanism.name()+" for log in mechanism "+currentSaslMechanism.getMechanism()); Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": not accepting hashed token "
+ tokenMechanism.name()
+ " for log in mechanism "
+ currentSaslMechanism.getMechanism());
this.account.resetFastToken(); this.account.resetFastToken();
} }
} else if (this.hashTokenRequest != null) { } else if (this.hashTokenRequest != null) {
@ -990,6 +1064,7 @@ public class XmppConnection implements Runnable {
return false; return false;
} }
} }
private void resetOutboundStanzaQueue() { private void resetOutboundStanzaQueue() {
synchronized (this.mStanzaQueue) { synchronized (this.mStanzaQueue) {
final ImmutableList.Builder<AbstractAcknowledgeableStanza> intermediateStanzasBuilder = final ImmutableList.Builder<AbstractAcknowledgeableStanza> intermediateStanzasBuilder =
@ -1042,7 +1117,6 @@ public class XmppConnection implements Runnable {
} }
} }
private void processFailure(final Element failure) throws IOException { private void processFailure(final Element failure) throws IOException {
final SaslMechanism.Version version; final SaslMechanism.Version version;
try { try {
@ -1118,7 +1192,9 @@ public class XmppConnection implements Runnable {
} else { } else {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": stream management enabled. resume at: " + streamId.location); account.getJid().asBareJid()
+ ": stream management enabled. resume at: "
+ streamId.location);
} }
this.streamId = streamId; this.streamId = streamId;
this.stanzasReceived = 0; this.stanzasReceived = 0;
@ -1164,8 +1240,7 @@ public class XmppConnection implements Runnable {
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas"); account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas");
for (final AbstractAcknowledgeableStanza packet : failedStanzas) { for (final AbstractAcknowledgeableStanza packet : failedStanzas) {
if (packet instanceof MessagePacket) { if (packet instanceof MessagePacket message) {
MessagePacket message = (MessagePacket) packet;
mXmppConnectionService.markMessage( mXmppConnectionService.markMessage(
account, account,
message.getTo().asBareJid(), message.getTo().asBareJid(),
@ -1237,8 +1312,7 @@ public class XmppConnection implements Runnable {
+ mStanzaQueue.keyAt(i)); + mStanzaQueue.keyAt(i));
} }
final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
if (stanza instanceof MessagePacket && acknowledgedListener != null) { if (stanza instanceof MessagePacket packet && acknowledgedListener != null) {
final MessagePacket packet = (MessagePacket) stanza;
final String id = packet.getId(); final String id = packet.getId();
final Jid to = packet.getTo(); final Jid to = packet.getTo();
if (id != null && to != null) { if (id != null && to != null) {
@ -1255,20 +1329,13 @@ public class XmppConnection implements Runnable {
private @NonNull Element processPacket(final Tag currentTag, final int packetType) private @NonNull Element processPacket(final Tag currentTag, final int packetType)
throws IOException { throws IOException {
final Element element; final Element element =
switch (packetType) { switch (packetType) {
case PACKET_IQ: case PACKET_IQ -> new IqPacket();
element = new IqPacket(); case PACKET_MESSAGE -> new MessagePacket();
break; case PACKET_PRESENCE -> new PresencePacket();
case PACKET_MESSAGE: default -> throw new AssertionError("Should never encounter invalid type");
element = new MessagePacket(); };
break;
case PACKET_PRESENCE:
element = new PresencePacket();
break;
default:
throw new AssertionError("Should never encounter invalid type");
}
element.setAttributes(currentTag.getAttributes()); element.setAttributes(currentTag.getAttributes());
Tag nextTag = tagReader.readTag(); Tag nextTag = tagReader.readTag();
if (nextTag == null) { if (nextTag == null) {
@ -1312,7 +1379,6 @@ public class XmppConnection implements Runnable {
private void processIq(final Tag currentTag) throws IOException { private void processIq(final Tag currentTag) throws IOException {
final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
if (!packet.valid()) { if (!packet.valid()) {
Log.e( Log.e(
Config.LOGTAG, Config.LOGTAG,
@ -1323,58 +1389,77 @@ public class XmppConnection implements Runnable {
+ "'"); + "'");
return; return;
} }
if (Thread.currentThread().isInterrupted()) {
if (packet instanceof JinglePacket) { Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + "Not processing iq. Thread was interrupted");
return;
}
if (packet instanceof JinglePacket jinglePacket && isBound) {
if (this.jingleListener != null) { if (this.jingleListener != null) {
this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet); this.jingleListener.onJinglePacketReceived(account, jinglePacket);
} }
} else { } else {
OnIqPacketReceived callback = null; final var callback = getIqPacketReceivedCallback(packet);
synchronized (this.packetCallbacks) { if (callback == null) {
final Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>> packetCallbackDuple = Log.d(
packetCallbacks.get(packet.getId()); Config.LOGTAG,
if (packetCallbackDuple != null) { account.getJid().asBareJid().toString()
ScheduledFuture timeoutFuture = packetCallbackDuple.second.second; + ": no callback registered for IQ from "
// Packets to the server should have responses from the server + packet.getFrom());
if (packetCallbackDuple.first.toServer(account)) { return;
if (packet.fromServer(account)) {
if (timeoutFuture == null || timeoutFuture.cancel(false)) {
callback = packetCallbackDuple.second.first;
}
packetCallbacks.remove(packet.getId());
} else {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": ignoring spoofed iq packet");
}
} else {
if (packet.getFrom() != null
&& packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
if (timeoutFuture == null || timeoutFuture.cancel(false)) {
callback = packetCallbackDuple.second.first;
}
packetCallbacks.remove(packet.getId());
} else {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": ignoring spoofed iq packet");
}
}
} else if (packet.getType() == IqPacket.TYPE.GET
|| packet.getType() == IqPacket.TYPE.SET) {
callback = this.unregisteredIqListener;
}
} }
if (callback != null) { final ScheduledFuture timeoutFuture = callback.second;
try { try {
callback.onIqPacketReceived(account, packet); if (timeoutFuture == null || timeoutFuture.cancel(false)) {
} catch (StateChangingError error) { callback.first.onIqPacketReceived(account, packet);
throw new StateChangingException(error.state); }
} catch (final StateChangingError error) {
throw new StateChangingException(error.state);
}
}
}
private Pair<OnIqPacketReceived, ScheduledFuture> getIqPacketReceivedCallback(final IqPacket stanza)
throws StateChangingException {
final boolean isRequest =
stanza.getType() == IqPacket.TYPE.GET || stanza.getType() == IqPacket.TYPE.SET;
if (isRequest) {
if (isBound) {
return new Pair<>(this.unregisteredIqListener, null);
} else {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
} else {
synchronized (this.packetCallbacks) {
final var pair = packetCallbacks.get(stanza.getId());
if (pair == null) {
return null;
}
if (pair.first.toServer(account)) {
if (stanza.fromServer(account)) {
packetCallbacks.remove(stanza.getId());
return pair.second;
} else {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": ignoring spoofed iq packet");
}
} else {
if (stanza.getFrom() != null && stanza.getFrom().equals(pair.first.getTo())) {
packetCallbacks.remove(stanza.getId());
return pair.second;
} else {
Log.e(
Config.LOGTAG,
account.getJid().asBareJid().toString()
+ ": ignoring spoofed iq packet");
}
} }
} }
} }
return null;
} }
private void processMessage(final Tag currentTag) throws IOException { private void processMessage(final Tag currentTag) throws IOException {
@ -1389,11 +1474,18 @@ public class XmppConnection implements Runnable {
+ "'"); + "'");
return; return;
} }
if (Thread.currentThread().isInterrupted()) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ "Not processing message. Thread was interrupted");
return;
}
this.messageListener.onMessagePacketReceived(account, packet); this.messageListener.onMessagePacketReceived(account, packet);
} }
private void processPresence(final Tag currentTag) throws IOException { private void processPresence(final Tag currentTag) throws IOException {
PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); final PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
if (!packet.valid()) { if (!packet.valid()) {
Log.e( Log.e(
Config.LOGTAG, Config.LOGTAG,
@ -1404,6 +1496,13 @@ public class XmppConnection implements Runnable {
+ "'"); + "'");
return; return;
} }
if (Thread.currentThread().isInterrupted()) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ "Not processing presence. Thread was interrupted");
return;
}
this.presenceListener.onPresencePacketReceived(account, packet); this.presenceListener.onPresencePacketReceived(account, packet);
} }
@ -1455,7 +1554,7 @@ public class XmppConnection implements Runnable {
this.dane = false; this.dane = false;
final SSLSocketFactory sslSocketFactory; final SSLSocketFactory sslSocketFactory;
try { try {
sslSocketFactory = getSSLSocketFactory(account.getPort(), (d) -> this.dane = d); sslSocketFactory = getSSLSocketFactory(socket.getPort(), (d) -> this.dane = d);
} catch (final NoSuchAlgorithmException | KeyManagementException e) { } catch (final NoSuchAlgorithmException | KeyManagementException e) {
throw new StateChangingException(Account.State.TLS_ERROR); throw new StateChangingException(Account.State.TLS_ERROR);
} }
@ -1463,7 +1562,7 @@ public class XmppConnection implements Runnable {
final SSLSocket sslSocket = final SSLSocket sslSocket =
(SSLSocket) (SSLSocket)
sslSocketFactory.createSocket( sslSocketFactory.createSocket(
socket, address.getHostAddress(), account.getPort(), true); socket, address.getHostAddress(), socket.getPort(), true);
SSLSockets.setSecurity(sslSocket); SSLSockets.setSecurity(sslSocket);
SSLSockets.setHostname(sslSocket, IDN.toASCII(account.getServer())); SSLSockets.setHostname(sslSocket, IDN.toASCII(account.getServer()));
SSLSockets.setApplicationProtocol(sslSocket, "xmpp-client"); SSLSockets.setApplicationProtocol(sslSocket, "xmpp-client");
@ -1592,11 +1691,11 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} }
} }
private boolean isSecure() { private boolean isSecure() {
return features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion() || account.isI2P(); return features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion() || account.isI2P();
} }
private void authenticate(final SaslMechanism.Version version) throws IOException { private void authenticate(final SaslMechanism.Version version) throws IOException {
final Element authElement; final Element authElement;
if (version == SaslMechanism.Version.SASL) { if (version == SaslMechanism.Version.SASL) {
@ -1609,10 +1708,12 @@ public class XmppConnection implements Runnable {
this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING); this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING);
final Collection<ChannelBinding> channelBindings = ChannelBinding.of(cbElement); final Collection<ChannelBinding> channelBindings = ChannelBinding.of(cbElement);
final SaslMechanism.Factory factory = new SaslMechanism.Factory(account); final SaslMechanism.Factory factory = new SaslMechanism.Factory(account);
final SaslMechanism saslMechanism = factory.of(mechanisms, channelBindings, version, SSLSockets.version(this.socket)); final SaslMechanism saslMechanism =
factory.of(mechanisms, channelBindings, version, SSLSockets.version(this.socket));
this.validate(saslMechanism, mechanisms); this.validate(saslMechanism, mechanisms);
final boolean quickStartAvailable; final boolean quickStartAvailable;
final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)); final String firstMessage =
saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket));
final boolean usingFast = SaslMechanism.hashedToken(saslMechanism); final boolean usingFast = SaslMechanism.hashedToken(saslMechanism);
final Element authenticate; final Element authenticate;
if (version == SaslMechanism.Version.SASL) { if (version == SaslMechanism.Version.SASL) {
@ -1621,7 +1722,7 @@ public class XmppConnection implements Runnable {
authenticate.setContent(firstMessage); authenticate.setContent(firstMessage);
} }
quickStartAvailable = false; quickStartAvailable = false;
this.loginInfo = new LoginInfo(saslMechanism,version,Collections.emptyList()); this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList());
} else if (version == SaslMechanism.Version.SASL_2) { } else if (version == SaslMechanism.Version.SASL_2) {
final Element inline = authElement.findChild("inline", Namespace.SASL_2); final Element inline = authElement.findChild("inline", Namespace.SASL_2);
final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT); final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT);
@ -1629,7 +1730,8 @@ public class XmppConnection implements Runnable {
if (usingFast) { if (usingFast) {
hashTokenRequest = null; hashTokenRequest = null;
} else { } else {
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST); final Element fast =
inline == null ? null : inline.findChild("fast", Namespace.FAST);
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast); final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
hashTokenRequest = hashTokenRequest =
HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
@ -1650,9 +1752,11 @@ public class XmppConnection implements Runnable {
return; return;
} }
} }
this.loginInfo = new LoginInfo(saslMechanism,version,bindFeatures); this.loginInfo = new LoginInfo(saslMechanism, version, bindFeatures);
this.hashTokenRequest = hashTokenRequest; this.hashTokenRequest = hashTokenRequest;
authenticate = generateAuthenticationRequest(firstMessage, usingFast, hashTokenRequest, bindFeatures, sm); authenticate =
generateAuthenticationRequest(
firstMessage, usingFast, hashTokenRequest, bindFeatures, sm);
} else { } else {
throw new AssertionError("Missing implementation for " + version); throw new AssertionError("Missing implementation for " + version);
} }
@ -1669,7 +1773,6 @@ public class XmppConnection implements Runnable {
+ "/" + "/"
+ LoginInfo.mechanism(this.loginInfo).getMechanism()); + LoginInfo.mechanism(this.loginInfo).getMechanism());
authenticate.setAttribute("mechanism", LoginInfo.mechanism(this.loginInfo).getMechanism()); authenticate.setAttribute("mechanism", LoginInfo.mechanism(this.loginInfo).getMechanism());
synchronized (this.mStanzaQueue) { synchronized (this.mStanzaQueue) {
this.stanzasSentBeforeAuthentication = this.stanzasSent; this.stanzasSentBeforeAuthentication = this.stanzasSent;
tagWriter.writeElement(authenticate); tagWriter.writeElement(authenticate);
@ -1681,7 +1784,9 @@ public class XmppConnection implements Runnable {
return inline != null && inline.hasChild("fast", Namespace.FAST); return inline != null && inline.hasChild("fast", Namespace.FAST);
} }
private void validate(final @Nullable SaslMechanism saslMechanism, Collection<String> mechanisms) throws StateChangingException { private void validate(
final @Nullable SaslMechanism saslMechanism, Collection<String> mechanisms)
throws StateChangingException {
if (saslMechanism == null) { if (saslMechanism == null) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -1708,8 +1813,10 @@ public class XmppConnection implements Runnable {
} }
} }
private Element generateAuthenticationRequest(final String firstMessage, final boolean usingFast) { private Element generateAuthenticationRequest(
return generateAuthenticationRequest(firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); final String firstMessage, final boolean usingFast) {
return generateAuthenticationRequest(
firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
} }
private Element generateAuthenticationRequest( private Element generateAuthenticationRequest(
@ -1854,6 +1961,7 @@ public class XmppConnection implements Runnable {
is = null; is = null;
} }
} }
if (is != null) { if (is != null) {
Bitmap captcha = BitmapFactory.decodeStream(is); Bitmap captcha = BitmapFactory.decodeStream(is);
try { try {
@ -1918,8 +2026,10 @@ public class XmppConnection implements Runnable {
resetAttemptCount(true); resetAttemptCount(true);
resetStreamId(); resetStreamId();
clearIqCallbacks(); clearIqCallbacks();
this.stanzasSent = 0; synchronized (this.mStanzaQueue) {
mStanzaQueue.clear(); this.stanzasSent = 0;
this.mStanzaQueue.clear();
}
this.redirectionUrl = null; this.redirectionUrl = null;
synchronized (this.disco) { synchronized (this.disco) {
disco.clear(); disco.clear();
@ -2447,19 +2557,25 @@ public class XmppConnection implements Runnable {
} else if (streamError.hasChild("policy-violation")) { } else if (streamError.hasChild("policy-violation")) {
this.lastConnect = SystemClock.elapsedRealtime(); this.lastConnect = SystemClock.elapsedRealtime();
final String text = streamError.findChildContent("text"); final String text = streamError.findChildContent("text");
if (text != null) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": policy violation. " + text); failPendingMessages(text);
failPendingMessages(text);
}
throw new StateChangingException(Account.State.POLICY_VIOLATION); throw new StateChangingException(Account.State.POLICY_VIOLATION);
} else if (streamError.hasChild("see-other-host")) { } else if (streamError.hasChild("see-other-host")) {
final String seeOtherHost = streamError.findChildContent("see-other-host"); final String seeOtherHost = streamError.findChildContent("see-other-host");
final Resolver.Result currentResolverResult = this.currentResolverResult; final Resolver.Result currentResolverResult = this.currentResolverResult;
if (Strings.isNullOrEmpty(seeOtherHost) || currentResolverResult == null) { if (Strings.isNullOrEmpty(seeOtherHost) || currentResolverResult == null) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream error " + streamError); Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": stream error " + streamError);
throw new StateChangingException(Account.State.STREAM_ERROR); throw new StateChangingException(Account.State.STREAM_ERROR);
} }
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": see other host: "+seeOtherHost+" "+currentResolverResult); Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": see other host: "
+ seeOtherHost
+ " "
+ currentResolverResult);
final Resolver.Result seeOtherResult = currentResolverResult.seeOtherHost(seeOtherHost); final Resolver.Result seeOtherResult = currentResolverResult.seeOtherHost(seeOtherHost);
if (seeOtherResult != null) { if (seeOtherResult != null) {
this.seeOtherHostResolverResult = seeOtherResult; this.seeOtherHostResolverResult = seeOtherResult;
@ -2477,8 +2593,7 @@ public class XmppConnection implements Runnable {
synchronized (this.mStanzaQueue) { synchronized (this.mStanzaQueue) {
for (int i = 0; i < mStanzaQueue.size(); ++i) { for (int i = 0; i < mStanzaQueue.size(); ++i) {
final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
if (stanza instanceof MessagePacket) { if (stanza instanceof MessagePacket packet) {
final MessagePacket packet = (MessagePacket) stanza;
final String id = packet.getId(); final String id = packet.getId();
final Jid to = packet.getTo(); final Jid to = packet.getTo();
mXmppConnectionService.markMessage( mXmppConnectionService.markMessage(
@ -2493,7 +2608,8 @@ public class XmppConnection implements Runnable {
final boolean secureConnection = sslVersion != SSLSockets.Version.NONE; final boolean secureConnection = sslVersion != SSLSockets.Version.NONE;
final SaslMechanism quickStartMechanism; final SaslMechanism quickStartMechanism;
if (secureConnection) { if (secureConnection) {
quickStartMechanism = SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion); quickStartMechanism =
SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion);
} else { } else {
quickStartMechanism = null; quickStartMechanism = null;
} }
@ -2502,10 +2618,16 @@ public class XmppConnection implements Runnable {
&& quickStartMechanism != null && quickStartMechanism != null
&& account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) { && account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) {
mXmppConnectionService.restoredFromDatabaseLatch.await(); mXmppConnectionService.restoredFromDatabaseLatch.await();
this.loginInfo = new LoginInfo(quickStartMechanism, SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); this.loginInfo =
new LoginInfo(
quickStartMechanism,
SaslMechanism.Version.SASL_2,
Bind2.QUICKSTART_FEATURES);
final boolean usingFast = quickStartMechanism instanceof HashedToken; final boolean usingFast = quickStartMechanism instanceof HashedToken;
final Element authenticate = final Element authenticate =
generateAuthenticationRequest(quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); generateAuthenticationRequest(
quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)),
usingFast);
authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism()); authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism());
sendStartStream(true, false); sendStartStream(true, false);
synchronized (this.mStanzaQueue) { synchronized (this.mStanzaQueue) {
@ -2614,17 +2736,23 @@ public class XmppConnection implements Runnable {
+ " do not write stanza to unbound stream " + " do not write stanza to unbound stream "
+ packet.toString()); + packet.toString());
} }
if (packet instanceof AbstractAcknowledgeableStanza) { if (packet instanceof AbstractAcknowledgeableStanza stanza) {
AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet;
if (this.mStanzaQueue.size() != 0) { if (this.mStanzaQueue.size() != 0) {
int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1); int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1);
if (currentHighestKey != stanzasSent) { if (currentHighestKey != stanzasSent) {
throw new AssertionError("Stanza count messed up"); throw new AssertionError("Stanza count messed up");
} }
} }
++stanzasSent; ++stanzasSent;
if (Config.EXTENDED_SM_LOGGING) { if (Config.EXTENDED_SM_LOGGING) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": counting outbound "+packet.getName()+" as #" + stanzasSent); Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": counting outbound "
+ packet.getName()
+ " as #"
+ stanzasSent);
} }
this.mStanzaQueue.append(stanzasSent, stanza); this.mStanzaQueue.append(stanzasSent, stanza);
if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) { if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) {
@ -2748,7 +2876,7 @@ public class XmppConnection implements Runnable {
this.boundStreamFeatures = null; this.boundStreamFeatures = null;
} }
public List<Entry<Jid, ServiceDiscoveryResult>> findDiscoItemsByFeature(final String feature) { private List<Entry<Jid, ServiceDiscoveryResult>> findDiscoItemsByFeature(final String feature) {
synchronized (this.disco) { synchronized (this.disco) {
final List<Entry<Jid, ServiceDiscoveryResult>> items = new ArrayList<>(); final List<Entry<Jid, ServiceDiscoveryResult>> items = new ArrayList<>();
for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) { for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) {
@ -2807,7 +2935,7 @@ public class XmppConnection implements Runnable {
public int getTimeToNextAttempt(final boolean aggressive) { public int getTimeToNextAttempt(final boolean aggressive) {
final int interval; final int interval;
if (aggressive) { if (aggressive) {
interval = Math.min((int) (3 * Math.pow(1.3,attempt)), 60); interval = Math.min((int) (3 * Math.pow(1.3, attempt)), 60);
} else { } else {
final int additionalTime = final int additionalTime =
account.getLastErrorStatus() == Account.State.POLICY_VIOLATION ? 3 : 0; account.getLastErrorStatus() == Account.State.POLICY_VIOLATION ? 3 : 0;
@ -2937,7 +3065,6 @@ public class XmppConnection implements Runnable {
return loginInfo == null ? null : loginInfo.saslMechanism; return loginInfo == null ? null : loginInfo.saslMechanism;
} }
public void success(final String challenge, final SSLSocket sslSocket) public void success(final String challenge, final SSLSocket sslSocket)
throws SaslMechanism.AuthenticationException { throws SaslMechanism.AuthenticationException {
final var response = this.saslMechanism.getResponse(challenge, sslSocket); final var response = this.saslMechanism.getResponse(challenge, sslSocket);
@ -3029,11 +3156,6 @@ public class XmppConnection implements Runnable {
&& pepPublishOptions(); && pepPublishOptions();
} }
public boolean avatarConversion() {
return hasDiscoFeature(account.getJid().asBareJid(), Namespace.AVATAR_CONVERSION)
&& pepPublishOptions();
}
public boolean blocking() { public boolean blocking() {
return hasDiscoFeature(account.getDomain(), Namespace.BLOCKING); return hasDiscoFeature(account.getDomain(), Namespace.BLOCKING);
} }
@ -3059,7 +3181,8 @@ public class XmppConnection implements Runnable {
public boolean sm() { public boolean sm() {
return streamId != null return streamId != null
|| (connection.streamFeatures != null || (connection.streamFeatures != null
&& connection.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT)); && connection.streamFeatures.hasChild(
"sm", Namespace.STREAM_MANAGEMENT));
} }
public boolean csi() { public boolean csi() {
@ -3180,7 +3303,8 @@ public class XmppConnection implements Runnable {
} }
public boolean bookmarks2() { public boolean bookmarks2() {
return pepPublishOptions() && hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT); return pepPublishOptions()
&& hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT);
} }
public boolean externalServiceDiscovery() { public boolean externalServiceDiscovery() {