Hello,
For a long time I've had a need for supporting more complicated
authentication mechanisms in OpenSMTPD than simple username/password
lookups. For the most part I can work around it but I'd really like to
be able to do it properly. On the other hand it would involve a lot of
complexity that definitely doesn't belong in OpenSMTPD itself, so I've
been thinking about how to go about extending the filter API with
support for implementing full SASL mechanisms.
It's a pretty early idea so far, and I'm not at all familiar with the
OpenSMTPD codebase so I'm not sure that I would be able to implement it
myself, but I wanted to throw the idea out there to see if the project
would be interested in it.
The API extension I've drafted so far only takes client authentication
into account, but the same idea should be easily extended to also
support authenticating against relays for e.g. XOAUTH2 support with GMail.
# Registering
> config|ready
< register|auth|MECHANISM
Where `MECHANISM` is the SASL mechanism name that OpenSMTPD would then
advertise to connections that the filter is attached to. A filter can
override one of the built-in mechanisms.
It could be argued that being able to override the built-in mechanisms
would be redundant with auth proc tables, but allowing for it means that
you a single filter can contain all authentication mechanism
implementations for a given authentication backend.
Preferably we'd be able to also explicitly disable support for all
built-in authentication mechanisms for a given listener to allow for
stricter control over available mechanisms.
# Authentication exchange
When the client issues an `AUTH` command with a mechanism that the
filter has registered, the server will send the following command to the
filter. If the client didn't specify an initial response the 8th field
is absent to allow for distinguishing between a non-present and an empty
initial response.
>
auth|0.7|0000000000.000000|initiate|7641df9771b4ed00|1ef1c203cc576e5d|MECHANISM(|INITIAL-RESPONSE)
Then the filter replies with a challenge and waits for a response from
the client. This exchange is repeated until either the filter sends
back an authentication status or the client cancels the authentication
exchange by sending a line consisting of a single asterisk.
Challenges and responses of lengths up to 12288 bytes should be handled
as recommended by RFC4954. Too long responses leads to the server
failing auth with `500 5.5.6`.
<
auth|0.7|0000000000.000000|challenge|7641df9771b4ed00|1ef1c203cc576e5d|CHALLENGE
= 334 base64(CHALLENGE)
>
auth|0.7|0000000000.000000|challenge-response|7641df9771b4ed00|1ef1c203cc576e5d|CHALLENGE-RESPONSE
…
<
auth|0.7|0000000000.000000|status|7641df9771b4ed00|1ef1c203cc576e5d|successful|AUTHZID
= 235 2.7.0 Authentication succeeded
<
auth|0.7|0000000000.000000|status|7641df9771b4ed00|1ef1c203cc576e5d|invalid-initial-response
= 501 5.7.0
<
auth|0.7|0000000000.000000|status|7641df9771b4ed00|1ef1c203cc576e5d|temporary-failure
= 454 4.7.0 Temporary authentication failure
<
auth|0.7|0000000000.000000|status|7641df9771b4ed00|1ef1c203cc576e5d|mechanism-too-weak
= 534 5.7.9 Authentication mechanism is too weak
<
auth|0.7|0000000000.000000|status|7641df9771b4ed00|1ef1c203cc576e5d|invalid-credentials
= 535 5.7.8 Authentication credentials invalid
Alternatively we could have a single "failed" status but let the filter
itself specify the status code and text.
If the client cancels the authentication exchange by sending a line
consisting of a single asterisk:
> auth|0.7|0000000000.000000|cancel|7641df9771b4ed00|1ef1c203cc576e5d
= 501
(Do we want an explicit acknowledge from the filter?)
# Open questions
1.