Start
Receive with rspamd
The rspamd module checks the address of every inbound IPv6 connection against Mode 1 DNS records, reports the result as a symbol, and adds an Authentication-Results field. Every symbol scores zero until you decide otherwise, and it never rejects mail.
Output on this page is real. It was captured from rspamd 4.2.2 running the module behind Postfix 3.10.13, with a test resolver serving records planted for documentation addresses. Those addresses have no records in public DNS, so on your own server they come back none. Long header lines are folded to fit.
This page assumes rspamd already filters your mail as a Postfix milter, through its proxy worker. If you don’t run rspamd and want only the check, the Postfix milter does it with no configuration file.
1. Install
$ git clone https://github.com/swornmail/rspamd-swornmail
$ cd rspamd-swornmail
$ install -d /etc/rspamd/plugins.d /etc/rspamd/lua/sworn
$ install -m 0644 swornmail.lua /etc/rspamd/plugins.d/swornmail.lua
$ install -m 0644 lua/sworn/*.lua /etc/rspamd/lua/sworn/Needs rspamd 3.x or later. Source: github.com/swornmail/rspamd-swornmail.
2. Turn it on
# /etc/rspamd/rspamd.conf.local
swornmail {
enabled = true;
lib_path = "/etc/rspamd/lua";
}It goes in rspamd.conf.local, not local.d/swornmail.conf. rspamd reads local.d files only for modules that ship their own defaults, which a third-party module doesn’t, and it disables a Lua module with no config section.
| Setting | Default | What it does |
|---|---|---|
authserv_id | rspamd’s host name | The id stamped on each sworn result. If rspamd runs on a different machine from Postfix, set it to Postfix’s name: rspamd’s own field takes the MTA’s name, and the two results would otherwise carry different ids. |
use_mta_hostname | true | Use the client host name Postfix has already confirmed instead of looking it up again. Set it to false if rspamd gets mail from something whose reverse DNS check you don’t trust. |
ineligible_prefixes | [] | IPv6 prefixes never matched to an operator. If rspamd sees IPv4 clients through NAT64 or SIIT, list the translation prefix: every translated client appears inside it. A malformed entry disables the module, with an error in the log. |
lib_path | /etc/rspamd/lua | Where lua/sworn/ was installed. |
3. Remove forged results
Anyone can write Authentication-Results: mx.example.com; sworn=pass into a message, so a forged field claiming your id must be removed before anything downstream reads it (see the trust boundary). The module adds its result but removes nothing. That job belongs to rspamd’s milter_headers module, and rspamd’s stock config doesn’t do it.
# /etc/rspamd/local.d/milter_headers.conf
use = ["authentication-results"];
authenticated_headers = ["authentication-results"];
local_headers = ["authentication-results"];
routines {
authentication-results {
add_smtp_user = false;
}
}The same settings as conf/milter_headers.conf in the repository, which explains each one. If you already have this file, merge them into it.
| Setting | Why |
|---|---|
use | Turns on the routine that deletes every inbound Authentication-Results field, then adds rspamd’s own SPF, DKIM and DMARC result. |
authenticated_headers | The routine skips authenticated senders unless told otherwise. Without this, anyone with a stolen password can send your users a forged sworn=pass. |
local_headers | It also skips private and loopback addresses. Behind a proxy or NAT, that’s every connection. |
add_smtp_user = false | Covering authenticated senders also puts rspamd’s field on the mail they send out, and by default that field names their login. This leaves it out. |
Every inbound result goes, not only forged ones. Unlike the Postfix milter, which removes only fields that could claim your id, this deletes other domains’ results too, and any written by an MTA of yours in front of rspamd. For trust, that is the safe direction. If something downstream relies on those results, it won’t see them any more.
While rspamd is down, nothing removes them. If Postfix keeps accepting mail when rspamd can’t be reached (default_action=accept on its milter entry), messages arrive with no sworn result and with any forged field claiming your id left in place. A downstream filter that trusts that id sees whatever the sender wrote until rspamd is back.
Don’t narrow the deletion with remove_ar_from. It recognises a field only by a plain name at its start, so a forged (c) mx.example.com; sworn=pass, or one with the id in quotes, gets through.
4. Restart and check
After a restart, the rspamd log should have:
swornmail: Mode-1 attestation enabled, authserv-id mx.example.comand nothing starting swornmail: forged sworn= results can reach mailboxes. The module reads the milter_headers config at startup and logs one of those for each setting that lets a forged result through. On rspamd’s stock config, without step 3:
swornmail: forged sworn= results can reach mailboxes: milter_headers doesn't list authentication-results in use, so forged inbound results are keptThe check reads the config file only. Per-message overrides from rspamd’s settings module can switch the routine off without it noticing.
Scan a test message as if it came from a given address:
$ printf 'From: [email protected]\nSubject: t\n\nhi\n' |
rspamc --ip=2001:db8:f00:1234::25 symbols | grep SWORN
Symbol: SWORN_PASS (0.00)[mailer.example.com:2001:db8:f00:1234::/64]Through Postfix, a message from 2001:db8:f00:1234::25 arrives carrying a forged result under the receiver’s id and a genuine one from another domain:
Authentication-Results: mx.example.com; sworn=pass policy.op=victim.example
Authentication-Results: other.example; spf=pass smtp.mailfrom=sender.exampleIn the mailbox:
Authentication-Results: mx.example.com;
dkim=none;
dmarc=none;
spf=none (mx.example.com: domain of [email protected] has no SPF policy
when checking 2001:db8:f00:1234::25) [email protected]
Authentication-Results: mx.example.com; sworn=pass policy.mode=dns
policy.op=mailer.example.com policy.unit="2001:db8:f00:1234::/64"
policy.observed="2001:db8:f00:1234::/64"Both inbound fields are gone, and rspamd’s own result sits above the sworn one. A message from a private address went the same way: the forged field was removed, and it got no sworn result, because it came over IPv4.
To check authenticated senders, ask rspamd directly. A zero for Authentication-Results under remove_headers means every inbound field of that name is deleted, and the field rspamd adds doesn’t name the login:
$ printf 'Authentication-Results: mx.example.com; sworn=pass\nFrom: [email protected]\n\nhi\n' |
rspamc --json --ip=2001:db8::1 --user=alice [email protected] |
grep -A11 '"milter"' "milter": {
"add_headers": {
"Authentication-Results": {
"order": 1,
"value": "mx.example.com;\n\tauth=pass [email protected]"
}
},
"remove_headers": {
"X-Spam": 0,
"Authentication-Results": 0
}
},What the results mean
| Symbol | Result | Meaning |
|---|---|---|
SWORN_PASS | sworn=pass | The address is inside a prefix the operator publishes, and the operator has accepted accountability for it. |
SWORN_TESTING | sworn=none policy.testing=y policy.wouldbe=pass | Same, but the operator is still in observe-only mode. Stake nothing on it, for credit or blame. |
SWORN_NONE | sworn=none | No operator confirms this address. |
SWORN_TEMPERROR | sworn=temperror | DNS failed, ran out of time, or the lookup limit was reached. Treat it like none, never as worse. |
An IPv4 connection gets no symbol and no sworn result at all: Mode 1 attests IPv6 space only. A pass names the operator and both prefixes, as in the mailbox example above. Key reputation on policy.op and policy.observed, the /64 this connection actually came from. policy.unit is what the operator asked for, which is a claim. The full rules are in reputation semantics.
Authenticated senders, and senders on private or loopback IPv6 addresses, still get a symbol but no sworn field: that mail isn’t arriving from another domain’s network.
Scores
All four symbols score 0.0, so installing the module changes no verdict. Leave it that way unless you have a reason. A pass says who is accountable for the address, not that the mail is any good, so it earns nothing on its own. Use it as the key for reputation you build from the mail itself. Never score SWORN_NONE or SWORN_TEMPERROR as worse than no result: missing or failed attestation must not make treatment worse than it is for IPv6 mail today.
Where it has to run
On the border MTA, the one that accepts connections from the Internet. The module judges the address Postfix hands rspamd, so it must be the sender’s. Behind a load balancer or proxy, have Postfix take the real client address from it (for example smtpd_upstream_proxy_protocol = haproxy). Otherwise every connection is judged as the balancer’s address, and if that address is private, rspamd treats every message as local. Only do this on a listener that nothing but the proxy can reach: anyone else who can connect to it can claim any address, including one inside an attested prefix, and get a pass.
DNS
The module uses rspamd’s resolver and makes at most ten lookups per message. Point rspamd at a validating resolver if you can: the module doesn’t check DNSSEC itself, an unsigned answer can be spoofed, and DNS is the root of trust (see trust in DNS). A lookup that times out or fails outright, or reaching the limit, gives temperror, never none, so a sender’s attestation can’t be erased by breaking their DNS. A record that simply isn’t there gives none.
What it doesn’t do
It doesn’t change what happens to mail while rspamd is down. That is whatever your existing Postfix entry for rspamd’s milter says. It also can’t verify Mode 2 tokens: rspamd sees the SMTP conversation through the milter protocol and can’t extend it (see implementations).