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.