diff options
5 files changed, 369 insertions, 24 deletions
diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 243db470a..ba0f7927d 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -55,6 +55,7 @@ import de.pixart.messenger.entities.ServiceDiscoveryResult; import de.pixart.messenger.services.ShortcutService; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.FtsUtils; +import de.pixart.messenger.utils.Resolver; import de.pixart.messenger.xmpp.InvalidJid; import de.pixart.messenger.xmpp.mam.MamReference; import rocks.xmpp.addr.Jid; @@ -62,7 +63,7 @@ import rocks.xmpp.addr.Jid; public class DatabaseBackend extends SQLiteOpenHelper { public static final String DATABASE_NAME = "history"; - public static final int DATABASE_VERSION = 47; // = Conversations DATABASE_VERSION + 3 + public static final int DATABASE_VERSION = 48; // = Conversations DATABASE_VERSION + 4 private static DatabaseBackend instance = null; private static String CREATE_CONTATCS_STATEMENT = "create table " @@ -150,6 +151,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT IGNORE" + ");"; + private static String RESOLVER_RESULTS_TABLENAME = "resolver_results"; + private static String CREATE_RESOLVER_RESULTS_TABLE = "create table " + RESOLVER_RESULTS_TABLENAME + "(" + + Resolver.Result.DOMAIN + " TEXT," + + Resolver.Result.HOSTNAME + " TEXT," + + Resolver.Result.IP + " BLOB," + + Resolver.Result.PRIORITY + " NUMBER," + + Resolver.Result.DIRECT_TLS + " NUMBER," + + Resolver.Result.AUTHENTICATED + " NUMBER," + + Resolver.Result.PORT + " NUMBER," + + "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE" + + ");"; private static String CREATE_MESSAGE_TIME_INDEX = "create INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")"; private static String CREATE_MESSAGE_CONVERSATION_INDEX = "create INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")"; private static String CREATE_MESSAGE_DELETED_INDEX = "create index message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")"; @@ -247,6 +259,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); @@ -516,6 +529,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0"); } + if (oldVersion < 40 && newVersion >= 40) { + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); + } + if (oldVersion < 42 && newVersion >= 42) { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); @@ -544,7 +561,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUSTED); // TODO - just to make old databases importable, column isn't needed at all } } - db.execSQL("DROP TABLE resolver_results"); + + if (oldVersion < 48 && newVersion >= 48) { + try { + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); + } catch (Exception e) { + //ignore + } + } } private boolean isColumnExisting(SQLiteDatabase db, String TableName, String ColumnName) { @@ -681,6 +705,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { return result; } + public void saveResolverResult(String domain, Resolver.Result result) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = result.toContentValues(); + contentValues.put(Resolver.Result.DOMAIN, domain); + db.insert(RESOLVER_RESULTS_TABLENAME, null, contentValues); + } + + public synchronized Resolver.Result findResolverResult(String domain) { + SQLiteDatabase db = this.getReadableDatabase(); + String where = Resolver.Result.DOMAIN + "=?"; + String[] whereArgs = {domain}; + final Cursor cursor = db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); + Resolver.Result result = null; + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + result = Resolver.Result.fromCursor(cursor); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to find cached resolver result in database " + e.getMessage()); + return null; + } finally { + cursor.close(); + } + } + return result; + } + public void insertPresenceTemplate(PresenceTemplate template) { SQLiteDatabase db = this.getWritableDatabase(); String whereToDelete = PresenceTemplate.MESSAGE + "=?"; diff --git a/src/main/java/de/pixart/messenger/utils/AndroidUsingExecLowPriority.java b/src/main/java/de/pixart/messenger/utils/AndroidUsingExecLowPriority.java new file mode 100644 index 000000000..6097fcd03 --- /dev/null +++ b/src/main/java/de/pixart/messenger/utils/AndroidUsingExecLowPriority.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2016 the original author or authors + * + * This software is licensed under the Apache License, Version 2.0, + * the GNU Lesser General Public License version 2 or later ("LGPL") + * and the WTFPL. + * You may choose either license to govern your use of this software only + * upon the condition that you accept all of the terms of either + * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. + */ + +package de.pixart.messenger.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.net.InetAddress; +import java.util.HashSet; +import java.util.logging.Level; + +import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; +import de.measite.minidns.dnsserverlookup.AndroidUsingReflection; +import de.measite.minidns.dnsserverlookup.DNSServerLookupMechanism; +import de.measite.minidns.util.PlatformDetection; + +/** + * Try to retrieve the list of DNS server by executing getprop. + */ +public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism { + + public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority(); + public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1; + + private AndroidUsingExecLowPriority() { + super(AndroidUsingExecLowPriority.class.getSimpleName(), PRIORITY); + } + + @Override + public String[] getDnsServerAddresses() { + try { + Process process = Runtime.getRuntime().exec("getprop"); + InputStream inputStream = process.getInputStream(); + LineNumberReader lnr = new LineNumberReader( + new InputStreamReader(inputStream)); + String line; + HashSet<String> server = new HashSet<>(6); + while ((line = lnr.readLine()) != null) { + int split = line.indexOf("]: ["); + if (split == -1) { + continue; + } + String property = line.substring(1, split); + String value = line.substring(split + 4, line.length() - 1); + + if (value.isEmpty()) { + continue; + } + + if (property.endsWith(".dns") || property.endsWith(".dns1") || + property.endsWith(".dns2") || property.endsWith(".dns3") || + property.endsWith(".dns4")) { + + // normalize the address + + InetAddress ip = InetAddress.getByName(value); + + if (ip == null) continue; + + value = ip.getHostAddress(); + + if (value == null) continue; + if (value.length() == 0) continue; + + server.add(value); + } + } + if (server.size() > 0) { + return server.toArray(new String[server.size()]); + } + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e); + } + return null; + } + + @Override + public boolean isAvailable() { + return PlatformDetection.isAndroid(); + } + +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/AndroidUsingLinkProperties.java b/src/main/java/de/pixart/messenger/utils/AndroidUsingLinkProperties.java new file mode 100644 index 000000000..1c0fca8bb --- /dev/null +++ b/src/main/java/de/pixart/messenger/utils/AndroidUsingLinkProperties.java @@ -0,0 +1,91 @@ +package de.pixart.messenger.utils; + +import android.annotation.TargetApi; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.RouteInfo; +import android.os.Build; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; +import de.measite.minidns.dnsserverlookup.AndroidUsingExec; + +public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism { + + private final Context context; + + protected AndroidUsingLinkProperties(Context context) { + super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1); + this.context = context; + } + + @Override + public boolean isAvailable() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + @Override + @TargetApi(21) + public String[] getDnsServerAddresses() { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + if (networks == null) { + return new String[0]; + } + final Network activeNetwork = getActiveNetwork(connectivityManager); + List<String> servers = new ArrayList<>(); + int vpnOffset = 0; + for (Network network : networks) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(network); + if (linkProperties == null) { + continue; + } + NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); + final boolean isActiveNetwork = network.equals(activeNetwork); + if (isActiveNetwork) { + if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN) { + final List<String> tmp = getIPv4First(linkProperties.getDnsServers()); + servers.addAll(0, tmp); + vpnOffset += tmp.size(); + } else { + servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers())); + } + } + } + return servers.toArray(new String[servers.size()]); + } + + @TargetApi(23) + private static Network getActiveNetwork(ConnectivityManager cm) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? cm.getActiveNetwork() : null; + } + + private static List<String> getIPv4First(List<InetAddress> in) { + List<String> out = new ArrayList<>(); + for (InetAddress addr : in) { + if (addr instanceof Inet4Address) { + out.add(0, addr.getHostAddress()); + } else { + out.add(addr.getHostAddress()); + } + } + return out; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean hasDefaultRoute(LinkProperties linkProperties) { + for (RouteInfo route : linkProperties.getRoutes()) { + if (route.isDefaultRoute()) { + return true; + } + } + return false; + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/Resolver.java b/src/main/java/de/pixart/messenger/utils/Resolver.java index edde83088..2378c429e 100644 --- a/src/main/java/de/pixart/messenger/utils/Resolver.java +++ b/src/main/java/de/pixart/messenger/utils/Resolver.java @@ -7,6 +7,9 @@ import android.util.Log; import java.io.IOException; import java.lang.reflect.Field; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -17,12 +20,16 @@ import de.measite.minidns.DNSName; import de.measite.minidns.Question; import de.measite.minidns.Record; import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException; +import de.measite.minidns.dnsserverlookup.AndroidUsingExec; import de.measite.minidns.hla.DnssecResolverApi; import de.measite.minidns.hla.ResolverApi; import de.measite.minidns.hla.ResolverResult; import de.measite.minidns.iterative.ReliableDNSClient; +import de.measite.minidns.record.A; +import de.measite.minidns.record.AAAA; import de.measite.minidns.record.CNAME; import de.measite.minidns.record.Data; +import de.measite.minidns.record.InternetAddressRR; import de.measite.minidns.record.SRV; import de.pixart.messenger.Config; import de.pixart.messenger.R; @@ -32,13 +39,16 @@ public class Resolver { public static final int DEFAULT_PORT_XMPP = 5222; private static final String DIRECT_TLS_SERVICE = "_xmpps-client"; - private static final String STARTTLS_SERVICE = "_xmpp-client"; + private static final String STARTTLS_SERICE = "_xmpp-client"; private static XmppConnectionService SERVICE = null; public static void init(XmppConnectionService service) { Resolver.SERVICE = service; + DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE); + DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE); + DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service)); final AbstractDNSClient client = ResolverApi.INSTANCE.getClient(); if (client instanceof ReliableDNSClient) { disableHardcodedDnsServers((ReliableDNSClient) client); @@ -138,41 +148,91 @@ public class Resolver { if (!IP.matches(domain)) { return Collections.emptyList(); } - return Collections.singletonList(Result.createDefault(DNSName.from(domain))); + try { + Result result = new Result(); + result.ip = InetAddress.getByName(domain); + result.port = DEFAULT_PORT_XMPP; + return Collections.singletonList(result); + } catch (UnknownHostException e) { + return Collections.emptyList(); + } } private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException { - DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); + DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE) + "._tcp." + domain); ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class); final List<Result> results = new ArrayList<>(); + final List<Thread> threads = new ArrayList<>(); for (SRV record : result.getAnswersOrEmptySet()) { if (record.name.length() == 0 && record.priority == 0) { continue; } - Result resolverResult = Result.fromRecord(record, directTls); - resolverResult.authenticated = result.isAuthenticData(); - results.add(resolverResult); - } + threads.add(new Thread(() -> { + final List<Result> ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls); + if (ipv4s.size() == 0) { + Result resolverResult = Result.fromRecord(record, directTls); + resolverResult.authenticated = resolverResult.isAuthenticated(); + ipv4s.add(resolverResult); + } + synchronized (results) { + results.addAll(ipv4s); + } + })); + threads.add(new Thread(() -> { + final List<Result> ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls); + synchronized (results) { + results.addAll(ipv6s); + } + })); + } + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + return Collections.emptyList(); + } + } return results; } + private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) { + List<Result> list = new ArrayList<>(); + try { + ResolverResult<D> results = resolveWithFallback(srv.name, type, authenticated); + for (D record : results.getAnswersOrEmptySet()) { + Result resolverResult = Result.fromRecord(srv, directTls); + resolverResult.authenticated = results.isAuthenticData() && authenticated; + resolverResult.ip = record.getInetAddress(); + list.add(resolverResult); + } + } catch (Throwable t) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage()); + } + return list; + } + private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) { List<Result> results = new ArrayList<>(); - Boolean resolveCNAME = false; try { - if (withCnames) { + for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) { + results.add(Result.createDefault(dnsName, a.getInetAddress())); + } + for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) { + results.add(Result.createDefault(dnsName, aaaa.getInetAddress())); + } + if (results.size() == 0 && withCnames) { for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) { results.addAll(resolveNoSrvRecords(cname.name, false)); - resolveCNAME = true; } } } catch (Throwable throwable) { Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); } - if(!resolveCNAME) { - results.add(Result.createDefault(dnsName)); - } + results.add(Result.createDefault(dnsName)); return results; } @@ -203,11 +263,13 @@ public class Resolver { public static class Result implements Comparable<Result> { public static final String DOMAIN = "domain"; + public static final String IP = "ip"; public static final String HOSTNAME = "hostname"; public static final String PORT = "port"; public static final String PRIORITY = "priority"; public static final String DIRECT_TLS = "directTls"; public static final String AUTHENTICATED = "authenticated"; + private InetAddress ip; private DNSName hostname; private int port = DEFAULT_PORT_XMPP; private boolean directTls = false; @@ -223,15 +285,25 @@ public class Resolver { return result; } - static Result createDefault(DNSName hostname) { + static Result createDefault(DNSName hostname, InetAddress ip) { Result result = new Result(); result.port = DEFAULT_PORT_XMPP; result.hostname = hostname; + result.ip = ip; return result; } + static Result createDefault(DNSName hostname) { + 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)); @@ -252,12 +324,14 @@ public class Resolver { if (directTls != result.directTls) return false; if (authenticated != result.authenticated) return false; if (priority != result.priority) return false; + if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false; return hostname != null ? hostname.equals(result.hostname) : result.hostname == null; } @Override public int hashCode() { - int result = hostname != null ? hostname.hashCode() : 0; + int result = ip != null ? ip.hashCode() : 0; + result = 31 * result + (hostname != null ? hostname.hashCode() : 0); result = 31 * result + port; result = 31 * result + (directTls ? 1 : 0); result = 31 * result + (authenticated ? 1 : 0); @@ -265,6 +339,10 @@ public class Resolver { return result; } + public InetAddress getIp() { + return ip; + } + public int getPort() { return port; } @@ -284,6 +362,7 @@ public class Resolver { @Override public String toString() { return "Result{" + + "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' + ", hostame='" + hostname.toString() + '\'' + ", port=" + port + ", directTls=" + directTls + @@ -296,7 +375,17 @@ public class Resolver { public int compareTo(@NonNull Result result) { if (result.priority == priority) { if (directTls == result.directTls) { + if (ip == null && result.ip == null) { return 0; + } else if (ip != null && result.ip != null) { + if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) { + return 0; + } else { + return ip instanceof Inet4Address ? -1 : 1; + } + } else { + return ip != null ? -1 : 1; + } } else { return directTls ? -1 : 1; } @@ -307,6 +396,7 @@ public class Resolver { public ContentValues toContentValues() { final ContentValues contentValues = new ContentValues(); + contentValues.put(IP, ip == null ? null : ip.getAddress()); contentValues.put(HOSTNAME, hostname == null ? null : hostname.toString()); contentValues.put(PORT, port); contentValues.put(PRIORITY, priority); @@ -315,5 +405,4 @@ public class Resolver { return contentValues; } } - -} +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index 9a72203b7..3a7e41c9a 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -320,6 +320,16 @@ public class XmppConnection implements Runnable { Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": Resolver results were empty"); return; } + final Resolver.Result storedBackupResult; + if (hardcoded) { + storedBackupResult = null; + } else { + storedBackupResult = mXmppConnectionService.databaseBackend.findResolverResult(domain); + if (storedBackupResult != null && !results.contains(storedBackupResult)) { + results.add(storedBackupResult); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": loaded backup resolver result from db: " + storedBackupResult); + } + } for (Iterator<Resolver.Result> iterator = results.iterator(); iterator.hasNext(); ) { final Resolver.Result result = iterator.next(); if (Thread.currentThread().isInterrupted()) { @@ -331,10 +341,18 @@ 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 = new InetSocketAddress(IDN.toASCII(result.getHostname().toString()), result.getPort()); - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() - + ": using values from resolver " - + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + final InetSocketAddress addr; + if (result.getIp() != null) { + addr = new InetSocketAddress(result.getIp(), result.getPort()); + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + + ": using values from resolver " + (result.getHostname() == null ? "" : result.getHostname().toString() + + "/") + result.getIp().getHostAddress() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + } else { + addr = new InetSocketAddress(IDN.toASCII(result.getHostname().toString()), result.getPort()); + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + + ": using values from resolver " + + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + } if (!features.encryptionEnabled) { localSocket = new Socket(); @@ -362,6 +380,9 @@ public class XmppConnection implements Runnable { localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); if (startXmpp(localSocket)) { localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this + if (!hardcoded && !result.equals(storedBackupResult)) { + mXmppConnectionService.databaseBackend.saveResolverResult(domain, result); + } break; // successfully connected to server that speaks xmpp } else { FileBackend.close(localSocket); @@ -1969,4 +1990,4 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getJid().asBareJid(), Namespace.STANZA_IDS); } } -} +}
\ No newline at end of file |