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.


Reply via email to