This looks really reasonable to me.
Other mail servers achieve sasl code mutualisation and expose it as extension.
I'd have naturaly bundled the side effects behind the sasl API to mimic current 
authentication code but I am actually curious to see where the more descriptive 
approach leads us to!
-- 


Best regards,

Benoit TELLIER

General manager of Linagora VIETNAM.
Product owner for Twake-Mail product.
Chairman of the Apache James project.

Mail: [email protected]
Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)



On Jun 3, 2026 5:22 AM, from Quan Tran Hong <[email protected]>Hi all,

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 *AuthenticateProcessor* or SMTP
*AuthCmdHandler*. This makes the protocol handlers accumulate
mechanism-specific branches, and would make future mechanisms such as
GSSAPI / Kerberos harder to add cleanly.

The goal is:

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

This takes inspiration from JMAP authentication strategies configuration
and Guice loading.


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

Each protocol would keep an operator-visible *auth.saslMechanisms* list.

Example for IMAP:



*    <auth>
<saslMechanisms>PlainSaslMechanism,OauthBearerSaslMechanism,XOauth2SaslMechanism,com.example.james.kerberos.GssapiSaslMechanism</saslMechanisms>
  </auth>*

Example for SMTP:



*    <auth>
<saslMechanisms>LoginSaslMechanism,PlainSaslMechanism,OauthBearerSaslMechanism,XOauth2SaslMechanism,com.example.james.kerberos.GssapiSaslMechanism</saslMechanisms>
  </auth>*

Expected semantics:

 - if *auth.saslMechanisms* is absent, load current protocol defaults;
 - IMAP defaults remain PLAIN, OAUTHBEARER, XOAUTH2;
 - SMTP defaults remain LOGIN, PLAIN, OAUTHBEARER, XOAUTH2;
 - if configured, load the configured list in the configured order;
 - simple class names resolve against James default SASL packages;
 - fully qualified class names allow external/community extensions;

Existing protocol settings still decide availability. For example, plain
auth restrictions still decide whether PLAIN is advertised, OIDC
configuration still decides whether OAuth mechanisms are advertised, and
SMTP *auth.announce* still controls whether SMTP advertises AUTH.


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

I propose introducing protocol-neutral SASL types in *protocols/api*, for
example under *org.apache.james.protocols.api.sasl*.

The core idea is a stateful SaslExchange: each SASL mechanism creates one
exchange per authentication attempt, and that exchange owns mechanism state
across the initial request and continuation responses.

Core structure:







































*    interface SaslMechanism {        String name();        boolean
supports(SaslProtocol protocol);        boolean
isAvailable(SaslSessionContext context);        SaslExchange
start(SaslInitialRequest request, SaslSessionContext context);    }    enum
SaslProtocol {        IMAP, SMTP, MANAGESIEVE, POP3    }    record
SaslInitialRequest(SaslProtocol protocol, String mechanismName,
Optional<byte[]> initialResponse) {    }    record SaslIdentity(Username
authenticationId, Username authorizationId) {    }    interface
SaslSessionContext {        SaslProtocol protocol();        boolean
isTlsStarted();        <T> Optional<T> configuration(Class<T>
configurationType);    }    interface SaslExchange extends AutoCloseable {
      SaslStep firstStep();        SaslStep onResponse(byte[]
clientResponse);        void abort();        @Override        void
close();    }    interface SaslStep {        record
Challenge(Optional<byte[]> payload) implements SaslStep {        }
record Success(SaslIdentity identity, Optional<byte[]> serverData, String
log) implements SaslStep {        }        record Failure(String log)
implements SaslStep {        }    }*

The intended split is:

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

For example, PLAIN and OAUTHBEARER can complete from *firstStep()*, while
GSSAPI can return Challenge first and complete later from *onResponse(...)*.

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 allowed
and how to build their protocol session state.


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

Mechanism loading would follow the same spirit as JMAP authentication
strategies:













*    interface SaslMechanismLoader {        ImmutableList<SaslMechanism>
load(Collection<String> classNames);    }    class SaslMechanismRegistry {
      Optional<SaslMechanism> find(String mechanismName, SaslProtocol
protocol) {            // match by mechanism name and supports(protocol)
    }        Stream<SaslMechanism> availableFor(SaslProtocol protocol,
SaslSessionContext context) {            // filter supports(protocol) and
isAvailable(context)        }    }*

Then implement a GuiceSaslMechanismLoader to actually load the SASL
mechanisms.


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

I suggest doing this step by step:

- *Step 1*: Start with a POC introducing the shared SaslMechanism SPI and
adapting IMAP.
  This must not introduce breaking changes to existing authentication
configs. The SASL SPI can be adopted gradually, protocol by protocol,
without changing behavior for protocols that have not adopted it yet.
- *Step 2*: Adapt SASL modularization for SMTP.
- *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
should make later adoption possible.

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

At minimum, I think we should prove:

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

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

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

Regards,
Quan

Reply via email to