the djb way

qmail with attitude


mailfront plugin architecture

In the years since we last wrote about Bruce Guenter's mailfront package, the project has continued to advance. Since version 1.0, mailfront has been substantially rewritten to incorporate a modular plugin architecture. This redesign provides considerable flexibility and extensibility to the mailfront paradigm. (The basis of the following article is mailfront version 1.11, dated 2007-09-27.)

The plugin paradigm for mailfront begins with its invocation (shown here as the last item in a daemontools runscript exec chain):

exec [...] mailfront proto backend [plugin ...]

Where:

So, for example, to provide an smtpd service for a qmail backend with some set of plugins, one might invoke:

exec [...] mailfront smtp qmail plugin plugin plugin ...

Plugins may be specified as arguments on the command line as shown above, and/or they may be defined in the environmental variable named PLUGINS as a colon-delimited string like so:

PLUGIN=plugin:plugin:plugin

Any plugins defined in PLUGINS are loaded after any plugin arguments given on the command-line. As we will see later, order is significant when defining a set of plugins.

A plugin is a run-time loaded module that provides purpose-specific processing at each stage of the protocol dialog. A set of plugins can provide a sequence of ACCEPT/REJECT filters to do things like check addresses, authenticate senders, validate recipients, and constrain message content.

Mailfront ships with an smptfront-qmail command-script that provides a standard set of plugins. Using smtpfront-qmail in place of the mailfront smtp qmail ... sequence in a daemontools runscript looks like this:

exec [...] smtpfront-qmail

The smtpfront-qmail command script emulates previous releases of mailfront and provides backward compatibility with any existing runscripts you may have already installed. The following table summarizes the standard set of plugins as ordered in the standard loading sequence:

plugin test(s) actions if
test true
environment
check-fqdn empty/bad address? S --> REJECT
R --> REJECT
DEFAULTDOMAIN
DEFAULTHOST
counters r > MAXRCPTS
h > MAXHOPS
d > DATABYTES
R --> REJECT
D --> REJECT
MAXRCPTS
MAXHOPS
DATABYTES
mailrules many possible S --> REJECT
R --> REJECT
MAILRULES
relayclient sender AUTHorized?
sender RELAYCLIENT?
R --> ACCEPT RELAYCLIENT
cvm-validate lookup recipient
in cvm fails?
R --> REJECT CVM_LOOKUP
CVM_LOOKUP_SECRET
qmail-validate in qmail/control -->
S in badmailfrom?
R in badrcptto?
R not in rcpthosts?
S --> REJECT
R --> REJECT
QMAILHOME
add-received modify message headers FIXUP_*
patterns found match in DATA
with patterns?
D --> REJECT PATTERNS
PATTERNS_LINEMAX
PATTERNS_RESP
accept-sender (none) S --> ACCEPT (none)

In this table, the letters S, R, and D abbreviate Sender, Recipient and Data.

The processing rules for plugins are succinctly described in the mailfront plugin API documentation. I would only mess up if I tried to restate them here. But basically you can view the set of plugins as a chain of filters. At each stage of the smtp dialog (HELO, MAIL, RCPT, DATA, etc), mailfront runs through the chain for those tests that apply. A plugin can then respond with ACCEPT, REJECT, or no response.

Additionally, mailfront requires the sender address to be explicitly ACCEPTed (or REJECTed) at some point during the plugin sequence; any 'no response' for the sender address results in rejection. That is why we see the 'accept-sender' plugin at the end of the chain above. This causes mailfront to accept senders who have not otherwise been rejected by the preceeding filters.

As mentioned above, order of plugins is significant. If, for example, the 'accept-sender' plugin was loaded first in the chain, instead of last, mailfront would cause all sender addresses to be ACCEPTed, short-circuiting all the other sender plugin tests, and you would have an open relay. This is known as: not what you want.

Mailfront ships with a selection of other plugins in addition to those summarized in the table above. The table below lists them in simple alphabetical order:

plugin test(s) actions if
test true
environment
accept (none) S --> ACCEPT
R --> ACCEPT
(none)
accept-recipient (none) R --> ACCEPT (none)
clamav virus detected? M --> REJECT CLAMAV_*
force-file (none) (none; saves message
to temp file)
?
reject (none) S --> REJECT
R --> REJECT
(none)
require-auth sender not AUTHorized?
sender not RELAYCLIENT?
S --> REJECT RELAYCLIENT

In this table, the letters S, R, and M abbreviate Sender, Recipient and Message. (Compared to the first table, M represents the entire received message, whereas D represents the message as it is being received.)

With these additional plugins, we begin to see that many configurations of an smptd service are possible, to suit any number of requirements. To see some examples of these possibilities, read on for the following:


Copyright © 2003 - 2007, Wayne Marshall.
All rights reserved.

Last edit 2007.12.26, wcm.