Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
585efa0
Integrate XEP-0198 Stream Management with SASL2 (XEP-0388) and Bind2 …
dwd Jul 3, 2026
ac54f28
Defer SM stanza redelivery until after stream features on SASL2 resume
dwd Jul 3, 2026
5adc07b
Update comment with devnote
dwd Aug 28, 2026
59ee280
Do not redeliver on Bind2 pending auth
dwd Aug 28, 2026
8f61ffa
Redeliver 198 for WebSocket
dwd Aug 28, 2026
ef1e5fd
OF-: Return SASL2 stream resume outcomes
dwd Aug 29, 2026
dbce289
OF-: Process stream resumption during SASL2
dwd Aug 29, 2026
5464aab
OF-: Adopt resumed session after SASL2
dwd Aug 29, 2026
cee2059
Advertise stream management as SASL2 inline feature
dwd Aug 29, 2026
b4e681e
Honor dynamic stream management state for Bind2
dwd Aug 29, 2026
d283f23
Report resumed full JID in SASL2 success
dwd Aug 29, 2026
3dd4db7
Unify stream resumption validation
dwd Aug 29, 2026
226a0da
Enable stream management after Bind2 authentication
dwd Aug 29, 2026
b3d7d2d
Skip stream features after inline resumption
dwd Aug 29, 2026
3390546
Test Bind2 fallback after failed stream resumption
dwd Aug 29, 2026
7383a62
Embed Bind2 stream management failures
dwd Aug 29, 2026
f901fc8
Restore Bind2 handler availability tests
dwd Aug 29, 2026
6f06402
Test inline resumption replay across transports
dwd Aug 29, 2026
850ee40
Test SASL2 resumed session ownership transfer
dwd Aug 29, 2026
9339f0b
Test successful inline stream resumption
dwd Aug 29, 2026
fe18aa9
Guarantee Bind2 stream management failure responses
dwd Aug 29, 2026
5d42ce1
Fail SASL2 when Bind2 resource binding fails
dwd Aug 29, 2026
e430fbd
Encapsulate SASL2 stream resumption completion
dwd Aug 29, 2026
8055fa8
Document SASL2 TLS override non-conformance
dwd Aug 29, 2026
38d85ce
Test SASL2 Bind2 stream management integration
dwd Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion i18n/src/main/resources/openfire_i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,7 @@ system_property.xmpp.auth.external.client.skip-cert-revalidation=Set to true to
system_property.xmpp.auth.external.server.require-authzid=Require the peer to provide an authorization identity through SASL (typically in the Initial Response) when authenticating an inbound S2S connection that uses the EXTERNAL SASL mechanism. This is not required by the XMPP protocol specification, but it was required by Openfire versions prior to release 4.8.0. This configuration option is added to allow for backwards compatibility.
system_property.xmpp.auth.external.server.skip-sending-authzid=Send an authorization identity in the Initial Response when attempting to authenticate using the SASL EXTERNAL mechanism with a remote XMPP domain. Sending the authzid in this manner is not required by the XMPP protocol specification, but is recommended in XEP-0178 for compatibility with older server implementations.
system_property.xmpp.auth.sasl2=Enables support for SASL2 authentication (XEP-0388)
system_property.xmpp.auth.sasl2.require-tls=Require TLS in order to authenticate with SASL2
system_property.xmpp.auth.sasl2.require-tls=Require TLS in order to authenticate with SASL2 (disabling this is not XEP-0388 compliant)
system_property.xmpp.auth.ssl.default-trustmanager-impl=The class to use as the default TLS TrustManager (which checks certificates from peers).
system_property.xmpp.auth.scram.mechanisms-per-user=Offer a client only the SCRAM mechanisms that the user it claims to be has credentials for, rather than those that every user is assumed to have. Requires the client to identify itself in the 'from' attribute of its stream header
system_property.xmpp.client.csi.enabled=Controls if Client State Indication (XEP-0352) functionality is supported by Openfire.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
import org.jivesoftware.openfire.session.*;
import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
import org.jivesoftware.openfire.spi.ConnectionType;
import org.jivesoftware.openfire.handler.Bind2StreamManagementHandler;
import org.jivesoftware.openfire.net.Bind2Request;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.openfire.streammanagement.TerminationDelegate;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.*;
Expand Down Expand Up @@ -1901,6 +1904,9 @@ public void start() throws IllegalStateException {
super.start();
localSessionManager.start();

// Register the XEP-0198 Stream Management handler for SASL2 Bind2 inline feature processing.
Bind2Request.registerElementHandler(new Bind2StreamManagementHandler());

// Run through the server sessions every 10% of the time of the maximum time that a session is allowed to be
// detached, or every 3 minutes if the max time is outside the default boundaries.
// TODO Reschedule task if getSessionDetachTime value changes.
Expand All @@ -1917,6 +1923,7 @@ public void start() throws IllegalStateException {
@Override
public void stop() {
Log.debug("SessionManager: Stopping server");
Bind2Request.unregisterElementHandler(StreamManager.NAMESPACE_V3);
// Stop threads that are sending packets to remote servers
OutgoingSessionPromise.getInstance().shutdown();
if (JiveGlobals.getBooleanProperty("shutdownMessage.enabled")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2024-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.
* 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.openfire.handler;

import org.dom4j.Element;
import org.jivesoftware.openfire.net.Bind2InlineHandler;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.PacketError;

/**
* A {@link Bind2InlineHandler} that processes XEP-0198 Stream Management {@code <enable/>} elements
* sent inline within a SASL2 Bind2 request (XEP-0388 / XEP-0386).
*
* <p>When a client includes an {@code <enable/>} element in the {@code urn:xmpp:sm:3} namespace
* inside its Bind2 {@code <bind/>} element, this handler delegates to the session's
* {@link StreamManager} to enable stream management (and optionally resumption) immediately
* after resource binding, without requiring a separate round-trip.</p>
*
* <p>The {@code <enabled/>} response from the server is added as a child of the {@code <bound/>}
* element in the SASL2 {@code <success/>} stanza.</p>
*
* @see <a href="https://xmpp.org/extensions/xep-0198.html">XEP-0198: Stream Management</a>
* @see <a href="https://xmpp.org/extensions/xep-0388.html">XEP-0388: Extensible SASL Profile</a>
*/
public class Bind2StreamManagementHandler implements Bind2InlineHandler {

private static final Logger Log = LoggerFactory.getLogger(Bind2StreamManagementHandler.class);

@Override
public String getNamespace() {
return StreamManager.NAMESPACE_V3;
}

@Override
public boolean isEnabled() {
return StreamManager.isStreamManagementActive();
}

/**
* Handles an {@code <enable/>} element from a Bind2 inline feature request by enabling
* XEP-0198 stream management on the session. The {@code <enabled/>} response element
* produced by the stream manager is added as a child of the provided {@code bound} element.
*
* <p>Only {@code <enable/>} elements are processed; any other element name is ignored.</p>
*
* @param session the client session on which stream management should be enabled
* @param bound the {@code <bound/>} element to which the {@code <enabled/>} response is added
* @param element the inline element from the Bind2 request (expected to be {@code <enable/>})
* @return {@code true} if the element was an {@code <enable/>} and was processed;
* {@code false} if the element was not an {@code <enable/>} or processing failed
*/
@Override
public boolean handleElement(LocalClientSession session, Element bound, Element element) {
if (!"enable".equals(element.getName())) {
Log.debug("Bind2StreamManagementHandler received unexpected element '{}'; ignoring.", element.getName());
return false;
}
Log.debug("Processing inline SM <enable/> for session {}", session.getAddress());
final String namespace = element.getNamespaceURI();
final String resumeAttr = element.attributeValue("resume");
final boolean resume = "true".equalsIgnoreCase(resumeAttr) || "1".equals(resumeAttr) || "yes".equalsIgnoreCase(resumeAttr);
final Element outcome = session.getStreamManager().enableAndBuildElement(namespace, resume);
if (outcome != null) {
bound.add(outcome);
return true;
}
return false;
}

@Override
public void handleFailure(LocalClientSession session, Element bound, Element element, Throwable cause) {
final PacketError.Condition condition = cause == null
? PacketError.Condition.bad_request
: PacketError.Condition.internal_server_error;
final Element failed = bound.addElement("failed", StreamManager.NAMESPACE_V3);
failed.addElement(condition.toXMPP(), "urn:ietf:params:xml:ns:xmpp-stanzas");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ public interface Bind2InlineHandler {
*/
String getNamespace();

/**
* Indicates whether this handler is currently available for advertisement and request processing.
*
* @return {@code true} when the inline feature is available
*/
default boolean isEnabled() {
return true;
}

/**
* Process an inline element from a bind2 request.
*
Expand All @@ -39,4 +48,16 @@ public interface Bind2InlineHandler {
* @return true if the element was handled successfully, false otherwise
*/
boolean handleElement(LocalClientSession session, Element bound, Element element);

/**
* Gives a handler an opportunity to add the protocol-defined failure response after request processing failed.
*
* @param session the client session
* @param bound the Bind2 response element
* @param element the request that could not be processed
* @param cause the processing exception, or {@code null} when the handler returned {@code false}
*/
default void handleFailure(LocalClientSession session, Element bound, Element element, Throwable cause) {
// Most inline extensions do not define a failure response.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ public Element processFeatureRequests(LocalClientSession clientSession, Element
String namespace = element.getNamespaceURI();
Bind2InlineHandler handler = elementHandlers.get(namespace);

if (handler != null) {
if (handler != null && handler.isEnabled()) {
try {
if (!handler.handleElement(clientSession, bound, element)) {
Log.warn("Handler for namespace {} failed to process element", namespace);
handler.handleFailure(clientSession, bound, element, null);
}
} catch (Exception e) {
Log.error("Error processing element with namespace: " + namespace, e);
handler.handleFailure(clientSession, bound, element, e);
}
} else {
Log.debug("No handler registered for namespace: {}", namespace);
Expand All @@ -115,6 +117,9 @@ public static Element featureElement() {
Element bind2 = DocumentHelper.createElement(new QName("bind", new Namespace("", "urn:xmpp:bind:0")));
Element bind2inline = bind2.addElement("inline");
for (Bind2InlineHandler handler : elementHandlers.values()) {
if (!handler.isEnabled()) {
continue;
}
Element var = bind2inline.addElement("feature");
var.addAttribute("var", handler.getNamespace());
}
Expand Down
Loading
Loading