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
:mechanisms()
is called in order to present the client with the supported mechanisms.:select(mech)
is called when the client initiates the authentication attempt.: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
.selected = mechanism;
selfreturn 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
.username = "bob"
selfreturn "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
.