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