Original RPR implementation

This is my original implementation, based heavily on Daniel Roethlisberger's solution.

The way we do this is by using a dedicated domain for these bounces and including the following information in the local part of the address:

These addresses take the form: rpr+<hash>+<hostname>+<ident>+<timestamp>+<original address>@rpr.infradead.org. When we get a bounce to one of these addresses, we check its hash is valid and the timestamp hasn't expired, then forward to the original address.

This is how it's done, in Exim 4. These two routers go immediately before the 'lookuphost' router which sends messages by remote SMTP. Note that you should make sure you accept 'postmaster@', but in my case that's done by an earlier router. You may uncomment the extra information in the failure cases, but make sure you don't actually let your server tell the outside world the correct hash when a hash fails. That's for debugging only.


RPR_DOMAIN = rpr.infradead.org
RPR_SECRET = xxxx
RPR_URL = http://www.infradead.org/rpr.html
RPR_DSN_TIMEOUT = 604800

  # Verify, and extract return address from, an RPR-address
rpr_bounce:
  caseful_local_part
  driver = redirect
  domains = RPR_DOMAIN
  allow_fail
  data = ${if !match {$local_part}{\N^rpr\+([^+]*)\+([^+]*)\+([^+]*)\+([0-9]+)\+(.*)\N} \
		{:fail: Unknown RPR localpart \
#			(malformed)\
		} \
 	{${if !eq {$1}{${hmac{md5}{RPR_SECRET}{$2+$3+$4+${quote_local_part:$5}}}} \
		{:fail: Unknown RPR localpart \
#			(HMAC should be ${hmac{md5}{RPR_SECRET}{$2+$3+$4+${quote_local_part:$5}}} not $1)\
		} \
	{${if <{$4}{$tod_epoch} \
		{:fail: Unknown RPR localpart \
#			(expired ${eval:$tod_epoch-$4} seconds ago)\
		} \
	{${if !eq {$sender_address}{} \
		{:fail: Only bounces to RPR localparts are permitted.\nSee RPR_URL} \
# Wheee. At last the actual rewrite part...
	{${sg {${sg {$5}{%#}{@}}}{%%}{%}} \
	}}}}}}}}
  headers_add = X-RPR-Return: DSN routed via $primary_hostname. See RPR_URL

# Rewrite reverse-path so that forwarding to SPF-afflicted servers
# doesn't break. We generate a limited-lifetime hash cookie,
# containing an escaped form of $sender_address along with (part of)
# $primary_hostname and $message_id so that we can track down the
# offending message in the log if it _does_ offend us.

rpr_rewrite:
  # Don't rewrite if it's a bounce, or from one of our own addresses.
  senders = ! : ! *@+local_domains : ! *@+virtual_domains
  # Don't rewrite for local delivery, or domains we're allowed to relay for anyway.
  domains = !+local_domains : !+relay_domains
  # Don't rewrite if the sender's domain doesn't actually advertise SPF records.
  # Actually it would be nicer if we could say 'only if we're not permitted
  # by the SPF record for this domain' but even if we had an SPF lookup type
  # we wouldn't have easy access to the IP we'd need to look up.
  condition = ${if match {${lookup dnsdb{txt=$sender_address_domain}{$value}fail}}{v=spf1} {1}}
  headers_add = "X-RPR-Rewrite: SMTP reverse-path rewritten from <$sender_address> by $primary_hostname\n\tSee RPR_URL"
  # Encode sender address by escaping % to %% and @ to %#, add expiry timestamp and tracking info
  address_data = ${sg {$primary_hostname}{^([^.]*)\..*}{\$1}}+$message_id+\
		${eval:$tod_epoch+RPR_DSN_TIMEOUT}+\
		${quote_local_part:${sg {${sg {$sender_address} {%}{%%}}} {@}{%#}}}
  errors_to = rpr+${hmac{md5}{RPR_SECRET}{$address_data}}+$address_data@RPR_DOMAIN
  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

dwmw2@infradead.org
Last modified: Wed Feb 25 10:16:12 GMT 2004