You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by do...@apache.org on 2006/08/29 00:04:08 UTC

svn commit: r437860 - in /spamassassin/branches/3.1: MANIFEST lib/Mail/SpamAssassin/Plugin/DomainKeys.pm rules/50_scores.cf rules/60_whitelist_dk.cf

Author: dos
Date: Mon Aug 28 15:04:08 2006
New Revision: 437860

URL: http://svn.apache.org/viewvc?rev=437860&view=rev
Log:
bug 5065: implement whitelist_from_dk

Added:
    spamassassin/branches/3.1/rules/60_whitelist_dk.cf
Modified:
    spamassassin/branches/3.1/MANIFEST
    spamassassin/branches/3.1/lib/Mail/SpamAssassin/Plugin/DomainKeys.pm
    spamassassin/branches/3.1/rules/50_scores.cf

Modified: spamassassin/branches/3.1/MANIFEST
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.1/MANIFEST?rev=437860&r1=437859&r2=437860&view=diff
==============================================================================
--- spamassassin/branches/3.1/MANIFEST (original)
+++ spamassassin/branches/3.1/MANIFEST Mon Aug 28 15:04:08 2006
@@ -205,6 +205,7 @@
 rules/50_scores.cf
 rules/60_awl.cf
 rules/60_whitelist.cf
+rules/60_whitelist_dk.cf
 rules/60_whitelist_dkim.cf
 rules/60_whitelist_spf.cf
 rules/60_whitelist_subject.cf

Modified: spamassassin/branches/3.1/lib/Mail/SpamAssassin/Plugin/DomainKeys.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.1/lib/Mail/SpamAssassin/Plugin/DomainKeys.pm?rev=437860&r1=437859&r2=437860&view=diff
==============================================================================
--- spamassassin/branches/3.1/lib/Mail/SpamAssassin/Plugin/DomainKeys.pm (original)
+++ spamassassin/branches/3.1/lib/Mail/SpamAssassin/Plugin/DomainKeys.pm Mon Aug 28 15:04:08 2006
@@ -61,6 +61,8 @@
   $self->register_eval_rule ("check_domainkeys_signsome");
   $self->register_eval_rule ("check_domainkeys_testing");
   $self->register_eval_rule ("check_domainkeys_signall");
+  $self->register_eval_rule ("check_for_dk_whitelist_from");
+  $self->register_eval_rule ("check_for_def_dk_whitelist_from");
 
   $self->set_config($mailsaobject->{conf});
 
@@ -90,6 +92,86 @@
     type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
   });
 
+=item whitelist_from_dk add@ress.com [signing domain name]
+
+Use this to supplement the whitelist_from addresses with a check to make sure
+the message has been signed by a DomainKeys signature that can be verified
+against the From: domain's DomainKeys public key.
+
+In order to support signing domain names that differ from the address domain
+name, only one whitelist entry is allowed per line, exactly like
+C<whitelist_from_rcvd>.  Multiple C<whitelist_from_dk> lines are allowed.  
+File-glob style meta characters are allowed for the From: address, just like
+with C<whitelist_from_rcvd>.  The optional signing domain name parameter must
+match from the right-most side, also like in C<whitelist_from_rcvd>.
+
+If no signing domain name parameter is specified the domain of the address
+parameter specified will be used instead.
+
+The From: address is obtained from a signed part of the message (ie. the
+"From:" header), not from envelope data that is possible to forge.
+
+Since this whitelist requires a DomainKeys check to be made, network tests must
+be enabled.
+
+Examples:
+
+  whitelist_from_dk joe@example.com
+  whitelist_from_dk *@corp.example.com
+
+  whitelist_from_dk bob@it.example.net  example.net
+  whitelist_from_dk *@eng.example.net   example.net
+
+=item def_whitelist_from_dk add@ress.com [signing domain name]
+
+Same as C<whitelist_from_dk>, but used for the default whitelist entries
+in the SpamAssassin distribution.  The whitelist score is lower, because
+these are often targets for spammer spoofing.
+
+=cut
+
+  push (@cmds, {
+    setting => 'whitelist_from_dk',
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      unless (defined $value && $value !~ /^$/) {
+	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+      }
+      unless ($value =~ /^(\S+)(?:\s+(\S+))?$/) {
+	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+      my $address = $1;
+      my $signer = (defined $2 ? $2 : $1);
+
+      unless (defined $2) {
+	$signer =~ s/^.*@(.*)$/$1/;
+      }
+      $self->{parser}->add_to_addrlist_rcvd ('whitelist_from_dk',
+						$address, $signer);
+    }
+  });
+
+  push (@cmds, {
+    setting => 'def_whitelist_from_dk',,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      unless (defined $value && $value !~ /^$/) {
+	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+      }
+      unless ($value =~ /^(\S+)(?:\s+(\S+))?$/) {
+	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+      my $address = $1;
+      my $signer = (defined $2 ? $2 : $1);
+
+      unless (defined $2) {
+	$signer =~ s/^.*@(.*)$/$1/;
+      }
+      $self->{parser}->add_to_addrlist_rcvd ('def_whitelist_from_dk',
+						$address, $signer);
+    }
+  });
+
   $conf->{parser}->register_commands(\@cmds);
 }
 
@@ -133,7 +215,19 @@
   return $scan->{domainkeys_signall};
 }
 
+sub check_for_dk_whitelist_from {
+  my ($self, $scan) = @_;
+  $self->_check_dk_whitelist($scan, 0) unless $scan->{dk_whitelist_from_checked};
+  $scan->{dk_whitelist_from};
+}
 
+sub check_for_def_dk_whitelist_from {
+  my ($self, $scan) = @_;
+  $self->_check_dk_whitelist($scan, 1) unless $scan->{def_dk_whitelist_from_checked};
+  $scan->{def_dk_whitelist_from};
+}
+
+# ---------------------------------------------------------------------------
 
 sub _check_domainkeys {
   my ($self, $scan) = @_;
@@ -167,6 +261,22 @@
     return;
   }
 
+  # get the sender address for whitelist checks
+  if (defined $message->sender()) {
+    $scan->{dk_address} = @{$message->sender()}[1];
+    dbg("dk: sender: $scan->{dk_address}");
+  } elsif (defined $message->from()) {
+    $scan->{dk_address} ||= @{$message->from()}[1];
+    dbg("dk: from: $scan->{dk_address}");
+  } else {
+    dbg("dk: could not determine sender: or from: identity");
+  }
+
+  # get the signing domain name for whitelist checks
+  $scan->{dk_signing_domain} = $self->_dkmsg_signing_domain($scan, $message);
+  dbg("dk: signing domain name: ".
+    ($scan->{dk_signing_domain} ? $scan->{dk_signing_domain} : "not found"));
+
   my $timeout = $scan->{conf}->{domainkeys_timeout};
 
   my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
@@ -245,6 +355,24 @@
   }
 }
 
+# get the DK signing domain name from the Mail::DomainKeys::Message object
+sub _dkmsg_signing_domain {
+  my ($self, $scan, $message) = @_;
+  # try to use the signature() API if it exists (post-0.80)
+  if ($message->can("signature")) {
+    if (!$message->signed) {
+      return undef;
+    }
+    return $message->signature->domain;
+  } else {
+    # otherwise parse it ourself
+    if ($scan->{msg}->get_header("DomainKey-Signature") =~ /d=(\S+);/) {
+      return $1;
+    }
+    return undef;
+  }
+}
+
 sub sanitize_header_for_dk {
   my ($self, $ref) = @_;
 
@@ -310,6 +438,102 @@
   # $$ref =~ s/^\n//gs; $$ref =~ s/\n$//gs;
   $$ref =~ s/!nl;/\n/gs;
   $$ref =~ s/!ex;/!/gs;
+}
+
+sub _check_dk_whitelist {
+  my ($self, $scan, $default) = @_;
+
+  return unless $scan->is_dns_available();
+
+  # trigger a DK check so we can get address/signer info
+  # if verification failed only continue if we want the debug info
+  unless ($self->check_domainkeys_verified($scan)) {
+    unless (would_log("dbg", "dk")) {
+      return;
+    }
+  }
+
+  unless ($scan->{dk_address}) {
+    dbg("dk: ". ($default ? "def_" : "") ."whitelist_from_dk: could not find sender or from address");
+    return;
+  }
+  unless ($scan->{dk_signing_domain}) {
+    dbg("dk: ". ($default ? "def_" : "") ."whitelist_from_dk: could not find signing domain name");
+    return;
+  }
+
+  if ($default) {
+    $scan->{def_dk_whitelist_from_checked} = 1;
+    $scan->{def_dk_whitelist_from} = 0;
+
+    # copied and butchered from the code for whitelist_from_rcvd in Evaltests.pm
+    ONE: foreach my $white_addr (keys %{$scan->{conf}->{def_whitelist_from_dk}}) {
+      my $regexp = qr/$scan->{conf}->{def_whitelist_from_dk}->{$white_addr}{re}/i;
+      foreach my $domain (@{$scan->{conf}->{def_whitelist_from_dk}->{$white_addr}{domain}}) {
+        if ($scan->{dk_address} =~ $regexp) {
+	  if ($scan->{dk_signing_domain} =~ /(?:^|\.)\Q${domain}\E$/i) {
+	    dbg("dk: address: $scan->{dk_address} matches def_whitelist_from_dk ".
+		"$scan->{conf}->{def_whitelist_from_dk}->{$white_addr}{re} ${domain}");
+	    $scan->{def_dk_whitelist_from} = 1;
+	    last ONE;
+	  }
+	}
+      }
+    }
+  } else {
+    $scan->{dk_whitelist_from_checked} = 1;
+    $scan->{dk_whitelist_from} = 0;
+
+    # copied and butchered from the code for whitelist_from_rcvd in Evaltests.pm
+    ONE: foreach my $white_addr (keys %{$scan->{conf}->{whitelist_from_dk}}) {
+      my $regexp = qr/$scan->{conf}->{whitelist_from_dk}->{$white_addr}{re}/i;
+      foreach my $domain (@{$scan->{conf}->{whitelist_from_dk}->{$white_addr}{domain}}) {
+        if ($scan->{dk_address} =~ $regexp) {
+	  if ($scan->{dk_signing_domain} =~ /(?:^|\.)\Q${domain}\E$/i) {
+	    dbg("dk: address: $scan->{dk_address} matches whitelist_from_dk ".
+		"$scan->{conf}->{whitelist_from_dk}->{$white_addr}{re} ${domain}");
+	    $scan->{dk_whitelist_from} = 1;
+	    last ONE;
+	  }
+	}
+      }
+    }
+  }
+
+  # if the message doesn't pass DK validation, it can't pass a DK whitelist
+  if ($default) {
+    if ($scan->{def_dk_whitelist_from}) {
+      if ($self->check_domainkeys_verified($scan)) {
+	dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is in user's DEF_WHITELIST_FROM_DK and ".
+	  "passed DK verification");
+      } else {
+	dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is in user's DEF_WHITELIST_FROM_DK but ".
+	  "failed DK verification");
+	$scan->{def_dk_whitelist_from} = 0;
+      }
+    } else {
+      dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is not in user's DEF_WHITELIST_FROM_DK");
+    }
+  } else {
+    if ($scan->{dk_whitelist_from}) {
+      if ($self->check_domainkeys_verified($scan)) {
+	dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is in user's WHITELIST_FROM_DK and ".
+	  "passed DK verification");
+      } else {
+	dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is in user's WHITELIST_FROM_DK but ".
+	  "failed DK verification");
+	$scan->{dk_whitelist_from} = 0;
+      }
+    } else {
+      dbg("dk: address: $scan->{dk_address} signing domain name: ".
+	  "$scan->{dk_signing_domain} is not in user's WHITELIST_FROM_DK");
+    }
+  }
 }
 
 1;

Modified: spamassassin/branches/3.1/rules/50_scores.cf
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.1/rules/50_scores.cf?rev=437860&r1=437859&r2=437860&view=diff
==============================================================================
--- spamassassin/branches/3.1/rules/50_scores.cf (original)
+++ spamassassin/branches/3.1/rules/50_scores.cf Mon Aug 28 15:04:08 2006
@@ -690,6 +690,12 @@
 score ENV_AND_HDR_DKIM_MATCH -7.500
 endif # Mail::SpamAssassin::Plugin::DKIM
 
+ifplugin Mail::SpamAssassin::Plugin::DomainKeys
+score USER_IN_DK_WHITELIST -100.000
+score USER_IN_DEF_DK_WL -7.500
+score ENV_AND_HDR_DK_MATCH -7.500
+endif # Mail::SpamAssassin::Plugin::DomainKeys
+
 ifplugin Mail::SpamAssassin::Plugin::SPF
 score USER_IN_SPF_WHITELIST -100.000
 score USER_IN_DEF_SPF_WL -7.500

Added: spamassassin/branches/3.1/rules/60_whitelist_dk.cf
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.1/rules/60_whitelist_dk.cf?rev=437860&view=auto
==============================================================================
--- spamassassin/branches/3.1/rules/60_whitelist_dk.cf (added)
+++ spamassassin/branches/3.1/rules/60_whitelist_dk.cf Mon Aug 28 15:04:08 2006
@@ -0,0 +1,57 @@
+# SpamAssassin rules file: default DK whitelists
+#
+# Please don't modify this file as your changes will be overwritten with
+# the next update. Use @@LOCAL_RULES_DIR@@/local.cf instead.
+# See 'perldoc Mail::SpamAssassin::Conf' for details.
+#
+# <@LICENSE>
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at:
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+###########################################################################
+# DK whitelist rules
+
+ifplugin Mail::SpamAssassin::Plugin::DomainKeys
+
+header USER_IN_DK_WHITELIST	eval:check_for_dk_whitelist_from()
+describe USER_IN_DK_WHITELIST	From: address is in the user's DK whitelist
+tflags USER_IN_DK_WHITELIST	userconf nice noautolearn net
+
+header USER_IN_DEF_DK_WL	eval:check_for_def_dk_whitelist_from()
+describe USER_IN_DEF_DK_WL	From: address is in the default DK white-list
+tflags USER_IN_DEF_DK_WL	userconf nice noautolearn net
+
+meta ENV_AND_HDR_DK_MATCH	(USER_IN_DEF_DK_WL && __ENV_AND_HDR_FROM_MATCH)
+describe ENV_AND_HDR_DK_MATCH	Env and Hdr From used in default DK WL Match
+tflags ENV_AND_HDR_DK_MATCH	userconf nice noautolearn net
+
+###########################################################################
+# Default whitelists.  These should be addresses which send mail that is often
+# tagged (incorrectly) as spam; it also helps that they be addresses of big
+# companies with lots of lawyers, so if spammers impersonate them, they'll get
+# into big trouble, so it doesn't provide a shortcut around SpamAssassin.
+#
+# Whitelist and blacklist addresses are now file-glob-style patterns, so
+# "friend@somewhere.com", "*@isp.com", or "*.domain.net" will all work.
+#
+# Please do not add unmoderated public mailing lists here.  They are
+# too easily abused by spammers.
+
+
+# add whitelisted by default domains using DK here
+
+
+endif # Mail::SpamAssassin::Plugin::DomainKeys