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 @@ -39,4 +39,30 @@ public interface Bind2InlineHandler {
* @return true if the element was handled successfully, false otherwise
*/
boolean handleElement(LocalClientSession session, Element bound, Element element);

/**
* Indicates whether this handler is currently available for advertisement and request processing.
*
* A handler whose feature is disabled by configuration is neither advertised in the Bind2 inline feature list nor
* invoked for a request that names its namespace, so that a peer is never offered something it cannot use.
*
* @return {@code true} when the inline feature is available
*/
default boolean isEnabled() {
return true;
}

/**
* Gives a handler an opportunity to add the protocol-defined failure response after request processing failed.
*
* Not every inline extension defines one, so the default does nothing.
*
* @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, Exception cause) {
// Most inline extensions do not define a failure response.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
import org.dom4j.QName;
import org.jivesoftware.openfire.auth.ScramUtils;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.sasl.SaslException;
import java.nio.charset.StandardCharsets;
import java.util.*;
Expand Down Expand Up @@ -94,27 +95,52 @@ 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);
Log.info("Handler for namespace {} failed to process element", namespace);
invokeFailureHandler(clientSession, bound, element, null, handler, namespace);
Comment thread
guusdk marked this conversation as resolved.
}
} catch (Exception e) {
Log.error("Error processing element with namespace: " + namespace, e);
Log.warn("Error processing element with namespace: {}", namespace, e);
invokeFailureHandler(clientSession, bound, element, e, handler, namespace);
}
} else {
Log.debug("No handler registered for namespace: {}", namespace);
Log.debug("No handler registered/enabled for namespace: {}", namespace);
// We don't fail here because there's no obvious way we could fail.
}
}

return bound;
}

/**
* Invokes the failure-handler of a Bind2-handler, logging but otherwise suppressing any exception thrown by the
* failure-handler.
*
* @param clientSession the client session.
* @param bound the bound element.
* @param element the element that failed to be processed.
* @param cause the processing exception, or {@code null} when the handler returned {@code false}.
* @param handler the Bind2-handler that failed to process the element.
* @param namespace the namespace of the element.
*/
private static void invokeFailureHandler(final LocalClientSession clientSession, final Element bound, final Element element, @Nullable final Exception cause, @Nonnull final Bind2InlineHandler handler, final String namespace)
{
try {
handler.handleFailure(clientSession, bound, element, cause);
} catch (Exception ex) {
Log.warn("Error invoking failure handler after failing to process element with namespace: {}", namespace, ex);
}
}

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 Expand Up @@ -221,21 +247,21 @@ public String generateResourceString(UserAgentInfo userAgentInfo) {
// Using a fixed constant here - building a rainbow table here for the case
// where the client supplies no tag is going to be very expensive, so this
// prevents an id recovery attack.
String valueToHmac = resource.toString() + "OpenfireResourceConstant";
String valueToHmac = resource + "OpenfireResourceConstant";

// Compute HMAC
byte[] hmacResult = ScramUtils.computeHmac(keyBytes, valueToHmac);
byte[] hmacResult = ScramUtils.computeHmac(keyBytes, valueToHmac, "HmacSHA1");

// Convert first 8 bytes of HMAC to hex for resource suffix (16 chars)
String hmacHex = StringUtils.encodeHex(Arrays.copyOf(hmacResult, 8));

// Construct final resource string
return resource.toString() + hmacHex;
return resource + hmacHex;

} catch (SaslException e) {
// Fall back to UUID in case of HMAC computation failure
Log.error("Failed to compute HMAC for resource string", e);
return resource.toString() + UUID.randomUUID().toString();
return resource.toString() + UUID.randomUUID();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -820,14 +820,16 @@ else if (session instanceof LocalIncomingServerSession serverSession) {
Log.warn("An exception occurred while binding resource '{}' for session '{}' during SASL2+Bind2 authentication.", resource, clientSession, throwable);
}
final boolean bound = throwable == null && result == SessionManager.BindResult.BOUND;
final Element success = SaslOutcome.buildSasl2SuccessElement(successData, authorizationIdentity, bound ? resource : null, finalFastToken);
if (bound) {
bind2Request.processFeatureRequests(clientSession, success);
}
if (bound) {
clientSession.setStatus(Session.Status.AUTHENTICATED);
SessionEventDispatcher.dispatchEvent(clientSession, SessionEventDispatcher.EventType.resource_bound);
if (!bound) {
Log.warn("Unable to bind resource '{}' for session '{}' during SASL2+Bind2 authentication. Bind result: {}", resource, clientSession, result);
SaslOutcome.authenticationFailed(clientSession, Failure.TEMPORARY_AUTH_FAILURE, true);
return;
}
final Element success = SaslOutcome.buildSasl2SuccessElement(successData, authorizationIdentity, resource, finalFastToken);
clientSession.setStatus(Session.Status.AUTHENTICATED);
bind2Request.processFeatureRequests(clientSession, success);
SessionEventDispatcher.dispatchEvent(clientSession, SessionEventDispatcher.EventType.resource_bound);

// Deliver stream features now that <success/> has been sent.
final Element features = DocumentHelper.createElement(QName.get("features", "stream", "http://etherx.jabber.org/streams"));
final List<org.dom4j.Element> specificFeatures = clientSession.getAvailableStreamFeatures();
Expand Down
Loading
Loading