diff options
author | Sam Whited <sam@samwhited.com> | 2014-11-12 15:35:44 -0500 |
---|---|---|
committer | Sam Whited <sam@samwhited.com> | 2014-11-14 18:00:12 -0500 |
commit | 0e550789d372a1a83caa432e93a4f969a0607c9a (patch) | |
tree | 126ddf717b9a3d1c4f36e904c658341fad5d5177 /src/main/java/eu/siacs/conversations/xmpp | |
parent | c61120bfc416676147998a435674bf1ac1587359 (diff) |
Add SCRAM-SHA1 support
Factor out GS2 tokanization into own class
Add authentication exception class
Fixes #71
Diffstat (limited to 'src/main/java/eu/siacs/conversations/xmpp')
-rw-r--r-- | src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java | 619 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java | 2 |
2 files changed, 322 insertions, 299 deletions
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 26854aa3..899ebf56 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -39,9 +39,11 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.sasl.AuthenticationException; import eu.siacs.conversations.crypto.sasl.DigestMd5; import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.crypto.sasl.SaslMechanism; +import eu.siacs.conversations.crypto.sasl.ScramSha1; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.DNSHelper; @@ -106,12 +108,12 @@ public class XmppConnection implements Runnable { private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; - private SaslMechanism saslMechanism; + private SaslMechanism saslMechanism; public XmppConnection(Account account, XmppConnectionService service) { this.account = account; this.wakeLock = service.getPowerManager().newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); + PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); tagWriter = new TagWriter(); mXmppConnectionService = service; applicationContext = service.getApplicationContext(); @@ -124,7 +126,7 @@ public class XmppConnection implements Runnable { && (account.getStatus() != Account.STATUS_ONLINE) && (account.getStatus() != Account.STATUS_DISABLED)) { return; - } + } if (nextStatus == Account.STATUS_ONLINE) { this.attempt = 0; } @@ -144,7 +146,7 @@ public class XmppConnection implements Runnable { this.attempt++; try { shouldAuthenticate = shouldBind = !account - .isOptionSet(Account.OPTION_REGISTER); + .isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); packetCallbacks.clear(); @@ -162,12 +164,12 @@ public class XmppConnection implements Runnable { Bundle namePort = (Bundle) values.get(i); try { String srvRecordServer; - try { - srvRecordServer=IDN.toASCII(namePort.getString("name")); - } catch (final IllegalArgumentException e) { - // TODO: Handle me?` - srvRecordServer = ""; - } + try { + srvRecordServer=IDN.toASCII(namePort.getString("name")); + } catch (final IllegalArgumentException e) { + // TODO: Handle me?` + srvRecordServer = ""; + } int srvRecordPort = namePort.getInt("port"); String srvIpServer = namePort.getString("ipv4"); InetSocketAddress addr; @@ -240,7 +242,7 @@ public class XmppConnection implements Runnable { } catch (final RuntimeException ignored) { } } - } catch (final IOException | XmlPullParserException e) { + } catch (final IOException | XmlPullParserException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.STATUS_OFFLINE); if (wakeLock.isHeld()) { @@ -249,7 +251,7 @@ public class XmppConnection implements Runnable { } catch (final RuntimeException ignored) { } } - } catch (NoSuchAlgorithmException e) { + } catch (NoSuchAlgorithmException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.STATUS_OFFLINE); Log.d(Config.LOGTAG, "compression exception " + e.getMessage()); @@ -259,9 +261,9 @@ public class XmppConnection implements Runnable { } catch (final RuntimeException ignored) { } } - } + } - } + } @Override public void run() { @@ -269,115 +271,127 @@ public class XmppConnection implements Runnable { } private void processStream(final Tag currentTag) throws XmlPullParserException, - IOException, NoSuchAlgorithmException { - Tag nextTag = tagReader.readTag(); - while ((nextTag != null) && (!nextTag.isEnd("stream"))) { - if (nextTag.isStart("error")) { - processStreamError(nextTag); - } else if (nextTag.isStart("features")) { - processStreamFeatures(nextTag); - } else if (nextTag.isStart("proceed")) { - switchOverToTls(nextTag); - } else if (nextTag.isStart("compressed")) { - switchOverToZLib(nextTag); - } else if (nextTag.isStart("success")) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); - tagReader.readTag(); - tagReader.reset(); - sendStartStream(); - processStream(tagReader.readTag()); - break; - } else if (nextTag.isStart("failure")) { - tagReader.readElement(nextTag); - changeStatus(Account.STATUS_UNAUTHORIZED); - } else if (nextTag.isStart("challenge")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - final Element response = new Element("response"); - response.setAttribute("xmlns", - "urn:ietf:params:xml:ns:xmpp-sasl"); - response.setContent(saslMechanism.getResponse(challenge)); - tagWriter.writeElement(response); - } else if (nextTag.isStart("enabled")) { - Element enabled = tagReader.readElement(nextTag); - if ("true".equals(enabled.getAttribute("resume"))) { - this.streamId = enabled.getAttribute("id"); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion - + ") enabled (resumable)"); - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion + ") enabled"); - } - this.lastSessionStarted = SystemClock.elapsedRealtime(); - this.stanzasReceived = 0; - RequestPacket r = new RequestPacket(smVersion); - tagWriter.writeStanzaAsync(r); - } else if (nextTag.isStart("resumed")) { - lastPaketReceived = SystemClock.elapsedRealtime(); - Element resumed = tagReader.readElement(nextTag); - String h = resumed.getAttribute("h"); - try { - int serverCount = Integer.parseInt(h); - if (serverCount != stanzasSent) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed with lost packages"); - stanzasSent = serverCount; - } else { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed"); - } - if (acknowledgedListener != null) { - for (int i = 0; i < messageReceipts.size(); ++i) { - if (serverCount >= messageReceipts.keyAt(i)) { - acknowledgedListener.onMessageAcknowledged( - account, messageReceipts.valueAt(i)); + IOException, NoSuchAlgorithmException { + Tag nextTag = tagReader.readTag(); + + while ((nextTag != null) && (!nextTag.isEnd("stream"))) { + if (nextTag.isStart("error")) { + processStreamError(nextTag); + } else if (nextTag.isStart("features")) { + processStreamFeatures(nextTag); + } else if (nextTag.isStart("proceed")) { + switchOverToTls(nextTag); + } else if (nextTag.isStart("compressed")) { + switchOverToZLib(nextTag); + } else if (nextTag.isStart("success")) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); + final String challenge = tagReader.readElement(nextTag).getContent(); + try { + saslMechanism.getResponse(challenge); + } catch (final AuthenticationException e) { + disconnect(true); + Log.e(Config.LOGTAG, String.valueOf(e)); + } + tagReader.reset(); + sendStartStream(); + processStream(tagReader.readTag()); + break; + } else if (nextTag.isStart("failure")) { + tagReader.readElement(nextTag); + changeStatus(Account.STATUS_UNAUTHORIZED); + } else if (nextTag.isStart("challenge")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + final Element response = new Element("response"); + response.setAttribute("xmlns", + "urn:ietf:params:xml:ns:xmpp-sasl"); + try { + response.setContent(saslMechanism.getResponse(challenge)); + } catch (final AuthenticationException e) { + // TODO: Send auth abort tag. + Log.e(Config.LOGTAG, e.toString()); + } + tagWriter.writeElement(response); + } else if (nextTag.isStart("enabled")) { + Element enabled = tagReader.readElement(nextTag); + if ("true".equals(enabled.getAttribute("resume"))) { + this.streamId = enabled.getAttribute("id"); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream managment(" + smVersion + + ") enabled (resumable)"); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream managment(" + smVersion + ") enabled"); + } + this.lastSessionStarted = SystemClock.elapsedRealtime(); + this.stanzasReceived = 0; + RequestPacket r = new RequestPacket(smVersion); + tagWriter.writeStanzaAsync(r); + } else if (nextTag.isStart("resumed")) { + lastPaketReceived = SystemClock.elapsedRealtime(); + Element resumed = tagReader.readElement(nextTag); + String h = resumed.getAttribute("h"); + try { + int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": session resumed"); + } + if (acknowledgedListener != null) { + for (int i = 0; i < messageReceipts.size(); ++i) { + if (serverCount >= messageReceipts.keyAt(i)) { + acknowledgedListener.onMessageAcknowledged( + account, messageReceipts.valueAt(i)); + } + } + } + messageReceipts.clear(); + } catch (final NumberFormatException ignored) { + + } + sendInitialPing(); + + } else if (nextTag.isStart("r")) { + tagReader.readElement(nextTag); + AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + tagWriter.writeStanzaAsync(ack); + } else if (nextTag.isStart("a")) { + Element ack = tagReader.readElement(nextTag); + lastPaketReceived = SystemClock.elapsedRealtime(); + int serverSequence = Integer.parseInt(ack.getAttribute("h")); + String msgId = this.messageReceipts.get(serverSequence); + if (msgId != null) { + if (this.acknowledgedListener != null) { + this.acknowledgedListener.onMessageAcknowledged( + account, msgId); + } + this.messageReceipts.remove(serverSequence); + } + } else if (nextTag.isStart("failed")) { + tagReader.readElement(nextTag); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); + streamId = null; + if (account.getStatus() != Account.STATUS_ONLINE) { + sendBindRequest(); + } + } else if (nextTag.isStart("iq")) { + processIq(nextTag); + } else if (nextTag.isStart("message")) { + processMessage(nextTag); + } else if (nextTag.isStart("presence")) { + processPresence(nextTag); + } + nextTag = tagReader.readTag(); + } + if (account.getStatus() == Account.STATUS_ONLINE) { + account. setStatus(Account.STATUS_OFFLINE); + if (statusListener != null) { + statusListener.onStatusChanged(account); } } - } - messageReceipts.clear(); - } catch (final NumberFormatException ignored) { - - } - sendInitialPing(); - - } else if (nextTag.isStart("r")) { - tagReader.readElement(nextTag); - AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); - tagWriter.writeStanzaAsync(ack); - } else if (nextTag.isStart("a")) { - Element ack = tagReader.readElement(nextTag); - lastPaketReceived = SystemClock.elapsedRealtime(); - int serverSequence = Integer.parseInt(ack.getAttribute("h")); - String msgId = this.messageReceipts.get(serverSequence); - if (msgId != null) { - if (this.acknowledgedListener != null) { - this.acknowledgedListener.onMessageAcknowledged( - account, msgId); - } - this.messageReceipts.remove(serverSequence); - } - } else if (nextTag.isStart("failed")) { - tagReader.readElement(nextTag); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); - streamId = null; - if (account.getStatus() != Account.STATUS_ONLINE) { - sendBindRequest(); - } - } else if (nextTag.isStart("iq")) { - processIq(nextTag); - } else if (nextTag.isStart("message")) { - processMessage(nextTag); - } else if (nextTag.isStart("presence")) { - processPresence(nextTag); - } - nextTag = tagReader.readTag(); - } - if (account.getStatus() == Account.STATUS_ONLINE) { - account. setStatus(Account.STATUS_OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } } private void sendInitialPing() { @@ -397,7 +411,7 @@ public class XmppConnection implements Runnable { } private Element processPacket(Tag currentTag, int packetType) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { Element element; switch (packetType) { case PACKET_IQ: @@ -424,10 +438,10 @@ public class XmppConnection implements Runnable { if (packetType == PACKET_IQ && "jingle".equals(child.getName()) && ("set".equalsIgnoreCase(type) || "get" - .equalsIgnoreCase(type))) { + .equalsIgnoreCase(type))) { element = new JinglePacket(); element.setAttributes(currentTag.getAttributes()); - } + } element.addChild(child); } nextTag = tagReader.readTag(); @@ -441,64 +455,64 @@ public class XmppConnection implements Runnable { } private void processIq(Tag currentTag) throws XmlPullParserException, - IOException { - IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); + IOException { + IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - if (packet.getId() == null) { - return; // an iq packet without id is definitely invalid - } - - if (packet instanceof JinglePacket) { - if (this.jingleListener != null) { - this.jingleListener.onJinglePacketReceived(account, - (JinglePacket) packet); - } - } else { - if (packetCallbacks.containsKey(packet.getId())) { - if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) { - ((OnIqPacketReceived) packetCallbacks.get(packet.getId())) - .onIqPacketReceived(account, packet); - } + if (packet.getId() == null) { + return; // an iq packet without id is definitely invalid + } - packetCallbacks.remove(packet.getId()); - } else if ((packet.getType() == IqPacket.TYPE_GET || packet - .getType() == IqPacket.TYPE_SET) - && this.unregisteredIqListener != null) { - this.unregisteredIqListener.onIqPacketReceived(account, packet); - } - } + if (packet instanceof JinglePacket) { + if (this.jingleListener != null) { + this.jingleListener.onJinglePacketReceived(account, + (JinglePacket) packet); + } + } else { + if (packetCallbacks.containsKey(packet.getId())) { + if (packetCallbacks.get(packet.getId()) instanceof OnIqPacketReceived) { + ((OnIqPacketReceived) packetCallbacks.get(packet.getId())) + .onIqPacketReceived(account, packet); + } + + packetCallbacks.remove(packet.getId()); + } else if ((packet.getType() == IqPacket.TYPE_GET || packet + .getType() == IqPacket.TYPE_SET) + && this.unregisteredIqListener != null) { + this.unregisteredIqListener.onIqPacketReceived(account, packet); + } + } } private void processMessage(Tag currentTag) throws XmlPullParserException, - IOException { - MessagePacket packet = (MessagePacket) processPacket(currentTag, - PACKET_MESSAGE); - String id = packet.getAttribute("id"); - if ((id != null) && (packetCallbacks.containsKey(id))) { - if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) { - ((OnMessagePacketReceived) packetCallbacks.get(id)) - .onMessagePacketReceived(account, packet); - } - packetCallbacks.remove(id); - } else if (this.messageListener != null) { - this.messageListener.onMessagePacketReceived(account, packet); - } + IOException { + MessagePacket packet = (MessagePacket) processPacket(currentTag, + PACKET_MESSAGE); + String id = packet.getAttribute("id"); + if ((id != null) && (packetCallbacks.containsKey(id))) { + if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) { + ((OnMessagePacketReceived) packetCallbacks.get(id)) + .onMessagePacketReceived(account, packet); + } + packetCallbacks.remove(id); + } else if (this.messageListener != null) { + this.messageListener.onMessagePacketReceived(account, packet); + } } private void processPresence(Tag currentTag) throws XmlPullParserException, - IOException { - PresencePacket packet = (PresencePacket) processPacket(currentTag, - PACKET_PRESENCE); - String id = packet.getAttribute("id"); - if ((id != null) && (packetCallbacks.containsKey(id))) { - if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) { - ((OnPresencePacketReceived) packetCallbacks.get(id)) - .onPresencePacketReceived(account, packet); - } - packetCallbacks.remove(id); - } else if (this.presenceListener != null) { - this.presenceListener.onPresencePacketReceived(account, packet); - } + IOException { + PresencePacket packet = (PresencePacket) processPacket(currentTag, + PACKET_PRESENCE); + String id = packet.getAttribute("id"); + if ((id != null) && (packetCallbacks.containsKey(id))) { + if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) { + ((OnPresencePacketReceived) packetCallbacks.get(id)) + .onPresencePacketReceived(account, packet); + } + packetCallbacks.remove(id); + } else if (this.presenceListener != null) { + this.presenceListener.onPresencePacketReceived(account, packet); + } } private void sendCompressionZlib() throws IOException { @@ -509,18 +523,18 @@ public class XmppConnection implements Runnable { } private void switchOverToZLib(final Tag currentTag) - throws XmlPullParserException, IOException, - NoSuchAlgorithmException { - tagReader.readTag(); // read tag close - tagWriter.setOutputStream(new ZLibOutputStream(tagWriter - .getOutputStream())); - tagReader - .setInputStream(new ZLibInputStream(tagReader.getInputStream())); - - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": compression enabled"); - usingCompression = true; - processStream(tagReader.readTag()); + throws XmlPullParserException, IOException, + NoSuchAlgorithmException { + tagReader.readTag(); // read tag close + tagWriter.setOutputStream(new ZLibOutputStream(tagWriter + .getOutputStream())); + tagReader + .setInputStream(new ZLibInputStream(tagReader.getInputStream())); + + sendStartStream(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": compression enabled"); + usingCompression = true; + processStream(tagReader.readTag()); } private void sendStartTLS() throws IOException { @@ -531,7 +545,7 @@ public class XmppConnection implements Runnable { private SharedPreferences getPreferences() { return PreferenceManager - .getDefaultSharedPreferences(applicationContext); + .getDefaultSharedPreferences(applicationContext); } private boolean enableLegacySSL() { @@ -539,64 +553,64 @@ public class XmppConnection implements Runnable { } private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, - IOException { - tagReader.readTag(); - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, - new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()}, - mXmppConnectionService.getRNG()); - SSLSocketFactory factory = sc.getSocketFactory(); - - if (factory == null) { - throw new IOException("SSLSocketFactory was null"); - } + IOException { + tagReader.readTag(); + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, + new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()}, + mXmppConnectionService.getRNG()); + SSLSocketFactory factory = sc.getSocketFactory(); + + if (factory == null) { + throw new IOException("SSLSocketFactory was null"); + } - final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); + final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); - if (socket == null) { - throw new IOException("socket was null"); - } - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, - socket.getInetAddress().getHostAddress(), socket.getPort(), - true); - - // Support all protocols except legacy SSL. - // The min SDK version prevents us having to worry about SSLv2. In - // future, this may be true of SSLv3 as well. - final String[] supportProtocols; - if (enableLegacySSL()) { - supportProtocols = sslSocket.getSupportedProtocols(); - } else { - final List<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = new String[supportedProtocols.size()]; - supportedProtocols.toArray(supportProtocols); - } - sslSocket.setEnabledProtocols(supportProtocols); - - if (verifier != null - && !verifier.verify(account.getServer().getDomainpart(), - sslSocket.getSession())) { - sslSocket.close(); - throw new IOException("host mismatch in TLS connection"); - } - tagReader.setInputStream(sslSocket.getInputStream()); - tagWriter.setOutputStream(sslSocket.getOutputStream()); - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": TLS connection established"); - usingEncryption = true; - processStream(tagReader.readTag()); - sslSocket.close(); - } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - e1.printStackTrace(); - } - } + if (socket == null) { + throw new IOException("socket was null"); + } + final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, + socket.getInetAddress().getHostAddress(), socket.getPort(), + true); + + // Support all protocols except legacy SSL. + // The min SDK version prevents us having to worry about SSLv2. In + // future, this may be true of SSLv3 as well. + final String[] supportProtocols; + if (enableLegacySSL()) { + supportProtocols = sslSocket.getSupportedProtocols(); + } else { + final List<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = new String[supportedProtocols.size()]; + supportedProtocols.toArray(supportProtocols); + } + sslSocket.setEnabledProtocols(supportProtocols); + + if (verifier != null + && !verifier.verify(account.getServer().getDomainpart(), + sslSocket.getSession())) { + sslSocket.close(); + throw new IOException("host mismatch in TLS connection"); + } + tagReader.setInputStream(sslSocket.getInputStream()); + tagWriter.setOutputStream(sslSocket.getOutputStream()); + sendStartStream(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": TLS connection established"); + usingEncryption = true; + processStream(tagReader.readTag()); + sslSocket.close(); + } catch (final NoSuchAlgorithmException | KeyManagementException e1) { + e1.printStackTrace(); + } + } private void processStreamFeatures(Tag currentTag) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { this.streamFeatures = tagReader.readElement(currentTag); if (this.streamFeatures.hasChild("starttls") && !usingEncryption) { sendStartTLS(); @@ -614,18 +628,27 @@ public class XmppConnection implements Runnable { && shouldAuthenticate && usingEncryption) { final List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); - final Element auth = new Element("auth"); - auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); - if (mechanisms.contains("DIGEST-MD5")) { - saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); - } else if (mechanisms.contains("PLAIN")) { - saslMechanism = new Plain(tagWriter, account); - auth.setContent(((Plain)saslMechanism).getStartAuth()); - } - auth.setAttribute("mechanism", saslMechanism.getMechanism()); - tagWriter.writeElement(auth); - } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" - + smVersion) + final Element auth = new Element("auth"); + auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); + if (mechanisms.contains(ScramSha1.getMechanism())) { + saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); + Log.d(Config.LOGTAG, "Authenticating with " + ScramSha1.getMechanism()); + auth.setAttribute("mechanism", ScramSha1.getMechanism()); + } else if (mechanisms.contains(DigestMd5.getMechanism())) { + Log.d(Config.LOGTAG, "Authenticating with " + DigestMd5.getMechanism()); + saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); + auth.setAttribute("mechanism", DigestMd5.getMechanism()); + } else if (mechanisms.contains(Plain.getMechanism())) { + Log.d(Config.LOGTAG, "Authenticating with " + Plain.getMechanism()); + saslMechanism = new Plain(tagWriter, account); + auth.setAttribute("mechanism", Plain.getMechanism()); + } + if (!saslMechanism.getClientFirstMessage().isEmpty()) { + auth.setContent(saslMechanism.getClientFirstMessage()); + } + tagWriter.writeElement(auth); + } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + + smVersion) && streamId != null) { ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); @@ -641,7 +664,7 @@ public class XmppConnection implements Runnable { private boolean compressionAvailable() { if (!this.streamFeatures.hasChild("compression", - "http://jabber.org/features/compress")) + "http://jabber.org/features/compress")) return false; if (!ZLibOutputStream.SUPPORTED) return false; @@ -683,23 +706,23 @@ public class XmppConnection implements Runnable { && (packet.query().hasChild("password"))) { IqPacket register = new IqPacket(IqPacket.TYPE_SET); Element username = new Element("username") - .setContent(account.getUsername()); + .setContent(account.getUsername()); Element password = new Element("password") - .setContent(account.getPassword()); + .setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); sendIqPacket(register, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, - IqPacket packet) { + IqPacket packet) { if (packet.getType() == IqPacket.TYPE_RESULT) { account.setOption(Account.OPTION_REGISTER, false); changeStatus(Account.STATUS_REGISTRATION_SUCCESSFULL); } else if (packet.hasChild("error") && (packet.findChild("error") - .hasChild("conflict"))) { + .hasChild("conflict"))) { changeStatus(Account.STATUS_REGISTRATION_CONFLICT); } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); @@ -722,7 +745,7 @@ public class XmppConnection implements Runnable { private void sendBindRequest() throws IOException { IqPacket iq = new IqPacket(IqPacket.TYPE_SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") - .addChild("resource").setContent(account.getResource()); + .addChild("resource").setContent(account.getResource()); this.sendUnboundIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -730,19 +753,19 @@ public class XmppConnection implements Runnable { if (bind != null) { final Element jid = bind.findChild("jid"); 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("sm", "urn:xmpp:sm:3")) { + 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("sm", "urn:xmpp:sm:3")) { smVersion = 3; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; messageReceipts.clear(); } else if (streamFeatures.hasChild("sm", - "urn:xmpp:sm:2")) { + "urn:xmpp:sm:2")) { smVersion = 2; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); @@ -783,11 +806,11 @@ public class XmppConnection implements Runnable { public void onIqPacketReceived(Account account, IqPacket packet) { final List<Element> elements = packet.query().getChildren(); final List<String> features = new ArrayList<>(); - for (Element element : elements) { - if (element.getName().equals("feature")) { - features.add(element.getAttribute("var")); - } - } + for (Element element : elements) { + if (element.getName().equals("feature")) { + features.add(element.getAttribute("var")); + } + } disco.put(server.toDomainJid().toString(), features); if (account.getServer().equals(server.toDomainJid())) { @@ -812,16 +835,16 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { List<Element> elements = packet.query().getChildren(); - for (Element element : elements) { - if (element.getName().equals("item")) { - final String jid = element.getAttribute("jid"); - try { - sendServiceDiscoveryInfo(Jid.fromString(jid).toDomainJid()); - } catch (final InvalidJidException ignored) { - // TODO: Handle the case where an external JID is technically invalid? - } - } - } + for (Element element : elements) { + if (element.getName().equals("item")) { + final String jid = element.getAttribute("jid"); + try { + sendServiceDiscoveryInfo(Jid.fromString(jid).toDomainJid()); + } catch (final InvalidJidException ignored) { + // TODO: Handle the case where an external JID is technically invalid? + } + } + } } }); } @@ -845,14 +868,14 @@ public class XmppConnection implements Runnable { } private void processStreamError(Tag currentTag) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { Element streamError = tagReader.readElement(currentTag); if (streamError != null && streamError.hasChild("conflict")) { final String resource = account.getResource().split("\\.")[0]; - account.setResource(resource + "." + nextRandomId()); - Log.d(Config.LOGTAG, + account.setResource(resource + "." + nextRandomId()); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": switching resource due to conflict (" - + account.getResource() + ")"); + + account.getResource() + ")"); } } @@ -897,11 +920,11 @@ public class XmppConnection implements Runnable { } private synchronized void sendPacket(final AbstractStanza packet, - PacketReceived callback) { + PacketReceived callback) { if (packet.getName().equals("iq") || packet.getName().equals("message") || packet.getName().equals("presence")) { ++stanzasSent; - } + } tagWriter.writeStanzaAsync(packet); if (packet instanceof MessagePacket && packet.getId() != null && this.streamId != null) { @@ -909,7 +932,7 @@ public class XmppConnection implements Runnable { + stanzasSent); this.messageReceipts.put(stanzasSent, packet.getId()); tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); - } + } if (callback != null) { if (packet.getId() == null) { packet.setId(nextRandomId()); @@ -933,22 +956,22 @@ public class XmppConnection implements Runnable { public void setOnMessagePacketReceivedListener( OnMessagePacketReceived listener) { this.messageListener = listener; - } + } public void setOnUnregisteredIqPacketReceivedListener( OnIqPacketReceived listener) { this.unregisteredIqListener = listener; - } + } public void setOnPresencePacketReceivedListener( OnPresencePacketReceived listener) { this.presenceListener = listener; - } + } public void setOnJinglePacketReceivedListener( OnJinglePacketReceived listener) { this.jingleListener = listener; - } + } public void setOnStatusChangedListener(OnStatusChanged listener) { this.statusListener = listener; @@ -1074,9 +1097,9 @@ public class XmppConnection implements Runnable { } private boolean hasDiscoFeature(final Jid server, final String feature) { - return connection.disco.containsKey(server.toDomainJid().toString()) && - connection.disco.get(server.toDomainJid().toString()).contains(feature); - } + return connection.disco.containsKey(server.toDomainJid().toString()) && + connection.disco.get(server.toDomainJid().toString()).contains(feature); + } public boolean carbons() { return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); @@ -1087,7 +1110,7 @@ public class XmppConnection implements Runnable { } public boolean csi() { - return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0"); + return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0"); } public boolean pubsub() { @@ -1100,12 +1123,12 @@ public class XmppConnection implements Runnable { } public boolean rosterVersioning() { - return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); + return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); } public boolean streamhost() { return connection - .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; + .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; } public boolean compression() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index d8d8e375..ebf8a6ed 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -117,7 +117,7 @@ public final class Jid { finaljid = finaljid + dp; } - // Remove trailling "." before storing the domain part. + // Remove trailing "." before storing the domain part. if (dp.endsWith(".")) { try { domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES); |