#spf #milter #dns #send-email #domain-name #spam

bin+lib spf-milter

Milter for SPF verification

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

GPL-3.0-or-later

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 socket
  • unix: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