From 34222bc0ad5dcff10be7d618b10ac0f3a37c251a Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Fri, 27 Mar 2026 17:41:12 +0100 Subject: [PATCH] OF-3222: Improve XEP-0359 stanza-id handling for one-on-one routing and archiving Add stanza-id generation for direct messages delivered to local bare and full JIDs using recipient bare JID ownership. Add sender-owned stanza-id for local-to-remote messages only after routing completes so outbound wire stanzas are not modified while post-processing interceptors can archive IDs. Exclude local component subdomains from sender-owned stanza-id injection so existing MUC stanza-id handling in MultiUserChatServiceImpl remains authoritative and is not duplicated. --- .../jivesoftware/openfire/MessageRouter.java | 17 ++++++++++++++++- .../openfire/spi/RoutingTableImpl.java | 12 +++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/MessageRouter.java b/xmppserver/src/main/java/org/jivesoftware/openfire/MessageRouter.java index 51699b3cb8..8b3c49c607 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/MessageRouter.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/MessageRouter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Jive Software, 2017-2025 Ignite Realtime Foundation. All rights reserved. + * Copyright (C) 2005-2008 Jive Software, 2017-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.jivesoftware.openfire.interceptor.PacketRejectedException; import org.jivesoftware.openfire.session.ClientSession; import org.jivesoftware.openfire.session.LocalClientSession; +import org.jivesoftware.openfire.stanzaid.StanzaIDUtil; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; @@ -131,6 +132,20 @@ public void route(Message packet) { routingFailed(recipientJID, packet); } + // For locally-originated messages to remote recipients, add sender-owned stanza-id to the packet + // AFTER routing has completed (so it was never present on the wire, satisfying XEP-0359 MUST-NOT), + // but BEFORE post-processing interceptors run (so archivers store XML that includes the stanza-id). + // For local-to-local, RoutingTableImpl already added a recipient-owned stanza-id during routePacket(); + // no sender-owned ID is added to avoid dual ownership. For component-bound messages (e.g. MUC at + // conference.example.com), the component itself manages stanza-ids (e.g. MultiUserChatServiceImpl); + // exclude those too by checking that the recipient domain is not a subdomain of this server. OF-3222 + if (packet.getFrom() != null + && serverName.equals(packet.getFrom().getDomain()) + && !serverName.equals(recipientJID.getDomain()) + && !recipientJID.getDomain().endsWith("." + serverName)) { + StanzaIDUtil.ensureUniqueAndStableStanzaID(packet, packet.getFrom().asBareJID()); + } + // Sent carbon copies to other resources of the sender: if (session != null && Forwarded.isEligibleForCarbonsDelivery(packet)) { List routes = routingTable.getRoutes(packet.getFrom().asBareJID(), null); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java index d07238cdc5..4439aacf1e 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Jive Software, 2016-2025 Ignite Realtime Foundation. All rights reserved. + * Copyright (C) 2005-2008 Jive Software, 2016-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.jivesoftware.openfire.server.OutgoingSessionPromise; import org.jivesoftware.openfire.server.RemoteServerManager; import org.jivesoftware.openfire.session.*; +import org.jivesoftware.openfire.stanzaid.StanzaIDUtil; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.TaskEngine; @@ -364,6 +365,11 @@ private boolean routeToLocalDomain(JID jid, Packet packet) } else { // RFC 6121 section 8.5.3. localpart@domainpart/resourcepart (Packet sent to a full JID of a user) + // Add stanza ID for one-on-one messages sent to full JIDs as per XEP-0359 + if (packet instanceof Message message) { + StanzaIDUtil.ensureUniqueAndStableStanzaID(message, jid.asBareJID()); + } + ClientRoute clientRoute = getClientRouteForLocalUser(jid); if (clientRoute != null) { // RFC-6121 section 8.5.3.1. Resource Matches @@ -624,6 +630,10 @@ private boolean routeToRemoteDomain(JID jid, Packet packet) { * @return true if at least one target session was found */ private boolean routeToBareJID(JID recipientJID, Message packet) { + // Add stanza ID for one-on-one messages as per XEP-0359 + // The assigning entity for one-on-one messages is the receiving user's bare JID + StanzaIDUtil.ensureUniqueAndStableStanzaID(packet, recipientJID.asBareJID()); + List sessions = new ArrayList<>(); // Get existing AVAILABLE sessions of this user or AVAILABLE to the sender of the packet for (JID address : getRoutes(recipientJID, packet.getFrom())) {