Skip to content

XEP-0198 SASL2 / Bind2 integration work#3417

Draft
dwd wants to merge 55 commits into
igniterealtime:mainfrom
dwd:sm-s2-b2
Draft

XEP-0198 SASL2 / Bind2 integration work#3417
dwd wants to merge 55 commits into
igniterealtime:mainfrom
dwd:sm-s2-b2

Conversation

@dwd

@dwd dwd commented Jul 3, 2026

Copy link
Copy Markdown
Member

Built on top of both SASL2 and Bind2 PRs, only the last two commits are relevent.

Dave Cridland and others added 30 commits July 3, 2026 11:37
# Conflicts:
#	xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java
#	xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
# Conflicts:
#	xmppserver/src/test/java/org/jivesoftware/openfire/sasl/SASLAuthenticationTest.java
Previously, the -PLUS mechanism filter was only applied in getSASLMechanismsElement
(advertisement), but not in getAvailableMechanismsForClientSession (eligibility check).
This meant a client could request a -PLUS mechanism that was never advertised, and the
eligibility check would pass — the rejection only happened deep inside the Java SASL
implementation rather than as a clean protocol-level <invalid-mechanism/> failure.

This commit moves the channel binding filter into getAvailableMechanismsForClientSession,
making it the single source of truth for session-available mechanisms. getSASLMechanismsElement
now simply iterates the result without additional filtering logic.

Co-authored-by: Junie <junie@jetbrains.com>
dwd and others added 25 commits July 3, 2026 11:37
…) checks

Replace all scattered mech.endsWith("-PLUS") checks with a single
requiresChannelBinding(String) helper method. This centralises the channel-binding
naming convention in one place so that any future change to how mechanisms signal
channel-binding support only needs to be made in one location.

Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Junie <junie@jetbrains.com>
…onManager.bindResource

Co-authored-by: Junie <junie@jetbrains.com>
…(XEP-0386)

XEP-0198 was previously supported in isolation but had no integration with
the SASL2 / Bind2 inline-feature negotiation that this branch also supports.
This commit wires the two together at all three integration points defined by
the specifications.

1. Inline feature advertisement (XEP-0388 §6.3.1)
   SASLAuthentication.getSASLMechanismsElement() now adds an <sm/> element
   (urn:xmpp:sm:3) inside the <inline/> child of the SASL2 <authentication/>
   feature, but only when stream management is globally active.

2. SM <enable> inside Bind2 (XEP-0386)
   A new Bind2InlineHandler implementation, Bind2StreamManagementHandler,
   handles <enable xmlns='urn:xmpp:sm:3'/> elements that arrive inside the
   Bind2 <bind/> element.  It calls the new
   StreamManager.enableAndBuildElement() method (which performs the same work
   as the existing private enable() but returns the <enabled/> element instead
   of sending it) and adds the result to the <bound/> element in the SASL2
   <success/> stanza, so the client receives everything in a single round-trip.
   The handler is registered in SessionManager.start() and unregistered in
   SessionManager.stop().

3. SM <resume> inside SASL2 <authenticate> (XEP-0388 §6.3.2)
   When a client includes a <resume xmlns='urn:xmpp:sm:3'/> element inside
   its SASL2 <authenticate/> stanza, SASLAuthentication.handle() stores it on
   the session.  After SASL authentication succeeds,
   authenticationSuccessful() detects the stored element and calls the new
   StreamManager.processSasl2Resume() method.  That method mirrors the
   existing startResume() logic but calls the new
   LocalSession.reattachForSasl2() instead of reattach(): the new variant
   takes over the connection and builds the <resumed/> element without sending
   it, so the caller can embed it inside the SASL2 <success/> stanza.

Supporting refactors
- StreamManager.onResume() is decomposed into buildResumedElement(),
  processClientAcknowledgementPublic(), and redeliverUnackedStanzas() so the
  SASL2 resume path can reuse the same logic without duplicating it.
- StreamManager.enable() is decomposed into enableInternal() (returns the
  element) and the original enable() (sends it), with the new public
  enableAndBuildElement() delegating to enableInternal().
- LocalSession gains reattachForSasl2() alongside the existing reattach().

Tests
- StreamManagerTest: three new tests for StreamManager.featureElement().
- Bind2StreamManagementHandlerTest: seven tests covering enable/resume
  attribute parsing, failure handling, and rejection of unexpected elements.
- SASLAuthenticationTest: three new tests verifying that the <sm/> inline
  feature is present in SASL2 advertisements when SM is active, absent when
  SM is inactive, and absent from SASL1 advertisements entirely.

Co-authored-by: Junie <junie@jetbrains.com>
When a session is resumed inline via SASL2 (XEP-0388 + XEP-0198), the
server must send pending unacknowledged stanzas only *after* the stream
features that follow the <success/> element, not before.

Previously, reattachForSasl2() called redeliverUnackedStanzas() directly,
which meant stanzas were sent before <success/> was even delivered to the
client, let alone the post-success stream features.

Fix:
- Add a boolean flag pendingSasl2Redelivery to StreamManager, with
  setPendingSasl2Redelivery(boolean) and redeliverIfPendingSasl2(JID).
- reattachForSasl2() in LocalSession now sets the flag instead of
  calling redeliverUnackedStanzas() directly.
- StanzaHandler calls redeliverIfPendingSasl2() immediately after
  delivering stream features following a successful SASL2 authenticate
  or response, ensuring the correct ordering:
    1. <success/> (with embedded <resumed/>)
    2. stream features
    3. unacknowledged stanzas redelivered

Three new unit tests in StreamManagerTest verify the flag semantics.

Co-authored-by: Junie <junie@jetbrains.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f63a60e6-9497-4f9e-9d1f-7d098bc42a0d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
.github/workflows/continuous-integration-workflow.yml (1)

332-382: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Job runs with default (broad) GITHUB_TOKEN permissions.

Static analysis flags this job as lacking an explicit permissions: block. Since this job only needs to checkout, download artifacts, and upload artifacts, consider scoping permissions (e.g., contents: read) to follow least privilege.

🔒 Suggested fix
   conversations:
     name: Execute Conversations e2e tests (${{ matrix.name }})
     runs-on: ubuntu-latest
     needs: build
+    permissions:
+      contents: read
     strategy:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/continuous-integration-workflow.yml around lines 332 -
382, The conversations e2e job is missing an explicit permissions scope, so it
inherits the broad default GITHUB_TOKEN permissions. Add a job-level permissions
block on the conversations workflow job and restrict it to the minimum needed
for its steps (checkout, artifact download, and artifact upload), such as
read-only repository contents. Update the conversations job definition in the
workflow alongside the existing matrix and steps so the permissions are clearly
scoped.

Source: Linters/SAST tools

xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java (1)

450-587: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

processSasl2Resume largely duplicates startResume; consider extracting shared validation.

Lines 464–586 replicate almost all of startResume(...) (h/previd parsing, allowResume()/isAuthenticated()/authToken gating, previd decode, route lookup, cluster/location handling, SM-compatibility and ack validation). The only real deltas are error-emission style (<failed/> vs closing the stream) and reattach vs reattachForSasl2. Duplicated security/validation logic tends to diverge over time, which is risky here. Extracting the common validation into a shared helper (returning the resolved otherSession) would keep the two entry points in lockstep.

Note also that, as in startResume, allowResume() returns false for an anonymous AuthToken, so the anonymous branch at Lines 518–520 is unreachable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java`
around lines 450 - 587, processSasl2Resume duplicates nearly all of
startResume’s validation and session lookup logic, so extract the shared
resume-validation flow into a helper that resolves and returns the target
LocalClientSession. Reuse the common checks in StreamManager for h/previd
parsing, allowResume(), isAuthenticated(), authToken retrieval, previd decoding,
route lookup, namespace/ack validation, and detached-session handling, while
keeping only the SASL2-specific differences in processSasl2Resume (failed stanza
emission and reattachForSasl2). Also remove the unreachable anonymous-auth
branch since allowResume() already rejects anonymous AuthToken cases.
xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java (1)

212-228: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract the duplicated post-auth SASL2 feature/redelivery block.

Lines 212-216 and 224-228 are identical. Consider a small private helper (e.g. deliverSasl2StreamFeatures()) to avoid divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java`
around lines 212 - 228, The SASL2 post-authentication feature delivery and
stream-manager redelivery logic is duplicated in StanzaHandler’s SASL handling
branch. Extract the repeated block into a small private helper such as
deliverSasl2StreamFeatures() and call it from both authenticated paths, so the
generateFeatures(), session.deliverRawText(), and
session.getStreamManager().redeliverIfPendingSasl2() behavior stays in one
place.
xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQMessageCarbonsHandler.java (1)

89-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the NAMESPACE constant instead of a duplicated string literal.

stop() hardcodes "urn:xmpp:carbons:2" instead of reusing the NAMESPACE field already declared in this class (Line 38), risking silent drift if the constant ever changes.

♻️ Proposed fix
     `@Override`
     public void stop() {
         super.stop();
-        Bind2Request.unregisterElementHandler("urn:xmpp:carbons:2");
+        Bind2Request.unregisterElementHandler(NAMESPACE);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQMessageCarbonsHandler.java`
around lines 89 - 93, The stop() method in IQMessageCarbonsHandler should not
hardcode the carbons namespace string; replace the duplicated
"urn:xmpp:carbons:2" literal with the existing NAMESPACE constant. Update the
Bind2Request.unregisterElementHandler call in stop() to use NAMESPACE so the
handler registration and teardown stay in sync if the namespace changes.
xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java (1)

70-71: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

resume="yes" is still accepted here The inline and non-inline SM paths both accept yes, so there’s no divergence. For strict XEP-0198 boolean parsing, narrow both handlers to true/false/1/0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java`
around lines 70 - 71, The Stream Management resume flag parsing in
Bind2StreamManagementHandler still accepts resume="yes", which should be
narrowed to strict XEP-0198 boolean handling. Update the parsing logic in the
resume attribute handling so it only treats true/false and 1/0 as valid values,
and make the same change in the matching non-inline SM path to keep both
handlers consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java`:
- Around line 14-17: The Bind2CarbonsHandler.handleElement logic currently
treats any element name other than "enable" as a disable request and always
succeeds, which lets malformed input slip through. Update handleElement to
explicitly validate the incoming Element name against the allowed carbons tags
(using the same rule set as IQMessageCarbonsHandler.handleIQ), only toggling
session.setMessageCarbonsEnabled for recognized values and rejecting unknown
names with a bad_request-style failure instead of returning success.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java`:
- Around line 205-212: Normalize or validate `clientTag` in
`Bind2Request.generateResourceString` before appending it to the resource, since
it is taken directly from `<tag>` and later used in `new JID(username,
serverName, resource, true)` without stringprep. Update the `clientTag` handling
path so only a compliant resourcepart is concatenated, either by rejecting
invalid values or applying the appropriate prep logic before the `StringBuilder`
append.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java`:
- Around line 212-216: The SASL2 authenticated branch in StanzaHandler.handle()
is sending <stream:features/> immediately, which can race ahead of the Bind2
<success/> path. Move the feature delivery and
StreamManager.redeliverIfPendingSasl2 call out of this synchronous block and
into the SASL2 Bind2 whenComplete(...) completion path (or gate it until Bind2
finishes) so the features stanza cannot overtake success for inline Bind2
sessions.
- Line 217: The SASL branch in StanzaHandler’s stanza processing is missing
proper gating for abort, so a bare abort can be handled outside an active SASL
exchange. Update the condition that checks startedSASL and the tag so both
"response" and "abort" are covered by the startedSASL check, keeping abort from
being processed unless SASL has already started.

---

Nitpick comments:
In @.github/workflows/continuous-integration-workflow.yml:
- Around line 332-382: The conversations e2e job is missing an explicit
permissions scope, so it inherits the broad default GITHUB_TOKEN permissions.
Add a job-level permissions block on the conversations workflow job and restrict
it to the minimum needed for its steps (checkout, artifact download, and
artifact upload), such as read-only repository contents. Update the
conversations job definition in the workflow alongside the existing matrix and
steps so the permissions are clearly scoped.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java`:
- Around line 70-71: The Stream Management resume flag parsing in
Bind2StreamManagementHandler still accepts resume="yes", which should be
narrowed to strict XEP-0198 boolean handling. Update the parsing logic in the
resume attribute handling so it only treats true/false and 1/0 as valid values,
and make the same change in the matching non-inline SM path to keep both
handlers consistent.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQMessageCarbonsHandler.java`:
- Around line 89-93: The stop() method in IQMessageCarbonsHandler should not
hardcode the carbons namespace string; replace the duplicated
"urn:xmpp:carbons:2" literal with the existing NAMESPACE constant. Update the
Bind2Request.unregisterElementHandler call in stop() to use NAMESPACE so the
handler registration and teardown stay in sync if the namespace changes.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java`:
- Around line 212-228: The SASL2 post-authentication feature delivery and
stream-manager redelivery logic is duplicated in StanzaHandler’s SASL handling
branch. Extract the repeated block into a small private helper such as
deliverSasl2StreamFeatures() and call it from both authenticated paths, so the
generateFeatures(), session.deliverRawText(), and
session.getStreamManager().redeliverIfPendingSasl2() behavior stays in one
place.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java`:
- Around line 450-587: processSasl2Resume duplicates nearly all of startResume’s
validation and session lookup logic, so extract the shared resume-validation
flow into a helper that resolves and returns the target LocalClientSession.
Reuse the common checks in StreamManager for h/previd parsing, allowResume(),
isAuthenticated(), authToken retrieval, previd decoding, route lookup,
namespace/ack validation, and detached-session handling, while keeping only the
SASL2-specific differences in processSasl2Resume (failed stanza emission and
reattachForSasl2). Also remove the unreachable anonymous-auth branch since
allowResume() already rejects anonymous AuthToken cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e0e29e89-9e16-4a8a-bd98-e21454134564

📥 Commits

Reviewing files that changed from the base of the PR and between 6e9d1d3 and 96766cd.

📒 Files selected for processing (34)
  • .github/actions/conversationstest-action/action.yml
  • .github/workflows/continuous-integration-workflow.yml
  • build/ci/conversations/configs/sasl2.xml
  • build/ci/conversations/flows/sasl2.yaml
  • documentation/openfire.doap
  • i18n/src/main/resources/openfire_i18n.properties
  • xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/SessionPacketRouter.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/XMPPServer.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/auth/ScramUtils.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/csi/CsiModule.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQMessageCarbonsHandler.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/http/HttpSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2InlineHandler.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/net/UserAgentInfo.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalClientSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalIncomingServerSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java
  • xmppserver/src/main/java/org/jivesoftware/openfire/websocket/WebSocketClientStanzaHandler.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandlerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/net/Bind2InlineHandlerTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/net/Bind2RequestProcessingTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/net/Bind2RequestTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/net/SASLAuthenticationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/net/UserAgentInfoTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/SASLAuthenticationTest.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/sasl/TestSaslMechanism.java
  • xmppserver/src/test/java/org/jivesoftware/openfire/streammanagement/StreamManagerTest.java

Comment on lines +14 to +17
public boolean handleElement(LocalClientSession session, Element bound, Element element) {
session.setMessageCarbonsEnabled(element.getName().equals("enable"));
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate element name instead of treating anything non-"enable" as "disable".

Unlike IQMessageCarbonsHandler.handleIQ, which rejects unrecognized element names with bad_request, this inline handler silently maps any element other than <enable/> to "disable" and always reports success. Malformed/unexpected client input goes unnoticed.

🛡️ Proposed fix
     public boolean handleElement(LocalClientSession session, Element bound, Element element) {
-        session.setMessageCarbonsEnabled(element.getName().equals("enable"));
-        return true;
+        final String name = element.getName();
+        if (!"enable".equals(name) && !"disable".equals(name)) {
+            return false;
+        }
+        session.setMessageCarbonsEnabled("enable".equals(name));
+        return true;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public boolean handleElement(LocalClientSession session, Element bound, Element element) {
session.setMessageCarbonsEnabled(element.getName().equals("enable"));
return true;
}
public boolean handleElement(LocalClientSession session, Element bound, Element element) {
final String name = element.getName();
if (!"enable".equals(name) && !"disable".equals(name)) {
return false;
}
session.setMessageCarbonsEnabled("enable".equals(name));
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java`
around lines 14 - 17, The Bind2CarbonsHandler.handleElement logic currently
treats any element name other than "enable" as a disable request and always
succeeds, which lets malformed input slip through. Update handleElement to
explicitly validate the incoming Element name against the allowed carbons tags
(using the same rule set as IQMessageCarbonsHandler.handleIQ), only toggling
session.setMessageCarbonsEnabled for recognized values and rejecting unknown
names with a bad_request-style failure instead of returning success.

Comment on lines +205 to +212
public String generateResourceString(UserAgentInfo userAgentInfo) {
StringBuilder resource = new StringBuilder();

// Add the client tag if available
if (clientTag != null && !clientTag.isEmpty()) {
resource.append(clientTag);
resource.append('/');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where generateResourceString is consumed and how the resource becomes a JID.
rg -nP -C4 'generateResourceString' --type=java
# Inspect resource/JID construction for prep/validation.
rg -nP -C3 '(new JID\(|prepResource|resourceprep|Resourceprep)' --type=java xmppserver/src/main/java/org/jivesoftware/openfire/net

Repository: igniterealtime/Openfire

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map Bind2Request and inspect the relevant method.
ast-grep outline xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java --view expanded || true
sed -n '1,320p' xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java

echo '--- usages of generateResourceString ---'
rg -n --hidden --glob '*.java' 'generateResourceString\s*\(' .

echo '--- resource/JID prep and validation references ---'
rg -n --hidden --glob '*.java' '(resourceprep|prepResource|validateResource|resourcepart|new JID\(|JID\()' xmppserver/src/main/java .

Repository: igniterealtime/Openfire

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- SASLAuthentication around generateResourceString usage ---'
sed -n '740,805p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java

echo '--- JID implementation outline ---'
ast-grep outline xmppserver/src/main/java/org/jivesoftware/openfire/JID.java --view expanded || true
sed -n '1,260p' xmppserver/src/main/java/org/jivesoftware/openfire/JID.java

echo '--- resource-related methods in JID ---'
rg -n --hidden --glob '*.java' 'resourceprep|prepResource|setResource|getResource|resourcePart|resource part|RESOURCE' xmppserver/src/main/java/org/jivesoftware/openfire/JID.java xmppserver/src/main/java/org/jivesoftware/util

Repository: igniterealtime/Openfire

Length of output: 4655


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate JID.java ---'
fd -a 'JID.java' .

echo '--- locate bindResource and related validation ---'
rg -n --hidden --glob '*.java' 'bindResource\(|createJID\(|new JID\(' xmppserver src .

echo '--- inspect SessionManager.bindResource call site and implementation ---'
rg -n --hidden --glob '*.java' 'class SessionManager|bindResource\(' xmppserver src .

Repository: igniterealtime/Openfire

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate JID implementation ---'
fd -a 'JID.java' xmppserver

echo '--- locate SessionManager implementation ---'
fd -a 'SessionManager.java' xmppserver

echo '--- locate bindResource definition and nearby calls ---'
rg -n -C 4 'bindResource\(' xmppserver/src/main/java --glob '*.java'

Repository: igniterealtime/Openfire

Length of output: 5623


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- all JID.java files in repo ---'
git ls-files '**/JID.java' '**/*JID.java'

echo '--- SessionManager.bindResource implementation ---'
sed -n '1475,1565p' xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java

echo '--- IQBindHandler resource handling ---'
sed -n '80,120p' xmppserver/src/main/java/org/jivesoftware/openfire/handler/IQBindHandler.java

echo '--- tests around invalid resource/JID behavior ---'
sed -n '1,140p' xmppserver/src/test/java/org/jivesoftware/util/JIDTest.java

Repository: igniterealtime/Openfire

Length of output: 10996


🌐 Web query:

org.xmpp.packet.JID constructor resource validation resourceprep Openfire

💡 Result:

In Openfire, the org.xmpp.packet.JID class handles the validation and normalization of XMPP address components, including the resource [1][2]. The JID constructors and associated methods ensure that XMPP compliance is maintained through stringprep profiles [1][2]. Key details regarding resource validation and resourceprep include: Normalization: The JID class uses resourceprep to normalize the resource portion of a JID [1][2]. This process is case-sensitive, which is a requirement for XMPP address comparison [1][3][2]. Constructors: The standard JID(String node, String domain, String resource) constructor performs validation and applies stringprep operations to the components [1][2]. An overloaded constructor, JID(String node, String domain, String resource, boolean skipStringprep), allows developers to bypass these operations if they are certain the input is already compliant or if performance optimization is required [1][2]. Passing true for the skipStringprep parameter disables the automatic application of resourceprep and other stringprep profiles [1][2]. Validation Errors: If the provided components, including the resource, cannot be processed or validated (e.g., due to invalid characters or formatting), the constructor throws an IllegalArgumentException [1][4][2]. Direct Usage: Beyond the JID constructor, Openfire exposes a static method JID.resourceprep(String resource) that can be called directly to validate or normalize a resource string independently of JID construction [2][5][6]. This method may throw a StringprepException if the resource does not conform to the expected requirements [2][5][6]. These mechanisms ensure that JIDs used within Openfire are well-formed and that resource-specific comparisons function according to XMPP standards [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' xmppserver/src/test/java/org/jivesoftware/openfire/net/Bind2RequestTest.java

Repository: igniterealtime/Openfire

Length of output: 5451


Normalize clientTag before appending it to the resource
clientTag is copied verbatim from <tag>, and the SASL2 bind path later calls new JID(username, serverName, resource, true), which skips stringprep. Reject or prep the tag before concatenation so the final resourcepart stays compliant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java`
around lines 205 - 212, Normalize or validate `clientTag` in
`Bind2Request.generateResourceString` before appending it to the resource, since
it is taken directly from `<tag>` and later used in `new JID(username,
serverName, resource, true)` without stringprep. Update the `clientTag` handling
path so only a compliant resourcepart is concatenated, either by rejecting
invalid values or applying the appropriate prep logic before the `StringBuilder`
append.

Comment on lines +212 to +216
if (saslStatus == SASLAuthentication.Status.authenticated && usingSASL2) {
Element features = generateFeatures();
session.deliverRawText(features.asXML());
session.getStreamManager().redeliverIfPendingSasl2(new JID(null, session.getServerName(), null, true));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect bindResource to determine whether it can complete asynchronously.
fd 'SessionManager.java' -p 'main/java' | xargs -r ast-grep run \
  --pattern 'public $_ bindResource($$$) { $$$ }' --lang java

Repository: igniterealtime/Openfire

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and map their structure.
git ls-files | rg '(^|/)(StanzaHandler\.java|SessionManager\.java|SASLAuthentication\.java|StreamManager\.java)$'

Repository: igniterealtime/Openfire

Length of output: 472


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant implementation slices with line numbers.
for f in \
  xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java \
  xmppserver/src/main/java/org/jivesoftware/openfire/spi/SessionManager.java \
  xmppserver/src/main/java/org/jivesoftware/openfire/sasl/SASLAuthentication.java \
  xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java
do
  echo "===== $f ====="
  wc -l "$f"
done

Repository: igniterealtime/Openfire

Length of output: 518


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant code around the reported lines.
sed -n '190,235p' xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java
printf '\n===== SASLAuthentication =====\n'
sed -n '730,820p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java
printf '\n===== SessionManager =====\n'
sed -n '1,260p' xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java
printf '\n===== StreamManager =====\n'
sed -n '1,260p' xmppserver/src/main/java/org/jivesoftware/openfire/streammanagement/StreamManager.java

Repository: igniterealtime/Openfire

Length of output: 31342


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect bindResource plus the delivery path it uses.
rg -n "bindResource\\(" xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java
printf '\n===== bindResource implementation =====\n'
sed -n '1320,1455p' xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java
printf '\n===== deliverRawText implementations =====\n'
rg -n "deliverRawText\\(" xmppserver/src/main/java/org/jivesoftware/openfire -g '*.java'

Repository: igniterealtime/Openfire

Length of output: 20891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect bindResource implementation and any async completion paths.
sed -n '1475,1615p' xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java

printf '\n===== CompletableFuture usages in SessionManager =====\n'
rg -n "CompletableFuture|supplyAsync|completeAsync|whenComplete|thenCompose|thenApply|Executor" xmppserver/src/main/java/org/jivesoftware/openfire/SessionManager.java

printf '\n===== LocalSession deliverRawText implementation =====\n'
sed -n '450,510p' xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalSession.java

printf '\n===== NettyConnection deliverRawText implementation =====\n'
sed -n '330,390p' xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyConnection.java

Repository: igniterealtime/Openfire

Length of output: 16631


Defer <stream:features/> until the SASL2 Bind2 whenComplete(...) path finishes. bindResource(...) runs on bindConflictExecutor, so the <success/> stanza can arrive after handle() already returns here. That lets <stream:features/> overtake <success/> for inline Bind2 sessions; move the feature/redelivery send into the completion stage or suppress it until Bind2 completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java`
around lines 212 - 216, The SASL2 authenticated branch in StanzaHandler.handle()
is sending <stream:features/> immediately, which can race ahead of the Bind2
<success/> path. Move the feature delivery and
StreamManager.redeliverIfPendingSasl2 call out of this synchronous block and
into the SASL2 Bind2 whenComplete(...) completion path (or gate it until Bind2
finishes) so the features stanza cannot overtake success for inline Bind2
sessions.

session.deliverRawText(features.asXML());
session.getStreamManager().redeliverIfPendingSasl2(new JID(null, session.getServerName(), null, true));
}
} else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '180,250p' xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java
rg -n "handle\(.*abort|class SASLAuthentication|ABORTED|startedSASL" xmppserver/src/main/java -S

Repository: igniterealtime/Openfire

Length of output: 4811


🏁 Script executed:

sed -n '180,250p' xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java
rg -n "handle\\(.*abort|class SASLAuthentication|ABORTED|startedSASL" xmppserver/src/main/java -S

Repository: igniterealtime/Openfire

Length of output: 4811


🏁 Script executed:

sed -n '430,530p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java
sed -n '200,250p' xmppserver/src/main/java/org/jivesoftware/openfire/net/RespondingServerStanzaHandler.java

Repository: igniterealtime/Openfire

Length of output: 9577


🏁 Script executed:

rg -n 'startedSASL && "response"\.equals\(tag\) \|\| "abort"\.equals\(tag\)|startedSASL && \("response"\.equals\(tag\) \|\| "abort"\.equals\(tag\)\)|"abort"\.equals\(tag\)' xmppserver/src/main/java -S
sed -n '120,160p' xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java

Repository: igniterealtime/Openfire

Length of output: 1808


🏁 Script executed:

rg -n "catch \\(SaslFailureException|Status\\.failed|retry|increment.*retry|ABORTED|send.*failure" xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java -n -S
sed -n '530,700p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java

Repository: igniterealtime/Openfire

Length of output: 9886


🏁 Script executed:

rg -n "authenticationFailed\\(|recordFailedLogin|retry" xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java -S
sed -n '700,820p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java

Repository: igniterealtime/Openfire

Length of output: 8350


🏁 Script executed:

sed -n '824,900p' xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java

Repository: igniterealtime/Openfire

Length of output: 3734


abort should be gated by startedSASL as well

&& binds tighter than ||, so this parses as (startedSASL && "response".equals(tag)) || "abort".equals(tag). As written, a bare <abort/> will enter the SASL handler even when no SASL exchange is in progress, incrementing auth retries and potentially closing the connection. Group both tags under the startedSASL check.

Proposed fix
-        } else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
+        } else if (startedSASL && ("response".equals(tag) || "abort".equals(tag))) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
} else if (startedSASL && ("response".equals(tag) || "abort".equals(tag))) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xmppserver/src/main/java/org/jivesoftware/openfire/net/StanzaHandler.java` at
line 217, The SASL branch in StanzaHandler’s stanza processing is missing proper
gating for abort, so a bare abort can be handled outside an active SASL
exchange. Update the condition that checks startedSASL and the tag so both
"response" and "abort" are covered by the startedSASL check, keeping abort from
being processed unless SASL has already started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants