Writing SASL handlers

SASL handler overview

Sometimes the simple get_password() or test_password() methods of authentication plugins are not enough and you need to implement a custom SASL handler.

The basic flow when a client authenticates is

  1. :mechanisms() is called in order to present the client with the supported mechanisms.
  2. :select(mech) is called when the client initiates the authentication attempt.
  3. :process(message) is called for each message the client sends.

mechanisms

The mechanisms method of the SASL object returns a set of supported mechanisms.

function sasl:mechanisms()
    return {
        ["PLAIN"] = true;
        -- other mechanisms
 
    }
end

select

Called when the user starts authentication.

function sasl:select(mechanism)
    if self:mechanisms()[mechanism] then
        self.selected = mechanism;
        return true;
    else
        return false;
    end
end

process

SASL is a back-and-forth messaging protocol. Each message the client sends is passed to the process method, which is expected to return a status code and optional response.

function sasl:process(message)
    if message == "I am Bob" then
        self.username = "bob"
        return "success"
    elseif message == "I am Eve" then
        return "failure", "not-authorized", "I don't think so"
    elseif message == "Who am I?" then
        return "challenge", "Try to remember";
    else
        return "failure", "error-code", "Something bad happened";
    end
end

The first return value is one of "success", "challenge" or "failure". Simple mechanisms might just validate the message and return success or failure, but more complicated mechanisms may return challenges for the client to complete.

Additional data may be passed as a second return value for success or challenge messages, eg for multi-step mechanisms or for mutual authentication. On failure, a SASL error code should be passed as second return value and an optional textual message as third. Make sure to not distinguish between eg "wrong password" and "no such user" in order to prevent directory harvesting attacks.

Backend profiles

Authentication modules provide one or more ‘backend profiles’, which helps Prosody determine what SASL mechanisms they are compatible with.

State

Backends return a state variable, which indicates the status of the user’s account. Valid values are:

true
Account is enabled
false
Account is disabled
nil
Account does not exist

Possible methods

plain(self, username, realm)

Implement this if you have access to the plaintext password. Although storing plaintext passwords is generally not recommended, it enables certain mechanisms to work.

Returns password:string, state:bool?

plain_test(self, username, password, realm)

Implement this if you are able to check if a given plaintext password is valid.

Returns is_correct_password:bool, state:bool?.

scram_

Implement this if you are able to provide SCRAM-compatible stored credentials.

All string values should be returned as binary blobs (no hex encoding, etc.).

Returns stored_key:string, server_key:string, iteration_count:number, salt:string, state:bool?.

anonymous(self, username, realm)

Implement this to allow anonymous logins. The provided username is randomly generated. If it is acceptable, return true to proceed. If you return false or nil, your method may be called again with another username until a successful one is found.

Returns is_acceptable_username:bool.

external(self, message)

Implement this to support SASL’s ‘EXTERNAL’ mechanism. The message (if any) is provided by the client (already run through SASLprep).

The following additional values of state have special meanings:

"expired" (string)
Provided previously valid credentials, but they have expired.

Returns: state.