You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by Quan tran hong <qu...@gmail.com> on 2026/06/03 03:22:08 UTC
[DISCUSS] Generic SASL mechanism extension model for James protocols
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
Re: [DISCUSS] Generic SASL mechanism extension model for James protocols
Posted by Quan tran hong <qu...@gmail.com>.
Hello folks,
FYI, following the SASL SPI introduction + IMAP adoption at
https://github.com/apache/james-project/pull/3059, we applied the SASL
adoption for SMTP at https://github.com/apache/james-project/pull/3072.
The IMAP + SMTP adoption looks good enough that we can be confident that
the SASL SPI should be generic enough for other protocols like POP3 or
ManageSieve, and also other SASL mechanism extension like Kerberos in the
future.
Review and feedback are welcome. After the 2 above PRs are merged, we would
plan to adapt SASL for POP3 and ManageSieve as well.
Cheers,
Quan
On Tue, Jun 23, 2026 at 3:13 PM Quan tran hong <qu...@gmail.com>
wrote:
> Hello Benoit,
>
> Thanks for your suggestions! That sounds reasonable to me.
>
> I will attempt to apply the suggestions for SMTP.
>
> Regards,
>
> Quan
>
> On Mon, Jun 22, 2026 at 5:17 PM Benoit TELLIER <bt...@linagora.com>
> wrote:
>
>> Hello Quan
>>
>> Here are my thoughts.
>>
>> I think AuthHook encompasses two things:
>>
>> - A bad implementation of SASL (no advertized capability) which is Ok
>> dropping.
>>
>> We could potentially even propose a SaslAuthHookAdapter that plugs in the
>> SASL flow and delegates to the given AuthHook thus easing migration?
>> It would then be mostly conf to adapt ones axtension...
>>
>> - Decorate login eg to push a notif somewhere.
>>
>> Maybe we culd have a new hook (SaslAuthResultHook ?) for being able to
>> decorate this and allow adding side effect effectively?
>>
>>
>> So I'd go with:
>> - Deprecate AuthHook
>> - Introduce a SaslAuthHookAdapter that one can extend to record existing
>> AuthHook. The SaslAuthHookAdapter should be flexible enough to allow
>> override the PLAIN mechanism.
>> - Have a new hook: SaslAuthResultHook
>> - Write an upgrade-instructions regarding this.
>>
>> Would this make sense ?--
>>
>>
>> Best regards,
>>
>> Benoit TELLIER
>>
>> General manager of Linagora VIETNAM.
>> Product owner for Twake-Mail product.
>> Chairman of the Apache James project.
>>
>> Mail: btellier@linagora.com
>> Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
>>
>>
>>
>> On Jun 22, 2026 12:08 PM, from Quan Tran Hong <
>> quan.tranhong1999@gmail.com>Hello folks,
>>
>> Following the Generic SASL modularization and adoption for IMAP at
>> github.com/apache/james-project/pull/3059, I am working further on
>> the adoption for SMTP (based on the IMAP PR) to see how it goes, before we
>> can decide if it is good to merge the IMAP PR and go forward with this
>> SASL
>> refactoring.
>>
>> Regarding SMTP adoption, I am not sure if we should drop the `AuthHook`
>> interface (and its extension capability).
>>
>> *If we drop `AuthHook` for SMTP*, then we would rely purely on the SASL
>> layer for the authentication logic, which should reduce the code
>> complexity. However, community `AuthHook` extension implementors would
>> then
>> need to be rewritten under a custom `SaslMechanism` implementation
>> instead.
>> Example of what we proposed for IMAP custom SASL:
>>
>> github.com/apache/james-project/pull/3059/changes/3f0a554e4c3b3914daecbd41b75a6aa21ae9c8cf
>> .
>> Also, please note that it may not be straightforward to just replace
>> `AuthHook` with the `SaslMechanism` extension: AuthHook can extend the
>> *PLAIN* auth capability, while SaslMechanism would likely be distinct by
>> the mechanism name (PLAIN) and only override is possible.
>>
>> *If we keep `AuthHook` for SMTP,* we would mix the legacy `AuthHook` code
>> and the modular SASL layer for SMTP authentication, which may introduce
>> extra complexity for SMTP authentication. Also, having 2 mechanisms to
>> configure authentication for SMTP (`AuthHook` and `SaslMechanism`) feels
>> odd. However, keeping the `AuthHook` would be less invasive for community
>> extension usage, if any.
>>
>> I am not sure about the `AuthHook` extension usage in our community, and
>> the best way to go for SMTP adoption here. Maybe I would go the defensive
>> way to try to keep the `AuthHook` mechanism, to avoid invasion. Or do you
>> think the code complexity and the limited adoption of `AuthHook` are not
>> worth keeping?
>>
>> Feedback and opinion are very welcome!
>>
>> Quan
>>
>>
>> On Wed, Jun 3, 2026 at 12:30 PM Benoit TELLIER <bt...@linagora.com>
>> wrote:
>>
>> > 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: btellier@linagora.com
>> > Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
>> >
>> >
>> >
>> > On Jun 3, 2026 5:22 AM, from Quan Tran Hong <
>> quan.tranhong1999@gmail.com>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
>> >
>> >
>>
>
Re: [DISCUSS] Generic SASL mechanism extension model for James protocols
Posted by Quan tran hong <qu...@gmail.com>.
Hello Benoit,
Thanks for your suggestions! That sounds reasonable to me.
I will attempt to apply the suggestions for SMTP.
Regards,
Quan
On Mon, Jun 22, 2026 at 5:17 PM Benoit TELLIER <bt...@linagora.com>
wrote:
> Hello Quan
>
> Here are my thoughts.
>
> I think AuthHook encompasses two things:
>
> - A bad implementation of SASL (no advertized capability) which is Ok
> dropping.
>
> We could potentially even propose a SaslAuthHookAdapter that plugs in the
> SASL flow and delegates to the given AuthHook thus easing migration?
> It would then be mostly conf to adapt ones axtension...
>
> - Decorate login eg to push a notif somewhere.
>
> Maybe we culd have a new hook (SaslAuthResultHook ?) for being able to
> decorate this and allow adding side effect effectively?
>
>
> So I'd go with:
> - Deprecate AuthHook
> - Introduce a SaslAuthHookAdapter that one can extend to record existing
> AuthHook. The SaslAuthHookAdapter should be flexible enough to allow
> override the PLAIN mechanism.
> - Have a new hook: SaslAuthResultHook
> - Write an upgrade-instructions regarding this.
>
> Would this make sense ?--
>
>
> Best regards,
>
> Benoit TELLIER
>
> General manager of Linagora VIETNAM.
> Product owner for Twake-Mail product.
> Chairman of the Apache James project.
>
> Mail: btellier@linagora.com
> Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
>
>
>
> On Jun 22, 2026 12:08 PM, from Quan Tran Hong <qu...@gmail.com>Hello
> folks,
>
> Following the Generic SASL modularization and adoption for IMAP at
> github.com/apache/james-project/pull/3059, I am working further on
> the adoption for SMTP (based on the IMAP PR) to see how it goes, before we
> can decide if it is good to merge the IMAP PR and go forward with this SASL
> refactoring.
>
> Regarding SMTP adoption, I am not sure if we should drop the `AuthHook`
> interface (and its extension capability).
>
> *If we drop `AuthHook` for SMTP*, then we would rely purely on the SASL
> layer for the authentication logic, which should reduce the code
> complexity. However, community `AuthHook` extension implementors would then
> need to be rewritten under a custom `SaslMechanism` implementation instead.
> Example of what we proposed for IMAP custom SASL:
>
> github.com/apache/james-project/pull/3059/changes/3f0a554e4c3b3914daecbd41b75a6aa21ae9c8cf
> .
> Also, please note that it may not be straightforward to just replace
> `AuthHook` with the `SaslMechanism` extension: AuthHook can extend the
> *PLAIN* auth capability, while SaslMechanism would likely be distinct by
> the mechanism name (PLAIN) and only override is possible.
>
> *If we keep `AuthHook` for SMTP,* we would mix the legacy `AuthHook` code
> and the modular SASL layer for SMTP authentication, which may introduce
> extra complexity for SMTP authentication. Also, having 2 mechanisms to
> configure authentication for SMTP (`AuthHook` and `SaslMechanism`) feels
> odd. However, keeping the `AuthHook` would be less invasive for community
> extension usage, if any.
>
> I am not sure about the `AuthHook` extension usage in our community, and
> the best way to go for SMTP adoption here. Maybe I would go the defensive
> way to try to keep the `AuthHook` mechanism, to avoid invasion. Or do you
> think the code complexity and the limited adoption of `AuthHook` are not
> worth keeping?
>
> Feedback and opinion are very welcome!
>
> Quan
>
>
> On Wed, Jun 3, 2026 at 12:30 PM Benoit TELLIER <bt...@linagora.com>
> wrote:
>
> > 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: btellier@linagora.com
> > Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
> >
> >
> >
> > On Jun 3, 2026 5:22 AM, from Quan Tran Hong <quan.tranhong1999@gmail.com
> >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
> >
> >
>
Re: [DISCUSS] Generic SASL mechanism extension model for James protocols
Posted by Benoit TELLIER <bt...@linagora.com>.
Hello Quan
Here are my thoughts.
I think AuthHook encompasses two things:
- A bad implementation of SASL (no advertized capability) which is Ok dropping.
We could potentially even propose a SaslAuthHookAdapter that plugs in the SASL flow and delegates to the given AuthHook thus easing migration?
It would then be mostly conf to adapt ones axtension...
- Decorate login eg to push a notif somewhere.
Maybe we culd have a new hook (SaslAuthResultHook ?) for being able to decorate this and allow adding side effect effectively?
So I'd go with:
- Deprecate AuthHook
- Introduce a SaslAuthHookAdapter that one can extend to record existing AuthHook. The SaslAuthHookAdapter should be flexible enough to allow override the PLAIN mechanism.
- Have a new hook: SaslAuthResultHook
- Write an upgrade-instructions regarding this.
Would this make sense ?--
Best regards,
Benoit TELLIER
General manager of Linagora VIETNAM.
Product owner for Twake-Mail product.
Chairman of the Apache James project.
Mail: btellier@linagora.com
Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
On Jun 22, 2026 12:08 PM, from Quan Tran Hong <qu...@gmail.com>Hello folks,
Following the Generic SASL modularization and adoption for IMAP at
github.com/apache/james-project/pull/3059, I am working further on
the adoption for SMTP (based on the IMAP PR) to see how it goes, before we
can decide if it is good to merge the IMAP PR and go forward with this SASL
refactoring.
Regarding SMTP adoption, I am not sure if we should drop the `AuthHook`
interface (and its extension capability).
*If we drop `AuthHook` for SMTP*, then we would rely purely on the SASL
layer for the authentication logic, which should reduce the code
complexity. However, community `AuthHook` extension implementors would then
need to be rewritten under a custom `SaslMechanism` implementation instead.
Example of what we proposed for IMAP custom SASL:
github.com/apache/james-project/pull/3059/changes/3f0a554e4c3b3914daecbd41b75a6aa21ae9c8cf
.
Also, please note that it may not be straightforward to just replace
`AuthHook` with the `SaslMechanism` extension: AuthHook can extend the
*PLAIN* auth capability, while SaslMechanism would likely be distinct by
the mechanism name (PLAIN) and only override is possible.
*If we keep `AuthHook` for SMTP,* we would mix the legacy `AuthHook` code
and the modular SASL layer for SMTP authentication, which may introduce
extra complexity for SMTP authentication. Also, having 2 mechanisms to
configure authentication for SMTP (`AuthHook` and `SaslMechanism`) feels
odd. However, keeping the `AuthHook` would be less invasive for community
extension usage, if any.
I am not sure about the `AuthHook` extension usage in our community, and
the best way to go for SMTP adoption here. Maybe I would go the defensive
way to try to keep the `AuthHook` mechanism, to avoid invasion. Or do you
think the code complexity and the limited adoption of `AuthHook` are not
worth keeping?
Feedback and opinion are very welcome!
Quan
On Wed, Jun 3, 2026 at 12:30 PM Benoit TELLIER <bt...@linagora.com>
wrote:
> 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: btellier@linagora.com
> Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
>
>
>
> On Jun 3, 2026 5:22 AM, from Quan Tran Hong <qu...@gmail.com>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
>
>
Re: [DISCUSS] Generic SASL mechanism extension model for James protocols
Posted by Quan tran hong <qu...@gmail.com>.
Hello folks,
Following the Generic SASL modularization and adoption for IMAP at
https://github.com/apache/james-project/pull/3059, I am working further on
the adoption for SMTP (based on the IMAP PR) to see how it goes, before we
can decide if it is good to merge the IMAP PR and go forward with this SASL
refactoring.
Regarding SMTP adoption, I am not sure if we should drop the `AuthHook`
interface (and its extension capability).
*If we drop `AuthHook` for SMTP*, then we would rely purely on the SASL
layer for the authentication logic, which should reduce the code
complexity. However, community `AuthHook` extension implementors would then
need to be rewritten under a custom `SaslMechanism` implementation instead.
Example of what we proposed for IMAP custom SASL:
https://github.com/apache/james-project/pull/3059/changes/3f0a554e4c3b3914daecbd41b75a6aa21ae9c8cf
.
Also, please note that it may not be straightforward to just replace
`AuthHook` with the `SaslMechanism` extension: AuthHook can extend the
*PLAIN* auth capability, while SaslMechanism would likely be distinct by
the mechanism name (PLAIN) and only override is possible.
*If we keep `AuthHook` for SMTP,* we would mix the legacy `AuthHook` code
and the modular SASL layer for SMTP authentication, which may introduce
extra complexity for SMTP authentication. Also, having 2 mechanisms to
configure authentication for SMTP (`AuthHook` and `SaslMechanism`) feels
odd. However, keeping the `AuthHook` would be less invasive for community
extension usage, if any.
I am not sure about the `AuthHook` extension usage in our community, and
the best way to go for SMTP adoption here. Maybe I would go the defensive
way to try to keep the `AuthHook` mechanism, to avoid invasion. Or do you
think the code complexity and the limited adoption of `AuthHook` are not
worth keeping?
Feedback and opinion are very welcome!
Quan
On Wed, Jun 3, 2026 at 12:30 PM Benoit TELLIER <bt...@linagora.com>
wrote:
> 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: btellier@linagora.com
> Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
>
>
>
> On Jun 3, 2026 5:22 AM, from Quan Tran Hong <qu...@gmail.com>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
>
>
Re: [DISCUSS] Generic SASL mechanism extension model for James protocols
Posted by Benoit TELLIER <bt...@linagora.com>.
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: btellier@linagora.com
Tel: (0033) 6 77 26 04 58 (WhatsApp, Signal)
On Jun 3, 2026 5:22 AM, from Quan Tran Hong <qu...@gmail.com>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