In case anyone cares... I switched to the format used by others with
significantly shorter localparts, moving some of the optional diagnostic
information into the domain part.
I also made the hash case-insensitive and made it handle sender
verification, although the latter is _ugly_ due to what I think is an
Exim bug (qv.)
This is the same as I posted to the SPF list (accidentally; I meant to
send to the SRS list) earlier today. I didn't want to cross-post.
You add a file CONFDIR/always-srs-senders for those senders who should
_never_ send mail with their 'raw' address -- such that you can also
reject any attempts to send _bounces_ to their raw address, and hence
sender verification callouts effectively protect those raw addresses
from being used as a faked $sender_address. (Basically doing the same
job which SPF was supposed to do but without requiring the whole world
to 'upgrade' to SMTP v2 to support it.)
You also add a file CONFDIR/spf-afflicted-domains listing those domains
which suffer from checking SPF but to which you absolutely _must_ be
able to forward mail, and hence for which you need to mangle
$sender_address. It's a single key lookup on the target domain which
returns 'all' to rewrite _all_ mail going that way, or 'spf' to rewrite
only mail where $sender_address_domain has SPF records.
--------- In the config somewhere ---------
# Define this to handle SRS-bounces
SRS_SECRET=somesecret
# And this if you want to (probably temporarily) accept two keys.
#SRS_OLD_SECRET=
domainlist rpr_domains = *.srs.mydomain.com
SRS_HASH_LENGTH=20
SRS_DSN_TIMEOUT=7
SRS_URL=
http://spf.pobox.com/srs.html
# Define this to enable SRS on forwarding.
SRS_DOMAIN=srs.mydomain.com
--------- In the routers immediately before 'lookuphost' ---------
.ifdef SRS_SECRET
# Urgh. Isn't there a better way to detect that we're in sender verification?
rpr_mark_sender_verify:
verify_only
verify_recipient = false
driver = redirect
data = ${quote_local_part:$local_part}@$domain
address_data = verifying sender
redirect_router = rpr_bounce
# Verify, and extract return address from, an SRS-address.
# Don't allow non-bounces, except from postmaster@* since some people use
# that for sender-verification callbacks.
rpr_bounce:
caseful_local_part
driver = redirect
domains = +rpr_domains
allow_fail
data = ${if !match {$local_part}{\N^[sS][rR][sS]0\+([^+]+)\+([0-9]+)\+([^+]+)\+(.*)\N} \
{:fail: Invalid SRS bounce \
.ifdef SRS_DEBUG
(malformed)\
.endif
} \
{${if and {{!eq {$1}{${length_SRS_HASH_LENGTH:${hmac{md5}{SRS_SECRET}{${lc:$2+$3+$4@$domain}}}}}} \
.ifdef SRS_OLD_SECRET
{!eq {$1}{${length_SRS_HASH_LENGTH:${hmac{md5}{SRS_OLD_SECRET}{${lc:$2+$3+$4@$domain}}}}}} \
.endif
} \
{:fail: Invalid SRS bounce \
.ifdef SRS_DEBUG
(HMAC should be ${length_SRS_HASH_LENGTH:${hmac{md5}{SRS_SECRET}{${lc:$2+$3+$4@$domain}}}} not $1)\
.endif
} \
{${if <{$2}{${eval:$tod_epoch/86400-12288-SRS_DSN_TIMEOUT}} \
{:fail: Invalid SRS bounce \
.ifdef SRS_DEBUG
(expired ${eval:$tod_epoch/86400-12288-SRS_DSN_TIMEOUT-$2} days ago)\
.endif
} \
{${if >{$2}{${eval:$tod_epoch/86400-12288}} \
{:fail: Invalid SRS bounce \
.ifdef SRS_DEBUG
(timestamp in future)\
.endif
} \
{${if and { {!eq {$sender_address}{}} \
{!eqi {$sender_address_local_part}{postmaster}}\
{!eq {$address_data}{verifying sender}}\
} \
{:fail: Invalid SRS bounce \
.ifdef SRS_DEBUG
(Not DSN: $sender_address instead)\
.endif
}\
# Wheee. At last the actual rewrite part...
{${quote_local_part:$4}@$3}\
}}}}}}}}}
headers_add = X-SRS-Return: DSN routed via $primary_hostname. See SRS_URL
# Rewrite reverse-path so that forwarding to known SPF-afflicted
# servers doesn't break. We generate a limited-lifetime hash cookie,
# from which we can later recreate the original sender address. We
# include the hostname and more precise timestamp in the domain of the
# generated address, so that we can track down the offending message
# in the log if it _does_ offend us.
.ifdef SRS_DOMAIN
rpr_outgoing_goto:
driver = redirect
# Don't rewrite unless the recipient is in a domain we _know_ to be broken
# but for local reasons have decided we need to work around.
# The text file listing broken recipient domains should look something like:
# gmx.net: all
# gmx.de: all
# aol.com: spf
# This lookup will leave the result of the lookup in $domain_data.
domains = lsearch;CONFDIR/spf-afflicted-domains
# Don't rewrite if it's a bounce, or from one of our own addresses.
senders = ! : ! *@+local_domains : ! *@+virtual_domains
# We expect either 'all' or 'spf' in $domain_data from the textfile lookup
# If the reason for the breakage is listed as 'SPF', then don't rewrite
# unless the sender's domain actually advertises SPF records.
condition = ${if or { {!eq{$domain_data}{spf}} \
{match {${lookup dnsdb{txt=$sender_address_domain}{$value}fail}}{v=spf1}} \
} {1}}
# We want to rewrite. We just jump to the rpr_rewrite router which is itself unconditional.
data = ${quote_local_part:$local_part}@$domain
redirect_router = rpr_rewrite
# Some addresses are joe-job protected by _always_ using SRS, and never actually
# sending mail from that address. That way, we can always reject bounces to these
# addresses, and prevent joe-jobs from being received by anyone who actually bothers
# to do sender verification callouts.
rpr_always_else:
driver = redirect
senders = !@@lsearch;CONFDIR/always-srs-senders
data = ${quote_local_part:$local_part}@$domain
redirect_router = lookuphost
# This is now unconditional. Either rpr_outgoing_goto jumped here
# because it's a mail we're forwarding to a known broken server, or
# rpr_always_else _didn't_ jump over us because it's from a sender
# listed in always-srs-senders.
rpr_rewrite:
headers_add = "X-SRS-Rewrite: SMTP reverse-path rewritten from <$sender_address> by $primary_hostname\n\tSee SRS_URL"
# Encode sender address, hash and timestamp according to http://www.anarres.org/projects/srs/
# We try to keep the generated localpart small. We add our own tracking info to the domain part.
address_data = ${eval:($tod_epoch/86400)-12288}+\
${sender_address_domain}+$sender_address_local_part\
@${sg {$primary_hostname}{^([^.]*)\..*}{\$1}}-\
${sg {$tod_log}{^.* ([0-9]+):([0-9]+):([0-9+])}{\$1\$2\$3}}.\
SRS_DOMAIN
errors_to = ${quote_local_part:SRS0+${length_SRS_HASH_LENGTH:${hmac{md5}{SRS_SECRET}{${lc:$address_data}}}}+\
${sg{$address_data}{(^.*)@[^@]*}{\$1}}}@\
${sg{$address_data}{^.*@([^@]*)}{\$1}}
driver = redirect
data = ${quote_local_part:$local_part}@$domain
# Straight to output; don't start routing again from the beginning.
redirect_router = lookuphost
no_verify
.endif // SRS_DOMAIN
.endif // SRS_SECRET
--
dwmw2