Authentication process design
There are four major classes in the code:
struct mech_module: Authentication mechanism
struct password_scheme: Password scheme
struct userdb_module: User database
struct passdb_module: Password database
There are a lot of implementations for each of these, and it's simple to add more of them. It should be easy to add more of them as plugins, although the current plugin loading code doesn't allow loading authentication mechanisms cleanly, and it's not possible to add new credentials (see below).
The code flow usually goes like:
- Authentication client begins an authentication
- Authentication mechanism backend handles it
- The mechanism looks up the password from passdb and the password scheme code to verifies it
- If user is logging in, the user information is looked up from the userdb
It's also possible to request a userdb lookup directly, for example Dovecot's [:LDA:deliver] needs that.
These are [:Sasl:SASL] authentication mechanism implementations. See ["Authentication/Mechanisms"] for a list of mechanisms supported by Dovecot.
A new mechanism is created by filling a struct mech_module (in mech.h) and passing it to mech_register_module(). The struct fields are:
- The public name of the mechanism. This is shown to clients in the IMAP, POP3 and SMTP capability lists. If you create a new non-standard mechanism, please prefix it with "X-".
Describes how secure the mechanism is. Also MECH_SEC_PRIVATE flag specifies that the mechanism shouldn't be advertised in the capability list. This is currently used only for APOP mechanism, which is defined by the POP3 protocol itself.
This mechanism uses passdb's verify_plain() function to verify the password's validity. This means that the mechanism has access to the plaintext password. This is true only for plaintext mechanisms such as PLAIN and LOGIN. The main purpose of this flag is to make dovecot-auth complain at startup if there are no passdbs defined in the configuration file. Note that a configuration without any passdbs is valid with eg. GSSAPI mechanism which doesn't need a passdb at all.
This mechanism uses passdb's lookup_credentials() function. See below for description of the credentials.
Allocates a new struct auth_request. Typically with more complex mechanisms it really allocates a struct <mech>_auth_request which contains struct auth_request as the first field, followed by mechanism-specific fields.
- auth_initial(request, data, data_size)
This begins the authentication, data and data_size containing the initial response sent by the client (decoded, not in base64). Call request->callback() once you're done (see below).
- auth_continue(request, data, data_size)
Continues the authentication. Works the same as auth_initial().
Free the request. Usually all the memory allocations for the request should be allocated from request->pool, so you can use mech_generic_auth_free() which simply frees the pool.
auth_initial() and auth_continue() continue or finish the authentication by calling request->callback():
typedef void mech_callback_t(struct auth_request *request, enum auth_client_result result, const void *reply, size_t reply_size);
The reply and reply_size contain the server's mechanism-specific reply to the client. If there is no need to return anything (which is usually the case with the "success" reply), the reply_size can be 0. The result parameter is one of:
- AUTH_CLIENT_RESULT_CONTINUE: Client can continue the authentication. The reply contains the mechanism-specific reply sent to the client.
- AUTH_CLIENT_RESULT_SUCCESS: Authentication successful. The reply is usually empty.
- AUTH_CLIENT_RESULT_FAILURE: Authentication failed. The reply is always ignored.
The request->callback() should actually be called directly only for continuation requests (a new function should probably be added for this as well). For success and failure replies, you should instead use one of these functions:
auth_request_internal_failure(): Use this if you couldn't figure out if the authentication succeeded or failed, for example because passdb lookup returned internal failure.
SASL authentication in general works like:
Client begins the authentication, optionally sending an "initial response", meaning some data that the mechanism sees in auth_initial().
Note that not all protocols support the initial response. For example IMAP supports it only if the server implements SASL-IR extension. Because of this mechanisms, such as PLAIN, support doing the authentication either in auth_initial() or in auth_continue().
If the client initiates the authentication (ie. server's initial reply is empty, such as with PLAIN mechanism) you can use mech_generic_auth_initial() instead of implementing your own.
Server processes the authentication request and replies back with request->callback().
If the authentication failed, it's placed into auth_failures_buf unless request->no_failure_delay=TRUE. The failures are flushed from the buffer once every 2 seconds to clients and mechanism->auth_free() is called.
- If the authentication succeeded and
there is a master connection associated with the request (IMAP/POP3 login), the authentication now waits for master connection to do a verification request. If this for some reason doesn't happen in AUTH_REQUEST_TIMEOUT seconds (3,5 mins), it's freed.
- there isn't a master connection (SMTP AUTH), the authentication is freed immediately.
- Client processes the reply:
If the authentication continues, it sends back more data which is processed in auth_continue(). Goto 2.
- If the authentication failed, it's done.
- If the authentication succeeded, the client requests a login from the master process, which in turn requests verification from the auth process.
- Besides verifying the authentication, dovecot-auth also does a userdb lookup to return the userdb information to master.
- If the verification fails (normally because userdb lookup fails), the client gets "internal authentication failure"
- If the verification succeeds, the user is now logged in
In either case, mechanism->auth_free() is called now.
Most of the non-plaintext mechanisms need to verify the authentication by using a special hash of the user's password. So either the passdb credentials lookup returns a plaintext password from which the hash can be created, or the hash directly. The plaintext to hash conversion is done by calling password_generate function of the password scheme.
Unfortunately the list of allowed credentials is currently hardcoded in enum passdb_credentials. The enum values are mapped to password scheme strings in passdb_credentials_to_str(). Some day the enum will be removed so plugins can add new mechanisms. Besides the mechanism-specific credentials, the enum contains:
- I don't remember why this really exists. It should probably be called _PASSDB_CREDENTIALS_INVALID or something and used only by some asserts..
- Request a plaintext password.
Request the password in any scheme. This is especially useful if you only want to verify a user's existence in a passdb. Used by [:UserDatabase/Static:static userdb] in userdb lookups.