NAME

Mail::SRS - OO interface to Sender Rewriting Scheme

SYNOPSIS

http://spf.pobox.com/srs.html

use Mail::SRS;
my $srs = Mail::SRS->new
  (bounce_delimiter  => '+',
   sender_delimiter  => '-',
   cookie_delimiter  => '-',
   alias_delimiter   => '=',
   address_delimiter => '#',
   secret            => [ 'my secret', 'older secrets', ... ],
   format            => 'bounce[% $bounce_delimiter . $sender . $cookie_delimiter . $cookie . $alias_delimiter . $alias_user %]@[% $alias_host %]';
   max_age           => 30, # days
   validator         => sub {
     my %o = @_; # cookie, sender, alias
     ...
     return; # valid.
     return "550 No more bounces accepted to that address.";
     return "550 Bounces not accepted to that address.";
   },
   extractor         => sub {
     my ($self, $address) = @_;
     ...
     return ($sender, $cookie, $alias_user, $alias_host);
   },
  );

$srs->set_secret('new secret');
$srs->set_secret('newer secret', $srs->get_secret);

my ($new_sender, $cookie) = $srs->forward(sender => 'sender@example.com',
                                          alias  => 'alias@forwarder.com',
                                          rcpts  => [ 'rcpt@example.net' ]);

# $new_sender is your new return-path.
# when you get mail to that return-path, you can recover the original data with:

my ($sender, $alias, $response) = $srs->reverse(address => $new_sender);

DESCRIPTION

The Sender Rewriting Scheme preserves .forward functionality in an SPF-compliant world.

This module should be considered alpha at this time. Documentation is incomplete. Pobox.com decided to publish Mail::SRS to CPAN anyway because there seems to be a fair amount of interest out there in implementing SRS.

SPF requires an SMTP client IP to match the envelope sender (return-path). When a message is forwarded through an intermediate server, that intermediate server may need to rewrite the return-path to remain SPF compliant. If the message bounces, that intermediate server needs to validate the bounce and forward the bounce to the original sender.

SRS provides a convention for return-path rewriting which allows multiple forwarding servers to compact the return-path. SRS also provides an authentication mechanism to ensure that purported bounces are not arbitrarily forwarded.

SRS is documented at http://spf.pobox.com/srs.html

A given SRS address is valid for one month by default.

Cookies are relatively unique.

You may wish to limit the number of bounces you will convey to a given SRS sender. The rcpts argument to forward lets you encode the approximate number of recipients into the cookie; you can thus limit a given SRS address to a specified number of uses by passing reverse() a validator callback which performs a local database lookup against the cookie,sender,alias tuple.

METHODS

new

my $srs = Mail::SRS->new
  (sender_delimiter  => '-',
   cookie_delimiter  => '-',
   alias_delimiter   => '=',
   address_delimiter => '#',
   secret            => [ 'my secret', 'older secrets', ... ],
   format            => 'bounce+[% $sender . $cookie_delimiter . $cookie . $alias_delimiter . $alias_user %]@[% $alias_host %]';
   max_age           => 30, # days
   validator         => sub {
     my %o = @_; # cookie, sender, alias
     ...
     return; # valid.
     return "550 No more bounces accepted to that address.";
     return "550 Bounces not accepted to that address.";
   },
   extractor         => sub {
     my ($self, $address) = @_;
     ...
     return ($sender, $cookie, $alias_user, $alias_host);
   },
  );

forward

my ($new_sender, $cookie) = $srs->forward(sender => 'sender@example.com',
                                          alias  => 'alias@forwarder.com',
                                          rcpts  => [ 'rcpt@example.net' ]);

# $new_sender is your new return-path.

reverse

# $new_sender is the return-path produced by ->forward().
# when you get mail to that return-path, you can recover the original data with:

my ($sender, $alias, $response) = $srs->reverse(address => $new_sender);

set_secret, get_secret

$srs->set_secret('new secret');
$srs->set_secret('newer secret', $srs->get_secret);

ALGORITHM

Cookies are needed so a reversing host doesn't become an open relay.

We are concerned that an attacker will try to forge or replay cookies.

We approach the replay problem by limiting the validity of a cookie in time and in the number of punches permitted that cookie.

We approach the forgery problem by using a secret string in the creation and validation of the cookie.

Punches: When we create a cookie, we do so knowing how many recipients are being used for that cookie; and we multiply that number by a modest ratio which allows for downstream .forwarding to multiple accounts. We encode that recipient count into the cookie and expose it in the salt.

Time: When we create a cookie, we do so knowing the current time. We encode the current time, with limited precision, into the cookie and expose it in the salt.

The salt of a standard crypt cookie can represent 12 bits of data, being m([a-zA-Z0-9./]{2}): each character is one of 64 bytes; two characters afford 4096 or 2**12 combinations.

* Let us specify that an SRS cookie may expect as few as 2 and as many as 8 discrete punches. More punches than 8 shall be considered "infinite".

Using 2 bits, an SRS cookie can specify a maximum punch count of 2, 4, 8, or infinite.

  0 = 2
  1 = 4
  2 = 8
  3 = infinite

That leaves 10 bits.

* Let us specify that an SRS cookie shall expire after 1 month.

Day precision is sufficient.  To store 256 days, we need 8 bits.

* Reserved: That leaves 2 bits reserved for future use.

* SRS cookie:

     8      2   2
[  day   ][ p][rr]

We test an SRS cookie for time-validity by decoding the salt to reveal the time slot and the punch count; we then confirm that the time slot and punch count were not forged by recrypting the cookie against the asserted data plus the secret.