[Exim] New Exim4 SRS implementation

Top Page
Delete this message
Reply to this message
Author: David Woodhouse
Date:  
To: exim-users
CC: Martin Treusch von Buttlar, Daniel Roethlisberger
Subject: [Exim] New Exim4 SRS implementation
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