Skip to main content

Implement a custom SASL authentication mechanism

By default Halon supports two SASL AUTH mechanisms (LOGIN and PLAIN). Both of these mechanisms are simple username/password based methods which does not require complex state transitions or a server based reply in order to obtain a username and password which can then be easily authenticated in the AUTH script. There are various way of verifying the username and password, common way of doing so in the Halon MTA includes

  • Forwarding SMTP lookup (connect to another SMTP server to verify the same username/password)
  • LDAP bind lookup
  • API lookup using eg. HTTP to verify credentials
  • Dovecot AUTH
  • A manual list of usernames and passwords

However other SASL mechanisms exist and most of them do not have a simple username/password scheme, some rely on a server generated challenge or multiple steps (responses/replies) in order to authenticate the user (e.g. OAUTHBEARER, OTP and CRAM-MD5). In order to enable you to build custom authentication we've given you direct access to the client response data (as defined by rfc4954) and also the ability to send a custom reply (response) while holding a state in the AUTH script.

The initial client response will be available in the first execution of the AUTH script (in the $arguments["response"] variable). The $arguments["state"] will be zero.

AUTH mechanism [initial-response]

You may then choose any of the following four functions

  • Accept - Accept the request (you should also set the username by argument)
  • Defer - Deny the authentication request with a temporary error
  • Reject - Deny the authentication request with a permanent error
  • Reply - Send a reply which will cause the next user-input to execute the authentication script again

The actions of Accept, Defer and Reject are straight forward regardless of the $arguments["state"] they are called in, so let us focus on how to use the Reply functions. The Reply function sends a 334 reply to the client with the response you provided (base64 encoded). The next user input will execute the AUTH script once again with $arguments["state"] incremented by one (in this example it's gonna be one).

334 [reply]
[client-response]

This chat scheme will move on until you call either Accept, Defer or Reject. In order to save state between each AUTH script execution, you can store data in the $context variable, based on the current $arguments["state"].

In order to announce and support (run the AUTH script for that mechanism) a custom SASL mechanism you will need to add it to the SMTP servers list of supported mechanisms in the configuration.

CRAM-MD5

In this section we will implement the CRAM-MD5 mechanism to our AUTH script. We start by announcing support for it in the SMTP server by adding CRAM-MD5 to the supported mechanisms in the configuration along with the other mechanisms we also still want to support (e.g. LOGIN and PLAIN). Then add the following to your AUTH script.

AUTH context
if ($arguments["mechanism"] == "CRAM-MD5")
{
if ($arguments["state"] == 0)
{
if ($arguments["response"] != none)
Reject("Bad syntax");
$context["saslchallenge"] = "<" . uuid() . "@" . gethostname() . ">";
Reply($context["saslchallenge"]);
}
if ($arguments["state"] == 1)
{
[$username, $password] = explode(" ", $arguments["response"]);
if ($username == "john.doe" and $password == hmac_md5("secret", $context["saslchallenge"]))
Accept(["username" => $username]);
Reject("Sorry");
}
}