aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java')
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java157
1 files changed, 69 insertions, 88 deletions
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index f75f027b..b9094a61 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -39,6 +39,7 @@ import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
+import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -51,6 +52,9 @@ import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.sasl.DigestMd5;
@@ -90,7 +94,7 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
public class XmppConnection implements Runnable {
-
+ private static final int DEFAULT_PORT = 5222;
private static final int PACKET_IQ = 0;
private static final int PACKET_MESSAGE = 1;
private static final int PACKET_PRESENCE = 2;
@@ -176,7 +180,8 @@ public class XmppConnection implements Runnable {
};
private Identity mServerIdentity = Identity.UNKNOWN;
- public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() {
+ private OnIqPacketReceived createPacketReceiveHandler() {
+ return new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
@@ -194,6 +199,7 @@ public class XmppConnection implements Runnable {
}
}
};
+ }
public XmppConnection(final Account account, final XmppConnectionService service) {
this.account = account;
@@ -229,7 +235,7 @@ public class XmppConnection implements Runnable {
}
protected void connect() {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
features.encryptionEnabled = false;
this.attempt++;
switch (account.getJid().getDomainpart()) {
@@ -248,19 +254,8 @@ public class XmppConnection implements Runnable {
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
- final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
- final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
- if (useTor) {
- String destination;
- if (account.getHostname() == null || account.getHostname().isEmpty()) {
- destination = account.getServer().toString();
- } else {
- destination = account.getHostname();
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
- socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
- startXmpp();
- } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ final boolean extended = ConversationsPlusPreferences.showConnectionOptions();
+ if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
socket = new Socket();
try {
socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
@@ -271,50 +266,29 @@ public class XmppConnection implements Runnable {
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try {
- socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
startXmpp();
} else {
- final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
- final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
- for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) {
- if (Thread.currentThread().isInterrupted()) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": Thread was interrupted");
- return;
- }
- final Bundle namePort = (Bundle) iterator.next();
- try {
- String srvRecordServer;
+ final TreeSet<SrvRecord> srvRecords = DNSHelper.querySrvRecord(account.getServer());
+ if (srvRecords.isEmpty()) {
+ socket = new Socket();
try {
- srvRecordServer = IDN.toASCII(namePort.getString("name"));
- } catch (final IllegalArgumentException e) {
- // TODO: Handle me?`
- srvRecordServer = "";
- }
- final int srvRecordPort = namePort.getInt("port");
- final String srvIpServer = namePort.getString("ip");
- // if tls is true, encryption is implied and must not be started
- features.encryptionEnabled = namePort.getBoolean("tls");
- final InetSocketAddress addr;
- if (srvIpServer != null) {
- addr = new InetSocketAddress(srvIpServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
- } else {
- addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
+ socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
}
-
- if (!features.encryptionEnabled) {
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ startXmpp();
} else {
- final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
+ for (SrvRecord srvRecord : srvRecords) {
+ // if tls is true, encryption is implied and must not be started
+ features.encryptionEnabled = srvRecord.isUseTls();
+ TlsFactoryVerifier tlsFactoryVerifier = null;
+ if (features.encryptionEnabled) {
+ try {
+ tlsFactoryVerifier = getTlsFactoryVerifier();
socket = tlsFactoryVerifier.factory.createSocket();
if (socket == null) {
@@ -324,23 +298,25 @@ public class XmppConnection implements Runnable {
SSLSocketHelper.setSecurity((SSLSocket) socket);
SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ } catch (SecurityException e) {
+ throw e;
+ } catch (KeyManagementException e) {
+ Logging.e("connection-init", "Error while creating TLS verifier factory: " + e.getMessage(), e);
+ throw new SecurityException();
+ }
+ } else {
+ socket = new Socket();
+ }
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ socket.connect(new InetSocketAddress(srvRecord.getName(), srvRecord.getPort()), Config.SOCKET_TIMEOUT * 1000);
- if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ if (null != tlsFactoryVerifier && !tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
- }
- if (startXmpp())
+ if (startXmpp()) {
break; // successfully connected to server that speaks xmpp
- } catch(final SecurityException e) {
- throw e;
- } catch (final Throwable e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
- if (!iterator.hasNext()) {
- throw new UnknownHostException();
}
}
}
@@ -354,10 +330,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.UNAUTHORIZED);
} catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
- } catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
- this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
} finally {
@@ -662,7 +636,7 @@ public class XmppConnection implements Runnable {
callback = packetCallbackDuple.second;
packetCallbacks.remove(packet.getId());
} else {
- Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
+ Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
}
}
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
@@ -754,7 +728,7 @@ public class XmppConnection implements Runnable {
authenticate();
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) {
if (Config.EXTENDED_SM_LOGGING) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
}
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
@@ -786,16 +760,16 @@ public class XmppConnection implements Runnable {
try {
if (keys.has(Account.PINNED_MECHANISM_KEY) &&
keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) {
- Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
+ Logging.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
" has lower priority (" + String.valueOf(saslMechanism.getPriority()) +
") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) +
"). Possible downgrade attack?");
throw new SecurityException();
}
} catch (final JSONException e) {
- Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
+ Logging.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
}
- Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
+ Logging.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
auth.setAttribute("mechanism", saslMechanism.getMechanism());
if (!saslMechanism.getClientFirstMessage().isEmpty()) {
auth.setContent(saslMechanism.getClientFirstMessage());
@@ -832,7 +806,7 @@ public class XmppConnection implements Runnable {
final Element password = new Element("password").setContent(account.getPassword());
register.query("jabber:iq:register").addChild(username);
register.query().addChild(password);
- sendIqPacket(register, registrationResponseListener);
+ sendIqPacket(register, createPacketReceiveHandler());
} else if (packet.getType() == IqPacket.TYPE.RESULT
&& (packet.query().hasChild("x", "jabber:x:data"))) {
final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
@@ -856,7 +830,7 @@ public class XmppConnection implements Runnable {
URL uri = new URL(urlString);
captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
} catch (IOException e) {
- Log.e(Config.LOGTAG, e.toString());
+ Logging.e(Config.LOGTAG, e.toString());
}
}
@@ -917,20 +891,22 @@ public class XmppConnection implements Runnable {
if (jid != null && jid.getContent() != null) {
try {
account.setResource(Jid.fromString(jid.getContent()).getResourcepart());
+ } catch (final InvalidJidException e) {
+ // TODO: Handle the case where an external JID is technically invalid?
+ }
if (streamFeatures.hasChild("session")) {
sendStartSession();
} else {
sendPostBindInitialization();
}
- return;
- } catch (final InvalidJidException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server reported invalid jid ("+jid.getContent()+") on bind");
+ } else {
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure. (no jid)");
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure ("+packet.toString());
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString());
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure (" + packet.toString());
}
forceCloseSocket();
changeStatus(Account.State.BIND_FAILURE);
@@ -991,7 +967,7 @@ public class XmppConnection implements Runnable {
if (packet.getType() == IqPacket.TYPE.RESULT) {
sendPostBindInitialization();
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
disconnect(true);
}
}
@@ -1085,7 +1061,7 @@ public class XmppConnection implements Runnable {
enableAdvancedStreamFeatures();
}
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
}
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
if (mPendingServiceDiscoveries.decrementAndGet() == 0
@@ -1102,7 +1078,7 @@ public class XmppConnection implements Runnable {
}
private void finalizeBind() {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
if (bindListener != null) {
bindListener.onBind(account);
}
@@ -1164,11 +1140,11 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (!packet.hasChild("error")) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": successfully enabled carbons");
features.carbonsEnabled = true;
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": error enableing carbons " + packet.toString());
}
}
@@ -1181,11 +1157,11 @@ public class XmppConnection implements Runnable {
if (streamError == null) {
return;
}
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
if (streamError.hasChild("conflict")) {
final String resource = account.getResource().split("\\.")[0];
account.setResource(resource + "." + nextRandomId());
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
account.getJid().toBareJid() + ": switching resource due to conflict ("
+ account.getResource() + ")");
} else if (streamError.hasChild("host-unknown")) {
@@ -1314,7 +1290,7 @@ public class XmppConnection implements Runnable {
Thread.sleep(10);
}
socket.close();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
} catch (IOException | InterruptedException e) {
return;
}
@@ -1322,7 +1298,7 @@ public class XmppConnection implements Runnable {
}).start();
} else {
forceCloseSocket();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
}
}
@@ -1331,7 +1307,7 @@ public class XmppConnection implements Runnable {
try {
socket.close();
} catch (IOException e) {
- e.printStackTrace();
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")");
}
}
}
@@ -1436,7 +1412,12 @@ public class XmppConnection implements Runnable {
}
public long getLastSessionEstablished() {
- final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ final long diff;
+ if (this.lastSessionStarted == 0) {
+ diff = SystemClock.elapsedRealtime() - this.lastConnect;
+ } else {
+ diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ }
return System.currentTimeMillis() - diff;
}