Smack 4.2.1

-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAlmR75tfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFLeXggAjdgj7YVUe22NtamnROBj1c3PaWwgSY0gEjcyDPsOz5qeqNUdQLHbmt2j
 XQQpYZWKg1/1uoQHlsixaFKbGVctKRk72aNEodRfd1osta11WTOwZKEb8nI411Tt
 7M0Fhf430WZY6nioZiZIorsmid57fftJ2EMPlmjEDp2FD0AVGAXkEhCneGaPtt9Q
 hbWbepIy9tApeIH+QgmFLBmPLnFCaSg+X6NUden3Z21bUz5vH8pmcbeUVfsNB7kW
 nkkDuNwKHPFLgjuhcq7D+KAKRwNU7n8WEuHseRzM7bMCEB+S/rZok5KPXe/tV4v+
 YZKN2e+2yh4j5l4FT/fCzELfWcvrgA==
 =MV3G
 -----END PGP SIGNATURE-----

Merge tag '4.2.1'

Smack 4.2.1
This commit is contained in:
Florian Schmaus 2017-08-14 20:59:32 +02:00
commit 43abd52d76
26 changed files with 332 additions and 228 deletions

View File

@ -191,7 +191,10 @@ gradle.taskGraph.whenReady { taskGraph ->
if (signingRequired if (signingRequired
&& taskGraph.allTasks.any { it instanceof Sign }) { && taskGraph.allTasks.any { it instanceof Sign }) {
// Use Java 6's console to read from the console (no good for a CI environment) // Use Java 6's console to read from the console (no good for a CI environment)
Console console = System.console() def console = System.console()
if (console == null) {
throw new Exception("Could not obtain system console (Console is 'null'). Did you build with gradle daemon? Try the same Gradle command with \"--no-daemon\".")
}
console.printf '\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n' console.printf '\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n'
def password = console.readPassword('GnuPG Private Key Password: ') def password = console.readPassword('GnuPG Private Key Password: ')
@ -245,11 +248,18 @@ task distributionZip(type: Zip, dependsOn: [javadocAll, prepareReleasedocs, mark
task maybeCheckForSnapshotDependencies { task maybeCheckForSnapshotDependencies {
// Don't check for Snapshot dependencies if this is a snapshot. // Don't check for Snapshot dependencies if this is a snapshot.
if (isSnapshot) return onlyIf { isReleaseVersion }
allprojects { project -> // Run in the execution phase, not in configuration phase, as the
project.configurations.runtime.each { // 'each' forces the runtime configuration to be resovled, which
if (it.toString().contains("-SNAPSHOT")) // causes "Cannot change dependencies of configuration after it
throw new Exception("Release build contains snapshot dependencies: " + it) // has been included in dependency resolution." errors.
// See https://discuss.gradle.org/t/23153
doLast {
allprojects { project ->
project.configurations.runtime.each {
if (it.toString().contains("-SNAPSHOT"))
throw new Exception("Release build contains snapshot dependencies: " + it)
}
} }
} }
} }
@ -402,6 +412,18 @@ configure(integrationTestProjects + project(':smack-repl')) {
project(':smack-omemo').clirr.enabled = false project(':smack-omemo').clirr.enabled = false
project(':smack-omemo-signal').clirr.enabled = false project(':smack-omemo-signal').clirr.enabled = false
configure(
[ ':smack-omemo',
':smack-omemo-signal',
':smack-omemo-signal-integration-test',
].collect{ project(it) }) {
uploadArchives {
// Only enable uploadArchives for the smack-omemo* projects
// for snapshots. Not yet for releases.
enabled = isSnapshot
}
}
subprojects*.jar { subprojects*.jar {
manifest { manifest {
from sharedManifest from sharedManifest

View File

@ -1,4 +1,4 @@
smackSmack Extensions User Manual Smack Extensions User Manual
============================ ============================
The XMPP protocol includes a base protocol and many optional extensions The XMPP protocol includes a base protocol and many optional extensions
@ -95,7 +95,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. | | [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. | | HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. | | [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| [OMEMO End Encryption (omemo.md) | [XEP-0384](http://xmpp.org/extensions/xep-0384.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | | [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
| Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers | | Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers |

View File

@ -141,6 +141,57 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>4.2.1 -- <span style="font-weight: normal;">2017-08-14</span></h2>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-749'>SMACK-749</a>] - SCRAM-SHA-1 and SCRAM-SHA-1-PLUS SASL mechanisms have the same priority, causing SASL authentication failures
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-755'>SMACK-755</a>] - DIGEST-MD5 sometimes causes malformed request server response
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-756'>SMACK-756</a>] - IoTIsFriendResponse has invalid name and produces invalid XML
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-759'>SMACK-759</a>] - PubSubManager.getLeafNode() throws PubSubAssertionError.DiscoInfoNodeAssertionError if node exists but its not a PubSub Node
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-764'>SMACK-764</a>] - NPE in hashCode() in Occupant when jid is null
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-766'>SMACK-766</a>] - Smack possibly includes &#39;ask&#39; attribute in roster items when sending requests
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-768'>SMACK-768</a>] - Smack throws NoResponse timeout when waiting for IQ although there was a response
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-771'>SMACK-771</a>] - XMPPTCPConnection should use KeyManagerFactory.getDefaultAlgorithm() instead of KeyManagerFactory.getInstance(&quot;sunX509&quot;);
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-772'>SMACK-772</a>] - HostAddress must deal with &#39;fqdn&#39; being null.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-773'>SMACK-773</a>] - Allow roster pushes from our full JID for backwards compatibility
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-774'>SMACK-774</a>] - HTTP File Upload&#39;s SlotRequest metadata should be attributes not child elements
</li>
</ul>
<h2> New Feature
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-746'>SMACK-746</a>] - Add support for XEP-0380: Explicit Message Encryption
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-758'>SMACK-758</a>] - Add support for XEP-0334: Message Processing Hints
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-760'>SMACK-760</a>] - Smack does not allow custom extension elements in SM&#39;s &lt;failed/&gt;
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-752'>SMACK-752</a>] - XEP-0357 Push Notification enable IQ uses wrong form type: Should be &#39;submit&#39; instead of &#39;form&#39;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-754'>SMACK-754</a>] - Allow MUC room subject changes from the MUCs bare JID
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-777'>SMACK-777</a>] - MamManager should use the user&#39;s bare JID to check if MAM is supported
</li>
</ul>
<h2>4.2.0 -- <span style="font-weight: normal;">2017-03-10</span></h2> <h2>4.2.0 -- <span style="font-weight: normal;">2017-03-10</span></h2>
<h2> Sub-task <h2> Sub-task

View File

@ -255,7 +255,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final ExecutorService cachedExecutorService = Executors.newCachedThreadPool( private final ExecutorService cachedExecutorService = Executors.newCachedThreadPool(
// @formatter:off // @formatter:off
// CHECKSTYLE:OFF // CHECKSTYLE:OFF
new SmackExecutorThreadFactory( // threadFactory new SmackExecutorThreadFactory(
this, this,
"Cached Executor" "Cached Executor"
) )

View File

@ -41,7 +41,7 @@ public interface StanzaListener {
/** /**
* Process the next stanza(/packet) sent to this stanza(/packet) listener. * Process the next stanza(/packet) sent to this stanza(/packet) listener.
* <p> * <p>
* A single thread is responsible for invoking all listeners, so * If this listener is synchronous, then a single thread is responsible for invoking all listeners, so
* it's very important that implementations of this method not block * it's very important that implementations of this method not block
* for any extended period of time. * for any extended period of time.
* </p> * </p>

View File

@ -75,6 +75,22 @@ public class HostAddress {
setException(e); setException(e);
} }
public String getHost() {
if (fqdn != null) {
return fqdn;
}
// In this case, the HostAddress(int, InetAddress) constructor must been used. We have no FQDN. And
// inetAddresses.size() must be exactly one.
assert inetAddresses.size() == 1;
return inetAddresses.get(0).getHostAddress();
}
/**
* Return the fully qualified domain name. This may return <code>null</code> in case there host address is only numeric, i.e. an IP address.
*
* @return the fully qualified domain name or <code>null</code>
*/
public String getFQDN() { public String getFQDN() {
return fqdn; return fqdn;
} }
@ -109,7 +125,7 @@ public class HostAddress {
@Override @Override
public String toString() { public String toString() {
return fqdn + ":" + port; return getHost() + ":" + port;
} }
@Override @Override
@ -123,7 +139,7 @@ public class HostAddress {
final HostAddress address = (HostAddress) o; final HostAddress address = (HostAddress) o;
if (!fqdn.equals(address.fqdn)) { if (!getHost().equals(address.getHost())) {
return false; return false;
} }
return port == address.port; return port == address.port;
@ -132,7 +148,7 @@ public class HostAddress {
@Override @Override
public int hashCode() { public int hashCode() {
int result = 1; int result = 1;
result = 37 * result + fqdn.hashCode(); result = 37 * result + getHost().hashCode();
return result * 37 + port; return result * 37 + port;
} }

View File

@ -32,9 +32,9 @@ public class SlotRequest extends IQ {
public static final String ELEMENT = "request"; public static final String ELEMENT = "request";
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE; public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE;
private final String filename; protected final String filename;
private final long size; protected final long size;
private final String contentType; protected final String contentType;
public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size) { public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size) {
this(uploadServiceAddress, filename, size, null); this(uploadServiceAddress, filename, size, null);
@ -82,10 +82,10 @@ public class SlotRequest extends IQ {
@Override @Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.rightAngleBracket(); xml.attribute("filename", filename);
xml.element("filename", filename); xml.attribute("size", String.valueOf(size));
xml.element("size", String.valueOf(size)); xml.optAttribute("content-type", contentType);
xml.optElement("content-type", contentType); xml.setEmptyElement();
return xml; return xml;
} }
} }

View File

@ -40,4 +40,13 @@ public class SlotRequest_V0_2 extends SlotRequest {
public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) { public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
super(uploadServiceAddress, filename, size, contentType, NAMESPACE); super(uploadServiceAddress, filename, size, contentType, NAMESPACE);
} }
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.rightAngleBracket();
xml.element("filename", filename);
xml.element("size", String.valueOf(size));
xml.optElement("content-type", contentType);
return xml;
}
} }

View File

@ -50,6 +50,7 @@ import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
@ -103,9 +104,12 @@ public final class MamManager extends Manager {
private final Jid archiveAddress; private final Jid archiveAddress;
private final ServiceDiscoveryManager serviceDiscoveryManager;
private MamManager(XMPPConnection connection, Jid archiveAddress) { private MamManager(XMPPConnection connection, Jid archiveAddress) {
super(connection); super(connection);
this.archiveAddress = archiveAddress; this.archiveAddress = archiveAddress;
serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
} }
/** /**
@ -586,6 +590,23 @@ public final class MamManager extends Manager {
} }
} }
/**
* Check if MAM is supported for the XMPP connection managed by this MamManager.
*
* @return true if MAM is supported for the XMPP connection, <code>false</code>otherwhise.
*
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @since 4.2.1
* @see <a href="https://xmpp.org/extensions/xep-0313.html#support">XEP-0313 § 7. Determining support</a>
*/
public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
BareJid myBareJid = connection().getUser().asBareJid();
return serviceDiscoveryManager.supportsFeature(myBareJid, MamElements.NAMESPACE);
}
/** /**
* Returns true if Message Archive Management is supported by the server. * Returns true if Message Archive Management is supported by the server.
* *
@ -594,7 +615,10 @@ public final class MamManager extends Manager {
* @throws XMPPErrorException * @throws XMPPErrorException
* @throws NoResponseException * @throws NoResponseException
* @throws InterruptedException * @throws InterruptedException
* @depreacted use {@link #isSupported()} instead.
*/ */
@Deprecated
// TODO Remove in Smack 4.3
public boolean isSupportedByServer() public boolean isSupportedByServer()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(MamElements.NAMESPACE); return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(MamElements.NAMESPACE);

View File

@ -16,49 +16,52 @@
*/ */
package org.jivesoftware.smackx.httpfileupload; package org.jivesoftware.smackx.httpfileupload;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import java.io.IOException;
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest; import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.jxmpp.jid.JidTestUtil; import org.jxmpp.jid.JidTestUtil;
import org.jxmpp.stringprep.XmppStringprepException; import org.xml.sax.SAXException;
public class SlotRequestCreateTest { public class SlotRequestCreateTest {
String testRequest String testRequest
= "<request xmlns='urn:xmpp:http:upload:0'>" = "<request xmlns='urn:xmpp:http:upload:0'"
+ "<filename>my_juliet.png</filename>" + " filename='my_juliet.png'"
+ "<size>23456</size>" + " size='23456'"
+ "<content-type>image/jpeg</content-type>" + " content-type='image/jpeg'"
+ "</request>"; + "/>";
String testRequestWithoutContentType String testRequestWithoutContentType
= "<request xmlns='urn:xmpp:http:upload:0'>" = "<request xmlns='urn:xmpp:http:upload:0'"
+ "<filename>my_romeo.png</filename>" + " filename='my_romeo.png'"
+ "<size>52523</size>" + " size='52523'"
+ "</request>"; + "/>";
@Test @Test
public void checkSlotRequestCreation() throws XmppStringprepException { public void checkSlotRequestCreation() throws SAXException, IOException {
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 23456, "image/jpeg"); SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 23456, "image/jpeg");
Assert.assertEquals("my_juliet.png", slotRequest.getFilename()); Assert.assertEquals("my_juliet.png", slotRequest.getFilename());
Assert.assertEquals(23456, slotRequest.getSize()); Assert.assertEquals(23456, slotRequest.getSize());
Assert.assertEquals("image/jpeg", slotRequest.getContentType()); Assert.assertEquals("image/jpeg", slotRequest.getContentType());
Assert.assertEquals(testRequest, slotRequest.getChildElementXML().toString()); assertXMLEqual(testRequest, slotRequest.getChildElementXML().toString());
} }
@Test @Test
public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException { public void checkSlotRequestCreationWithoutContentType() throws SAXException, IOException {
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_romeo.png", 52523); SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_romeo.png", 52523);
Assert.assertEquals("my_romeo.png", slotRequest.getFilename()); Assert.assertEquals("my_romeo.png", slotRequest.getFilename());
Assert.assertEquals(52523, slotRequest.getSize()); Assert.assertEquals(52523, slotRequest.getSize());
Assert.assertEquals(null, slotRequest.getContentType()); Assert.assertEquals(null, slotRequest.getContentType());
Assert.assertEquals(testRequestWithoutContentType, slotRequest.getChildElementXML().toString()); assertXMLEqual(testRequestWithoutContentType, slotRequest.getChildElementXML().toString());
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)

View File

@ -159,7 +159,7 @@ public final class BoBManager extends Manager {
public BoBInfo addBoB(BoBData bobData) { public BoBInfo addBoB(BoBData bobData) {
// We only support SHA-1 for now. // We only support SHA-1 for now.
BoBHash bobHash = new BoBHash("sha1", SHA1.hex(bobData.getContent())); BoBHash bobHash = new BoBHash(SHA1.hex(bobData.getContent()), "sha1");
Set<BoBHash> bobHashes = Collections.singleton(bobHash); Set<BoBHash> bobHashes = Collections.singleton(bobHash);
bobHashes = Collections.unmodifiableSet(bobHashes); bobHashes = Collections.unmodifiableSet(bobHashes);

View File

@ -1011,7 +1011,7 @@ public class MultiUserChat {
* *
* @param presenceInterceptor the stanza(/packet) interceptor to remove. * @param presenceInterceptor the stanza(/packet) interceptor to remove.
*/ */
public void removePresenceInterceptor(StanzaListener presenceInterceptor) { public void removePresenceInterceptor(PresenceListener presenceInterceptor) {
presenceInterceptors.remove(presenceInterceptor); presenceInterceptors.remove(presenceInterceptor);
} }

View File

@ -39,6 +39,7 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.pubsub.EventElement; import org.jivesoftware.smackx.pubsub.EventElement;
import org.jivesoftware.smackx.pubsub.Item; import org.jivesoftware.smackx.pubsub.Item;
import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import org.jivesoftware.smackx.pubsub.PubSubFeature; import org.jivesoftware.smackx.pubsub.PubSubFeature;
import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter; import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter;
@ -137,9 +138,10 @@ public final class PEPManager extends Manager {
* @throws InterruptedException * @throws InterruptedException
* @throws XMPPErrorException * @throws XMPPErrorException
* @throws NoResponseException * @throws NoResponseException
* @throws NotAPubSubNodeException
*/ */
public void publish(Item item, String node) throws NotConnectedException, InterruptedException, public void publish(Item item, String node) throws NotConnectedException, InterruptedException,
NoResponseException, XMPPErrorException { NoResponseException, XMPPErrorException, NotAPubSubNodeException {
XMPPConnection connection = connection(); XMPPConnection connection = connection();
PubSubManager pubSubManager = PubSubManager.getInstance(connection, connection.getUser().asEntityBareJid()); PubSubManager pubSubManager = PubSubManager.getInstance(connection, connection.getUser().asEntityBareJid());
LeafNode pubSubNode = pubSubManager.getNode(node); LeafNode pubSubNode = pubSubManager.getNode(node);

View File

@ -1,45 +0,0 @@
/**
*
* Copyright 2017 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.pubsub;
import org.jxmpp.jid.BareJid;
public abstract class PubSubAssertionError extends AssertionError {
/**
*
*/
private static final long serialVersionUID = 1L;
protected PubSubAssertionError(String message) {
super(message);
}
public static class DiscoInfoNodeAssertionError extends PubSubAssertionError {
/**
*
*/
private static final long serialVersionUID = 1L;
DiscoInfoNodeAssertionError(BareJid pubSubService, String nodeId) {
super("PubSub service '" + pubSubService + "' returned disco info result for node '" + nodeId
+ "', but it did not contain an Identity of type 'leaf' or 'collection' (and category 'pubsub'), which is not allowed according to XEP-60 5.3.");
}
}
}

View File

@ -18,6 +18,8 @@ package org.jivesoftware.smackx.pubsub;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
public abstract class PubSubException extends SmackException { public abstract class PubSubException extends SmackException {
@ -27,6 +29,16 @@ public abstract class PubSubException extends SmackException {
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final String nodeId;
protected PubSubException(String nodeId) {
this.nodeId = nodeId;
}
public String getNodeId() {
return nodeId;
}
public static class NotALeafNodeException extends PubSubException { public static class NotALeafNodeException extends PubSubException {
/** /**
@ -34,21 +46,35 @@ public abstract class PubSubException extends SmackException {
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final String nodeId;
private final BareJid pubSubService; private final BareJid pubSubService;
NotALeafNodeException(String nodeId, BareJid pubSubService) { NotALeafNodeException(String nodeId, BareJid pubSubService) {
this.nodeId = nodeId; super(nodeId);
this.pubSubService = pubSubService; this.pubSubService = pubSubService;
} }
public String getNodeId() {
return nodeId;
}
public BareJid getPubSubService() { public BareJid getPubSubService() {
return pubSubService; return pubSubService;
} }
} }
public static class NotAPubSubNodeException extends PubSubException {
/**
*
*/
private static final long serialVersionUID = 1L;
private final DiscoverInfo discoverInfo;
NotAPubSubNodeException(String nodeId, DiscoverInfo discoverInfo) {
super(nodeId);
this.discoverInfo = discoverInfo;
}
public DiscoverInfo getDiscoverInfo() {
return discoverInfo;
}
}
} }

View File

@ -42,6 +42,7 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
import org.jivesoftware.smackx.pubsub.util.NodeUtils; import org.jivesoftware.smackx.pubsub.util.NodeUtils;
@ -229,8 +230,9 @@ public final class PubSubManager extends Manager {
* @throws NoResponseException if there was no response from the server. * @throws NoResponseException if there was no response from the server.
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
* @throws NotAPubSubNodeException
*/ */
public <T extends Node> T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException public <T extends Node> T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotAPubSubNodeException
{ {
Node node = nodeMap.get(id); Node node = nodeMap.get(id);
@ -249,10 +251,7 @@ public final class PubSubManager extends Manager {
node = new CollectionNode(this, id); node = new CollectionNode(this, id);
} }
else { else {
// XEP-60 5.3 states that throw new PubSubException.NotAPubSubNodeException(id, infoReply);
// "The 'disco#info' result MUST include an identity with a category of 'pubsub' and a type of either 'leaf' or 'collection'."
// If this is not the case, then we are dealing with an PubSub implementation that doesn't follow the specification.
throw new PubSubAssertionError.DiscoInfoNodeAssertionError(pubSubService, id);
} }
nodeMap.put(id, node); nodeMap.put(id, node);
} }
@ -278,6 +277,9 @@ public final class PubSubManager extends Manager {
try { try {
return getNode(id); return getNode(id);
} }
catch (NotAPubSubNodeException e) {
return createNode(id);
}
catch (XMPPErrorException e1) { catch (XMPPErrorException e1) {
if (e1.getXMPPError().getCondition() == Condition.item_not_found) { if (e1.getXMPPError().getCondition() == Condition.item_not_found) {
try { try {
@ -286,7 +288,13 @@ public final class PubSubManager extends Manager {
catch (XMPPErrorException e2) { catch (XMPPErrorException e2) {
if (e2.getXMPPError().getCondition() == Condition.conflict) { if (e2.getXMPPError().getCondition() == Condition.conflict) {
// The node was created in the meantime, re-try getNode(). Note that this case should be rare. // The node was created in the meantime, re-try getNode(). Note that this case should be rare.
return getNode(id); try {
return getNode(id);
}
catch (NotAPubSubNodeException e) {
// Should not happen
throw new IllegalStateException(e);
}
} }
throw e2; throw e2;
} }
@ -313,10 +321,11 @@ public final class PubSubManager extends Manager {
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
* @throws XMPPErrorException * @throws XMPPErrorException
* @throws NotAPubSubNodeException
* @since 4.2.1 * @since 4.2.1
*/ */
public LeafNode getLeafNode(String id) throws NotALeafNodeException, NoResponseException, NotConnectedException, public LeafNode getLeafNode(String id) throws NotALeafNodeException, NoResponseException, NotConnectedException,
InterruptedException, XMPPErrorException { InterruptedException, XMPPErrorException, NotAPubSubNodeException {
Node node; Node node;
try { try {
node = getNode(id); node = getNode(id);

View File

@ -1676,8 +1676,8 @@ public final class Roster extends Manager {
final XMPPConnection connection = connection(); final XMPPConnection connection = connection();
RosterPacket rosterPacket = (RosterPacket) iqRequest; RosterPacket rosterPacket = (RosterPacket) iqRequest;
EntityFullJid localAddress = connection.getUser(); EntityFullJid ourFullJid = connection.getUser();
if (localAddress == null) { if (ourFullJid == null) {
LOGGER.warning("Ignoring roster push " + iqRequest + " while " + connection LOGGER.warning("Ignoring roster push " + iqRequest + " while " + connection
+ " has no bound resource. This may be a server bug."); + " has no bound resource. This may be a server bug.");
return null; return null;
@ -1685,12 +1685,23 @@ public final class Roster extends Manager {
// Roster push (RFC 6121, 2.1.6) // Roster push (RFC 6121, 2.1.6)
// A roster push with a non-empty from not matching our address MUST be ignored // A roster push with a non-empty from not matching our address MUST be ignored
EntityBareJid jid = localAddress.asEntityBareJid(); EntityBareJid ourBareJid = ourFullJid.asEntityBareJid();
Jid from = rosterPacket.getFrom(); Jid from = rosterPacket.getFrom();
if (from != null && !from.equals(jid)) { if (from != null) {
LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from if (from.equals(ourFullJid)) {
+ "'"); // Since RFC 6121 roster pushes are no longer allowed to
return IQ.createErrorResponse(iqRequest, Condition.service_unavailable); // origin from the full JID as it was the case with RFC
// 3921. Log a warning an continue processing the push.
// See also SMACK-773.
LOGGER.warning(
"Received roster push from full JID. This behavior is since RFC 6121 not longer standard compliant. "
+ "Please ask your server vendor to fix this and comply to RFC 6121 § 2.1.6. IQ roster push stanza: "
+ iqRequest);
} else if (!from.equals(ourBareJid)) {
LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + ourBareJid + "' from='"
+ from + "'");
return IQ.createErrorResponse(iqRequest, Condition.service_unavailable);
}
} }
// A roster push must contain exactly one entry // A roster push must contain exactly one entry

View File

@ -46,7 +46,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
mamManagerConTwo = MamManager.getInstanceFor(conTwo); mamManagerConTwo = MamManager.getInstanceFor(conTwo);
if (!mamManagerConTwo.isSupportedByServer()) { if (!mamManagerConTwo.isSupported()) {
throw new TestNotPossibleException("Message Archive Management (XEP-0313) is not supported by the server."); throw new TestNotPossibleException("Message Archive Management (XEP-0313) is not supported by the server.");
} }

View File

@ -30,6 +30,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.util.OmemoConstants; import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
@ -52,9 +53,10 @@ public class OmemoInitializationTest extends AbstractOmemoIntegrationTest {
/** /**
* Tests, if the initialization is done properly. * Tests, if the initialization is done properly.
* @throws NotAPubSubNodeException
*/ */
@SmackIntegrationTest @SmackIntegrationTest
public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException { public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException, NotAPubSubNodeException {
//test keys. //test keys.
setUpOmemoManager(alice); setUpOmemoManager(alice);
assertNotNull("IdentityKey must not be null after initialization.", store.loadOmemoIdentityKeyPair(alice)); assertNotNull("IdentityKey must not be null after initialization.", store.loadOmemoIdentityKeyPair(alice));

View File

@ -34,8 +34,8 @@ import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionExcep
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.util.OmemoConstants; import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubAssertionError;
import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.PubSubManager;
/** /**
@ -70,26 +70,26 @@ final class OmemoIntegrationTestHelper {
for (int id : deviceList.getAllDevices()) { for (int id : deviceList.getAllDevices()) {
try { try {
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems(); pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) { } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) {
//Silent //Silent
} }
try { try {
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)); pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) { } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
//Silent //Silent
} }
} }
try { try {
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems(); pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) { } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) {
//Silent //Silent
} }
try { try {
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST); pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) { } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
//Silent //Silent
} }
} }
@ -147,7 +147,7 @@ final class OmemoIntegrationTestHelper {
} }
static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException { static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
omemoManager.initialize(); omemoManager.initialize();
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice()); OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
assertNotNull("Bundle must not be null.", bundle); assertNotNull("Bundle must not be null.", bundle);

View File

@ -40,6 +40,7 @@ import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -85,9 +86,15 @@ public class OmemoMessageSendingTest extends AbstractOmemoIntegrationTest {
* @throws UndecidedOmemoIdentityException * @throws UndecidedOmemoIdentityException
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
* @throws CryptoFailedException * @throws CryptoFailedException
* @throws NotAPubSubNodeException
*/ */
@SmackIntegrationTest @SmackIntegrationTest
public void messageSendingTest() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException { public void messageSendingTest()
throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
SmackException.NotLoggedInException, PubSubException.NotALeafNodeException,
CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
CryptoFailedException, PubSubException.NotAPubSubNodeException {
final String alicesSecret = "Hey Bob! I love you!"; final String alicesSecret = "Hey Bob! I love you!";
final String bobsSecret = "I love you too, Alice."; //aww <3 final String bobsSecret = "I love you too, Alice."; //aww <3

View File

@ -19,7 +19,6 @@ package org.jivesoftware.smackx.omemo;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.BODY_OMEMO_HINT; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.BODY_OMEMO_HINT;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL; import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,8 +31,8 @@ import java.util.WeakHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener;
import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
@ -41,6 +40,7 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.carbons.CarbonManager; import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
@ -78,7 +78,6 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
@ -110,8 +109,29 @@ public final class OmemoManager extends Manager {
*/ */
private OmemoManager(XMPPConnection connection, int deviceId) { private OmemoManager(XMPPConnection connection, int deviceId) {
super(connection); super(connection);
setConnectionListener();
this.deviceId = deviceId; this.deviceId = deviceId;
connection.addConnectionListener(new AbstractConnectionListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
if (resumed) {
return;
}
Async.go(new Runnable() {
@Override
public void run() {
try {
initialize();
} catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: "
+ e.getMessage());
}
}
});
}
});
service = OmemoService.getInstance(); service = OmemoService.getInstance();
} }
@ -163,13 +183,13 @@ public final class OmemoManager extends Manager {
} }
} }
int defaulDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user); int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user);
if (defaulDeviceId < 1) { if (defaultDeviceId < 1) {
defaulDeviceId = randomDeviceId(); defaultDeviceId = randomDeviceId();
OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaulDeviceId); OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId);
} }
return getInstanceFor(connection, defaulDeviceId); return getInstanceFor(connection, defaultDeviceId);
} }
/** /**
@ -471,24 +491,6 @@ public final class OmemoManager extends Manager {
.getActiveDevices().isEmpty(); .getActiveDevices().isEmpty();
} }
/**
* Returns true, if the device resource has announced OMEMO support.
* Throws an IllegalArgumentException if the provided FullJid does not have a resource part.
*
* @param fullJid jid of a resource
* @return true if resource supports OMEMO
* @throws XMPPException.XMPPErrorException if
* @throws SmackException.NotConnectedException something
* @throws InterruptedException goes
* @throws SmackException.NoResponseException wrong
*/
public boolean resourceSupportsOmemo(FullJid fullJid) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
if (fullJid.hasNoResource()) {
throw new IllegalArgumentException("Jid " + fullJid + " has no resource part.");
}
return ServiceDiscoveryManager.getInstanceFor(connection()).discoverInfo(fullJid).containsFeature(PEP_NODE_DEVICE_LIST_NOTIFY);
}
/** /**
* Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite * Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite
* for OMEMO encryption in MUC). * for OMEMO encryption in MUC).
@ -641,54 +643,6 @@ public final class OmemoManager extends Manager {
} }
} }
private void setConnectionListener() {
connection().addConnectionListener(new ConnectionListener() {
@Override
public void connected(XMPPConnection connection) {
LOGGER.log(Level.INFO, "connected");
}
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
LOGGER.log(Level.INFO, "authenticated. Resumed: " + resumed);
if (resumed) {
return;
}
try {
initialize();
} catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: "
+ e.getMessage());
}
}
@Override
public void connectionClosed() {
}
@Override
public void connectionClosedOnError(Exception e) {
connectionClosed();
}
@Override
public void reconnectionSuccessful() {
}
@Override
public void reconnectingIn(int seconds) {
}
@Override
public void reconnectionFailed(Exception e) {
}
});
}
public static int randomDeviceId() { public static int randomDeviceId() {
int i = new Random().nextInt(Integer.MAX_VALUE); int i = new Random().nextInt(Integer.MAX_VALUE);

View File

@ -39,7 +39,6 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
@ -51,6 +50,7 @@ import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener; import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
import org.jivesoftware.smackx.carbons.CarbonManager; import org.jivesoftware.smackx.carbons.CarbonManager;
@ -82,12 +82,13 @@ import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
import org.jivesoftware.smackx.pep.PEPManager; import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubAssertionError;
import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;
/** /**
* This class contains OMEMO related logic and registers listeners etc. * This class contains OMEMO related logic and registers listeners etc.
@ -427,11 +428,11 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws XMPPException.XMPPErrorException * @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException * @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException * @throws SmackException.NoResponseException
* @throws PubSubAssertionError.DiscoInfoNodeAssertionError ejabberd bug: https://github.com/processone/ejabberd/issues/1717 * @throws NotAPubSubNodeException
*/ */
static LeafNode fetchDeviceListNode(OmemoManager omemoManager, BareJid contact) static LeafNode fetchDeviceListNode(OmemoManager omemoManager, BareJid contact)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, PubSubAssertionError.DiscoInfoNodeAssertionError { SmackException.NotConnectedException, SmackException.NoResponseException, NotAPubSubNodeException {
return PubSubManager.getInstance(omemoManager.getConnection(), contact).getLeafNode(PEP_NODE_DEVICE_LIST); return PubSubManager.getInstance(omemoManager.getConnection(), contact).getLeafNode(PEP_NODE_DEVICE_LIST);
} }
@ -446,9 +447,11 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws InterruptedException goes * @throws InterruptedException goes
* @throws SmackException.NoResponseException wrong * @throws SmackException.NoResponseException wrong
* @throws PubSubException.NotALeafNodeException when the device lists node is not a LeafNode * @throws PubSubException.NotALeafNodeException when the device lists node is not a LeafNode
* @throws PubSubAssertionError.DiscoInfoNodeAssertionError ejabberd bug: https://github.com/processone/ejabberd/issues/1717 * @throws NotAPubSubNodeException
*/ */
static OmemoDeviceListElement fetchDeviceList(OmemoManager omemoManager, BareJid contact) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException, PubSubAssertionError.DiscoInfoNodeAssertionError { static OmemoDeviceListElement fetchDeviceList(OmemoManager omemoManager, BareJid contact)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
return extractDeviceListFrom(fetchDeviceListNode(omemoManager, contact)); return extractDeviceListFrom(fetchDeviceListNode(omemoManager, contact));
} }
@ -482,9 +485,9 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
e.getMessage()); e.getMessage());
} }
catch (PubSubAssertionError.DiscoInfoNodeAssertionError bug) { catch (PubSubException.NotAPubSubNodeException e) {
LOGGER.log(Level.WARNING, "Caught a PubSubAssertionError when fetching a deviceList node. " + LOGGER.log(Level.WARNING, "Caught a PubSubAssertionError when fetching a deviceList node. " +
"This probably means that we're dealing with an ejabberd server and the LeafNode does not exist."); "This probably means that we're dealing with an ejabberd server and the LeafNode does not exist.", e);
return true; return true;
} }
return false; return false;
@ -506,9 +509,8 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact + ": " + e, e); LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact + ": " + e, e);
return; return;
} }
catch (PubSubAssertionError.DiscoInfoNodeAssertionError bug) { catch (NotAPubSubNodeException e) {
LOGGER.log(Level.WARNING, "Caught a PubSubAssertionError when fetching a deviceList node. " + LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact ,e);
"This probably means that the LeafNode does not exist.");
return; return;
} }
@ -526,9 +528,13 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @throws InterruptedException goes * @throws InterruptedException goes
* @throws SmackException.NoResponseException wrong * @throws SmackException.NoResponseException wrong
* @throws PubSubException.NotALeafNodeException when the bundles node is not a LeafNode * @throws PubSubException.NotALeafNodeException when the bundles node is not a LeafNode
* @throws NotAPubSubNodeException
*/ */
static OmemoBundleVAxolotlElement fetchBundle(OmemoManager omemoManager, OmemoDevice contact) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException, PubSubAssertionError.DiscoInfoNodeAssertionError { static OmemoBundleVAxolotlElement fetchBundle(OmemoManager omemoManager, OmemoDevice contact)
LeafNode node = PubSubManager.getInstance(omemoManager.getConnection(), contact.getJid()).getLeafNode(PEP_NODE_BUNDLE_FROM_DEVICE_ID(contact.getDeviceId())); throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
LeafNode node = PubSubManager.getInstance(omemoManager.getConnection(), contact.getJid()).getLeafNode(
PEP_NODE_BUNDLE_FROM_DEVICE_ID(contact.getDeviceId()));
return extractBundleFrom(node); return extractBundleFrom(node);
} }
@ -665,7 +671,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
OmemoBundleVAxolotlElement bundle; OmemoBundleVAxolotlElement bundle;
try { try {
bundle = fetchBundle(omemoManager, device); bundle = fetchBundle(omemoManager, device);
} catch (SmackException | XMPPException.XMPPErrorException | InterruptedException | PubSubAssertionError e) { } catch (SmackException | XMPPException.XMPPErrorException | InterruptedException e) {
throw new CannotEstablishOmemoSessionException(device, e); throw new CannotEstablishOmemoSessionException(device, e);
} }
@ -1177,7 +1183,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
*/ */
private static OmemoDevice getSender(OmemoManager omemoManager, Stanza stanza) { private static OmemoDevice getSender(OmemoManager omemoManager, Stanza stanza) {
OmemoElement omemoElement = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); OmemoElement omemoElement = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
BareJid sender = stanza.getFrom().asBareJid(); Jid sender = stanza.getFrom();
if (isMucMessage(omemoManager, stanza)) { if (isMucMessage(omemoManager, stanza)) {
MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
MultiUserChat muc = mucm.getMultiUserChat(sender.asEntityBareJidIfPossible()); MultiUserChat muc = mucm.getMultiUserChat(sender.asEntityBareJidIfPossible());
@ -1186,7 +1192,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
if (sender == null) { if (sender == null) {
throw new AssertionError("Sender is null."); throw new AssertionError("Sender is null.");
} }
return new OmemoDevice(sender, omemoElement.getHeader().getSid()); return new OmemoDevice(sender.asBareJid(), omemoElement.getHeader().getSid());
} }
/** /**
@ -1309,7 +1315,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
@Override @Override
public void onCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) { public void onCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
if (filter.accept(carbonCopy)) { if (filter.accept(carbonCopy)) {
OmemoDevice senderDevice = getSender(omemoManager, carbonCopy); final OmemoDevice senderDevice = getSender(omemoManager, carbonCopy);
Message decrypted; Message decrypted;
MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
OmemoElement omemoMessage = carbonCopy.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); OmemoElement omemoMessage = carbonCopy.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
@ -1365,16 +1371,22 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO carbon copy: " LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO carbon copy: "
+ e.getMessage()); + e.getMessage());
} catch (NoRawSessionException e) { } catch (final NoRawSessionException e) {
try { Async.go(new Runnable() {
LOGGER.log(Level.INFO, "Received OMEMO carbon copy message with invalid session from " + @Override
senderDevice + ". Send RatchetUpdateMessage."); public void run() {
service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true); try {
LOGGER.log(Level.INFO, "Received OMEMO carbon copy message with invalid session from " +
senderDevice + ". Send RatchetUpdateMessage.");
service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true);
} catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) {
LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO carbon message: "
+ e.getMessage());
}
}
});
} catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) {
LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO carbon message: "
+ e.getMessage());
}
} }
} }
} }

View File

@ -35,20 +35,20 @@ public class UndecidedOmemoIdentityException extends Exception {
} }
/** /**
* Return the HashSet of untrusted devices. * Return the HashSet of undecided devices.
* *
* @return untrusted devices * @return undecided devices
*/ */
public HashSet<OmemoDevice> getUntrustedDevices() { public HashSet<OmemoDevice> getUndecidedDevices() {
return this.devices; return this.devices;
} }
/** /**
* Add all untrusted devices of another Exception to this Exceptions HashSet of untrusted devices. * Add all undecided devices of another Exception to this Exceptions HashSet of undecided devices.
* *
* @param other other Exception * @param other other Exception
*/ */
public void join(UndecidedOmemoIdentityException other) { public void join(UndecidedOmemoIdentityException other) {
this.devices.addAll(other.getUntrustedDevices()); this.devices.addAll(other.getUndecidedDevices());
} }
} }

View File

@ -45,18 +45,18 @@ public class OmemoExceptionsTest {
OmemoDevice mallory = new OmemoDevice(JidCreate.bareFrom("mallory@server.tld"), 9876); OmemoDevice mallory = new OmemoDevice(JidCreate.bareFrom("mallory@server.tld"), 9876);
UndecidedOmemoIdentityException u = new UndecidedOmemoIdentityException(alice); UndecidedOmemoIdentityException u = new UndecidedOmemoIdentityException(alice);
assertTrue(u.getUntrustedDevices().contains(alice)); assertTrue(u.getUndecidedDevices().contains(alice));
assertTrue(u.getUntrustedDevices().size() == 1); assertTrue(u.getUndecidedDevices().size() == 1);
UndecidedOmemoIdentityException v = new UndecidedOmemoIdentityException(bob); UndecidedOmemoIdentityException v = new UndecidedOmemoIdentityException(bob);
v.getUntrustedDevices().add(mallory); v.getUndecidedDevices().add(mallory);
assertTrue(v.getUntrustedDevices().size() == 2); assertTrue(v.getUndecidedDevices().size() == 2);
assertTrue(v.getUntrustedDevices().contains(bob)); assertTrue(v.getUndecidedDevices().contains(bob));
assertTrue(v.getUntrustedDevices().contains(mallory)); assertTrue(v.getUndecidedDevices().contains(mallory));
u.getUntrustedDevices().add(bob); u.getUndecidedDevices().add(bob);
u.join(v); u.join(v);
assertTrue(u.getUntrustedDevices().size() == 3); assertTrue(u.getUndecidedDevices().size() == 3);
} }
@Test @Test

View File

@ -563,7 +563,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
for (HostAddress hostAddress : hostAddresses) { for (HostAddress hostAddress : hostAddresses) {
Iterator<InetAddress> inetAddresses = null; Iterator<InetAddress> inetAddresses = null;
String host = hostAddress.getFQDN(); String host = hostAddress.getHost();
int port = hostAddress.getPort(); int port = hostAddress.getPort();
if (proxyInfo == null) { if (proxyInfo == null) {
inetAddresses = hostAddress.getInetAddresses().iterator(); inetAddresses = hostAddress.getInetAddresses().iterator();
@ -737,7 +737,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
if (ks != null) { if (ks != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
try { try {
if (pcb == null) { if (pcb == null) {
kmf.init(ks, null); kmf.init(ks, null);