SPF Milter
This software is in the initial development stage. Feedback from early adopters is welcome, but note that this is under construction and actively being worked on. Production use is not yet recommended.
SPF Milter is a milter application that verifies email senders using the Sender Policy Framework (SPF) protocol. It can be integrated with milter-capable MTAs to check and enforce authorisation published as SPF policy in the DNS.
SPF Milter closely adheres to the rules and recommendations of the SPF
specification, RFC 7208. By design, SPF verification proceeds in the
recommended manner: The first, optional step is to verify a client’s HELO
identity (ie, the domain name given with the SMTP HELO
command). If this step
is not done or is not conclusive, then the client’s MAIL FROM identity (ie,
the reverse-path or envelope sender given with the SMTP MAIL
command) is
verified. In either case, verification produces a final SPF result. The milter
may then take action, either by rejecting the message with an SMTP error reply,
or by recording the result in the message header.
Within the constraints of the specification, SPF Milter exposes highly flexible configuration options. Configuration toggles and settings for the verification procedure, result handling, SMTP reply, header fields, and more, support a broad range of use cases. SPF Milter is capable of in-flight configuration reloading, so that no restart is necessary for configuration changes.
In terms of implementation, SPF Milter is essentially a configuration interface to an SPF verifier integrated in the milter protocol. The SPF implementation is provided by the viaspf library, and the DNS implementation is provided by the domain library. We believe these to be a solid foundation for SPF milter software.
Installation
(not yet published)
SPF Milter is a Rust project. It can be built and/or installed using Cargo in the usual way. For example, use the following command to install the latest version published on crates.io:
cargo install --locked spf-milter
As a milter, SPF Milter requires the libmilter C library to be available. For
example, on Debian and Ubuntu one needs to install the libmilter-dev
package.
If your distribution does not provide pkg-config metadata for libmilter, try
using the provided milter.pc
file: Run any Cargo command with the directory
containing milter.pc
added to the pkg-config path:
PKG_CONFIG_PATH=/path/to/dir cargo build
The domain library requires the OpenSSL library and development files. On Debian
and Ubuntu, install the package libssl-dev
.
The minimum supported Rust version is 1.45.0.
Usage
Once installed, SPF Milter can be invoked on the command-line as spf-milter
.
This program reads configuration parameters from the default configuration file
/etc/spf-milter.conf
. At a minimum, the mandatory socket
parameter must be
specified in that file. See the included spf-milter.conf
for a sample
configuration.
Running spf-milter
causes SPF Milter to read the configuration and start the
milter. Send a termination signal to the process or press Control-C to shut the
milter down.
To set up SPF Milter as a system service, try using the provided
spf-milter.service
systemd service. Install this file in
/etc/systemd/system
, then start and enable the service.
SPF Milter logs status messages to syslog. By default, warnings and errors are logged, as well as the verification result for each verified identity.
Configuration
SPF Milter is configured primarily by setting configuration parameters in the
configuration file /etc/spf-milter.conf
. Parameters come with sensible default
settings, and SPF Milter can be used right away by specifying just the mandatory
socket
parameter.
The included man page spf-milter.conf(5) currently serves as the reference
documentation. (Tip: You can view the man page without installing by giving the
path: man ./spf-milter.conf.5
)
Configuration can be reloaded during operation by sending the signal SIGUSR1 to the milter process. Refer to the man page for details.
Before integrating SPF Milter in your mail server setup, consult at least the documentation for the parameters presented in brief below.
Socket
The mandatory socket
parameter controls where SPF Milter opens a listening
socket for the connection from the MTA. This parameter accepts a socket
specification of the form inet:port@host
for a TCP socket, or unix:path
for
a UNIX domain socket. The socket specification is passed to the milter library
as-is.
Verification procedure
The main configurable facet of the verification procedure is whether the HELO
identity is verified or not: this is controlled with the boolean parameter
verify_helo
. Thus there are two possible verification procedures:
- MAIL FROM only: verify MAIL FROM identity and treat result as final outcome
- HELO and MAIL FROM: verify HELO identity; if the result is definitive, treat it as the final outcome; if it is not definitive then verify MAIL FROM identity and treat that result as final outcome
Which HELO identity results are definitive can be controlled with
definitive_helo_results
.
Negative authorisation results and error results may be rejected at the SMTP
level. The selection of results can be controlled with reject_results
.
SMTP reply
When a definitive result has been reached, and the result is one to be rejected then it will be rejected with a transient or permanent SMTP error reply.
The SMTP reply code and reply text, and the enhanced status code can be
specified for each SPF result. For example, for the fail result, use
parameters fail_reply_code
, fail_status_code
, and
fail_reply_text
.
Header
The type of header field to be added can be configured using the header
parameter. The values Received-SPF
and Authentication-Results
each select
the header field of the same name. The default Received-SPF
header records SPF
verification parameters in full detail, whereas the Authentication-Results
header only records the result and sender domain. Compare:
Received-SPF: pass (mail.gluet.ch: domain of
users-return-122976-dbuergin=gluet.ch@spamassassin.apache.org has authorized
host 95.216.194.37) receiver=mail.gluet.ch; client-ip=95.216.194.37;
helo=mxout1-he-de.apache.org;
envelope-from="users-return-122976-dbuergin=gluet.ch@spamassassin.apache.org";
identity=mailfrom; mechanism="include:_spf.apache.org"
Authentication-Results: mail.gluet.ch; spf=pass
smtp.mailfrom=spamassassin.apache.org
Use cases
To make the above information more practical, this section presents two common example use cases: ‘Standard’ SPF, and SPF as part of DMARC.
Standard SPF
The standard, RFC-compliant SPF verification use case is well covered by SPF
Milter’s default configuration settings. Therefore, just setting the mandatory
socket
parameter and leaving all other parameters at their default is enough
to configure a standard SPF verification use case.
/etc/spf-milter.conf
:
socket = inet:3000@localhost
Let us do a brief walkthrough of the default behaviour. SPF Milter will first
verify the HELO identity (verify_helo = yes
). If the HELO identity cannot be
evaluated to one of the definitive authorisation results – pass or fail –,
next the MAIL FROM identity is verified. Which HELO verification result is
treated as definitive is configurable through the parameter
definitive_helo_results
.
The final result from either identity will then be enforced as suggested in RFC
7208. That is, the results fail, temperror, and permerror will be rejected
with an appropriate permanent or transient SMTP error reply, and for all other
results a header line recording the result is added to the message. The
specifics of rejection at the SMTP level can be adjusted using the parameter
reject_results
and a number of parameters for each SPF result.
By default, a header field of type Received-SPF
is used. The parameter for
configuring the header field type is named header
.
Variants
Treat softfail
as a failing result. A very strict setup may treat
softfail
with the same severity as a fail
result. To implement this, the
softfail
result needs to be added to the definitive HELO results and also to
the results that get rejected.
/etc/spf-milter.conf
:
definitive_helo_results = pass, fail, softfail
reject_results = fail, softfail, temperror, permerror
Enable both header fields. SPF Milter supports both of the specified header fields, and one might want to have them both added to messages. The configuration is straightforward. Note that in the RFC there is cautionary advice that care must be taken to ensure that both headers convey the same result; of course, SPF Milter ensures that already.
/etc/spf-milter.conf
:
header = Received-SPF, Authentication-Results
SPF as part of DMARC
SPF may also be used as part of a DMARC verification setup. Domain-based Message Authentication, Reporting, and Conformance (DMARC) is specified in RFC 7489.
/etc/spf-milter.conf
:
socket = inet:3000@localhost
verify_helo = no
reject_results =
header = Authentication-Results
DMARC will use an SPF result as an input for its own validation procedure. As shown, a few adjustments are necessary for a minimal DMARC setup.
First, DMARC does not use HELO identity verification, therefore this step should
be turned off by disabling verify_helo
. Only the MAIL FROM identity is
considered.
Second, one might want to delegate authorisation to DMARC, and not do any
rejection during SPF verification. Though depending on requirements other
approaches may make sense. The above configuration disables rejection with
reject_results =
(the empty set), thus always letting the milter add a header
line to messages and ultimately delegate the authorisation decision to a
subsequent DMARC component.
The final parameter header
changes the header type from the default
Received-SPF
to Authentication-Results
, since this is the header field that
DMARC relies on.
Variants
Reject failing HELO identity early. When SPF is set up for DMARC, it is
still possible to enable HELO identity verification. In this variant, we set up
HELO identity verification, deferring all HELO results except the result fail
.
We then reject this result, but only for the HELO identity. The resulting
behaviour is just like above, with the difference that a HELO identity which
evaluates to fail
is rejected right away, before the usual
Authentication-Results
header field is added.
/etc/spf-milter.conf
:
verify_helo = yes
definitive_helo_results = fail
reject_helo_results = fail
reject_results =
Licence
Copyright © 2020 David Bürgin
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.