CVM: Credential Validation Module

Description

CVM is a framework for validating a set of credentials against a database using a filter program. The modules act as a filter, taking a set of credentials as input and writing a set of facts as output if those credentials are valid. Optional input is given to the module through environment variables.

Some of the ideas for CVM came from experience with the checkpassword interface used by qmail-pop3d, and the "authmod" interface used by Courier IMAP and POP3. This framework places fewer restrictions on the invoking client than checkpassword does, and is much simpler to implement on both sides than the authmod framework.

Input

Input to the authenticator is as follows. All items are NUL-terminated strings. The total length of the input must not exceed 4096 bytes.

  1. Account identifier (ie user name).
  2. List of credentials.

The credentials consist of one of the following:

Each module will implement a single type of credential validation. The invoker will choose which modules to invoke depending on what type of credentials it needs validated.

Environment Variables

The following environment variables may be set by the invoker:

SERVICE
The service name, to be used (for example) by PAM modules to determine which configuration file to load.

Return Values

An authenticator will report a list of facts and exit 0 if authentication succeeds. If this module cannot handle the given credentials, the module will exit 111 (temporary failure). If the credentials are accepted by this module, but are not valid, the module will exit 100 (permanent failure). Any other exit code is undefined.

Output

If authentication succeeds, the output from the module is a list of facts about the authenticator. Each fact consists of a single byte identifying what type of fact is being reported, followed by a sequence of zero or more non-zero bytes, terminated by a single NUL byte. A second NUL byte follows the last fact and indicates the end of the list. The total size of the output must not exceed 4096 bytes.

All predefined facts use values less than 128. All other fact values are reserved for local or experimental use. Facts marked as "required" must be present at least once in the result. Facts marked as "multiple" may be present more than once; all other facts must be present at most once. Facts may be reported in any order, and that order carries no significance. All numbers are expressed in ASCII decimal.

Code Considerations

The module must exit 111 if it detects malformed input (too few credentials, etc.). Extra input is a fault in the invoking code, and may produce undefined results.

The invoker must assume a temporary error if the module either fails to completely read its input or produces incomplete output, even if the module exits without error.

All error codes other than 100 should be treated as temporary errors.

If multiple authentication types are part of the scheme (ex POP3 allows plain and APOP logins, and requires seperate modules for each), the invoking code should be configured to execute a list of modules until one either succeeds or exits 100.

The invoking code should change directory to the named home directory and drop root priviledges as soon as possible after successful authentication. Where reasonable, the invoking code should also chroot to the directory for added protection.

Design Rationale

Single credential type
Eliminating the choice of credential types from the logic required by the modules simplifies the support code required by those modules. In fact, it should also simplify the code required by the invoking code, since the invoker will necessarily have different handling for reading and parsing different credential types from a client. Servers that only handle one type of credentials do not have to deal with this detail.
Variable fact format
There is no one list of facts that must be reported by such an authenticator. This list that is reported may be extended to include more optional facts.
Single-byte fact identifiers
Simplifying identifier names into a single byte greatly simplifies parsing code, without making the output code any more complex, and without significantly reducing the range of facts that can be expressed.
No chaining
Chaining of authentication modules is the responsibility of the invoking code rather than that of the authentication module. This eliminates most of the coding headaches associated with chaining Courier IMAP authentication modules, without adding significant additional code to the invoker.
No execution by the module
With the checkpassword interface, the authentication module drops root priviledges before executing the unpriviledged code. This however greatly reduces or eliminates the feasability of executing the unpriviledged module in a chroot environment for additional security.
Limited input and output size
Limiting the total input and output and output size to reasonable values eliminates one class of denial of service attacks by limiting the amount of memory required for buffers and parsing on both the part of the module and the invoker.

Compatibility

This interface is not directly compatible with either checkpassword or Courier auth modules. Direct compatibility was not a design consideration.

Checkpassword

It should be fairly simple to write a checkpassword module that can invoke a chain of authentictor modules and execute a command line upon success, thus providing compatibility with programs that require a checkpassword interface.

It should be possible, but not necessarily straightforward or efficient to provide an authenticator module that executes a checkpassword module and provides results from it.

Courier "authlib" modules

It should be fairly simple to write a auth module that can invoke an authenticator.

I am unsure about the feasability of writing a authenticator module that can invoke auth modules.