You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by mm...@apache.org on 2007/10/02 03:12:12 UTC
svn commit: r581122 -
/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm
Author: mmartinec
Date: Mon Oct 1 18:12:12 2007
New Revision: 581122
URL: http://svn.apache.org/viewvc?rev=581122&view=rev
Log:
revise r580453, accomodating some of the concerns in bug 5662#1 for Plugin::DKIM
Modified:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm?rev=581122&r1=581121&r2=581122&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm Mon Oct 1 18:12:12 2007
@@ -42,53 +42,7 @@
http://jason.long.name/dkimproxy/
http://tools.ietf.org/rfc/rfc4871.txt
http://tools.ietf.org/rfc/rfc4870.txt
- http://www.ietf.org/internet-drafts/draft-ietf-dkim-ssp-01.txt
- http://www.ietf.org/internet-drafts/draft-ietf-dkim-overview-05.txt
-
-=head1 A BRIEF INTRODUCTION TO TERMINOLOGY
-
-B<Originator Address> is the author's e-mail address in a "From:" header field.
-
-A message may carry one or more B<signatures> (signature header fields),
-each signature carries an B<identity> (the i= tag) telling who provided it
-and how it can be verified.
-
-Only B<valid signatures> matter (i.e. verified signatures), all invalid
-signatures can be and MUST be ignored (making a distinction here can provide
-advantage to malicious senders or spam senders, as faking an invalid signature
-is trivial).
-
-A valid signature (one or more) whose identity matches I<Originator Address>
-is called B<Originator Signature> (or sometimes a I<First-Party Signature>),
-and is normally acceptable to recipients unconditionally (that doesn't say
-anything about the merits of a message, it just tells the message is coming
-from where it claims to be coming).
-
-A I<valid signature> which is I<not an Originator Signature> is called a
-B<Third-Party Signature> (e.g. supplied by a mailing list or re-mailer).
-
-It is up to a recipient (the verifier) to decide which I<Third-Party
-Signatures> are acceptable to him (e.g. those provided by a reputable
-mailing list server with good anti-spam measures) and which are not.
-Such signatures are called B<Verifier Acceptable Third-Party Signatures>.
-
-There exists a mechanism called B<DKIM Sender Signing Practices> (SSP),
-by which an I<Originator> (i.e. a mail author or his sending mailer) can
-tell recipients (verifiers) about his signing practices (previously called
-signing policy). An Internet Draft is approaching ratification stage,
-but is not there yet. This plugin does not yet fully implement it.
-
-Having a proof a message is coming from where it claims to be coming
-is an essential stone in anti-phishing and anti-spam protection, but
-is not sufficient by itself. Some B<reputation scheme> is needed, a
-community-based reputation scheme would be useful, but is not available
-for the time being. Currently it is up to verifiers to organize it for
-themselves, e.g. in a form of I<DKIM-based whitelisting>, as provided by
-this plugin. The mechanism allows to whitelist an I<Originator Signature>
-as well as selected I<Third-Party Signatures>.
-
-For details please consult RFC 4871, draft-ietf-dkim-ssp-01 (or later)
-and draft-ietf-dkim-overview-05 (or later).
+ http://ietf.org/html.charters/dkim-charter.html
=cut
@@ -146,26 +100,17 @@
Use this to supplement the whitelist_from addresses with a check to make sure
the message with a given From: author's address (originator address) carries
a valid Domain Keys Identified Mail (DKIM) signature by a verifier-acceptable
-signing-identity (the i= tag). Signature verification is based on a signing
-identity's DKIM public key, fetched by a DNS lookup from a domain specified
-in a d= tag of a signature.
-
-In order to support multiple optional verifier-acceptable signing identities
-(e.g. signatures supplied by mailing lists), only one whitelist entry
-is allowed per line, exactly like C<whitelist_from_rcvd>. Multiple
-C<whitelist_from_dkim> lines are allowed. File-glob style meta characters
-are allowed for the From: address, just like with C<whitelist_from_rcvd>.
-
-If no signing identity (second parameter) is specified, the only acceptable
-signature will be an originator signature (not a third-party signature).
-An originator signature is a signature where the signing identity matches
-the originator address (i.e. the address in a From header field). If the
-signing identity does not include a localpart, then only the domains must
-match; otherwise, the two addresses must be identical. Note that there is
-no subdomain stripping magic, a match on domain must be exact.
+signing-identity (the i= tag).
-The originator address is obtained from the "From:" header field, which
-should be in a signed part of the message.
+Only one whitelist entry is allowed per line, as in C<whitelist_from_rcvd>.
+Multiple C<whitelist_from_dkim> lines are allowed. File-glob style
+meta characters are allowed for the From: address (the first parameter),
+just like with C<whitelist_from_rcvd>.
+
+If no signing identity parameter is specified, the only acceptable signature
+will be an originator signature (not a third-party signature). An originator
+signature is a signature where the signing identity of a signature matches
+the originator address (i.e. the address in a From header field).
Since this whitelist requires a DKIM check to be made, network tests must
be enabled.
@@ -174,18 +119,15 @@
whitelist_from_dkim joe@example.com
whitelist_from_dkim *@corp.example.com
- whitelist_from_dkim *@gmail.com
+ whitelist_from_dkim *@*.example.com
-Examples of whitelisting based on an third-party signatures:
+Examples of whitelisting based on third-party signatures:
whitelist_from_dkim rick@example.net richard@example.net
whitelist_from_dkim rick@sub.example.net example.net
whitelist_from_dkim jane@example.net example.org
- whitelist_from_dkim *@* spamassassin.apache.org
- whitelist_from_dkim *@* postfix.org
-
-(the last two examples illustrate singing by mailing lists, although the
-featured mailing lists are not currently re-signing mailing list traffic)
+ whitelist_from_dkim *@info.example.com example.com
+ whitelist_from_dkim *@* remailer.example.com
=item def_whitelist_from_dkim originator@example.com [signing-identity]
@@ -207,11 +149,7 @@
return $Mail::SpamAssassin::Conf::INVALID_VALUE;
}
my $address = $1;
- my $identity = ''; # when empty only originator signature is acceptable
- if (defined $2) { # explicit additional acceptable signing identity
- $identity = $2;
- $identity = '@' . $identity if $identity !~ /\@/;
- }
+ my $identity = defined $2 ? $2 : ''; # empty implies originator signature
$self->{parser}->add_to_addrlist_rcvd('whitelist_from_dkim',
$address, $identity);
}
@@ -229,11 +167,7 @@
return $Mail::SpamAssassin::Conf::INVALID_VALUE;
}
my $address = $1;
- my $identity = ''; # when empty only originator signature is acceptable
- if (defined $2) { # explicit additional acceptable signing identity
- $identity = $2;
- $identity = '@' . $identity if $identity !~ /\@/;
- }
+ my $identity = defined $2 ? $2 : ''; # empty implies originator signature
$self->{parser}->add_to_addrlist_rcvd('def_whitelist_from_dkim',
$address, $identity);
}
@@ -361,8 +295,8 @@
$scan->{dkim_address} = !$message->message_originator ? ''
: $message->message_originator->address();
- dbg("dkim: originator address: %s",
- $scan->{dkim_address} ? $scan->{dkim_address} : 'none');
+ dbg("dkim: originator address: ".
+ ($scan->{dkim_address} ? $scan->{dkim_address} : 'none'));
$scan->{dkim_identity} = '';
if ($message->signature) {
@@ -371,17 +305,20 @@
# OPTIONAL, default is an empty local-part followed by an "@"
# followed by the domain from the "d=" tag).
$scan->{dkim_identity} = $message->signature->identity();
+ dbg("dkim: signing identity: ".$scan->{dkim_identity}.
+ ", signing domain: ".$message->signature->domain());
if ($scan->{dkim_identity} eq '') {
$scan->{dkim_identity} = '@' . $message->signature->domain();
+ } elsif ($scan->{dkim_identity} !~ /\@/) {
+ $scan->{dkim_identity} = '@' . $scan->{dkim_identity};
}
- dbg("dkim: signing identity: ".$scan->{dkim_identity});
}
my $result = $message->result();
my $detail = $message->result_detail();
# let the result stand out more clearly in the log, use uppercase
- dbg("dkim: signature verification result: %s",
- $detail eq 'none' ? $detail : uc $detail);
+ dbg("dkim: signature verification result: ".
+ ($detail eq 'none' ? $detail : uc $detail));
# extract the actual lookup results
if ($result eq 'pass') {
@@ -487,18 +424,21 @@
return unless $scan->is_dns_available();
# trigger a DKIM check so we can get address/identity info
- unless ($self->check_dkim_verified($scan)) {
- return;
- }
+ my $verified = $self->check_dkim_verified($scan);
+
+ return unless $verified || would_log("dbg","dkim");
+ # continue if verification succeeded or we want the debug info
unless ($scan->{dkim_address}) {
dbg("dkim: %swhitelist_from_dkim: could not find originator address",
$default ? "def_" : "");
return;
}
- unless ($scan->{dkim_identity}) {
- dbg("dkim: %swhitelist_from_dkim: could not find signing identity",
- $default ? "def_" : "");
+
+ my $identity = $scan->{dkim_identity};
+ unless ($identity) {
+ # (useless double debug line, we already reported if there is no signature)
+ # dbg("dkim: %swhitelist_from_dkim: no signature", $default ? "def_" : "");
return;
}
@@ -523,62 +463,57 @@
}
}
+ # prepare summary info string to be used for logging
+ my $info = $identity eq '' ? 'no' : $verified ? 'verified' : 'failed';
+ my $originator_matching_part = $scan->{dkim_address}; # address in 'From'
+ if ($identity =~ /^\@/) { # empty localpart in signing identity
+ local($1);
+ $originator_matching_part =~ s/^.*?(\@[^\@]*)?$/$1/s; # strip localpart
+ }
+ $info .= lc $identity eq lc $originator_matching_part ? ' originator'
+ : ' third-party';
+ $info .= " signature by id $identity, originator $scan->{dkim_address}";
+
# if the message doesn't pass DKIM validation, it can't pass DKIM whitelist
- if ($default) { # DEF_DKIM_WHITELIST_FROM
+
+ if ($default) {
if ($scan->{def_dkim_whitelist_from}) {
- if ($self->check_dkim_verified($scan)) { # double-check just in case
- dbg("dkim: originator %s, signing identity %s, found in ".
- "def_whitelist_from_dkim and passed DKIM verification",
- $scan->{dkim_address}, $scan->{dkim_identity});
- } else {
- $scan->{def_dkim_whitelist_from} = 0;
- }
+ dbg("dkim: $info, found in DEF_WHITELIST_FROM_DKIM" .
+ ($verified ? '' : ' but ignored') );
+ if (!$verified) { $scan->{def_dkim_whitelist_from} = 0 }
+ } else {
+ dbg("dkim: $info, not in DEF_WHITELIST_FROM_DKIM");
}
-# else {
-# dbg("dkim: originator %s, signing identity %s, ".
-# "not in def_whitelist_from_dkim",
-# $scan->{dkim_address}, $scan->{dkim_identity});
-# }
- } else { # DKIM_WHITELIST_FROM
+ } else {
if ($scan->{dkim_whitelist_from}) {
- if ($self->check_dkim_verified($scan)) { # double-check just in case
- dbg("dkim: originator %s, signing identity %s, found in ".
- "whitelist_from_dkim and passed DKIM verification",
- $scan->{dkim_address}, $scan->{dkim_identity});
- } else {
- $scan->{dkim_whitelist_from} = 0;
- }
+ dbg("dkim: $info, found in WHITELIST_FROM_DKIM" .
+ ($verified ? '' : ' but ignored') );
+ if (!$verified) { $scan->{dkim_whitelist_from} = 0 }
+ } else {
+ dbg("dkim: $info, not in WHITELIST_FROM_DKIM");
}
-# else {
-# dbg("dkim: originator %s, signing identity %s, ".
-# "not in whitelist_from_dkim",
-# $scan->{dkim_address}, $scan->{dkim_identity});
-# }
}
}
-# check for an originator signature(s), as well as for additional
-# verifier-acceptable signatures if provided in a config as a second
-# parameter on dkim whitelist entries
+# check for verifier-acceptable signatures; an empty (or undefined) signing
+# identity in a whitelist implies checking for an originator signature
#
sub _wlcheck_acceptable_signature {
my ($self, $scan, $wl) = @_;
foreach my $white_addr (keys %{$scan->{conf}->{$wl}}) {
my $re = qr/$scan->{conf}->{$wl}->{$white_addr}{re}/i;
- # check for the originator signature is implied
- $self->_wlcheck_one($scan, $wl, $white_addr, undef, $re) and return 1;
- # walk through all additional verifier-acceptable signing identities
- foreach my $acceptable_identity
- (@{$scan->{conf}->{$wl}->{$white_addr}{domain}}) {
- next if !defined $acceptable_identity || $acceptable_identity eq '';
- $self->_wlcheck_one($scan, $wl, $white_addr, $acceptable_identity, $re)
- and return 1;
+ if ($scan->{dkim_address} =~ $re) {
+ foreach my $acceptable_identity
+ (@{$scan->{conf}->{$wl}->{$white_addr}{domain}}) {
+ $self->_wlcheck_one($scan, $wl, $white_addr, $acceptable_identity, $re)
+ and return 1;
+ }
}
}
return 0;
}
-# use a traditional whitelist_from-style addrlist, the only acceptable DKIM
+# use a traditional whitelist_from -style addrlist, the only acceptable DKIM
# signature is an Originator Signature. Note: don't pre-parse and store the
# domains; that's inefficient memory-wise and only saves one m//
#
@@ -600,48 +535,52 @@
# When $acceptable_identity is undef or an empty string it implies an
# originator signature check.
- my $originator = $scan->{dkim_address};
- if ($originator =~ $re) { # originator address does match a whitelist entry
+ my $matches = 0;
+ my $originator = $scan->{dkim_address}; # address in a 'From' header field
+ my $identity = $scan->{dkim_identity}; # TODO: support multiple signatures
+ if ($originator =~ $re) {
+ # originator address does match a whitelist entry (or we are debugging),
# but does it carry a signature of a verifier-acceptable signing identity?
- my $identity = $scan->{dkim_identity}; # TODO: support multiple signatures
- local($1);
+ # An "Originator Signature" is any Valid Signature where the signing
+ # identity matches the Originator Address. If the signing identity
+ # does not include a localpart, then only the domains must match;
+ # otherwise, the two addresses must be identical.
+
+ local($1,$2);
+ my $originator_matching_part = $originator;
+ if ($identity =~ /^\@/) { # empty localpart in signing identity
+ $originator_matching_part =~ s/^.*?(\@[^\@]*)?$/$1/s; # strip localpart
+ }
if (!defined $acceptable_identity || $acceptable_identity eq '') {
# checking for originator signature
- #
- # An "Originator Signature" is any Valid Signature where the signing
- # identity matches the Originator Address. If the signing identity
- # does not include a localpart, then only the domains must match;
- # otherwise, the two addresses must be identical.
- #
- my $originator_matching_part = $originator;
- if ($identity =~ /^\@/) { # no localpart in signing identity
- $originator_matching_part =~ s/^.*?(\@[^\@]*)?$/$1/s; # strip localpart
- }
- if (lc($originator_matching_part) eq lc($identity)) {
- dbg("dkim: originator signature from %s, signing identity %s, ".
- "matches %s %s", $originator, $identity, $wl, $re);
- return 1;
- }
- }
-
- else {
- if ($acceptable_identity !~ /\@/) { # ensure domain part, possibly empty
+ $matches = 1 if lc $identity eq lc $originator_matching_part;
+ } else { # checking for verifier-acceptable signature
+ if ($acceptable_identity !~ /\@/) {
$acceptable_identity = '@' . $acceptable_identity;
}
- my $identity_matching_part = $identity;
- if ($acceptable_identity =~ /^\@/) { # no localpart, just domain?
- $identity_matching_part =~ s/^.*?(\@[^\@]*)?\z/$1/s; # strip localpart
- }
- if (lc($identity_matching_part) eq lc($acceptable_identity)) {
- dbg("dkim: originator %s, signing identity %s, ".
- "verifier-acceptable (%s), matches %s %s",
- $originator, $identity, $acceptable_identity, $wl, $re);
- return 1;
- }
+ # split into local part and domain
+ $identity =~ /^ (.*?) \@ ([^\@]*) $/xs;
+ my($actual_id_mbx, $actual_id_dom) = ($1,$2);
+ $acceptable_identity =~ /^ (.*?) \@ ([^\@]*) $/xs;
+ my($accept_id_mbx, $accept_id_dom) = ($1,$2);
+
+ # let's take a liberty and compare local parts case-insensitively
+ if ($accept_id_mbx ne '') { # local part exists, full id must match
+ $matches = 1 if lc $identity eq lc $acceptable_identity;
+ } else { # any local part in signing identity is acceptable
+ # as long as domain matches or is a subdomain
+ $matches = 1 if $actual_id_dom =~ /(^|\.)\Q$accept_id_dom\Q/i;
+ }
+ }
+ if ($matches && would_log("dbg","dkim")) {
+ my $verified = $self->check_dkim_verified($scan);
+ my $info = $identity eq '' ? 'no' : $verified ? 'verified' : 'failed';
+ dbg("dkim: $info signature by id $identity, originator $originator ".
+ " matches $wl $re");
}
}
- return 0;
+ return $matches;
}
1;