Re: [DISCUSS] Generic SASL mechanism extension model for James protocols

Benoit TELLIER <[email protected]> Wed, 3 Jun 2026 05:30:38 +0000
Newsgroups gmane.comp.jakarta.james.devel
Message-ID <[email protected]>
---=Part.55c.db52ebe66a5b0f8f.19e8bf65a95.b981ca89d344dfe4=-
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE
Content-Language: en-US

This looks really reasonable to me=2E
Other mail servers achieve sasl code =
mutualisation and expose it as extension=2E
I'd have naturaly bundled the s=
ide 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!=

--=C2=A0


Best regards,

Benoit TELLIER

General manager of Linagora VIET=
NAM=2E
Product owner for Twake-Mail product=2E
Chairman of the Apache James=
 project=2E

Mail: btellier@linagora=2Ecom
Tel: (0033) 6 77 26 04 58 (Whats=
App, Signal)



On Jun 3, 2026 5:22 AM, from Quan Tran Hong <quan=2Etranhon=
g1999@gmail=2Ecom>Hi all,

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

Today, when adding a new authentic=
ation mechanism for IMAP or SMTP, we need
to modify core protocol code, suc=
h as IMAP *AuthenticateProcessor* or SMTP
*AuthCmdHandler*=2E This makes th=
e protocol handlers accumulate
mechanism-specific branches, and would make =
future mechanisms such as
GSSAPI / Kerberos harder to add cleanly=2E

The g=
oal is:

 - adding a new SASL mechanism should not require modifying core I=
MAP/SMTP
command handlers;
 - protocol code should keep protocol framing an=
d side effects;
 - SASL mechanism code should own mechanism semantics and e=
xchange state;
 - existing IMAP/SMTP behavior should remain unchanged when =
no new
configuration is provided=2E

This takes inspiration from JMAP authe=
ntication strategies configuration
and Guice loading=2E


*Configuration sh=
ape*
-------------------

Each protocol would keep an operator-visible *aut=
h=2EsaslMechanisms* list=2E

Example for IMAP:



*    <auth>
<saslMechanis=
ms>PlainSaslMechanism,OauthBearerSaslMechanism,XOauth2SaslMechanism,com=2Ee=
xample=2Ejames=2Ekerberos=2EGssapiSaslMechanism</saslMechanisms>
  </auth>*=


Example for SMTP:



*    <auth>
<saslMechanisms>LoginSaslMechanism,Plain=
SaslMechanism,OauthBearerSaslMechanism,XOauth2SaslMechanism,com=2Eexample=
=2Ejames=2Ekerberos=2EGssapiSaslMechanism</saslMechanisms>
  </auth>*

Expe=
cted semantics:

 - if *auth=2EsaslMechanisms* is absent, load current prot=
ocol 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 ava=
ilability=2E For example, plain
auth restrictions still decide whether PLAI=
N is advertised, OIDC
configuration still decides whether OAuth mechanisms =
are advertised, and
SMTP *auth=2Eannounce* still controls whether SMTP adve=
rtises AUTH=2E


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

I propose introdu=
cing protocol-neutral SASL types in *protocols/api*, for
example under *org=
=2Eapache=2Ejames=2Eprotocols=2Eapi=2Esasl*=2E

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=2E

Core structure:




























=











*    interface SaslMechanism {        String name();        bool=
ean
supports(SaslProtocol protocol);        boolean
isAvailable(SaslSession=
Context context);        SaslExchange
start(SaslInitialRequest request, Sas=
lSessionContext context);    }    enum
SaslProtocol {        IMAP, SMTP, MA=
NAGESIEVE, POP3    }    record
SaslInitialRequest(SaslProtocol protocol, St=
ring mechanismName,
Optional<byte[]> initialResponse) {    }    record Sasl=
Identity(Username
authenticationId, Username authorizationId) {    }    int=
erface
SaslSessionContext {        SaslProtocol protocol();        boolean
=
isTlsStarted();        <T> Optional<T> configuration(Class<T>
configuration=
Type);    }    interface SaslExchange extends AutoCloseable {
      SaslSte=
p firstStep();        SaslStep onResponse(byte[]
clientResponse);        vo=
id abort();        @Override        void
close();    }    interface SaslSte=
p {        record
Challenge(Optional<byte[]> payload) implements SaslStep {=
        }
record Success(SaslIdentity identity, Optional<byte[]> serverData=
, String
log) implements SaslStep {        }        record Failure(String l=
og)
implements SaslStep {        }    }*

The intended split is:

 - SASL m=
echanisms own payload parsing, challenge generation, response
validation, f=
inal SASL identity extraction, and exchange state=2E
 - IMAP/SMTP bridges o=
wn base64/wire framing, continuation responses,
success/failure session sid=
e effects, audit logs, hooks, and
protocol-specific error responses=2E

For=
 example, PLAIN and OAUTHBEARER can complete from *firstStep()*, while
GSSA=
PI can return Challenge first and complete later from *onResponse(=2E=2E=2E=
)*=2E

SaslIdentity carries both the authentication identity and the author=
ization
identity=2E This keeps delegation expressible by the SPI without gr=
anting it
automatically=2E IMAP/SMTP bridges still decide whether delegatio=
n is allowed
and how to build their protocol session state=2E


*Loading an=
d registry*
--------------------

Mechanism loading would follow the same s=
pirit as JMAP authentication
strategies:













*    interface SaslMe=
chanismLoader {        ImmutableList<SaslMechanism>
load(Collection<String>=
 classNames);    }    class SaslMechanismRegistry {
      Optional<SaslMech=
anism> find(String mechanismName, SaslProtocol
protocol) {            // ma=
tch by mechanism name and supports(protocol)
    }        Stream<SaslMechan=
ism> availableFor(SaslProtocol protocol,
SaslSessionContext context) {     =
       // filter supports(protocol) and
isAvailable(context)        }    }*=


Then implement a GuiceSaslMechanismLoader to actually load the SASL
mecha=
nisms=2E


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

I suggest doing this step by step:

- *Step 1*: Start with a POC introdu=
cing the shared SaslMechanism SPI and
adapting IMAP=2E
  This must not intr=
oduce breaking changes to existing authentication
configs=2E The SASL SPI c=
an be adopted gradually, protocol by protocol,
without changing behavior fo=
r protocols that have not adopted it yet=2E
- *Step 2*: Adapt SASL modulari=
zation for SMTP=2E
- *Step 3*: Leverage the SASL modularization to implemen=
t GSSAPI / Kerberos
mechanism support=2E

ManageSieve and POP3 are not part=
 of the immediate scope, but the SPI
should make later adoption possible=2E=


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

At minimum, I think we shoul=
d prove:

 - no breaking change: absent auth=2EsaslMechanisms keeps existin=
g IMAP/SMTP
SASL methods;
 - configured custom SASL mechanism loads and aut=
henticates through real or
near-real protocol wiring;
 - authentication / a=
uthorization identity handling preserves existing
delegation behavior (we s=
hould already have tests for these);
 - fake multi-step mechanism works for=
 continuation;
 - exchange cleanup is covered=2E

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

Does this refactoring direction look reasonable to you? Fee=
dback and
discussion are welcome!
Meanwhile, I would start POC work on my s=
ide to see how this design goes=2E=2E=2E

Regards,
Quan


---=Part.55c.db52ebe66a5b0f8f.19e8bf65a95.b981ca89d344dfe4=---