# SPF Milter 📖 *Deutsche Version [hier][README.de-CH]. (German version [here][README.de-CH].)* 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. [README.de-CH]: https://codeberg.org/glts/spf-milter/src/tag/0.7.0-alpha.1/README.de-CH.md [RFC 7208]: https://www.rfc-editor.org/rfc/rfc7208 [viaspf]: https://crates.io/crates/viaspf [domain]: https://crates.io/crates/domain ## 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. [Rust]: https://www.rust-lang.org [crates.io]: https://crates.io/crates/spf-milter ## 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. [`spf-milter.conf`]: https://codeberg.org/glts/spf-milter/src/tag/0.7.0-alpha.1/spf-milter.conf [`spf-milter.service`]: https://codeberg.org/glts/spf-milter/src/tag/0.7.0-alpha.1/spf-milter.service ## 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. [*spf-milter.conf*(5)]: https://codeberg.org/glts/spf-milter/src/tag/0.7.0-alpha.1/spf-milter.conf.5 ### 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). [Postfix]: https://www.postfix.org ## 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]. [RFC 7489]: https://www.rfc-editor.org/rfc/rfc7489 [RFC 8601]: https://www.rfc-editor.org/rfc/rfc8601 #### 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 .