From 0c036fae5d82235f9b95e8be165f92cdf1a6a9ea Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 18 May 2021 02:47:01 -0400 Subject: [PATCH] import spamassassin-3.4.4-3.el8 --- .gitignore | 4 +- .spamassassin.metadata | 4 +- .../0001-Drop-the-ResourceLimits-plugin.patch | 20 +- SOURCES/spamassassin-3.4.2-drop-geoip.patch | 477 ++- ...pamassassin-3.4.2-fix-CVE-2018-11805.patch | 2881 ----------------- ...pamassassin-3.4.2-fix-CVE-2019-12420.patch | 25 - ...spamassassin-3.4.2-fix-CVE-2020-1930.patch | 41 - ...spamassassin-3.4.2-fix-CVE-2020-1931.patch | 44 - ...assassin-3.4.2-fix-file-handle-leaks.patch | 18 - ....4.2-fix-rawbody-rules-documentation.patch | 63 - ...pamassassin-3.4.2-fix-use-after-free.patch | 10 - SOURCES/spamassassin-cert_update.patch | 108 + ...in-disable_tests_for_deleted_modules.patch | 40 + SOURCES/spamassassin-sql92_syntax.patch | 354 ++ SPECS/spamassassin.spec | 75 +- 15 files changed, 899 insertions(+), 3265 deletions(-) delete mode 100644 SOURCES/spamassassin-3.4.2-fix-CVE-2018-11805.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-CVE-2019-12420.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-CVE-2020-1930.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-CVE-2020-1931.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-file-handle-leaks.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-rawbody-rules-documentation.patch delete mode 100644 SOURCES/spamassassin-3.4.2-fix-use-after-free.patch create mode 100644 SOURCES/spamassassin-cert_update.patch create mode 100644 SOURCES/spamassassin-disable_tests_for_deleted_modules.patch create mode 100644 SOURCES/spamassassin-sql92_syntax.patch diff --git a/.gitignore b/.gitignore index 26e4048..bc510d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -SOURCES/Mail-SpamAssassin-3.4.2.tar.bz2 -SOURCES/Mail-SpamAssassin-rules-3.4.2.r1840640.tgz +SOURCES/Mail-SpamAssassin-3.4.4.tar.bz2 +SOURCES/Mail-SpamAssassin-rules-3.4.4.r1873061.tgz diff --git a/.spamassassin.metadata b/.spamassassin.metadata index a90446a..cd26fa1 100644 --- a/.spamassassin.metadata +++ b/.spamassassin.metadata @@ -1,2 +1,2 @@ -9d3128dfe943be5e429edbd77883179138f40536 SOURCES/Mail-SpamAssassin-3.4.2.tar.bz2 -c93006e1572297f816a0e186a98cbbae246a4945 SOURCES/Mail-SpamAssassin-rules-3.4.2.r1840640.tgz +23ab1edb89570c0367c117cbaf1e816c0dc1fb90 SOURCES/Mail-SpamAssassin-3.4.4.tar.bz2 +ee022f1cc18a66d49c1954936c70c36049c37dcf SOURCES/Mail-SpamAssassin-rules-3.4.4.r1873061.tgz diff --git a/SOURCES/0001-Drop-the-ResourceLimits-plugin.patch b/SOURCES/0001-Drop-the-ResourceLimits-plugin.patch index 1736adf..61798f4 100644 --- a/SOURCES/0001-Drop-the-ResourceLimits-plugin.patch +++ b/SOURCES/0001-Drop-the-ResourceLimits-plugin.patch @@ -1,8 +1,8 @@ diff --git a/MANIFEST b/MANIFEST -index 95e2af5..d1aa471 100644 +index c4aca1b..5ba3a0c 100644 --- a/MANIFEST +++ b/MANIFEST -@@ -100,7 +100,6 @@ lib/Mail/SpamAssassin/Plugin/Pyzor.pm +@@ -101,7 +101,6 @@ lib/Mail/SpamAssassin/Plugin/Pyzor.pm lib/Mail/SpamAssassin/Plugin/Razor2.pm lib/Mail/SpamAssassin/Plugin/RelayCountry.pm lib/Mail/SpamAssassin/Plugin/RelayEval.pm @@ -11,10 +11,10 @@ index 95e2af5..d1aa471 100644 lib/Mail/SpamAssassin/Plugin/Reuse.pm lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm diff --git a/lib/Mail/SpamAssassin/Util/DependencyInfo.pm b/lib/Mail/SpamAssassin/Util/DependencyInfo.pm -index 7467744..3d92c7d 100644 +index eca12e1..9a6b4fb 100644 --- a/lib/Mail/SpamAssassin/Util/DependencyInfo.pm +++ b/lib/Mail/SpamAssassin/Util/DependencyInfo.pm -@@ -257,12 +257,6 @@ our @OPTIONAL_MODULES = ( +@@ -233,12 +233,6 @@ our @OPTIONAL_MODULES = ( check for both Net::DNS and Net::DNS::Nameserver. However, Net::DNS::Nameserver is only used in make test as of June 2014.', }, @@ -24,21 +24,21 @@ index 7467744..3d92c7d 100644 - desc => 'BSD::Resource provides BSD process resource limit and priority - functions. It is used by the optional ResourceLimits Plugin.', -}, - ); - - our @BINARIES = (); + { + module => 'Archive::Zip', + version => 0, diff --git a/rules/v342.pre b/rules/v342.pre -index 4ab7736..9b425fd 100644 +index 8e0fb07..c4758e9 100644 --- a/rules/v342.pre +++ b/rules/v342.pre @@ -19,10 +19,6 @@ - # HashBL - Use EBL email blocklist + # HashBL - Query hashed/unhashed strings, emails, uris etc from DNS lists # loadplugin Mail::SpamAssassin::Plugin::HashBL -# ResourceLimits - assure your spamd child processes -# do not exceed specified CPU or memory limit -# loadplugin Mail::SpamAssassin::Plugin::ResourceLimits - - # FromNameSpoof - help stop spam that tries to spoof other domains using # the from name + # loadplugin Mail::SpamAssassin::Plugin::FromNameSpoof diff --git a/SOURCES/spamassassin-3.4.2-drop-geoip.patch b/SOURCES/spamassassin-3.4.2-drop-geoip.patch index 5fb0ad0..74ceeab 100644 --- a/SOURCES/spamassassin-3.4.2-drop-geoip.patch +++ b/SOURCES/spamassassin-3.4.2-drop-geoip.patch @@ -1,20 +1,8 @@ -From cab335f2c6b096f847402c35ee896a3f3d82958c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= -Date: Mon, 23 Jul 2018 13:28:39 +0200 -Subject: [PATCH] Dropped GeoIP and optional plugins requiring it -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Namely URILocalBL and RelayCountry. - -Signed-off-by: Jaroslav Škarvada - diff --git a/MANIFEST b/MANIFEST -index 2b1b291..a6bbcdb 100644 +index a988fa1..c4aca1b 100644 --- a/MANIFEST +++ b/MANIFEST -@@ -117,7 +117,6 @@ lib/Mail/SpamAssassin/Plugin/VBounce.pm +@@ -118,7 +118,6 @@ lib/Mail/SpamAssassin/Plugin/VBounce.pm lib/Mail/SpamAssassin/Plugin/WLBLEval.pm lib/Mail/SpamAssassin/Plugin/WhiteListSubject.pm lib/Mail/SpamAssassin/PluginHandler.pm @@ -24,10 +12,10 @@ index 2b1b291..a6bbcdb 100644 lib/Mail/SpamAssassin/SQLBasedAddrList.pm diff --git a/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm b/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm deleted file mode 100644 -index 2e172f3..0000000 +index 38ec1e3..0000000 --- a/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm +++ /dev/null -@@ -1,280 +0,0 @@ +@@ -1,407 +0,0 @@ -# <@LICENSE> -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with @@ -57,8 +45,29 @@ index 2e172f3..0000000 - -The RelayCountry plugin attempts to determine the domain country codes -of each relay used in the delivery path of messages and add that information --to the message metadata as "X-Relay-Countries", or the C<_RELAYCOUNTRY_> --header markup. +-to the message metadata. +- +-Following metadata headers and tags are added: +- +- X-Relay-Countries _RELAYCOUNTRY_ +- All untrusted relays. Contains all relays starting from the +- trusted_networks border. This method has been used by default since +- early SA versions. +- +- X-Relay-Countries-External _RELAYCOUNTRYEXT_ +- All external relays. Contains all relays starting from the +- internal_networks border. Could be useful in some cases when +- trusted/msa_networks extend beyond the internal border and those +- need to be checked too. +- +- X-Relay-Countries-All _RELAYCOUNTRYALL_ +- All possible relays (internal + external). +- +- X-Relay-Countries-Auth _RELAYCOUNTRYAUTH_ +- Auth will contain all relays starting from the first relay that used +- authentication. For example, this could be used to check for hacked +- local users coming in from unexpected countries. If there are no +- authenticated relays, this will be empty. - -=head1 REQUIREMENT - @@ -81,11 +90,6 @@ index 2e172f3..0000000 - -our @ISA = qw(Mail::SpamAssassin::Plugin); - --my $db; --my $dbv6; --my $db_info; # will hold database info --my $db_type; # will hold database type -- -# constructor: register the eval rule -sub new { - my $class = shift; @@ -127,10 +131,9 @@ index 2e172f3..0000000 - type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, - code => sub { - my ($self, $key, $value, $line) = @_; -- if ( $value !~ /GeoIP|GeoIP2|DB_File|Fast/) { +- if ($value !~ /^(?:GeoIP|GeoIP2|DB_File|Fast)$/) { - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } -- - $self->{country_db_type} = $value; - } - }); @@ -141,6 +144,15 @@ index 2e172f3..0000000 - -This option tells SpamAssassin where to find MaxMind GeoIP2 or IP::Country::DB_File database. - +-If not defined, GeoIP2 default search includes: +- /usr/local/share/GeoIP/GeoIP2-Country.mmdb +- /usr/share/GeoIP/GeoIP2-Country.mmdb +- /var/lib/GeoIP/GeoIP2-Country.mmdb +- /usr/local/share/GeoIP/GeoLite2-Country.mmdb +- /usr/share/GeoIP/GeoLite2-Country.mmdb +- /var/lib/GeoIP/GeoLite2-Country.mmdb +- (and same paths again for -City.mmdb, which also has country functionality) +- -=back - -=cut @@ -154,84 +166,186 @@ index 2e172f3..0000000 - if (!defined $value || !length $value) { - return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; - } -- if (!-f $value) { +- if (!-e $value) { - info("config: country_db_path \"$value\" is not accessible"); - $self->{country_db_path} = $value; - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } -- - $self->{country_db_path} = $value; - } - }); +- +- push (@cmds, { +- setting => 'geoip2_default_db_path', +- default => [ +- '/usr/local/share/GeoIP/GeoIP2-Country.mmdb', +- '/usr/share/GeoIP/GeoIP2-Country.mmdb', +- '/var/lib/GeoIP/GeoIP2-Country.mmdb', +- '/usr/local/share/GeoIP/GeoLite2-Country.mmdb', +- '/usr/share/GeoIP/GeoLite2-Country.mmdb', +- '/var/lib/GeoIP/GeoLite2-Country.mmdb', +- '/usr/local/share/GeoIP/GeoIP2-City.mmdb', +- '/usr/share/GeoIP/GeoIP2-City.mmdb', +- '/var/lib/GeoIP/GeoIP2-City.mmdb', +- '/usr/local/share/GeoIP/GeoLite2-City.mmdb', +- '/usr/share/GeoIP/GeoLite2-City.mmdb', +- '/var/lib/GeoIP/GeoLite2-City.mmdb', +- ], +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST, +- code => sub { +- my ($self, $key, $value, $line) = @_; +- if ($value eq '') { +- return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; +- } +- push(@{$self->{geoip2_default_db_path}}, split(/\s+/, $value)); +- } +- }); - - $conf->{parser}->register_commands(\@cmds); -} - +-sub get_country { +- my ($self, $ip, $db, $dbv6, $country_db_type) = @_; +- my $cc; +- my $IP_PRIVATE = IP_PRIVATE; +- my $IPV4_ADDRESS = IPV4_ADDRESS; +- +- # Private IPs will always be returned as '**' +- if ($ip =~ /^$IP_PRIVATE$/o) { +- $cc = "**"; +- } +- elsif ($country_db_type eq "GeoIP") { +- if ($ip =~ /^$IPV4_ADDRESS$/o) { +- $cc = $db->country_code_by_addr($ip); +- } elsif (defined $dbv6) { +- $cc = $dbv6->country_code_by_addr_v6($ip); +- } +- } +- elsif ($country_db_type eq "GeoIP2") { +- my ($country, $country_rec); +- eval { +- if (index($db->metadata()->description()->{en}, 'City') != -1) { +- $country = $db->city( ip => $ip ); +- } else { +- $country = $db->country( ip => $ip ); +- } +- $country_rec = $country->country(); +- $cc = $country_rec->iso_code(); +- 1; +- } or do { +- $@ =~ s/\s+Trace begun.*//s; +- dbg("metadata: RelayCountry: GeoIP2 failed: $@"); +- } +- } +- elsif ($country_db_type eq "DB_File") { +- if ($ip =~ /^$IPV4_ADDRESS$/o ) { +- $cc = $db->inet_atocc($ip); +- } else { +- $cc = $db->inet6_atocc($ip); +- } +- } +- elsif ($country_db_type eq "Fast") { +- $cc = $db->inet_atocc($ip); +- } +- +- $cc ||= 'XX'; +- +- return $cc; +-} +- -sub extract_metadata { - my ($self, $opts) = @_; -- my $geo; -- my $cc; +- my $pms = $opts->{permsgstatus}; - -- my $conf_country_db_type = $self->{'main'}{'resolver'}{'conf'}->{country_db_type}; -- my $conf_country_db_path = $self->{'main'}{'resolver'}{'conf'}->{country_db_path}; +- my $db; +- my $dbv6; +- my $db_info; # will hold database info +- my $db_type; # will hold database type - -- if ( $conf_country_db_type eq "GeoIP") { +- my $country_db_type = $opts->{conf}->{country_db_type}; +- my $country_db_path = $opts->{conf}->{country_db_path}; +- +- if ($country_db_type eq "GeoIP") { - eval { - require Geo::IP; - $db = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION, Geo::IP->GEOIP_STANDARD); - die "GeoIP.dat not found" unless $db; - # IPv6 requires version Geo::IP 1.39+ with GeoIP C API 1.4.7+ - if (Geo::IP->VERSION >= 1.39 && Geo::IP->api eq 'CAPI') { -- $dbv6 = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION_V6, Geo::IP->GEOIP_STANDARD); -- if (!$dbv6) { -- dbg("metadata: RelayCountry: IPv6 support not enabled, GeoIPv6.dat not found"); -- } -- $db_info = sub { return "Geo::IP " . ($db->database_info || '?') }; +- $dbv6 = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION_V6, Geo::IP->GEOIP_STANDARD); +- if (!$dbv6) { +- dbg("metadata: RelayCountry: GeoIP: IPv6 support not enabled, GeoIPv6.dat not found"); +- } - } else { -- dbg("metadata: RelayCountry: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required"); +- dbg("metadata: RelayCountry: GeoIP: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required"); +- } +- $db_info = sub { return "Geo::IP IPv4: " . ($db->database_info || '?')." / IPv6: ".($dbv6 ? $dbv6->database_info || '?' : '?') }; +- 1; +- } or do { +- # Fallback to IP::Country::Fast +- dbg("metadata: RelayCountry: GeoIP: GeoIP.dat not found, trying IP::Country::Fast as fallback"); +- $country_db_type = "Fast"; +- } +- } +- elsif ($country_db_type eq "GeoIP2") { +- if (!$country_db_path) { +- # Try some default locations +- foreach (@{$opts->{conf}->{geoip2_default_db_path}}) { +- if (-f $_) { +- $country_db_path = $_; +- last; +- } +- } +- } +- if (-f $country_db_path) { +- eval { +- require GeoIP2::Database::Reader; +- $db = GeoIP2::Database::Reader->new( +- file => $country_db_path, +- locales => [ 'en' ] +- ); +- die "unknown error" unless $db; +- $db_info = sub { +- my $m = $db->metadata(); +- return "GeoIP2 ".$m->description()->{en}." / ".localtime($m->build_epoch()); +- }; +- 1; +- } or do { +- # Fallback to IP::Country::Fast +- $@ =~ s/\s+Trace begun.*//s; +- dbg("metadata: RelayCountry: GeoIP2: ${country_db_path} load failed: $@, trying IP::Country::Fast as fallback"); +- $country_db_type = "Fast"; - } -- } or do { -- # Fallback to IP::Country::Fast -- dbg("metadata: RelayCountry: GeoIP.dat not found, IP::Country::Fast enabled as fallback"); -- $conf_country_db_type = "Fast"; -- } -- } elsif ( $conf_country_db_type eq "GeoIP2" ) { -- if ( -f $conf_country_db_path ) { -- eval { -- require GeoIP2::Database::Reader; -- $db = GeoIP2::Database::Reader->new( -- file => $conf_country_db_path, -- locales => [ 'en' ] -- ); -- die "${conf_country_db_path} not found" unless $db; -- $db_info = sub { return "GeoIP2 " . ($db->metadata()->description()->{en} || '?') }; -- 1; -- } or do { -- # Fallback to IP::Country::Fast -- dbg("metadata: RelayCountry: ${conf_country_db_path} not found, IP::Country::Fast enabled as fallback"); -- $conf_country_db_type = "Fast"; -- } -- } else { -- # Fallback to IP::Country::Fast -- dbg("metadata: RelayCountry: ${conf_country_db_path} not found, IP::Country::Fast enabled as fallback"); -- $conf_country_db_type = "Fast"; -- } -- } elsif ( $conf_country_db_type eq "DB_File") { -- if ( -f $conf_country_db_path ) { -- require IP::Country::DB_File; -- $db = IP::Country::DB_File->new($conf_country_db_path); -- die "Country db not found, please see build_ipcc.pl(1)" unless $db; -- $db_info = sub { return "IP::Country::DB_File ".localtime($db->db_time()); }; -- 1; - } else { - # Fallback to IP::Country::Fast -- dbg("metadata: RelayCountry: ${conf_country_db_path} not found, IP::Country::Fast enabled as fallback"); -- $conf_country_db_type = "Fast"; +- my $err = $country_db_path ? +- "$country_db_path not found" : "database not found from default locations"; +- dbg("metadata: RelayCountry: GeoIP2: $err, trying IP::Country::Fast as fallback"); +- $country_db_type = "Fast"; +- } +- } +- elsif ($country_db_type eq "DB_File") { +- if (-f $country_db_path) { +- eval { +- require IP::Country::DB_File; +- $db = IP::Country::DB_File->new($country_db_path); +- die "unknown error" unless $db; +- $db_info = sub { return "IP::Country::DB_File ".localtime($db->db_time()); }; +- 1; +- } or do { +- # Fallback to IP::Country::Fast +- dbg("metadata: RelayCountry: DB_File: ${country_db_path} load failed: $@, trying IP::Country::Fast as fallback"); +- $country_db_type = "Fast"; +- } +- } else { +- # Fallback to IP::Country::Fast +- dbg("metadata: RelayCountry: DB_File: ${country_db_path} not found, trying IP::Country::Fast as fallback"); +- $country_db_type = "Fast"; - } - } -- if( $conf_country_db_type eq "Fast") { +- +- if ($country_db_type eq "Fast") { - my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; -- # Try IP::Country::Fast as backup - eval { - require IP::Country::Fast; - $db = IP::Country::Fast->new(); @@ -241,79 +355,80 @@ index 2e172f3..0000000 - my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; - dbg("metadata: RelayCountry: failed to load 'IP::Country::Fast', skipping: $eval_stat"); - return 1; -- }; -- }; +- } +- } - -- return 1 unless $db; +- if (!$db) { +- return 1; +- } - - dbg("metadata: RelayCountry: Using database: ".$db_info->()); - my $msg = $opts->{msg}; - -- my $countries = ''; -- my $IP_PRIVATE = IP_PRIVATE; -- my $IPV4_ADDRESS = IPV4_ADDRESS; +- my @cc_untrusted; - foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) { - my $ip = $relay->{ip}; -- # Private IPs will always be returned as '**' -- if ( $conf_country_db_type eq "GeoIP" ) { -- if ( $ip !~ /^$IPV4_ADDRESS$/o ) { -- if ( defined $dbv6 ) { -- $geo = $dbv6->country_code_by_addr_v6($ip) || "XX"; -- } else { -- $geo = "XX"; -- } -- } else { -- $geo = $db->country_code_by_addr($ip) || "XX"; -- } -- } elsif ($conf_country_db_type eq "GeoIP2" ) { -- if ( $ip !~ /^$IP_PRIVATE$/o ) { -- my $country = $db->country( ip => $ip ); -- my $country_rec = $country->country(); -- $geo = $country_rec->iso_code() || "XX"; -- $cc = $ip =~ /^$IP_PRIVATE$/o ? '**' : $geo; -- } else { -- $cc = '**'; -- } -- } elsif ( $conf_country_db_type eq "DB_File" ) { -- if ( $ip !~ /^$IPV4_ADDRESS$/o ) { -- $geo = $db->inet6_atocc($ip) || "XX"; -- } else { -- $geo = $db->inet_atocc($ip) || "XX"; -- } -- } elsif ( $conf_country_db_type eq "Fast" ) { -- $geo = $db->inet_atocc($ip) || "XX"; -- } -- $cc = $ip =~ /^$IP_PRIVATE$/o ? '**' : $geo; -- $countries .= $cc." "; +- my $cc = $self->get_country($ip, $db, $dbv6, $country_db_type); +- push @cc_untrusted, $cc; - } - -- chop $countries; -- $msg->put_metadata("X-Relay-Countries", $countries); -- dbg("metadata: X-Relay-Countries: $countries"); +- my @cc_external; +- foreach my $relay (@{$msg->{metadata}->{relays_external}}) { +- my $ip = $relay->{ip}; +- my $cc = $self->get_country($ip, $db, $dbv6, $country_db_type); +- push @cc_external, $cc; +- } - -- return 1; --} +- my @cc_auth; +- my $found_auth; +- foreach my $relay (@{$msg->{metadata}->{relays_trusted}}) { +- if ($relay->{auth}) { +- $found_auth = 1; +- } +- if ($found_auth) { +- my $ip = $relay->{ip}; +- my $cc = $self->get_country($ip, $db, $dbv6, $country_db_type); +- push @cc_auth, $cc; +- } +- } - --sub parsed_metadata { -- my ($self, $opts) = @_; +- my @cc_all; +- foreach my $relay (@{$msg->{metadata}->{relays_internal}}, @{$msg->{metadata}->{relays_external}}) { +- my $ip = $relay->{ip}; +- my $cc = $self->get_country($ip, $db, $dbv6, $country_db_type); +- push @cc_all, $cc; +- } - -- return 1 unless $db; +- my $ccstr = join(' ', @cc_untrusted); +- $msg->put_metadata("X-Relay-Countries", $ccstr); +- dbg("metadata: X-Relay-Countries: $ccstr"); +- $pms->set_tag("RELAYCOUNTRY", @cc_untrusted == 1 ? $cc_untrusted[0] : \@cc_untrusted); +- +- $ccstr = join(' ', @cc_external); +- $msg->put_metadata("X-Relay-Countries-External", $ccstr); +- dbg("metadata: X-Relay-Countries-External: $ccstr"); +- $pms->set_tag("RELAYCOUNTRYEXT", @cc_external == 1 ? $cc_external[0] : \@cc_external); +- +- $ccstr = join(' ', @cc_auth); +- $msg->put_metadata("X-Relay-Countries-Auth", $ccstr); +- dbg("metadata: X-Relay-Countries-Auth: $ccstr"); +- $pms->set_tag("RELAYCOUNTRYAUTH", @cc_auth == 1 ? $cc_auth[0] : \@cc_auth); +- +- $ccstr = join(' ', @cc_all); +- $msg->put_metadata("X-Relay-Countries-All", $ccstr); +- dbg("metadata: X-Relay-Countries-All: $ccstr"); +- $pms->set_tag("RELAYCOUNTRYALL", @cc_all == 1 ? $cc_all[0] : \@cc_all); - -- my $countries = -- $opts->{permsgstatus}->get_message->get_metadata('X-Relay-Countries'); -- my @c_list = split(' ', $countries); -- $opts->{permsgstatus}->set_tag("RELAYCOUNTRY", -- @c_list == 1 ? $c_list[0] : \@c_list); - return 1; -} - -1; diff --git a/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm b/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm deleted file mode 100644 -index e190fab..0000000 +index 4fbbcb7..0000000 --- a/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm +++ /dev/null -@@ -1,658 +0,0 @@ +@@ -1,705 +0,0 @@ -# <@LICENSE> -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with @@ -417,9 +532,9 @@ index e190fab..0000000 -package Mail::SpamAssassin::Plugin::URILocalBL; -use Mail::SpamAssassin::Plugin; -use Mail::SpamAssassin::Logger; +-use Mail::SpamAssassin::Constants qw(:ip); -use Mail::SpamAssassin::Util qw(untaint_var); - --use Net::CIDR::Lite; -use Socket; - -use strict; @@ -432,6 +547,7 @@ index e190fab..0000000 - -use constant HAS_GEOIP => eval { require Geo::IP; }; -use constant HAS_GEOIP2 => eval { require GeoIP2::Database::Reader; }; +-use constant HAS_CIDR => eval { require Net::CIDR::Lite; }; - -# constructor -sub new { @@ -463,6 +579,7 @@ index e190fab..0000000 - - push (@cmds, { - setting => 'uri_block_cc', +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; @@ -504,6 +621,7 @@ index e190fab..0000000 - - push (@cmds, { - setting => 'uri_block_cont', +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; @@ -545,6 +663,7 @@ index e190fab..0000000 - - push (@cmds, { - setting => 'uri_block_isp', +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; @@ -584,10 +703,16 @@ index e190fab..0000000 - - push (@cmds, { - setting => 'uri_block_cidr', +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; - +- if (!HAS_CIDR) { +- warn "config: uri_block_cidr not supported, required module Net::CIDR::Lite missing\n"; +- return $Mail::SpamAssassin::Conf::INVALID_VALUE; +- } +- - if ($value !~ /^(\S+)\s+(.+)$/) { - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } @@ -629,6 +754,7 @@ index e190fab..0000000 - - push (@cmds, { - setting => 'uri_block_exclude', +- type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; @@ -673,7 +799,7 @@ index e190fab..0000000 -=item uri_country_db_path STRING - -This option tells SpamAssassin where to find the MaxMind country GeoIP2 --database. +-database. Country or City database are both supported. - -=back - @@ -745,6 +871,7 @@ index e190fab..0000000 - # If country_db_path is set I am using GeoIP2 api - if ( HAS_GEOIP2 and ( ( defined $conf_country_db_path ) or ( defined $conf_country_db_isp_path ) ) ) { - +- eval { - $self->{geoip} = GeoIP2::Database::Reader->new( - file => $conf_country_db_path, - locales => [ 'en' ] @@ -762,6 +889,13 @@ index e190fab..0000000 - warn "$conf_country_db_isp_path not found" unless $self->{geoisp}; - } - $self->{use_geoip2} = 1; +- }; +- if ($@ || !($self->{geoip} || $self->{geoisp})) { +- $@ =~ s/\s+Trace begun.*//s; +- warn "URILocalBL: GeoIP2 load failed: $@\n"; +- return 0; +- } +- - } elsif ( HAS_GEOIP ) { - BEGIN { - Geo::IP->import( qw(GEOIP_MEMORY_CACHE GEOIP_CHECK_CACHE GEOIP_ISP_EDITION) ); @@ -775,20 +909,32 @@ index e190fab..0000000 - - # this code burps an ugly message if it fails, but that's redirected elsewhere - my $flags = 0; -- eval '$flags = Geo::IP::GEOIP_SILENCE' if ($gip_wanted >= $gip_have); +- my $flag_isp = 0; +- my $flag_silent = 0; +- eval '$flags = GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE' if ($gip_wanted >= $gip_have); +- eval '$flag_silent = Geo::IP::GEOIP_SILENCE' if ($gip_wanted >= $gip_have); +- eval '$flag_isp = GEOIP_ISP_EDITION' if ($gip_wanted >= $gip_have); - -- if ($flags && $gic_wanted >= $gic_have) { -- $self->{geoip} = Geo::IP->new(GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE | $flags); -- $self->{geoisp} = Geo::IP->open_type(GEOIP_ISP_EDITION, GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE | $flags); +- eval { +- if ($flag_silent && $gic_wanted >= $gic_have) { +- $self->{geoip} = Geo::IP->new($flags | $flag_silent); +- $self->{geoisp} = Geo::IP->open_type($flag_isp | $flag_silent | $flags); - } else { - open(OLDERR, ">&STDERR"); - open(STDERR, ">", "/dev/null"); -- $self->{geoip} = Geo::IP->new(GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE); -- $self->{geoisp} = Geo::IP->open_type(GEOIP_ISP_EDITION, GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE); +- $self->{geoip} = Geo::IP->new($flags); +- $self->{geoisp} = Geo::IP->open_type($flag_isp); - open(STDERR, ">&OLDERR"); - close(OLDERR); - } -- $db_info = sub { return "Geo::IP " . ($self->{geoip}->database_info || '?') }; +- }; +- if ($@ || !($self->{geoip} || $self->{geoisp})) { +- $@ =~ s/\s+Trace begun.*//s; +- warn "URILocalBL: GeoIP load failed: $@\n"; +- return 0; +- } +- +- $db_info = sub { return "Geo::IP " . ($self->{geoip}->database_info || '?') }; - } else { - dbg("No GeoIP module available"); - return 0; @@ -800,6 +946,8 @@ index e190fab..0000000 - - my %hit_tests; - my $got_hit = 0; +- my @addrs; +- my $IP_ADDRESS = IP_ADDRESS; - - if ( defined $self->{geoip} ) { - dbg("check: uri_local_bl evaluating rule %s using database %s\n", $test, $db_info->()); @@ -807,12 +955,14 @@ index e190fab..0000000 - dbg("check: uri_local_bl evaluating rule %s\n", $test); - } - +- my $dns_available = $permsg->is_dns_available(); +- - while (my ($raw, $info) = each %uri_detail) { - - next unless $info->{hosts}; - - # look for W3 links only -- next unless (defined $info->{types}->{a}); +- next unless (defined $info->{types}->{a} || defined $info->{types}->{parsed}); - - while (my($host, $domain) = each %{$info->{hosts}}) { - @@ -822,11 +972,18 @@ index e190fab..0000000 - next; - } - -- # this would be best cached from prior lookups -- my @addrs = gethostbyname($host); -- -- # convert to string values address list -- @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs]; +- if($host !~ /^$IP_ADDRESS$/) { +- if (!$dns_available) { +- dbg("check: uri_local_bl skipping $host, dns not available"); +- next; +- } +- # this would be best cached from prior lookups +- @addrs = gethostbyname($host); +- # convert to string values address list +- @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs]; +- } else { +- @addrs = ($host); +- } - - dbg("check: uri_local_bl %s addrs %s\n", $host, join(', ', @addrs)); - @@ -841,7 +998,12 @@ index e190fab..0000000 - dbg("check: uri_local_bl countries %s\n", join(' ', sort keys %{$rule->{countries}})); - - if ( $self->{use_geoip2} == 1 ) { -- my $country = $self->{geoip}->country( ip => $ip ); +- my $country; +- if (index($self->{geoip}->metadata()->description()->{en}, 'City') != -1) { +- $country = $self->{geoip}->city( ip => $ip ); +- } else { +- $country = $self->{geoip}->country( ip => $ip ); +- } - my $country_rec = $country->country(); - $cc = $country_rec->iso_code(); - } else { @@ -973,14 +1135,22 @@ index e190fab..0000000 -1; - diff --git a/lib/Mail/SpamAssassin/Util/DependencyInfo.pm b/lib/Mail/SpamAssassin/Util/DependencyInfo.pm -index 8127595..17aacbc 100644 +index 2f8aa65..eca12e1 100644 --- a/lib/Mail/SpamAssassin/Util/DependencyInfo.pm +++ b/lib/Mail/SpamAssassin/Util/DependencyInfo.pm -@@ -124,30 +124,6 @@ our @OPTIONAL_MODULES = ( +@@ -124,46 +124,6 @@ our @OPTIONAL_MODULES = ( desc => 'Used to check DNS Sender Policy Framework (SPF) records to fight email address forgery and make it easier to identify spams.', }, -{ +- module => 'GeoIP2::Database::Reader', +- version => 0, +- desc => 'Used by the RelayCountry plugin (not enabled by default) to +- determine the domain country codes of each relay in the path of an email. +- Also used by the URILocalBL plugin (not enabled by default) to provide ISP +- and Country code based filtering.', +-}, +-{ - module => 'Geo::IP', - version => 0, - desc => 'Used by the RelayCountry plugin (not enabled by default) to determine @@ -988,6 +1158,14 @@ index 8127595..17aacbc 100644 - the URILocalBL plugin to provide ISP and Country code based filtering.', -}, -{ +- module => 'IP::Country::DB_File', +- version => 0, +- desc => 'Used by the RelayCountry plugin (not enabled by default) to +- determine the domain country codes of each relay in the path of an email. +- Also used by the URILocalBL plugin (not enabled by default) to provide +- Country code based filtering.', +-}, +-{ - module => 'Net::CIDR::Lite', - version => 0, - desc => 'Used by the URILocalBL plugin to process IP address ranges.', @@ -1008,7 +1186,7 @@ index 8127595..17aacbc 100644 # module => 'Net::Ident', # version => 0, diff --git a/rules/init.pre b/rules/init.pre -index a330bad..6313a03 100644 +index f9ee06a..0539b29 100644 --- a/rules/init.pre +++ b/rules/init.pre @@ -14,13 +14,6 @@ @@ -1041,10 +1219,10 @@ index 489dd4c..7ff8e84 100644 # PDFInfo - Use several methods to detect a PDF file's ham/spam traits # loadplugin Mail::SpamAssassin::Plugin::PDFInfo diff --git a/spamassassin.raw b/spamassassin.raw -index 9d03d4f..443e154 100755 +index 4b52ef9..959297a 100755 --- a/spamassassin.raw +++ b/spamassassin.raw -@@ -881,9 +881,6 @@ from the SpamAssassin distribution. +@@ -872,9 +872,6 @@ from the SpamAssassin distribution. Mail::SpamAssassin::Plugin::Hashcash perform hashcash verification tests @@ -1054,6 +1232,3 @@ index 9d03d4f..443e154 100755 Mail::SpamAssassin::Plugin::SPF perform SPF verification tests --- -2.14.4 - diff --git a/SOURCES/spamassassin-3.4.2-fix-CVE-2018-11805.patch b/SOURCES/spamassassin-3.4.2-fix-CVE-2018-11805.patch deleted file mode 100644 index 693e8f1..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-CVE-2018-11805.patch +++ /dev/null @@ -1,2881 +0,0 @@ -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Conf/Parser.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Conf/Parser.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Conf/Parser.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Conf/Parser.pm 2020-03-12 14:01:47.277350965 +0100 -@@ -137,7 +137,7 @@ package Mail::SpamAssassin::Conf::Parser - use Mail::SpamAssassin::Conf; - use Mail::SpamAssassin::Constants qw(:sa); - use Mail::SpamAssassin::Logger; --use Mail::SpamAssassin::Util qw(untaint_var); -+use Mail::SpamAssassin::Util qw(untaint_var compile_regexp); - use Mail::SpamAssassin::NetSet; - - use strict; -@@ -147,6 +147,9 @@ use re 'taint'; - - our @ISA = qw(); - -+my $RULENAME_RE = RULENAME_RE; -+my $ARITH_EXPRESSION_LEXER = ARITH_EXPRESSION_LEXER; -+ - ########################################################################### - - sub new { -@@ -508,13 +511,12 @@ sub handle_conditional { - my ($self, $key, $value, $if_stack_ref, $skip_parsing_ref) = @_; - my $conf = $self->{conf}; - -- my $lexer = ARITH_EXPRESSION_LEXER; -- my @tokens = ($value =~ m/($lexer)/og); -+ my @tokens = ($value =~ /($ARITH_EXPRESSION_LEXER)/og); - - my $eval = ''; - my $bad = 0; - foreach my $token (@tokens) { -- if ($token =~ /^(?:\W+|[+-]?\d+(?:\.\d+)?)$/) { -+ if ($token =~ /^(?:\W{1,5}|[+-]?\d+(?:\.\d+)?)$/) { - # using tainted subr. argument may taint the whole expression, avoid - my $u = untaint_var($token); - $eval .= $u . " "; -@@ -538,17 +540,25 @@ sub handle_conditional { - $eval .= $]." "; - } - elsif ($token =~ /^\w[\w\:]+$/) { # class name -- my $u = untaint_var($token); -- $eval .= '"' . $u . '" '; -+ # Strictly controlled form: -+ if ($token =~ /^(?:\w+::){0,10}\w+$/) { -+ my $u = untaint_var($token); -+ $eval .= "'$u'"; -+ } else { -+ warn "config: illegal name '$token' in 'if $value'\n"; -+ $bad++; -+ last; -+ } - } - else { - $bad++; - warn "config: unparseable chars in 'if $value': '$token'\n"; -+ last; - } - } - - if ($bad) { -- $self->lint_warn("bad 'if' line, in \"$self->{currentfile}\"", undef); -+ $self->lint_warn("config: bad 'if' line, in \"$self->{currentfile}\"", undef); - return -1; - } - -@@ -574,7 +584,7 @@ sub cond_clause_plugin_loaded { - - sub cond_clause_can { - my ($self, $method) = @_; -- if ($self->{currentfile} =~ q!/user_prefs$! ) { -+ if ($self->{currentfile} =~ q!\buser_prefs$! ) { - warn "config: 'if can $method' not available in user_prefs"; - return 0 - } -@@ -591,7 +601,7 @@ sub cond_clause_can_or_has { - - local($1,$2); - if (!defined $method) { -- $self->lint_warn("bad 'if' line, no argument to $fn_name(), ". -+ $self->lint_warn("config: bad 'if' line, no argument to $fn_name(), ". - "in \"$self->{currentfile}\"", undef); - } elsif ($method =~ /^(.*)::([^:]+)$/) { - no strict "refs"; -@@ -599,7 +609,7 @@ sub cond_clause_can_or_has { - return 1 if $module->can($meth) && - ( $fn_name eq 'has' || &{$method}() ); - } else { -- $self->lint_warn("bad 'if' line, cannot find '::' in $fn_name($method), ". -+ $self->lint_warn("config: bad 'if' line, cannot find '::' in $fn_name($method), ". - "in \"$self->{currentfile}\"", undef); - } - return; -@@ -878,39 +888,40 @@ sub finish_parsing { - - # eval type handling - if (($type & 1) == 1) { -- if (my ($function, $args) = ($text =~ m/(.*?)\s*\((.*?)\)\s*$/)) { -- my ($packed, $argsref) = -- $self->pack_eval_method($function, $args, $name, $text); -- -- if (!$packed) { -- # we've already warned about this -+ if (my ($function, $args) = ($text =~ /^(\w+)\((.*?)\)$/)) { -+ my $argsref = $self->pack_eval_args($args); -+ if (!defined $argsref) { -+ $self->lint_warn("syntax error for eval function $name: $text"); -+ next; - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS) { -- $conf->{body_evals}->{$priority}->{$name} = $packed; -+ $conf->{body_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS) { -- $conf->{head_evals}->{$priority}->{$name} = $packed; -+ $conf->{head_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS) { - # We don't do priorities for $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS - # we also use the arrayref instead of the packed string -- $conf->{rbl_evals}->{$name} = [ $function, @$argsref ]; -+ $conf->{rbl_evals}->{$name} = [ $function, [@$argsref] ]; - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS) { -- $conf->{rawbody_evals}->{$priority}->{$name} = $packed; -+ $conf->{rawbody_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS) { -- $conf->{full_evals}->{$priority}->{$name} = $packed; -+ $conf->{full_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; - } - #elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_EVALS) { -- # $conf->{uri_evals}->{$priority}->{$name} = $packed; -+ # $conf->{uri_evals}->{$priority}->{$name} = [ $function, [@$argsref] ]; - #} - else { - $self->lint_warn("unknown type $type for $name: $text", $name); -+ next; - } - } - else { - $self->lint_warn("syntax error for eval function $name: $text", $name); -+ next; - } - } - # non-eval tests -@@ -937,6 +948,7 @@ sub finish_parsing { - } - else { - $self->lint_warn("unknown type $type for $name: $text", $name); -+ next; - } - } - } -@@ -988,8 +1000,7 @@ sub _meta_deps_recurse { - return unless $rule; - - # Lex the rule into tokens using a rather simple RE method ... -- my $lexer = ARITH_EXPRESSION_LEXER; -- my @tokens = ($rule =~ m/$lexer/og); -+ my @tokens = ($rule =~ /($ARITH_EXPRESSION_LEXER)/og); - - # Go through each token in the meta rule - my $conf_tests = $conf->{tests}; -@@ -1088,40 +1099,36 @@ sub find_dup_rules { - } - } - -+# Deprecated function - sub pack_eval_method { -- my ($self, $function, $args, $name, $text) = @_; -+ warn "deprecated function pack_eval_method() used\n"; -+ return ('',undef); -+} - -+sub pack_eval_args { -+ my ($self, $args) = @_; -+ -+ return [] if $args =~ /^\s+$/; -+ -+ # bug 4419: Parse quoted strings, unquoted alphanumerics/floats, -+ # unquoted IPv4 and IPv6 addresses, and unquoted common domain names. -+ # s// is used so that we can determine whether or not we successfully -+ # parsed ALL arguments. - my @args; -- if (defined $args) { -- # bug 4419: Parse quoted strings, unquoted alphanumerics/floats, -- # unquoted IPv4 and IPv6 addresses, and unquoted common domain names. -- # s// is used so that we can determine whether or not we successfully -- # parsed ALL arguments. -- local($1,$2,$3); -- while ($args =~ s/^\s* (?: (['"]) (.*?) \1 | ( [\d\.:A-Za-z-]+? ) ) -- \s* (?: , \s* | $ )//x) { -- if (defined $2) { -- push @args, $2; -- } -- else { -- push @args, $3; -- } -- } -+ local($1,$2,$3); -+ while ($args =~ s/^\s* (?: (['"]) (.*?) \1 | ( [\d\.:A-Za-z-]+? ) ) -+ \s* (?: , \s* | $ )//x) { -+ # DO NOT UNTAINT THESE ARGS -+ # The eval function that handles these should do that as necessary, -+ # we have no idea what acceptable arguments look like here. -+ push @args, defined $2 ? $2 : $3; - } - - if ($args ne '') { -- $self->lint_warn("syntax error (unparsable argument: $args) for eval function: $name: $text", $name); -- return; -+ return undef; - } - -- my $argstr = $function; -- $argstr =~ s/\s+//gs; -- -- if (@args > 0) { -- $argstr .= ',' . join(', ', -- map { my $s = $_; $s =~ s/\#/[HASH]/gs; 'q#' . $s . '#' } @args); -- } -- return ($argstr, \@args); -+ return \@args; - } - - ########################################################################### -@@ -1183,7 +1190,7 @@ sub add_test { - my $conf = $self->{conf}; - - # Don't allow invalid names ... -- if ($name !~ /^[_[:alpha:]]\w*$/) { -+ if ($name !~ /^${RULENAME_RE}$/) { - $self->lint_warn("config: error: rule '$name' has invalid characters ". - "(not Alphanumeric + Underscore + starting with a non-digit)\n", $name); - return; -@@ -1206,29 +1213,68 @@ sub add_test { - } - } - -+ # parameter to compile_regexp() -+ my $ignore_amre = -+ $self->{conf}->{lint_rules} || -+ $self->{conf}->{ignore_always_matching_regexps}; -+ - # all of these rule types are regexps - if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS || - $type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS || - $type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS || - $type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS) - { -- return unless $self->is_delimited_regexp_valid($name, $text); -+ my ($rec, $err) = compile_regexp($text, 1, $ignore_amre); -+ if (!$rec) { -+ $self->lint_warn("config: invalid regexp for $name '$text': $err", $name); -+ return; -+ } -+ $conf->{test_qrs}->{$name} = $rec; - } -- if ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) -+ elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) - { -+ local($1,$2,$3); - # RFC 5322 section 3.6.8, ftext printable US-ASCII chars not including ":" - # no re "strict"; # since perl 5.21.8: Ranges of ASCII printables... -- if ($text =~ /^!?defined\([!-9;-\176]+\)$/) { -- # fine, implements 'exists:' -+ if ($text =~ /^exists:(.*)/) { -+ my $hdr = $1; -+ # never evaled, so can be quite generous with the name -+ # check :addr etc header options -+ if ($hdr !~ /^[^:\s]+:?$/) { -+ $self->lint_warn("config: invalid head test $name header: $hdr"); -+ return; -+ } -+ $hdr =~ s/:$//; -+ $conf->{test_opt_header}->{$name} = $hdr; -+ $conf->{test_opt_exists}->{$name} = 1; - } else { -- my ($pat) = ($text =~ /^\s*\S+\s*(?:\=|\!)\~\s*(\S.*?\S)\s*$/); -- if ($pat) { $pat =~ s/\s+\[if-unset:\s+(.+)\]\s*$//; } -- return unless $self->is_delimited_regexp_valid($name, $pat); -+ if ($text !~ /^([^:\s]+(?:\:|(?:\:[a-z]+){1,2})?)\s*([=!]~)\s*(.+)$/) { -+ $self->lint_warn("config: invalid head test $name: $text"); -+ return; -+ } -+ my ($hdr, $op, $pat) = ($1, $2, $3); -+ $hdr =~ s/:$//; -+ if ($pat =~ s/\s+\[if-unset:\s+(.+)\]$//) { -+ $conf->{test_opt_unset}->{$name} = $1; -+ } -+ my ($rec, $err) = compile_regexp($pat, 1, $ignore_amre); -+ if (!$rec) { -+ $self->lint_warn("config: invalid regexp for $name '$pat': $err", $name); -+ return; -+ } -+ $conf->{test_qrs}->{$name} = $rec; -+ $conf->{test_opt_header}->{$name} = $hdr; -+ $conf->{test_opt_neg}->{$name} = 1 if $op eq '!~'; - } - } - elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) - { -- return unless $self->is_meta_valid($name, $text); -+ if ($self->is_meta_valid($name, $text)) { -+ # Untaint now once and not repeatedly later -+ $text = untaint_var($text); -+ } else { -+ return; -+ } - } - - $conf->{tests}->{$name} = $text; -@@ -1293,38 +1339,36 @@ sub is_meta_valid { - - # $meta is a degenerate translation of the rule, replacing all variables (i.e. rule names) with 0. - my $meta = ''; -- $rule = untaint_var($rule); # must be careful below -- # Bug #7557 code injection -- if ( $rule =~ /\S(::|->)\S/ ) { -- warn("is_meta_valid: Bogus rule $name: $rule") ; -+ -+ # Paranoid check (Bug #7557) -+ if ($rule =~ /(?:\:\:|->)/) { -+ warn("config: invalid meta $name rule: $rule") ; - return 0; - } - - # Lex the rule into tokens using a rather simple RE method ... -- my $lexer = ARITH_EXPRESSION_LEXER; -- my @tokens = ($rule =~ m/$lexer/og); -- if (length($name) == 1) { -- for (@tokens) { -- print "$name $_\n " or die "Error writing token: $!"; -- } -- } -+ my @tokens = ($rule =~ /($ARITH_EXPRESSION_LEXER)/og); -+ - # Go through each token in the meta rule - foreach my $token (@tokens) { - # If the token is a syntactically legal rule name, make it zero -- if ($token =~ /^[_[:alpha:]]\w+\z/s) { -+ if ($token =~ /^${RULENAME_RE}\z/s) { - $meta .= "0 "; - } -- # if it is a number or a string of 1 or 2 punctuation characters (i.e. operators) tack it onto the degenerate rule -- elsif ( $token =~ /^(\d+|[[:punct:]]{1,2})\z/s ) { -+ # if it is a (decimal) number or a string of 1 or 2 punctuation -+ # characters (i.e. operators) tack it onto the degenerate rule -+ elsif ($token =~ /^(\d+(?:\.\d+)?|[[:punct:]]{1,2})\z/s) { - $meta .= "$token "; - } -- # WTF is it? Just warn, for now. Bug #7557 -+ # Skip anything unknown (Bug #7557) - else { -- $self->lint_warn("config: Strange rule token: $token", $name); -- $meta .= "$token "; -+ $self->lint_warn("config: invalid meta $name token: $token", $name); -+ return 0; - } - } -- my $evalstr = 'my $x = ' . $meta . '; 1;'; -+ -+ $meta = untaint_var($meta); # was carefully checked -+ my $evalstr = 'my $x = '.$meta.'; 1;'; - if (eval $evalstr) { - return 1; - } -@@ -1335,94 +1379,21 @@ sub is_meta_valid { - return 0; - } - -+# Deprecated functions, leave just in case.. - sub is_delimited_regexp_valid { -- my ($self, $name, $re) = @_; -- -- if (!$re || $re !~ /^\s*m?(\W).*(?:\1|>|}|\)|\])[a-z]*\s*$/) { -- $re ||= ''; -- $self->lint_warn("config: invalid regexp for rule $name: $re: missing or invalid delimiters\n", $name); -- return 0; -- } -- return $self->is_regexp_valid($name, $re); -+ my ($self, $rule, $re) = @_; -+ warn "deprecated is_delimited_regexp_valid() called, use compile_regexp()\n"; -+ my ($rec, $err) = compile_regexp($re, 1, 1); -+ return $rec; - } -- - sub is_regexp_valid { -- my ($self, $name, $re) = @_; -- -- # OK, try to remove any normal perl-style regexp delimiters at -- # the start and end, and modifiers at the end if present, -- # so we can validate those too. -- my $origre = $re; -- my $safere = $re; -- my $mods = ''; -- local ($1,$2); -- if ($re =~ s/^m\{//) { -- $re =~ s/\}([a-z]*)\z//; $mods = $1; -- } -- elsif ($re =~ s/^m\(//) { -- $re =~ s/\)([a-z]*)\z//; $mods = $1; -- } -- elsif ($re =~ s/^m([a-z]*)\z//; $mods = $1; -- } -- elsif ($re =~ s/^m(\W)//) { -- $re =~ s/\Q$1\E([a-z]*)\z//; $mods = $1; -- } -- elsif ($re =~ s{^/(.*)/([a-z]*)\z}{$1}) { -- $mods = $2; -- } -- else { -- $safere = "m#".$re."#"; -- } -- -- if ($self->{conf}->{lint_rules} || -- $self->{conf}->{ignore_always_matching_regexps}) -- { -- my $msg = $self->is_always_matching_regexp($name, $re); -- -- if (defined $msg) { -- if ($self->{conf}->{lint_rules}) { -- $self->lint_warn($msg, $name); -- } else { -- warn $msg; -- return 0; -- } -- } -- } -- -- # now prepend the modifiers, in order to check if they're valid -- if ($mods) { -- $re = "(?" . $mods . ")" . $re; -- } -- -- # note: this MUST use m/...${re}.../ in some form or another, ie. -- # interpolation of the $re variable into a code regexp, in order to test the -- # security of the regexp. simply using ("" =~ $re) will NOT do that, and -- # will therefore open a hole! -- { # no re "strict"; # since perl 5.21.8: Ranges of ASCII printables... -- if (eval { ("" =~ m{$re}); 1; }) { return 1 } -- } -- my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err; -- $err =~ s/ at .*? line \d.*$//; -- $self->lint_warn("config: invalid regexp for rule $name: $origre: $err\n", $name); -- return 0; -+ my ($self, $rule, $re) = @_; -+ warn "deprecated is_regexp_valid() called, use compile_regexp()\n"; -+ my ($rec, $err) = compile_regexp($re, 1, 1); -+ return $rec; - } -- --# check the pattern for some basic errors, and warn if found - sub is_always_matching_regexp { -- my ($self, $name, $re) = @_; -- -- if ($re =~ /(? 'redirector_pattern', - is_priv => 1, -+ default => [], -+ type => $CONF_TYPE_STRINGLIST, - code => sub { - my ($self, $key, $value, $line) = @_; -+ -+ $value =~ s/^\s+//; - if ($value eq '') { - return $MISSING_REQUIRED_VALUE; - } -- elsif (!$self->{parser}->is_delimited_regexp_valid("redirector_pattern", $value)) { -+ -+ my ($rec, $err) = compile_regexp($value, 1); -+ if (!$rec) { -+ dbg("config: invalid redirector_pattern '$value': $err"); - return $INVALID_VALUE; - } - -- # convert to qr// while including modifiers -- local ($1,$2,$3); -- $value =~ /^m?(\W)(.*)(?:\1|>|}|\)|\])(.*?)$/; -- my $pattern = $2; -- $pattern = "(?".$3.")".$pattern if $3; -- $pattern = qr/$pattern/; -- -- push @{$self->{main}->{conf}->{redirector_patterns}}, $pattern; -- # dbg("config: adding redirector regex: " . $value); -+ push @{$self->{main}->{conf}->{redirector_patterns}}, $rec; - } - }); - -@@ -2983,11 +2981,9 @@ why the IP is listed, typically a hyperl - Create a sub-test for 'set'. If you want to look up a multi-meaning zone - like relays.osirusoft.com, you can then query the results from that zone - using the zone ID from the original query. The sub-test may either be an --IPv4 dotted address for RBLs that return multiple A records or a -+IPv4 dotted address for RBLs that return multiple A records, or a - non-negative decimal number to specify a bitmask for RBLs that return a --single A record containing a bitmask of results, a SenderBase test --beginning with "sb:", or (if none of the preceding options seem to fit) a --regular expression. -+single A record containing a bitmask of results, or a regular expression. - - Note: the set name must be exactly the same for as the main query rule, - including selections like '-notfirsthop' appearing at the end of the set -@@ -3001,11 +2997,17 @@ name. - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- local ($1,$2); -- if ($value =~ /^(\S+)\s+(?:rbl)?eval:(.*)$/) { -- my ($rulename, $fn) = ($1, $2); -- dbg("config: header eval rule name is $rulename function is $fn"); -- if ($fn !~ /^\w+(\(.*\))?$/) { -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { -+ return $MISSING_REQUIRED_VALUE; -+ } -+ if ($value =~ /^(?:rbl)?eval:(.*)$/) { -+ my $fn = $1; -+ if ($fn !~ /^\w+\(.*\)$/) { - return $INVALID_VALUE; - } - if ($fn =~ /^check_(?:rbl|dns)/) { -@@ -3015,25 +3017,9 @@ name. - $self->{parser}->add_test ($rulename, $fn, $TYPE_HEAD_EVALS); - } - } -- elsif ($value =~ /^(\S+)\s+exists:(.*)$/) { -- my ($rulename, $header_name) = ($1, $2); -- # RFC 5322 section 3.6.8, ftext printable US-ASCII ch not including ":" -- if ($header_name !~ /\S/) { -- return $MISSING_REQUIRED_VALUE; -- # } elsif ($header_name !~ /^([!-9;-\176]+)$/) { -- } elsif ($header_name !~ /^([^: \t]+)$/) { # be generous -- return $INVALID_HEADER_FIELD_NAME; -- } -- $self->{parser}->add_test ($rulename, "defined($header_name)", -- $TYPE_HEAD_TESTS); -- $self->{descriptions}->{$rulename} = "Found a $header_name header"; -- } - else { -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -- return $MISSING_REQUIRED_VALUE; -- } -- $self->{parser}->add_test (@values, $TYPE_HEAD_TESTS); -+ # Detailed parsing in add_test -+ $self->{parser}->add_test ($rulename, $value, $TYPE_HEAD_TESTS); - } - } - }); -@@ -3070,22 +3056,22 @@ Define a body eval test. See above. - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- local ($1,$2); -- if ($value =~ /^(\S+)\s+eval:(.*)$/) { -- my ($rulename, $fn) = ($1, $2); -- dbg("config: body eval rule name is $rulename function is $fn"); -- -- if ($fn !~ /^\w+(\(.*\))?$/) { -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { -+ return $MISSING_REQUIRED_VALUE; -+ } -+ if ($value =~ /^eval:(.*)$/) { -+ my $fn = $1; -+ if ($fn !~ /^\w+\(.*\)$/) { - return $INVALID_VALUE; - } - $self->{parser}->add_test ($rulename, $fn, $TYPE_BODY_EVALS); -- } -- else { -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -- return $MISSING_REQUIRED_VALUE; -- } -- $self->{parser}->add_test (@values, $TYPE_BODY_TESTS); -+ } else { -+ $self->{parser}->add_test ($rulename, $value, $TYPE_BODY_TESTS); - } - } - }); -@@ -3114,11 +3100,15 @@ points of the URI, and will also be fast - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { - return $MISSING_REQUIRED_VALUE; - } -- $self->{parser}->add_test (@values, $TYPE_URI_TESTS); -+ $self->{parser}->add_test ($rulename, $value, $TYPE_URI_TESTS); - } - }); - -@@ -3149,15 +3139,22 @@ Define a raw-body eval test. See above. - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- local ($1,$2); -- if ($value =~ /^(\S+)\s+eval:(.*)$/) { -- $self->{parser}->add_test ($1, $2, $TYPE_RAWBODY_EVALS); -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { -+ return $MISSING_REQUIRED_VALUE; -+ } -+ if ($value =~ /^eval:(.*)$/) { -+ my $fn = $1; -+ if ($fn !~ /^\w+\(.*\)$/) { -+ return $INVALID_VALUE; -+ } -+ $self->{parser}->add_test ($rulename, $fn, $TYPE_RAWBODY_EVALS); - } else { -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -- return $MISSING_REQUIRED_VALUE; -- } -- $self->{parser}->add_test (@values, $TYPE_RAWBODY_TESTS); -+ $self->{parser}->add_test ($rulename, $value, $TYPE_RAWBODY_TESTS); - } - } - }); -@@ -3183,15 +3180,22 @@ Define a full message eval test. See ab - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- local ($1,$2); -- if ($value =~ /^(\S+)\s+eval:(.*)$/) { -- $self->{parser}->add_test ($1, $2, $TYPE_FULL_EVALS); -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { -+ return $MISSING_REQUIRED_VALUE; -+ } -+ if ($value =~ /^eval:(.*)$/) { -+ my $fn = $1; -+ if ($fn !~ /^\w+\(.*\)$/) { -+ return $INVALID_VALUE; -+ } -+ $self->{parser}->add_test ($rulename, $fn, $TYPE_FULL_EVALS); - } else { -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -- return $MISSING_REQUIRED_VALUE; -- } -- $self->{parser}->add_test (@values, $TYPE_FULL_TESTS); -+ $self->{parser}->add_test ($rulename, $value, $TYPE_FULL_TESTS); - } - } - }); -@@ -3236,15 +3240,19 @@ ignore these for scoring. - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- my @values = split(/\s+/, $value, 2); -- if (@values != 2) { -+ local($1); -+ if ($value !~ s/^(\S+)\s+//) { -+ return $INVALID_VALUE; -+ } -+ my $rulename = $1; -+ if ($value eq '') { - return $MISSING_REQUIRED_VALUE; - } -- if ($values[1] =~ /\*\s*\*/) { -+ if ($value =~ /\*\s*\*/) { - info("config: found invalid '**' or '* *' operator in meta command"); - return $INVALID_VALUE; - } -- $self->{parser}->add_test (@values, $TYPE_META_TESTS); -+ $self->{parser}->add_test ($rulename, $value, $TYPE_META_TESTS); - } - }); - -@@ -4182,12 +4190,15 @@ from SQL or LDAP, instead of passing the - type => $CONF_TYPE_BOOL, - }); - --=item loadplugin PluginModuleName [/path/module.pm] -+=item loadplugin [Mail::SpamAssassin::Plugin::]ModuleName [/path/module.pm] - --Load a SpamAssassin plugin module. The C is the perl module -+Load a SpamAssassin plugin module. The C is the perl module - name, used to create the plugin object itself. - --C is the file to load, containing the module's perl code; -+Module naming is strict, name must only contain alphanumeric characters or -+underscores. File must have .pm extension. -+ -+C is the file to load, containing the module's perl code; - if it's specified as a relative path, it's considered to be relative to the - current configuration file. If it is omitted, the module will be loaded - using perl's search path (the C<@INC> array). -@@ -4206,20 +4217,16 @@ See C for mo - } - my ($package, $path); - local ($1,$2); -- if ($value =~ /^(\S+)\s+(\S+)$/) { -+ if ($value =~ /^((?:\w+::){0,10}\w+)(?:\s+(\S+\.pm))?$/i) { - ($package, $path) = ($1, $2); -- } elsif ($value =~ /^\S+$/) { -- ($package, $path) = ($value, undef); - } else { - return $INVALID_VALUE; - } -- # is blindly untainting safe? it is no worse than before -- $_ = untaint_var($_) for ($package,$path); - $self->load_plugin ($package, $path); - } - }); - --=item tryplugin PluginModuleName [/path/module.pm] -+=item tryplugin ModuleName [/path/module.pm] - - Same as C, but silently ignored if the .pm file cannot be found in - the filesystem. -@@ -4236,15 +4243,11 @@ the filesystem. - } - my ($package, $path); - local ($1,$2); -- if ($value =~ /^(\S+)\s+(\S+)$/) { -+ if ($value =~ /^((?:\w+::){0,10}\w+)(?:\s+(\S+\.pm))?$/i) { - ($package, $path) = ($1, $2); -- } elsif ($value =~ /^\S+$/) { -- ($package, $path) = ($value, undef); - } else { - return $INVALID_VALUE; - } -- # is blindly untainting safe? it is no worse than before -- $_ = untaint_var($_) for ($package,$path); - $self->load_plugin ($package, $path, 1); - } - }); -@@ -5022,12 +5025,7 @@ sub maybe_body_only { - - sub load_plugin { - my ($self, $package, $path, $silent) = @_; -- if ($path) { -- $path = $self->{parser}->fix_path_relative_to_current_file($path); -- } -- # it wouldn't hurt to do some checking on validity of $package -- # and $path before untainting them -- $self->{main}->{plugins}->load_plugin(untaint_var($package), $path, $silent); -+ $self->{main}->{plugins}->load_plugin($package, $path, $silent); - } - - sub load_plugin_succeeded { -@@ -5208,6 +5206,7 @@ sub feature_bug6558_free { 1 } - sub feature_edns { 1 } # supports 'dns_options edns' config option - sub feature_dns_query_restriction { 1 } # supported config option - sub feature_registryboundaries { 1 } # replaces deprecated registrarboundaries -+sub feature_compile_regexp { 1 } # Util::compile_regexp - sub perl_min_version_5010000 { return $] >= 5.010000 } # perl version check ("perl_version" not neatly backwards-compatible) - - ########################################################################### -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Constants.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Constants.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Constants.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Constants.pm 2020-03-12 14:01:47.277350965 +0100 -@@ -32,7 +32,7 @@ our(@BAYES_VARS, @IP_VARS, @SA_VARS, %EX - - # NOTE: Unless you need these to be available at BEGIN time, you're better with this out of a BEGIN block with a simple our statement. - BEGIN { -- @IP_VARS = qw( -+ @IP_VARS = qw( - IP_IN_RESERVED_RANGE IP_PRIVATE LOCALHOST IPV4_ADDRESS IP_ADDRESS - ); - @BAYES_VARS = qw( -@@ -43,7 +43,7 @@ BEGIN { - HARVEST_DNSBL_PRIORITY MBX_SEPARATOR - MAX_BODY_LINE_LENGTH MAX_HEADER_KEY_LENGTH MAX_HEADER_VALUE_LENGTH - MAX_HEADER_LENGTH ARITH_EXPRESSION_LEXER AI_TIME_UNKNOWN -- CHARSETS_LIKELY_TO_FP_AS_CAPS MAX_URI_LENGTH -+ CHARSETS_LIKELY_TO_FP_AS_CAPS MAX_URI_LENGTH RULENAME_RE - ); - - %EXPORT_TAGS = ( -@@ -402,4 +402,7 @@ use constant CHARSETS_LIKELY_TO_FP_AS_CA - koi|jp|jis|euc|gb|big5|isoir|cp1251|windows-1251|georgianps|pt154|tis - )[-_a-z0-9]*}ix; - -+# Allowed rulename format -+use constant RULENAME_RE => qr([_a-zA-Z][_a-zA-Z0-9]{0,127}); -+ - 1; -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Dns.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Dns.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Dns.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Dns.pm 2020-03-12 14:01:47.277350965 +0100 -@@ -139,6 +139,12 @@ sub do_rbl_lookup { - # TODO: these are constant so they should only be added once at startup - sub register_rbl_subtest { - my ($self, $rule, $set, $subtest) = @_; -+ -+ if ($subtest =~ /^sb:/) { -+ warn("dns: ignored $rule, SenderBase rules are deprecated\n"); -+ return 0; -+ } -+ - $self->{dnspost}->{$set}->{$subtest} = $rule; - } - -@@ -307,30 +313,6 @@ sub process_dnsbl_set { - # test for exact equality, not a regexp (an IPv4 address) - $self->dnsbl_hit($rule, $question, $answer) if $subtest eq $rdatastr; - } -- # senderbase -- elsif ($subtest =~ s/^sb://) { -- # SB rules are not available to users -- if ($self->{conf}->{user_defined_rules}->{$rule}) { -- dbg("dns: skipping rule '$rule': not supported when user-defined"); -- next; -- } -- -- $rdatastr =~ s/^\d+-//; -- my %sb = ($rdatastr =~ m/(?:^|\|)(\d+)=([^|]+)/g); -- my $undef = 0; -- while ($subtest =~ m/\bS(\d+)\b/g) { -- if (!defined $sb{$1}) { -- $undef = 1; -- last; -- } -- $subtest =~ s/\bS(\d+)\b/\$sb{$1}/; -- } -- -- # untaint. (bug 3325) -- $subtest = untaint_var($subtest); -- -- $self->got_hit($rule, "SenderBase: ", ruletype => "dnsbl") if !$undef && eval $subtest; -- } - # bitmask - elsif ($subtest =~ /^\d+$/) { - # Bug 6803: response should be within 127.0.0.0/8, ignore otherwise -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Logger.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Logger.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Logger.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Logger.pm 2020-03-12 14:01:47.277350965 +0100 -@@ -265,6 +265,8 @@ sub add { - my $name = lc($params{method}); - my $class = ucfirst($name); - -+ return 0 if $class !~ /^\w+$/; # be paranoid -+ - eval 'use Mail::SpamAssassin::Logger::'.$class.'; 1' - or do { - my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Message.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Message.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Message.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Message.pm 2020-03-12 14:01:47.277350965 +0100 -@@ -188,14 +188,34 @@ sub new { - @message = split(/^/m, $message, -1); - } - -- # Pull off mbox and mbx separators -- # also deal with null messages -+ # Deal with null message - if (!@message) { - # bug 4884: - # if we get here, it means that the input was null, so fake the message - # content as a single newline... - @message = ("\n"); -- } elsif ($message[0] =~ /^From\s+(?!:)/) { -+ } -+ -+ # Bug 7648: -+ # Make sure the message is tainted. When linting, @testmsg is not, so this -+ # handles that. Perhaps 3rd party tools could call this with untainted -+ # messages? Tainting the message is important because it prevents certain -+ # exploits later. -+ if (Mail::SpamAssassin::Util::am_running_in_taint_mode() && -+ grep { !Scalar::Util::tainted($_) } @message) { -+ local($_); -+ # To preserve newlines, no joining and splitting here, process each line -+ # directly as is. -+ foreach (@message) { -+ $_ = Mail::SpamAssassin::Util::taint_var($_); -+ } -+ if (grep { !Scalar::Util::tainted($_) } @message) { -+ die "Mail::SpamAssassin::Message failed to enforce message taintness"; -+ } -+ } -+ -+ # Pull off mbox and mbx separators -+ if ($message[0] =~ /^From\s+(?!:)/) { - # careful not to confuse with obsolete syntax which allowed WSP before ':' - # mbox formated mailbox - $self->{'mbox_sep'} = shift @message; -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/PerMsgStatus.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/PerMsgStatus.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/PerMsgStatus.pm 2020-03-12 13:57:15.929406694 +0100 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/PerMsgStatus.pm 2020-03-12 14:01:47.278350975 +0100 -@@ -269,7 +269,6 @@ sub new { - 'master_deadline' => $msg->{master_deadline}, # dflt inherited from msg - 'deadline_exceeded' => 0, # time limit exceeded, skipping further tests - }; -- #$self->{main}->{use_rule_subs} = 1; - - dbg("check: pms new, time limit in %.3f s", - $self->{master_deadline} - time) if $self->{master_deadline}; -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Bayes.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Bayes.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Bayes.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Bayes.pm 2020-03-12 14:01:47.278350975 +0100 -@@ -1644,8 +1644,14 @@ sub learner_new { - my ($self) = @_; - - my $store; -- my $module = untaint_var($self->{conf}->{bayes_store_module}); -- $module = 'Mail::SpamAssassin::BayesStore::DBM' if !$module; -+ my $module = $self->{conf}->{bayes_store_module}; -+ if (!$module) { -+ $module = 'Mail::SpamAssassin::BayesStore::DBM'; -+ } elsif ($module =~ /^([_A-Za-z0-9:]+)$/) { -+ $module = untaint_var($module); -+ } else { -+ die "bayes: invalid module: $module\n"; -+ } - - dbg("bayes: learner_new self=%s, bayes_store_module=%s", $self,$module); - undef $self->{store}; # DESTROYs previous object, if any -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm 2020-03-12 14:01:47.278350975 +0100 -@@ -29,7 +29,7 @@ package Mail::SpamAssassin::Plugin::Body - - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Logger; --use Mail::SpamAssassin::Util qw(untaint_var); -+use Mail::SpamAssassin::Util qw(untaint_var qr_to_string); - use Mail::SpamAssassin::Util::Progress; - - use Errno qw(ENOENT EACCES EEXIST); -@@ -152,8 +152,12 @@ NEXT_RULE: - foreach my $name (keys %{$rules}) { - $self->{show_progress} and $progress and $progress->update(++$count); - -- my $rule = $rules->{$name}; -- my $cachekey = join "#", $name, $rule; -+ #my $rule = $rules->{$name}; -+ my $rule = qr_to_string($conf->{test_qrs}->{$name}); -+ if (!defined $rule) { -+ die "zoom: error: regexp for $rule not found\n"; -+ } -+ my $cachekey = $name.'#'.$rule; - - my $cent = $cached->{rule_bases}->{$cachekey}; - if (defined $cent) { -@@ -177,7 +181,7 @@ NEXT_RULE: - } - - # ignore ReplaceTags rules -- my $is_a_replacetags_rule = $conf->{rules_to_replace}->{$name}; -+ my $is_a_replacetags_rule = $conf->{replace_rules}->{$name}; - my ($minlen, $lossy, @bases); - - if (!$is_a_replacetags_rule) { -@@ -407,11 +411,14 @@ sub simplify_and_qr_regexp { - my $rule = shift; - - my $main = $self->{main}; -- $rule = Mail::SpamAssassin::Util::regexp_remove_delimiters($rule); - -- # remove the regexp modifiers, keep for later -+ - my $mods = ''; -- while ($rule =~ s/^\(\?([a-z]*)\)//) { $mods .= $1; } -+ -+ # remove the regexp modifiers, keep for later -+ while ($rule =~ s/^\(\?([a-z]*)\)//) { -+ $mods .= $1; -+ } - - # modifier removal - while ($rule =~ s/^\(\?-([a-z]*)\)//) { -@@ -685,7 +692,7 @@ sub extract_hints { - $add_candidate->(); - - if (!$longestexact) { -- die "no long-enough string found in $rawrule"; -+ die "no long-enough string found in $rawrule\n"; - # all unrolled versions must have a long string, otherwise - # we cannot reliably match all variants of the rule - } else { -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Check.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Check.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Check.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Check.pm 2020-03-12 14:01:47.278350975 +0100 -@@ -28,6 +28,9 @@ use Mail::SpamAssassin::Constants qw(:sa - - our @ISA = qw(Mail::SpamAssassin::Plugin); - -+my $ARITH_EXPRESSION_LEXER = ARITH_EXPRESSION_LEXER; -+my $RULENAME_RE = RULENAME_RE; -+ - # methods defined by the compiled ruleset; deleted in finish_tests() - our @TEMPORARY_METHODS; - -@@ -263,11 +266,15 @@ sub run_rbl_eval_tests { - - %{$pms->{test_log_msgs}} = (); # clear test state - -- my ($function, @args) = @{$test}; -+ my $function = $test->[0]; -+ if (!exists $pms->{conf}->{eval_plugins}->{$function}) { -+ warn("rules: unknown eval '$function' for $rulename, ignoring RBL eval\n"); -+ return 0; -+ } - - my $result; - eval { -- $result = $pms->$function($rulename, @args); 1; -+ $result = $pms->$function($rulename, @{$test->[1]}); 1; - } or do { - my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; - die "rules: $eval_stat\n" if $eval_stat =~ /__alarm__ignore__/; -@@ -334,6 +341,7 @@ sub run_generic_tests { - $self->push_evalstr_prefix($pms, ' - # start_rules_plugin_code '.$ruletype.' '.$priority.' - my $scoresptr = $self->{conf}->{scores}; -+ my $qrptr = $self->{conf}->{test_qrs}; - '); - if (defined $opts{pre_loop_body}) { - $opts{pre_loop_body}->($self, $pms, $conf, %nopts); -@@ -529,11 +537,9 @@ sub do_meta_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $rule, %opts) = @_; -- $rule = untaint_var($rule); # presumably checked - - # Lex the rule into tokens using a rather simple RE method ... -- my $lexer = ARITH_EXPRESSION_LEXER; -- my @tokens = ($rule =~ m/$lexer/og); -+ my @tokens = ($rule =~ /$ARITH_EXPRESSION_LEXER/og); - - # Set the rule blank to start - $meta{$rulename} = ""; -@@ -544,15 +550,12 @@ sub do_meta_tests { - # Go through each token in the meta rule - foreach my $token (@tokens) { - -- # Numbers can't be rule names -- if ($token =~ tr{A-Za-z0-9_}{}c || substr($token,0,1) =~ tr{A-Za-z_}{}c) { -- $meta{$rulename} .= "$token "; -- } -- else { # token is a rule name -+ # ... rulename? -+ if ($token =~ /^${RULENAME_RE}\z/) { - # the " || 0" formulation is to avoid "use of uninitialized value" - # warnings; this is better than adding a 0 to a hash for every - # rule referred to in a meta... -- $meta{$rulename} .= "(\$h->{'$token'} || 0) "; -+ $meta{$rulename} .= "(\$h->{'$token'}||0) "; - - if (!exists $conf->{scores}->{$token}) { - dbg("rules: meta test $rulename has undefined dependency '$token'"); -@@ -571,6 +574,9 @@ sub do_meta_tests { - # If the token is another meta rule, add it as a dependency - push (@{ $rule_deps{$rulename} }, $token) - if (exists $conf->{meta_tests}->{$opts{priority}}->{$token}); -+ } else { -+ # ... number or operator -+ $meta{$rulename} .= "$token "; - } - } - }, -@@ -666,66 +672,30 @@ sub do_head_tests { - args => [ ], - loop_body => sub - { -- my ($self, $pms, $conf, $rulename, $rule, %opts) = @_; -- my $def; -- $rule = untaint_var($rule); # presumably checked -- my ($hdrname, $op, $op_infix, $pat); -- if ($rule =~ /^\s* (\S+) \s* ([=!]~) \s* (\S .*? \S) \s*$/x) { -- ($hdrname, $op, $pat) = ($1,$2,$3); # e.g.: Subject =~ /patt/ -- $op_infix = 1; -- if (!defined $pat) { -- warn "rules: invalid rule: $rulename\n"; -- $pms->{rule_errors}++; -- next; -- } -- if ($pat =~ s/\s+\[if-unset:\s+(.+)\]\s*$//) { $def = $1 } -- } elsif ($rule =~ /^\s* (\S+) \s* \( \s* (\S+) \s* \) \s*$/x) { -- # implements exists:name_of_header (and similar function or prefix ops) -- ($hdrname, $op) = ($2,$1); # e.g.: !defined(Subject) -+ my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -+ my ($op, $op_infix); -+ my $hdrname = $conf->{test_opt_header}->{$rulename}; -+ if (exists $conf->{test_opt_exists}->{$rulename}) { - $op_infix = 0; -- } else { -- warn "rules: unrecognized rule: $rulename\n"; -- $pms->{rule_errors}++; -- next; -+ if (exists $conf->{test_opt_neg}->{$rulename}) { -+ $op = '!defined'; -+ } else { -+ $op = 'defined'; -+ } -+ } -+ else { -+ $op_infix = 1; -+ $op = $conf->{test_opt_neg}->{$rulename} ? '!~' : '=~'; - } - -+ my $def = $conf->{test_opt_unset}->{$rulename}; - push(@{ $ordered{$hdrname . (!defined $def ? '' : "\t".$def)} }, - $rulename); - -- next if ($opts{doing_user_rules} && -+ return if ($opts{doing_user_rules} && - !$self->is_user_rule_sub($rulename.'_head_test')); - -- # caller can set this member of the Mail::SpamAssassin object to -- # override this; useful for profiling rule runtimes, although I think -- # the HitFreqsRuleTiming.pm plugin is probably better nowadays anyway -- if ($self->{main}->{use_rule_subs}) { -- my $matching_string_unavailable = 0; -- my $expr; -- if ($op =~ /^!?[A-Za-z_]+$/) { # function or its negation -- $expr = $op . '($text)'; -- $matching_string_unavailable = 1; -- } else { # infix operator -- $expr = '$text ' . $op . ' ' . $pat; -- if ($op eq '=~' || $op eq '!~') { -- $expr .= 'g'; -- } else { -- $matching_string_unavailable = 1; -- } -- } -- $self->add_temporary_method ($rulename.'_head_test', '{ -- my($self,$text) = @_; -- '.$self->hash_line_for_rule($pms, $rulename).' -- while ('.$expr.') { -- $self->got_hit(q{'.$rulename.'}, "", ruletype => "header"); -- '. $self->hit_rule_plugin_code($pms, $rulename, "header", "last", -- $matching_string_unavailable) . ' -- } -- }'); -- } -- else { -- # store for use below -- $testcode{$rulename} = [$op_infix, $op, $pat]; -- } -+ $testcode{$rulename} = [$op_infix, $op, $pat]; - }, - pre_loop_body => sub - { -@@ -746,15 +716,6 @@ sub do_head_tests { - (!defined($def) ? 'undef' : 'q{'.$def.'}') . '); - '); - foreach my $rulename (@{$v}) { -- if ($self->{main}->{use_rule_subs}) { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$rulename.'_head_test($self, $hval); -- '.$self->ran_rule_plugin_code($rulename, "header").' -- } -- '); -- } -- else { - my $tc_ref = $testcode{$rulename}; - my ($op_infix, $op, $pat); - ($op_infix, $op, $pat) = @$tc_ref if defined $tc_ref; -@@ -772,9 +733,7 @@ sub do_head_tests { - $matching_string_unavailable = 1; - } - else { # infix operator -- if (! ($op eq '=~' || $op eq '!~') ) { # not a pattern matching op. -- $matching_string_unavailable = 1; -- } elsif ( ($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/ ) { -+ if (($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/) { - $posline = 'pos $hval = 0; $hits = 0;'; - $ifwhile = 'while'; - $hitdone = 'last'; -@@ -783,7 +742,11 @@ sub do_head_tests { - $max = untaint_var($max); - $whlimit = ' && $hits++ < '.$max if $max; - } -- $expr = '$hval ' . $op . ' ' . $pat . $matchg; -+ if ($matchg) { -+ $expr = '$hval '.$op.' /$qrptr->{q{'.$rulename.'}}/g'; -+ } else { -+ $expr = '$hval '.$op.' $qrptr->{q{'.$rulename.'}}'; -+ } - } - - $self->add_evalstr($pms, ' -@@ -798,7 +761,6 @@ sub do_head_tests { - '.$self->ran_rule_plugin_code($rulename, "header").' - } - '); -- } - } - $self->pop_evalstr_prefix(); - } -@@ -820,7 +782,6 @@ sub do_body_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -- $pat = untaint_var($pat); # presumably checked - my $sub = ''; - if (would_log('dbg', 'rules-all') == 2) { - $sub .= ' -@@ -838,7 +799,7 @@ sub do_body_tests { - body_'.$loopid.': foreach my $l (@_) { - pos $l = 0; - '.$self->hash_line_for_rule($pms, $rulename).' -- while ($l =~ '.$pat.'g'. ($max? ' && $hits++ < '.$max:'') .') { -+ while ($l =~ /$qrptr->{q{'.$rulename.'}}/g'. ($max? ' && $hits++ < '.$max:'') .') { - $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "body"); - '. $self->hit_rule_plugin_code($pms, $rulename, 'body', - "last body_".$loopid) . ' -@@ -853,7 +814,7 @@ sub do_body_tests { - $sub .= ' - foreach my $l (@_) { - '.$self->hash_line_for_rule($pms, $rulename).' -- if ($l =~ '.$pat.') { -+ if ($l =~ $qrptr->{q{'.$rulename.'}}) { - $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "body"); - '. $self->hit_rule_plugin_code($pms, $rulename, "body", "last") .' - } -@@ -861,30 +822,15 @@ sub do_body_tests { - '; - } - -- if ($self->{main}->{use_rule_subs}) { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$rulename.'_body_test($self,@_); -- '.$self->ran_rule_plugin_code($rulename, "body").' -- } -- '); -- } -- else { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$sub.' -- '.$self->ran_rule_plugin_code($rulename, "body").' -- } -- '); -- } -+ $self->add_evalstr($pms, ' -+ if ($scoresptr->{q{'.$rulename.'}}) { -+ '.$sub.' -+ '.$self->ran_rule_plugin_code($rulename, "body").' -+ } -+ '); - -- next if ($opts{doing_user_rules} && -+ return if ($opts{doing_user_rules} && - !$self->is_user_rule_sub($rulename.'_body_test')); -- -- if ($self->{main}->{use_rule_subs}) { -- $self->add_temporary_method ($rulename.'_body_test', -- '{ my $self = shift; '.$sub.' }'); -- } - } - ); - } -@@ -902,7 +848,6 @@ sub do_uri_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -- $pat = untaint_var($pat); # presumably checked - my $sub = ''; - if (would_log('dbg', 'rules-all') == 2) { - $sub .= ' -@@ -918,7 +863,7 @@ sub do_uri_tests { - uri_'.$loopid.': foreach my $l (@_) { - pos $l = 0; - '.$self->hash_line_for_rule($pms, $rulename).' -- while ($l =~ '.$pat.'g'. ($max? ' && $hits++ < '.$max:'') .') { -+ while ($l =~ /$qrptr->{q{'.$rulename.'}}/g'. ($max? ' && $hits++ < '.$max:'') .') { - $self->got_hit(q{'.$rulename.'}, "URI: ", ruletype => "uri"); - '. $self->hit_rule_plugin_code($pms, $rulename, "uri", - "last uri_".$loopid) . ' -@@ -930,7 +875,7 @@ sub do_uri_tests { - $sub .= ' - foreach my $l (@_) { - '.$self->hash_line_for_rule($pms, $rulename).' -- if ($l =~ '.$pat.') { -+ if ($l =~ $qrptr->{q{'.$rulename.'}}) { - $self->got_hit(q{'.$rulename.'}, "URI: ", ruletype => "uri"); - '. $self->hit_rule_plugin_code($pms, $rulename, "uri", "last") .' - } -@@ -938,30 +883,15 @@ sub do_uri_tests { - '; - } - -- if ($self->{main}->{use_rule_subs}) { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$rulename.'_uri_test($self, @_); -- '.$self->ran_rule_plugin_code($rulename, "uri").' -- } -- '); -- } -- else { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$sub.' -- '.$self->ran_rule_plugin_code($rulename, "uri").' -- } -- '); -- } -+ $self->add_evalstr($pms, ' -+ if ($scoresptr->{q{'.$rulename.'}}) { -+ '.$sub.' -+ '.$self->ran_rule_plugin_code($rulename, "uri").' -+ } -+ '); - -- next if ($opts{doing_user_rules} && -+ return if ($opts{doing_user_rules} && - !$self->is_user_rule_sub($rulename.'_uri_test')); -- -- if ($self->{main}->{use_rule_subs}) { -- $self->add_temporary_method ($rulename.'_uri_test', -- '{ my $self = shift; '.$sub.' }'); -- } - } - ); - } -@@ -979,7 +909,6 @@ sub do_rawbody_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -- $pat = untaint_var($pat); # presumably checked - my $sub = ''; - if (would_log('dbg', 'rules-all') == 2) { - $sub .= ' -@@ -997,7 +926,7 @@ sub do_rawbody_tests { - rawbody_'.$loopid.': foreach my $l (@_) { - pos $l = 0; - '.$self->hash_line_for_rule($pms, $rulename).' -- while ($l =~ '.$pat.'g'. ($max? ' && $hits++ < '.$max:'') .') { -+ while ($l =~ /$qrptr->{q{'.$rulename.'}}/g'. ($max? ' && $hits++ < '.$max:'') .') { - $self->got_hit(q{'.$rulename.'}, "RAW: ", ruletype => "rawbody"); - '. $self->hit_rule_plugin_code($pms, $rulename, "rawbody", - "last rawbody_".$loopid) . ' -@@ -1010,7 +939,7 @@ sub do_rawbody_tests { - $sub .= ' - foreach my $l (@_) { - '.$self->hash_line_for_rule($pms, $rulename).' -- if ($l =~ '.$pat.') { -+ if ($l =~ $qrptr->{q{'.$rulename.'}}) { - $self->got_hit(q{'.$rulename.'}, "RAW: ", ruletype => "rawbody"); - '. $self->hit_rule_plugin_code($pms, $rulename, "rawbody", "last") . ' - } -@@ -1018,30 +947,15 @@ sub do_rawbody_tests { - '; - } - -- if ($self->{main}->{use_rule_subs}) { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$rulename.'_rawbody_test($self, @_); -- '.$self->ran_rule_plugin_code($rulename, "rawbody").' -- } -- '); -- } -- else { -- $self->add_evalstr($pms, ' -- if ($scoresptr->{q{'.$rulename.'}}) { -- '.$sub.' -- '.$self->ran_rule_plugin_code($rulename, "rawbody").' -- } -- '); -- } -+ $self->add_evalstr($pms, ' -+ if ($scoresptr->{q{'.$rulename.'}}) { -+ '.$sub.' -+ '.$self->ran_rule_plugin_code($rulename, "rawbody").' -+ } -+ '); - -- next if ($opts{doing_user_rules} && -+ return if ($opts{doing_user_rules} && - !$self->is_user_rule_sub($rulename.'_rawbody_test')); -- -- if ($self->{main}->{use_rule_subs}) { -- $self->add_temporary_method ($rulename.'_rawbody_test', -- '{ my $self = shift; '.$sub.' }'); -- } - } - ); - } -@@ -1066,7 +980,6 @@ sub do_full_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -- $pat = untaint_var($pat); # presumably checked - my ($max) = ($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmaxhits=(\d+)\b/; - $max = untaint_var($max); - $self->add_evalstr($pms, ' -@@ -1075,7 +988,7 @@ sub do_full_tests { - '.$self->hash_line_for_rule($pms, $rulename).' - dbg("rules-all: running full rule %s", q{'.$rulename.'}); - $hits = 0; -- while ($$fullmsgref =~ '.$pat.'g'. ($max? ' && $hits++ < '.$max:'') .') { -+ while ($$fullmsgref =~ /$qrptr->{q{'.$rulename.'}}/g'. ($max? ' && $hits++ < '.$max:'') .') { - $self->got_hit(q{'.$rulename.'}, "FULL: ", ruletype => "full"); - '. $self->hit_rule_plugin_code($pms, $rulename, "full", "last") . ' - } -@@ -1093,7 +1006,7 @@ sub do_head_eval_tests { - return unless (defined($pms->{conf}->{head_evals}->{$priority})); - dbg("rules: running head_eval tests; score so far=".$pms->{score}); - $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS, -- $pms->{conf}->{head_evals}->{$priority}, '', $priority); -+ 'head_evals', '', $priority); - } - - sub do_body_eval_tests { -@@ -1101,8 +1014,7 @@ sub do_body_eval_tests { - return unless (defined($pms->{conf}->{body_evals}->{$priority})); - dbg("rules: running body_eval tests; score so far=".$pms->{score}); - $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS, -- $pms->{conf}->{body_evals}->{$priority}, 'BODY: ', -- $priority, $bodystring); -+ 'body_evals', 'BODY: ', $priority, $bodystring); - } - - sub do_rawbody_eval_tests { -@@ -1110,8 +1022,7 @@ sub do_rawbody_eval_tests { - return unless (defined($pms->{conf}->{rawbody_evals}->{$priority})); - dbg("rules: running rawbody_eval tests; score so far=".$pms->{score}); - $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS, -- $pms->{conf}->{rawbody_evals}->{$priority}, 'RAW: ', -- $priority, $bodystring); -+ 'rawbody_evals', 'RAW: ', $priority, $bodystring); - } - - sub do_full_eval_tests { -@@ -1119,12 +1030,11 @@ sub do_full_eval_tests { - return unless (defined($pms->{conf}->{full_evals}->{$priority})); - dbg("rules: running full_eval tests; score so far=".$pms->{score}); - $self->run_eval_tests($pms, $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS, -- $pms->{conf}->{full_evals}->{$priority}, '', -- $priority, $fullmsgref); -+ 'full_evals', '', $priority, $fullmsgref); - } - - sub run_eval_tests { -- my ($self, $pms, $testtype, $evalhash, $prepend2desc, $priority, @extraevalargs) = @_; -+ my ($self, $pms, $testtype, $evalname, $prepend2desc, $priority, @extraevalargs) = @_; - - my $master_deadline = $pms->{master_deadline}; - if ($pms->{deadline_exceeded}) { -@@ -1159,7 +1069,7 @@ sub run_eval_tests { - && !$doing_user_rules) - { - my $method = "${package_name}::${methodname}"; -- # dbg("rules: run_eval_tests - calling previously compiled %s", $method); -+ #dbg("rules: run_eval_tests - calling previously compiled %s", $method); - my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline }); - my $err = $t->run(sub { - no strict "refs"; -@@ -1173,24 +1083,23 @@ sub run_eval_tests { - } - - # look these up once in advance to save repeated lookups in loop below -+ my $evalhash = $conf->{$evalname}->{$priority}; - my $tflagsref = $conf->{tflags}; -+ my $scoresref = $conf->{scores}; - my $eval_pluginsref = $conf->{eval_plugins}; - my $have_start_rules = $self->{main}->have_plugin("start_rules"); - my $have_ran_rule = $self->{main}->have_plugin("ran_rule"); - - # the buffer for the evaluated code -- my $evalstr = q{ }; -- $evalstr .= q{ my $function; }; -- -+ my $evalstr = ''; -+ - # conditionally include the dbg in the eval str -- my $dbgstr = q{ }; -+ my $dbgstr = ''; - if (would_log('dbg')) { -- $dbgstr = q{ -- dbg("rules: ran eval rule $rulename ======> got hit ($result)"); -- }; -+ $dbgstr = 'dbg("rules: ran eval rule $rulename ======> got hit ($result)");'; - } - -- while (my ($rulename, $test) = each %{$evalhash}) { -+ while (my ($rulename, $test) = each %{$evalhash}) { - if ($tflagsref->{$rulename}) { - # If the rule is a net rule, and we are in a non-net scoreset, skip it. - if ($tflagsref->{$rulename} =~ /\bnet\b/) { -@@ -1201,34 +1110,35 @@ sub run_eval_tests { - next if (($scoreset & 2) == 0); - } - } -+ -+ # skip if score zeroed -+ next if !$scoresref->{$rulename}; - -- $test = untaint_var($test); # presumably checked -- my ($function, $argstr) = ($test,''); -- if ($test =~ s/^([^,]+)(,.*)$//gs) { -- ($function, $argstr) = ($1,$2); -+ my $function = untaint_var($test->[0]); # was validated with \w+ -+ if (!$function) { -+ warn "rules: error: no eval function defined for $rulename"; -+ next; - } - -- if (!$function) { -- warn "rules: error: no function defined for $rulename"; -+ if (!exists $conf->{eval_plugins}->{$function}) { -+ warn("rules: error: unknown eval '$function' for $rulename\n"); - next; - } -- -+ - $evalstr .= ' -- if ($scoresptr->{q#'.$rulename.'#}) { -+ { - $rulename = q#'.$rulename.'#; - %{$self->{test_log_msgs}} = (); -- '; -+'; - - # only need to set current_rule_name for plugin evals - if ($eval_pluginsref->{$function}) { - # let plugins get the name of the rule that is currently being run, - # and ensure their eval functions exist - $evalstr .= ' -- -- $self->{current_rule_name} = $rulename; -- $self->register_plugin_eval_glue(q#'.$function.'#); -- -- '; -+ $self->{current_rule_name} = $rulename; -+ $self->register_plugin_eval_glue(q#'.$function.'#); -+'; - } - - # this stuff is quite slow, and totally superfluous if -@@ -1236,47 +1146,41 @@ sub run_eval_tests { - if ($have_start_rules) { - # XXX - should we use helper function here? - $evalstr .= ' -- - $self->{main}->call_plugins("start_rules", { - permsgstatus => $self, - ruletype => "eval", - priority => '.$priority.' - }); - -- '; -+'; - } -- -- $evalstr .= ' - -+ $evalstr .= ' - eval { -- $result = $self->' . $function . ' (@extraevalargs '. $argstr .' ); 1; -+ $result = $self->'.$function.'(@extraevalargs, @{$testptr->{q#'.$rulename.'#}->[1]}); 1; - } or do { - $result = 0; - die "rules: $@\n" if $@ =~ /__alarm__ignore__/; - $self->handle_eval_rule_errors($rulename); - }; -- -- '; -+'; - - if ($have_ran_rule) { - # XXX - should we use helper function here? - $evalstr .= ' -- - $self->{main}->call_plugins("ran_rule", { - permsgstatus => $self, ruletype => "eval", rulename => $rulename - }); -- -- '; -+'; - } - - $evalstr .= ' -- - if ($result) { - $self->got_hit($rulename, $prepend2desc, ruletype => "eval", value => $result); - '.$dbgstr.' - } - } -- '; -+'; - } - - # don't free the eval ruleset here -- we need it in the compiled code! -@@ -1288,16 +1192,15 @@ sub run_eval_tests { - { - package $package_name; - -- sub ${methodname} { -- my (\$self, \@extraevalargs) = \@_; -- -- my \$scoresptr = \$self->{conf}->{scores}; -- my \$prepend2desc = q#$prepend2desc#; -- my \$rulename; -- my \$result; -+ sub ${methodname} { -+ my (\$self, \@extraevalargs) = \@_; - -- $evalstr -- } -+ my \$testptr = \$self->{conf}->{$evalname}->{$priority}; -+ my \$prepend2desc = q#$prepend2desc#; -+ my \$rulename; -+ my \$result; -+ $evalstr -+ } - - 1; - } -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -24,7 +24,7 @@ use re 'taint'; - - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Locales; --use Mail::SpamAssassin::Util qw(untaint_var); -+use Mail::SpamAssassin::Util qw(untaint_var compile_regexp); - - our @ISA = qw(Mail::SpamAssassin::Plugin); - -@@ -57,13 +57,18 @@ sub new { - - sub html_tag_balance { - my ($self, $pms, undef, $rawtag, $rawexpr) = @_; -- $rawtag =~ /^([a-zA-Z0-9]+)$/; my $tag = $1; -- $rawexpr =~ /^([\<\>\=\!\-\+ 0-9]+)$/; my $expr = $1; -+ -+ return 0 if $rawtag !~ /^([a-zA-Z0-9]+)$/; -+ my $tag = $1; - - return 0 unless exists $pms->{html}{inside}{$tag}; - -+ return 0 if $rawexpr !~ /^([\<\>\=\!\-\+ 0-9]+)$/; -+ my $expr = untaint_var($1); -+ - $pms->{html}{inside}{$tag} =~ /^([\<\>\=\!\-\+ 0-9]+)$/; -- my $val = $1; -+ my $val = untaint_var($1); -+ - return eval "\$val $expr"; - } - -@@ -119,14 +124,14 @@ sub html_test { - - sub html_eval { - my ($self, $pms, undef, $test, $rawexpr) = @_; -- my $expr; -- if ($rawexpr =~ /^[\<\>\=\!\-\+ 0-9]+$/) { -- $expr = untaint_var($rawexpr); -- } -+ -+ return 0 if $rawexpr !~ /^([\<\>\=\!\-\+ 0-9]+)$/; -+ my $expr = untaint_var($1); -+ - # workaround bug 3320: wierd perl bug where additional, very explicit - # untainting into a new var is required. - my $tainted = $pms->{html}{$test}; -- return unless defined($tainted); -+ return 0 unless defined($tainted); - my $val = $tainted; - - # just use the value in $val, don't copy it needlessly -@@ -135,8 +140,14 @@ sub html_eval { - - sub html_text_match { - my ($self, $pms, undef, $text, $regexp) = @_; -- for my $string (@{ $pms->{html}{$text} }) { -- if (defined $string && $string =~ /${regexp}/) { -+ my ($rec, $err) = compile_regexp($regexp, 0); -+ if (!$rec) { -+ warn "htmleval: html_text_match invalid regexp '$regexp': $err"; -+ return 0; -+ } -+ foreach my $string (@{$pms->{html}{$text}}) { -+ next unless defined $string; -+ if ($string =~ $rec) { - return 1; - } - } -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/MIMEHeader.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/MIMEHeader.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/MIMEHeader.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/MIMEHeader.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -65,12 +65,15 @@ use re 'taint'; - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Conf; - use Mail::SpamAssassin::Logger; --use Mail::SpamAssassin::Util qw(untaint_var); -+use Mail::SpamAssassin::Util qw(untaint_var compile_regexp); -+use Mail::SpamAssassin::Constants qw(:sa); - - our @ISA = qw(Mail::SpamAssassin::Plugin); - - our @TEMPORARY_METHODS; - -+my $RULENAME_RE = RULENAME_RE; -+ - # --------------------------------------------------------------------------- - - # constructor -@@ -101,27 +104,37 @@ sub set_config { - is_priv => 1, - code => sub { - my ($self, $key, $value, $line) = @_; -- local ($1,$2,$3,$4); -- if ($value !~ /^(\S+)\s+(\S+)\s*([\=\!]\~)\s*(.+)$/) { -+ local ($1,$2,$3); -+ if ($value !~ s/^(${RULENAME_RE})\s+//) { - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } -- -- # provide stricter syntax for rule name!? - my $rulename = untaint_var($1); -- my $hdrname = $2; -- my $negated = ($3 eq '!~') ? 1 : 0; -- my $pattern = $4; -- -- return unless $self->{parser}->is_delimited_regexp_valid($rulename, $pattern); -- -- $pattern = Mail::SpamAssassin::Util::make_qr($pattern); -- return $Mail::SpamAssassin::Conf::INVALID_VALUE unless $pattern; -+ if ($value eq '') { -+ return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; -+ } -+ # Take :raw to hdrname! -+ if ($value !~ /^([^:\s]+(?:\:(?:raw)?)?)\s*([=!]~)\s*(.+)$/) { -+ return $Mail::SpamAssassin::Conf::INVALID_VALUE; -+ } -+ my $hdrname = $1; -+ my $negated = $2 eq '!~' ? 1 : 0; -+ my $pattern = $3; -+ $hdrname =~ s/:$//; -+ my $if_unset = ''; -+ if ($pattern =~ s/\s+\[if-unset:\s+(.+)\]$//) { -+ $if_unset = $1; -+ } -+ my ($rec, $err) = compile_regexp($pattern, 1); -+ if (!$rec) { -+ info("mimeheader: invalid regexp for $rulename '$pattern': $err"); -+ return $Mail::SpamAssassin::Conf::INVALID_VALUE; -+ } - - $self->{mimeheader_tests}->{$rulename} = { - hdr => $hdrname, - negated => $negated, -- if_unset => '', # TODO! -- pattern => $pattern -+ if_unset => $if_unset, -+ pattern => $rec - }; - - # now here's a hack; generate a fake eval rule function to -@@ -129,7 +142,6 @@ sub set_config { - # TODO: we should have a more elegant way for new rule types to - # be defined - my $evalfn = "_mimeheader_eval_$rulename"; -- $evalfn =~ s/[^a-zA-Z0-9_]/_/gs; - - # don't redefine the subroutine if it already exists! - # this causes lots of annoying warnings and such during things like -@@ -139,6 +151,7 @@ sub set_config { - $self->{parser}->add_test($rulename, $evalfn."()", - $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS); - -+ # evalfn/rulename safe, sanitized by $RULENAME_RE - my $evalcode = ' - sub Mail::SpamAssassin::Plugin::MIMEHeader::'.$evalfn.' { - $_[0]->eval_hook_called($_[1], q{'.$rulename.'}); -@@ -175,7 +188,7 @@ sub eval_hook_called { - - - my $getraw; -- if ($hdr =~ s/:raw$//i) { -+ if ($hdr =~ s/:raw$//) { - $getraw = 1; - } else { - $getraw = 0; -@@ -188,9 +201,9 @@ sub eval_hook_called { - } else { - $val = $p->get_header($hdr); - } -- $val ||= $if_unset; -+ $val = $if_unset if !defined $val; - -- if ($val =~ ${pattern}) { -+ if ($val =~ $pattern) { - return ($negated ? 0 : 1); - } - } -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/PDFInfo.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/PDFInfo.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/PDFInfo.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/PDFInfo.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -142,7 +142,7 @@ package Mail::SpamAssassin::Plugin::PDFI - - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Logger; --use Mail::SpamAssassin::Util; -+use Mail::SpamAssassin::Util qw(compile_regexp); - use strict; - use warnings; - # use bytes; -@@ -471,16 +471,15 @@ sub pdf_name_regex { - return 0 if (exists $pms->{'pdfinfo'}->{'no_parts'}); - return 0 unless (exists $pms->{'pdfinfo'}->{"names_pdf"}); - -+ my ($rec, $err) = compile_regexp($re, 2); -+ if (!$rec) { -+ info("pdfinfo: invalid regexp '$re': $err"); -+ return 0; -+ } -+ - my $hit = 0; - foreach my $name (keys %{$pms->{'pdfinfo'}->{"names_pdf"}}) { -- eval { -- my $regex = Mail::SpamAssassin::Util::make_qr($re); -- if ( $name =~ m/$regex/ ) { -- $hit = 1; -- } -- }; -- dbg("pdfinfo: error in regex $re - $@") if $@; -- if ($hit) { -+ if ($name =~ $rec) { - dbg("pdfinfo: pdf_name_regex hit on $name"); - return 1; - } -@@ -722,15 +721,13 @@ sub pdf_match_details { - my $check_value = $pms->{pdfinfo}->{details}->{$detail}; - return unless $check_value; - -- my $hit = 0; -- eval { -- my $re = Mail::SpamAssassin::Util::make_qr($regex); -- if ( $check_value =~ m/$re/ ) { -- $hit = 1; -- } -- }; -- dbg("pdfinfo: error in regex $regex - $@") if $@; -- if ($hit) { -+ my ($rec, $err) = compile_regexp($regex, 2); -+ if (!$rec) { -+ info("pdfinfo: invalid regexp '$regex': $err"); -+ return 0; -+ } -+ -+ if ($check_value =~ $rec) { - dbg("pdfinfo: pdf_match_details $detail $regex matches $check_value"); - return 1; - } -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -52,6 +52,7 @@ package Mail::SpamAssassin::Plugin::Repl - use Mail::SpamAssassin; - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Logger; -+use Mail::SpamAssassin::Util qw(compile_regexp qr_to_string); - - use strict; - use warnings; -@@ -73,6 +74,16 @@ sub new { - return $self; - } - -+sub finish_parsing_start { -+ my ($self, $opts) = @_; -+ -+ # keeps track of replaced rules -+ # don't have $pms in finish_parsing_end() so init this.. -+ $self->{replace_rules_done} = {}; -+ -+ return 1; -+} -+ - sub finish_parsing_end { - my ($self, $opts) = @_; - -@@ -82,94 +93,96 @@ sub finish_parsing_end { - my $start = $conf->{replace_start}; - my $end = $conf->{replace_end}; - -- # this is the version-specific code -- for my $type (qw|body_tests rawbody_tests head_tests full_tests uri_tests|) { -- for my $priority (keys %{$conf->{$type}}) { -- while (my ($rule, $re) = each %{$conf->{$type}->{$priority}}) { -- # skip if not listed by replace_rules -- next unless $conf->{rules_to_replace}{$rule}; -- -- if (would_log('dbg', 'replacetags') > 1) { -- dbg("replacetags: replacing $rule: $re"); -- } -- -- my $passes = 0; -- my $doagain; -+ foreach my $rule (keys %{$conf->{replace_rules}}) { -+ # process rules only once, mark to replace_rules_done, -+ # do NOT delete $conf->{replace_rules}, it's used by BodyRuleExtractor -+ next if exists $self->{replace_rules_done}->{$rule}; -+ $self->{replace_rules_done}->{$rule} = 1; -+ -+ if (!exists $conf->{test_qrs}->{$rule}) { -+ dbg("replacetags: replace requested for non-existing rule: $rule\n"); -+ next; -+ } - -- do { -- my $pre_name; -- my $post_name; -- my $inter_name; -- $doagain = 0; -- -- # get modifier tags -- if ($re =~ s/${start}pre (.+?)${end}//) { -- $pre_name = $1; -- } -- if ($re =~ s/${start}post (.+?)${end}//) { -- $post_name = $1; -- } -- if ($re =~ s/${start}inter (.+?)${end}//) { -- $inter_name = $1; -- } -+ my $re = qr_to_string($conf->{test_qrs}->{$rule}); -+ next unless defined $re; -+ my $origre = $re; -+ -+ my $passes = 0; -+ my $doagain; -+ -+ do { -+ my $pre_name; -+ my $post_name; -+ my $inter_name; -+ $doagain = 0; -+ -+ # get modifier tags -+ if ($re =~ s/${start}pre (.+?)${end}//) { -+ $pre_name = $1; -+ } -+ if ($re =~ s/${start}post (.+?)${end}//) { -+ $post_name = $1; -+ } -+ if ($re =~ s/${start}inter (.+?)${end}//) { -+ $inter_name = $1; -+ } - -- # this will produce an array of tags to be replaced -- # for two adjacent tags, an element of "" will be between the two -- my @re = split(/(<[^<>]+>)/, $re); -- -- if ($pre_name) { -- my $pre = $conf->{replace_pre}->{$pre_name}; -- if ($pre) { -- s{($start.+?$end)}{$pre$1} for @re; -- } -- } -- if ($post_name) { -- my $post = $conf->{replace_post}->{$post_name}; -- if ($post) { -- s{($start.+?$end)}{$1$post}g for @re; -- } -- } -- if ($inter_name) { -- my $inter = $conf->{replace_inter}->{$inter_name}; -- if ($inter) { -- s{^$}{$inter} for @re; -- } -- } -- for (my $i = 0; $i < @re; $i++) { -- if ($re[$i] =~ m|$start(.+?)$end|g) { -- my $tag_name = $1; -- # if the tag exists, replace it with the corresponding phrase -- if ($tag_name) { -- my $replacement = $conf->{replace_tag}->{$tag_name}; -- if ($replacement) { -- $re[$i] =~ s|$start$tag_name$end|$replacement|g; -- $doagain = 1 if !$doagain && $replacement =~ /<[^>]+>/; -- } -- } -+ # this will produce an array of tags to be replaced -+ # for two adjacent tags, an element of "" will be between the two -+ my @re = split(/(<[^<>]+>)/, $re); -+ -+ if ($pre_name) { -+ my $pre = $conf->{replace_pre}->{$pre_name}; -+ if ($pre) { -+ s{($start.+?$end)}{$pre$1} for @re; -+ } -+ } -+ if ($post_name) { -+ my $post = $conf->{replace_post}->{$post_name}; -+ if ($post) { -+ s{($start.+?$end)}{$1$post}g for @re; -+ } -+ } -+ if ($inter_name) { -+ my $inter = $conf->{replace_inter}->{$inter_name}; -+ if ($inter) { -+ s{^$}{$inter} for @re; -+ } -+ } -+ for (my $i = 0; $i < @re; $i++) { -+ if ($re[$i] =~ m|$start(.+?)$end|g) { -+ my $tag_name = $1; -+ # if the tag exists, replace it with the corresponding phrase -+ if ($tag_name) { -+ my $replacement = $conf->{replace_tag}->{$tag_name}; -+ if ($replacement) { -+ $re[$i] =~ s|$start$tag_name$end|$replacement|g; -+ $doagain = 1 if !$doagain && $replacement =~ /<[^>]+>/; - } - } -+ } -+ } - -- $re = join('', @re); -- -- # do the actual replacement -- $conf->{$type}->{$priority}->{$rule} = $re; -+ $re = join('', @re); - -- if (would_log('dbg', 'replacetags') > 1) { -- dbg("replacetags: replaced $rule: $re"); -- } -+ $passes++; -+ } while $doagain && $passes <= 5; - -- $passes++; -- } while $doagain && $passes <= 5; -+ if ($re ne $origre) { -+ # do the actual replacement -+ my ($rec, $err) = compile_regexp($re, 0); -+ if (!$rec) { -+ info("replacetags: regexp compilation failed '$re': $err"); -+ next; - } -+ $conf->{test_qrs}->{$rule} = $rec; -+ #dbg("replacetags: replaced $rule: '$origre' => '$re'"); -+ dbg("replacetags: replaced $rule"); -+ } else { -+ dbg("replacetags: nothing was replaced in $rule"); - } - } -- -- # free this up, if possible -- if (!$conf->{allow_user_rules}) { -- delete $conf->{rules_to_replace}; -- } -- -- dbg("replacetags: done replacing tags"); - } - - sub user_conf_parsing_end { -@@ -250,6 +263,7 @@ body, header, uri, full, rawbody tests a - push(@cmds, { - setting => 'replace_rules', - is_priv => 1, -+ default => {}, - type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, - code => sub { - my ($self, $key, $value, $line) = @_; -@@ -259,8 +273,8 @@ body, header, uri, full, rawbody tests a - unless ($value =~ /\S+/) { - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } -- foreach my $rule (split(' ', $value)) { -- $conf->{rules_to_replace}->{$rule} = 1; -+ foreach my $rule (split(/\s+/, $value)) { -+ $self->{replace_rules}->{$rule} = 1; - } - } - }); -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/Rule2XSBody.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -38,6 +38,7 @@ package Mail::SpamAssassin::Plugin::Rule - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Logger; - use Mail::SpamAssassin::Plugin::OneLineBodyRuleType; -+use Mail::SpamAssassin::Util qw(qr_to_string); - - use strict; - use warnings; -@@ -120,24 +121,25 @@ sub setup_test_set_pri { - - my $found = 0; - foreach my $name (keys %{$rules}) { -- my $rule = $rules->{$name}; -+ #my $rule = $rules->{$name}; -+ my $rule = qr_to_string($conf->{test_qrs}->{$name}); - my $comprule = $hasrules->{$longname{$name} || ''}; - $rule =~ s/\#/\[hash\]/gs; - -- if (!$comprule) { -+ if (!$comprule) { - # this is pretty common, based on rule complexity; don't warn - # dbg "zoom: skipping rule $name, not in compiled ruleset"; - next; - } - if ($comprule ne $rule) { -- dbg "zoom: skipping rule $name, code differs in compiled ruleset"; -+ dbg "zoom: skipping rule $name, code differs in compiled ruleset '$comprule' '$rule'"; - next; - } - - # ignore rules marked for ReplaceTags work! - # TODO: we should be able to order the 'finish_parsing_end' - # plugin calls to do this. -- if ($conf->{rules_to_replace}->{$name}) { -+ if ($conf->{replace_rules}->{$name}) { - dbg "zoom: skipping rule $name, ReplaceTags"; - next; - } -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/URIDetail.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/URIDetail.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Plugin/URIDetail.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Plugin/URIDetail.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -68,7 +68,7 @@ Regular expressions should be delimited - package Mail::SpamAssassin::Plugin::URIDetail; - use Mail::SpamAssassin::Plugin; - use Mail::SpamAssassin::Logger; --use Mail::SpamAssassin::Util qw(untaint_var); -+use Mail::SpamAssassin::Util qw(untaint_var compile_regexp); - - use strict; - use warnings; -@@ -122,22 +122,23 @@ sub set_config { - if ($target !~ /^(?:raw|type|cleaned|text|domain)$/) { - return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } -- if ($conf->{parser}->is_delimited_regexp_valid($name, $pattern)) { -- $pattern = $pluginobj->make_qr($pattern); -- } -- else { -- return $Mail::SpamAssassin::Conf::INVALID_VALUE; -+ -+ my ($rec, $err) = compile_regexp($pattern, 1); -+ if (!$rec) { -+ dbg("config: uri_detail invalid regexp '$pattern': $err"); -+ return $Mail::SpamAssassin::Conf::INVALID_VALUE; - } - -- dbg("config: uri_detail adding ($target $op /$pattern/) to $name"); -+ dbg("config: uri_detail adding ($target $op /$rec/) to $name"); - $conf->{parser}->{conf}->{uri_detail}->{$name}->{$target} = -- [$op, $pattern]; -+ [$op, $rec]; - $added_criteria = 1; - } - - if ($added_criteria) { - dbg("config: uri_detail added $name\n"); -- $conf->{parser}->add_test($name, 'check_uri_detail()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS); -+ $conf->{parser}->add_test($name, 'check_uri_detail()', -+ $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS); - } - else { - warn "config: failed to add invalid rule $name"; -@@ -163,8 +164,8 @@ sub check_uri_detail { - - if (exists $rule->{raw}) { - my($op,$patt) = @{$rule->{raw}}; -- if ( ($op eq '=~' && $raw =~ /$patt/) || -- ($op eq '!~' && $raw !~ /$patt/) ) { -+ if ( ($op eq '=~' && $raw =~ $patt) || -+ ($op eq '!~' && $raw !~ $patt) ) { - dbg("uri: raw matched: '%s' %s /%s/", $raw,$op,$patt); - } else { - next; -@@ -176,8 +177,8 @@ sub check_uri_detail { - my($op,$patt) = @{$rule->{type}}; - my $match; - for my $text (keys %{ $info->{types} }) { -- if ( ($op eq '=~' && $text =~ /$patt/) || -- ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } -+ if ( ($op eq '=~' && $text =~ $patt) || -+ ($op eq '!~' && $text !~ $patt) ) { $match = $text; last } - } - next unless defined $match; - dbg("uri: type matched: '%s' %s /%s/", $match,$op,$patt); -@@ -188,8 +189,8 @@ sub check_uri_detail { - my($op,$patt) = @{$rule->{cleaned}}; - my $match; - for my $text (@{ $info->{cleaned} }) { -- if ( ($op eq '=~' && $text =~ /$patt/) || -- ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } -+ if ( ($op eq '=~' && $text =~ $patt) || -+ ($op eq '!~' && $text !~ $patt) ) { $match = $text; last } - } - next unless defined $match; - dbg("uri: cleaned matched: '%s' %s /%s/", $match,$op,$patt); -@@ -200,8 +201,8 @@ sub check_uri_detail { - my($op,$patt) = @{$rule->{text}}; - my $match; - for my $text (@{ $info->{anchor_text} }) { -- if ( ($op eq '=~' && $text =~ /$patt/) || -- ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } -+ if ( ($op eq '=~' && $text =~ $patt) || -+ ($op eq '!~' && $text !~ $patt) ) { $match = $text; last } - } - next unless defined $match; - dbg("uri: text matched: '%s' %s /%s/", $match,$op,$patt); -@@ -212,8 +213,8 @@ sub check_uri_detail { - my($op,$patt) = @{$rule->{domain}}; - my $match; - for my $text (keys %{ $info->{domains} }) { -- if ( ($op eq '=~' && $text =~ /$patt/) || -- ($op eq '!~' && $text !~ /$patt/) ) { $match = $text; last } -+ if ( ($op eq '=~' && $text =~ $patt) || -+ ($op eq '!~' && $text !~ $patt) ) { $match = $text; last } - } - next unless defined $match; - dbg("uri: domain matched: '%s' %s /%s/", $match,$op,$patt); -@@ -235,29 +236,5 @@ sub check_uri_detail { - } - - # --------------------------------------------------------------------------- -- --# turn "/foobar/i" into qr/(?i)foobar/ --sub make_qr { -- my ($self, $pattern) = @_; -- -- my $re_delim; -- if ($pattern =~ s/^m(\W)//) { # m!foo/bar! -- $re_delim = $1; -- } else { # /foo\/bar/ or !foo/bar! -- $pattern =~ s/^(\W)//; $re_delim = $1; -- } -- if (!$re_delim) { -- return; -- } -- -- $pattern =~ s/${re_delim}([imsx]*)$//; -- -- my $mods = $1; -- if ($mods) { $pattern = "(?".$mods.")".$pattern; } -- -- return qr/$pattern/; --} -- --# --------------------------------------------------------------------------- - - 1; -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/PluginHandler.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/PluginHandler.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/PluginHandler.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/PluginHandler.pm 2020-03-12 14:01:47.279350986 +0100 -@@ -74,6 +74,13 @@ sub new { - sub load_plugin { - my ($self, $package, $path, $silent) = @_; - -+ # Strict name checking -+ if ($package !~ /^(?:\w+::){0,10}\w+$/) { -+ warn "plugin: illegal plugin name, not loading: $package\n"; -+ return; -+ } -+ $package = Mail::SpamAssassin::Util::untaint_var($package); -+ - # Don't load the same plugin twice! - # Do this *before* calling ->new(), otherwise eval rules will be - # registered on a nonexistent object -@@ -86,6 +93,13 @@ sub load_plugin { - - my $ret; - if ($path) { -+ if ($path !~ /^\S+\.pm/i) { -+ warn "plugin: illegal plugin filename, not loading: $path"; -+ return; -+ } -+ -+ $path = $self->{main}->{conf}->{parser}->fix_path_relative_to_current_file($path); -+ - # bug 3717: - # At least Perl 5.8.0 seems to confuse $cwd internally at some point -- we - # need to use an absolute path here else we get a "File not found" error. -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Util.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Util.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Util.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Util.pm 2020-03-12 14:01:47.280350997 +0100 -@@ -57,7 +57,8 @@ our @EXPORT_OK = qw(&local_tz &base64_de - &exit_status_str &proc_status_ok &am_running_on_windows - &reverse_ip_address &decode_dns_question_entry - &get_my_locales &parse_rfc822_date &get_user_groups -- &secure_tmpfile &secure_tmpdir &uri_list_canonicalize); -+ &secure_tmpfile &secure_tmpdir &uri_list_canonicalize -+ &compile_regexp &qr_to_string); - - our $AM_TAINTED; - -@@ -1097,7 +1098,8 @@ with Perl. - sub first_available_module { - my (@packages) = @_; - foreach my $mod (@packages) { -- if (eval 'require '.$mod.'; 1; ') { -+ next if $mod !~ /^[\w:]+$/; # be paranoid -+ if (eval 'require '.$mod.'; 1;') { - return $mod; - } - } -@@ -1228,6 +1230,8 @@ sub secure_tmpdir { - ## Replaced with Mail::SpamAssassin::RegistryBoundaries::uri_to_domain. - ## - -+########################################################################### -+ - *uri_list_canonify = \&uri_list_canonicalize; # compatibility alias - sub uri_list_canonicalize { - my($redirector_patterns, @uris) = @_; -@@ -1690,6 +1694,157 @@ sub trap_sigalrm_fully { - - ########################################################################### - -+# returns ($compiled_re, $error) -+# if any errors, $compiled_re = undef, $error has string -+# args: -+# - regexp -+# - strip_delimiters (default: 1) (value 2 means, try strip, but don't error) -+# - ignore_always_matching (default: 0) -+sub compile_regexp { -+ my ($re, $strip_delimiters, $ignore_always_matching) = @_; -+ local($1); -+ -+ # Do not allow already compiled regexes or other funky refs -+ if (ref($re)) { -+ return (undef, 'ref passed'); -+ } -+ -+ # try stripping by default -+ $strip_delimiters = 1 if !defined $strip_delimiters; -+ -+ # OK, try to remove any normal perl-style regexp delimiters at -+ # the start and end, and modifiers at the end if present, -+ # so we can validate those too. -+ my $origre = $re; -+ my $delim_end = ''; -+ -+ if ($strip_delimiters >= 1) { -+ # most common delimiter -+ if ($re =~ s{^/}{}) { -+ $delim_end = '/'; -+ } -+ # symmetric delimiters -+ elsif ($re =~ s/^(?:m|qr)([\{\(\<\[])//) { -+ ($delim_end = $1) =~ tr/\{\(\<\[/\}\)\>\]/; -+ } -+ # any non-wordchar delimiter, but let's ignore backslash.. -+ elsif ($re =~ s/^(?:m|qr)(\W)//) { -+ $delim_end = $1; -+ if ($delim_end eq '\\') { -+ return (undef, 'backslash delimiter not allowed'); -+ } -+ } -+ elsif ($strip_delimiters != 2) { -+ return (undef, 'missing regexp delimiters'); -+ } -+ } -+ -+ # cut end delimiter, mods -+ my $mods; -+ if ($delim_end) { -+ # Ignore e because paranoid -+ if ($re =~ s/\Q${delim_end}\E([a-df-z]*)\z//) { -+ $mods = $1; -+ } else { -+ return (undef, 'invalid end delimiter/mods'); -+ } -+ } -+ -+ # paranoid check for eval exec (?{foo}), in case someone -+ # actually put "use re 'eval'" somewhere.. -+ if ($re =~ /\(\?\??\{/) { -+ return (undef, 'eval (?{}) found'); -+ } -+ -+ # check unescaped delimiter, but only if it's not symmetric, -+ # those will fp on .{0,10} [xyz] etc, no need for so strict checks -+ # since these regexes don't end up in eval strings anyway -+ if ($delim_end && $delim_end !~ tr/\}\)\]//) { -+ # first we remove all escaped backslashes "\\" -+ my $dbs_stripped = $re; -+ $dbs_stripped =~ s/\\\\//g; -+ # now we can properly check if something is unescaped -+ if ($dbs_stripped =~ /(? '%s'", $origre, $compiled_re); -+ return ($compiled_re, ''); -+ } else { -+ my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err; -+ $err =~ s/ at .*? line \d.*$//; -+ return (undef, $err); -+ } -+} -+ -+sub is_always_matching_regexp { -+ my ($re) = @_; -+ -+ if ($re eq '') { -+ return "empty"; -+ } -+ elsif ($re =~ /(? -+ elsif ($re =~ s/^m? - $delim = '>'; - } -- elsif ($re =~ s/^m(\W)//) { # m#foo/bar# -+ elsif ($re =~ s/^m?(\W)//) { # m#foo/bar# - $delim = $1; - } else { # /foo\/bar/ or !foo/bar! -- $re =~ s/^(\W)//; $delim = $1; -+ return undef; # invalid - } - -- $re =~ s/\Q${delim}\E([imsx]*)$// or warn "unbalanced re: $re"; -+ if ($re !~ s/\Q${delim}\E([imsx]*)$//) { -+ return undef; -+ } - - my $mods = $1; - if ($mods) { -@@ -1732,8 +1893,17 @@ sub regexp_remove_delimiters { - - sub make_qr { - my ($re) = @_; -+ -+ warn("deprecated Util make_qr() called\n"); -+ - $re = regexp_remove_delimiters($re); -- return qr/$re/; -+ return undef if !defined $re || $re eq ''; -+ my $compiled_re; -+ if (eval { $compiled_re = qr/$re/; 1; } && ref($compiled_re) eq 'Regexp') { -+ return $compiled_re; -+ } else { -+ return undef; -+ } - } - - ########################################################################### -diff -urp Mail-SpamAssassin-3.4.2.old/t/dnsbl.t Mail-SpamAssassin-3.4.2/t/dnsbl.t ---- Mail-SpamAssassin-3.4.2.old/t/dnsbl.t 2018-09-14 03:27:53.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/t/dnsbl.t 2020-03-12 14:01:47.280350997 +0100 -@@ -7,7 +7,7 @@ use Test::More; - plan skip_all => "Long running tests disabled" unless conf_bool('run_long_tests'); - plan skip_all => "Net tests disabled" unless conf_bool('run_net_tests'); - plan skip_all => "Can't use Net::DNS Safely" unless can_use_net_dns_safely(); --plan tests => 23; -+plan tests => 17; - - # --------------------------------------------------------------------------- - # bind configuration currently used to support this test -@@ -54,7 +54,6 @@ EOF - q{ [127.0.0.1] } => 'P_4', - q{ [127.0.0.1] } => 'P_5', - q{ [127.0.0.2] } => 'P_6', -- q{ } => 'P_7', - q{,DNSBL_TEST_TOP,} => 'P_8', - q{,DNSBL_TEST_WHITELIST,} => 'P_9', - q{,DNSBL_TEST_DYNAMIC,} => 'P_10', -@@ -63,16 +62,11 @@ EOF - q{,DNSBL_TXT_TOP,} => 'P_13', - q{,DNSBL_TXT_RE,} => 'P_14', - q{,DNSBL_RHS,} => 'P_15', -- q{,DNSBL_SB_TIME,} => 'P_16', -- q{,DNSBL_SB_FLOAT,} => 'P_17', -- q{,DNSBL_SB_STR,} => 'P_18', - ); - - %anti_patterns = ( - q{,DNSBL_TEST_MISS,} => 'P_19', - q{,DNSBL_TXT_MISS,} => 'P_20', -- q{,DNSBL_SB_UNDEF,} => 'P_21', -- q{,DNSBL_SB_MISS,} => 'P_22', - q{ launching DNS A query for 14.35.17.212.untrusted.dnsbltest.spamassassin.org. } => 'untrusted', - ); - -@@ -136,28 +130,6 @@ header DNSBL_RHS eval:check_rbl_from_hos - describe DNSBL_RHS DNSBL RHS match - tflags DNSBL_RHS net - --header __TEST_SENDERBASE eval:check_rbl_txt('sb', 'sb.dnsbltest.spamassassin.org.') --tflags __TEST_SENDERBASE net -- --header DNSBL_SB_TIME eval:check_rbl_sub('sb', 'sb:S6 == 1060085863 && S6 < time') --describe DNSBL_SB_TIME DNSBL SenderBase time --tflags DNSBL_SB_TIME net -- --header DNSBL_SB_FLOAT eval:check_rbl_sub('sb', 'sb:S3 > 7.0 && S3 < 7.2') --describe DNSBL_SB_FLOAT DNSBL SenderBase floating point --tflags DNSBL_SB_FLOAT net -- --header DNSBL_SB_STR eval:check_rbl_sub('sb', 'sb:S1 eq \"Spammer Networks\" && S49 !~ /Y/ && index(S21, \".com\") > 0') --describe DNSBL_SB_STR DNSBL SenderBase strings --tflags DNSBL_SB_STR net -- --header DNSBL_SB_UNDEF eval:check_rbl_sub('sb', 'sb:S98 =~ /foo/ && S99 > 10') --describe DNSBL_SB_UNDEF DNSBL SenderBase undefined --tflags DNSBL_SB_UNDEF net -- --header DNSBL_SB_MISS eval:check_rbl_sub('sb', 'sb:S2 < 3.0') --describe DNSBL_SB_MISS DNSBL SenderBase miss --tflags DNSBL_SB_MISS net - "); - - # The -D clobbers test performance but some patterns & antipatterns depend on debug output -diff -urp Mail-SpamAssassin-3.4.2.old/t/if_can.t Mail-SpamAssassin-3.4.2/t/if_can.t ---- Mail-SpamAssassin-3.4.2.old/t/if_can.t 2018-09-14 03:27:53.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/t/if_can.t 2020-03-12 14:01:47.280350997 +0100 -@@ -2,7 +2,7 @@ - - use lib '.'; use lib 't'; - use SATest; sa_t_init("if_can"); --use Test::More tests => 13; -+use Test::More tests => 16; - - # --------------------------------------------------------------------------- - -@@ -16,6 +16,9 @@ use Test::More tests => 13; - q{ SHOULD_BE_CALLED5 }, 'should_be_called5', - q{ SHOULD_BE_CALLED6 }, 'should_be_called6', - q{ SHOULD_BE_CALLED7 }, 'should_be_called7', -+ q{ SHOULD_BE_CALLED8 }, 'should_be_called8', -+ q{ SHOULD_BE_CALLED9 }, 'should_be_called9', -+ q{ SHOULD_BE_CALLED10 }, 'should_be_called10', - - ); - %anti_patterns = ( -@@ -51,6 +54,15 @@ tstlocalrules (q{ - if (!can(Mail::SpamAssassin::Plugin::Test::test_feature_xxxx_nosuch)) - body SHOULD_BE_CALLED7 /./ - endif -+ if can(Mail::SpamAssassin::Plugin::Test::test_feature_xxxx_true) && version > 0.00000 -+ body SHOULD_BE_CALLED8 /./ -+ endif -+ if !can(Mail::SpamAssassin::Plugin::Test::test_feature_xxxx_false ) && !(! version > 0.00000) -+ body SHOULD_BE_CALLED9 /./ -+ endif -+ if has(Mail::SpamAssassin::Plugin::Test::test_feature_xxxx_true) && (!can(Mail::SpamAssassin::Plugin::Test::test_feature_xxxx_nosuch)) -+ body SHOULD_BE_CALLED10 /./ -+ endif - - if !has(Mail::SpamAssassin::Plugin::Test::check_test_plugin) - body SHOULD_NOT_BE_CALLED1 /./ -diff -urp Mail-SpamAssassin-3.4.2.old/t/mimeheader.t Mail-SpamAssassin-3.4.2/t/mimeheader.t ---- Mail-SpamAssassin-3.4.2.old/t/mimeheader.t 2018-09-14 03:27:53.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/t/mimeheader.t 2020-03-12 14:01:47.280350997 +0100 -@@ -2,7 +2,7 @@ - - use lib '.'; use lib 't'; - use SATest; sa_t_init("mimeheader"); --use Test::More tests => 4; -+use Test::More tests => 6; - - $ENV{'LANGUAGE'} = $ENV{'LC_ALL'} = 'C'; # a cheat, but we need the patterns to work - -@@ -14,18 +14,33 @@ $ENV{'LANGUAGE'} = $ENV{'LC_ALL'} = 'C'; - q{ MIMEHEADER_TEST2 }, q{ test2 }, - q{ MATCH_NL_NONRAW }, q{ match_nl_nonraw }, - q{ MATCH_NL_RAW }, q{ match_nl_raw }, -+ q{ MIMEHEADER_FOUND }, q{ unset_found }, - - ); - -+%anti_patterns = ( -+ -+ q{ MIMEHEADER_NOTFOUND }, q{ unset_notfound }, -+ -+); -+ -+tstpre(q{ -+ -+ loadplugin Mail::SpamAssassin::Plugin::MIMEHeader -+ -+}); -+ - tstprefs (q{ - -- # loadplugin Mail::SpamAssassin::Plugin::MIMEHeader - mimeheader MIMEHEADER_TEST1 content-type =~ /application\/msword/ - mimeheader MIMEHEADER_TEST2 content-type =~ m!APPLICATION/MSWORD!i - - mimeheader MATCH_NL_NONRAW Content-Type =~ /msword; name/ - mimeheader MATCH_NL_RAW Content-Type:raw =~ /msword;\n\tname/ - -+ mimeheader MIMEHEADER_NOTFOUND xyzzy =~ /foobar/ -+ mimeheader MIMEHEADER_FOUND xyzzy =~ /foobar/ [if-unset: xyzfoobarxyz] -+ - }); - - sarun ("-L -t < data/nice/004", \&patterns_run_cb); -diff -urp Mail-SpamAssassin-3.4.2.old/t/regexp_valid.t Mail-SpamAssassin-3.4.2/t/regexp_valid.t ---- Mail-SpamAssassin-3.4.2.old/t/regexp_valid.t 2018-09-14 03:27:54.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2/t/regexp_valid.t 2020-03-12 14:01:47.280350997 +0100 -@@ -18,55 +18,34 @@ if (-e 'test_dir') { # runnin - use strict; - use lib '.'; use lib 't'; - use SATest; sa_t_init("regexp_valid"); -+use Mail::SpamAssassin::Util qw(compile_regexp); - --use Test::More tests => 24; -- --# initialize SpamAssassin --use Mail::SpamAssassin; --my $sa = create_saobj({'dont_copy_prefs' => 1}); --$sa->init(0); # parse rules -- -- --# make a _copy_ of the STDERR file descriptor --# (so we can restore it after redirecting it) --open(OLDERR, ">&STDERR") || die "Cannot copy STDERR file handle"; -- --# create a file descriptior for logging STDERR --# (we do not want warnings for regexps we know are invalid) --my $fh = IO::File->new_tmpfile(); --open(LOGERR, ">&".fileno($fh)) || die "Cannot create LOGERR temp file"; -- --# quiet "used only once" warnings --1 if *OLDERR; --1 if *LOGERR; -- -+use Test::More tests => 41; - -+my $showerr; - sub tryone { -- my $re = shift; -- return $sa->{conf}->{parser}->is_regexp_valid('test', $re); -+ my ($re, $strip) = @_; -+ $strip = 1 if !defined $strip; -+ my ($rec, $err) = compile_regexp($re, $strip, 1); -+ if (!$rec && $showerr) { print STDERR "invalid regex '$re': $err\n"; } -+ return $rec; - } - - # test valid regexps with this sub - sub goodone { -- my $re = shift; -- open(STDERR, ">&=OLDERR") || die "Cannot reopen STDERR"; -- return tryone $re; -+ my ($re, $strip) = @_; -+ $showerr = 1; -+ return tryone($re, $strip); - } - - # test invalid regexps with this sub - sub badone { -- my $re = shift; -- open(STDERR, ">&=LOGERR") || die "Cannot reopen STDERR (for logging)"; -- return !tryone $re; -+ my ($re, $strip) = @_; -+ $showerr = 0; -+ return !tryone($re, $strip); - } - - --ok goodone qr/foo bar/; --ok goodone qr/foo bar/i; --ok goodone qr/foo bar/is; --ok goodone qr/foo bar/im; --ok goodone qr!foo bar!im; -- - ok goodone 'qr/foo bar/'; - ok goodone 'qr/foo bar/im'; - ok goodone 'qr!foo bar!'; -@@ -80,14 +59,38 @@ ok goodone 'm{foo bar}is'; - ok goodone 'm(foo bar)is'; - - ok goodone 'mis'; --ok goodone 'foo bar'; --ok goodone 'foo/bar'; --ok badone 'foo(bar'; --ok badone 'foo(?{1})bar'; -+ok goodone 'foo bar', 0; -+ok goodone 'foo/bar', 0; -+ok badone 'foo(bar', 0; - -+ok badone 'foo(?{1})bar'; -+ok badone 'foo(??{1})bar'; - ok badone '/foo(?{1})bar/'; -+ok badone '/foo(??{1})bar/'; - ok badone 'm!foo(?{1})bar!'; --# ok badone '/test//'; # removed for bug 4700 --ok goodone '.*'; -+ -+ok goodone '/test\//'; -+ok badone '/test//'; # removed for bug 4700 - and back from 7648 -+ok badone 'm!test!xyz!i'; -+ok badone '//'; -+ok badone 'm!|foo!'; -+ok goodone 'm!\|foo!'; -+ok badone 'm{bar||y}'; -+ -+ok goodone 'm{test}}'; # it's actually bad, but no way to parse this with simple code -+ok goodone 'm}test}}'; # it's actually bad, but no way to parse this with simple code -+ok goodone 'm{test{}'; # it's good even though perl warns unescaped { is deprecated -+ok goodone 'm}test{}'; -+ok goodone 'm{test.{0,10}}'; -+ok goodone 'm}test.{0,10}}'; -+ok goodone 'm[foo[bar]]'; -+ok badone 'm[foo[bar\]]'; -+ok goodone 'm(foo(?:bar)x)'; -+ok badone 'm(foo\(?:bar)x)'; -+ok goodone 'm/test # comment/x'; -+ok badone 'm/test # comm/ent/x'; # well you shouldn't use comments anyway -+ok goodone 'm[test # \] foo []x'; -+ -+ok goodone '.*', 0; - ok goodone 'm* 13; -+use Test::More tests => 12; - - # --------------------------------------------------------------------------- - - use strict; - require Mail::SpamAssassin; -- --my $sa = create_saobj({'dont_copy_prefs' => 1}); --$sa->init(0); --ok($sa); -+use Mail::SpamAssassin::Util qw(compile_regexp); - - sub is_caught { - my ($re) = @_; -- return $sa->{conf}->{parser}->is_always_matching_regexp($re, $re); -+ my ($rec, $err) = compile_regexp($re, 0, 1); -+ return !$rec; - } - - ok !is_caught 'foo|bar'; diff --git a/SOURCES/spamassassin-3.4.2-fix-CVE-2019-12420.patch b/SOURCES/spamassassin-3.4.2-fix-CVE-2019-12420.patch deleted file mode 100644 index 4c55b23..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-CVE-2019-12420.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff -urp Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Message.pm Mail-SpamAssassin-3.4.2.new/lib/Mail/SpamAssassin/Message.pm ---- Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Message.pm 2018-09-14 03:27:51.000000000 +0200 -+++ Mail-SpamAssassin-3.4.2.new/lib/Mail/SpamAssassin/Message.pm 2020-04-09 15:17:34.300986337 +0200 -@@ -876,6 +876,7 @@ sub _parse_multipart { - my $header; - my $part_array; - my $found_end_boundary; -+ my $partcnt = 0; - - my $line_count = @{$body}; - foreach ( @{$body} ) { -@@ -948,6 +949,13 @@ sub _parse_multipart { - } - } - -+ # Maximum parts to process -+ if (++$partcnt == 1000) { -+ dbg("message: mimepart limit exceeded, stopping parsing"); -+ $self->{'mimepart_limit_exceeded'} = 1; -+ return; -+ } -+ - # make sure we start with a new clean node - $in_body = 0; - $part_msg = Mail::SpamAssassin::Message::Node->new({ normalize=>$self->{normalize} }); diff --git a/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1930.patch b/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1930.patch deleted file mode 100644 index 6e196c5..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1930.patch +++ /dev/null @@ -1,41 +0,0 @@ -diff --git a/lib/Mail/SpamAssassin/Plugin/OneLineBodyRuleType.pm b/lib/Mail/SpamAssassin/Plugin/OneLineBodyRuleType.pm -index 2d931ea..7b6244e 100644 ---- a/lib/Mail/SpamAssassin/Plugin/OneLineBodyRuleType.pm -+++ b/lib/Mail/SpamAssassin/Plugin/OneLineBodyRuleType.pm -@@ -89,17 +89,19 @@ sub do_one_line_body_tests { - loop_body => sub - { - my ($self, $pms, $conf, $rulename, $pat, %opts) = @_; -- $pat = untaint_var($pat); -- my $sub; -+ my $sub = ' -+ my ($self, $line) = @_; -+ my $qrptr = $self->{main}->{conf}->{test_qrs}; -+ '; - - if (($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/) - { - # avoid [perl #86784] bug (fixed in 5.13.x), access the arg through ref -- $sub = ' -- my $lref = \$_[1]; -+ $sub .= ' -+ my $lref = \$line; - pos $$lref = 0; - '.$self->hash_line_for_rule($pms, $rulename).' -- while ($$lref =~ '.$pat.'g) { -+ while ($$lref =~ /$qrptr->{q{'.$rulename.'}}/go) { - my $self = $_[0]; - $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "one_line_body"); - '. $self->hit_rule_plugin_code($pms, $rulename, "one_line_body", -@@ -108,9 +110,9 @@ sub do_one_line_body_tests { - '; - - } else { -- $sub = ' -+ $sub .= ' - '.$self->hash_line_for_rule($pms, $rulename).' -- if ($_[1] =~ '.$pat.') { -+ if ($line =~ /$qrptr->{q{'.$rulename.'}}/o) { - my $self = $_[0]; - $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "one_line_body"); - '. $self->hit_rule_plugin_code($pms, $rulename, "one_line_body", "return 1") . ' diff --git a/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1931.patch b/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1931.patch deleted file mode 100644 index 70845f7..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-CVE-2020-1931.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Conf.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Conf.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Conf.pm 2020-06-15 19:10:21.700917582 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Conf.pm 2020-06-15 19:07:59.045897164 +0200 -@@ -3414,6 +3414,20 @@ internally, and should not be used. - setting => 'priority', - is_priv => 1, - type => $CONF_TYPE_HASH_KEY_VALUE, -+ code => sub { -+ my ($self, $key, $value, $line) = @_; -+ my ($rulename, $priority) = split(/\s+/, $value, 2); -+ unless (defined $priority) { -+ return $MISSING_REQUIRED_VALUE; -+ } -+ unless ($rulename =~ IS_RULENAME) { -+ return $INVALID_VALUE; -+ } -+ unless ($priority =~ /^-?\d+$/) { -+ return $INVALID_VALUE; -+ } -+ $self->{priority}->{$rulename} = $priority; -+ } - }); - - =back -diff -urp Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Constants.pm Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Constants.pm ---- Mail-SpamAssassin-3.4.2.old/lib/Mail/SpamAssassin/Constants.pm 2020-06-15 19:10:21.701917596 +0200 -+++ Mail-SpamAssassin-3.4.2/lib/Mail/SpamAssassin/Constants.pm 2020-06-15 19:07:59.045897164 +0200 -@@ -43,7 +43,7 @@ BEGIN { - HARVEST_DNSBL_PRIORITY MBX_SEPARATOR - MAX_BODY_LINE_LENGTH MAX_HEADER_KEY_LENGTH MAX_HEADER_VALUE_LENGTH - MAX_HEADER_LENGTH ARITH_EXPRESSION_LEXER AI_TIME_UNKNOWN -- CHARSETS_LIKELY_TO_FP_AS_CAPS MAX_URI_LENGTH RULENAME_RE -+ CHARSETS_LIKELY_TO_FP_AS_CAPS MAX_URI_LENGTH RULENAME_RE IS_RULENAME - ); - - %EXPORT_TAGS = ( -@@ -404,5 +404,7 @@ use constant CHARSETS_LIKELY_TO_FP_AS_CA - - # Allowed rulename format - use constant RULENAME_RE => qr([_a-zA-Z][_a-zA-Z0-9]{0,127}); -+# Exact match -+use constant IS_RULENAME => qr/^${\(RULENAME_RE)}$/; - - 1; diff --git a/SOURCES/spamassassin-3.4.2-fix-file-handle-leaks.patch b/SOURCES/spamassassin-3.4.2-fix-file-handle-leaks.patch deleted file mode 100644 index f0fd334..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-file-handle-leaks.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- a/spamc/spamc.c 2018/10/02 16:54:17 1842644 -+++ b/spamc/spamc.c 2018/10/02 17:40:43 1842645 -@@ -616,6 +616,7 @@ - fprintf(stderr,"Exceeded max line size (%d) in %s\n", - CONFIG_MAX_LINE_SIZE-2, config_file); - } -+ fclose(config); - return EX_CONFIG; - } - -@@ -629,6 +630,7 @@ - if (*combo_argc >= COMBO_ARGV_SIZE) { - fprintf(stderr,"Exceeded max number of arguments (%d) in %s\n", - COMBO_ARGV_SIZE, config_file); -+ fclose(config); - return EX_CONFIG; - } - combo_argv[*combo_argc] = strdup(tok); diff --git a/SOURCES/spamassassin-3.4.2-fix-rawbody-rules-documentation.patch b/SOURCES/spamassassin-3.4.2-fix-rawbody-rules-documentation.patch deleted file mode 100644 index b05329e..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-rawbody-rules-documentation.patch +++ /dev/null @@ -1,63 +0,0 @@ ---- a/lib/Mail/SpamAssassin/Conf.pm 2019/08/01 12:28:38 1864149 -+++ b/lib/Mail/SpamAssassin/Conf.pm 2019/08/08 08:11:36 1864686 -@@ -3066,12 +3066,19 @@ - as per the header tests, C<#> must be escaped (C<\#>) or else it is considered - the beginning of a comment. - --The 'body' in this case is the textual parts of the message body; --any non-text MIME parts are stripped, and the message decoded from --Quoted-Printable or Base-64-encoded format if necessary. The message --Subject header is considered part of the body and becomes the first --paragraph when running the rules. All HTML tags and line breaks will --be removed before matching. -+The 'body' in this case is the textual parts of the message body; any -+non-text MIME parts are stripped, and the message decoded from -+Quoted-Printable or Base-64-encoded format if necessary. Parts declared as -+text/html will be rendered from HTML to text. -+ -+All body paragraphs (double-newline-separated blocks text) are turned into a -+line breaks removed, whitespace normalized single line. Any lines longer -+than 2kB are split into shorter separate lines (from a boundary when -+possible), this may unexpectedly prevent pattern from matching. Patterns -+are matched independently against each of these lines. -+ -+Note that the message Subject header is considered part of the body and -+becomes the first line when running the rules. - - =item body SYMBOLIC_TEST_NAME eval:name_of_eval_method([args]) - -@@ -3152,6 +3159,10 @@ - tags and line breaks will still be present. Multiline expressions will - need to be used to match strings that are broken by line breaks. - -+Note that the text is split into 2-4kB chunks (from a word boundary when -+possible), this may unexpectedly prevent pattern from matching. Patterns -+are matched independently against each of these chunks. -+ - =item rawbody SYMBOLIC_TEST_NAME eval:name_of_eval_method([args]) - - Define a raw-body eval test. See above. ---- a/lib/Mail/SpamAssassin/PerMsgStatus.pm 2019/08/03 13:55:00 1864336 -+++ b/lib/Mail/SpamAssassin/PerMsgStatus.pm 2019/08/08 08:11:36 1864686 -@@ -1769,8 +1769,10 @@ - Returns the message body, with B or B encodings - decoded, and non-text parts or non-inline attachments stripped. - --It is returned as an array of strings, with each string representing --one newline-separated line of the body. -+This is the same result text as used in 'rawbody' rules. -+ -+It is returned as an array of strings, with each string being a 2-4kB chunk -+of the body, split from boundaries if possible. - - =cut - -@@ -1784,6 +1786,8 @@ - get_decoded_body_text_array()), with HTML rendered, and with whitespace - normalized. - -+This is the same result text as used in 'body' rules. -+ - It will always render text/html, and will use a heuristic to determine if other - text/* parts should be considered text/html. - diff --git a/SOURCES/spamassassin-3.4.2-fix-use-after-free.patch b/SOURCES/spamassassin-3.4.2-fix-use-after-free.patch deleted file mode 100644 index 8372d18..0000000 --- a/SOURCES/spamassassin-3.4.2-fix-use-after-free.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/spamc/getopt.c 2018/09/20 06:52:49 1841432 -+++ b/spamc/getopt.c 2018/09/20 07:18:53 1841433 -@@ -274,7 +274,6 @@ - } else if(longopts[i].has_arg == optional_argument) { - if(((spamc_optind < argc) && (argv[spamc_optind]) && (argv[spamc_optind][0] != '-')) || - (opt != NULL)) { -- free(opt); - if(opt != NULL) { - spamc_optarg = opt; - } else { diff --git a/SOURCES/spamassassin-cert_update.patch b/SOURCES/spamassassin-cert_update.patch new file mode 100644 index 0000000..2d0e68f --- /dev/null +++ b/SOURCES/spamassassin-cert_update.patch @@ -0,0 +1,108 @@ +commit be7b4d87d84f15446652569cd218db1ced7ae92a +Author: Henrik Krohns +Date: Fri Apr 10 07:47:37 2020 +0000 + + Bug 7807 - t/spamd_ssl.t fails due to small key size + + + git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1876347 13f79535-47bb-0310-9956-ffa450edef68 + +diff --git a/t/data/etc/testhost.cert b/t/data/etc/testhost.cert +index 86cff9aae..dca7c5a16 100644 +--- a/t/data/etc/testhost.cert ++++ b/t/data/etc/testhost.cert +@@ -1,23 +1,25 @@ + -----BEGIN CERTIFICATE----- +-MIIDyjCCAzOgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBpTELMAkGA1UEBhMCU0Ex +-CzAJBgNVBAgTAlNBMRowGAYDVQQHExFTcGFtQXNzYXNzaW4gQ2l0eTEVMBMGA1UE +-ChMMU3BhbUFzc2Fzc2luMRkwFwYDVQQLExBGT1IgVEVTVElORyBPTkxZMRIwEAYD +-VQQDEwlsb2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGHNwYW1hc3Nhc3NpbkBleGFt +-cGxlLmNvbTAeFw0wNDA3MDkyMTE4NDdaFw0yNDA3MTQyMTE4NDdaMIGlMQswCQYD +-VQQGEwJTQTELMAkGA1UECBMCU0ExGjAYBgNVBAcTEVNwYW1Bc3Nhc3NpbiBDaXR5 +-MRUwEwYDVQQKEwxTcGFtQXNzYXNzaW4xGTAXBgNVBAsTEEZPUiBURVNUSU5HIE9O +-TFkxEjAQBgNVBAMTCWxvY2FsaG9zdDEnMCUGCSqGSIb3DQEJARYYc3BhbWFzc2Fz +-c2luQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1l6To +-Fxkyc2glblqZYFq/BWFSMKh9LsouTVowuxC7yQkEENBjeQ4paHUFsi49s0RnabsA +-D1c56O8BBT0C89CMiCubRbv/KyHMDut5Nt0brWKS+VRWHgN9T5eYOy3wKPwbc7bm +-sPdgt5bOCS78F0luL5T6DPcf5mgE7XAv8qp+mwIDAQABo4IBBjCCAQIwHQYDVR0O +-BBYEFGJWfa4QHDeC9Wn3UNLIaf7prjmUMIHSBgNVHSMEgcowgceAFGJWfa4QHDeC +-9Wn3UNLIaf7prjmUoYGrpIGoMIGlMQswCQYDVQQGEwJTQTELMAkGA1UECBMCU0Ex +-GjAYBgNVBAcTEVNwYW1Bc3Nhc3NpbiBDaXR5MRUwEwYDVQQKEwxTcGFtQXNzYXNz +-aW4xGTAXBgNVBAsTEEZPUiBURVNUSU5HIE9OTFkxEjAQBgNVBAMTCWxvY2FsaG9z +-dDEnMCUGCSqGSIb3DQEJARYYc3BhbWFzc2Fzc2luQGV4YW1wbGUuY29tggEAMAwG +-A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAsKbOfgXZlBm1rQlM6W2Nxf5g +-r3QA67liZjVxhIb9chvreCttoe3IkHRSM4axToeKo+x7IV6ceXnqMc5chnu2lUmH +-2z7m3IYRYmIOaXroXZcfqX/P4HMw9NOphpEZ4yNsvKSl2n3h3bJ3ErqxMgghlTGD +-KYC+xKTFTsPjOE53Jh8= ++MIIELTCCAxWgAwIBAgIUCEYDt5yF4chKPuWTkkbpgLAZWycwDQYJKoZIhvcNAQEL ++BQAwgaUxCzAJBgNVBAYTAlNBMQswCQYDVQQIDAJTQTEaMBgGA1UEBwwRU3BhbUFz ++c2Fzc2luIENpdHkxFTATBgNVBAoMDFNwYW1Bc3Nhc3NpbjEZMBcGA1UECwwQRk9S ++IFRFU1RJTkcgT05MWTESMBAGA1UEAwwJbG9jYWxob3N0MScwJQYJKoZIhvcNAQkB ++FhhzcGFtYXNzYXNzaW5AZXhhbXBsZS5jb20wHhcNMjAwNDEwMDczMTA3WhcNMzAw ++NDA4MDczMTA3WjCBpTELMAkGA1UEBhMCU0ExCzAJBgNVBAgMAlNBMRowGAYDVQQH ++DBFTcGFtQXNzYXNzaW4gQ2l0eTEVMBMGA1UECgwMU3BhbUFzc2Fzc2luMRkwFwYD ++VQQLDBBGT1IgVEVTVElORyBPTkxZMRIwEAYDVQQDDAlsb2NhbGhvc3QxJzAlBgkq ++hkiG9w0BCQEWGHNwYW1hc3Nhc3NpbkBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN ++AQEBBQADggEPADCCAQoCggEBAK+IP5AWcdwiGG33Tt9pAjQ/tWnqhoUoSGHJMq9n ++hV3GI6CyqbbJXN/VFDC27F9qltoZp2eSEigRvLgnrjmnJFJ3H34CcKghx3TfEFdf ++ILCKPXvYSIe7fpeD5+0LN2t+0Kr56vejyGkQAiGDs7FU9rPOHENolU8SvRv102M0 ++K65sZU940ML43ns3JCg1AGGBQj3LabqlxHM+zBAYHFLMd9/fMzKfaFgvyPvZIlIv ++zhzdBksBrl2Wa8RPdJUtWhDGbYMEAHPreZQmiqdtfBbOc946LYYrDTaCAPaAYzUd ++WOqOU9grNcWjIuiIlUjcCPdRe1njDSerfsMdcXUjGroGj90CAwEAAaNTMFEwHQYD ++VR0OBBYEFP8lys2m3PDCcYXieVxpOBaBUX/eMB8GA1UdIwQYMBaAFP8lys2m3PDC ++cYXieVxpOBaBUX/eMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB ++AAuVryADs+3mv4mLLzWPmjBLSzlemqXqZPu7OPe8GrzD3BkovudWErI/nW7OTWcJ ++JixVeL5IgJ/ddDZXIaLM6c73NqcVpOQ+E/vnkbHHJtYTPJ8aSl5DWFd7Pid8JV2M ++gHcNFXStCWQ2XN3iXliDnb7LBI2DphCIBBKGajbP3mlVcVwZstqqyoqRn8wshlrJ ++j7FlrmDF2f62huGBKgfgC4lov95B7TH45hjUnIHaemgVw9VLyZHqw0jQ7Fd14069 ++RMHuzP2DGi6+uJ4rS8pnDGYXHh4Exyl3TXGLAoWFjCmh3TWGRqwbs/VWC6TrbUSe ++mmPZfIFNtvb9bh/uatmTIFU= + -----END CERTIFICATE----- +diff --git a/t/data/etc/testhost.key b/t/data/etc/testhost.key +index 018e89089..e7fc5711a 100644 +--- a/t/data/etc/testhost.key ++++ b/t/data/etc/testhost.key +@@ -1,15 +1,28 @@ +------BEGIN RSA PRIVATE KEY----- +-MIICXQIBAAKBgQC1l6ToFxkyc2glblqZYFq/BWFSMKh9LsouTVowuxC7yQkEENBj +-eQ4paHUFsi49s0RnabsAD1c56O8BBT0C89CMiCubRbv/KyHMDut5Nt0brWKS+VRW +-HgN9T5eYOy3wKPwbc7bmsPdgt5bOCS78F0luL5T6DPcf5mgE7XAv8qp+mwIDAQAB +-AoGAXyirQvAvxQ9TCSJuGaezhhxkpnRXJtppGqBwXc75ct5jehzxht79+9cAU87O +-ioixlNuEIgDn/bHB5TAAi9aGduwz9hsv4NpNQ89/z7tmRKq43QFpf9hFHxZ3Q4e9 +-Hmey7ZsJ3/TpqFNfhH0IkLy6fA5+iClss3b2/6BbqqL8drkCQQDb9TSAJW56LnY2 +-aRtImgEyoeg2iEgV98SuDvNNUpTYvG0pkFolFlhb49w5KCmKqOtPA+etc/K2vRJf +-8zkfz/BnAkEA01kVZoojg/am0K184Qfotxr7+t8Rjxn4k4g6Eq7ryth/Uicffixx +-9E9sp2aadEW+Lt5pao+BsIwxxtLhphkPrQJBALQi1KQ+A4Q8mMNmwNlshp6Yyjwe +-0Cpth72ksM0aXJxDdIMdnTXSXvqiWbDVm2/bdxp28D21P4k4vtulrZrICPECQQDP +-RzhQ57wrSXWThgLK5KndIy7sfWhIM81yTBHAjyOcPraoSMx/P3HHjTnf+CYVUO1U +-JDe5FeO41dZfzCUYdM3FAkA8IXYe74FVG9VM9yzZYbZHaS17TCyztkqagIuUBfDe +-g0frip4VCEHvh+qDk678GZ1tMJ30utuTx6JgXJ5HeuFi +------END RSA PRIVATE KEY----- ++-----BEGIN PRIVATE KEY----- ++MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCviD+QFnHcIhht ++907faQI0P7Vp6oaFKEhhyTKvZ4VdxiOgsqm2yVzf1RQwtuxfapbaGadnkhIoEby4 ++J645pyRSdx9+AnCoIcd03xBXXyCwij172EiHu36Xg+ftCzdrftCq+er3o8hpEAIh ++g7OxVPazzhxDaJVPEr0b9dNjNCuubGVPeNDC+N57NyQoNQBhgUI9y2m6pcRzPswQ ++GBxSzHff3zMyn2hYL8j72SJSL84c3QZLAa5dlmvET3SVLVoQxm2DBABz63mUJoqn ++bXwWznPeOi2GKw02ggD2gGM1HVjqjlPYKzXFoyLoiJVI3Aj3UXtZ4w0nq37DHXF1 ++Ixq6Bo/dAgMBAAECggEAGrgqtMw12MR344VZxDg/1aQ9ATpp76sKGZh+1qRzcKrQ ++kY2gt+o+8POGd0PhB0U6YUi2atWZXfAVQhMIOk5vAP6RFKVacL1zlx69Zg9c8oKY ++iVmK8N5hjr7Ndw/WB87uBJi2Y6vSvetp56AcoknEo+oj8z7gO4EmuDLbUqaGX65v ++08wyrSoWYAUPDhXvOclReBITDkWuGzhd5IDv3kVmf9oizOO6gbKxh5speFKA91Yg ++WMuBbi7hWB41Nn1ycSKfAx9W7kvBhxA35G/QKupXoTPoNNw5Z08zpg2tiqGlrVFS ++xs5SagfBjxqF2I44y/xH9xv3WuYNPSsQDZANxat+AQKBgQDVbWWryhas9AyyQQdr ++uJRCpBf6Gyzd/FwUnb3370YhK/mVogAIbyT2TwTliXjPYTccvDzP9mWnmH2QVBfG ++e5NW7A3zYwCrculucdKL+PZ58PF/LuCzQUuSlZ72fhqIPHXi3Acipjk6NDLntI/4 ++OF6HO1ZwNqL7tsf2bFjarOjIbQKBgQDSi7+hI6rg/8ovt9UrJYZEv5bR8wHsZRB3 ++4uHvCHHkbNlvZiorAsaVO8w0EoSqT3tsxHTz6l1TskxQhznjru+/mn5QDVu4gHMh ++BFewZ+PRZKxqx61Y1zlvRSHLWM1uXxVcCrBCIC3fFb50oluiCVcI+OE7qXPgX6zN ++Iqd4XB8fMQKBgBT1nVp1iiyJ+wU2ZagqhETXIJOrP4i9U72ikBldIiH305biReaO ++I68cq3pvObID9gmYYtB5fhorW4lk+MDBOgL7Q5N3UYWEz2/NNcQROwNWqiC3VdHE ++w8stbmL+l1Rxu5H/0aORd2VD4wvmK41Q29ej+CUk+HSHyrNkiIVRHbsxAoGBALlq ++rskG8y6QtQsO9uC3fCZBH14nhrMbUcRyfwwVwM8seNQdAde86wtzjOLyx/iAZK6l ++2GUSKTxTEdf9LqLvZV20fbab/QFfxYP3Ig5Nf5jg8uJalLNfXso59Ify3X9spw/W ++reCeHCeRK5flf5MUbJyPKTN0iIK5XyGMejWJ0zKRAoGAc2lrUlT64htPj1//kcgl ++ILzQo51ujjzjOlHYanvN6ekMSfj3Q1ZCTJUp2ArVA64w7Jt16cEEEFCaP4+i9UaW ++aM9/8tho4J29QSllAyxfUIF9i5gcElTfzRhpNGMVrnV/ynyOaTXzUkk7L6FH9soq ++JS3kXA6oQN5NkW6AF/3X314= ++-----END PRIVATE KEY----- diff --git a/SOURCES/spamassassin-disable_tests_for_deleted_modules.patch b/SOURCES/spamassassin-disable_tests_for_deleted_modules.patch new file mode 100644 index 0000000..0a890ba --- /dev/null +++ b/SOURCES/spamassassin-disable_tests_for_deleted_modules.patch @@ -0,0 +1,40 @@ +diff --git a/t/all_modules.t b/t/all_modules.t +index 855395f..a976012 100755 +--- a/t/all_modules.t ++++ b/t/all_modules.t +@@ -12,15 +12,7 @@ plan tests => 5; + + my $plugins = ''; + +-if (eval { require BSD::Resource; }) { +- $plugins .= "loadplugin Mail::SpamAssassin::Plugin::ResourceLimits\n" +-} +-if (eval { require Net::CIDR::Lite; }) { +- $plugins .= "loadplugin Mail::SpamAssassin::Plugin::URILocalBL\n"; +-} +- + tstpre (" +-loadplugin Mail::SpamAssassin::Plugin::RelayCountry + loadplugin Mail::SpamAssassin::Plugin::URIDNSBL + loadplugin Mail::SpamAssassin::Plugin::Hashcash + loadplugin Mail::SpamAssassin::Plugin::SPF +diff --git a/t/data/01_test_rules.pre b/t/data/01_test_rules.pre +index c4681ca..1694ddf 100644 +--- a/t/data/01_test_rules.pre ++++ b/t/data/01_test_rules.pre +@@ -21,7 +21,6 @@ loadplugin Mail::SpamAssassin::Plugin::WLBLEval + loadplugin Mail::SpamAssassin::Plugin::VBounce + + # Try to load some non-default plugins also +-loadplugin Mail::SpamAssassin::Plugin::RelayCountry + loadplugin Mail::SpamAssassin::Plugin::DCC + loadplugin Mail::SpamAssassin::Plugin::AntiVirus + loadplugin Mail::SpamAssassin::Plugin::AWL +@@ -32,7 +31,6 @@ loadplugin Mail::SpamAssassin::Plugin::Shortcircuit + loadplugin Mail::SpamAssassin::Plugin::ASN + #loadplugin Mail::SpamAssassin::Plugin::PhishTag + #loadplugin Mail::SpamAssassin::Plugin::TxRep +-loadplugin Mail::SpamAssassin::Plugin::URILocalBL + loadplugin Mail::SpamAssassin::Plugin::PDFInfo + loadplugin Mail::SpamAssassin::Plugin::HashBL + #loadplugin Mail::SpamAssassin::Plugin::ResourceLimits diff --git a/SOURCES/spamassassin-sql92_syntax.patch b/SOURCES/spamassassin-sql92_syntax.patch new file mode 100644 index 0000000..298b192 --- /dev/null +++ b/SOURCES/spamassassin-sql92_syntax.patch @@ -0,0 +1,354 @@ +diff --git a/lib/Mail/SpamAssassin/AutoWhitelist.pm b/lib/Mail/SpamAssassin/AutoWhitelist.pm +index 627e249..cc3c97b 100644 +--- a/lib/Mail/SpamAssassin/AutoWhitelist.pm ++++ b/lib/Mail/SpamAssassin/AutoWhitelist.pm +@@ -128,35 +128,35 @@ sub check_address { + my $entry = $self->{checker}->get_addr_entry ($fulladdr, $signedby); + $self->{entry} = $entry; + +- if (!$entry->{msgcount}) { ++ if (!$entry->{count}) { + # no entry found + if (defined $origip) { + # try upgrading a default entry (probably from "add-addr-to-foo") + my $noipaddr = $self->pack_addr ($addr, undef); + my $noipent = $self->{checker}->get_addr_entry ($noipaddr, undef); + +- if (defined $noipent->{msgcount} && $noipent->{msgcount} > 0) { ++ if (defined $noipent->{count} && $noipent->{count} > 0) { + dbg("auto-whitelist: found entry w/o IP address for $addr: replacing with $origip"); + $self->{checker}->remove_entry($noipent); + # Now assign proper entry the count and totscore values of the + # no-IP entry instead of assigning the whole value to avoid + # wiping out any information added to the previous entry. +- $entry->{msgcount} = $noipent->{msgcount}; ++ $entry->{count} = $noipent->{count}; + $entry->{totscore} = $noipent->{totscore}; + } + } + } + +- if ($entry->{msgcount} < 0 || +- $entry->{msgcount} != $entry->{msgcount} || # test for NaN ++ if ($entry->{count} < 0 || ++ $entry->{count} != $entry->{count} || # test for NaN + $entry->{totscore} != $entry->{totscore}) + { + warn "auto-whitelist: resetting bad data for ($addr, $origip), ". +- "count: $entry->{msgcount}, totscore: $entry->{totscore}\n"; +- $entry->{msgcount} = $entry->{totscore} = 0; ++ "count: $entry->{count}, totscore: $entry->{totscore}\n"; ++ $entry->{count} = $entry->{totscore} = 0; + } + +- return !$entry->{msgcount} ? undef : $entry->{totscore} / $entry->{msgcount}; ++ return !$entry->{count} ? undef : $entry->{totscore} / $entry->{count}; + } + + ########################################################################### +@@ -170,7 +170,7 @@ whitelist correction. + + sub count { + my $self = shift; +- return $self->{entry}->{msgcount}; ++ return $self->{entry}->{count}; + } + + +@@ -195,7 +195,7 @@ sub add_score { + return; # don't try to add a NaN + } + +- $self->{entry}->{msgcount} ||= 0; ++ $self->{entry}->{count} ||= 0; + $self->{checker}->add_score($self->{entry}, $score); + } + +diff --git a/lib/Mail/SpamAssassin/DBBasedAddrList.pm b/lib/Mail/SpamAssassin/DBBasedAddrList.pm +index 6aaed86..a1adca3 100644 +--- a/lib/Mail/SpamAssassin/DBBasedAddrList.pm ++++ b/lib/Mail/SpamAssassin/DBBasedAddrList.pm +@@ -125,10 +125,10 @@ sub get_addr_entry { + addr => $addr, + }; + +- $entry->{msgcount} = $self->{accum}->{$addr} || 0; ++ $entry->{count} = $self->{accum}->{$addr} || 0; + $entry->{totscore} = $self->{accum}->{$addr.'|totscore'} || 0; + +- dbg("auto-whitelist: db-based $addr scores ".$entry->{msgcount}.'/'.$entry->{totscore}); ++ dbg("auto-whitelist: db-based $addr scores ".$entry->{count}.'/'.$entry->{totscore}); + return $entry; + } + +@@ -137,15 +137,15 @@ sub get_addr_entry { + sub add_score { + my($self, $entry, $score) = @_; + +- $entry->{msgcount} ||= 0; ++ $entry->{count} ||= 0; + $entry->{addr} ||= ''; + +- $entry->{msgcount}++; ++ $entry->{count}++; + $entry->{totscore} += $score; + +- dbg("auto-whitelist: add_score: new count: ".$entry->{msgcount}.", new totscore: ".$entry->{totscore}); ++ dbg("auto-whitelist: add_score: new count: ".$entry->{count}.", new totscore: ".$entry->{totscore}); + +- $self->{accum}->{$entry->{addr}} = $entry->{msgcount}; ++ $self->{accum}->{$entry->{addr}} = $entry->{count}; + $self->{accum}->{$entry->{addr}.'|totscore'} = $entry->{totscore}; + return $entry; + } +diff --git a/lib/Mail/SpamAssassin/Plugin/TxRep.pm b/lib/Mail/SpamAssassin/Plugin/TxRep.pm +index 2ef3ddc..08b432b 100644 +--- a/lib/Mail/SpamAssassin/Plugin/TxRep.pm ++++ b/lib/Mail/SpamAssassin/Plugin/TxRep.pm +@@ -1521,7 +1521,7 @@ sub check_reputation { + #-------------------------------------------------------------------------- + + ########################################################################### +-sub count {my $self=shift; return (defined $self->{checker})? $self->{entry}->{msgcount} : undef;} ++sub count {my $self=shift; return (defined $self->{checker})? $self->{entry}->{count} : undef;} + sub total {my $self=shift; return (defined $self->{checker})? $self->{entry}->{totscore} : undef;} + ########################################################################### + +@@ -1538,11 +1538,11 @@ sub get_sender { + $self->{entry} = $entry; + $origip = $origip || 'none'; + +- if ($entry->{msgcount}<0 || $entry->{msgcount}=~/^(nan|)$/ || $entry->{totscore}=~/^(nan|)$/) { +- warn "TxRep: resetting bad data for ($addr, $origip), count: $entry->{msgcount}, totscore: $entry->{totscore}\n"; +- $self->{entry}->{msgcount} = $self->{entry}->{totscore} = 0; ++ if ($entry->{count}<0 || $entry->{count}=~/^(nan|)$/ || $entry->{totscore}=~/^(nan|)$/) { ++ warn "TxRep: resetting bad data for ($addr, $origip), count: $entry->{count}, totscore: $entry->{totscore}\n"; ++ $self->{entry}->{count} = $self->{entry}->{totscore} = 0; + } +- return $self->{entry}->{msgcount}; ++ return $self->{entry}->{count}; + } + + +@@ -1557,7 +1557,7 @@ sub add_score { + warn "TxRep: attempt to add a $score to TxRep entry ignored\n"; + return; # don't try to add a NaN + } +- $self->{entry}->{msgcount} ||= 0; ++ $self->{entry}->{count} ||= 0; + + # performing the dilution aging correction + if (defined $self->total() && defined $self->count() && defined $self->{txrep_dilution_factor}) { +@@ -1587,9 +1587,9 @@ sub remove_score { + } + # no reversal dilution aging correction (not easily possible), + # just removing the original message score +- if ($self->{entry}->{msgcount} > 2) +- {$self->{entry}->{msgcount} -= 2;} +- else {$self->{entry}->{msgcount} = 0;} ++ if ($self->{entry}->{count} > 2) ++ {$self->{entry}->{count} -= 2;} ++ else {$self->{entry}->{count} = 0;} + # subtract 2, and add a score; hence decrementing by 1 + $self->{checker}->add_score($self->{entry}, -1*$score); + } +diff --git a/lib/Mail/SpamAssassin/SQLBasedAddrList.pm b/lib/Mail/SpamAssassin/SQLBasedAddrList.pm +index 278f792..f3f40b4 100644 +--- a/lib/Mail/SpamAssassin/SQLBasedAddrList.pm ++++ b/lib/Mail/SpamAssassin/SQLBasedAddrList.pm +@@ -45,7 +45,7 @@ CREATE TABLE awl ( + username varchar(100) NOT NULL default '', + email varchar(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount int(11) NOT NULL default '0', ++ count int(11) NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + PRIMARY KEY (username,email,signedby,ip) +@@ -191,7 +191,7 @@ sub get_addr_entry { + + my $entry = { addr => $addr, + exists_p => 0, +- msgcount => 0, ++ count => 0, + totscore => 0, + signedby => $signedby, + }; +@@ -200,7 +200,7 @@ sub get_addr_entry { + + return $entry unless $email ne '' && (defined $ip || defined $signedby); + +- my $sql = "SELECT msgcount, totscore FROM $self->{tablename} " . ++ my $sql = "SELECT count, totscore FROM $self->{tablename} " . + "WHERE username = ? AND email = ?"; + my @args = ( $email ); + if (!$self->{_with_awl_signer}) { +@@ -225,7 +225,7 @@ sub get_addr_entry { + if (!$rc) { # there was an error, but try to go on + info("auto-whitelist: sql-based get_addr_entry %s: SQL error: %s", + join('|',@args), $sth->errstr); +- $entry->{msgcount} = 0; ++ $entry->{count} = 0; + $entry->{totscore} = 0; + } + else { +@@ -234,8 +234,8 @@ sub get_addr_entry { + # how to combine data if there are several entries (like signed by + # an author domain and by a remailer)? for now just take an average + while ( defined($aryref = $sth->fetchrow_arrayref()) ) { +- if (defined $entry->{msgcount} && defined $aryref->[1]) { +- $entry->{msgcount} = $aryref->[0]; ++ if (defined $entry->{count} && defined $aryref->[1]) { ++ $entry->{count} = $aryref->[0]; + $entry->{totscore} = $aryref->[1]; + } + $entry->{exists_p} = 1; +@@ -247,8 +247,8 @@ sub get_addr_entry { + } + $sth->finish(); + +- dbg("auto-whitelist: sql-based %s scores %s, msgcount %s", +- join('|',@args), $entry->{totscore}, $entry->{msgcount}); ++ dbg("auto-whitelist: sql-based %s scores %s, count %s", ++ join('|',@args), $entry->{totscore}, $entry->{count}); + + return $entry; + } +@@ -275,7 +275,7 @@ sub add_score { + + my ($email, $ip) = $self->_unpack_addr($entry->{addr}); + +- $entry->{msgcount} += 1; ++ $entry->{count} += 1; + $entry->{totscore} += $score; + my $signedby = $entry->{signedby}; + +@@ -286,7 +286,7 @@ sub add_score { + + my $inserted = 0; + +- { my @fields = qw(username email ip msgcount totscore); ++ { my @fields = qw(username email ip count totscore); + my @signedby; + if ($self->{_with_awl_signer}) { + push(@fields, 'signedby'); +@@ -327,9 +327,9 @@ sub add_score { + # insert failed, assume primary key constraint, so try the update + + my $sql = "UPDATE $self->{tablename} ". +- "SET msgcount = ?, totscore = totscore + ? ". ++ "SET count = ?, totscore = totscore + ? ". + "WHERE username = ? AND email = ?"; +- my(@args) = ($entry->{msgcount}, $score, $self->{_username}, $email); ++ my(@args) = ($entry->{count}, $score, $self->{_username}, $email); + if ($self->{_with_awl_signer}) { + my @signedby = !defined $signedby ? () : split(' ', lc $signedby); + if (!@signedby) { +@@ -352,8 +352,8 @@ sub add_score { + join('|',@args), $sth->errstr); + } else { + dbg("auto-whitelist: sql-based add_score/update ". +- "new msgcount: %s, new totscore: %s for %s", +- $entry->{msgcount}, $entry->{totscore}, join('|',@args)); ++ "new count: %s, new totscore: %s for %s", ++ $entry->{count}, $entry->{totscore}, join('|',@args)); + $entry->{exists_p} = 1; + } + } +diff --git a/sql/README.awl b/sql/README.awl +index 68de4a1..a1ddf40 100644 +--- a/sql/README.awl ++++ b/sql/README.awl +@@ -75,7 +75,7 @@ setting: "awl") with at least these fields: + username varchar(100) # this is the username whose e-mail is being filtered + email varchar(200) # this is the address key + ip varchar(40) # this is the ip key (fits IPv4 or IPv6) +- msgcount int(11) # this is the message counter ++ count int(11) # this is the message counter + totscore float # this is the total calculated score + signedby varchar(255) # a DKIM or DomainKeys signing domain(s) + +@@ -109,7 +109,7 @@ CREATE TABLE awl ( + username varchar(100) NOT NULL default '', + email varchar(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount int(11) NOT NULL default '0', ++ count int(11) NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + PRIMARY KEY (username,email,signedby,ip) +diff --git a/sql/awl_mysql.sql b/sql/awl_mysql.sql +index a8b6926..0bfa99a 100644 +--- a/sql/awl_mysql.sql ++++ b/sql/awl_mysql.sql +@@ -2,7 +2,7 @@ CREATE TABLE awl ( + username varchar(100) NOT NULL default '', + email varbinary(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount int(11) NOT NULL default '0', ++ count int(11) NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + last_hit timestamp NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +diff --git a/sql/awl_pg.sql b/sql/awl_pg.sql +index 2cb3f3e..4f59d72 100644 +--- a/sql/awl_pg.sql ++++ b/sql/awl_pg.sql +@@ -2,7 +2,7 @@ CREATE TABLE awl ( + username varchar(100) NOT NULL default '', + email varchar(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount bigint NOT NULL default '0', ++ count bigint NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + last_hit timestamp NOT NULL default CURRENT_TIMESTAMP, +diff --git a/sql/txrep_mysql.sql b/sql/txrep_mysql.sql +index 9a4888b..bbe6b95 100644 +--- a/sql/txrep_mysql.sql ++++ b/sql/txrep_mysql.sql +@@ -2,7 +2,7 @@ CREATE TABLE txrep ( + username varchar(100) NOT NULL default '', + email varchar(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount int(11) NOT NULL default '0', ++ count int(11) NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + last_hit timestamp NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +diff --git a/sql/txrep_pg.sql b/sql/txrep_pg.sql +index 191074c..463006b 100644 +--- a/sql/txrep_pg.sql ++++ b/sql/txrep_pg.sql +@@ -2,7 +2,7 @@ CREATE TABLE txrep ( + username varchar(100) NOT NULL default '', + email varchar(255) NOT NULL default '', + ip varchar(40) NOT NULL default '', +- msgcount bigint NOT NULL default '0', ++ count bigint NOT NULL default '0', + totscore float NOT NULL default '0', + signedby varchar(255) NOT NULL default '', + last_hit timestamp NOT NULL default CURRENT_TIMESTAMP, +diff --git a/UPGRADE b/UPGRADE +index cfd31ab..b555b0b 100644 +--- a/UPGRADE ++++ b/UPGRADE +@@ -24,18 +24,6 @@ Note for Users Upgrading to SpamAssassin 3.4.3 + This is to make sure all the legacy installations and wiki guides etc + still using it needlessly get fixed. + +-- TxRep and Awl plugins has been modified to be compatible +- with latest Postgresql versions. +- You should upgrade your sql database running the following command: +- MySQL: +- "ALTER TABLE `txrep` CHANGE `count` `msgcount` INT(11) NOT NULL DEFAULT '0';" +- "ALTER TABLE `awl` CHANGE `count` `msgcount` INT(11) NOT NULL DEFAULT '0';" +- "ALTER TABLE `awl` ADD last_hit timestamp NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;" +- PostgreSQL: +- "ALTER TABLE txrep RENAME COLUMN count TO msgcount;" +- "ALTER TABLE awl RENAME COLUMN count TO msgcount;" +- "ALTER TABLE awl ADD last_hit timestamp NOT NULL default CURRENT_TIMESTAMP;" +- + - body_part_scan_size 50000, rawbody_part_scan_size 500000 defaults added (Bug 6582) + These enable safer and faster scanning of large emails. + diff --git a/SPECS/spamassassin.spec b/SPECS/spamassassin.spec index cd80952..9b22160 100644 --- a/SPECS/spamassassin.spec +++ b/SPECS/spamassassin.spec @@ -53,20 +53,20 @@ %define real_name Mail-SpamAssassin %{!?perl_vendorlib: %define perl_vendorlib %(eval "`%{__perl} -V:installvendorlib`"; echo $installvendorlib)} -%global saversion 3.004002 +%global saversion 3.004004 #%%global prerev rc2 Summary: Spam filter for email which can be invoked from mail delivery agents Name: spamassassin -Version: 3.4.2 +Version: 3.4.4 #Release: 0.8.%%{prerev}%%{?dist} -Release: 10%{?dist} +Release: 3%{?dist} License: ASL 2.0 Group: Applications/Internet URL: https://spamassassin.apache.org/ Source0: https://www.apache.org/dist/%{name}/source/%{real_name}-%{version}.tar.bz2 #Source0: %%{real_name}-%%{version}-%%{prerev}.tar.bz2 -Source1: https://www.apache.org/dist/%{name}/source/%{real_name}-rules-%{version}.r1840640.tgz +Source1: https://www.apache.org/dist/%{name}/source/%{real_name}-rules-%{version}.r1873061.tgz #Source1: %%{real_name}-rules-%%{version}.%%{prerev}.tgz Source2: redhat_local.cf Source3: spamassassin-default.rc @@ -94,15 +94,14 @@ Patch1: spamassassin-3.4.1-add-logfile-homedir-options.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1607372 Patch2: spamassassin-3.4.2-drop-geoip.patch Patch3: 0001-Drop-the-ResourceLimits-plugin.patch +%if 0%{?rhel} <= 8 +Patch4: spamassassin-sql92_syntax.patch +%endif +Patch5: spamassassin-disable_tests_for_deleted_modules.patch # Patches 100+ are SVN backports (DO NOT REUSE!) -Patch100: spamassassin-3.4.2-fix-use-after-free.patch -Patch101: spamassassin-3.4.2-fix-file-handle-leaks.patch -Patch102: spamassassin-3.4.2-fix-rawbody-rules-documentation.patch -Patch103: spamassassin-3.4.2-fix-CVE-2019-12420.patch -Patch104: spamassassin-3.4.2-fix-CVE-2018-11805.patch -Patch105: spamassassin-3.4.2-fix-CVE-2020-1930.patch -Patch106: spamassassin-3.4.2-fix-CVE-2020-1931.patch + +Patch100: spamassassin-cert_update.patch # end of patches @@ -120,16 +119,44 @@ BuildRequires: perl(Time::HiRes) BuildRequires: perl(HTML::Parser) BuildRequires: perl(NetAddr::IP) BuildRequires: openssl-devel + + %if %{use_systemd} BuildRequires: systemd-units %endif +# BuildRequires for test suite +BuildRequires: perl(Test::Builder) +BuildRequires: perl(Test) perl(Test::More) +BuildRequires: perl(Net::DNS::Nameserver) +BuildRequires: perl(DB_File) +BuildRequires: perl(DBI) perl(DBD::mysql) +BuildRequires: perl(Mail::DKIM) +BuildRequires: perl(LWP::UserAgent) +BuildRequires: perl(Archive::Zip) +BuildRequires: perl(IO::String) +buildrequires: wget + +%if 0%{?fedora} +BuildRequires: perl(Devel::Cycle) +BuildRequires: perl(Net::Patricia) +%endif + +BuildRequires: perl(Mail::SPF) +BuildRequires: perl(IO::Socket::INET6) +BuildRequires: perl(IO::Socket::SSL) +BuildRequires: perl(Encode::Detect::Detector) + + + + Requires: perl(HTTP::Date) Requires: perl(LWP::UserAgent) Requires: perl(Net::DNS) Requires: perl(Time::HiRes) Requires: perl(DB_File) Requires: perl(Mail::SPF) +Requires: perl(IO::String) %if %{require_encode_detect} Requires: perl(Encode::Detect) %endif @@ -205,21 +232,22 @@ To filter spam for all users, add that line to /etc/procmailrc %patch0 -p1 %patch1 -p1 %patch2 -p1 +rm -f lib/Mail/SpamAssassin/Plugin/RelayCountry.pm +rm -f lib/Mail/SpamAssassin/Plugin/URILocalBL.pm # Drop the ResourceLimits plugin - it requires perl(BSD::Resource) # which is not in RHEL-8 %patch3 -p1 rm -f lib/Mail/SpamAssassin/Plugin/ResourceLimits.pm +# Reverting this in thel8 to not complicate update process +# https://github.com/apache/spamassassin/commit/f7d39bfd3ef2e5ef4a9de480764ee1d73be9349 +%if 0%{?rhel} == 8 +%patch4 -p1 +%endif +%patch5 -p1 # Patches 100+ are SVN backports (DO NOT REUSE!) %patch100 -p1 -%patch101 -p1 -%patch102 -p1 -%patch103 -p1 -%patch104 -p1 -%patch105 -p1 -%patch106 -p1 - # end of patches echo "RHEL=%{?rhel} FEDORA=%{?fedora}" @@ -308,6 +336,10 @@ install -m 0644 %{SOURCE13} $RPM_BUILD_DIR/Mail-SpamAssassin-%{version}/ mkdir -m 0700 -p $RPM_BUILD_ROOT%{_sharedstatedir}/razor/ %endif +%check +rm -f t/relaycountry* t/urilocal* +make disttest + %files -f %{name}-%{version}-filelist %doc LICENSE NOTICE CREDITS Changes README TRADEMARK UPGRADE %doc USAGE sample-nonspam.txt sample-spam.txt @@ -402,6 +434,13 @@ exit 0 %endif %changelog +* Tue Oct 6 2020 Pavel Zhukov - 3.4.4-3 +- Add tests BRs + +* Tue Oct 6 2020 Pavel Zhukov - 3.4.4-2 +- New version v3.4.4 +- Enable tests + * Mon Jun 15 2020 Ondřej Lysoněk - 3.4.2-10 - Fixed CVE-2018-11805 - Resolves: rhbz#1787514