diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 38f20c88..564e8cc2 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -48,6 +48,7 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -214,7 +215,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store); for (String address : store.getKnownAddresses()) { deviceIds = store.getSubDeviceSessions(address); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+" adding device ids for "+address+" "+deviceIds); putDevicesForJid(address, deviceIds, store); } @@ -437,16 +437,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } Set deviceIds = new HashSet<>(); deviceIds.add(getOwnDeviceId()); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, PublishOptions.openAccess(), null); - } - } - }); + publishDeviceIdsAndRefineAccessModel(deviceIds); } public void distrustFingerprint(final String fingerprint) { @@ -513,17 +504,40 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { numPublishTriesOnEmptyPep = 0; } deviceIdsCopy.add(getOwnDeviceId()); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); + publishDeviceIdsAndRefineAccessModel(deviceIdsCopy); + } + + private void publishDeviceIdsAndRefineAccessModel(Set ids) { + publishDeviceIdsAndRefineAccessModel(ids,true); + } + + private void publishDeviceIdsAndRefineAccessModel(final Set ids, final boolean firstAttempt) { + final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); ownPushPending.set(true); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - ownPushPending.set(false); - if (packet.getType() == IqPacket.TYPE.ERROR) { - pepBroken = true; - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); - } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { - mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, PublishOptions.openAccess(), null); + Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; + if (firstAttempt && error != null && error.hasChild("precondition-not-met",Namespace.PUBSUB_ERROR)) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": precondition wasn't met for device list. pushing node configuration"); + mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + publishDeviceIdsAndRefineAccessModel(ids, false); + } + + @Override + public void onPushFailed() { + publishDeviceIdsAndRefineAccessModel(ids, false); + } + }); + } else { + ownPushPending.set(false); + if (packet.getType() == IqPacket.TYPE.ERROR) { + pepBroken = true; + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); + } } } }); @@ -683,32 +697,45 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Set preKeyRecords, final boolean announceAfter, final boolean wipe) { + publishDeviceBundle(signedPreKeyRecord,preKeyRecords,announceAfter,wipe,true); + } + + private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord, + final Set preKeyRecords, + final boolean announceAfter, + final boolean wipe, + final boolean firstAttempt) { + final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), - preKeyRecords, getOwnDeviceId()); + preKeyRecords, getOwnDeviceId(),publishOptions); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { + Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; + if (firstAttempt && error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR)) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": precondition wasn't met for bundle. pushing node configuration"); final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); - mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { + mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { @Override public void onPushSucceeded() { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); - if (wipe) { - wipeOtherPepDevices(); - } else if (announceAfter) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); - } + publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false); } @Override public void onPushFailed() { - Log.d(Config.LOGTAG,"unable to change access model for pubsub node"); + publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false); } }); + } if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } } else if (packet.getType() == IqPacket.TYPE.ERROR) { pepBroken = true; Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index ed16cfc7..ddca539a 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -91,16 +91,24 @@ public class IqGenerator extends AbstractGenerator { return packet; } - protected IqPacket publish(final String node, final Element item) { + protected IqPacket publish(final String node, final Element item, final Bundle options) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); final Element publish = pubsub.addChild("publish"); publish.setAttribute("node", node); publish.addChild(item); + if (options != null) { + final Element publishOptions = pubsub.addChild("publish-options"); + publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options)); + } return packet; } + protected IqPacket publish(final String node, final Element item) { + return publish(node,item,null); + } + protected IqPacket retrieve(String node, Element item) { final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Element pubsub = packet.addChild("pubsub", @@ -184,7 +192,7 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket publishDeviceIds(final Set ids) { + public IqPacket publishDeviceIds(final Set ids, final Bundle publishOptions) { final Element item = new Element("item"); final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); for(Integer id:ids) { @@ -192,11 +200,11 @@ public class IqGenerator extends AbstractGenerator { device.setAttribute("id", id); list.addChild(device); } - return publish(AxolotlService.PEP_DEVICE_LIST, item); + return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions); } public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, - final Set preKeyRecords, final int deviceId) { + final Set preKeyRecords, final int deviceId, Bundle publishOptions) { final Element item = new Element("item"); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); @@ -215,7 +223,7 @@ public class IqGenerator extends AbstractGenerator { prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); } - return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item); + return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item, publishOptions); } public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 0bcd9d6e..f5cbf574 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -13,4 +13,6 @@ public final class Namespace { public static final String OOB = "jabber:x:oob"; public static final String SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; + public static final String PUBSUB_PUBLISH_OPTIONS = "http://jabber.org/protocol/pubsub#publish-options"; + public static final String PUBSUB_ERROR = "http://jabber.org/protocol/pubsub#errors"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7a6603dd..0d0e6428 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1671,6 +1671,13 @@ public class XmppConnection implements Runnable { } } + public boolean pepPublishOptions() { + synchronized (XmppConnection.this.disco) { + ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid()); + return info != null && info.getFeatures().contains(Namespace.PUBSUB_PUBLISH_OPTIONS); + } + } + public boolean mam() { return hasDiscoFeature(account.getJid().toBareJid(), Namespace.MAM) || hasDiscoFeature(account.getJid().toBareJid(), Namespace.MAM_LEGACY); diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index e22eeada..679379ca 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -39,13 +39,14 @@ public class Data extends Element { return null; } - public void put(String name, String value) { + public Field put(String name, String value) { Field field = getFieldByName(name); if (field == null) { field = new Field(name); this.addChild(field); } field.setValue(value); + return field; } public void put(String name, Collection values) { @@ -91,7 +92,8 @@ public class Data extends Element { } public void setFormType(String formType) { - this.put(FORM_TYPE, formType); + Field field = this.put(FORM_TYPE, formType); + field.setAttribute("type","hidden"); } public String getFormType() { @@ -108,4 +110,15 @@ public class Data extends Element { return findChildContent("title"); } + + public static Data create(String type, Bundle bundle) { + Data data = new Data(); + data.setFormType(type); + data.setAttribute("type","submit"); + for(String key : bundle.keySet()) { + data.put(key,bundle.getString(key)); + } + return data; + } + }