[jira] [Created] (JAMES-4210) Generic SASL mechanism extension model for James protocols

"Tran Hong Quan (Jira)" <[email protected]> Wed, 3 Jun 2026 03:25:00 +0000 (UTC)
Newsgroups gmane.comp.jakarta.james.devel
Message-ID <[email protected]>
Tran Hong Quan created JAMES-4210:
-------------------------------------

             Summary: Generic SASL mechanism extension model for James prot=
ocols
                 Key: JAMES-4210
                 URL: https://issues.apache.org/jira/browse/JAMES-4210
             Project: James Server
          Issue Type: Improvement
          Components: IMAPServer, POP3Server, sieve, SMTPServer
    Affects Versions: 3.10
            Reporter: Tran Hong Quan


I would like to propose a generic SASL mechanism extension model for James =
protocols.

Today, when adding a new authentication mechanism for IMAP or SMTP, we need=
 to modify core protocol code, such as IMAP=C2=A0_AuthenticateProcessor_=C2=
=A0or SMTP=C2=A0{_}AuthCmdHandler{_}. This makes the protocol handlers accu=
mulate mechanism-specific branches, and would make future mechanisms such a=
s GSSAPI / Kerberos harder to add cleanly.

The goal is:

=C2=A0- adding a new SASL mechanism should not require modifying core IMAP/=
SMTP command handlers;
=C2=A0- protocol code should keep protocol framing and side effects;
=C2=A0- SASL mechanism code should own mechanism semantics and exchange sta=
te;
=C2=A0- existing IMAP/SMTP behavior should remain unchanged when no new con=
figuration is provided.

This takes inspiration from JMAP authentication strategies configuration an=
d Guice loading.


*Configuration shape*
-------------------

Each protocol would keep an operator-visible=C2=A0_auth.saslMechanisms_=C2=
=A0list.

Example for IMAP:

=C2=A0 =C2=A0 _<auth>_
=C2=A0 =C2=A0 =C2=A0 _<saslMechanisms>PlainSaslMechanism,OauthBearerSaslMec=
hanism,XOauth2SaslMechanism,com.example.james.kerberos.GssapiSaslMechanism<=
/saslMechanisms>_
=C2=A0 =C2=A0 _</auth>_

Example for SMTP:

=C2=A0 =C2=A0 _<auth>_
=C2=A0 =C2=A0 =C2=A0 _<saslMechanisms>LoginSaslMechanism,PlainSaslMechanism=
,OauthBearerSaslMechanism,XOauth2SaslMechanism,com.example.james.kerberos.G=
ssapiSaslMechanism</saslMechanisms>_
=C2=A0 =C2=A0 _</auth>_

Expected semantics:

=C2=A0- if=C2=A0_auth.saslMechanisms_=C2=A0is absent, load current protocol=
 defaults;
=C2=A0- IMAP defaults remain PLAIN, OAUTHBEARER, XOAUTH2;
=C2=A0- SMTP defaults remain LOGIN, PLAIN, OAUTHBEARER, XOAUTH2;
=C2=A0- if configured, load the configured list in the configured order;
=C2=A0- simple class names resolve against James default SASL packages;
=C2=A0- fully qualified class names allow external/community extensions;

Existing protocol settings still decide availability. For example, plain au=
th restrictions still decide whether PLAIN is advertised, OIDC configuratio=
n still decides whether OAuth mechanisms are advertised, and SMTP=C2=A0_aut=
h.announce_=C2=A0still controls whether SMTP advertises AUTH.


*Proposed SPI shape*
------------------

I propose introducing protocol-neutral SASL types in=C2=A0{_}protocols/api{=
_}, for example under=C2=A0{_}org.apache.james.protocols.api.sasl{_}.

The core idea is a stateful=C2=A0SaslExchange: each SASL mechanism creates =
one exchange per authentication attempt, and that exchange owns mechanism s=
tate across the initial request and continuation responses.

Core structure:

=C2=A0 =C2=A0 _interface SaslMechanism {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _String name();_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _boolean supports(SaslProtocol protocol);_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _boolean isAvailable(SaslSessionContext=C2=A0co=
ntext);_
=C2=A0 =C2=A0 =C2=A0 =C2=A0=C2=A0_SaslExchange=C2=A0start(SaslInitialReques=
t=C2=A0request,=C2=A0SaslSessionContext=C2=A0context);_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _enum=C2=A0SaslProtocol=C2=A0{_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _IMAP, SMTP, MANAGESIEVE, POP3_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _record SaslInitialRequest(SaslProtocol protocol, String mech=
anismName, Optional<byte[]> initialResponse) {_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _record SaslIdentity(Username authenticationId, Username auth=
orizationId) {_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _interface=C2=A0SaslSessionContext=C2=A0{_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _SaslProtocol protocol();_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _boolean=C2=A0isTlsStarted();_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _<T> Optional<T> configuration(Class<T> configu=
rationType);_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _interface SaslExchange extends AutoCloseable {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _SaslStep firstStep();_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _SaslStep onResponse(byte[] clientResponse);_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _void abort();_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _@Override_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _void close();_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _interface SaslStep {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _record Challenge(Optional<byte[]> payload) imp=
lements SaslStep {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _}_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _record Success(SaslIdentity identity, Optional=
<byte[]> serverData, String log) implements SaslStep {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _}_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _record Failure(String log) implements SaslStep=
 {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _}_
=C2=A0 =C2=A0 _}_

The intended split is:

=C2=A0- SASL mechanisms own payload parsing, challenge generation, response=
 validation, final SASL identity extraction, and exchange state.
=C2=A0- IMAP/SMTP bridges own base64/wire framing, continuation responses, =
success/failure session side effects, audit logs, hooks, and protocol-speci=
fic error responses.

For example, PLAIN and OAUTHBEARER can complete from=C2=A0{_}firstStep(){_}=
, while GSSAPI can return Challenge first and complete later from=C2=A0{_}o=
nResponse(...){_}.

SaslIdentity carries both the authentication identity and the authorization=
 identity. This keeps delegation expressible by the SPI without granting it=
 automatically. IMAP/SMTP bridges still decide whether delegation is allowe=
d and how to build their protocol session state.


*Loading and registry*
--------------------

Mechanism loading would follow the same spirit as JMAP authentication strat=
egies:

=C2=A0 =C2=A0 _interface SaslMechanismLoader {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _ImmutableList<SaslMechanism> load(Collection<S=
tring> classNames);_
=C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 _class SaslMechanismRegistry {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _Optional<SaslMechanism> find(String mechanismN=
ame, SaslProtocol protocol) {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _// match by mechanism name and s=
upports(protocol)_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _}_

=C2=A0 =C2=A0 =C2=A0 =C2=A0 _Stream<SaslMechanism> availableFor(SaslProtoco=
l protocol,=C2=A0SaslSessionContext=C2=A0context) {_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 _// filter supports(protocol) and=
 isAvailable(context)_
=C2=A0 =C2=A0 =C2=A0 =C2=A0 _}_
=C2=A0 =C2=A0 _}_

Then implement a=C2=A0GuiceSaslMechanismLoader=C2=A0to actually load the SA=
SL mechanisms.


*Incremental implementation plan*
-------------------------------

I suggest doing this step by step:

-=C2=A0{_}Step 1{_}: Start with a POC introducing the shared SaslMechanism =
SPI and adapting IMAP.
=C2=A0 This must not introduce breaking changes to existing authentication =
configs. The SASL SPI can be adopted gradually, protocol by protocol, witho=
ut changing behavior for protocols that have not adopted it yet.
-=C2=A0{_}Step 2{_}: Adapt SASL modularization for SMTP.
-=C2=A0{_}Step 3{_}: Leverage the SASL modularization to implement GSSAPI /=
 Kerberos mechanism support.

ManageSieve and POP3 are not part of the immediate scope, but the SPI shoul=
d make later adoption possible.

*Testing expectations*
--------------------

At minimum, I think we should prove:

=C2=A0- no breaking change: absent auth.saslMechanisms keeps existing IMAP/=
SMTP SASL methods;
=C2=A0- configured custom SASL mechanism loads and authenticates through re=
al or near-real protocol wiring;
=C2=A0- authentication / authorization identity handling preserves existing=
 delegation behavior (we should already have tests for these);
=C2=A0- fake multi-step mechanism works for continuation;
=C2=A0- exchange cleanup is covered.

*Feedback requested*
------------------

Does this refactoring direction look reasonable to you? Feedback and discus=
sion are welcome!
Meanwhile, I would start POC work on my side to see how this design goes...

Regards,
Quan



--
This message was sent by Atlassian Jira
(v8.20.10#820010)