Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
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.TerminationDelegate;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.*;
Expand Down Expand Up @@ -270,6 +272,7 @@ public class SessionManager extends BasicModule implements ClusterEventListener
private RoutingTable routingTable;

private StreamIDFactory streamIDFactory;
private Bind2StreamManagementHandler bind2StreamManagementHandler;

/**
* Returns the instance of <CODE>SessionManagerImpl</CODE> being used by the XMPPServer.
Expand Down Expand Up @@ -1897,10 +1900,15 @@ private Message createServerMessage(String subject, String body) {
}

@Override
public void start() throws IllegalStateException {
public synchronized void start() throws IllegalStateException {
super.start();
localSessionManager.start();

// Register the XEP-0198 Stream Management handler for SASL2 Bind2 inline feature processing.
final Bind2StreamManagementHandler localHandler = new Bind2StreamManagementHandler();
Bind2Request.registerElementHandler(localHandler);
Comment thread
guusdk marked this conversation as resolved.
bind2StreamManagementHandler = localHandler; // Only dereference any previous handler after registration succeeds, otherwise that previous handler can never be removed again.

// 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 @@ -1915,8 +1923,14 @@ public void start() throws IllegalStateException {
}

@Override
public void stop() {
public synchronized void stop() {
Log.debug("SessionManager: Stopping server");

if (bind2StreamManagementHandler != null) {
Bind2Request.unregisterElementHandler(bind2StreamManagementHandler);
bind2StreamManagementHandler = null;
}

// 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,101 @@
/*
* Copyright (C) 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.StreamManagementException;
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("Received unexpected element '{}'; ignoring.", element.getName());
return false;
}
final String resumeAttr = element.attributeValue("resume");
final boolean resume = "true".equalsIgnoreCase(resumeAttr) || "1".equals(resumeAttr) || "yes".equalsIgnoreCase(resumeAttr);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
bound.add(session.getStreamManager().enableAndBuildElement(getNamespace(), resume));
Comment thread
guusdk marked this conversation as resolved.
return true;
}

/**
* Adds the {@code <failed/>} element that XEP-0198 § 9.1.1 requires inside {@code <bound/>} when stream
* management could not be enabled.
*
* The condition is taken from the failure itself where one is available. A handler that declined the request
* without failing did so because the element was not an {@code <enable/>}, which is a malformed request.
*/
@Override
public void handleFailure(LocalClientSession session, Element bound, Element element, Exception cause) {
final PacketError.Condition condition;
if (cause instanceof StreamManagementException sme) {
condition = sme.getCondition();
} else if (cause == null) {
condition = PacketError.Condition.bad_request;
} else {
condition = PacketError.Condition.internal_server_error;
}
bound.addElement("failed", StreamManager.NAMESPACE_V3)
.addElement(condition.toXMPP(), "urn:ietf:params:xml:ns:xmpp-stanzas");
}
}
Loading
Loading