Comments only, no behaviour changed. Each main.cf/master.cf parameter the
build sets, the wrapper's and the reload hook's reliance on Postfix 3.7.11
(postfix-script, master.5, postconf.5, cleanup_init.c, smtpd_peer.c,
README_FILES), supervisor 4.2.5 (process.py, options.py, events.rst),
logrotate 3.21.0, OpenDKIM's config, Cyrus SASL's sasldb, rspamd 3.11.1
(milter_headers.lua, multimap, the map watcher), Docker Compose, CoreDNS
and certbot now say where that is written. Measurements already there stay
measurements ("date not recorded" where none was); hand-composed fixtures
say so.
Contradictions left for their own commits: C-P31 cleanup's size limit,
C-P42 master outside supervisor's signal, C-V5/V6 startsecs, C-V10,
C-S3/Q3 sasldb written in place, C-Q4/A1 certbot's symlinks, C-R7 rspamd
skips local clients but the signature routine does not, C-P8, C-R4, C-R12,
C-M7, C-E2, C-K8/K9, C-D1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.7 KiB
Plaintext
58 lines
2.7 KiB
Plaintext
# Mounted into the sidecar as /etc/rspamd/local.d/milter_headers.conf.
|
|
#
|
|
# rspamd writes its verdict into two headers of every message it lets through:
|
|
# the action it chose and the score with the symbols behind it. SelfPost's
|
|
# inbound journal reads them (Inbound > Log shows them next to the message),
|
|
# and the upstream mailbox server receives them with the mail.
|
|
#
|
|
# rspamd removes headers of these names that a sender wrote himself before it
|
|
# adds its own. Without this file the journal still lists accepted mail, with
|
|
# no verdict next to it.
|
|
#
|
|
# The third header is the proof that the other two are rspamd's. The filter is
|
|
# attached so that mail still flows when it is down, and then nothing removes
|
|
# what a sender wrote: he could send his own verdict. So rspamd signs each
|
|
# message - an HMAC-SHA256 of the message's queue id under a key SelfPost
|
|
# generates and leaves in the directory it shares with this container - and
|
|
# SelfPost reads a verdict only next to a signature it can recompute. The
|
|
# signature travels on with the mail and is of no use to whoever sees it: it
|
|
# fits that one queue id.
|
|
#
|
|
# rspamd 3.11.1, src/plugins/lua/milter_headers.lua: a custom routine is called
|
|
# as function(task, common_meta) and returns error, headers to add, headers to
|
|
# remove, metadata; a remove value of 0 removes every header of that name
|
|
# (docs.rspamd.com/modules/milter_headers, "Custom routines").
|
|
# src/lua/lua_cryptobox.c, rspamd_lua_hash_create(): "sha256" with a key is
|
|
# HMAC-SHA256 over EVP_sha256(), the key being the bytes given - here the hex
|
|
# text. task:get_queue_id() is the Queue-Id the proxy took from the milter
|
|
# macro "{i}" or "i" (src/libserver/milter.c, rspamd_milter_macro_http(); read
|
|
# back in src/libserver/protocol.c), which Postfix sends at
|
|
# end-of-message (Postfix 3.7.11, src/global/mail_params.h, DEF_MILT_EOD_MACROS
|
|
# "i").
|
|
use = ["x-spamd-result", "x-rspamd-action", "selfpost-signature"];
|
|
|
|
custom {
|
|
selfpost-signature = <<EOD
|
|
return function(task, common_meta)
|
|
local remove = { ['X-SelfPost-Filter'] = 0 }
|
|
-- The key is read for every message and kept nowhere: SelfPost may make a
|
|
-- new one (the file was lost, a backup was restored), and a key remembered
|
|
-- here would go on signing what SelfPost no longer accepts until someone
|
|
-- restarted this container. The file is one short line.
|
|
local key
|
|
local f = io.open('/etc/rspamd/selfpost/filter.key', 'r')
|
|
if f then
|
|
key = f:read('*l')
|
|
f:close()
|
|
end
|
|
local qid = task:get_queue_id()
|
|
if not key or key == '' or not qid or qid == '' then
|
|
return nil, {}, remove, {}
|
|
end
|
|
local hash = require "rspamd_cryptobox_hash"
|
|
local sig = hash.create_specific_keyed(key, 'sha256', qid):hex()
|
|
return nil, { ['X-SelfPost-Filter'] = sig }, remove, {}
|
|
end
|
|
EOD;
|
|
}
|