19 releases
0.7.0-alpha.1 | Jun 13, 2024 |
---|---|
0.6.0 | Sep 20, 2023 |
0.5.0 | Feb 7, 2023 |
0.4.3 | Nov 16, 2022 |
0.1.0 | Nov 28, 2020 |
#74 in Email
220KB
5K
SLoC
SPF Milter
📖 Deutsche Version hier. (German version here.)
SPF Milter is a milter application that verifies email senders using the Sender Policy Framework 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. SPF verification strictly proceeds in the recommended
manner: The first, optional step is to verify a client’s HELO identity (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 (the reverse-path or
envelope sender given with the SMTP MAIL FROM
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 flexible configuration options. Configuration parameters 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.
Installation
SPF Milter is a Rust project. It can be built and/or installed using Cargo as usual. For example, use the following command to install the latest version published on crates.io:
cargo install --locked spf-milter
As discussed in the following sections, the default, compiled-in configuration
file path is /etc/spf-milter.conf
. When building SPF Milter, this default path
can be overridden by setting the environment variable SPF_MILTER_CONFIG_FILE
to the desired path.
Once built, installation can be achieved by copying spf-milter
to /usr/sbin
;
spf-milter.service
to /etc/systemd/system
; and spf-milter.conf
to /etc
.
After reviewing the configuration, start the service by executing systemctl enable --now spf-milter
.
Installation of manual pages can be done in the same way by simply copying:
cp spf-milter.8 /usr/local/share/man/man8/
cp spf-milter.conf.5 /usr/local/share/man/man5/
mandb
The minimum supported Rust version is 1.74.0.
Usage
Once installed, SPF Milter can be invoked on the command-line as spf-milter
.
SPF Milter reads configuration parameters from the default configuration file
/etc/spf-milter.conf
. At a minimum, the mandatory socket
parameter must be
set in that file. See the included spf-milter.conf
for a sample
configuration.
Invoking spf-milter
starts the milter in the foreground. 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. Modify this file to suit your needs (for
example, by setting User
, Group
, and UMask
), install it in
/etc/systemd/system
, then start and enable the service.
SPF Milter logs status messages to syslog. By default, in addition to errors and warnings the verification result for each verified identity is written to the log.
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 manual page spf-milter.conf(5) serves as the reference
documentation. (You can view the manual page without installing by passing the
file’s path to man
: man ./spf-milter.conf.5
)
Configuration can be reloaded from disk during operation by sending the signal
SIGHUP
to the milter process. Refer to the manual page for details.
For those new to SPF Milter, the following section has an introductory guide that presents in all brevity the core configuration parameters.
Getting started
Let’s take two minutes to run through an initial setup of SPF Milter.
The first step is picking and configuring the listening socket of the milter for
the connection from the MTA. This purpose is served by the socket
parameter. This parameter’s value should be a socket specification in one of two
forms:
inet:host:port
for a TCP socketunix:path
for a UNIX domain socket
And here is how you set it in the configuration file:
# A TCP socket listening on port 3000:
socket = inet:localhost:3000
Recall that configuration parameters go in the file /etc/spf-milter.conf
,
using the syntax shown here and documented in the manual page. And of course,
don’t forget to integrate the milter with the MTA. For example, with Postfix
add the socket location to the milters in /etc/postfix/main.cf
:
smtpd_milters = inet:localhost:3000
With this set up, the milter started and the Postfix configuration reloaded, SPF Milter will begin processing messages as they arrive.
Various aspects of SPF Milter operation can be adjusted. The main configurable
facet of the verification procedure is whether to also verify the HELO identity:
this is controlled with the Boolean parameter verify_helo
:
# Also verify HELO before MAIL FROM:
verify_helo = yes
Enabling HELO verification does not mean disabling MAIL FROM verification. Rather, the HELO identity is verified in addition before the MAIL FROM identity. Provided that the HELO identity is of interest at all, leaving this enabled may be advantageous, because, as noted in section 2.3 of RFC 7208, processing of the HELO identity is usually simpler than the more complex MAIL FROM identity, often using a less complex SPF policy and therefore less DNS resources.
Sender identities that evaluate to a negative authorisation result or an error
result may be rejected at the SMTP level, by having the milter respond with a
transient or permanent SMTP error reply. The set of SPF results to reject is
declared with the reject_results
parameter:
# Reject senders that evaluate to one of the following results:
reject_results = fail, temperror, permerror
The value lists the SPF results, separated by commas. Above, messages from a sender with SPF result fail, for example, are rejected, while those with result softfail would be accepted.
Messages from accepted senders, instead of being rejected, have the result
recorded in a new entry added to the message header. The type of header field
can be configured with the header
parameter. The values Received-SPF
and
Authentication-Results
each select the header field of the same name. For
example:
# Add a ‘Received-SPF’ header to accepted messages:
header = Received-SPF
And with this we leave you to experiment on your own.
While experimenting with the configuration you do not need to restart SPF Milter, just send it the reload signal. An explanation of this and of many more settings can be found in the manual page, spf-milter.conf(5).
Use cases
To make the above information more practical, this section discusses in more detail 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:localhost:3000
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 evaluates to a result that is neither in the set of results to be
rejected, nor in the set of ‘definitive’ HELO results, next the MAIL FROM
identity is verified. The above two settings, reject_helo_results
and
definitive_helo_results
, allow tuning precisely which HELO results may
shortcut or bypass MAIL FROM identity verification. By default, the same set of
results is rejected for both the HELO and MAIL FROM identity, and no HELO result
is ‘definitive’.
For either the HELO or MAIL FROM identity a final outcome results. This is then
enforced in line with the suggestions in RFC 7208. The results fail,
temperror, and permerror are rejected with an appropriate permanent or
transient SMTP error reply, and for all other results a header field recording
the result is added to the message header. The set of results to reject can be
adjusted freely with parameters reject_results
and reject_helo_results
. The
SMTP reply code and text can also be configured for each SPF result
individually.
For the header entry, by default a header field of type Received-SPF is
generated. The parameter for configuring the header field type is named header
(header = Received-SPF
). Both header types of RFC 7208 are ‘standard’, but
they serve different purposes: Received-SPF provides full information about
input parameters and additional information about the result, whereas
Authentication-Results only conveys the result itself. Here as elsewhere, SPF
Milter aims for perfect compliance with specifications, especially RFC 7208 and
8601, but also those referenced therein. When in doubt, refer to the RFCs.
Variants
Treat softfail
as a failing result. A very strict setup may wish to treat
softfail
with the same severity as a fail
result, and reject it. To
implement this, simply add softfail
to the results to be rejected.
/etc/spf-milter.conf
:
reject_results = fail, softfail, temperror, permerror
More information in the header. SPF Milter supports both of the specified
header fields, and one might want to have them both added to messages.
Configuration using the header
parameter is straightforward. In addition,
enabling include_all_results
causes results for all verified identities to be
recorded (both HELO and MAIL FROM).
/etc/spf-milter.conf
:
header = Received-SPF, Authentication-Results
include_all_results = yes
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. Since DMARC will use an SPF result as an input for its own validation procedure, a few adjustments to the default configuration are necessary.
/etc/spf-milter.conf
:
socket = inet:localhost:3000
verify_helo = no
reject_results =
header = Authentication-Results
First, in DMARC only the MAIL FROM identity is of interest; the HELO identity is
not considered. Therefore, this step should be skipped by disabling
verify_helo
.
Second, with the SPF result being an input to DMARC, one might not want to
reject senders after SPF verification, but instead delegate such an action to a
subsequent DMARC component (though depending on requirements other approaches
may make sense). In the above configuration rejection is disabled with the
setting reject_results =
(the empty set). This instructs the milter to record
the result in the header and not to return an SMTP error reply.
Finally, the parameter header
changes the header field from the default
Received-SPF
to Authentication-Results
, since this is the type of header
field that DMARC relies on. The Authentication-Results header is a
general-purpose device for conveying the authentication status for later machine
processing. It was specified more recently in RFC 8601.
Variants
Reject failing HELO identity early. With a bit of imagination one may still
make use of the HELO identity when SPF is set up for DMARC: A sender with a
failing HELO identity is probably up to no good and may be rejected early. This
requirement can be implemented by including fail
in the results to be
rejected, but only for the HELO identity. In all other aspects this
configuration behaves like the setup above.
/etc/spf-milter.conf
:
verify_helo = yes
reject_helo_results = fail
reject_results =
Contributing
Contributions of any kind are welcome. Open a ticket on the issue tracker for questions, suggestions, bug reports, feature requests, documentation, translations etc. In the interest of long-term viability of this project I can also grant you commit access.
Before implementing a new feature, please discuss it first on the issue tracker. Implementation is often the easy part, designing and motivating a feature is where we find we spend the most time. Compliance with and robust implementation of RFCs is one of the main accomplishments of SPF Milter vis-à-vis similar software; please do consult the RFCs.
This project is developed as free software under a GPL licence. Please respect this choice of licence.
Licence
Copyright © 2020–2024 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.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
Dependencies
~10–19MB
~272K SLoC