You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by gb...@apache.org on 2018/03/02 13:54:03 UTC
svn commit: r1825724 - in /spamassassin/trunk: Makefile.PL
lib/Mail/SpamAssassin/Plugin/HashBL.pm rules/v342.pre
Author: gbechis
Date: Fri Mar 2 13:54:02 2018
New Revision: 1825724
URL: http://svn.apache.org/viewvc?rev=1825724&view=rev
Log:
Add HashBL (Email Blocklist (EBL), http://msbl.org/ebl.html) plugin, bz #7548
Added:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
spamassassin/trunk/rules/v342.pre
Modified:
spamassassin/trunk/Makefile.PL
Modified: spamassassin/trunk/Makefile.PL
URL: http://svn.apache.org/viewvc/spamassassin/trunk/Makefile.PL?rev=1825724&r1=1825723&r2=1825724&view=diff
==============================================================================
--- spamassassin/trunk/Makefile.PL (original)
+++ spamassassin/trunk/Makefile.PL Fri Mar 2 13:54:02 2018
@@ -1128,6 +1128,7 @@ conf__install:
$(PERL) -MFile::Copy -e "copy(q[rules/v330.pre], q{$(B_CONFDIR)/v330.pre}) unless -f q{$(B_CONFDIR)/v330.pre}"
$(PERL) -MFile::Copy -e "copy(q[rules/v340.pre], q{$(B_CONFDIR)/v340.pre}) unless -f q{$(B_CONFDIR)/v340.pre}"
$(PERL) -MFile::Copy -e "copy(q[rules/v341.pre], q{$(B_CONFDIR)/v341.pre}) unless -f q{$(B_CONFDIR)/v341.pre}"
+ $(PERL) -MFile::Copy -e "copy(q[rules/v342.pre], q{$(B_CONFDIR)/v342.pre}) unless -f q{$(B_CONFDIR)/v342.pre}"
data__install:
Added: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm?rev=1825724&view=auto
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm (added)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm Fri Mar 2 13:54:02 2018
@@ -0,0 +1,266 @@
+# Author: Steve Freegard <st...@fsl.com>
+# Copyright 2016 Steve Freegard
+#
+# <@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>
+#
+
+=head1 NAME
+
+HashBL - seearch email addresses in HashBL blocklists
+
+=head1 SYNOPSIS
+
+ loadplugin Mail::SpamAssassin::Plugin::HashBL
+ header HASHBL_EMAIL eval:check_hashbl_emails('ebl.msbl.org')
+ describe HASHBL_EMAIL Message contains email address found on EBL
+
+=head1 DESCRIPTION
+
+The Email Blocklist (EBL) contains email addresses used to receive responses to spam emails.
+These email addresses are sometimes called contact email addresses or
+drop boxes.
+The initial target of this blocklist was "Nigerian" 419 Advance Fee Fraud spam. As time passed and more types of spam that used drop boxes was identified,
+these drop boxes also were listed.
+The EBL now lists significant numbers of drop boxes used in spam sent
+by Chinese manufacturers of high-tech and light industrial products,
+SEO/web development companies, direct spam services, list sellers, and a number
+of fraudulent or outright illegal products sold by botnets.
+
+=cut
+
+package Mail::SpamAssassin::Plugin::HashBL;
+use strict;
+use warnings;
+my $VERSION = 0.001;
+
+use Mail::SpamAssassin::Plugin;
+use Mail::SpamAssassin::PerMsgStatus;
+use Mail::SpamAssassin::Util;
+use Digest::SHA qw(sha1_hex);
+use Digest::MD5 qw(md5_hex);
+
+use vars qw(@ISA $email_whitelist $skip_replyto_envfrom);
+@ISA = qw(Mail::SpamAssassin::Plugin);
+
+sub dbg { Mail::SpamAssassin::Plugin::dbg ("HashBL: @_"); }
+
+sub new {
+ my ($class, $mailsa) = @_;
+
+ $class = ref($class) || $class;
+ my $self = $class->SUPER::new($mailsa);
+ bless ($self, $class);
+
+ $self->{hashbl_available} = 1;
+ $self->set_config($mailsa->{conf});
+ $self->register_eval_rule("check_hashbl_emails");
+
+ # Need to init the regex here, utilizing registryboundaries->valid_tlds_re
+ # Some regexp tips courtesy of http://www.regular-expressions.info/email.html
+ # full email regex v0.02
+ $self->{email_regex} = qr/
+ (?=.{0,64}\@) # limit userpart to 64 chars (and speed up searching?)
+ (?<![a-z0-9!#\$%&'*+\/=?^_`{|}~-]) # start boundary
+ ( # capture email
+ [a-z0-9!#\$%&'*+\/=?^_`{|}~-]+ # no dot in beginning
+ (?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)* # no consecutive dots, no ending dot
+ \@
+ (?:[a-z0-9](?:[a-z0-9-]{0,59}[a-z0-9])?\.){1,4} # max 4x61 char parts (should be enough?)
+ $self->{main}->{registryboundaries}->{valid_tlds_re} # ends with valid tld
+ )
+ (?!(?:[a-z0-9-]|\.[a-z0-9])) # make sure domain ends here
+ /xi;
+
+ return $self;
+}
+
+sub set_config {
+ my ($self, $conf) = @_;
+ my @cmds;
+}
+
+sub parse_config {
+ my ($self, $opts) = @_;
+ return 0;
+}
+
+sub _parse_headers {
+ my ($self, $pms) = @_;
+
+ if (not defined $pms->{hashbl_email_cache}) {
+ %{$pms->{hashbl_email_cache}{'headers'}} = ();
+ }
+
+ my @headers = ('EnvelopeFrom', 'Sender', 'From', 'Reply-To');
+
+ foreach my $header (@headers) {
+ my $email = $pms->get($header . ':addr');
+ if ($email) {
+ dbg("Found email $email in header $header");
+ $pms->{hashbl_email_cache}{'headers'}{$email} = 1;
+ }
+ }
+
+ return 1;
+}
+
+sub _parse_body {
+ my ($self, $pms) = @_;
+
+ # Parse body
+ if (not defined $pms->{hashbl_email_cache}) {
+ %{$pms->{hashbl_email_cache}{'body'}} = ();
+ }
+
+ my %seen;
+ my @body_emails;
+ # get all <a href="mailto:", since they don't show up on stripped_body
+ my $parsed = $pms->get_uri_detail_list();
+ while (my($uri, $info) = each %{$parsed}) {
+ if (defined $info->{types}->{a} and not defined $info->{types}->{parsed}) {
+ if ($uri =~ /^(?:(?i)mailto):$self->{email_regex}/) {
+ my $email = lc($1);
+ push(@body_emails, $email) unless defined $seen{$email};
+ $seen{$email} = 1;
+ last if scalar @body_emails >= 20; # sanity
+ }
+ }
+ }
+ # scan stripped normalized body
+ # have to do this way since get_uri_detail_list doesn't know what mails are inside <>
+ my $body = $pms->get_decoded_stripped_body_text_array();
+ BODY: foreach (@$body) {
+ # strip urls with possible emails inside
+ s#<?https?://\S{0,255}(?:\@|%40)\S{0,255}# #gi;
+ # strip emails contained in <>, not mailto:
+ # also strip ones followed by quote-like "wrote:" (but not fax: and tel: etc)
+ s#<?(?<!mailto:)$self->{email_regex}(?:>|\s{1,10}(?!(?:fa(?:x|csi)|tel|phone|e?-?mail))[a-z]{2,11}:)# #gi;
+ while (/$self->{email_regex}/g) {
+ my $email = lc($1);
+ push(@body_emails, $email) unless defined $seen{$email};
+ $seen{$email} = 1;
+ last BODY if scalar @body_emails >= 40; # sanity
+ }
+ }
+ foreach my $email (@body_emails) {
+ dbg("Found email $email in body");
+ $pms->{hashbl_email_cache}{'body'}{$email} = 1;
+ }
+
+ return 1;
+}
+
+sub _got_hit {
+ my ($self, $pms, $rulename, $email, $desc) = @_;
+
+ if (defined $pms->{conf}->{descriptions}->{$rulename}) {
+ $desc = $pms->{conf}->{descriptions}->{$rulename};
+ }
+
+ $email =~ s/\@/[at]/g;
+ $pms->got_hit($rulename, "", description => $desc." ($email)", ruletype => 'eval');
+}
+
+sub _submit_email_query {
+ my ($self, $pms, $list, $type, $email) = @_;
+ my $rulename = $pms->get_current_eval_rule_name();
+ my ($hash, $lookup, $key);
+ if (uc($type) eq 'SHA1') {
+ $hash = sha1_hex($email);
+ }
+ elsif (uc($type) eq 'MD5') {
+ $hash = md5_hex($email);
+ }
+ $lookup = "$hash.$list.";
+ my $obj = { email => $email };
+ dbg("list: $list, type: $type, email: $email, hash: $hash, lookup: $lookup");
+ $key = "HASHBL_EMAIL:$lookup";
+ my $ent = {
+ key => $key,
+ zone => $list,
+ obj => $obj,
+ type => 'HASHBL',
+ rulename => $rulename,
+ };
+
+ $ent = $pms->{async}->bgsend_and_start_lookup($lookup, 'A', undef, $ent, sub {
+ my ($ent2, $pkt) = @_;
+ $self->_finish_email_lookup($pms, $ent2, $pkt);
+ }, master_deadline => $pms->{master_deadline} );
+
+ return $ent;
+}
+
+sub _finish_email_lookup {
+ my ($self, $pms, $ent, $pkt) = @_;
+
+ if (!$pkt) {
+ # $pkt will be undef if the DNS query was aborted (e.g. timed out)
+ dbg("_finish_email_lookup aborted: ",
+ $ent->{rulename}, $ent->{key});
+ return;
+ }
+
+ my $email = $ent->{obj}->{email};
+
+ dbg("_finish_email_lookup: ", $ent->{rulename}, $ent->{key}, $email);
+
+ my @answer = $pkt->answer;
+ foreach my $rr (@answer) {
+ if ($rr->address =~ /^127\./) {
+ $self->_got_hit($pms, $ent->{rulename}, $email);
+ $pms->register_async_rule_finish($ent->{rulename});
+ }
+ }
+}
+
+sub check_hashbl_emails {
+ my ($self, $pms, $list, $type) = @_;
+
+ return 0 unless $self->{hashbl_available};
+
+ my $rulename = $pms->get_current_eval_rule_name();
+
+ # First we lookup all unique email addresses found in the headers
+ return 0 unless $self->_parse_headers($pms);
+ foreach my $email (keys %{$pms->{hashbl_email_cache}{'headers'}}) {
+ # Remove this from the body hash
+ delete $pms->{hashbl_email_cache}{'body'}{$email};
+ dbg("HEADER: $email");
+ $self->_submit_email_query($pms, $list, (($type) ? $type : 'SHA1'), $email);
+ }
+
+ # Check any e-mail addresses found in the message body
+ return 0 unless $self->_parse_body($pms);
+
+ my (@emails) = keys %{$pms->{hashbl_email_cache}{'body'}};
+
+ # Randomize order and truncate the array to 10 items maximum
+ Mail::SpamAssassin::Util::fisher_yates_shuffle(\@emails);
+ $#emails = 9 if (scalar @emails > 10);
+
+ foreach my $email (@emails) {
+ #$self->_got_hit($pms, $email, "Email found in list $list");
+ dbg("BODY: $email");
+ $self->_submit_email_query($pms, $list, (($type) ? $type : 'SHA1'), $email);
+ }
+
+ return 0;
+}
+
+1;
Added: spamassassin/trunk/rules/v342.pre
URL: http://svn.apache.org/viewvc/spamassassin/trunk/rules/v342.pre?rev=1825724&view=auto
==============================================================================
--- spamassassin/trunk/rules/v342.pre (added)
+++ spamassassin/trunk/rules/v342.pre Fri Mar 2 13:54:02 2018
@@ -0,0 +1,20 @@
+# This is the right place to customize your installation of SpamAssassin.
+#
+# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be
+# tweaked.
+#
+# This file was installed during the installation of SpamAssassin 3.4.1,
+# and contains plugin loading commands for the new plugins added in that
+# release. It will not be overwritten during future SpamAssassin installs,
+# so you can modify it to enable some disabled-by-default plugins below,
+# if you so wish.
+#
+# There are now multiple files read to enable plugins in the
+# /etc/mail/spamassassin directory; previously only one, "init.pre" was
+# read. Now both "init.pre", "v310.pre", and any other files ending in
+# ".pre" will be read. As future releases are made, new plugins will be
+# added to new files, named according to the release they're added in.
+###########################################################################
+
+# HashBL - Use EBL email blocklist
+# loadplugin Mail::SpamAssassin::Plugin::HashBL