NAME¶
Mail::SRS - Interface to Sender Rewriting Scheme
SYNOPSIS¶
use Mail::SRS;
my $srs = new Mail::SRS(
Secret => [ .... ], # scalar or array
MaxAge => 49, # days
HashLength => 4, # base64 characters: 4 x 6bits
HashMin => 4, # base64 characters
);
my $srsaddress = $srs->forward($sender, $alias);
my $sender = $srs->reverse($srsaddress);
DESCRIPTION¶
The Sender Rewriting Scheme preserves .forward functionality in an SPF-compliant
world.
SPF requires the 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 and many points about the
scheme are discussed at
http://www.anarres.org/projects/srs/
For a better understanding of this code and how it functions, please read this
document and run the interactive walkthrough in eg/simple.pl in this
distribution. To run this from the build directory, type "make
teach".
METHODS¶
$srs = new Mail::SRS(...)¶
Construct a new Mail::SRS object and return it. Available parameters are:
- Secret => $string
- A key for the cryptographic algorithms. This may be an array or a single
string. A string is promoted into an array of one element.
- MaxAge
- The maximum number of days for which a timestamp is considered valid.
After this time, the timestamp is invalid.
- HashLength => $integer
- The number of bytes of base64 encoded data to use for the cryptographic
hash. More is better, but makes for longer addresses which might exceed
the 64 character length suggested by RFC2821. This defaults to 4, which
gives 4 x 6 = 24 bits of cryptographic information, which means that a
spammer will have to make 2^24 attempts to guarantee forging an SRS
address.
- HashMin => $integer
- The shortest hash which we will allow to pass authentication. Since we
allow any valid prefix of the full SHA1 HMAC to pass authentication, a
spammer might just suggest a hash of length 0. We require at least HashMin
characters, which must all be correct. Naturally, this must be no greater
than HashLength and will default to HashLength unless otherwise
specified.
- Separator => $character
- Specify the initial separator to use immediately after the SRS tag. SRS
uses the = separator throughout EXCEPT for the initial separator, which
may be any of + - or =.
Some MTAs already have a feature by which text after a + or - is ignored for
the purpose of identifying a local recipient. If the initial separator is
set to + or -, then an administrator may process all SRS mails by creating
users SRS0 and SRS1, and using Mail::SRS in the default delivery rule for
these users.
Some notes on the use and preservation of these separators are found in the
perldoc for Mail::SRS::Guarded.
- AlwaysRewrite => $boolean
- SRS rewriting is not performed by default if the alias host matches the
sender host, since it would be unnecessary to do so, and it interacts
badly with ezmlm if we do. Set this to true if you want always to rewrite
when requested to do so.
- IgnoreTimestamp => $boolean
- Consider all timestamps to be valid. Defaults to false. It is STRONGLY
recommended that this remain false. This parameter is provided so that
timestamps may be ignored temporarily after a change in the timestamp
format or encoding, until all timestamps in the old encoding would have
become invalid. Note that timestamps still form a part of the
cryptographic data when this is enabled.
- AllowUnsafeSrs
- This is a backwards compatibility option for an older version of the
protocol where SRS1 was not hash-protected. The 'reverse' method will
detect such addresses, and handle them properly. Deployments upgrading
from version <=0.27 to any version >=0.28 should enable this for
MaxAge+1 days.
When this option is enabled, all new addresses will be generated with
cryptographic protection.
Some subclasses require other parameters. See their documentation for details.
$srsaddress = $srs->forward($sender, $alias)¶
Map a sender address into a new sender and a cryptographic cookie. Returns an
SRS address to use as the new sender.
There are alternative subclasses, some of which will return SRS compliant
addresses, some will simply return non-SRS but valid RFC821 addresses. See the
interactive walkthrough for more information on this ("make teach").
$sender = $srs->reverse($srsaddress)¶
Reverse the mapping to get back the original address. Validates all
cryptographic and timestamp information. Returns the original sender address.
This method will die if the address cannot be reversed.
$srs->compile($sendhost, $senduser)¶
This method, designed to be overridden by subclasses, takes as parameters the
original host and user and must compile a new username for the SRS transformed
address. It is expected that this new username will be joined on $SRSSEP, and
will contain a hash generated from $self->hash_create(...), and possibly a
timestamp generated by $self->
timestamp_create().
$srs->parse($srsuser)¶
This method, designed to be overridden by subclasses, takes an SRS-transformed
username as an argument, and must reverse the transformation produced by
compile(). It is required to verify any hash and timestamp in the
parsed data, using $self->hash_verify($hash, ...) and
$self->timestamp_check($timestamp).
$srs->timestamp_create([$time])¶
Return a two character timestamp representing 'today', or $time if given. $time
is a Unix timestamp (seconds since the aeon).
This Perl function has been designed to be agnostic as to base, and in practice,
base32 is used since it can be reversed even if a remote MTA smashes case (in
violation of RFC2821 section 2.4). The agnosticism means that the Perl uses
division instead of rightshift, but in Perl that doesn't matter. C
implementors should implement this operation as a right shift by 5.
$srs->timestamp_check($timestamp)¶
Return 1 if a timestamp is valid, undef otherwise. There are 4096 possible
timestamps, used in a cycle. At any time, $srs->{MaxAge} timestamps in this
cycle are valid, the last one being today. A timestamp from the future is not
valid, neither is a timestamp from too far into the past. Of course if you go
far enough into the future, the cycle wraps around, and there are valid
timestamps again, but the likelihood of a random timestamp being valid is
4096/$srs->{MaxAge}, which is usually quite small: 1 in 132 by default.
$srs->time_check($time)¶
Similar to $srs->timestamp_check($timestamp), but takes a Unix time, and
checks that an alias created at that Unix time is still valid. This is
designed for use by subclasses with storage backends.
$srs->hash_create(@data)¶
Returns a cryptographic hash of all data in @data. Any piece of data encoded
into an address which must remain inviolate should be hashed, so that when the
address is reversed, we can check that this data has not been tampered with.
You must provide at least one piece of data to this method (otherwise this
system is both cryptographically weak and there may be collision problems with
sender addresses).
$srs->hash_verify($hash, @data)¶
Verify that @data has not been tampered with, given the cryptographic hash
previously output by $srs->
hash_create(); Returns 1 or undef. All
known secrets are tried in order to see if the hash was created with an old
secret.
$srs->set_secret($new, @old)¶
Add a new secret to the rewriter. When an address is returned, all secrets are
tried to see if the hash can be validated. Don't use "foo",
"secret", "password", "10downing",
"god" or "wednesday" as your secret.
$srs->get_secret()¶
Return the list of secrets. These are secret. Don't publish them.
$srs->separator()¶
Return the initial separator, which follows the SRS tag. This is only used as
the initial separator, for the convenience of administrators who wish to make
srs0 and srs1 users on their mail servers and require to use + or - as the
user delimiter. All other separators in the SRS address must be "=".
EXPORTS¶
Given :all, this module exports the following variables.
- $SRSSEP
- The SRS separator. The choice of "=" as internal separator was
fairly arbitrary. It cannot be any of the following:
- / +
- Used in Base64.
- -
- Used in domains.
- ! %
- Used in bang paths and source routing.
- :
- Cannot be used in a Windows NT or Apple filename.
- ; | *
- Shell or regular expression metacharacters are probably to be
avoided.
- $SRS0TAG
- The SRS0 tag.
- $SRS1TAG
- The SRS1 tag.
- $SRSTAG
- Deprecated, equal to $SRS0TAG.
- $SRSWRAP
- Deprecated, equal to $SRS1TAG.
- $SRSHASHLENGTH
- The default hash length for the SRS HMAC.
- $SRSMAXAGE
- The default expiry time for timestamps.
EXAMPLES OF USAGE¶
For people wanting boilerplate and those less familiar with using Perl modules
in larger applications.
Forward Rewriting¶
my $srs = new Mail::SRS(...);
my $address = ...
my $domain = ...
my $srsaddress = eval { $srs->forward($srsaddress, $domain); };
if ($@) {
# The rewrite failed
}
else {
# The rewrite succeeded
}
Reverse Rewriting¶
my $srs = new Mail::SRS(...);
my $srsaddress = ...
my $address = eval { $srs->reverse($srsaddress); };
if ($@) {
# The rewrite failed
}
else {
# The rewrite succeeded
}
NOTES ON SRS¶
Case Sensitivity¶
RFC2821 states in section 2.4: "The local-part of a mailbox MUST BE treated
as case sensitive. Therefore, SMTP implementations MUST take care to preserve
the case of mailbox local-parts. [...] In particular, for some hosts the user
"smith" is different from the user "Smith". However,
exploiting the case sensitivity of mailbox local-parts impedes
interoperability and is discouraged."
SRS does not rely on case sensitivity in the local part. It uses base64 for
encoding the hash, but allows a case insensitive match, making this
approximately equivalent to base36 at worst. It will issue a warning if it
detects that a remote MTA has smashed case. The timestamp is encoded in
base32.
The 64 Billion Character Question¶
RFC2821 section 4.5.3.1: Size limits and minimums:
There are several objects that have required minimum/maximum
sizes. Every implementation MUST be able to receive objects
of at least these sizes. Objects larger than these sizes
SHOULD be avoided when possible. However, some Internet
mail constructs such as encoded X.400 addresses [16] will
often require larger objects: clients MAY attempt to transmit
these, but MUST be prepared for a server to reject them if
they cannot be handled by it. To the maximum extent possible,
implementation techniques which impose no limits on the length
of these objects should be used.
local-part
The maximum total length of a user name or other
local-part is 64 characters.
Clearly, by including 2 domain names and a local-part in the rewritten address,
there is no way in which SRS can guarantee to stay under this limit. However,
very few systems are known to actively enforce this limit, and those which
become known to the developers will be listed here.
- Cisco: PIX MailGuard (firewall gimmick)
- WebShield [something] (firewall gimmick)
Invalid SRS Addresses¶
DO NOT MALFORMAT ADDRESSES. This is designed to be an interoperable format.
Certain things are allowed, such as changing the semantics of the hash or the
timestamp. However, both of these fields must be present and separated by the
SRS separator character "=". The purpose of this section is to
illustrate that if a malicious party were to malformat an address, he would
gain nothing by doing so, nor would the network suffer.
The SRS protocol is predicated on the fact that the first forwarder provides a
cryptographic wrapper on the forward chain for sending mail to the original
sender. So what happens if an SRS address is invalid, or faked by a spammer?
The minimum parsing of existing SRS addresses is done at each hop. If an SRS0
address is not valid or badly formatted, it will not affect the operation of
the system: the mail will go out along the forwarder chain, and return to the
invalid or badly formatted address.
If the spammer is not pretending to be the first hop, then he must somehow
construct an SRS0 address to embed within his SRS1 address. The cryptographic
checks on this SRS0 address will fail at the first forwarder and the mail will
be dropped.
If the spammer is pretending to be the first hop, then SPF should require that
any bounces coming back return to his mail server, thus he wins nothing.
Cryptographic Systems¶
The hash in the address is designed to prevent the forging of reverse addresses
by a spammer, who might then use the SRS host as a forwarder. It may only be
constructed or validated by a party who knows the secret key.
The cryptographic system in the default implementation is not mandated. Since
nobody else ever needs to interpret the hash, it is reasonable to put any
binary data into this field (subject to the possible constraint of case
insensitive encoding).
The SRS maintainers have attempted to provide a good system. It satisfies a
simple set of basic requirements: to provide unforgeability of SRS addresses
given that every MTA for a domain shares a secret key. We prefer SHA1 over MD5
for political, rather than practical reasons. (Anyone disputing this statement
must include an example of a practical weakness in their mail. We would love
to see it.)
If you find a weakness in our system, or you think you know of a better system,
please tell us. If your requirements are different, you may override
hash_create() and
hash_verify() to implement a different system
without adversely impacting the network, as long as your addresses still
behave as SRS addresses.
Extending Mail::SRS¶
Write a subclass. You will probably want to override
compile() and
parse(). If you are more familiar with the internals of SRS, you might
want to override
hash_create(),
hash_verify(),
timestamp_create() or
timestamp_check().
CHANGELOG¶
MINOR CHANGES since v0.29¶
- timestamp_check now explicitly smashes case when verifying. This means
that the base used must be base32, NOT base64.
- hash_create and hash_verify now explicitly smash case when creating and
verifying hashes. This does not have a significant cryptographic
impact.
MAJOR CHANGES since v0.27¶
- The SRS1 address format has changed to include cryptographic information.
Existing deployments should consider setting AllowUnsafeSrs for MaxAge+1
days.
MINOR CHANGES since v0.26¶
- parse() and compile() are explicitly specified to
die() on error.
MINOR CHANGES since v0.23¶
- Update BASE32 according to RFC3548.
MINOR CHANGES since v0.21¶
- Dates are now encoded in base32.
- Case insensitive MAC validation is now allowed, but will issue a
warning.
MINOR CHANGES since v0.18¶
- $SRSTAG and $SRSWRAP are deprecated.
- Mail::SRS::Reversable is now Mail::SRS::Reversible
- This should not be a problem since people should not be using it!
You must use $SRS0RE and $SRS1RE to detect SRS addresses.
MAJOR CHANGES since v0.15¶
- The separator character is now "=".
- The cryptographic scheme is now HMAC with SHA1.
- Only a prefix of the MAC is used.
This API is still a release candidate and should remain relatively stable.
BUGS¶
Email address parsing for quoted addresses is not yet done properly.
Case insensitive MAC validation should become an option.
TODO¶
Write a testsuite for testing user-defined SRS implementations.
SEE ALSO¶
Mail::SRS::Guarded, Mail::SRS::DB, Mail::SRS::Reversable, "make
teach", eg/*,
http://www.anarres.org/projects/srs/
AUTHOR¶
Shevek
CPAN ID: SHEVEK
cpan@anarres.org
http://www.anarres.org/projects/
COPYRIGHT¶
Copyright (c) 2004 Shevek. All rights reserved.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.